@tillstack/auth-node 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +21 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +21 -9
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
package/dist/index.cjs
CHANGED
|
@@ -137,7 +137,20 @@ function parseCookieHeader(header) {
|
|
|
137
137
|
|
|
138
138
|
// src/index.ts
|
|
139
139
|
var DEFAULT_API2 = "https://auth.tilldev.dev";
|
|
140
|
-
var
|
|
140
|
+
var LEGACY_ISSUER = "tillauth";
|
|
141
|
+
var REALM_SEGMENT = "/realms/";
|
|
142
|
+
function trimSlash(s) {
|
|
143
|
+
return s.replace(/\/+$/, "");
|
|
144
|
+
}
|
|
145
|
+
function issuerAccepted(iss, canonical, pinnedIssuer) {
|
|
146
|
+
if (typeof iss !== "string" || iss.length === 0) return false;
|
|
147
|
+
if (iss === LEGACY_ISSUER) return true;
|
|
148
|
+
if (pinnedIssuer) return iss === trimSlash(pinnedIssuer);
|
|
149
|
+
const canon = trimSlash(canonical);
|
|
150
|
+
if (iss === canon) return true;
|
|
151
|
+
const prefix = `${canon}${REALM_SEGMENT}`;
|
|
152
|
+
return iss.startsWith(prefix) && iss.length > prefix.length;
|
|
153
|
+
}
|
|
141
154
|
var TillAuthVerifyError = class extends Error {
|
|
142
155
|
constructor(message, code) {
|
|
143
156
|
super(message);
|
|
@@ -154,6 +167,7 @@ var TillAuthVerifier = class {
|
|
|
154
167
|
requireVerifiedEmail: opts.requireVerifiedEmail ?? false,
|
|
155
168
|
requireMfa: opts.requireMfa ?? false
|
|
156
169
|
};
|
|
170
|
+
this.pinnedIssuer = opts.issuer ? opts.issuer.replace(/\/$/, "") : void 0;
|
|
157
171
|
if (opts.publicJwks && opts.publicJwks.length > 0) {
|
|
158
172
|
this.remote = null;
|
|
159
173
|
this.inlineKeys = Promise.all(
|
|
@@ -173,10 +187,7 @@ var TillAuthVerifier = class {
|
|
|
173
187
|
let lastErr;
|
|
174
188
|
for (const k of keys) {
|
|
175
189
|
try {
|
|
176
|
-
const { payload: payload2 } = await jose.jwtVerify(token, k.key, {
|
|
177
|
-
algorithms: ["EdDSA"],
|
|
178
|
-
issuer: ISSUER
|
|
179
|
-
});
|
|
190
|
+
const { payload: payload2 } = await jose.jwtVerify(token, k.key, { algorithms: ["EdDSA"] });
|
|
180
191
|
claims = payload2;
|
|
181
192
|
return this.applyClaimsChecks(claims);
|
|
182
193
|
} catch (e) {
|
|
@@ -185,12 +196,10 @@ var TillAuthVerifier = class {
|
|
|
185
196
|
}
|
|
186
197
|
throw lastErr ?? new Error("No key matched");
|
|
187
198
|
}
|
|
188
|
-
const { payload } = await jose.jwtVerify(token, this.remote, {
|
|
189
|
-
algorithms: ["EdDSA"],
|
|
190
|
-
issuer: ISSUER
|
|
191
|
-
});
|
|
199
|
+
const { payload } = await jose.jwtVerify(token, this.remote, { algorithms: ["EdDSA"] });
|
|
192
200
|
claims = payload;
|
|
193
201
|
} catch (e) {
|
|
202
|
+
if (e instanceof TillAuthVerifyError) throw e;
|
|
194
203
|
throw new TillAuthVerifyError(
|
|
195
204
|
e instanceof Error ? e.message : "Invalid token",
|
|
196
205
|
"invalid_token"
|
|
@@ -199,6 +208,9 @@ var TillAuthVerifier = class {
|
|
|
199
208
|
return this.applyClaimsChecks(claims);
|
|
200
209
|
}
|
|
201
210
|
applyClaimsChecks(claims) {
|
|
211
|
+
if (!issuerAccepted(claims.iss, this.opts.apiBase, this.pinnedIssuer)) {
|
|
212
|
+
throw new TillAuthVerifyError("Untrusted token issuer", "invalid_issuer");
|
|
213
|
+
}
|
|
202
214
|
if (claims.app !== this.opts.appId) {
|
|
203
215
|
throw new TillAuthVerifyError(`Token issued for different app`, "wrong_app");
|
|
204
216
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cookie-proxy.ts","../src/index.ts"],"names":["DEFAULT_API","importJWK","createRemoteJWKSet","payload","jwtVerify"],"mappings":"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","file":"index.cjs","sourcesContent":["// Cookie-mode proxy for TillAuth.\n//\n// Mount this on the customer's backend (typically at /api/auth/*). It\n// forwards JSON bodies to TillAuth's /v1/* endpoints, then translates\n// access_token + refresh_token in the response into HttpOnly cookies on\n// the customer's domain. The browser's localStorage never sees the\n// tokens — XSS can't exfiltrate them.\n//\n// Two adapters: a generic web-fetch handler (Cloudflare Workers, Next.js\n// Route Handlers, Bun, Deno) and an Express middleware. Pick one.\n\nconst DEFAULT_API = 'https://auth.tilldev.dev'\n\nexport interface CookieProxyOptions {\n /** TillAuth public app id (`tau_…`). */\n appId: string\n /** Override the TillAuth API base. */\n apiBase?: string\n /** Cookie name for the access token. Default `tau_at`. */\n accessCookie?: string\n /** Cookie name for the refresh token. Default `tau_rt`. */\n refreshCookie?: string\n /** Cookie path. Default '/'. */\n cookiePath?: string\n /** Cookie domain (e.g. `.example.com` to share across subdomains). */\n cookieDomain?: string\n /** Secure flag. Defaults to true in production, false otherwise. */\n secure?: boolean\n}\n\ninterface NormalizedRequest {\n method: string\n pathname: string // path relative to the mount point (`/signin`, `/refresh`)\n body: string\n cookies: Record<string, string>\n}\ninterface NormalizedResponse {\n status: number\n body: string\n setCookies: string[]\n headers: Record<string, string>\n}\n\ninterface AuthBody {\n access_token?: string\n refresh_token?: string\n refresh_expires_at?: string\n [key: string]: unknown\n}\n\nfunction resolve(opts: CookieProxyOptions): Required<CookieProxyOptions> {\n return {\n appId: opts.appId,\n apiBase: (opts.apiBase ?? DEFAULT_API).replace(/\\/$/, ''),\n accessCookie: opts.accessCookie ?? 'tau_at',\n refreshCookie: opts.refreshCookie ?? 'tau_rt',\n cookiePath: opts.cookiePath ?? '/',\n cookieDomain: opts.cookieDomain ?? '',\n secure: opts.secure ?? process.env.NODE_ENV === 'production',\n }\n}\n\nfunction makeCookie(\n name: string,\n value: string,\n o: Required<CookieProxyOptions>,\n opts: { maxAge?: number; clear?: boolean } = {},\n): string {\n const parts = [`${name}=${opts.clear ? '' : value}`]\n parts.push('HttpOnly')\n if (o.secure) parts.push('Secure')\n parts.push('SameSite=Lax')\n parts.push(`Path=${o.cookiePath}`)\n if (o.cookieDomain) parts.push(`Domain=${o.cookieDomain}`)\n if (opts.clear) parts.push('Max-Age=0')\n else if (opts.maxAge !== undefined) parts.push(`Max-Age=${opts.maxAge}`)\n return parts.join('; ')\n}\n\nasync function handleProxy(req: NormalizedRequest, raw: CookieProxyOptions): Promise<NormalizedResponse> {\n const opts = resolve(raw)\n const path = req.pathname.replace(/^\\/+/, '')\n\n // Map proxy paths → TillAuth paths.\n const url = `${opts.apiBase}/v1/${path}`\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'X-TillAuth-App': opts.appId,\n 'User-Agent': 'tilldev-auth-node/cookie-proxy',\n }\n\n // /me, /refresh, /signout need to carry tokens from cookies, not the body.\n let body = req.body\n if (path === 'me' && req.cookies[opts.accessCookie]) {\n headers.Authorization = `Bearer ${req.cookies[opts.accessCookie]}`\n }\n if (path === 'refresh') {\n const rt = req.cookies[opts.refreshCookie]\n if (!rt) {\n return {\n status: 401,\n body: JSON.stringify({ error: 'no refresh cookie' }),\n setCookies: [],\n headers: { 'Content-Type': 'application/json' },\n }\n }\n body = JSON.stringify({ refresh_token: rt })\n }\n if (path === 'signout') {\n const rt = req.cookies[opts.refreshCookie]\n body = JSON.stringify({ refresh_token: rt ?? '' })\n }\n\n const upstream = await fetch(url, { method: req.method, headers, body })\n const text = await upstream.text()\n let json: AuthBody = {}\n try {\n json = JSON.parse(text) as AuthBody\n } catch {\n json = {}\n }\n\n const setCookies: string[] = []\n // Always strip token fields before returning to the SPA.\n const out = { ...json }\n\n if (typeof json.access_token === 'string') {\n setCookies.push(makeCookie(opts.accessCookie, json.access_token, opts, { maxAge: 60 * 10 }))\n delete out.access_token\n }\n if (typeof json.refresh_token === 'string') {\n // 30d in line with the Fastify REFRESH_TTL_MS.\n setCookies.push(makeCookie(opts.refreshCookie, json.refresh_token, opts, { maxAge: 60 * 60 * 24 * 30 }))\n delete out.refresh_token\n delete out.refresh_expires_at\n }\n if (path === 'signout') {\n setCookies.push(makeCookie(opts.accessCookie, '', opts, { clear: true }))\n setCookies.push(makeCookie(opts.refreshCookie, '', opts, { clear: true }))\n }\n\n return {\n status: upstream.status,\n body: JSON.stringify(out),\n setCookies,\n headers: { 'Content-Type': 'application/json' },\n }\n}\n\n// ── Adapter 1: web-fetch (Next.js Route Handlers, Workers, Bun, Deno) ───────\n\nexport function createFetchHandler(opts: CookieProxyOptions) {\n return async function handler(req: Request, ctx: { params?: { path?: string | string[] } } = {}): Promise<Response> {\n const url = new URL(req.url)\n // Derive the proxy-relative path. Prefer ctx.params.path (Next.js catch-all);\n // fall back to the part of the URL after the mount point heuristic.\n let pathname: string\n if (ctx.params?.path) {\n pathname = Array.isArray(ctx.params.path) ? `/${ctx.params.path.join('/')}` : `/${ctx.params.path}`\n } else {\n pathname = url.pathname.replace(/^\\/api\\/auth/, '')\n }\n const cookies = parseCookieHeader(req.headers.get('cookie'))\n const body = req.method === 'POST' ? await req.text() : '{}'\n const out = await handleProxy({ method: req.method, pathname, body, cookies }, opts)\n const headers = new Headers(out.headers)\n for (const c of out.setCookies) headers.append('Set-Cookie', c)\n return new Response(out.body, { status: out.status, headers })\n }\n}\n\n// ── Adapter 2: Express ──────────────────────────────────────────────────────\n\ninterface ExpReq {\n method: string\n url?: string\n originalUrl?: string\n path?: string\n body?: unknown\n headers: Record<string, string | string[] | undefined>\n}\ninterface ExpRes {\n status(n: number): ExpRes\n setHeader(name: string, value: string | string[]): void\n json(body: unknown): void\n}\n\nexport function expressCookieProxy(opts: CookieProxyOptions) {\n return async function (req: ExpReq, res: ExpRes): Promise<void> {\n const path =\n req.path ?? (req.originalUrl ?? req.url ?? '/').replace(/^\\/api\\/auth/, '').split('?')[0] ?? '/'\n const cookieHeader =\n typeof req.headers.cookie === 'string' ? req.headers.cookie : Array.isArray(req.headers.cookie) ? req.headers.cookie[0] : ''\n const body =\n typeof req.body === 'string' ? req.body : req.body ? JSON.stringify(req.body) : '{}'\n const out = await handleProxy(\n {\n method: req.method,\n pathname: path,\n body,\n cookies: parseCookieHeader(cookieHeader ?? null),\n },\n opts,\n )\n res.status(out.status)\n if (out.setCookies.length > 0) res.setHeader('Set-Cookie', out.setCookies)\n for (const [k, v] of Object.entries(out.headers)) res.setHeader(k, v)\n res.json(JSON.parse(out.body))\n }\n}\n\nfunction parseCookieHeader(header: string | null): Record<string, string> {\n const out: Record<string, string> = {}\n if (!header) return out\n for (const part of header.split(';')) {\n const [k, ...rest] = part.trim().split('=')\n if (!k) continue\n out[k] = decodeURIComponent(rest.join('='))\n }\n return out\n}\n","import { jwtVerify, createRemoteJWKSet, importJWK, type JWK } from 'jose'\n\n// Server-side TillAuth verification.\n//\n// You configure once with the issuer + JWKS endpoint (or inline public keys),\n// then call `verify(token)` or use the framework-specific middleware.\n//\n// Verification is offline against the cached JWKS — no round-trip to TillAuth\n// on the hot path.\n\nconst DEFAULT_API = 'https://auth.tilldev.dev'\nconst ISSUER = 'tillauth'\n\nexport interface VerifyOptions {\n /** TillAuth public app id (`tau_…`). Used to enforce app scoping. */\n appId: string\n /** TillAuth API base. Defaults to https://auth.tilldev.dev */\n apiBase?: string\n /**\n * Inline JWKS as a JWK array (the same `publicJwk` entries you'd find in\n * the TILLAUTH_JWT_KEYS env). Use this in environments where you want\n * fully air-gapped verification.\n */\n publicJwks?: JWK[]\n /** Require email_verified=true. Defaults to false. */\n requireVerifiedEmail?: boolean\n /** Require MFA was satisfied this session. Defaults to false. */\n requireMfa?: boolean\n}\n\nexport interface AuthClaims {\n /** User id */\n sub: string\n /** App id the token was issued for */\n app: string\n /** Lowercased email */\n email: string\n /** Whether MFA was satisfied this session */\n mfa: boolean\n /**\n * Whether the account's email was verified at issue time. Optional: tokens\n * minted before this claim existed omit it. When `requireVerifiedEmail` is\n * set, an absent claim is treated as unverified (fail-closed on the check).\n */\n email_verified?: boolean\n /** Issued-at (seconds) */\n iat: number\n /** Expires-at (seconds) */\n exp: number\n}\n\nexport class TillAuthVerifyError extends Error {\n code: 'no_token' | 'invalid_token' | 'wrong_app' | 'mfa_required' | 'email_not_verified'\n constructor(message: string, code: TillAuthVerifyError['code']) {\n super(message)\n this.name = 'TillAuthVerifyError'\n this.code = code\n }\n}\n\nexport class TillAuthVerifier {\n private readonly opts: Required<Pick<VerifyOptions, 'appId' | 'apiBase' | 'requireVerifiedEmail' | 'requireMfa'>>\n private readonly remote: ReturnType<typeof createRemoteJWKSet> | null\n private readonly inlineKeys: Promise<Array<{ kid?: string; key: Awaited<ReturnType<typeof importJWK>> }>> | null\n\n constructor(opts: VerifyOptions) {\n if (!opts.appId) throw new Error('TillAuthVerifier: appId is required')\n this.opts = {\n appId: opts.appId,\n apiBase: (opts.apiBase ?? DEFAULT_API).replace(/\\/$/, ''),\n requireVerifiedEmail: opts.requireVerifiedEmail ?? false,\n requireMfa: opts.requireMfa ?? false,\n }\n if (opts.publicJwks && opts.publicJwks.length > 0) {\n this.remote = null\n this.inlineKeys = Promise.all(\n opts.publicJwks.map(async (k) => ({ kid: k.kid, key: await importJWK(k, 'EdDSA') })),\n )\n } else {\n this.remote = createRemoteJWKSet(new URL(`${this.opts.apiBase}/.well-known/jwks.json`))\n this.inlineKeys = null\n }\n }\n\n async verify(token: string): Promise<AuthClaims> {\n if (!token) throw new TillAuthVerifyError('No token', 'no_token')\n\n let claims: AuthClaims\n try {\n if (this.inlineKeys) {\n const keys = await this.inlineKeys\n let lastErr: unknown\n for (const k of keys) {\n try {\n const { payload } = await jwtVerify(token, k.key, {\n algorithms: ['EdDSA'],\n issuer: ISSUER,\n })\n claims = payload as unknown as AuthClaims\n return this.applyClaimsChecks(claims)\n } catch (e) {\n lastErr = e\n }\n }\n throw lastErr ?? new Error('No key matched')\n }\n const { payload } = await jwtVerify(token, this.remote!, {\n algorithms: ['EdDSA'],\n issuer: ISSUER,\n })\n claims = payload as unknown as AuthClaims\n } catch (e) {\n throw new TillAuthVerifyError(\n e instanceof Error ? e.message : 'Invalid token',\n 'invalid_token',\n )\n }\n return this.applyClaimsChecks(claims)\n }\n\n private applyClaimsChecks(claims: AuthClaims): AuthClaims {\n if (claims.app !== this.opts.appId) {\n throw new TillAuthVerifyError(`Token issued for different app`, 'wrong_app')\n }\n if (this.opts.requireMfa && !claims.mfa) {\n throw new TillAuthVerifyError('MFA required', 'mfa_required')\n }\n // Absent claim (older tokens) is treated as unverified only when the check\n // is enabled — so requireVerifiedEmail fails closed rather than silently\n // passing when the issuer predates the claim.\n if (this.opts.requireVerifiedEmail && claims.email_verified !== true) {\n throw new TillAuthVerifyError('Email not verified', 'email_not_verified')\n }\n return claims\n }\n\n /**\n * Pull a Bearer token out of an Authorization header and verify. Returns\n * the claims or throws TillAuthVerifyError.\n */\n async verifyHeader(authHeader: string | undefined | null): Promise<AuthClaims> {\n if (!authHeader || !authHeader.startsWith('Bearer ')) {\n throw new TillAuthVerifyError('Missing Bearer token', 'no_token')\n }\n return this.verify(authHeader.slice(7))\n }\n}\n\n// ── Framework adapters ──────────────────────────────────────────────────────\n\n// Express-style middleware. Adds `req.user` on success.\nexport function expressMiddleware(verifier: TillAuthVerifier, opts: { onError?: (err: TillAuthVerifyError) => unknown } = {}) {\n return async function (req: ExpressLikeReq, res: ExpressLikeRes, next: () => void) {\n try {\n const h = req.headers?.authorization\n const claims = await verifier.verifyHeader(Array.isArray(h) ? h[0] : h)\n ;(req as ExpressLikeReq & { user: AuthClaims }).user = claims\n next()\n } catch (e) {\n const err = e instanceof TillAuthVerifyError ? e : new TillAuthVerifyError('Auth failed', 'invalid_token')\n if (opts.onError) {\n const out = opts.onError(err)\n if (out !== undefined) {\n res.status(401).json(out as Record<string, unknown>)\n return\n }\n }\n res.status(401).json({ error: err.message, code: err.code })\n }\n }\n}\n\ninterface ExpressLikeReq {\n headers?: { authorization?: string | string[] }\n}\ninterface ExpressLikeRes {\n status: (n: number) => { json: (b: Record<string, unknown>) => void }\n}\n\n// Fastify plugin shape — not bound to Fastify's types to avoid a peer dep.\nexport function fastifyHook(verifier: TillAuthVerifier) {\n return async function (\n req: { headers: Record<string, string | string[] | undefined>; user?: AuthClaims },\n reply: { code: (n: number) => { send: (b: unknown) => void } },\n ) {\n try {\n const h = req.headers['authorization']\n const auth = Array.isArray(h) ? h[0] : h\n const claims = await verifier.verifyHeader(auth)\n req.user = claims\n } catch (e) {\n const err = e instanceof TillAuthVerifyError ? e : new TillAuthVerifyError('Auth failed', 'invalid_token')\n reply.code(401).send({ error: err.message, code: err.code })\n }\n }\n}\n\n// Convenience entrypoint for users who want a one-call setup.\nexport function createVerifier(opts: VerifyOptions): TillAuthVerifier {\n return new TillAuthVerifier(opts)\n}\n\nexport { createFetchHandler, expressCookieProxy } from './cookie-proxy.js'\nexport type { CookieProxyOptions } from './cookie-proxy.js'\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cookie-proxy.ts","../src/index.ts"],"names":["DEFAULT_API","importJWK","createRemoteJWKSet","payload","jwtVerify"],"mappings":"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","file":"index.cjs","sourcesContent":["// Cookie-mode proxy for TillAuth.\n//\n// Mount this on the customer's backend (typically at /api/auth/*). It\n// forwards JSON bodies to TillAuth's /v1/* endpoints, then translates\n// access_token + refresh_token in the response into HttpOnly cookies on\n// the customer's domain. The browser's localStorage never sees the\n// tokens — XSS can't exfiltrate them.\n//\n// Two adapters: a generic web-fetch handler (Cloudflare Workers, Next.js\n// Route Handlers, Bun, Deno) and an Express middleware. Pick one.\n\nconst DEFAULT_API = 'https://auth.tilldev.dev'\n\nexport interface CookieProxyOptions {\n /** TillAuth public app id (`tau_…`). */\n appId: string\n /** Override the TillAuth API base. */\n apiBase?: string\n /** Cookie name for the access token. Default `tau_at`. */\n accessCookie?: string\n /** Cookie name for the refresh token. Default `tau_rt`. */\n refreshCookie?: string\n /** Cookie path. Default '/'. */\n cookiePath?: string\n /** Cookie domain (e.g. `.example.com` to share across subdomains). */\n cookieDomain?: string\n /** Secure flag. Defaults to true in production, false otherwise. */\n secure?: boolean\n}\n\ninterface NormalizedRequest {\n method: string\n pathname: string // path relative to the mount point (`/signin`, `/refresh`)\n body: string\n cookies: Record<string, string>\n}\ninterface NormalizedResponse {\n status: number\n body: string\n setCookies: string[]\n headers: Record<string, string>\n}\n\ninterface AuthBody {\n access_token?: string\n refresh_token?: string\n refresh_expires_at?: string\n [key: string]: unknown\n}\n\nfunction resolve(opts: CookieProxyOptions): Required<CookieProxyOptions> {\n return {\n appId: opts.appId,\n apiBase: (opts.apiBase ?? DEFAULT_API).replace(/\\/$/, ''),\n accessCookie: opts.accessCookie ?? 'tau_at',\n refreshCookie: opts.refreshCookie ?? 'tau_rt',\n cookiePath: opts.cookiePath ?? '/',\n cookieDomain: opts.cookieDomain ?? '',\n secure: opts.secure ?? process.env.NODE_ENV === 'production',\n }\n}\n\nfunction makeCookie(\n name: string,\n value: string,\n o: Required<CookieProxyOptions>,\n opts: { maxAge?: number; clear?: boolean } = {},\n): string {\n const parts = [`${name}=${opts.clear ? '' : value}`]\n parts.push('HttpOnly')\n if (o.secure) parts.push('Secure')\n parts.push('SameSite=Lax')\n parts.push(`Path=${o.cookiePath}`)\n if (o.cookieDomain) parts.push(`Domain=${o.cookieDomain}`)\n if (opts.clear) parts.push('Max-Age=0')\n else if (opts.maxAge !== undefined) parts.push(`Max-Age=${opts.maxAge}`)\n return parts.join('; ')\n}\n\nasync function handleProxy(req: NormalizedRequest, raw: CookieProxyOptions): Promise<NormalizedResponse> {\n const opts = resolve(raw)\n const path = req.pathname.replace(/^\\/+/, '')\n\n // Map proxy paths → TillAuth paths.\n const url = `${opts.apiBase}/v1/${path}`\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'X-TillAuth-App': opts.appId,\n 'User-Agent': 'tilldev-auth-node/cookie-proxy',\n }\n\n // /me, /refresh, /signout need to carry tokens from cookies, not the body.\n let body = req.body\n if (path === 'me' && req.cookies[opts.accessCookie]) {\n headers.Authorization = `Bearer ${req.cookies[opts.accessCookie]}`\n }\n if (path === 'refresh') {\n const rt = req.cookies[opts.refreshCookie]\n if (!rt) {\n return {\n status: 401,\n body: JSON.stringify({ error: 'no refresh cookie' }),\n setCookies: [],\n headers: { 'Content-Type': 'application/json' },\n }\n }\n body = JSON.stringify({ refresh_token: rt })\n }\n if (path === 'signout') {\n const rt = req.cookies[opts.refreshCookie]\n body = JSON.stringify({ refresh_token: rt ?? '' })\n }\n\n const upstream = await fetch(url, { method: req.method, headers, body })\n const text = await upstream.text()\n let json: AuthBody = {}\n try {\n json = JSON.parse(text) as AuthBody\n } catch {\n json = {}\n }\n\n const setCookies: string[] = []\n // Always strip token fields before returning to the SPA.\n const out = { ...json }\n\n if (typeof json.access_token === 'string') {\n setCookies.push(makeCookie(opts.accessCookie, json.access_token, opts, { maxAge: 60 * 10 }))\n delete out.access_token\n }\n if (typeof json.refresh_token === 'string') {\n // 30d in line with the Fastify REFRESH_TTL_MS.\n setCookies.push(makeCookie(opts.refreshCookie, json.refresh_token, opts, { maxAge: 60 * 60 * 24 * 30 }))\n delete out.refresh_token\n delete out.refresh_expires_at\n }\n if (path === 'signout') {\n setCookies.push(makeCookie(opts.accessCookie, '', opts, { clear: true }))\n setCookies.push(makeCookie(opts.refreshCookie, '', opts, { clear: true }))\n }\n\n return {\n status: upstream.status,\n body: JSON.stringify(out),\n setCookies,\n headers: { 'Content-Type': 'application/json' },\n }\n}\n\n// ── Adapter 1: web-fetch (Next.js Route Handlers, Workers, Bun, Deno) ───────\n\nexport function createFetchHandler(opts: CookieProxyOptions) {\n return async function handler(req: Request, ctx: { params?: { path?: string | string[] } } = {}): Promise<Response> {\n const url = new URL(req.url)\n // Derive the proxy-relative path. Prefer ctx.params.path (Next.js catch-all);\n // fall back to the part of the URL after the mount point heuristic.\n let pathname: string\n if (ctx.params?.path) {\n pathname = Array.isArray(ctx.params.path) ? `/${ctx.params.path.join('/')}` : `/${ctx.params.path}`\n } else {\n pathname = url.pathname.replace(/^\\/api\\/auth/, '')\n }\n const cookies = parseCookieHeader(req.headers.get('cookie'))\n const body = req.method === 'POST' ? await req.text() : '{}'\n const out = await handleProxy({ method: req.method, pathname, body, cookies }, opts)\n const headers = new Headers(out.headers)\n for (const c of out.setCookies) headers.append('Set-Cookie', c)\n return new Response(out.body, { status: out.status, headers })\n }\n}\n\n// ── Adapter 2: Express ──────────────────────────────────────────────────────\n\ninterface ExpReq {\n method: string\n url?: string\n originalUrl?: string\n path?: string\n body?: unknown\n headers: Record<string, string | string[] | undefined>\n}\ninterface ExpRes {\n status(n: number): ExpRes\n setHeader(name: string, value: string | string[]): void\n json(body: unknown): void\n}\n\nexport function expressCookieProxy(opts: CookieProxyOptions) {\n return async function (req: ExpReq, res: ExpRes): Promise<void> {\n const path =\n req.path ?? (req.originalUrl ?? req.url ?? '/').replace(/^\\/api\\/auth/, '').split('?')[0] ?? '/'\n const cookieHeader =\n typeof req.headers.cookie === 'string' ? req.headers.cookie : Array.isArray(req.headers.cookie) ? req.headers.cookie[0] : ''\n const body =\n typeof req.body === 'string' ? req.body : req.body ? JSON.stringify(req.body) : '{}'\n const out = await handleProxy(\n {\n method: req.method,\n pathname: path,\n body,\n cookies: parseCookieHeader(cookieHeader ?? null),\n },\n opts,\n )\n res.status(out.status)\n if (out.setCookies.length > 0) res.setHeader('Set-Cookie', out.setCookies)\n for (const [k, v] of Object.entries(out.headers)) res.setHeader(k, v)\n res.json(JSON.parse(out.body))\n }\n}\n\nfunction parseCookieHeader(header: string | null): Record<string, string> {\n const out: Record<string, string> = {}\n if (!header) return out\n for (const part of header.split(';')) {\n const [k, ...rest] = part.trim().split('=')\n if (!k) continue\n out[k] = decodeURIComponent(rest.join('='))\n }\n return out\n}\n","import { jwtVerify, createRemoteJWKSet, importJWK, type JWK } from 'jose'\n\n// Server-side TillAuth verification.\n//\n// You configure once with the issuer + JWKS endpoint (or inline public keys),\n// then call `verify(token)` or use the framework-specific middleware.\n//\n// Verification is offline against the cached JWKS — no round-trip to TillAuth\n// on the hot path.\n\nconst DEFAULT_API = 'https://auth.tilldev.dev'\nconst LEGACY_ISSUER = 'tillauth'\nconst REALM_SEGMENT = '/realms/'\n\nfunction trimSlash(s: string): string {\n return s.replace(/\\/+$/, '')\n}\n\n/**\n * Dual-accept issuer check. TillAuth is migrating access tokens from the legacy\n * `\"tillauth\"` string to a canonical HTTPS issuer (equal to `apiBase`) and, for\n * dedicated realms, a per-realm issuer `{apiBase}/realms/<app>`. During the\n * migration window a verifier accepts all three. Pass `pinnedIssuer` (the\n * `issuer` option) to hard-pin to exactly one realm's issuer instead.\n */\nfunction issuerAccepted(iss: unknown, canonical: string, pinnedIssuer?: string): boolean {\n if (typeof iss !== 'string' || iss.length === 0) return false\n if (iss === LEGACY_ISSUER) return true // legacy window\n if (pinnedIssuer) return iss === trimSlash(pinnedIssuer)\n const canon = trimSlash(canonical)\n if (iss === canon) return true\n const prefix = `${canon}${REALM_SEGMENT}`\n return iss.startsWith(prefix) && iss.length > prefix.length\n}\n\nexport interface VerifyOptions {\n /** TillAuth public app id (`tau_…`). Used to enforce app scoping. */\n appId: string\n /** TillAuth API base. Defaults to https://auth.tilldev.dev */\n apiBase?: string\n /**\n * Pin verification to exactly one issuer (e.g. a dedicated realm's\n * `https://auth.tilldev.dev/realms/<app>`). When omitted, the verifier accepts\n * the legacy string, the canonical HTTPS issuer (derived from `apiBase`), and\n * any per-realm issuer under it — the migration-window default.\n */\n issuer?: string\n /**\n * Inline JWKS as a JWK array (the same `publicJwk` entries you'd find in\n * the TILLAUTH_JWT_KEYS env). Use this in environments where you want\n * fully air-gapped verification.\n */\n publicJwks?: JWK[]\n /** Require email_verified=true. Defaults to false. */\n requireVerifiedEmail?: boolean\n /** Require MFA was satisfied this session. Defaults to false. */\n requireMfa?: boolean\n}\n\nexport interface AuthClaims {\n /** User id */\n sub: string\n /** App id the token was issued for */\n app: string\n /** Lowercased email */\n email: string\n /** Whether MFA was satisfied this session */\n mfa: boolean\n /**\n * Whether the account's email was verified at issue time. Optional: tokens\n * minted before this claim existed omit it. When `requireVerifiedEmail` is\n * set, an absent claim is treated as unverified (fail-closed on the check).\n */\n email_verified?: boolean\n /** Token issuer — validated against the accepted set (see `issuerAccepted`). */\n iss?: string\n /** Issued-at (seconds) */\n iat: number\n /** Expires-at (seconds) */\n exp: number\n}\n\nexport class TillAuthVerifyError extends Error {\n code: 'no_token' | 'invalid_token' | 'wrong_app' | 'mfa_required' | 'email_not_verified' | 'invalid_issuer'\n constructor(message: string, code: TillAuthVerifyError['code']) {\n super(message)\n this.name = 'TillAuthVerifyError'\n this.code = code\n }\n}\n\nexport class TillAuthVerifier {\n private readonly opts: Required<Pick<VerifyOptions, 'appId' | 'apiBase' | 'requireVerifiedEmail' | 'requireMfa'>>\n private readonly pinnedIssuer: string | undefined\n private readonly remote: ReturnType<typeof createRemoteJWKSet> | null\n private readonly inlineKeys: Promise<Array<{ kid?: string; key: Awaited<ReturnType<typeof importJWK>> }>> | null\n\n constructor(opts: VerifyOptions) {\n if (!opts.appId) throw new Error('TillAuthVerifier: appId is required')\n this.opts = {\n appId: opts.appId,\n apiBase: (opts.apiBase ?? DEFAULT_API).replace(/\\/$/, ''),\n requireVerifiedEmail: opts.requireVerifiedEmail ?? false,\n requireMfa: opts.requireMfa ?? false,\n }\n this.pinnedIssuer = opts.issuer ? opts.issuer.replace(/\\/$/, '') : undefined\n if (opts.publicJwks && opts.publicJwks.length > 0) {\n this.remote = null\n this.inlineKeys = Promise.all(\n opts.publicJwks.map(async (k) => ({ kid: k.kid, key: await importJWK(k, 'EdDSA') })),\n )\n } else {\n this.remote = createRemoteJWKSet(new URL(`${this.opts.apiBase}/.well-known/jwks.json`))\n this.inlineKeys = null\n }\n }\n\n async verify(token: string): Promise<AuthClaims> {\n if (!token) throw new TillAuthVerifyError('No token', 'no_token')\n\n let claims: AuthClaims\n try {\n if (this.inlineKeys) {\n const keys = await this.inlineKeys\n let lastErr: unknown\n for (const k of keys) {\n try {\n // No jose `issuer` pin — the issuer is one of several accepted values\n // (legacy string / canonical HTTPS / per-realm), checked below.\n const { payload } = await jwtVerify(token, k.key, { algorithms: ['EdDSA'] })\n claims = payload as unknown as AuthClaims\n return this.applyClaimsChecks(claims)\n } catch (e) {\n lastErr = e\n }\n }\n throw lastErr ?? new Error('No key matched')\n }\n const { payload } = await jwtVerify(token, this.remote!, { algorithms: ['EdDSA'] })\n claims = payload as unknown as AuthClaims\n } catch (e) {\n // Preserve a specific claims verdict (wrong_app / invalid_issuer) thrown by\n // applyClaimsChecks on the inline path; only a genuine signature/JWKS\n // failure becomes the generic invalid_token.\n if (e instanceof TillAuthVerifyError) throw e\n throw new TillAuthVerifyError(\n e instanceof Error ? e.message : 'Invalid token',\n 'invalid_token',\n )\n }\n return this.applyClaimsChecks(claims)\n }\n\n private applyClaimsChecks(claims: AuthClaims): AuthClaims {\n // Issuer is validated here (jose's exact `issuer` pin is intentionally not\n // used) so the legacy→HTTPS→per-realm migration doesn't break verification.\n if (!issuerAccepted(claims.iss, this.opts.apiBase, this.pinnedIssuer)) {\n throw new TillAuthVerifyError('Untrusted token issuer', 'invalid_issuer')\n }\n if (claims.app !== this.opts.appId) {\n throw new TillAuthVerifyError(`Token issued for different app`, 'wrong_app')\n }\n if (this.opts.requireMfa && !claims.mfa) {\n throw new TillAuthVerifyError('MFA required', 'mfa_required')\n }\n // Absent claim (older tokens) is treated as unverified only when the check\n // is enabled — so requireVerifiedEmail fails closed rather than silently\n // passing when the issuer predates the claim.\n if (this.opts.requireVerifiedEmail && claims.email_verified !== true) {\n throw new TillAuthVerifyError('Email not verified', 'email_not_verified')\n }\n return claims\n }\n\n /**\n * Pull a Bearer token out of an Authorization header and verify. Returns\n * the claims or throws TillAuthVerifyError.\n */\n async verifyHeader(authHeader: string | undefined | null): Promise<AuthClaims> {\n if (!authHeader || !authHeader.startsWith('Bearer ')) {\n throw new TillAuthVerifyError('Missing Bearer token', 'no_token')\n }\n return this.verify(authHeader.slice(7))\n }\n}\n\n// ── Framework adapters ──────────────────────────────────────────────────────\n\n// Express-style middleware. Adds `req.user` on success.\nexport function expressMiddleware(verifier: TillAuthVerifier, opts: { onError?: (err: TillAuthVerifyError) => unknown } = {}) {\n return async function (req: ExpressLikeReq, res: ExpressLikeRes, next: () => void) {\n try {\n const h = req.headers?.authorization\n const claims = await verifier.verifyHeader(Array.isArray(h) ? h[0] : h)\n ;(req as ExpressLikeReq & { user: AuthClaims }).user = claims\n next()\n } catch (e) {\n const err = e instanceof TillAuthVerifyError ? e : new TillAuthVerifyError('Auth failed', 'invalid_token')\n if (opts.onError) {\n const out = opts.onError(err)\n if (out !== undefined) {\n res.status(401).json(out as Record<string, unknown>)\n return\n }\n }\n res.status(401).json({ error: err.message, code: err.code })\n }\n }\n}\n\ninterface ExpressLikeReq {\n headers?: { authorization?: string | string[] }\n}\ninterface ExpressLikeRes {\n status: (n: number) => { json: (b: Record<string, unknown>) => void }\n}\n\n// Fastify plugin shape — not bound to Fastify's types to avoid a peer dep.\nexport function fastifyHook(verifier: TillAuthVerifier) {\n return async function (\n req: { headers: Record<string, string | string[] | undefined>; user?: AuthClaims },\n reply: { code: (n: number) => { send: (b: unknown) => void } },\n ) {\n try {\n const h = req.headers['authorization']\n const auth = Array.isArray(h) ? h[0] : h\n const claims = await verifier.verifyHeader(auth)\n req.user = claims\n } catch (e) {\n const err = e instanceof TillAuthVerifyError ? e : new TillAuthVerifyError('Auth failed', 'invalid_token')\n reply.code(401).send({ error: err.message, code: err.code })\n }\n }\n}\n\n// Convenience entrypoint for users who want a one-call setup.\nexport function createVerifier(opts: VerifyOptions): TillAuthVerifier {\n return new TillAuthVerifier(opts)\n}\n\nexport { createFetchHandler, expressCookieProxy } from './cookie-proxy.js'\nexport type { CookieProxyOptions } from './cookie-proxy.js'\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -41,6 +41,13 @@ interface VerifyOptions {
|
|
|
41
41
|
appId: string;
|
|
42
42
|
/** TillAuth API base. Defaults to https://auth.tilldev.dev */
|
|
43
43
|
apiBase?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Pin verification to exactly one issuer (e.g. a dedicated realm's
|
|
46
|
+
* `https://auth.tilldev.dev/realms/<app>`). When omitted, the verifier accepts
|
|
47
|
+
* the legacy string, the canonical HTTPS issuer (derived from `apiBase`), and
|
|
48
|
+
* any per-realm issuer under it — the migration-window default.
|
|
49
|
+
*/
|
|
50
|
+
issuer?: string;
|
|
44
51
|
/**
|
|
45
52
|
* Inline JWKS as a JWK array (the same `publicJwk` entries you'd find in
|
|
46
53
|
* the TILLAUTH_JWT_KEYS env). Use this in environments where you want
|
|
@@ -67,17 +74,20 @@ interface AuthClaims {
|
|
|
67
74
|
* set, an absent claim is treated as unverified (fail-closed on the check).
|
|
68
75
|
*/
|
|
69
76
|
email_verified?: boolean;
|
|
77
|
+
/** Token issuer — validated against the accepted set (see `issuerAccepted`). */
|
|
78
|
+
iss?: string;
|
|
70
79
|
/** Issued-at (seconds) */
|
|
71
80
|
iat: number;
|
|
72
81
|
/** Expires-at (seconds) */
|
|
73
82
|
exp: number;
|
|
74
83
|
}
|
|
75
84
|
declare class TillAuthVerifyError extends Error {
|
|
76
|
-
code: 'no_token' | 'invalid_token' | 'wrong_app' | 'mfa_required' | 'email_not_verified';
|
|
85
|
+
code: 'no_token' | 'invalid_token' | 'wrong_app' | 'mfa_required' | 'email_not_verified' | 'invalid_issuer';
|
|
77
86
|
constructor(message: string, code: TillAuthVerifyError['code']);
|
|
78
87
|
}
|
|
79
88
|
declare class TillAuthVerifier {
|
|
80
89
|
private readonly opts;
|
|
90
|
+
private readonly pinnedIssuer;
|
|
81
91
|
private readonly remote;
|
|
82
92
|
private readonly inlineKeys;
|
|
83
93
|
constructor(opts: VerifyOptions);
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,13 @@ interface VerifyOptions {
|
|
|
41
41
|
appId: string;
|
|
42
42
|
/** TillAuth API base. Defaults to https://auth.tilldev.dev */
|
|
43
43
|
apiBase?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Pin verification to exactly one issuer (e.g. a dedicated realm's
|
|
46
|
+
* `https://auth.tilldev.dev/realms/<app>`). When omitted, the verifier accepts
|
|
47
|
+
* the legacy string, the canonical HTTPS issuer (derived from `apiBase`), and
|
|
48
|
+
* any per-realm issuer under it — the migration-window default.
|
|
49
|
+
*/
|
|
50
|
+
issuer?: string;
|
|
44
51
|
/**
|
|
45
52
|
* Inline JWKS as a JWK array (the same `publicJwk` entries you'd find in
|
|
46
53
|
* the TILLAUTH_JWT_KEYS env). Use this in environments where you want
|
|
@@ -67,17 +74,20 @@ interface AuthClaims {
|
|
|
67
74
|
* set, an absent claim is treated as unverified (fail-closed on the check).
|
|
68
75
|
*/
|
|
69
76
|
email_verified?: boolean;
|
|
77
|
+
/** Token issuer — validated against the accepted set (see `issuerAccepted`). */
|
|
78
|
+
iss?: string;
|
|
70
79
|
/** Issued-at (seconds) */
|
|
71
80
|
iat: number;
|
|
72
81
|
/** Expires-at (seconds) */
|
|
73
82
|
exp: number;
|
|
74
83
|
}
|
|
75
84
|
declare class TillAuthVerifyError extends Error {
|
|
76
|
-
code: 'no_token' | 'invalid_token' | 'wrong_app' | 'mfa_required' | 'email_not_verified';
|
|
85
|
+
code: 'no_token' | 'invalid_token' | 'wrong_app' | 'mfa_required' | 'email_not_verified' | 'invalid_issuer';
|
|
77
86
|
constructor(message: string, code: TillAuthVerifyError['code']);
|
|
78
87
|
}
|
|
79
88
|
declare class TillAuthVerifier {
|
|
80
89
|
private readonly opts;
|
|
90
|
+
private readonly pinnedIssuer;
|
|
81
91
|
private readonly remote;
|
|
82
92
|
private readonly inlineKeys;
|
|
83
93
|
constructor(opts: VerifyOptions);
|
package/dist/index.js
CHANGED
|
@@ -135,7 +135,20 @@ function parseCookieHeader(header) {
|
|
|
135
135
|
|
|
136
136
|
// src/index.ts
|
|
137
137
|
var DEFAULT_API2 = "https://auth.tilldev.dev";
|
|
138
|
-
var
|
|
138
|
+
var LEGACY_ISSUER = "tillauth";
|
|
139
|
+
var REALM_SEGMENT = "/realms/";
|
|
140
|
+
function trimSlash(s) {
|
|
141
|
+
return s.replace(/\/+$/, "");
|
|
142
|
+
}
|
|
143
|
+
function issuerAccepted(iss, canonical, pinnedIssuer) {
|
|
144
|
+
if (typeof iss !== "string" || iss.length === 0) return false;
|
|
145
|
+
if (iss === LEGACY_ISSUER) return true;
|
|
146
|
+
if (pinnedIssuer) return iss === trimSlash(pinnedIssuer);
|
|
147
|
+
const canon = trimSlash(canonical);
|
|
148
|
+
if (iss === canon) return true;
|
|
149
|
+
const prefix = `${canon}${REALM_SEGMENT}`;
|
|
150
|
+
return iss.startsWith(prefix) && iss.length > prefix.length;
|
|
151
|
+
}
|
|
139
152
|
var TillAuthVerifyError = class extends Error {
|
|
140
153
|
constructor(message, code) {
|
|
141
154
|
super(message);
|
|
@@ -152,6 +165,7 @@ var TillAuthVerifier = class {
|
|
|
152
165
|
requireVerifiedEmail: opts.requireVerifiedEmail ?? false,
|
|
153
166
|
requireMfa: opts.requireMfa ?? false
|
|
154
167
|
};
|
|
168
|
+
this.pinnedIssuer = opts.issuer ? opts.issuer.replace(/\/$/, "") : void 0;
|
|
155
169
|
if (opts.publicJwks && opts.publicJwks.length > 0) {
|
|
156
170
|
this.remote = null;
|
|
157
171
|
this.inlineKeys = Promise.all(
|
|
@@ -171,10 +185,7 @@ var TillAuthVerifier = class {
|
|
|
171
185
|
let lastErr;
|
|
172
186
|
for (const k of keys) {
|
|
173
187
|
try {
|
|
174
|
-
const { payload: payload2 } = await jwtVerify(token, k.key, {
|
|
175
|
-
algorithms: ["EdDSA"],
|
|
176
|
-
issuer: ISSUER
|
|
177
|
-
});
|
|
188
|
+
const { payload: payload2 } = await jwtVerify(token, k.key, { algorithms: ["EdDSA"] });
|
|
178
189
|
claims = payload2;
|
|
179
190
|
return this.applyClaimsChecks(claims);
|
|
180
191
|
} catch (e) {
|
|
@@ -183,12 +194,10 @@ var TillAuthVerifier = class {
|
|
|
183
194
|
}
|
|
184
195
|
throw lastErr ?? new Error("No key matched");
|
|
185
196
|
}
|
|
186
|
-
const { payload } = await jwtVerify(token, this.remote, {
|
|
187
|
-
algorithms: ["EdDSA"],
|
|
188
|
-
issuer: ISSUER
|
|
189
|
-
});
|
|
197
|
+
const { payload } = await jwtVerify(token, this.remote, { algorithms: ["EdDSA"] });
|
|
190
198
|
claims = payload;
|
|
191
199
|
} catch (e) {
|
|
200
|
+
if (e instanceof TillAuthVerifyError) throw e;
|
|
192
201
|
throw new TillAuthVerifyError(
|
|
193
202
|
e instanceof Error ? e.message : "Invalid token",
|
|
194
203
|
"invalid_token"
|
|
@@ -197,6 +206,9 @@ var TillAuthVerifier = class {
|
|
|
197
206
|
return this.applyClaimsChecks(claims);
|
|
198
207
|
}
|
|
199
208
|
applyClaimsChecks(claims) {
|
|
209
|
+
if (!issuerAccepted(claims.iss, this.opts.apiBase, this.pinnedIssuer)) {
|
|
210
|
+
throw new TillAuthVerifyError("Untrusted token issuer", "invalid_issuer");
|
|
211
|
+
}
|
|
200
212
|
if (claims.app !== this.opts.appId) {
|
|
201
213
|
throw new TillAuthVerifyError(`Token issued for different app`, "wrong_app");
|
|
202
214
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cookie-proxy.ts","../src/index.ts"],"names":["DEFAULT_API","payload"],"mappings":"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","file":"index.js","sourcesContent":["// Cookie-mode proxy for TillAuth.\n//\n// Mount this on the customer's backend (typically at /api/auth/*). It\n// forwards JSON bodies to TillAuth's /v1/* endpoints, then translates\n// access_token + refresh_token in the response into HttpOnly cookies on\n// the customer's domain. The browser's localStorage never sees the\n// tokens — XSS can't exfiltrate them.\n//\n// Two adapters: a generic web-fetch handler (Cloudflare Workers, Next.js\n// Route Handlers, Bun, Deno) and an Express middleware. Pick one.\n\nconst DEFAULT_API = 'https://auth.tilldev.dev'\n\nexport interface CookieProxyOptions {\n /** TillAuth public app id (`tau_…`). */\n appId: string\n /** Override the TillAuth API base. */\n apiBase?: string\n /** Cookie name for the access token. Default `tau_at`. */\n accessCookie?: string\n /** Cookie name for the refresh token. Default `tau_rt`. */\n refreshCookie?: string\n /** Cookie path. Default '/'. */\n cookiePath?: string\n /** Cookie domain (e.g. `.example.com` to share across subdomains). */\n cookieDomain?: string\n /** Secure flag. Defaults to true in production, false otherwise. */\n secure?: boolean\n}\n\ninterface NormalizedRequest {\n method: string\n pathname: string // path relative to the mount point (`/signin`, `/refresh`)\n body: string\n cookies: Record<string, string>\n}\ninterface NormalizedResponse {\n status: number\n body: string\n setCookies: string[]\n headers: Record<string, string>\n}\n\ninterface AuthBody {\n access_token?: string\n refresh_token?: string\n refresh_expires_at?: string\n [key: string]: unknown\n}\n\nfunction resolve(opts: CookieProxyOptions): Required<CookieProxyOptions> {\n return {\n appId: opts.appId,\n apiBase: (opts.apiBase ?? DEFAULT_API).replace(/\\/$/, ''),\n accessCookie: opts.accessCookie ?? 'tau_at',\n refreshCookie: opts.refreshCookie ?? 'tau_rt',\n cookiePath: opts.cookiePath ?? '/',\n cookieDomain: opts.cookieDomain ?? '',\n secure: opts.secure ?? process.env.NODE_ENV === 'production',\n }\n}\n\nfunction makeCookie(\n name: string,\n value: string,\n o: Required<CookieProxyOptions>,\n opts: { maxAge?: number; clear?: boolean } = {},\n): string {\n const parts = [`${name}=${opts.clear ? '' : value}`]\n parts.push('HttpOnly')\n if (o.secure) parts.push('Secure')\n parts.push('SameSite=Lax')\n parts.push(`Path=${o.cookiePath}`)\n if (o.cookieDomain) parts.push(`Domain=${o.cookieDomain}`)\n if (opts.clear) parts.push('Max-Age=0')\n else if (opts.maxAge !== undefined) parts.push(`Max-Age=${opts.maxAge}`)\n return parts.join('; ')\n}\n\nasync function handleProxy(req: NormalizedRequest, raw: CookieProxyOptions): Promise<NormalizedResponse> {\n const opts = resolve(raw)\n const path = req.pathname.replace(/^\\/+/, '')\n\n // Map proxy paths → TillAuth paths.\n const url = `${opts.apiBase}/v1/${path}`\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'X-TillAuth-App': opts.appId,\n 'User-Agent': 'tilldev-auth-node/cookie-proxy',\n }\n\n // /me, /refresh, /signout need to carry tokens from cookies, not the body.\n let body = req.body\n if (path === 'me' && req.cookies[opts.accessCookie]) {\n headers.Authorization = `Bearer ${req.cookies[opts.accessCookie]}`\n }\n if (path === 'refresh') {\n const rt = req.cookies[opts.refreshCookie]\n if (!rt) {\n return {\n status: 401,\n body: JSON.stringify({ error: 'no refresh cookie' }),\n setCookies: [],\n headers: { 'Content-Type': 'application/json' },\n }\n }\n body = JSON.stringify({ refresh_token: rt })\n }\n if (path === 'signout') {\n const rt = req.cookies[opts.refreshCookie]\n body = JSON.stringify({ refresh_token: rt ?? '' })\n }\n\n const upstream = await fetch(url, { method: req.method, headers, body })\n const text = await upstream.text()\n let json: AuthBody = {}\n try {\n json = JSON.parse(text) as AuthBody\n } catch {\n json = {}\n }\n\n const setCookies: string[] = []\n // Always strip token fields before returning to the SPA.\n const out = { ...json }\n\n if (typeof json.access_token === 'string') {\n setCookies.push(makeCookie(opts.accessCookie, json.access_token, opts, { maxAge: 60 * 10 }))\n delete out.access_token\n }\n if (typeof json.refresh_token === 'string') {\n // 30d in line with the Fastify REFRESH_TTL_MS.\n setCookies.push(makeCookie(opts.refreshCookie, json.refresh_token, opts, { maxAge: 60 * 60 * 24 * 30 }))\n delete out.refresh_token\n delete out.refresh_expires_at\n }\n if (path === 'signout') {\n setCookies.push(makeCookie(opts.accessCookie, '', opts, { clear: true }))\n setCookies.push(makeCookie(opts.refreshCookie, '', opts, { clear: true }))\n }\n\n return {\n status: upstream.status,\n body: JSON.stringify(out),\n setCookies,\n headers: { 'Content-Type': 'application/json' },\n }\n}\n\n// ── Adapter 1: web-fetch (Next.js Route Handlers, Workers, Bun, Deno) ───────\n\nexport function createFetchHandler(opts: CookieProxyOptions) {\n return async function handler(req: Request, ctx: { params?: { path?: string | string[] } } = {}): Promise<Response> {\n const url = new URL(req.url)\n // Derive the proxy-relative path. Prefer ctx.params.path (Next.js catch-all);\n // fall back to the part of the URL after the mount point heuristic.\n let pathname: string\n if (ctx.params?.path) {\n pathname = Array.isArray(ctx.params.path) ? `/${ctx.params.path.join('/')}` : `/${ctx.params.path}`\n } else {\n pathname = url.pathname.replace(/^\\/api\\/auth/, '')\n }\n const cookies = parseCookieHeader(req.headers.get('cookie'))\n const body = req.method === 'POST' ? await req.text() : '{}'\n const out = await handleProxy({ method: req.method, pathname, body, cookies }, opts)\n const headers = new Headers(out.headers)\n for (const c of out.setCookies) headers.append('Set-Cookie', c)\n return new Response(out.body, { status: out.status, headers })\n }\n}\n\n// ── Adapter 2: Express ──────────────────────────────────────────────────────\n\ninterface ExpReq {\n method: string\n url?: string\n originalUrl?: string\n path?: string\n body?: unknown\n headers: Record<string, string | string[] | undefined>\n}\ninterface ExpRes {\n status(n: number): ExpRes\n setHeader(name: string, value: string | string[]): void\n json(body: unknown): void\n}\n\nexport function expressCookieProxy(opts: CookieProxyOptions) {\n return async function (req: ExpReq, res: ExpRes): Promise<void> {\n const path =\n req.path ?? (req.originalUrl ?? req.url ?? '/').replace(/^\\/api\\/auth/, '').split('?')[0] ?? '/'\n const cookieHeader =\n typeof req.headers.cookie === 'string' ? req.headers.cookie : Array.isArray(req.headers.cookie) ? req.headers.cookie[0] : ''\n const body =\n typeof req.body === 'string' ? req.body : req.body ? JSON.stringify(req.body) : '{}'\n const out = await handleProxy(\n {\n method: req.method,\n pathname: path,\n body,\n cookies: parseCookieHeader(cookieHeader ?? null),\n },\n opts,\n )\n res.status(out.status)\n if (out.setCookies.length > 0) res.setHeader('Set-Cookie', out.setCookies)\n for (const [k, v] of Object.entries(out.headers)) res.setHeader(k, v)\n res.json(JSON.parse(out.body))\n }\n}\n\nfunction parseCookieHeader(header: string | null): Record<string, string> {\n const out: Record<string, string> = {}\n if (!header) return out\n for (const part of header.split(';')) {\n const [k, ...rest] = part.trim().split('=')\n if (!k) continue\n out[k] = decodeURIComponent(rest.join('='))\n }\n return out\n}\n","import { jwtVerify, createRemoteJWKSet, importJWK, type JWK } from 'jose'\n\n// Server-side TillAuth verification.\n//\n// You configure once with the issuer + JWKS endpoint (or inline public keys),\n// then call `verify(token)` or use the framework-specific middleware.\n//\n// Verification is offline against the cached JWKS — no round-trip to TillAuth\n// on the hot path.\n\nconst DEFAULT_API = 'https://auth.tilldev.dev'\nconst ISSUER = 'tillauth'\n\nexport interface VerifyOptions {\n /** TillAuth public app id (`tau_…`). Used to enforce app scoping. */\n appId: string\n /** TillAuth API base. Defaults to https://auth.tilldev.dev */\n apiBase?: string\n /**\n * Inline JWKS as a JWK array (the same `publicJwk` entries you'd find in\n * the TILLAUTH_JWT_KEYS env). Use this in environments where you want\n * fully air-gapped verification.\n */\n publicJwks?: JWK[]\n /** Require email_verified=true. Defaults to false. */\n requireVerifiedEmail?: boolean\n /** Require MFA was satisfied this session. Defaults to false. */\n requireMfa?: boolean\n}\n\nexport interface AuthClaims {\n /** User id */\n sub: string\n /** App id the token was issued for */\n app: string\n /** Lowercased email */\n email: string\n /** Whether MFA was satisfied this session */\n mfa: boolean\n /**\n * Whether the account's email was verified at issue time. Optional: tokens\n * minted before this claim existed omit it. When `requireVerifiedEmail` is\n * set, an absent claim is treated as unverified (fail-closed on the check).\n */\n email_verified?: boolean\n /** Issued-at (seconds) */\n iat: number\n /** Expires-at (seconds) */\n exp: number\n}\n\nexport class TillAuthVerifyError extends Error {\n code: 'no_token' | 'invalid_token' | 'wrong_app' | 'mfa_required' | 'email_not_verified'\n constructor(message: string, code: TillAuthVerifyError['code']) {\n super(message)\n this.name = 'TillAuthVerifyError'\n this.code = code\n }\n}\n\nexport class TillAuthVerifier {\n private readonly opts: Required<Pick<VerifyOptions, 'appId' | 'apiBase' | 'requireVerifiedEmail' | 'requireMfa'>>\n private readonly remote: ReturnType<typeof createRemoteJWKSet> | null\n private readonly inlineKeys: Promise<Array<{ kid?: string; key: Awaited<ReturnType<typeof importJWK>> }>> | null\n\n constructor(opts: VerifyOptions) {\n if (!opts.appId) throw new Error('TillAuthVerifier: appId is required')\n this.opts = {\n appId: opts.appId,\n apiBase: (opts.apiBase ?? DEFAULT_API).replace(/\\/$/, ''),\n requireVerifiedEmail: opts.requireVerifiedEmail ?? false,\n requireMfa: opts.requireMfa ?? false,\n }\n if (opts.publicJwks && opts.publicJwks.length > 0) {\n this.remote = null\n this.inlineKeys = Promise.all(\n opts.publicJwks.map(async (k) => ({ kid: k.kid, key: await importJWK(k, 'EdDSA') })),\n )\n } else {\n this.remote = createRemoteJWKSet(new URL(`${this.opts.apiBase}/.well-known/jwks.json`))\n this.inlineKeys = null\n }\n }\n\n async verify(token: string): Promise<AuthClaims> {\n if (!token) throw new TillAuthVerifyError('No token', 'no_token')\n\n let claims: AuthClaims\n try {\n if (this.inlineKeys) {\n const keys = await this.inlineKeys\n let lastErr: unknown\n for (const k of keys) {\n try {\n const { payload } = await jwtVerify(token, k.key, {\n algorithms: ['EdDSA'],\n issuer: ISSUER,\n })\n claims = payload as unknown as AuthClaims\n return this.applyClaimsChecks(claims)\n } catch (e) {\n lastErr = e\n }\n }\n throw lastErr ?? new Error('No key matched')\n }\n const { payload } = await jwtVerify(token, this.remote!, {\n algorithms: ['EdDSA'],\n issuer: ISSUER,\n })\n claims = payload as unknown as AuthClaims\n } catch (e) {\n throw new TillAuthVerifyError(\n e instanceof Error ? e.message : 'Invalid token',\n 'invalid_token',\n )\n }\n return this.applyClaimsChecks(claims)\n }\n\n private applyClaimsChecks(claims: AuthClaims): AuthClaims {\n if (claims.app !== this.opts.appId) {\n throw new TillAuthVerifyError(`Token issued for different app`, 'wrong_app')\n }\n if (this.opts.requireMfa && !claims.mfa) {\n throw new TillAuthVerifyError('MFA required', 'mfa_required')\n }\n // Absent claim (older tokens) is treated as unverified only when the check\n // is enabled — so requireVerifiedEmail fails closed rather than silently\n // passing when the issuer predates the claim.\n if (this.opts.requireVerifiedEmail && claims.email_verified !== true) {\n throw new TillAuthVerifyError('Email not verified', 'email_not_verified')\n }\n return claims\n }\n\n /**\n * Pull a Bearer token out of an Authorization header and verify. Returns\n * the claims or throws TillAuthVerifyError.\n */\n async verifyHeader(authHeader: string | undefined | null): Promise<AuthClaims> {\n if (!authHeader || !authHeader.startsWith('Bearer ')) {\n throw new TillAuthVerifyError('Missing Bearer token', 'no_token')\n }\n return this.verify(authHeader.slice(7))\n }\n}\n\n// ── Framework adapters ──────────────────────────────────────────────────────\n\n// Express-style middleware. Adds `req.user` on success.\nexport function expressMiddleware(verifier: TillAuthVerifier, opts: { onError?: (err: TillAuthVerifyError) => unknown } = {}) {\n return async function (req: ExpressLikeReq, res: ExpressLikeRes, next: () => void) {\n try {\n const h = req.headers?.authorization\n const claims = await verifier.verifyHeader(Array.isArray(h) ? h[0] : h)\n ;(req as ExpressLikeReq & { user: AuthClaims }).user = claims\n next()\n } catch (e) {\n const err = e instanceof TillAuthVerifyError ? e : new TillAuthVerifyError('Auth failed', 'invalid_token')\n if (opts.onError) {\n const out = opts.onError(err)\n if (out !== undefined) {\n res.status(401).json(out as Record<string, unknown>)\n return\n }\n }\n res.status(401).json({ error: err.message, code: err.code })\n }\n }\n}\n\ninterface ExpressLikeReq {\n headers?: { authorization?: string | string[] }\n}\ninterface ExpressLikeRes {\n status: (n: number) => { json: (b: Record<string, unknown>) => void }\n}\n\n// Fastify plugin shape — not bound to Fastify's types to avoid a peer dep.\nexport function fastifyHook(verifier: TillAuthVerifier) {\n return async function (\n req: { headers: Record<string, string | string[] | undefined>; user?: AuthClaims },\n reply: { code: (n: number) => { send: (b: unknown) => void } },\n ) {\n try {\n const h = req.headers['authorization']\n const auth = Array.isArray(h) ? h[0] : h\n const claims = await verifier.verifyHeader(auth)\n req.user = claims\n } catch (e) {\n const err = e instanceof TillAuthVerifyError ? e : new TillAuthVerifyError('Auth failed', 'invalid_token')\n reply.code(401).send({ error: err.message, code: err.code })\n }\n }\n}\n\n// Convenience entrypoint for users who want a one-call setup.\nexport function createVerifier(opts: VerifyOptions): TillAuthVerifier {\n return new TillAuthVerifier(opts)\n}\n\nexport { createFetchHandler, expressCookieProxy } from './cookie-proxy.js'\nexport type { CookieProxyOptions } from './cookie-proxy.js'\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cookie-proxy.ts","../src/index.ts"],"names":["DEFAULT_API","payload"],"mappings":"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","file":"index.js","sourcesContent":["// Cookie-mode proxy for TillAuth.\n//\n// Mount this on the customer's backend (typically at /api/auth/*). It\n// forwards JSON bodies to TillAuth's /v1/* endpoints, then translates\n// access_token + refresh_token in the response into HttpOnly cookies on\n// the customer's domain. The browser's localStorage never sees the\n// tokens — XSS can't exfiltrate them.\n//\n// Two adapters: a generic web-fetch handler (Cloudflare Workers, Next.js\n// Route Handlers, Bun, Deno) and an Express middleware. Pick one.\n\nconst DEFAULT_API = 'https://auth.tilldev.dev'\n\nexport interface CookieProxyOptions {\n /** TillAuth public app id (`tau_…`). */\n appId: string\n /** Override the TillAuth API base. */\n apiBase?: string\n /** Cookie name for the access token. Default `tau_at`. */\n accessCookie?: string\n /** Cookie name for the refresh token. Default `tau_rt`. */\n refreshCookie?: string\n /** Cookie path. Default '/'. */\n cookiePath?: string\n /** Cookie domain (e.g. `.example.com` to share across subdomains). */\n cookieDomain?: string\n /** Secure flag. Defaults to true in production, false otherwise. */\n secure?: boolean\n}\n\ninterface NormalizedRequest {\n method: string\n pathname: string // path relative to the mount point (`/signin`, `/refresh`)\n body: string\n cookies: Record<string, string>\n}\ninterface NormalizedResponse {\n status: number\n body: string\n setCookies: string[]\n headers: Record<string, string>\n}\n\ninterface AuthBody {\n access_token?: string\n refresh_token?: string\n refresh_expires_at?: string\n [key: string]: unknown\n}\n\nfunction resolve(opts: CookieProxyOptions): Required<CookieProxyOptions> {\n return {\n appId: opts.appId,\n apiBase: (opts.apiBase ?? DEFAULT_API).replace(/\\/$/, ''),\n accessCookie: opts.accessCookie ?? 'tau_at',\n refreshCookie: opts.refreshCookie ?? 'tau_rt',\n cookiePath: opts.cookiePath ?? '/',\n cookieDomain: opts.cookieDomain ?? '',\n secure: opts.secure ?? process.env.NODE_ENV === 'production',\n }\n}\n\nfunction makeCookie(\n name: string,\n value: string,\n o: Required<CookieProxyOptions>,\n opts: { maxAge?: number; clear?: boolean } = {},\n): string {\n const parts = [`${name}=${opts.clear ? '' : value}`]\n parts.push('HttpOnly')\n if (o.secure) parts.push('Secure')\n parts.push('SameSite=Lax')\n parts.push(`Path=${o.cookiePath}`)\n if (o.cookieDomain) parts.push(`Domain=${o.cookieDomain}`)\n if (opts.clear) parts.push('Max-Age=0')\n else if (opts.maxAge !== undefined) parts.push(`Max-Age=${opts.maxAge}`)\n return parts.join('; ')\n}\n\nasync function handleProxy(req: NormalizedRequest, raw: CookieProxyOptions): Promise<NormalizedResponse> {\n const opts = resolve(raw)\n const path = req.pathname.replace(/^\\/+/, '')\n\n // Map proxy paths → TillAuth paths.\n const url = `${opts.apiBase}/v1/${path}`\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'X-TillAuth-App': opts.appId,\n 'User-Agent': 'tilldev-auth-node/cookie-proxy',\n }\n\n // /me, /refresh, /signout need to carry tokens from cookies, not the body.\n let body = req.body\n if (path === 'me' && req.cookies[opts.accessCookie]) {\n headers.Authorization = `Bearer ${req.cookies[opts.accessCookie]}`\n }\n if (path === 'refresh') {\n const rt = req.cookies[opts.refreshCookie]\n if (!rt) {\n return {\n status: 401,\n body: JSON.stringify({ error: 'no refresh cookie' }),\n setCookies: [],\n headers: { 'Content-Type': 'application/json' },\n }\n }\n body = JSON.stringify({ refresh_token: rt })\n }\n if (path === 'signout') {\n const rt = req.cookies[opts.refreshCookie]\n body = JSON.stringify({ refresh_token: rt ?? '' })\n }\n\n const upstream = await fetch(url, { method: req.method, headers, body })\n const text = await upstream.text()\n let json: AuthBody = {}\n try {\n json = JSON.parse(text) as AuthBody\n } catch {\n json = {}\n }\n\n const setCookies: string[] = []\n // Always strip token fields before returning to the SPA.\n const out = { ...json }\n\n if (typeof json.access_token === 'string') {\n setCookies.push(makeCookie(opts.accessCookie, json.access_token, opts, { maxAge: 60 * 10 }))\n delete out.access_token\n }\n if (typeof json.refresh_token === 'string') {\n // 30d in line with the Fastify REFRESH_TTL_MS.\n setCookies.push(makeCookie(opts.refreshCookie, json.refresh_token, opts, { maxAge: 60 * 60 * 24 * 30 }))\n delete out.refresh_token\n delete out.refresh_expires_at\n }\n if (path === 'signout') {\n setCookies.push(makeCookie(opts.accessCookie, '', opts, { clear: true }))\n setCookies.push(makeCookie(opts.refreshCookie, '', opts, { clear: true }))\n }\n\n return {\n status: upstream.status,\n body: JSON.stringify(out),\n setCookies,\n headers: { 'Content-Type': 'application/json' },\n }\n}\n\n// ── Adapter 1: web-fetch (Next.js Route Handlers, Workers, Bun, Deno) ───────\n\nexport function createFetchHandler(opts: CookieProxyOptions) {\n return async function handler(req: Request, ctx: { params?: { path?: string | string[] } } = {}): Promise<Response> {\n const url = new URL(req.url)\n // Derive the proxy-relative path. Prefer ctx.params.path (Next.js catch-all);\n // fall back to the part of the URL after the mount point heuristic.\n let pathname: string\n if (ctx.params?.path) {\n pathname = Array.isArray(ctx.params.path) ? `/${ctx.params.path.join('/')}` : `/${ctx.params.path}`\n } else {\n pathname = url.pathname.replace(/^\\/api\\/auth/, '')\n }\n const cookies = parseCookieHeader(req.headers.get('cookie'))\n const body = req.method === 'POST' ? await req.text() : '{}'\n const out = await handleProxy({ method: req.method, pathname, body, cookies }, opts)\n const headers = new Headers(out.headers)\n for (const c of out.setCookies) headers.append('Set-Cookie', c)\n return new Response(out.body, { status: out.status, headers })\n }\n}\n\n// ── Adapter 2: Express ──────────────────────────────────────────────────────\n\ninterface ExpReq {\n method: string\n url?: string\n originalUrl?: string\n path?: string\n body?: unknown\n headers: Record<string, string | string[] | undefined>\n}\ninterface ExpRes {\n status(n: number): ExpRes\n setHeader(name: string, value: string | string[]): void\n json(body: unknown): void\n}\n\nexport function expressCookieProxy(opts: CookieProxyOptions) {\n return async function (req: ExpReq, res: ExpRes): Promise<void> {\n const path =\n req.path ?? (req.originalUrl ?? req.url ?? '/').replace(/^\\/api\\/auth/, '').split('?')[0] ?? '/'\n const cookieHeader =\n typeof req.headers.cookie === 'string' ? req.headers.cookie : Array.isArray(req.headers.cookie) ? req.headers.cookie[0] : ''\n const body =\n typeof req.body === 'string' ? req.body : req.body ? JSON.stringify(req.body) : '{}'\n const out = await handleProxy(\n {\n method: req.method,\n pathname: path,\n body,\n cookies: parseCookieHeader(cookieHeader ?? null),\n },\n opts,\n )\n res.status(out.status)\n if (out.setCookies.length > 0) res.setHeader('Set-Cookie', out.setCookies)\n for (const [k, v] of Object.entries(out.headers)) res.setHeader(k, v)\n res.json(JSON.parse(out.body))\n }\n}\n\nfunction parseCookieHeader(header: string | null): Record<string, string> {\n const out: Record<string, string> = {}\n if (!header) return out\n for (const part of header.split(';')) {\n const [k, ...rest] = part.trim().split('=')\n if (!k) continue\n out[k] = decodeURIComponent(rest.join('='))\n }\n return out\n}\n","import { jwtVerify, createRemoteJWKSet, importJWK, type JWK } from 'jose'\n\n// Server-side TillAuth verification.\n//\n// You configure once with the issuer + JWKS endpoint (or inline public keys),\n// then call `verify(token)` or use the framework-specific middleware.\n//\n// Verification is offline against the cached JWKS — no round-trip to TillAuth\n// on the hot path.\n\nconst DEFAULT_API = 'https://auth.tilldev.dev'\nconst LEGACY_ISSUER = 'tillauth'\nconst REALM_SEGMENT = '/realms/'\n\nfunction trimSlash(s: string): string {\n return s.replace(/\\/+$/, '')\n}\n\n/**\n * Dual-accept issuer check. TillAuth is migrating access tokens from the legacy\n * `\"tillauth\"` string to a canonical HTTPS issuer (equal to `apiBase`) and, for\n * dedicated realms, a per-realm issuer `{apiBase}/realms/<app>`. During the\n * migration window a verifier accepts all three. Pass `pinnedIssuer` (the\n * `issuer` option) to hard-pin to exactly one realm's issuer instead.\n */\nfunction issuerAccepted(iss: unknown, canonical: string, pinnedIssuer?: string): boolean {\n if (typeof iss !== 'string' || iss.length === 0) return false\n if (iss === LEGACY_ISSUER) return true // legacy window\n if (pinnedIssuer) return iss === trimSlash(pinnedIssuer)\n const canon = trimSlash(canonical)\n if (iss === canon) return true\n const prefix = `${canon}${REALM_SEGMENT}`\n return iss.startsWith(prefix) && iss.length > prefix.length\n}\n\nexport interface VerifyOptions {\n /** TillAuth public app id (`tau_…`). Used to enforce app scoping. */\n appId: string\n /** TillAuth API base. Defaults to https://auth.tilldev.dev */\n apiBase?: string\n /**\n * Pin verification to exactly one issuer (e.g. a dedicated realm's\n * `https://auth.tilldev.dev/realms/<app>`). When omitted, the verifier accepts\n * the legacy string, the canonical HTTPS issuer (derived from `apiBase`), and\n * any per-realm issuer under it — the migration-window default.\n */\n issuer?: string\n /**\n * Inline JWKS as a JWK array (the same `publicJwk` entries you'd find in\n * the TILLAUTH_JWT_KEYS env). Use this in environments where you want\n * fully air-gapped verification.\n */\n publicJwks?: JWK[]\n /** Require email_verified=true. Defaults to false. */\n requireVerifiedEmail?: boolean\n /** Require MFA was satisfied this session. Defaults to false. */\n requireMfa?: boolean\n}\n\nexport interface AuthClaims {\n /** User id */\n sub: string\n /** App id the token was issued for */\n app: string\n /** Lowercased email */\n email: string\n /** Whether MFA was satisfied this session */\n mfa: boolean\n /**\n * Whether the account's email was verified at issue time. Optional: tokens\n * minted before this claim existed omit it. When `requireVerifiedEmail` is\n * set, an absent claim is treated as unverified (fail-closed on the check).\n */\n email_verified?: boolean\n /** Token issuer — validated against the accepted set (see `issuerAccepted`). */\n iss?: string\n /** Issued-at (seconds) */\n iat: number\n /** Expires-at (seconds) */\n exp: number\n}\n\nexport class TillAuthVerifyError extends Error {\n code: 'no_token' | 'invalid_token' | 'wrong_app' | 'mfa_required' | 'email_not_verified' | 'invalid_issuer'\n constructor(message: string, code: TillAuthVerifyError['code']) {\n super(message)\n this.name = 'TillAuthVerifyError'\n this.code = code\n }\n}\n\nexport class TillAuthVerifier {\n private readonly opts: Required<Pick<VerifyOptions, 'appId' | 'apiBase' | 'requireVerifiedEmail' | 'requireMfa'>>\n private readonly pinnedIssuer: string | undefined\n private readonly remote: ReturnType<typeof createRemoteJWKSet> | null\n private readonly inlineKeys: Promise<Array<{ kid?: string; key: Awaited<ReturnType<typeof importJWK>> }>> | null\n\n constructor(opts: VerifyOptions) {\n if (!opts.appId) throw new Error('TillAuthVerifier: appId is required')\n this.opts = {\n appId: opts.appId,\n apiBase: (opts.apiBase ?? DEFAULT_API).replace(/\\/$/, ''),\n requireVerifiedEmail: opts.requireVerifiedEmail ?? false,\n requireMfa: opts.requireMfa ?? false,\n }\n this.pinnedIssuer = opts.issuer ? opts.issuer.replace(/\\/$/, '') : undefined\n if (opts.publicJwks && opts.publicJwks.length > 0) {\n this.remote = null\n this.inlineKeys = Promise.all(\n opts.publicJwks.map(async (k) => ({ kid: k.kid, key: await importJWK(k, 'EdDSA') })),\n )\n } else {\n this.remote = createRemoteJWKSet(new URL(`${this.opts.apiBase}/.well-known/jwks.json`))\n this.inlineKeys = null\n }\n }\n\n async verify(token: string): Promise<AuthClaims> {\n if (!token) throw new TillAuthVerifyError('No token', 'no_token')\n\n let claims: AuthClaims\n try {\n if (this.inlineKeys) {\n const keys = await this.inlineKeys\n let lastErr: unknown\n for (const k of keys) {\n try {\n // No jose `issuer` pin — the issuer is one of several accepted values\n // (legacy string / canonical HTTPS / per-realm), checked below.\n const { payload } = await jwtVerify(token, k.key, { algorithms: ['EdDSA'] })\n claims = payload as unknown as AuthClaims\n return this.applyClaimsChecks(claims)\n } catch (e) {\n lastErr = e\n }\n }\n throw lastErr ?? new Error('No key matched')\n }\n const { payload } = await jwtVerify(token, this.remote!, { algorithms: ['EdDSA'] })\n claims = payload as unknown as AuthClaims\n } catch (e) {\n // Preserve a specific claims verdict (wrong_app / invalid_issuer) thrown by\n // applyClaimsChecks on the inline path; only a genuine signature/JWKS\n // failure becomes the generic invalid_token.\n if (e instanceof TillAuthVerifyError) throw e\n throw new TillAuthVerifyError(\n e instanceof Error ? e.message : 'Invalid token',\n 'invalid_token',\n )\n }\n return this.applyClaimsChecks(claims)\n }\n\n private applyClaimsChecks(claims: AuthClaims): AuthClaims {\n // Issuer is validated here (jose's exact `issuer` pin is intentionally not\n // used) so the legacy→HTTPS→per-realm migration doesn't break verification.\n if (!issuerAccepted(claims.iss, this.opts.apiBase, this.pinnedIssuer)) {\n throw new TillAuthVerifyError('Untrusted token issuer', 'invalid_issuer')\n }\n if (claims.app !== this.opts.appId) {\n throw new TillAuthVerifyError(`Token issued for different app`, 'wrong_app')\n }\n if (this.opts.requireMfa && !claims.mfa) {\n throw new TillAuthVerifyError('MFA required', 'mfa_required')\n }\n // Absent claim (older tokens) is treated as unverified only when the check\n // is enabled — so requireVerifiedEmail fails closed rather than silently\n // passing when the issuer predates the claim.\n if (this.opts.requireVerifiedEmail && claims.email_verified !== true) {\n throw new TillAuthVerifyError('Email not verified', 'email_not_verified')\n }\n return claims\n }\n\n /**\n * Pull a Bearer token out of an Authorization header and verify. Returns\n * the claims or throws TillAuthVerifyError.\n */\n async verifyHeader(authHeader: string | undefined | null): Promise<AuthClaims> {\n if (!authHeader || !authHeader.startsWith('Bearer ')) {\n throw new TillAuthVerifyError('Missing Bearer token', 'no_token')\n }\n return this.verify(authHeader.slice(7))\n }\n}\n\n// ── Framework adapters ──────────────────────────────────────────────────────\n\n// Express-style middleware. Adds `req.user` on success.\nexport function expressMiddleware(verifier: TillAuthVerifier, opts: { onError?: (err: TillAuthVerifyError) => unknown } = {}) {\n return async function (req: ExpressLikeReq, res: ExpressLikeRes, next: () => void) {\n try {\n const h = req.headers?.authorization\n const claims = await verifier.verifyHeader(Array.isArray(h) ? h[0] : h)\n ;(req as ExpressLikeReq & { user: AuthClaims }).user = claims\n next()\n } catch (e) {\n const err = e instanceof TillAuthVerifyError ? e : new TillAuthVerifyError('Auth failed', 'invalid_token')\n if (opts.onError) {\n const out = opts.onError(err)\n if (out !== undefined) {\n res.status(401).json(out as Record<string, unknown>)\n return\n }\n }\n res.status(401).json({ error: err.message, code: err.code })\n }\n }\n}\n\ninterface ExpressLikeReq {\n headers?: { authorization?: string | string[] }\n}\ninterface ExpressLikeRes {\n status: (n: number) => { json: (b: Record<string, unknown>) => void }\n}\n\n// Fastify plugin shape — not bound to Fastify's types to avoid a peer dep.\nexport function fastifyHook(verifier: TillAuthVerifier) {\n return async function (\n req: { headers: Record<string, string | string[] | undefined>; user?: AuthClaims },\n reply: { code: (n: number) => { send: (b: unknown) => void } },\n ) {\n try {\n const h = req.headers['authorization']\n const auth = Array.isArray(h) ? h[0] : h\n const claims = await verifier.verifyHeader(auth)\n req.user = claims\n } catch (e) {\n const err = e instanceof TillAuthVerifyError ? e : new TillAuthVerifyError('Auth failed', 'invalid_token')\n reply.code(401).send({ error: err.message, code: err.code })\n }\n }\n}\n\n// Convenience entrypoint for users who want a one-call setup.\nexport function createVerifier(opts: VerifyOptions): TillAuthVerifier {\n return new TillAuthVerifier(opts)\n}\n\nexport { createFetchHandler, expressCookieProxy } from './cookie-proxy.js'\nexport type { CookieProxyOptions } from './cookie-proxy.js'\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tillstack/auth-node",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Node SDK for TillAuth — JWT verification + middleware",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -25,6 +25,13 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"dist"
|
|
27
27
|
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"dev": "tsup --watch",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"prepack": "tsup",
|
|
33
|
+
"clean": "rm -rf dist"
|
|
34
|
+
},
|
|
28
35
|
"dependencies": {
|
|
29
36
|
"jose": "^5.9.6"
|
|
30
37
|
},
|
|
@@ -43,11 +50,5 @@
|
|
|
43
50
|
},
|
|
44
51
|
"publishConfig": {
|
|
45
52
|
"access": "public"
|
|
46
|
-
},
|
|
47
|
-
"scripts": {
|
|
48
|
-
"build": "tsup",
|
|
49
|
-
"dev": "tsup --watch",
|
|
50
|
-
"typecheck": "tsc --noEmit",
|
|
51
|
-
"clean": "rm -rf dist"
|
|
52
53
|
}
|
|
53
|
-
}
|
|
54
|
+
}
|