Restrict full site for members only?

Hey!

Im building a blog for family and friends and would like the site to be only accessible when users login. Preferrably some logic applied centrally, so no need to change each and every page template. As well, I do like a modal like solution where the site content would be in a blurred background and the ootb member portal login UI in the front.

Considered a couple of solutions, but they all come with limitations/drawbacks:

  1. Private site ootb feature - sharing 1 password to be used by all users comes with security risks and in addition users would afterwards have to create an account to stay updated by mail - not an ideal UX
  2. Private posts ootb feature - solves the problem only partly, as the home page, featured images and post titles are still visible
  3. Custom modal pop-up in theme - currently added a custom modal with some hbs/css/js in the theme, triggered from the default.hbs using {{#unless @member}} as conditon and then a link to the ootb member portal #/portal/signin it works, but hacky and a bit of a funny UX

Can you think of a better (more elegant) way to restrict access to the site using the member login?

I think that (1) really gets you closest to what you’re looking for most easily.

  1. If you can edit the theme, you can change the rendering to check if someone is a member and only show the page if they are. Look at modifying default.hbs, as a way to get it applied everywhere easily.
    HOWEVER, note that the Content API will still give access to any posts not marked members-only.

3 - see #2, and:

You probably don’t really want to load the site content and then blur it, if you care about privacy, since that means the site content would be in the source code. Better to load some faked blurred content, and to never load the content. (Load and overwrite with javascript is easily overcome.)

1 Like

I use Ghost this way myself :)

Most of the themes don’t really support the use case, even if a site is set to invite only, there is still some teaser content.

I used the private posts OOTB and made the site invite-only. Then chose the theme I liked best, forked it, and customised it to hide all the bits of content I didn’t want behind {{#if @member}}

So basically a slightly different approach to Option 2/3.

This allowed me to customise what users who weren’t logged in could see, so that I could guide people to login.

It’s a little bit involved to customise the theme that way, but I don’t think it’s much more work than a custom modal-popup and it solves the problem fully.

2 Likes

Thanks a lot both!

I ended up combining some of your and my own ideas and arrived at the following:

  1. Restrict content at the Post level by setting them to Private
  2. Setting the Portal to members on invite only
  3. Adjusted default.hbs to render the site body only for rembers, if not trigger a script that loads the portal pop-up
  4. Created a separate file for the portal pop-up script (assets/js/portal-popup.js). It contains logic that it will reload when a users tries to click it away and is not yet logged in.

let me know if you see anything weird or room for improvement :smiley:

default.hbs adjustments:

<div class="site-content">
        {{#if @member}}
            {{{body}}}
        {{else}}
            <script src="{{asset "js/portal-popup.js"}}"></script>
        {{/if}}
    </div>

portal-popup.js script:

async function isMember() {
    try {
        const response = await fetch(`${location.origin}/members/api/member/`);
        return response.status === 200; // Returns true if the user is a member
    } catch (error) {
        console.error('Error checking member status:', error);
        return false;
    }
}

async function openPopup() {
    const member = await isMember();

    if (!member) {
        // Create or find the portal trigger link
        let portalLink = document.querySelector('a.gh-portal-trigger');
        if (!portalLink) {
            portalLink = document.createElement('a');
            portalLink.setAttribute('href', '#/portal');
            portalLink.classList.add('gh-portal-trigger');
            portalLink.style.display = 'none'; // Keep it hidden
            document.body.appendChild(portalLink);
        }

        // Trigger the portal popup
        portalLink.click();
    }
}

document.addEventListener('DOMContentLoaded', async function () {
    await openPopup();

    // Continuously check if the popup should be reopened
    setInterval(async function () {
        const member = await isMember();
        if (!member) {
            openPopup();
        }
    }, 100); // Check every 100ms
});