UNPKG

better-auth

Version:

The most comprehensive authentication framework for TypeScript.

108 lines (107 loc) 4.13 kB
import { generateRandomString } from "../../crypto/random.mjs"; import { setSessionCookie } from "../../cookies/index.mjs"; import { sessionMiddleware } from "../../api/routes/session.mjs"; import { PACKAGE_VERSION } from "../../version.mjs"; import { defaultKeyHasher } from "./utils.mjs"; import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api"; import * as z from "zod"; //#region src/plugins/one-time-token/index.ts const verifyOneTimeTokenBodySchema = z.object({ token: z.string().meta({ description: "The token to verify. Eg: \"some-token\"" }) }); const oneTimeToken = (options) => { const opts = { storeToken: "plain", ...options }; async function storeToken(ctx, token) { if (opts.storeToken === "hashed") return await defaultKeyHasher(token); if (typeof opts.storeToken === "object" && "type" in opts.storeToken && opts.storeToken.type === "custom-hasher") return await opts.storeToken.hash(token); return token; } async function generateToken(c, session) { const token = opts?.generateToken ? await opts.generateToken(session, c) : generateRandomString(32); const expiresAt = new Date(Date.now() + (opts?.expiresIn ?? 3) * 60 * 1e3); const storedToken = await storeToken(c, token); await c.context.internalAdapter.createVerificationValue({ value: session.session.token, identifier: `one-time-token:${storedToken}`, expiresAt }); return token; } return { id: "one-time-token", version: PACKAGE_VERSION, endpoints: { /** * ### Endpoint * * GET `/one-time-token/generate` * * ### API Methods * * **server:** * `auth.api.generateOneTimeToken` * * **client:** * `authClient.oneTimeToken.generate` * * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/one-time-token#api-method-one-time-token-generate) */ generateOneTimeToken: createAuthEndpoint("/one-time-token/generate", { method: "GET", use: [sessionMiddleware] }, async (c) => { if (opts?.disableClientRequest && c.request) throw c.error("BAD_REQUEST", { message: "Client requests are disabled" }); const session = c.context.session; const token = await generateToken(c, session); return c.json({ token }); }), /** * ### Endpoint * * POST `/one-time-token/verify` * * ### API Methods * * **server:** * `auth.api.verifyOneTimeToken` * * **client:** * `authClient.oneTimeToken.verify` * * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/one-time-token#api-method-one-time-token-verify) */ verifyOneTimeToken: createAuthEndpoint("/one-time-token/verify", { method: "POST", body: verifyOneTimeTokenBodySchema }, async (c) => { const { token } = c.body; const storedToken = await storeToken(c, token); const verificationValue = await c.context.internalAdapter.consumeVerificationValue(`one-time-token:${storedToken}`); if (!verificationValue) throw c.error("BAD_REQUEST", { message: "Invalid token" }); const session = await c.context.internalAdapter.findSession(verificationValue.value); if (!session) throw c.error("BAD_REQUEST", { message: "Session not found" }); if (!opts?.disableSetSessionCookie) await setSessionCookie(c, session); if (session.session.expiresAt < /* @__PURE__ */ new Date()) throw c.error("BAD_REQUEST", { message: "Session expired" }); return c.json(session); }) }, hooks: { after: [{ matcher: () => true, handler: createAuthMiddleware(async (ctx) => { if (ctx.context.newSession) { if (!opts?.setOttHeaderOnNewSession) return; const exposedHeaders = ctx.context.responseHeaders?.get("access-control-expose-headers") || ""; const headersSet = new Set(exposedHeaders.split(",").map((header) => header.trim()).filter(Boolean)); headersSet.add("set-ott"); const token = await generateToken(ctx, ctx.context.newSession); ctx.setHeader("set-ott", token); ctx.setHeader("Access-Control-Expose-Headers", Array.from(headersSet).join(", ")); } }) }] }, options }; }; //#endregion export { oneTimeToken };