accounts
Version:
Tempo Accounts SDK
138 lines • 5.08 kB
JavaScript
import { setCookie as core_setCookie } from 'hono/cookie';
import { Hex } from 'ox';
/**
* Shared session helpers used by SDK handlers that issue server-side
* sessions (e.g. `auth`, `webAuthn`). Each handler is responsible for its
* own session payload shape and storage; this module only provides the
* token-extraction, cookie-issuance, and token-generation primitives so
* the conventions stay consistent.
*/
/** Default `Set-Cookie` attributes for handler-issued session cookies. */
export const defaults = {
httpOnly: true,
sameSite: 'Lax',
path: '/',
};
/**
* Parse a `Bearer <token>` value out of an `Authorization` header. Returns
* `undefined` when the header is missing, doesn't use the `Bearer`
* scheme, or contains an empty token.
*/
export function bearerToken(authorization) {
if (!authorization)
return undefined;
if (!authorization.toLowerCase().startsWith('bearer '))
return undefined;
return authorization.slice(7).trim() || undefined;
}
/**
* Extract the value of a single cookie from a raw `Cookie` header.
* Returns `undefined` when the cookie is absent.
*/
export function parseCookieValue(header, name) {
for (const part of header.split(';')) {
const trimmed = part.trim();
const eq = trimmed.indexOf('=');
if (eq === -1)
continue;
if (trimmed.slice(0, eq) === name)
return decodeURIComponent(trimmed.slice(eq + 1));
}
return undefined;
}
/**
* Read a single header value from a `SessionRequest`. Handles both
* the Fetch API `Headers` (`.get()`) and the Node.js record shape
* where values may be `string | string[] | undefined`.
*/
function getHeader(req, name) {
if ('get' in req.headers && typeof req.headers.get === 'function')
return req.headers.get(name);
const value = req.headers[name];
if (value === undefined)
return null;
return Array.isArray(value) ? value.join('; ') : value;
}
/**
* Resolve the session token for a request. Prefers `Authorization: Bearer
* <token>` over the cookie. When `cookie: false`, the cookie is ignored
* even if present so callers cannot opt back into cookie mode by sending
* a stale `Set-Cookie` value.
*
* Accepts both Fetch API `Request` and Node.js `IncomingMessage`-shaped
* objects (see {@link SessionRequest}).
*/
export function tokenFromRequest(req, options) {
const bearer = bearerToken(getHeader(req, 'authorization'));
if (bearer)
return bearer;
if (!options.cookie)
return undefined;
const cookieHeader = getHeader(req, 'cookie');
return cookieHeader ? parseCookieValue(cookieHeader, options.cookieName) : undefined;
}
/**
* Build the raw `Set-Cookie` header value for a session cookie. Use this
* when the route handler returns a freshly-constructed `Response` (which
* bypasses Hono's context header merging) — append the returned string
* to the response's `Set-Cookie` header directly.
*/
export function serializeCookie(options) {
const parts = [`${options.name}=${encodeURIComponent(options.value)}`];
parts.push(`Max-Age=${options.ttl}`);
parts.push(`Path=${defaults.path}`);
parts.push(`SameSite=${defaults.sameSite}`);
if (defaults.httpOnly)
parts.push('HttpOnly');
if (options.protocol === 'https:')
parts.push('Secure');
return parts.join('; ');
}
/**
* Build the raw `Set-Cookie` header value that clears a previously
* issued session cookie.
*/
export function clearCookieHeader(name) {
return `${name}=; Max-Age=0; Path=${defaults.path}`;
}
/**
* Clear a previously-issued session cookie by writing an empty value with
* `Max-Age=0`.
*/
export function clearCookie(c, name) {
core_setCookie(c, name, '', { path: '/', maxAge: 0 });
}
/**
* Generate a 256-bit cryptographically-random session token, encoded as
* lowercase hex without the `0x` prefix.
*/
export function generateToken() {
return Hex.fromBytes(crypto.getRandomValues(new Uint8Array(32))).slice(2);
}
/**
* Build the final JSON response for a verify/login route, merging an
* optional hook `Response` (extra body fields, status, custom headers)
* with the handler's own JSON and an optional `Set-Cookie` header.
*
* The hook contract — return a `Response` whose body fields and status
* are folded onto the default response — is shared by `auth` and
* `webAuthn`. Hook fields take precedence over the handler's defaults
* via spread order.
*/
export async function mergeResponse(json, hook, cookieHeader) {
const headers = hook ? new Headers(hook.headers) : new Headers();
headers.set('content-type', 'application/json');
if (cookieHeader)
headers.append('set-cookie', cookieHeader);
if (!hook)
return new Response(JSON.stringify(json), {
headers,
status: 200,
});
const extra = (await hook.json().catch(() => ({})));
return new Response(JSON.stringify({ ...json, ...extra }), {
headers,
status: hook.status,
});
}
//# sourceMappingURL=session.js.map