Skip to content

Commit

Permalink
feat(user): add email verification recipe (#482)
Browse files Browse the repository at this point in the history
  • Loading branch information
dipendraupreti committed Aug 30, 2023
1 parent 61530ef commit 3d24b17
Show file tree
Hide file tree
Showing 12 changed files with 151 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const emailPasswordSignUpPOST = (
throw new Error("Should never come here");
}

if (fastify.config.user.features?.signUp === false) {
if (fastify.config.user.features?.signUp?.enabled === false) {
throw {
name: "SIGN_UP_DISABLED",
message: "SignUp feature is currently disabled",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const thirdPartySignInUp = (
input.userContext
);

if (!thirdPartyUser && config.user.features?.signUp === false) {
if (!thirdPartyUser && config.user.features?.signUp?.enabled === false) {
throw {
name: "SIGN_UP_DISABLED",
message: "SignUp feature is currently disabled",
Expand Down
4 changes: 4 additions & 0 deletions packages/user/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ const ROUTE_ME = "/me";
const ROUTE_USERS = "/users";
const TABLE_USERS = "users";

// Email verification
const EMAIL_VERIFICATION_MODE = "REQUIRED";

export {
EMAIL_VERIFICATION_MODE,
INVITATION_ACCEPT_PATH,
INVITATION_EXPIRE_AFTER_IN_DAYS,
RESET_PASSWORD_PATH,
Expand Down
16 changes: 14 additions & 2 deletions packages/user/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,16 @@ declare module "@dzangolab/fastify-config" {
name?: string;
};
features?: {
signUp?: boolean;
signUp?: {
/**
* @default true
*/
enabled?: boolean;
/**
* @default false
*/
emailVerification?: boolean;
};
};
role?: string;
};
Expand All @@ -60,7 +69,10 @@ export { default as validatePassword } from "./validator/password";

export * from "./constants";

export type { ThirdPartyEmailPasswordRecipe } from "./supertokens/types";
export type {
EmailVerificationRecipe,
ThirdPartyEmailPasswordRecipe,
} from "./supertokens/types";
export type {
AuthUser,
ChangePasswordInput,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import emailVerification from "supertokens-node/recipe/emailverification";

import getOrigin from "../../../../lib/getOrigin";
import sendEmail from "../../../../lib/sendEmail";

import type { FastifyInstance, FastifyRequest } from "fastify";
import type { EmailDeliveryInterface } from "supertokens-node/lib/build/ingredients/emaildelivery/types";
import type { TypeEmailVerificationEmailDeliveryInput } from "supertokens-node/recipe/emailverification/types";

const sendEmailVerificationEmail = (
originalImplementation: EmailDeliveryInterface<TypeEmailVerificationEmailDeliveryInput>,
fastify: FastifyInstance
): typeof emailVerification.sendEmail => {
const websiteDomain = fastify.config.appOrigin[0] as string;

return async (input) => {
const request: FastifyRequest = input.userContext._default.request.request;

const url =
request.headers.referer || request.headers.origin || request.hostname;

const origin = getOrigin(url) || websiteDomain;

const emailVerifyLink = input.emailVerifyLink.replace(
websiteDomain,
origin
);

sendEmail({
fastify,
subject: "Email Verification",
templateName: "email-verification",
to: input.user.email,
templateData: {
emailVerifyLink,
},
});
};
};

export default sendEmailVerificationEmail;
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import sendEmailVerificationEmail from "./email-verification/sendEmailVerificationEmail";
import { EMAIL_VERIFICATION_MODE } from "../../../constants";

import type {
EmailVerificationSendEmailWrapper as SendEmailWrapper,
EmailVerificationRecipe,
} from "../../types";
import type { FastifyInstance } from "fastify";
import type { TypeInput as EmailVerificationRecipeConfig } from "supertokens-node/recipe/emailverification/types";

const getEmailVerificationRecipeConfig = (
fastify: FastifyInstance
): EmailVerificationRecipeConfig => {
const { config } = fastify;

let emailVerification: EmailVerificationRecipe = {};

if (typeof config.user.supertokens.recipes?.emailVerification === "object") {
emailVerification = config.user.supertokens.recipes.emailVerification;
}

return {
mode: emailVerification?.mode || EMAIL_VERIFICATION_MODE,
emailDelivery: {
override: (originalImplementation) => {
let sendEmailConfig: SendEmailWrapper | undefined;

if (emailVerification?.sendEmail) {
sendEmailConfig = emailVerification.sendEmail;
}

return {
...originalImplementation,
sendEmail: sendEmailConfig
? sendEmailConfig(originalImplementation, fastify)
: sendEmailVerificationEmail(originalImplementation, fastify),
};
},
},
};
};

export default getEmailVerificationRecipeConfig;
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const emailPasswordSignUpPOST = (
throw new Error("Should never come here");
}

if (fastify.config.user.features?.signUp === false) {
if (fastify.config.user.features?.signUp?.enabled === false) {
throw {
name: "SIGN_UP_DISABLED",
message: "SignUp feature is currently disabled",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const thirdPartySignInUp = (
input.userContext
);

if (!thirdPartyUser && config.user.features?.signUp === false) {
if (!thirdPartyUser && config.user.features?.signUp?.enabled === false) {
throw {
name: "SIGN_UP_DISABLED",
message: "SignUp feature is currently disabled",
Expand Down
5 changes: 5 additions & 0 deletions packages/user/src/supertokens/recipes/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import initEmailVerificationRecipe from "./initEmailVerificationRecipe";
import initSessionRecipe from "./initSessionRecipe";
import initThirdPartyEmailPassword from "./initThirdPartyEmailPasswordRecipe";
import initUserRolesRecipe from "./initUserRolesRecipe";
Expand All @@ -12,6 +13,10 @@ const getRecipeList = (fastify: FastifyInstance): RecipeListFunction[] => {
initUserRolesRecipe(fastify),
];

if (fastify.config.user.features?.signUp?.emailVerification) {
recipeList.push(initEmailVerificationRecipe(fastify));
}

return recipeList;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import EmailVerification from "supertokens-node/recipe/emailverification";

import getEmailVerificationRecipeConfig from "./config/emailVerificationRecipeConfig";

import type { SupertokensRecipes } from "../types";
import type { FastifyInstance } from "fastify";

const init = (fastify: FastifyInstance) => {
const emailVerification: SupertokensRecipes["emailVerification"] =
fastify.config.user.supertokens.recipes?.emailVerification;

if (typeof emailVerification === "function") {
return EmailVerification.init(emailVerification(fastify));
}

return EmailVerification.init(getEmailVerificationRecipeConfig(fastify));
};

export default init;
20 changes: 20 additions & 0 deletions packages/user/src/supertokens/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import EmailVerification from "supertokens-node/recipe/emailverification";
import ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword";

import type { FastifyInstance } from "fastify";
import type { EmailDeliveryInterface } from "supertokens-node/lib/build/ingredients/emaildelivery/types";
import type { TypeEmailPasswordPasswordResetEmailDeliveryInput } from "supertokens-node/lib/build/recipe/emailpassword/types";
import type {
TypeInput as EmailVerificationRecipeConfig,
TypeEmailVerificationEmailDeliveryInput,
} from "supertokens-node/recipe/emailverification/types";
import type { TypeInput as SessionRecipeConfig } from "supertokens-node/recipe/session/types";
import type {
TypeInput as ThirdPartyEmailPasswordRecipeConfig,
Expand All @@ -26,6 +31,11 @@ type SendEmailWrapper = (
fastify: FastifyInstance
) => typeof ThirdPartyEmailPassword.sendEmail;

type EmailVerificationSendEmailWrapper = (
originalImplementation: EmailDeliveryInterface<TypeEmailVerificationEmailDeliveryInput>,
fastify: FastifyInstance
) => typeof EmailVerification.sendEmail;

type RecipeInterfaceWrapper = {
[key in keyof RecipeInterface]?: (
originalImplementation: RecipeInterface,
Expand All @@ -34,6 +44,9 @@ type RecipeInterfaceWrapper = {
};

interface SupertokensRecipes {
emailVerification?:
| EmailVerificationRecipe
| ((fastify: FastifyInstance) => EmailVerificationRecipeConfig);
session?: (fastify: FastifyInstance) => SessionRecipeConfig;
userRoles?: (fastify: FastifyInstance) => UserRolesRecipeConfig;
thirdPartyEmailPassword?:
Expand All @@ -48,6 +61,11 @@ interface SupertokensThirdPartyProvider {
google?: Parameters<typeof Google>[0];
}

interface EmailVerificationRecipe {
mode?: "REQUIRED" | "OPTIONAL";
sendEmail?: EmailVerificationSendEmailWrapper;
}

interface ThirdPartyEmailPasswordRecipe {
override?: {
apis?: APIInterfaceWrapper;
Expand All @@ -67,6 +85,8 @@ interface SupertokensConfig {

export type {
APIInterfaceWrapper,
EmailVerificationRecipe,
EmailVerificationSendEmailWrapper,
RecipeInterfaceWrapper,
SendEmailWrapper,
SupertokensConfig,
Expand Down
1 change: 1 addition & 0 deletions packages/user/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export default defineConfig(({ mode }) => {
slonik: "Slonik",
"supertokens-node": "SupertokensNode",
"supertokens-node/framework/fastify": "SupertokensFastify",
"supertokens-node/recipe/emailverification": "EmailVerification",
"supertokens-node/recipe/session/framework/fastify":
"SupertokensSessionFastify",
"supertokens-node/recipe/session": "SupertokensSession",
Expand Down

0 comments on commit 3d24b17

Please sign in to comment.