better-auth
Version:
The most comprehensive authentication framework for TypeScript.
1 lines • 16.8 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/multi-session/index.ts"],"sourcesContent":["import type { BetterAuthPlugin } from \"@better-auth/core\";\nimport {\n\tcreateAuthEndpoint,\n\tcreateAuthMiddleware,\n} from \"@better-auth/core/api\";\nimport { defineErrorCodes } from \"@better-auth/core/utils\";\nimport * as z from \"zod\";\nimport { APIError, sessionMiddleware } from \"../../api\";\nimport {\n\tdeleteSessionCookie,\n\tparseCookies,\n\tparseSetCookieHeader,\n\tsetSessionCookie,\n} from \"../../cookies\";\n\nexport interface MultiSessionConfig {\n\t/**\n\t * The maximum number of sessions a user can have\n\t * at a time\n\t * @default 5\n\t */\n\tmaximumSessions?: number | undefined;\n}\n\nconst ERROR_CODES = defineErrorCodes({\n\tINVALID_SESSION_TOKEN: \"Invalid session token\",\n});\n\nconst setActiveSessionBodySchema = z.object({\n\tsessionToken: z.string().meta({\n\t\tdescription: \"The session token to set as active\",\n\t}),\n});\n\nconst revokeDeviceSessionBodySchema = z.object({\n\tsessionToken: z.string().meta({\n\t\tdescription: \"The session token to revoke\",\n\t}),\n});\n\nexport const multiSession = (options?: MultiSessionConfig | undefined) => {\n\tconst opts = {\n\t\tmaximumSessions: 5,\n\t\t...options,\n\t};\n\n\tconst isMultiSessionCookie = (key: string) => key.includes(\"_multi-\");\n\n\treturn {\n\t\tid: \"multi-session\",\n\t\tendpoints: {\n\t\t\t/**\n\t\t\t * ### Endpoint\n\t\t\t *\n\t\t\t * GET `/multi-session/list-device-sessions`\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.listDeviceSessions`\n\t\t\t *\n\t\t\t * **client:**\n\t\t\t * `authClient.multiSession.listDeviceSessions`\n\t\t\t *\n\t\t\t * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/multi-session#api-method-multi-session-list-device-sessions)\n\t\t\t */\n\t\t\tlistDeviceSessions: createAuthEndpoint(\n\t\t\t\t\"/multi-session/list-device-sessions\",\n\t\t\t\t{\n\t\t\t\t\tmethod: \"GET\",\n\t\t\t\t\trequireHeaders: true,\n\t\t\t\t},\n\t\t\t\tasync (ctx) => {\n\t\t\t\t\tconst cookieHeader = ctx.headers?.get(\"cookie\");\n\t\t\t\t\tif (!cookieHeader) return ctx.json([]);\n\n\t\t\t\t\tconst cookies = Object.fromEntries(parseCookies(cookieHeader));\n\t\t\t\t\tconst sessionTokens = (\n\t\t\t\t\t\tawait Promise.all(\n\t\t\t\t\t\t\tObject.entries(cookies)\n\t\t\t\t\t\t\t\t.filter(([key]) => isMultiSessionCookie(key))\n\t\t\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\t\t\tasync ([key]) =>\n\t\t\t\t\t\t\t\t\t\tawait ctx.getSignedCookie(key, ctx.context.secret),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t)\n\t\t\t\t\t).filter((v) => typeof v === \"string\");\n\n\t\t\t\t\tif (!sessionTokens.length) return ctx.json([]);\n\t\t\t\t\tconst sessions =\n\t\t\t\t\t\tawait ctx.context.internalAdapter.findSessions(sessionTokens);\n\t\t\t\t\tconst validSessions = sessions.filter(\n\t\t\t\t\t\t(session) => session && session.session.expiresAt > new Date(),\n\t\t\t\t\t);\n\t\t\t\t\tconst uniqueUserSessions = validSessions.reduce(\n\t\t\t\t\t\t(acc, session) => {\n\t\t\t\t\t\t\tif (!acc.find((s) => s.user.id === session.user.id)) {\n\t\t\t\t\t\t\t\tacc.push(session);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn acc;\n\t\t\t\t\t\t},\n\t\t\t\t\t\t[] as typeof validSessions,\n\t\t\t\t\t);\n\t\t\t\t\treturn ctx.json(uniqueUserSessions);\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 * POST `/multi-session/set-active`\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.setActiveSession`\n\t\t\t *\n\t\t\t * **client:**\n\t\t\t * `authClient.multiSession.setActive`\n\t\t\t *\n\t\t\t * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/multi-session#api-method-multi-session-set-active)\n\t\t\t */\n\t\t\tsetActiveSession: createAuthEndpoint(\n\t\t\t\t\"/multi-session/set-active\",\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\tbody: setActiveSessionBodySchema,\n\t\t\t\t\trequireHeaders: true,\n\t\t\t\t\tuse: [sessionMiddleware],\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\topenapi: {\n\t\t\t\t\t\t\tdescription: \"Set the active session\",\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},\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 sessionToken = ctx.body.sessionToken;\n\t\t\t\t\tconst multiSessionCookieName = `${\n\t\t\t\t\t\tctx.context.authCookies.sessionToken.name\n\t\t\t\t\t}_multi-${sessionToken.toLowerCase()}`;\n\t\t\t\t\tconst sessionCookie = await ctx.getSignedCookie(\n\t\t\t\t\t\tmultiSessionCookieName,\n\t\t\t\t\t\tctx.context.secret,\n\t\t\t\t\t);\n\t\t\t\t\tif (!sessionCookie) {\n\t\t\t\t\t\tthrow new APIError(\"UNAUTHORIZED\", {\n\t\t\t\t\t\t\tmessage: ERROR_CODES.INVALID_SESSION_TOKEN,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tconst session =\n\t\t\t\t\t\tawait ctx.context.internalAdapter.findSession(sessionToken);\n\t\t\t\t\tif (!session || session.session.expiresAt < new Date()) {\n\t\t\t\t\t\tctx.setCookie(multiSessionCookieName, \"\", {\n\t\t\t\t\t\t\t...ctx.context.authCookies.sessionToken.options,\n\t\t\t\t\t\t\tmaxAge: 0,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthrow new APIError(\"UNAUTHORIZED\", {\n\t\t\t\t\t\t\tmessage: ERROR_CODES.INVALID_SESSION_TOKEN,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tawait setSessionCookie(ctx, session);\n\t\t\t\t\treturn ctx.json(session);\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 * POST `/multi-session/revoke`\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.revokeDeviceSession`\n\t\t\t *\n\t\t\t * **client:**\n\t\t\t * `authClient.multiSession.revoke`\n\t\t\t *\n\t\t\t * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/multi-session#api-method-multi-session-revoke)\n\t\t\t */\n\t\t\trevokeDeviceSession: createAuthEndpoint(\n\t\t\t\t\"/multi-session/revoke\",\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\tbody: revokeDeviceSessionBodySchema,\n\t\t\t\t\trequireHeaders: true,\n\t\t\t\t\tuse: [sessionMiddleware],\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\topenapi: {\n\t\t\t\t\t\t\tdescription: \"Revoke a device session\",\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 sessionToken = ctx.body.sessionToken;\n\t\t\t\t\tconst multiSessionCookieName = `${\n\t\t\t\t\t\tctx.context.authCookies.sessionToken.name\n\t\t\t\t\t}_multi-${sessionToken.toLowerCase()}`;\n\t\t\t\t\tconst sessionCookie = await ctx.getSignedCookie(\n\t\t\t\t\t\tmultiSessionCookieName,\n\t\t\t\t\t\tctx.context.secret,\n\t\t\t\t\t);\n\t\t\t\t\tif (!sessionCookie) {\n\t\t\t\t\t\tthrow new APIError(\"UNAUTHORIZED\", {\n\t\t\t\t\t\t\tmessage: ERROR_CODES.INVALID_SESSION_TOKEN,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\tawait ctx.context.internalAdapter.deleteSession(sessionToken);\n\t\t\t\t\tctx.setCookie(multiSessionCookieName, \"\", {\n\t\t\t\t\t\t...ctx.context.authCookies.sessionToken.options,\n\t\t\t\t\t\tmaxAge: 0,\n\t\t\t\t\t});\n\t\t\t\t\tconst isActive = ctx.context.session?.session.token === sessionToken;\n\t\t\t\t\tif (!isActive) return ctx.json({ status: true });\n\n\t\t\t\t\tconst cookieHeader = ctx.headers?.get(\"cookie\");\n\t\t\t\t\tif (cookieHeader) {\n\t\t\t\t\t\tconst cookies = Object.fromEntries(parseCookies(cookieHeader));\n\n\t\t\t\t\t\tconst sessionTokens = (\n\t\t\t\t\t\t\tawait Promise.all(\n\t\t\t\t\t\t\t\tObject.entries(cookies)\n\t\t\t\t\t\t\t\t\t.filter(([key]) => isMultiSessionCookie(key))\n\t\t\t\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\t\t\t\tasync ([key]) =>\n\t\t\t\t\t\t\t\t\t\t\tawait ctx.getSignedCookie(key, ctx.context.secret),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t).filter((v) => typeof v === \"string\");\n\t\t\t\t\t\tconst internalAdapter = ctx.context.internalAdapter;\n\n\t\t\t\t\t\tif (sessionTokens.length > 0) {\n\t\t\t\t\t\t\tconst sessions =\n\t\t\t\t\t\t\t\tawait internalAdapter.findSessions(sessionTokens);\n\t\t\t\t\t\t\tconst validSessions = sessions.filter(\n\t\t\t\t\t\t\t\t(session) => session && session.session.expiresAt > new Date(),\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\tif (validSessions.length > 0) {\n\t\t\t\t\t\t\t\tconst nextSession = validSessions[0]!;\n\t\t\t\t\t\t\t\tawait setSessionCookie(ctx, nextSession);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdeleteSessionCookie(ctx);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tdeleteSessionCookie(ctx);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdeleteSessionCookie(ctx);\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},\n\t\thooks: {\n\t\t\tafter: [\n\t\t\t\t{\n\t\t\t\t\tmatcher: () => true,\n\t\t\t\t\thandler: createAuthMiddleware(async (ctx) => {\n\t\t\t\t\t\tconst cookieString = ctx.context.responseHeaders?.get(\"set-cookie\");\n\t\t\t\t\t\tif (!cookieString) return;\n\t\t\t\t\t\tconst setCookies = parseSetCookieHeader(cookieString);\n\t\t\t\t\t\tconst sessionCookieConfig = ctx.context.authCookies.sessionToken;\n\t\t\t\t\t\tconst sessionToken = ctx.context.newSession?.session.token;\n\t\t\t\t\t\tif (!sessionToken) return;\n\t\t\t\t\t\tconst cookies = parseCookies(ctx.headers?.get(\"cookie\") || \"\");\n\n\t\t\t\t\t\tconst cookieName = `${\n\t\t\t\t\t\t\tsessionCookieConfig.name\n\t\t\t\t\t\t}_multi-${sessionToken.toLowerCase()}`;\n\n\t\t\t\t\t\tif (setCookies.get(cookieName) || cookies.get(cookieName)) return;\n\n\t\t\t\t\t\tconst currentMultiSessions =\n\t\t\t\t\t\t\tObject.keys(Object.fromEntries(cookies)).filter(\n\t\t\t\t\t\t\t\tisMultiSessionCookie,\n\t\t\t\t\t\t\t).length + (cookieString.includes(\"session_token\") ? 1 : 0);\n\n\t\t\t\t\t\tif (currentMultiSessions >= opts.maximumSessions) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tawait ctx.setSignedCookie(\n\t\t\t\t\t\t\tcookieName,\n\t\t\t\t\t\t\tsessionToken,\n\t\t\t\t\t\t\tctx.context.secret,\n\t\t\t\t\t\t\tsessionCookieConfig.options,\n\t\t\t\t\t\t);\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tmatcher: (context) => context.path === \"/sign-out\",\n\t\t\t\t\thandler: createAuthMiddleware(async (ctx) => {\n\t\t\t\t\t\tconst cookieHeader = ctx.headers?.get(\"cookie\");\n\t\t\t\t\t\tif (!cookieHeader) return;\n\t\t\t\t\t\tconst cookies = Object.fromEntries(parseCookies(cookieHeader));\n\t\t\t\t\t\tconst multiSessionKeys = Object.keys(cookies).filter((key) =>\n\t\t\t\t\t\t\tisMultiSessionCookie(key),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst verifiedTokens = (\n\t\t\t\t\t\t\tawait Promise.all(\n\t\t\t\t\t\t\t\tmultiSessionKeys.map(async (key) => {\n\t\t\t\t\t\t\t\t\tconst verifiedToken = await ctx.getSignedCookie(\n\t\t\t\t\t\t\t\t\t\tkey,\n\t\t\t\t\t\t\t\t\t\tctx.context.secret,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tif (verifiedToken) {\n\t\t\t\t\t\t\t\t\t\tctx.setCookie(\n\t\t\t\t\t\t\t\t\t\t\tkey.toLowerCase().replace(\"__secure-\", \"__Secure-\"),\n\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\t\t...ctx.context.authCookies.sessionToken.options,\n\t\t\t\t\t\t\t\t\t\t\t\tmaxAge: 0,\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\treturn verifiedToken;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t).filter((v) => typeof v === \"string\");\n\t\t\t\t\t\tif (verifiedTokens.length > 0) {\n\t\t\t\t\t\t\tawait ctx.context.internalAdapter.deleteSessions(verifiedTokens);\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\t$ERROR_CODES: ERROR_CODES,\n\t} satisfies BetterAuthPlugin;\n};\n"],"mappings":"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"}