UNPKG

better-auth

Version:

The most comprehensive authentication framework for TypeScript.

1 lines • 14.7 kB
{"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 {\n\tAPIError,\n\tgetSessionFromCtx,\n\tsensitiveSessionMiddleware,\n} from \"../../api\";\nimport {\n\tdeleteSessionCookie,\n\tparseSetCookieHeader,\n\tsetSessionCookie,\n} from \"../../cookies\";\nimport { mergeSchema, parseUserOutput } from \"../../db/schema\";\nimport { ANONYMOUS_ERROR_CODES } from \"./error-codes\";\nimport { schema } from \"./schema\";\nimport type {\n\tAnonymousOptions,\n\tAnonymousSession,\n\tUserWithAnonymous,\n} 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 | null;\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: parseUserOutput(ctx.context.options, newUser),\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t),\n\t\t\tdeleteAnonymousUser: createAuthEndpoint(\n\t\t\t\t\"/delete-anonymous-user\",\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\tuse: [sensitiveSessionMiddleware],\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\topenapi: {\n\t\t\t\t\t\t\tdescription: \"Delete an anonymous user\",\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: \"Anonymous user deleted\",\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\tsuccess: {\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\t\"400\": {\n\t\t\t\t\t\t\t\t\tdescription: \"Anonymous user deletion is disabled\",\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\tmessage: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\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\trequired: [\"message\"],\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\t\"500\": {\n\t\t\t\t\t\t\t\t\tdescription: \"Internal server error\",\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\tmessage: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\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\trequired: [\"message\"],\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 session = ctx.context.session as AnonymousSession;\n\n\t\t\t\t\tif (options?.disableDeleteAnonymousUser) {\n\t\t\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\t\t\tmessage: ANONYMOUS_ERROR_CODES.DELETE_ANONYMOUS_USER_DISABLED,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!session.user.isAnonymous) {\n\t\t\t\t\t\tthrow new APIError(\"FORBIDDEN\", {\n\t\t\t\t\t\t\tmessage: ANONYMOUS_ERROR_CODES.USER_IS_NOT_ANONYMOUS,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait ctx.context.internalAdapter.deleteUser(session.user.id);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tctx.context.logger.error(\"Failed to delete anonymous user\", error);\n\t\t\t\t\t\tthrow new APIError(\"INTERNAL_SERVER_ERROR\", {\n\t\t\t\t\t\t\tmessage: ANONYMOUS_ERROR_CODES.FAILED_TO_DELETE_ANONYMOUS_USER,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tdeleteSessionCookie(ctx);\n\t\t\t\t\treturn ctx.json({ success: true });\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\tfalse\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<{\n\t\t\t\t\t\t\tisAnonymous: boolean | null;\n\t\t\t\t\t\t}>(ctx, {\n\t\t\t\t\t\t\tdisableRefresh: true,\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\n\t\t\t\t\t\tconst user = {\n\t\t\t\t\t\t\t...session.user,\n\t\t\t\t\t\t\t// Type hack to ensure `isAnonymous` is correctly inferred as true.\n\t\t\t\t\t\t\t// Without this, `isAnonymous` is inferred as `boolean | null` despite\n\t\t\t\t\t\t\t// the conditional checks above suggesting otherwise.\n\t\t\t\t\t\t\tisAnonymous: session.user.isAnonymous,\n\t\t\t\t\t\t};\n\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: session.session, user },\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\tconst newSessionUser = newSession.user as\n\t\t\t\t\t\t\t| (UserWithAnonymous & Record<string, any>)\n\t\t\t\t\t\t\t| undefined;\n\t\t\t\t\t\tconst isSameUser = newSessionUser?.id === session.user.id;\n\t\t\t\t\t\tconst newSessionIsAnonymous = Boolean(newSessionUser?.isAnonymous);\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\toptions?.disableDeleteAnonymousUser ||\n\t\t\t\t\t\t\tisSameUser ||\n\t\t\t\t\t\t\tnewSessionIsAnonymous\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait ctx.context.internalAdapter.deleteUser(session.user.id);\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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"}