better-auth
Version:
The most comprehensive authentication library for TypeScript.
336 lines (330 loc) • 12.1 kB
JavaScript
;
const z = require('zod/v4');
const betterCall = require('better-call');
const socialProviders_index = require('../../shared/better-auth.Bafolo-S.cjs');
const cookies_index = require('../../cookies/index.cjs');
require('../../shared/better-auth.BIMq4RPW.cjs');
require('../../shared/better-auth.DiSjtgs9.cjs');
require('../../shared/better-auth.CXhVNgXP.cjs');
require('defu');
require('../../shared/better-auth.C1hdVENX.cjs');
require('@better-auth/utils/hash');
require('@better-auth/utils/base64');
require('../../crypto/index.cjs');
require('@noble/ciphers/chacha');
require('@noble/ciphers/utils');
require('@noble/ciphers/webcrypto');
require('jose');
require('@noble/hashes/scrypt');
require('@better-auth/utils');
require('@better-auth/utils/hex');
require('@noble/hashes/utils');
require('../../shared/better-auth.CYeOI8C-.cjs');
require('@better-auth/utils/random');
require('@better-fetch/fetch');
require('../../shared/better-auth.C-R0J0n1.cjs');
require('../../shared/better-auth.ANpbi45u.cjs');
require('../../shared/better-auth.D3mtHEZg.cjs');
require('@better-auth/utils/hmac');
require('@better-auth/utils/binary');
require('jose/errors');
function _interopNamespaceCompat(e) {
if (e && typeof e === 'object' && 'default' in e) return e;
const n = Object.create(null);
if (e) {
for (const k in e) {
n[k] = e[k];
}
}
n.default = e;
return n;
}
const z__namespace = /*#__PURE__*/_interopNamespaceCompat(z);
const multiSession = (options) => {
const opts = {
maximumSessions: 5,
...options
};
const isMultiSessionCookie = (key) => key.includes("_multi-");
const ERROR_CODES = {
INVALID_SESSION_TOKEN: "Invalid session token"
};
return {
id: "multi-session",
endpoints: {
/**
* ### Endpoint
*
* GET `/multi-session/list-device-sessions`
*
* ### API Methods
*
* **server:**
* `auth.api.listDeviceSessions`
*
* **client:**
* `authClient.multiSession.listDeviceSessions`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/multi-session#api-method-multi-session-list-device-sessions)
*/
listDeviceSessions: socialProviders_index.createAuthEndpoint(
"/multi-session/list-device-sessions",
{
method: "GET",
requireHeaders: true
},
async (ctx) => {
const cookieHeader = ctx.headers?.get("cookie");
if (!cookieHeader) return ctx.json([]);
const cookies = Object.fromEntries(cookies_index.parseCookies(cookieHeader));
const sessionTokens = (await Promise.all(
Object.entries(cookies).filter(([key]) => isMultiSessionCookie(key)).map(
async ([key]) => await ctx.getSignedCookie(key, ctx.context.secret)
)
)).filter((v) => v !== null);
if (!sessionTokens.length) return ctx.json([]);
const sessions = await ctx.context.internalAdapter.findSessions(sessionTokens);
const validSessions = sessions.filter(
(session) => session && session.session.expiresAt > /* @__PURE__ */ new Date()
);
const uniqueUserSessions = validSessions.reduce(
(acc, session) => {
if (!acc.find((s) => s.user.id === session.user.id)) {
acc.push(session);
}
return acc;
},
[]
);
return ctx.json(uniqueUserSessions);
}
),
/**
* ### Endpoint
*
* POST `/multi-session/set-active`
*
* ### API Methods
*
* **server:**
* `auth.api.setActiveSession`
*
* **client:**
* `authClient.multiSession.setActive`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/multi-session#api-method-multi-session-set-active)
*/
setActiveSession: socialProviders_index.createAuthEndpoint(
"/multi-session/set-active",
{
method: "POST",
body: z__namespace.object({
sessionToken: z__namespace.string().meta({
description: "The session token to set as active"
})
}),
requireHeaders: true,
use: [socialProviders_index.sessionMiddleware],
metadata: {
openapi: {
description: "Set the active session",
responses: {
200: {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
properties: {
session: {
$ref: "#/components/schemas/Session"
}
}
}
}
}
}
}
}
}
},
async (ctx) => {
const sessionToken = ctx.body.sessionToken;
const multiSessionCookieName = `${ctx.context.authCookies.sessionToken.name}_multi-${sessionToken.toLowerCase()}`;
const sessionCookie = await ctx.getSignedCookie(
multiSessionCookieName,
ctx.context.secret
);
if (!sessionCookie) {
throw new betterCall.APIError("UNAUTHORIZED", {
message: ERROR_CODES.INVALID_SESSION_TOKEN
});
}
const session = await ctx.context.internalAdapter.findSession(sessionToken);
if (!session || session.session.expiresAt < /* @__PURE__ */ new Date()) {
ctx.setCookie(multiSessionCookieName, "", {
...ctx.context.authCookies.sessionToken.options,
maxAge: 0
});
throw new betterCall.APIError("UNAUTHORIZED", {
message: ERROR_CODES.INVALID_SESSION_TOKEN
});
}
await cookies_index.setSessionCookie(ctx, session);
return ctx.json(session);
}
),
/**
* ### Endpoint
*
* POST `/multi-session/revoke`
*
* ### API Methods
*
* **server:**
* `auth.api.revokeDeviceSession`
*
* **client:**
* `authClient.multiSession.revoke`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/multi-session#api-method-multi-session-revoke)
*/
revokeDeviceSession: socialProviders_index.createAuthEndpoint(
"/multi-session/revoke",
{
method: "POST",
body: z__namespace.object({
sessionToken: z__namespace.string().meta({
description: "The session token to revoke"
})
}),
requireHeaders: true,
use: [socialProviders_index.sessionMiddleware],
metadata: {
openapi: {
description: "Revoke a device session",
responses: {
200: {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
properties: {
status: {
type: "boolean"
}
}
}
}
}
}
}
}
}
},
async (ctx) => {
const sessionToken = ctx.body.sessionToken;
const multiSessionCookieName = `${ctx.context.authCookies.sessionToken.name}_multi-${sessionToken.toLowerCase()}`;
const sessionCookie = await ctx.getSignedCookie(
multiSessionCookieName,
ctx.context.secret
);
if (!sessionCookie) {
throw new betterCall.APIError("UNAUTHORIZED", {
message: ERROR_CODES.INVALID_SESSION_TOKEN
});
}
await ctx.context.internalAdapter.deleteSession(sessionToken);
ctx.setCookie(multiSessionCookieName, "", {
...ctx.context.authCookies.sessionToken.options,
maxAge: 0
});
const isActive = ctx.context.session?.session.token === sessionToken;
if (!isActive) return ctx.json({ status: true });
const cookieHeader = ctx.headers?.get("cookie");
if (cookieHeader) {
const cookies = Object.fromEntries(cookies_index.parseCookies(cookieHeader));
const sessionTokens = (await Promise.all(
Object.entries(cookies).filter(([key]) => isMultiSessionCookie(key)).map(
async ([key]) => await ctx.getSignedCookie(key, ctx.context.secret)
)
)).filter((v) => v !== void 0);
const internalAdapter = ctx.context.internalAdapter;
if (sessionTokens.length > 0) {
const sessions = await internalAdapter.findSessions(sessionTokens);
const validSessions = sessions.filter(
(session) => session && session.session.expiresAt > /* @__PURE__ */ new Date()
);
if (validSessions.length > 0) {
const nextSession = validSessions[0];
await cookies_index.setSessionCookie(ctx, nextSession);
} else {
cookies_index.deleteSessionCookie(ctx);
}
} else {
cookies_index.deleteSessionCookie(ctx);
}
} else {
cookies_index.deleteSessionCookie(ctx);
}
return ctx.json({
status: true
});
}
)
},
hooks: {
after: [
{
matcher: () => true,
handler: socialProviders_index.createAuthMiddleware(async (ctx) => {
const cookieString = ctx.context.responseHeaders?.get("set-cookie");
if (!cookieString) return;
const setCookies = cookies_index.parseSetCookieHeader(cookieString);
const sessionCookieConfig = ctx.context.authCookies.sessionToken;
const sessionToken = ctx.context.newSession?.session.token;
if (!sessionToken) return;
const cookies = cookies_index.parseCookies(ctx.headers?.get("cookie") || "");
const cookieName = `${sessionCookieConfig.name}_multi-${sessionToken.toLowerCase()}`;
if (setCookies.get(cookieName) || cookies.get(cookieName)) return;
const currentMultiSessions = Object.keys(Object.fromEntries(cookies)).filter(
isMultiSessionCookie
).length + (cookieString.includes("session_token") ? 1 : 0);
if (currentMultiSessions >= opts.maximumSessions) {
return;
}
await ctx.setSignedCookie(
cookieName,
sessionToken,
ctx.context.secret,
sessionCookieConfig.options
);
})
},
{
matcher: (context) => context.path === "/sign-out",
handler: socialProviders_index.createAuthMiddleware(async (ctx) => {
const cookieHeader = ctx.headers?.get("cookie");
if (!cookieHeader) return;
const cookies = Object.fromEntries(cookies_index.parseCookies(cookieHeader));
const ids = Object.keys(cookies).map((key) => {
if (isMultiSessionCookie(key)) {
ctx.setCookie(key.toLowerCase(), "", {
...ctx.context.authCookies.sessionToken.options,
maxAge: 0
});
const token = cookies[key].split(".")[0];
return token;
}
return null;
}).filter((v) => v !== null);
await ctx.context.internalAdapter.deleteSessions(ids);
})
}
]
},
$ERROR_CODES: ERROR_CODES
};
};
exports.multiSession = multiSession;