better-auth
Version:
The most comprehensive authentication framework for TypeScript.
1 lines • 9.88 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/anonymous/index.ts"],"sourcesContent":["import type { BetterAuthPlugin } from \"@better-auth/core\";\nimport {\n\tcreateAuthEndpoint,\n\tcreateAuthMiddleware,\n} from \"@better-auth/core/api\";\nimport { generateId } from \"@better-auth/core/utils\";\nimport * as z from \"zod\";\nimport { APIError, getSessionFromCtx } from \"../../api\";\nimport { parseSetCookieHeader, setSessionCookie } from \"../../cookies\";\nimport { mergeSchema } from \"../../db/schema\";\nimport { ANONYMOUS_ERROR_CODES } from \"./error-codes\";\nimport { schema } from \"./schema\";\nimport type { AnonymousOptions } from \"./types\";\n\nasync function getAnonUserEmail(\n\toptions: AnonymousOptions | undefined,\n): Promise<string> {\n\tconst customEmail = await options?.generateRandomEmail?.();\n\tif (customEmail) {\n\t\tconst validation = z.email().safeParse(customEmail);\n\t\tif (!validation.success) {\n\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\tmessage: ANONYMOUS_ERROR_CODES.INVALID_EMAIL_FORMAT,\n\t\t\t});\n\t\t}\n\t\treturn customEmail;\n\t}\n\n\tconst id = generateId();\n\tif (options?.emailDomainName) {\n\t\treturn `temp-${id}@${options.emailDomainName}`;\n\t}\n\n\treturn `temp@${id}.com`;\n}\n\nexport const anonymous = (options?: AnonymousOptions | undefined) => {\n\treturn {\n\t\tid: \"anonymous\",\n\t\tendpoints: {\n\t\t\tsignInAnonymous: createAuthEndpoint(\n\t\t\t\t\"/sign-in/anonymous\",\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\topenapi: {\n\t\t\t\t\t\t\tdescription: \"Sign in anonymously\",\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: \"Sign in anonymously\",\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\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\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},\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\t// If the current request already has a valid anonymous session, we should\n\t\t\t\t\t// reject any further attempts to create another anonymous user. This\n\t\t\t\t\t// prevents an anonymous user from signing in anonymously again while they\n\t\t\t\t\t// are already authenticated.\n\t\t\t\t\tconst existingSession = await getSessionFromCtx<{\n\t\t\t\t\t\tisAnonymous: boolean;\n\t\t\t\t\t}>(ctx, { disableRefresh: true });\n\t\t\t\t\tif (existingSession?.user.isAnonymous) {\n\t\t\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\tANONYMOUS_ERROR_CODES.ANONYMOUS_USERS_CANNOT_SIGN_IN_AGAIN_ANONYMOUSLY,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\tconst email = await getAnonUserEmail(options);\n\t\t\t\t\tconst name = (await options?.generateName?.(ctx)) || \"Anonymous\";\n\t\t\t\t\tconst newUser = await ctx.context.internalAdapter.createUser({\n\t\t\t\t\t\temail,\n\t\t\t\t\t\temailVerified: false,\n\t\t\t\t\t\tisAnonymous: true,\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tcreatedAt: new Date(),\n\t\t\t\t\t\tupdatedAt: new Date(),\n\t\t\t\t\t});\n\t\t\t\t\tif (!newUser) {\n\t\t\t\t\t\tthrow ctx.error(\"INTERNAL_SERVER_ERROR\", {\n\t\t\t\t\t\t\tmessage: ANONYMOUS_ERROR_CODES.FAILED_TO_CREATE_USER,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tconst session = await ctx.context.internalAdapter.createSession(\n\t\t\t\t\t\tnewUser.id,\n\t\t\t\t\t);\n\t\t\t\t\tif (!session) {\n\t\t\t\t\t\treturn ctx.json(null, {\n\t\t\t\t\t\t\tstatus: 400,\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\tmessage: ANONYMOUS_ERROR_CODES.COULD_NOT_CREATE_SESSION,\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\tawait setSessionCookie(ctx, {\n\t\t\t\t\t\tsession,\n\t\t\t\t\t\tuser: newUser,\n\t\t\t\t\t});\n\t\t\t\t\treturn ctx.json({\n\t\t\t\t\t\ttoken: session.token,\n\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\tid: newUser.id,\n\t\t\t\t\t\t\temail: newUser.email,\n\t\t\t\t\t\t\temailVerified: newUser.emailVerified,\n\t\t\t\t\t\t\tname: newUser.name,\n\t\t\t\t\t\t\tcreatedAt: newUser.createdAt,\n\t\t\t\t\t\t\tupdatedAt: newUser.updatedAt,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t),\n\t\t},\n\t\thooks: {\n\t\t\tafter: [\n\t\t\t\t{\n\t\t\t\t\tmatcher(ctx) {\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\tctx.path.startsWith(\"/sign-in\") ||\n\t\t\t\t\t\t\tctx.path.startsWith(\"/sign-up\") ||\n\t\t\t\t\t\t\tctx.path.startsWith(\"/callback\") ||\n\t\t\t\t\t\t\tctx.path.startsWith(\"/oauth2/callback\") ||\n\t\t\t\t\t\t\tctx.path.startsWith(\"/magic-link/verify\") ||\n\t\t\t\t\t\t\tctx.path.startsWith(\"/email-otp/verify-email\") ||\n\t\t\t\t\t\t\tctx.path.startsWith(\"/one-tap/callback\") ||\n\t\t\t\t\t\t\tctx.path.startsWith(\"/passkey/verify-authentication\") ||\n\t\t\t\t\t\t\tctx.path.startsWith(\"/phone-number/verify\")\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t\thandler: createAuthMiddleware(async (ctx) => {\n\t\t\t\t\t\tconst setCookie = ctx.context.responseHeaders?.get(\"set-cookie\");\n\n\t\t\t\t\t\t/**\n\t\t\t\t\t\t * We can consider the user is about to sign in or sign up\n\t\t\t\t\t\t * if the response contains a session token.\n\t\t\t\t\t\t */\n\t\t\t\t\t\tconst sessionTokenName = ctx.context.authCookies.sessionToken.name;\n\t\t\t\t\t\t/**\n\t\t\t\t\t\t * The user is about to link their account.\n\t\t\t\t\t\t */\n\t\t\t\t\t\tconst sessionCookie = parseSetCookieHeader(setCookie || \"\")\n\t\t\t\t\t\t\t.get(sessionTokenName)\n\t\t\t\t\t\t\t?.value.split(\".\")[0]!;\n\n\t\t\t\t\t\tif (!sessionCookie) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t/**\n\t\t\t\t\t\t * Make sure the user had an anonymous session.\n\t\t\t\t\t\t */\n\t\t\t\t\t\tconst session = await getSessionFromCtx<{ isAnonymous: boolean }>(\n\t\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tdisableRefresh: true,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tif (!session || !session.user.isAnonymous) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (ctx.path === \"/sign-in/anonymous\" && !ctx.context.newSession) {\n\t\t\t\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\t\tANONYMOUS_ERROR_CODES.ANONYMOUS_USERS_CANNOT_SIGN_IN_AGAIN_ANONYMOUSLY,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst newSession = ctx.context.newSession;\n\t\t\t\t\t\tif (!newSession) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// At this point the user is linking their previous anonymous account with a\n\t\t\t\t\t\t// new credential (email / social). Invoke the provided callback so that the\n\t\t\t\t\t\t// integrator can perform any additional logic such as transferring data\n\t\t\t\t\t\t// from the anonymous user to the new user.\n\t\t\t\t\t\tif (options?.onLinkAccount) {\n\t\t\t\t\t\t\tawait options?.onLinkAccount?.({\n\t\t\t\t\t\t\t\tanonymousUser: session,\n\t\t\t\t\t\t\t\tnewUser: newSession,\n\t\t\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!options?.disableDeleteAnonymousUser) {\n\t\t\t\t\t\t\tawait ctx.context.internalAdapter.deleteUser(session.user.id);\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\toptions,\n\t\tschema: mergeSchema(schema, options?.schema),\n\t\t$ERROR_CODES: ANONYMOUS_ERROR_CODES,\n\t} satisfies BetterAuthPlugin;\n};\n"],"mappings":"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"}