UNPKG

better-auth

Version:

The most comprehensive authentication framework for TypeScript.

1 lines • 17.5 kB
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/magic-link/index.ts"],"sourcesContent":["import type {\n\tAwaitable,\n\tBetterAuthPlugin,\n\tGenericEndpointContext,\n} from \"@better-auth/core\";\nimport { createAuthEndpoint } from \"@better-auth/core/api\";\nimport * as z from \"zod\";\nimport { originCheck } from \"../../api\";\nimport { setSessionCookie } from \"../../cookies\";\nimport { generateRandomString } from \"../../crypto\";\nimport { defaultKeyHasher } from \"./utils\";\n\nexport interface MagicLinkOptions {\n\t/**\n\t * Time in seconds until the magic link expires.\n\t * @default (60 * 5) // 5 minutes\n\t */\n\texpiresIn?: number | undefined;\n\t/**\n\t * Send magic link implementation.\n\t */\n\tsendMagicLink: (\n\t\tdata: {\n\t\t\temail: string;\n\t\t\turl: string;\n\t\t\ttoken: string;\n\t\t},\n\t\tctx?: GenericEndpointContext | undefined,\n\t) => Awaitable<void>;\n\t/**\n\t * Disable sign up if user is not found.\n\t *\n\t * @default false\n\t */\n\tdisableSignUp?: boolean | undefined;\n\t/**\n\t * Rate limit configuration.\n\t *\n\t * @default {\n\t * window: 60,\n\t * max: 5,\n\t * }\n\t */\n\trateLimit?:\n\t\t| {\n\t\t\t\twindow: number;\n\t\t\t\tmax: number;\n\t\t }\n\t\t| undefined;\n\t/**\n\t * Custom function to generate a token\n\t */\n\tgenerateToken?: ((email: string) => Awaitable<string>) | undefined;\n\n\t/**\n\t * This option allows you to configure how the token is stored in your database.\n\t * Note: This will not affect the token that's sent, it will only affect the token stored in your database.\n\t *\n\t * @default \"plain\"\n\t */\n\tstoreToken?:\n\t\t| (\n\t\t\t\t| \"plain\"\n\t\t\t\t| \"hashed\"\n\t\t\t\t| { type: \"custom-hasher\"; hash: (token: string) => Promise<string> }\n\t\t )\n\t\t| undefined;\n}\n\nconst signInMagicLinkBodySchema = z.object({\n\temail: z.email().meta({\n\t\tdescription: \"Email address to send the magic link\",\n\t}),\n\tname: z\n\t\t.string()\n\t\t.meta({\n\t\t\tdescription:\n\t\t\t\t'User display name. Only used if the user is registering for the first time. Eg: \"my-name\"',\n\t\t})\n\t\t.optional(),\n\tcallbackURL: z\n\t\t.string()\n\t\t.meta({\n\t\t\tdescription: \"URL to redirect after magic link verification\",\n\t\t})\n\t\t.optional(),\n\tnewUserCallbackURL: z\n\t\t.string()\n\t\t.meta({\n\t\t\tdescription:\n\t\t\t\t\"URL to redirect after new user signup. Only used if the user is registering for the first time.\",\n\t\t})\n\t\t.optional(),\n\terrorCallbackURL: z\n\t\t.string()\n\t\t.meta({\n\t\t\tdescription: \"URL to redirect after error.\",\n\t\t})\n\t\t.optional(),\n});\nconst magicLinkVerifyQuerySchema = z.object({\n\ttoken: z.string().meta({\n\t\tdescription: \"Verification token\",\n\t}),\n\tcallbackURL: z\n\t\t.string()\n\t\t.meta({\n\t\t\tdescription:\n\t\t\t\t'URL to redirect after magic link verification, if not provided the user will be redirected to the root URL. Eg: \"/dashboard\"',\n\t\t})\n\t\t.optional(),\n\terrorCallbackURL: z\n\t\t.string()\n\t\t.meta({\n\t\t\tdescription: \"URL to redirect after error.\",\n\t\t})\n\t\t.optional(),\n\tnewUserCallbackURL: z\n\t\t.string()\n\t\t.meta({\n\t\t\tdescription:\n\t\t\t\t\"URL to redirect after new user signup. Only used if the user is registering for the first time.\",\n\t\t})\n\t\t.optional(),\n});\nexport const magicLink = (options: MagicLinkOptions) => {\n\tconst opts = {\n\t\tstoreToken: \"plain\",\n\t\t...options,\n\t} satisfies MagicLinkOptions;\n\n\tasync function storeToken(ctx: GenericEndpointContext, token: string) {\n\t\tif (opts.storeToken === \"hashed\") {\n\t\t\treturn await defaultKeyHasher(token);\n\t\t}\n\t\tif (\n\t\t\ttypeof opts.storeToken === \"object\" &&\n\t\t\t\"type\" in opts.storeToken &&\n\t\t\topts.storeToken.type === \"custom-hasher\"\n\t\t) {\n\t\t\treturn await opts.storeToken.hash(token);\n\t\t}\n\t\treturn token;\n\t}\n\n\treturn {\n\t\tid: \"magic-link\",\n\t\tendpoints: {\n\t\t\t/**\n\t\t\t * ### Endpoint\n\t\t\t *\n\t\t\t * POST `/sign-in/magic-link`\n\t\t\t *\n\t\t\t * ### API Methods\n\t\t\t *\n\t\t\t * **server:**\n\t\t\t * `auth.api.signInMagicLink`\n\t\t\t *\n\t\t\t * **client:**\n\t\t\t * `authClient.signIn.magicLink`\n\t\t\t *\n\t\t\t * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/sign-in#api-method-sign-in-magic-link)\n\t\t\t */\n\t\t\tsignInMagicLink: createAuthEndpoint(\n\t\t\t\t\"/sign-in/magic-link\",\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\trequireHeaders: true,\n\t\t\t\t\tbody: signInMagicLinkBodySchema,\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\topenapi: {\n\t\t\t\t\t\t\toperationId: \"signInWithMagicLink\",\n\t\t\t\t\t\t\tdescription: \"Sign in with magic link\",\n\t\t\t\t\t\t\tresponses: {\n\t\t\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\t\t\tstatus: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tasync (ctx) => {\n\t\t\t\t\tconst { email } = ctx.body;\n\n\t\t\t\t\tconst verificationToken = opts?.generateToken\n\t\t\t\t\t\t? await opts.generateToken(email)\n\t\t\t\t\t\t: generateRandomString(32, \"a-z\", \"A-Z\");\n\t\t\t\t\tconst storedToken = await storeToken(ctx, verificationToken);\n\t\t\t\t\tawait ctx.context.internalAdapter.createVerificationValue({\n\t\t\t\t\t\tidentifier: storedToken,\n\t\t\t\t\t\tvalue: JSON.stringify({ email, name: ctx.body.name }),\n\t\t\t\t\t\texpiresAt: new Date(Date.now() + (opts.expiresIn || 60 * 5) * 1000),\n\t\t\t\t\t});\n\t\t\t\t\tconst realBaseURL = new URL(ctx.context.baseURL);\n\t\t\t\t\tconst pathname =\n\t\t\t\t\t\trealBaseURL.pathname === \"/\" ? \"\" : realBaseURL.pathname;\n\t\t\t\t\tconst basePath = pathname ? \"\" : ctx.context.options.basePath || \"\";\n\t\t\t\t\tconst url = new URL(\n\t\t\t\t\t\t`${pathname}${basePath}/magic-link/verify`,\n\t\t\t\t\t\trealBaseURL.origin,\n\t\t\t\t\t);\n\t\t\t\t\turl.searchParams.set(\"token\", verificationToken);\n\t\t\t\t\turl.searchParams.set(\"callbackURL\", ctx.body.callbackURL || \"/\");\n\t\t\t\t\tif (ctx.body.newUserCallbackURL) {\n\t\t\t\t\t\turl.searchParams.set(\n\t\t\t\t\t\t\t\"newUserCallbackURL\",\n\t\t\t\t\t\t\tctx.body.newUserCallbackURL,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (ctx.body.errorCallbackURL) {\n\t\t\t\t\t\turl.searchParams.set(\"errorCallbackURL\", ctx.body.errorCallbackURL);\n\t\t\t\t\t}\n\t\t\t\t\tawait options.sendMagicLink(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\temail,\n\t\t\t\t\t\t\turl: url.toString(),\n\t\t\t\t\t\t\ttoken: verificationToken,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tctx,\n\t\t\t\t\t);\n\t\t\t\t\treturn ctx.json({\n\t\t\t\t\t\tstatus: true,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t),\n\t\t\t/**\n\t\t\t * ### Endpoint\n\t\t\t *\n\t\t\t * GET `/magic-link/verify`\n\t\t\t *\n\t\t\t * ### API Methods\n\t\t\t *\n\t\t\t * **server:**\n\t\t\t * `auth.api.magicLinkVerify`\n\t\t\t *\n\t\t\t * **client:**\n\t\t\t * `authClient.magicLink.verify`\n\t\t\t *\n\t\t\t * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/magic-link#api-method-magic-link-verify)\n\t\t\t */\n\t\t\tmagicLinkVerify: createAuthEndpoint(\n\t\t\t\t\"/magic-link/verify\",\n\t\t\t\t{\n\t\t\t\t\tmethod: \"GET\",\n\t\t\t\t\tquery: magicLinkVerifyQuerySchema,\n\t\t\t\t\tuse: [\n\t\t\t\t\t\toriginCheck((ctx) => {\n\t\t\t\t\t\t\treturn ctx.query.callbackURL\n\t\t\t\t\t\t\t\t? decodeURIComponent(ctx.query.callbackURL)\n\t\t\t\t\t\t\t\t: \"/\";\n\t\t\t\t\t\t}),\n\t\t\t\t\t\toriginCheck((ctx) => {\n\t\t\t\t\t\t\treturn ctx.query.newUserCallbackURL\n\t\t\t\t\t\t\t\t? decodeURIComponent(ctx.query.newUserCallbackURL)\n\t\t\t\t\t\t\t\t: \"/\";\n\t\t\t\t\t\t}),\n\t\t\t\t\t\toriginCheck((ctx) => {\n\t\t\t\t\t\t\treturn ctx.query.errorCallbackURL\n\t\t\t\t\t\t\t\t? decodeURIComponent(ctx.query.errorCallbackURL)\n\t\t\t\t\t\t\t\t: \"/\";\n\t\t\t\t\t\t}),\n\t\t\t\t\t],\n\t\t\t\t\trequireHeaders: true,\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\topenapi: {\n\t\t\t\t\t\t\toperationId: \"verifyMagicLink\",\n\t\t\t\t\t\t\tdescription: \"Verify magic link\",\n\t\t\t\t\t\t\tresponses: {\n\t\t\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\t\t\tsession: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t$ref: \"#/components/schemas/Session\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t$ref: \"#/components/schemas/User\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tasync (ctx) => {\n\t\t\t\t\tconst token = ctx.query.token;\n\t\t\t\t\t// If the first argument provides the origin, it will ignore the second argument of `new URL`.\n\t\t\t\t\t// new URL(\"http://localhost:3001/hello\", \"http://localhost:3000\").toString()\n\t\t\t\t\t// Returns http://localhost:3001/hello\n\t\t\t\t\tconst callbackURL = new URL(\n\t\t\t\t\t\tctx.query.callbackURL\n\t\t\t\t\t\t\t? decodeURIComponent(ctx.query.callbackURL)\n\t\t\t\t\t\t\t: \"/\",\n\t\t\t\t\t\tctx.context.baseURL,\n\t\t\t\t\t).toString();\n\t\t\t\t\tconst errorCallbackURL = new URL(\n\t\t\t\t\t\tctx.query.errorCallbackURL\n\t\t\t\t\t\t\t? decodeURIComponent(ctx.query.errorCallbackURL)\n\t\t\t\t\t\t\t: callbackURL,\n\t\t\t\t\t\tctx.context.baseURL,\n\t\t\t\t\t);\n\n\t\t\t\t\tfunction redirectWithError(error: string): never {\n\t\t\t\t\t\terrorCallbackURL.searchParams.set(\"error\", error);\n\t\t\t\t\t\tthrow ctx.redirect(errorCallbackURL.toString());\n\t\t\t\t\t}\n\n\t\t\t\t\tconst newUserCallbackURL = new URL(\n\t\t\t\t\t\tctx.query.newUserCallbackURL\n\t\t\t\t\t\t\t? decodeURIComponent(ctx.query.newUserCallbackURL)\n\t\t\t\t\t\t\t: callbackURL,\n\t\t\t\t\t\tctx.context.baseURL,\n\t\t\t\t\t).toString();\n\t\t\t\t\tconst storedToken = await storeToken(ctx, token);\n\t\t\t\t\tconst tokenValue =\n\t\t\t\t\t\tawait ctx.context.internalAdapter.findVerificationValue(\n\t\t\t\t\t\t\tstoredToken,\n\t\t\t\t\t\t);\n\t\t\t\t\tif (!tokenValue) {\n\t\t\t\t\t\tredirectWithError(\"INVALID_TOKEN\");\n\t\t\t\t\t}\n\t\t\t\t\tif (tokenValue.expiresAt < new Date()) {\n\t\t\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationValue(\n\t\t\t\t\t\t\ttokenValue.id,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tredirectWithError(\"EXPIRED_TOKEN\");\n\t\t\t\t\t}\n\t\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationValue(\n\t\t\t\t\t\ttokenValue.id,\n\t\t\t\t\t);\n\t\t\t\t\tconst { email, name } = JSON.parse(tokenValue.value) as {\n\t\t\t\t\t\temail: string;\n\t\t\t\t\t\tname?: string | undefined;\n\t\t\t\t\t};\n\t\t\t\t\tlet isNewUser = false;\n\t\t\t\t\tlet user = await ctx.context.internalAdapter\n\t\t\t\t\t\t.findUserByEmail(email)\n\t\t\t\t\t\t.then((res) => res?.user);\n\n\t\t\t\t\tif (!user) {\n\t\t\t\t\t\tif (!opts.disableSignUp) {\n\t\t\t\t\t\t\tconst newUser = await ctx.context.internalAdapter.createUser({\n\t\t\t\t\t\t\t\temail: email,\n\t\t\t\t\t\t\t\temailVerified: true,\n\t\t\t\t\t\t\t\tname: name || \"\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tisNewUser = true;\n\t\t\t\t\t\t\tuser = newUser;\n\t\t\t\t\t\t\tif (!user) {\n\t\t\t\t\t\t\t\tredirectWithError(\"failed_to_create_user\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tredirectWithError(\"new_user_signup_disabled\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!user.emailVerified) {\n\t\t\t\t\t\tuser = await ctx.context.internalAdapter.updateUser(user.id, {\n\t\t\t\t\t\t\temailVerified: true,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\tconst session = await ctx.context.internalAdapter.createSession(\n\t\t\t\t\t\tuser.id,\n\t\t\t\t\t);\n\n\t\t\t\t\tif (!session) {\n\t\t\t\t\t\tredirectWithError(\"failed_to_create_session\");\n\t\t\t\t\t}\n\n\t\t\t\t\tawait setSessionCookie(ctx, {\n\t\t\t\t\t\tsession,\n\t\t\t\t\t\tuser,\n\t\t\t\t\t});\n\t\t\t\t\tif (!ctx.query.callbackURL) {\n\t\t\t\t\t\treturn ctx.json({\n\t\t\t\t\t\t\ttoken: session.token,\n\t\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\t\tid: user.id,\n\t\t\t\t\t\t\t\temail: user.email,\n\t\t\t\t\t\t\t\temailVerified: user.emailVerified,\n\t\t\t\t\t\t\t\tname: user.name,\n\t\t\t\t\t\t\t\timage: user.image,\n\t\t\t\t\t\t\t\tcreatedAt: user.createdAt,\n\t\t\t\t\t\t\t\tupdatedAt: user.updatedAt,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tif (isNewUser) {\n\t\t\t\t\t\tthrow ctx.redirect(newUserCallbackURL);\n\t\t\t\t\t}\n\t\t\t\t\tthrow ctx.redirect(callbackURL);\n\t\t\t\t},\n\t\t\t),\n\t\t},\n\t\trateLimit: [\n\t\t\t{\n\t\t\t\tpathMatcher(path) {\n\t\t\t\t\treturn (\n\t\t\t\t\t\tpath.startsWith(\"/sign-in/magic-link\") ||\n\t\t\t\t\t\tpath.startsWith(\"/magic-link/verify\")\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t\twindow: opts.rateLimit?.window || 60,\n\t\t\t\tmax: opts.rateLimit?.max || 5,\n\t\t\t},\n\t\t],\n\t\toptions,\n\t} satisfies BetterAuthPlugin;\n};\n"],"mappings":"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"}