ndom91 / briefkasten

📮 Self hosted bookmarking app
https://briefkastenhq.com
MIT License
879 stars 45 forks source link

Whitelisting of possible signups #36

Open hechi opened 1 year ago

hechi commented 1 year ago

Clear and concise description of the problem

Hey Nico, first of all thx a lot for briefkasten and your contribution in so many project :heart: it's really nice. I really envy your frontend as well as backend skill :sweat_smile: anyway...

I played around with briefkasten and one thing which i miss is to not allow everybody to sign up on my instance :sweat_smile: ex. i have email verify activated (easiest one for me) and i would like to give it to my family members and some friends but not the whole world. Same applies if i would use one of the Oauth Providers (like github). I tried to search on next-auth.js but they also don't have an option for it.


I wish i could have a list of allowed user/domain's to my instance, so that i can provide access to some people around me but not everybody.

Suggested solution

Before sending the login option to next-auth.js check if a domain or usernames are in the whitelist :shrug:

Alternative

Hardcoded ENV with domainnames would be totally enough i think

ALLOWED_EMAIL_SIGNUP=*example.com,hans@gmx.de

Additional context

No response

Validations

ndom91 commented 1 year ago

One quick option would be to limit Google OAuth logins to internal domain members only. This only works in the case that you're using it for a family/company domain. So for example, if you set it up under a G Suite / Google Worksapce domain company.com. Then you can check the box in the Client ID setup for "internal only" and it'll only allow users with google accounts *@company.com.

Obviously this is a rather limited use-case..

For the email provider, you could theoretically return early from the createVerificationRequest method. This is the callback next-auth gives you to modify the Email it generates to send out to potential sign-ups. So what you cuold do it take the built-in behavior of that, and just wrap it to see if the domains you want are included in the email or not and if not just don't send the email.

See: https://next-auth.js.org/providers/email#customizing-emails

For example:

import { createTransport } from "nodemailer"

const wantedEmails = process.env.ALLOWED_EMAIL_DOMAINS?.split(' ')
// This is the space separated ALLOWED_EMAILS you'd suggested.
// For example: 
// ALLOWED_EMAIL_DOMAINS=gmx.de gmail.com company.de

async function sendVerificationRequest(params) {
  const { identifier, url, provider, theme } = params
  const emailDomain = identifier.split("@")[1]

  if (wantedEmails.includes(emailDomain)) {
    const { host } = new URL(url)
    const transport = createTransport(provider.server)
    const result = await transport.sendMail({
      to: identifier,
      from: provider.from,
      subject: `Sign in to ${host}`,
      text: text({ url, host }),
      html: html({ url, host, theme }),
    })
    const failed = result.rejected.concat(result.pending).filter(Boolean)
    if (failed.length) {
      throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`)
    }
  }
}
hechi commented 1 year ago

Thanks a lot for your detailed answer :) The google solution sounds interesting but not all of them have google yet :D

I agree its probably a very limited use-case but the customizing-email solution looks very interesting. If i would try my luck would here be the best place for it or would you see it somewhere else?

Thanks again :blush:

ndom91 commented 1 year ago

Thanks a lot for your detailed answer :) The google solution sounds interesting but not all of them have google yet :D

I agree its probably a very limited use-case but the customizing-email solution looks very interesting. If i would try my luck would here be the best place for it or would you see it somewhere else?

Thanks again blush

Exactly, so there you could do something like:

const wantedEmails = process.env.ALLOWED_EMAIL_DOMAINS?.split(' ') ?? []
// This is the space separated ALLOWED_EMAILS you'd suggested.
// For example: 
// ALLOWED_EMAIL_DOMAINS=gmx.de gmail.com company.de

...

if (process.env.SMTP_SERVER && process.env.SMTP_FROM) {
  providers.push(
    EmailProvider({
      server: process.env.SMTP_SERVER,
      from: process.env.SMTP_FROM,
      sendVerificationRequest: async ({
        identifier: email,
        url,
        provider: { server, from },
        theme,
      }) => {
        const emailDomain = email.split("@")[1];

        if (wantedEmails.includes(emailDomain)) {
          const { host } = new URL(url);
          const transport = createTransport(server);
          const result = await transport.sendMail({
            to: email,
            from: from,
            subject: `Sign in to ${host}`,
            text: text({ url, host }),
            html: html({ url, host, theme }),
          });
          const failed = result.rejected.concat(result.pending).filter(Boolean);
          if (failed.length) {
            throw new Error(
              `Email(s) (${failed.join(", ")}) could not be sent`
            );
          }
        }
      },
    })
  );
}