@shware/security 2.5.1 → 2.5.3

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.
@@ -24,7 +24,7 @@ __export(auth_exports, {
24
24
  PATH: () => PATH
25
25
  });
26
26
  module.exports = __toCommonJS(auth_exports);
27
- var import_crypto = require("crypto");
27
+ var import_node_crypto = require("crypto");
28
28
  var import_utils = require("@shware/utils");
29
29
  var import_core = require("../core/index.cjs");
30
30
  var import_schema = require("../email/schema.cjs");
@@ -104,7 +104,7 @@ var Auth = class {
104
104
  return sessionId;
105
105
  }
106
106
  csrf = async (_request) => {
107
- const token = (0, import_crypto.randomUUID)();
107
+ const token = (0, import_node_crypto.randomUUID)();
108
108
  const response = Response.json({
109
109
  token,
110
110
  parameterName: "_csrf",
@@ -141,8 +141,8 @@ var Auth = class {
141
141
  return response;
142
142
  };
143
143
  createPkceParameters() {
144
- const code_verifier = (0, import_crypto.randomUUID)();
145
- const code_challenge = (0, import_crypto.createHash)("sha256").update(code_verifier).digest().toString("base64url");
144
+ const code_verifier = (0, import_node_crypto.randomUUID)();
145
+ const code_challenge = (0, import_node_crypto.createHash)("sha256").update(code_verifier).digest().toString("base64url");
146
146
  return { code_verifier, code_challenge, code_challenge_method: "S256" };
147
147
  }
148
148
  getOauth2StateKey(state) {
@@ -162,13 +162,13 @@ var Auth = class {
162
162
  }
163
163
  oauth2State = async (request) => {
164
164
  const { registrationId } = (0, import_http.param)(request, PATH.OAUTH2_STATE);
165
- const state = (0, import_crypto.randomUUID)();
165
+ const state = (0, import_node_crypto.randomUUID)();
166
166
  await this.kv.setItem(this.getOauth2StateKey(state), registrationId, 10 * 60);
167
167
  return Response.json({ state });
168
168
  };
169
169
  oauth2Nonce = async (request) => {
170
170
  const { registrationId } = (0, import_http.param)(request, PATH.OAUTH2_NONCE);
171
- const nonce = (0, import_crypto.randomUUID)();
171
+ const nonce = (0, import_node_crypto.randomUUID)();
172
172
  await this.kv.setItem(this.getOauth2NonceKey(nonce), registrationId, 10 * 60);
173
173
  return Response.json({ nonce });
174
174
  };
@@ -176,7 +176,7 @@ var Auth = class {
176
176
  (0, import_utils.invariant)(this.oauth2Client, "oauth2Client is not initialized");
177
177
  const { mark, setTiming } = (0, import_timing.timing)({ enabled: this.timing });
178
178
  const { registrationId } = (0, import_http.param)(request, PATH.OAUTH2_AUTHORIZATION);
179
- const state = (0, import_crypto.randomUUID)();
179
+ const state = (0, import_node_crypto.randomUUID)();
180
180
  const pkce = this.createPkceParameters();
181
181
  const uri = await this.oauth2Client.createAuthorizationUri({ registrationId, state, pkce });
182
182
  const sessionId = this.getSessionId(request);
@@ -348,7 +348,7 @@ var Auth = class {
348
348
  const result = await (0, import_valid.verifyTurnstileToken)(this.turnstileSecretKey, turnstileToken);
349
349
  if (result.error) return Response.json(result.error, { status: result.error.error.code });
350
350
  try {
351
- const verificationCode = (0, import_crypto.randomInt)(1e6).toString().padStart(6, "0");
351
+ const verificationCode = (0, import_node_crypto.randomInt)(1e6).toString().padStart(6, "0");
352
352
  await sender({ email, verificationCode });
353
353
  await this.kv.setItem(this.getEmailVerificationCodeKey(email), verificationCode, 10 * 60);
354
354
  return Response.json({});
@@ -363,7 +363,7 @@ var Auth = class {
363
363
  const { email, verificationCode } = data;
364
364
  const key = this.getEmailVerificationCodeKey(email);
365
365
  const stored = await this.kv.getItem(key);
366
- if (typeof stored !== "string" || stored.length !== verificationCode.length || !(0, import_crypto.timingSafeEqual)(Buffer.from(stored), Buffer.from(verificationCode))) {
366
+ if (typeof stored !== "string" || stored.length !== verificationCode.length || !(0, import_node_crypto.timingSafeEqual)(Buffer.from(stored), Buffer.from(verificationCode))) {
367
367
  return Response.json((0, import_valid.invalidArgument)("INVALID_VERIFICATION_CODE"), { status: 400 });
368
368
  }
369
369
  await this.kv.removeItem(key);
@@ -422,7 +422,7 @@ var Auth = class {
422
422
  if (!codeChallenge || body.code_challenge_method !== "S256" || !PKCE_FORMAT.test(codeChallenge)) {
423
423
  return Response.json((0, import_valid.invalidArgument)("INVALID_ARGUMENT"), { status: 400 });
424
424
  }
425
- const code = (0, import_crypto.randomUUID)();
425
+ const code = (0, import_node_crypto.randomUUID)();
426
426
  const record = { name: principal.name, cc: codeChallenge };
427
427
  await this.kv.setItem(this.getDesktopCodeKey(code), JSON.stringify(record), 5 * 60);
428
428
  return Response.json({ code });
@@ -469,9 +469,9 @@ var Auth = class {
469
469
  await this.kv.removeItem(key);
470
470
  return Response.json((0, import_valid.invalidArgument)("INVALID_DESKTOP_CODE"), { status: 400 });
471
471
  }
472
- const computed = (0, import_crypto.createHash)("sha256").update(verifier).digest();
472
+ const computed = (0, import_node_crypto.createHash)("sha256").update(verifier).digest();
473
473
  const stored = Buffer.from(record.cc, "base64url");
474
- if (stored.length !== computed.length || !(0, import_crypto.timingSafeEqual)(computed, stored)) {
474
+ if (stored.length !== computed.length || !(0, import_node_crypto.timingSafeEqual)(computed, stored)) {
475
475
  return Response.json((0, import_valid.invalidArgument)("INVALID_DESKTOP_CODE"), { status: 400 });
476
476
  }
477
477
  await this.kv.removeItem(key);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/auth/index.ts"],"sourcesContent":["import { createHash, randomInt, randomUUID, timingSafeEqual } from 'crypto';\nimport { invariant } from '@shware/utils';\nimport { type Principal, Provider } from '../core/index';\nimport { loginEmailSchema, sendEmailVerificationCodeSchema } from '../email/schema';\nimport { OAuth2Client, googleOneTapSchema, oauth2RedirectQuerySchema } from '../oauth2/client';\nimport type { OAuth2ErrorType } from '../oauth2/error';\nimport { google } from '../oauth2/provider/index';\nimport type {\n NativeCredential,\n OAuth2AuthorizationRequest,\n PkceParameters,\n StandardClaims,\n} from '../oauth2/types';\nimport { PRINCIPAL_NAME_INDEX_NAME } from '../session/common';\nimport type { KVRepository, Session, SessionRepository } from '../session/types';\nimport {\n type CookieOptions,\n deleteCookie,\n getCookie,\n param,\n query,\n setCookie,\n} from '../utils/http';\nimport { timing } from '../utils/timing';\nimport { invalidArgument, valid, verifyTurnstileToken } from '../utils/valid';\nimport type { AuthConfig, AuthService, AuthorizedHandler, LoggedHandler } from './types';\n\n// PKCE per RFC 7636: code_verifier / code_challenge are 43-128 chars from\n// the unreserved set. We restrict to base64url (drops \".\" and \"~\") since\n// that's what S256 produces and what callers should be sending; rejecting\n// the rest shrinks the accepted input shape without losing legit clients.\n// A 32-byte random verifier base64url-encodes to exactly 43 chars.\nconst PKCE_FORMAT = /^[A-Za-z0-9_-]{43,128}$/;\n\ninterface DesktopCodeRecord {\n /** principal.name from the upstream cookie session */\n name: string;\n /** code_challenge (S256, base64url) — verifier must hash to this */\n cc: string;\n}\n\nexport const PATH = {\n CSRF: '/csrf',\n LOGOUT: '/logout',\n LOGGED: '/logged',\n\n OAUTH2_STATE: '/oauth2/state/:registrationId',\n OAUTH2_NONCE: '/oauth2/nonce/:registrationId',\n OAUTH2_AUTHORIZATION: '/oauth2/authorization/:registrationId',\n LOGIN_OAUTH2_CODE: '/login/oauth2/code/:registrationId',\n LOGIN_OAUTH2_NATIVE: '/login/oauth2/native/:registrationId',\n LOGIN_OAUTH2_ONETAP: '/login/oauth2/onetap/google',\n\n LOGIN_EMAIL: '/login/email',\n LOGIN_PHONE: '/login/phone',\n SEND_EMAIL_VERIFICATION_CODE: '/auth/sendEmailVerificationCode',\n SEND_PHONE_VERIFICATION_CODE: '/auth/sendPhoneVerificationCode',\n\n // Desktop / native app sign-in via web loopback (RFC 8252 + OAuth 2.1 Auth Code Flow):\n // 1. user already authenticated with cookie session → POST DESKTOP_AUTHORIZE → { code }\n // 2. web redirects to desktop's loopback callback with the code\n // 3. desktop renderer POSTs DESKTOP_EXCHANGE with the code → server returns\n // Set-Cookie carrying a fresh session, single-use code is invalidated\n // The two-step flow keeps the actual session id off the redirect URL — only a\n // 5-minute single-use code transits via the loopback.\n DESKTOP_AUTHORIZE: '/auth/desktop/authorize',\n DESKTOP_EXCHANGE: '/auth/desktop/exchange',\n\n CLEANUP_EXPIRED_SESSIONS: '/sessions/expired/cleanup',\n} as const;\n\nexport class Auth implements AuthService {\n private readonly timing: boolean;\n private readonly cookieName;\n private readonly cookieOptions: CookieOptions;\n private readonly kv: KVRepository;\n private readonly repository: SessionRepository;\n private readonly turnstileSecretKey?: string;\n private readonly oauth2Client: OAuth2Client | null;\n private readonly ATTR_OAUTH2_AUTHORIZATION_REQUEST = 'oauth2AuthorizationRequest';\n\n constructor({\n sessionRepository,\n kvRepository,\n turnstileSecretKey,\n oauth2,\n cookie,\n timing,\n }: AuthConfig) {\n this.timing = timing ?? false;\n this.kv = kvRepository;\n this.repository = sessionRepository;\n this.turnstileSecretKey = turnstileSecretKey;\n const { name, ...cookieOptions } = cookie ?? {};\n this.cookieName = name ?? 'SESSION';\n this.cookieOptions = {\n path: '/',\n sameSite: 'none',\n secure: true,\n httpOnly: true,\n ...cookieOptions,\n };\n this.oauth2Client = oauth2?.client ? new OAuth2Client(oauth2.client) : null;\n }\n\n private getSessionId(request: Request): string | undefined {\n let sessionId: string | undefined;\n const header = request.headers.get('Authorization');\n if (header?.startsWith('Bearer ')) {\n sessionId = header.replace('Bearer ', '');\n }\n if (!sessionId) {\n sessionId = getCookie(request, this.cookieName);\n }\n return sessionId;\n }\n\n csrf = async (_request: Request): Promise<Response> => {\n const token = randomUUID();\n const response = Response.json({\n token,\n parameterName: '_csrf',\n headerName: 'X-XSRF-TOKEN',\n });\n\n // Double Submit Cookie Protection\n setCookie(response, 'XSRF-TOKEN', token, {\n path: '/',\n secure: true,\n httpOnly: false,\n sameSite: 'none',\n domain: this.cookieOptions.domain,\n });\n return response;\n };\n\n logout = async (request: Request): Promise<Response> => {\n const sessionId = this.getSessionId(request);\n if (sessionId) await this.repository.deleteById(sessionId);\n const response = Response.json({});\n deleteCookie(response, this.cookieName);\n return response;\n };\n\n logged = async (request: Request, onLogged?: LoggedHandler): Promise<Response> => {\n // 1. get session from header or cookie\n const sessionId = this.getSessionId(request);\n if (!sessionId) return Response.json({ data: false });\n const session = await this.repository.findById(sessionId);\n if (!session) return Response.json({ data: false });\n\n // 2. get principal name from session\n const name = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n session.setLastAccessedTime(Date.now());\n await this.repository.save(session);\n\n // 3. call onLogged\n if (name !== null) await onLogged?.({ name: String(name) });\n\n // 4. renew cookie\n const response = Response.json({ data: name !== null });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n return response;\n };\n\n private createPkceParameters(): PkceParameters {\n const code_verifier = randomUUID();\n const code_challenge = createHash('sha256')\n .update(code_verifier)\n .digest()\n .toString('base64url');\n\n return { code_verifier, code_challenge, code_challenge_method: 'S256' };\n }\n\n private getOauth2StateKey(state: string) {\n return `oauth2:state:${state}`;\n }\n\n private getOauth2NonceKey(nonce: string) {\n return `oauth2:nonce:${nonce}`;\n }\n\n private getEmailVerificationCodeKey(email: string) {\n return `email:verification:${email}`;\n }\n\n private getPhoneVerificationCodeKey(phone: string) {\n return `phone:verification:${phone}`;\n }\n\n private getDesktopCodeKey(code: string) {\n return `desktop:code:${code}`;\n }\n\n oauth2State = async (request: Request): Promise<Response> => {\n const { registrationId } = param(request, PATH.OAUTH2_STATE);\n const state = randomUUID();\n await this.kv.setItem(this.getOauth2StateKey(state), registrationId, 10 * 60);\n return Response.json({ state });\n };\n\n oauth2Nonce = async (request: Request): Promise<Response> => {\n const { registrationId } = param(request, PATH.OAUTH2_NONCE);\n const nonce = randomUUID();\n await this.kv.setItem(this.getOauth2NonceKey(nonce), registrationId, 10 * 60);\n return Response.json({ nonce });\n };\n\n oauth2Authorization = async (request: Request): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n const { registrationId } = param(request, PATH.OAUTH2_AUTHORIZATION);\n const state = randomUUID();\n const pkce = this.createPkceParameters();\n const uri = await this.oauth2Client.createAuthorizationUri({ registrationId, state, pkce });\n const sessionId = this.getSessionId(request);\n const session = sessionId\n ? ((await this.repository.findById(sessionId)) ?? this.repository.createSession())\n : this.repository.createSession();\n mark('fetch_session');\n\n const authorizationRequest: OAuth2AuthorizationRequest = {\n state,\n registrationId,\n authorizationRequestUri: uri.href,\n additionalParameters: pkce,\n };\n\n const value = JSON.stringify(authorizationRequest);\n session.setAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST, value);\n await this.repository.save(session);\n mark('save_session');\n\n const response = new Response(null, { status: 302, headers: { location: uri.href } });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n private redirect = (error: OAuth2ErrorType, description?: string): Response => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const uri = new URL(this.oauth2Client.errorUri);\n uri.searchParams.set('error', error);\n if (description) uri.searchParams.set('error_description', description);\n return Response.redirect(uri.href, 302);\n };\n\n loginOAuth2Code = async (request: Request, handler: AuthorizedHandler): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n\n // 1. get session from header or cookie\n const sessionId = this.getSessionId(request);\n const session = sessionId ? await this.repository.findById(sessionId) : null;\n if (!session) {\n return this.redirect('invalid_request', 'session not found');\n }\n mark('load_session');\n\n // 2. get cached authorization request from session\n const json = session.getAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST) as string | null;\n if (!json) {\n return this.redirect('invalid_request', 'authorization request not found');\n }\n const cached: OAuth2AuthorizationRequest = JSON.parse(json);\n\n // 3. validate redirect query/formdata\n const { registrationId } = param(request, PATH.LOGIN_OAUTH2_CODE);\n let data: Record<string, string>;\n // apple redirect: response_mode=form_post\n if (\n request.method === 'POST' &&\n request.headers.get('content-type')?.toLowerCase() === 'application/x-www-form-urlencoded'\n ) {\n const params = new URLSearchParams(await request.text());\n data = Object.fromEntries(params.entries());\n } else {\n data = query(request);\n }\n\n const parsed = oauth2RedirectQuerySchema.safeParse(data);\n if (!parsed.success) {\n return this.redirect('invalid_request', 'invalid redirect query');\n }\n if (!parsed.data.code || !parsed.data.state) {\n return this.redirect('invalid_request', 'invalid redirect query');\n }\n if (parsed.data.state !== cached.state) {\n return this.redirect('invalid_request', 'redirect state mismatch');\n }\n if (registrationId !== cached.registrationId) {\n return this.redirect('invalid_request', 'redirect registration mismatch');\n }\n mark('validate_session');\n\n // 4. exchange authorization code for token and get user info\n const { code } = parsed.data;\n\n const token = await this.oauth2Client.exchangeAuthorizationCode({\n registrationId,\n code,\n pkce: cached.additionalParameters,\n });\n const userInfo = await this.oauth2Client.getUserInfo({ registrationId, token });\n mark('exchange_code');\n\n // 5. create or update principal\n const principal = await handler(request, registrationId, userInfo, token);\n mark('save_principal');\n\n // 6. update session\n session.setLastAccessedTime(Date.now());\n session.removeAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST);\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n mark('update_session');\n\n const uri = new URL(this.oauth2Client.successUri);\n uri.searchParams.set('status', 'success');\n uri.searchParams.set('registrationId', registrationId);\n\n const response = new Response(null, { status: 302, headers: { location: uri.href } });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n loginOAuth2Native = async (request: Request, handler: AuthorizedHandler): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n const { registrationId } = param(request, PATH.LOGIN_OAUTH2_NATIVE);\n const credentials = (await request.json()) as NativeCredential;\n const { userInfo, token } = await this.oauth2Client.loginOAuth2Native({\n registrationId,\n credentials,\n });\n mark('login_oauth2_native');\n\n const { nonce } = userInfo.data as { nonce?: string };\n if (nonce) {\n const value = await this.kv.getItem(this.getOauth2NonceKey(nonce));\n if (value !== registrationId) {\n const json = { error: 'invalid_request', error_description: 'nonce mismatch' };\n return Response.json(json, { status: 400 });\n }\n await this.kv.removeItem(this.getOauth2NonceKey(nonce));\n mark('check_nonce');\n }\n\n const principal = await handler(request, registrationId, userInfo, token);\n mark('save_principal');\n\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n loginOAuth2Onetap = async (\n request: Request,\n handler: AuthorizedHandler,\n registrationId = 'google'\n ): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n\n // should support application/x-www-form-urlencoded and application/json\n let body: Record<string, string>;\n if (request.headers.get('Content-Type')?.includes('application/x-www-form-urlencoded')) {\n const text = await request.text();\n body = Object.fromEntries(new URLSearchParams(text));\n } else if (request.headers.get('Content-Type')?.includes('application/json')) {\n body = (await request.json()) as Record<string, string>;\n } else {\n const json = { error: 'invalid_request', error_description: 'invalid content type' };\n return Response.json(json, { status: 400 });\n }\n\n const parsed = googleOneTapSchema.parse(body);\n\n // csrf token is not supported in FedCM mode\n // const csrf_cookie = getCookie(request, 'g_csrf_token');\n // const csrf_request = parsed.g_csrf_token;\n // if (!csrf_request || csrf_cookie !== csrf_request) {\n // const json = { error: 'invalid_request', error_description: 'invalid csrf token' };\n // return Response.json(json, { status: 400 });\n // }\n\n let principal: Principal;\n if (parsed.credential) {\n const { token, userInfo } = await google.getTokenInfo(parsed.credential);\n principal = await handler(request, registrationId, userInfo, token);\n } else if (parsed.code) {\n const token = await this.oauth2Client.exchangeAuthorizationCode({\n registrationId,\n code: parsed.code,\n });\n const userInfo = await this.oauth2Client.getUserInfo({ registrationId, token });\n principal = await handler(request, registrationId, userInfo, token);\n } else {\n throw new Error('invalid request');\n }\n\n mark('save_principal');\n\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n sendEmailVerificationCode = async (\n request: Request,\n sender: (data: { email: string; verificationCode: string }) => Promise<void>\n ): Promise<Response> => {\n invariant(this.turnstileSecretKey, 'turnstileSecretKey is not configured');\n\n const { data, error } = valid(sendEmailVerificationCodeSchema, await request.json());\n if (error) return Response.json(error, { status: error.error.code });\n const { email, turnstileToken } = data;\n\n const result = await verifyTurnstileToken(this.turnstileSecretKey, turnstileToken);\n if (result.error) return Response.json(result.error, { status: result.error.error.code });\n\n try {\n const verificationCode = randomInt(1_000_000).toString().padStart(6, '0');\n await sender({ email, verificationCode });\n await this.kv.setItem(this.getEmailVerificationCodeKey(email), verificationCode, 10 * 60);\n return Response.json({});\n } catch (e) {\n console.error('Failed to send email verification code', e);\n return Response.json(invalidArgument('EMAIL_DELIVERY_FAILED'), { status: 400 });\n }\n };\n\n loginEmail = async (request: Request, handler: AuthorizedHandler): Promise<Response> => {\n const { data, error } = valid(loginEmailSchema, await request.json());\n if (error) return Response.json(error, { status: error.error.code });\n const { email, verificationCode } = data;\n\n const key = this.getEmailVerificationCodeKey(email);\n const stored = await this.kv.getItem(key);\n if (\n typeof stored !== 'string' ||\n stored.length !== verificationCode.length ||\n !timingSafeEqual(Buffer.from(stored), Buffer.from(verificationCode))\n ) {\n return Response.json(invalidArgument('INVALID_VERIFICATION_CODE'), { status: 400 });\n }\n await this.kv.removeItem(key);\n const claims: StandardClaims = { sub: email, email, email_verified: true };\n const principal = await handler(request, Provider.EMAIL, { claims, data: {} });\n\n // save session\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n\n return response;\n };\n\n /**\n * Step 1 of the desktop / native app sign-in handshake. The caller already\n * completed normal sign-in on the web (so a cookie session exists); this\n * issues a 5-minute single-use code that the web page hands off to the\n * desktop client via a loopback redirect.\n *\n * PKCE (RFC 7636, S256 only): the desktop generates a random `code_verifier`\n * before opening the browser, derives `code_challenge = base64url(sha256(verifier))`,\n * and threads the challenge through the URL → web page → here. We pin\n * the challenge to the code in KV; `desktopExchange` enforces that the\n * caller knows the matching verifier. This blocks an attacker who only\n * intercepts the auth code (browser history, malicious extension, packet\n * capture on loopback) from exchanging it.\n *\n * Requires an authenticated cookie session — anonymous callers get 401.\n * Throttling / abuse protection on this endpoint is the API layer's job\n * (rate-limit per principal); the SDK doesn't gate it because the\n * upstream sign-in flow already passed CAPTCHA / OAuth / verification.\n */\n desktopAuthorize = async (request: Request): Promise<Response> => {\n const principal = await this.getPrincipal(request);\n if (!principal) {\n const body = {\n error: {\n code: 401,\n status: 'UNAUTHENTICATED',\n message: 'sign in first',\n details: [],\n },\n };\n return Response.json(body, { status: 401 });\n }\n\n let body: { code_challenge?: unknown; code_challenge_method?: unknown };\n try {\n body = (await request.json()) as {\n code_challenge?: unknown;\n code_challenge_method?: unknown;\n };\n } catch {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const codeChallenge = typeof body.code_challenge === 'string' ? body.code_challenge : null;\n if (\n !codeChallenge ||\n body.code_challenge_method !== 'S256' ||\n !PKCE_FORMAT.test(codeChallenge)\n ) {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const code = randomUUID();\n const record: DesktopCodeRecord = { name: principal.name, cc: codeChallenge };\n await this.kv.setItem(this.getDesktopCodeKey(code), JSON.stringify(record), 5 * 60);\n\n return Response.json({ code });\n };\n\n /**\n * Step 2 of the desktop / native app sign-in handshake. The desktop\n * client hands back the auth code it received via the loopback callback,\n * and we mint a fresh session for it — independent of the web cookie\n * session that originally `authorize`'d the code, so logging out on\n * either side doesn't cascade to the other.\n *\n * Cookie attributes: defaults to `SameSite=None; Secure` so the cookie\n * survives cross-site fetches from the desktop renderer's `file://` /\n * `app://` origin. The configured base `cookieOptions` provide\n * everything else (`domain`, `path`, `httpOnly`, `maxAge`); the override\n * is targeted at the SameSite/Secure pair only. In a non-HTTPS dev\n * backend this combination is rejected by Chromium — see the desktop\n * sign-in PRD for workarounds.\n *\n * No CSRF check needed — the request carries no cookie, the auth code\n * itself is the credential, and KV's single-use removal blocks replay.\n */\n desktopExchange = async (request: Request): Promise<Response> => {\n let body: { code?: unknown; code_verifier?: unknown };\n try {\n body = (await request.json()) as { code?: unknown; code_verifier?: unknown };\n } catch {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const code = typeof body.code === 'string' ? body.code : null;\n const verifier = typeof body.code_verifier === 'string' ? body.code_verifier : null;\n if (!code || !verifier || !PKCE_FORMAT.test(verifier)) {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const key = this.getDesktopCodeKey(code);\n const raw = await this.kv.getItem(key);\n if (typeof raw !== 'string') {\n return Response.json(invalidArgument('INVALID_DESKTOP_CODE'), { status: 400 });\n }\n let record: DesktopCodeRecord;\n try {\n record = JSON.parse(raw) as DesktopCodeRecord;\n } catch {\n // Malformed KV record (shouldn't happen with our own writes) — drop it\n // and surface as an invalid code; the caller can retry the whole flow.\n await this.kv.removeItem(key);\n return Response.json(invalidArgument('INVALID_DESKTOP_CODE'), { status: 400 });\n }\n\n // Verify PKCE: base64url(sha256(verifier)) === stored challenge.\n // Don't burn the code on a verifier mismatch — that would let an\n // attacker who races a network retry brick a legit exchange. The code\n // is single-use on success and 5-min TTL bounded; brute-forcing the\n // 256-bit verifier in that window is infeasible.\n const computed = createHash('sha256').update(verifier).digest();\n const stored = Buffer.from(record.cc, 'base64url');\n if (stored.length !== computed.length || !timingSafeEqual(computed, stored)) {\n return Response.json(invalidArgument('INVALID_DESKTOP_CODE'), { status: 400 });\n }\n\n // One-time use: remove BEFORE creating the session so a concurrent\n // duplicate exchange (e.g. retry after timeout) sees the code as\n // consumed even if our session save races it.\n await this.kv.removeItem(key);\n\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, record.name);\n session.setAttribute('client_type', 'desktop');\n await this.repository.save(session);\n\n const response = Response.json({ ok: true });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), {\n ...this.cookieOptions,\n // Override sameSite/secure for desktop: the renderer's origin\n // (file:// / app://) is cross-site to the API, so SameSite=Lax/Strict\n // would block the cookie on subsequent fetches.\n sameSite: 'none',\n secure: true,\n maxAge,\n });\n return response;\n };\n\n sendPhoneVerificationCode = async (\n _request: Request,\n _sender: (data: { phone: string; verificationCode: string }) => Promise<void>\n ): Promise<Response> => {\n return Response.json({ error: 'not_implemented' }, { status: 501 });\n };\n\n loginPhone = async (_request: Request, _handler: AuthorizedHandler): Promise<Response> => {\n return Response.json({ error: 'not_implemented' }, { status: 501 });\n };\n\n kick = async (principal: Principal): Promise<void> => {\n const sessions = await this.repository.findByPrincipalName(principal.name);\n for (const sessionId of sessions.keys()) {\n await this.repository.deleteById(sessionId);\n }\n };\n\n isAuthenticated = async (request: Request): Promise<boolean> => {\n const sessionId = this.getSessionId(request);\n if (!sessionId) return false;\n const session = await this.repository.findById(sessionId);\n if (!session) return false;\n const principalName = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n if (!principalName) return false;\n return true;\n };\n\n getSession = async <T extends boolean>(\n request: Request,\n create: T\n ): Promise<T extends true ? Session : Session | null> => {\n const sessionId = this.getSessionId(request);\n if (!sessionId) {\n if (create) {\n const session = this.repository.createSession();\n await this.repository.save(session);\n return session;\n } else {\n return null as T extends true ? Session : Session | null;\n }\n }\n const session = await this.repository.findById(sessionId);\n if (!session) {\n if (create) {\n const session = this.repository.createSession();\n await this.repository.save(session);\n return session;\n } else {\n return null as T extends true ? Session : Session | null;\n }\n }\n return session;\n };\n\n deleteSession = async (sessionId: string): Promise<void> => {\n await this.repository.deleteById(sessionId);\n };\n\n getPrincipal = async (request: Request): Promise<Principal | null> => {\n const sessionId = this.getSessionId(request);\n if (!sessionId) return null;\n const session = await this.repository.findById(sessionId);\n if (!session) return null;\n const name = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n if (!name) return null;\n return { name: String(name) };\n };\n\n listSessions = async (principal: Principal): Promise<Session[]> => {\n const sessions = await this.repository.findByPrincipalName(principal.name);\n return Array.from(sessions.values());\n };\n\n cleanupExpiredSessions = async (cleanupCount?: number): Promise<Response> => {\n await this.repository.cleanupExpiredSessions(cleanupCount);\n return Response.json({});\n };\n}\n"],"mappings":"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","names":["timing","body","session"]}
1
+ {"version":3,"sources":["../../src/auth/index.ts"],"sourcesContent":["import { createHash, randomInt, randomUUID, timingSafeEqual } from 'node:crypto';\nimport { invariant } from '@shware/utils';\nimport { type Principal, Provider } from '../core/index';\nimport { loginEmailSchema, sendEmailVerificationCodeSchema } from '../email/schema';\nimport { OAuth2Client, googleOneTapSchema, oauth2RedirectQuerySchema } from '../oauth2/client';\nimport type { OAuth2ErrorType } from '../oauth2/error';\nimport { google } from '../oauth2/provider/index';\nimport type {\n NativeCredential,\n OAuth2AuthorizationRequest,\n PkceParameters,\n StandardClaims,\n} from '../oauth2/types';\nimport { PRINCIPAL_NAME_INDEX_NAME } from '../session/common';\nimport type { KVRepository, Session, SessionRepository } from '../session/types';\nimport {\n type CookieOptions,\n deleteCookie,\n getCookie,\n param,\n query,\n setCookie,\n} from '../utils/http';\nimport { timing } from '../utils/timing';\nimport { invalidArgument, valid, verifyTurnstileToken } from '../utils/valid';\nimport type { AuthConfig, AuthService, AuthorizedHandler, LoggedHandler } from './types';\n\n// PKCE per RFC 7636: code_verifier / code_challenge are 43-128 chars from\n// the unreserved set. We restrict to base64url (drops \".\" and \"~\") since\n// that's what S256 produces and what callers should be sending; rejecting\n// the rest shrinks the accepted input shape without losing legit clients.\n// A 32-byte random verifier base64url-encodes to exactly 43 chars.\nconst PKCE_FORMAT = /^[A-Za-z0-9_-]{43,128}$/;\n\ninterface DesktopCodeRecord {\n /** principal.name from the upstream cookie session */\n name: string;\n /** code_challenge (S256, base64url) — verifier must hash to this */\n cc: string;\n}\n\nexport const PATH = {\n CSRF: '/csrf',\n LOGOUT: '/logout',\n LOGGED: '/logged',\n\n OAUTH2_STATE: '/oauth2/state/:registrationId',\n OAUTH2_NONCE: '/oauth2/nonce/:registrationId',\n OAUTH2_AUTHORIZATION: '/oauth2/authorization/:registrationId',\n LOGIN_OAUTH2_CODE: '/login/oauth2/code/:registrationId',\n LOGIN_OAUTH2_NATIVE: '/login/oauth2/native/:registrationId',\n LOGIN_OAUTH2_ONETAP: '/login/oauth2/onetap/google',\n\n LOGIN_EMAIL: '/login/email',\n LOGIN_PHONE: '/login/phone',\n SEND_EMAIL_VERIFICATION_CODE: '/auth/sendEmailVerificationCode',\n SEND_PHONE_VERIFICATION_CODE: '/auth/sendPhoneVerificationCode',\n\n // Desktop / native app sign-in via web loopback (RFC 8252 + OAuth 2.1 Auth Code Flow):\n // 1. user already authenticated with cookie session → POST DESKTOP_AUTHORIZE → { code }\n // 2. web redirects to desktop's loopback callback with the code\n // 3. desktop renderer POSTs DESKTOP_EXCHANGE with the code → server returns\n // Set-Cookie carrying a fresh session, single-use code is invalidated\n // The two-step flow keeps the actual session id off the redirect URL — only a\n // 5-minute single-use code transits via the loopback.\n DESKTOP_AUTHORIZE: '/auth/desktop/authorize',\n DESKTOP_EXCHANGE: '/auth/desktop/exchange',\n\n CLEANUP_EXPIRED_SESSIONS: '/sessions/expired/cleanup',\n} as const;\n\nexport class Auth implements AuthService {\n private readonly timing: boolean;\n private readonly cookieName;\n private readonly cookieOptions: CookieOptions;\n private readonly kv: KVRepository;\n private readonly repository: SessionRepository;\n private readonly turnstileSecretKey?: string;\n private readonly oauth2Client: OAuth2Client | null;\n private readonly ATTR_OAUTH2_AUTHORIZATION_REQUEST = 'oauth2AuthorizationRequest';\n\n constructor({\n sessionRepository,\n kvRepository,\n turnstileSecretKey,\n oauth2,\n cookie,\n timing,\n }: AuthConfig) {\n this.timing = timing ?? false;\n this.kv = kvRepository;\n this.repository = sessionRepository;\n this.turnstileSecretKey = turnstileSecretKey;\n const { name, ...cookieOptions } = cookie ?? {};\n this.cookieName = name ?? 'SESSION';\n this.cookieOptions = {\n path: '/',\n sameSite: 'none',\n secure: true,\n httpOnly: true,\n ...cookieOptions,\n };\n this.oauth2Client = oauth2?.client ? new OAuth2Client(oauth2.client) : null;\n }\n\n private getSessionId(request: Request): string | undefined {\n let sessionId: string | undefined;\n const header = request.headers.get('Authorization');\n if (header?.startsWith('Bearer ')) {\n sessionId = header.replace('Bearer ', '');\n }\n if (!sessionId) {\n sessionId = getCookie(request, this.cookieName);\n }\n return sessionId;\n }\n\n csrf = async (_request: Request): Promise<Response> => {\n const token = randomUUID();\n const response = Response.json({\n token,\n parameterName: '_csrf',\n headerName: 'X-XSRF-TOKEN',\n });\n\n // Double Submit Cookie Protection\n setCookie(response, 'XSRF-TOKEN', token, {\n path: '/',\n secure: true,\n httpOnly: false,\n sameSite: 'none',\n domain: this.cookieOptions.domain,\n });\n return response;\n };\n\n logout = async (request: Request): Promise<Response> => {\n const sessionId = this.getSessionId(request);\n if (sessionId) await this.repository.deleteById(sessionId);\n const response = Response.json({});\n deleteCookie(response, this.cookieName);\n return response;\n };\n\n logged = async (request: Request, onLogged?: LoggedHandler): Promise<Response> => {\n // 1. get session from header or cookie\n const sessionId = this.getSessionId(request);\n if (!sessionId) return Response.json({ data: false });\n const session = await this.repository.findById(sessionId);\n if (!session) return Response.json({ data: false });\n\n // 2. get principal name from session\n const name = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n session.setLastAccessedTime(Date.now());\n await this.repository.save(session);\n\n // 3. call onLogged\n if (name !== null) await onLogged?.({ name: String(name) });\n\n // 4. renew cookie\n const response = Response.json({ data: name !== null });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n return response;\n };\n\n private createPkceParameters(): PkceParameters {\n const code_verifier = randomUUID();\n const code_challenge = createHash('sha256')\n .update(code_verifier)\n .digest()\n .toString('base64url');\n\n return { code_verifier, code_challenge, code_challenge_method: 'S256' };\n }\n\n private getOauth2StateKey(state: string) {\n return `oauth2:state:${state}`;\n }\n\n private getOauth2NonceKey(nonce: string) {\n return `oauth2:nonce:${nonce}`;\n }\n\n private getEmailVerificationCodeKey(email: string) {\n return `email:verification:${email}`;\n }\n\n private getPhoneVerificationCodeKey(phone: string) {\n return `phone:verification:${phone}`;\n }\n\n private getDesktopCodeKey(code: string) {\n return `desktop:code:${code}`;\n }\n\n oauth2State = async (request: Request): Promise<Response> => {\n const { registrationId } = param(request, PATH.OAUTH2_STATE);\n const state = randomUUID();\n await this.kv.setItem(this.getOauth2StateKey(state), registrationId, 10 * 60);\n return Response.json({ state });\n };\n\n oauth2Nonce = async (request: Request): Promise<Response> => {\n const { registrationId } = param(request, PATH.OAUTH2_NONCE);\n const nonce = randomUUID();\n await this.kv.setItem(this.getOauth2NonceKey(nonce), registrationId, 10 * 60);\n return Response.json({ nonce });\n };\n\n oauth2Authorization = async (request: Request): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n const { registrationId } = param(request, PATH.OAUTH2_AUTHORIZATION);\n const state = randomUUID();\n const pkce = this.createPkceParameters();\n const uri = await this.oauth2Client.createAuthorizationUri({ registrationId, state, pkce });\n const sessionId = this.getSessionId(request);\n const session = sessionId\n ? ((await this.repository.findById(sessionId)) ?? this.repository.createSession())\n : this.repository.createSession();\n mark('fetch_session');\n\n const authorizationRequest: OAuth2AuthorizationRequest = {\n state,\n registrationId,\n authorizationRequestUri: uri.href,\n additionalParameters: pkce,\n };\n\n const value = JSON.stringify(authorizationRequest);\n session.setAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST, value);\n await this.repository.save(session);\n mark('save_session');\n\n const response = new Response(null, { status: 302, headers: { location: uri.href } });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n private redirect = (error: OAuth2ErrorType, description?: string): Response => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const uri = new URL(this.oauth2Client.errorUri);\n uri.searchParams.set('error', error);\n if (description) uri.searchParams.set('error_description', description);\n return Response.redirect(uri.href, 302);\n };\n\n loginOAuth2Code = async (request: Request, handler: AuthorizedHandler): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n\n // 1. get session from header or cookie\n const sessionId = this.getSessionId(request);\n const session = sessionId ? await this.repository.findById(sessionId) : null;\n if (!session) {\n return this.redirect('invalid_request', 'session not found');\n }\n mark('load_session');\n\n // 2. get cached authorization request from session\n const json = session.getAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST) as string | null;\n if (!json) {\n return this.redirect('invalid_request', 'authorization request not found');\n }\n const cached: OAuth2AuthorizationRequest = JSON.parse(json);\n\n // 3. validate redirect query/formdata\n const { registrationId } = param(request, PATH.LOGIN_OAUTH2_CODE);\n let data: Record<string, string>;\n // apple redirect: response_mode=form_post\n if (\n request.method === 'POST' &&\n request.headers.get('content-type')?.toLowerCase() === 'application/x-www-form-urlencoded'\n ) {\n const params = new URLSearchParams(await request.text());\n data = Object.fromEntries(params.entries());\n } else {\n data = query(request);\n }\n\n const parsed = oauth2RedirectQuerySchema.safeParse(data);\n if (!parsed.success) {\n return this.redirect('invalid_request', 'invalid redirect query');\n }\n if (!parsed.data.code || !parsed.data.state) {\n return this.redirect('invalid_request', 'invalid redirect query');\n }\n if (parsed.data.state !== cached.state) {\n return this.redirect('invalid_request', 'redirect state mismatch');\n }\n if (registrationId !== cached.registrationId) {\n return this.redirect('invalid_request', 'redirect registration mismatch');\n }\n mark('validate_session');\n\n // 4. exchange authorization code for token and get user info\n const { code } = parsed.data;\n\n const token = await this.oauth2Client.exchangeAuthorizationCode({\n registrationId,\n code,\n pkce: cached.additionalParameters,\n });\n const userInfo = await this.oauth2Client.getUserInfo({ registrationId, token });\n mark('exchange_code');\n\n // 5. create or update principal\n const principal = await handler(request, registrationId, userInfo, token);\n mark('save_principal');\n\n // 6. update session\n session.setLastAccessedTime(Date.now());\n session.removeAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST);\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n mark('update_session');\n\n const uri = new URL(this.oauth2Client.successUri);\n uri.searchParams.set('status', 'success');\n uri.searchParams.set('registrationId', registrationId);\n\n const response = new Response(null, { status: 302, headers: { location: uri.href } });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n loginOAuth2Native = async (request: Request, handler: AuthorizedHandler): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n const { registrationId } = param(request, PATH.LOGIN_OAUTH2_NATIVE);\n const credentials = (await request.json()) as NativeCredential;\n const { userInfo, token } = await this.oauth2Client.loginOAuth2Native({\n registrationId,\n credentials,\n });\n mark('login_oauth2_native');\n\n const { nonce } = userInfo.data as { nonce?: string };\n if (nonce) {\n const value = await this.kv.getItem(this.getOauth2NonceKey(nonce));\n if (value !== registrationId) {\n const json = { error: 'invalid_request', error_description: 'nonce mismatch' };\n return Response.json(json, { status: 400 });\n }\n await this.kv.removeItem(this.getOauth2NonceKey(nonce));\n mark('check_nonce');\n }\n\n const principal = await handler(request, registrationId, userInfo, token);\n mark('save_principal');\n\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n loginOAuth2Onetap = async (\n request: Request,\n handler: AuthorizedHandler,\n registrationId = 'google'\n ): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n\n // should support application/x-www-form-urlencoded and application/json\n let body: Record<string, string>;\n if (request.headers.get('Content-Type')?.includes('application/x-www-form-urlencoded')) {\n const text = await request.text();\n body = Object.fromEntries(new URLSearchParams(text));\n } else if (request.headers.get('Content-Type')?.includes('application/json')) {\n body = (await request.json()) as Record<string, string>;\n } else {\n const json = { error: 'invalid_request', error_description: 'invalid content type' };\n return Response.json(json, { status: 400 });\n }\n\n const parsed = googleOneTapSchema.parse(body);\n\n // csrf token is not supported in FedCM mode\n // const csrf_cookie = getCookie(request, 'g_csrf_token');\n // const csrf_request = parsed.g_csrf_token;\n // if (!csrf_request || csrf_cookie !== csrf_request) {\n // const json = { error: 'invalid_request', error_description: 'invalid csrf token' };\n // return Response.json(json, { status: 400 });\n // }\n\n let principal: Principal;\n if (parsed.credential) {\n const { token, userInfo } = await google.getTokenInfo(parsed.credential);\n principal = await handler(request, registrationId, userInfo, token);\n } else if (parsed.code) {\n const token = await this.oauth2Client.exchangeAuthorizationCode({\n registrationId,\n code: parsed.code,\n });\n const userInfo = await this.oauth2Client.getUserInfo({ registrationId, token });\n principal = await handler(request, registrationId, userInfo, token);\n } else {\n throw new Error('invalid request');\n }\n\n mark('save_principal');\n\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n sendEmailVerificationCode = async (\n request: Request,\n sender: (data: { email: string; verificationCode: string }) => Promise<void>\n ): Promise<Response> => {\n invariant(this.turnstileSecretKey, 'turnstileSecretKey is not configured');\n\n const { data, error } = valid(sendEmailVerificationCodeSchema, await request.json());\n if (error) return Response.json(error, { status: error.error.code });\n const { email, turnstileToken } = data;\n\n const result = await verifyTurnstileToken(this.turnstileSecretKey, turnstileToken);\n if (result.error) return Response.json(result.error, { status: result.error.error.code });\n\n try {\n const verificationCode = randomInt(1_000_000).toString().padStart(6, '0');\n await sender({ email, verificationCode });\n await this.kv.setItem(this.getEmailVerificationCodeKey(email), verificationCode, 10 * 60);\n return Response.json({});\n } catch (e) {\n console.error('Failed to send email verification code', e);\n return Response.json(invalidArgument('EMAIL_DELIVERY_FAILED'), { status: 400 });\n }\n };\n\n loginEmail = async (request: Request, handler: AuthorizedHandler): Promise<Response> => {\n const { data, error } = valid(loginEmailSchema, await request.json());\n if (error) return Response.json(error, { status: error.error.code });\n const { email, verificationCode } = data;\n\n const key = this.getEmailVerificationCodeKey(email);\n const stored = await this.kv.getItem(key);\n if (\n typeof stored !== 'string' ||\n stored.length !== verificationCode.length ||\n !timingSafeEqual(Buffer.from(stored), Buffer.from(verificationCode))\n ) {\n return Response.json(invalidArgument('INVALID_VERIFICATION_CODE'), { status: 400 });\n }\n await this.kv.removeItem(key);\n const claims: StandardClaims = { sub: email, email, email_verified: true };\n const principal = await handler(request, Provider.EMAIL, { claims, data: {} });\n\n // save session\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n\n return response;\n };\n\n /**\n * Step 1 of the desktop / native app sign-in handshake. The caller already\n * completed normal sign-in on the web (so a cookie session exists); this\n * issues a 5-minute single-use code that the web page hands off to the\n * desktop client via a loopback redirect.\n *\n * PKCE (RFC 7636, S256 only): the desktop generates a random `code_verifier`\n * before opening the browser, derives `code_challenge = base64url(sha256(verifier))`,\n * and threads the challenge through the URL → web page → here. We pin\n * the challenge to the code in KV; `desktopExchange` enforces that the\n * caller knows the matching verifier. This blocks an attacker who only\n * intercepts the auth code (browser history, malicious extension, packet\n * capture on loopback) from exchanging it.\n *\n * Requires an authenticated cookie session — anonymous callers get 401.\n * Throttling / abuse protection on this endpoint is the API layer's job\n * (rate-limit per principal); the SDK doesn't gate it because the\n * upstream sign-in flow already passed CAPTCHA / OAuth / verification.\n */\n desktopAuthorize = async (request: Request): Promise<Response> => {\n const principal = await this.getPrincipal(request);\n if (!principal) {\n const body = {\n error: {\n code: 401,\n status: 'UNAUTHENTICATED',\n message: 'sign in first',\n details: [],\n },\n };\n return Response.json(body, { status: 401 });\n }\n\n let body: { code_challenge?: unknown; code_challenge_method?: unknown };\n try {\n body = (await request.json()) as {\n code_challenge?: unknown;\n code_challenge_method?: unknown;\n };\n } catch {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const codeChallenge = typeof body.code_challenge === 'string' ? body.code_challenge : null;\n if (\n !codeChallenge ||\n body.code_challenge_method !== 'S256' ||\n !PKCE_FORMAT.test(codeChallenge)\n ) {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const code = randomUUID();\n const record: DesktopCodeRecord = { name: principal.name, cc: codeChallenge };\n await this.kv.setItem(this.getDesktopCodeKey(code), JSON.stringify(record), 5 * 60);\n\n return Response.json({ code });\n };\n\n /**\n * Step 2 of the desktop / native app sign-in handshake. The desktop\n * client hands back the auth code it received via the loopback callback,\n * and we mint a fresh session for it — independent of the web cookie\n * session that originally `authorize`'d the code, so logging out on\n * either side doesn't cascade to the other.\n *\n * Cookie attributes: defaults to `SameSite=None; Secure` so the cookie\n * survives cross-site fetches from the desktop renderer's `file://` /\n * `app://` origin. The configured base `cookieOptions` provide\n * everything else (`domain`, `path`, `httpOnly`, `maxAge`); the override\n * is targeted at the SameSite/Secure pair only. In a non-HTTPS dev\n * backend this combination is rejected by Chromium — see the desktop\n * sign-in PRD for workarounds.\n *\n * No CSRF check needed — the request carries no cookie, the auth code\n * itself is the credential, and KV's single-use removal blocks replay.\n */\n desktopExchange = async (request: Request): Promise<Response> => {\n let body: { code?: unknown; code_verifier?: unknown };\n try {\n body = (await request.json()) as { code?: unknown; code_verifier?: unknown };\n } catch {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const code = typeof body.code === 'string' ? body.code : null;\n const verifier = typeof body.code_verifier === 'string' ? body.code_verifier : null;\n if (!code || !verifier || !PKCE_FORMAT.test(verifier)) {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const key = this.getDesktopCodeKey(code);\n const raw = await this.kv.getItem(key);\n if (typeof raw !== 'string') {\n return Response.json(invalidArgument('INVALID_DESKTOP_CODE'), { status: 400 });\n }\n let record: DesktopCodeRecord;\n try {\n record = JSON.parse(raw) as DesktopCodeRecord;\n } catch {\n // Malformed KV record (shouldn't happen with our own writes) — drop it\n // and surface as an invalid code; the caller can retry the whole flow.\n await this.kv.removeItem(key);\n return Response.json(invalidArgument('INVALID_DESKTOP_CODE'), { status: 400 });\n }\n\n // Verify PKCE: base64url(sha256(verifier)) === stored challenge.\n // Don't burn the code on a verifier mismatch — that would let an\n // attacker who races a network retry brick a legit exchange. The code\n // is single-use on success and 5-min TTL bounded; brute-forcing the\n // 256-bit verifier in that window is infeasible.\n const computed = createHash('sha256').update(verifier).digest();\n const stored = Buffer.from(record.cc, 'base64url');\n if (stored.length !== computed.length || !timingSafeEqual(computed, stored)) {\n return Response.json(invalidArgument('INVALID_DESKTOP_CODE'), { status: 400 });\n }\n\n // One-time use: remove BEFORE creating the session so a concurrent\n // duplicate exchange (e.g. retry after timeout) sees the code as\n // consumed even if our session save races it.\n await this.kv.removeItem(key);\n\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, record.name);\n session.setAttribute('client_type', 'desktop');\n await this.repository.save(session);\n\n const response = Response.json({ ok: true });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), {\n ...this.cookieOptions,\n // Override sameSite/secure for desktop: the renderer's origin\n // (file:// / app://) is cross-site to the API, so SameSite=Lax/Strict\n // would block the cookie on subsequent fetches.\n sameSite: 'none',\n secure: true,\n maxAge,\n });\n return response;\n };\n\n sendPhoneVerificationCode = async (\n _request: Request,\n _sender: (data: { phone: string; verificationCode: string }) => Promise<void>\n ): Promise<Response> => {\n return Response.json({ error: 'not_implemented' }, { status: 501 });\n };\n\n loginPhone = async (_request: Request, _handler: AuthorizedHandler): Promise<Response> => {\n return Response.json({ error: 'not_implemented' }, { status: 501 });\n };\n\n kick = async (principal: Principal): Promise<void> => {\n const sessions = await this.repository.findByPrincipalName(principal.name);\n for (const sessionId of sessions.keys()) {\n await this.repository.deleteById(sessionId);\n }\n };\n\n isAuthenticated = async (request: Request): Promise<boolean> => {\n const sessionId = this.getSessionId(request);\n if (!sessionId) return false;\n const session = await this.repository.findById(sessionId);\n if (!session) return false;\n const principalName = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n if (!principalName) return false;\n return true;\n };\n\n getSession = async <T extends boolean>(\n request: Request,\n create: T\n ): Promise<T extends true ? Session : Session | null> => {\n const sessionId = this.getSessionId(request);\n if (!sessionId) {\n if (create) {\n const session = this.repository.createSession();\n await this.repository.save(session);\n return session;\n } else {\n return null as T extends true ? Session : Session | null;\n }\n }\n const session = await this.repository.findById(sessionId);\n if (!session) {\n if (create) {\n const session = this.repository.createSession();\n await this.repository.save(session);\n return session;\n } else {\n return null as T extends true ? Session : Session | null;\n }\n }\n return session;\n };\n\n deleteSession = async (sessionId: string): Promise<void> => {\n await this.repository.deleteById(sessionId);\n };\n\n getPrincipal = async (request: Request): Promise<Principal | null> => {\n const sessionId = this.getSessionId(request);\n if (!sessionId) return null;\n const session = await this.repository.findById(sessionId);\n if (!session) return null;\n const name = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n if (!name) return null;\n return { name: String(name) };\n };\n\n listSessions = async (principal: Principal): Promise<Session[]> => {\n const sessions = await this.repository.findByPrincipalName(principal.name);\n return Array.from(sessions.values());\n };\n\n cleanupExpiredSessions = async (cleanupCount?: number): Promise<Response> => {\n await this.repository.cleanupExpiredSessions(cleanupCount);\n return Response.json({});\n };\n}\n"],"mappings":"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","names":["timing","body","session"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/auth/index.ts"],"sourcesContent":["import { createHash, randomInt, randomUUID, timingSafeEqual } from 'crypto';\nimport { invariant } from '@shware/utils';\nimport { type Principal, Provider } from '../core/index';\nimport { loginEmailSchema, sendEmailVerificationCodeSchema } from '../email/schema';\nimport { OAuth2Client, googleOneTapSchema, oauth2RedirectQuerySchema } from '../oauth2/client';\nimport type { OAuth2ErrorType } from '../oauth2/error';\nimport { google } from '../oauth2/provider/index';\nimport type {\n NativeCredential,\n OAuth2AuthorizationRequest,\n PkceParameters,\n StandardClaims,\n} from '../oauth2/types';\nimport { PRINCIPAL_NAME_INDEX_NAME } from '../session/common';\nimport type { KVRepository, Session, SessionRepository } from '../session/types';\nimport {\n type CookieOptions,\n deleteCookie,\n getCookie,\n param,\n query,\n setCookie,\n} from '../utils/http';\nimport { timing } from '../utils/timing';\nimport { invalidArgument, valid, verifyTurnstileToken } from '../utils/valid';\nimport type { AuthConfig, AuthService, AuthorizedHandler, LoggedHandler } from './types';\n\n// PKCE per RFC 7636: code_verifier / code_challenge are 43-128 chars from\n// the unreserved set. We restrict to base64url (drops \".\" and \"~\") since\n// that's what S256 produces and what callers should be sending; rejecting\n// the rest shrinks the accepted input shape without losing legit clients.\n// A 32-byte random verifier base64url-encodes to exactly 43 chars.\nconst PKCE_FORMAT = /^[A-Za-z0-9_-]{43,128}$/;\n\ninterface DesktopCodeRecord {\n /** principal.name from the upstream cookie session */\n name: string;\n /** code_challenge (S256, base64url) — verifier must hash to this */\n cc: string;\n}\n\nexport const PATH = {\n CSRF: '/csrf',\n LOGOUT: '/logout',\n LOGGED: '/logged',\n\n OAUTH2_STATE: '/oauth2/state/:registrationId',\n OAUTH2_NONCE: '/oauth2/nonce/:registrationId',\n OAUTH2_AUTHORIZATION: '/oauth2/authorization/:registrationId',\n LOGIN_OAUTH2_CODE: '/login/oauth2/code/:registrationId',\n LOGIN_OAUTH2_NATIVE: '/login/oauth2/native/:registrationId',\n LOGIN_OAUTH2_ONETAP: '/login/oauth2/onetap/google',\n\n LOGIN_EMAIL: '/login/email',\n LOGIN_PHONE: '/login/phone',\n SEND_EMAIL_VERIFICATION_CODE: '/auth/sendEmailVerificationCode',\n SEND_PHONE_VERIFICATION_CODE: '/auth/sendPhoneVerificationCode',\n\n // Desktop / native app sign-in via web loopback (RFC 8252 + OAuth 2.1 Auth Code Flow):\n // 1. user already authenticated with cookie session → POST DESKTOP_AUTHORIZE → { code }\n // 2. web redirects to desktop's loopback callback with the code\n // 3. desktop renderer POSTs DESKTOP_EXCHANGE with the code → server returns\n // Set-Cookie carrying a fresh session, single-use code is invalidated\n // The two-step flow keeps the actual session id off the redirect URL — only a\n // 5-minute single-use code transits via the loopback.\n DESKTOP_AUTHORIZE: '/auth/desktop/authorize',\n DESKTOP_EXCHANGE: '/auth/desktop/exchange',\n\n CLEANUP_EXPIRED_SESSIONS: '/sessions/expired/cleanup',\n} as const;\n\nexport class Auth implements AuthService {\n private readonly timing: boolean;\n private readonly cookieName;\n private readonly cookieOptions: CookieOptions;\n private readonly kv: KVRepository;\n private readonly repository: SessionRepository;\n private readonly turnstileSecretKey?: string;\n private readonly oauth2Client: OAuth2Client | null;\n private readonly ATTR_OAUTH2_AUTHORIZATION_REQUEST = 'oauth2AuthorizationRequest';\n\n constructor({\n sessionRepository,\n kvRepository,\n turnstileSecretKey,\n oauth2,\n cookie,\n timing,\n }: AuthConfig) {\n this.timing = timing ?? false;\n this.kv = kvRepository;\n this.repository = sessionRepository;\n this.turnstileSecretKey = turnstileSecretKey;\n const { name, ...cookieOptions } = cookie ?? {};\n this.cookieName = name ?? 'SESSION';\n this.cookieOptions = {\n path: '/',\n sameSite: 'none',\n secure: true,\n httpOnly: true,\n ...cookieOptions,\n };\n this.oauth2Client = oauth2?.client ? new OAuth2Client(oauth2.client) : null;\n }\n\n private getSessionId(request: Request): string | undefined {\n let sessionId: string | undefined;\n const header = request.headers.get('Authorization');\n if (header?.startsWith('Bearer ')) {\n sessionId = header.replace('Bearer ', '');\n }\n if (!sessionId) {\n sessionId = getCookie(request, this.cookieName);\n }\n return sessionId;\n }\n\n csrf = async (_request: Request): Promise<Response> => {\n const token = randomUUID();\n const response = Response.json({\n token,\n parameterName: '_csrf',\n headerName: 'X-XSRF-TOKEN',\n });\n\n // Double Submit Cookie Protection\n setCookie(response, 'XSRF-TOKEN', token, {\n path: '/',\n secure: true,\n httpOnly: false,\n sameSite: 'none',\n domain: this.cookieOptions.domain,\n });\n return response;\n };\n\n logout = async (request: Request): Promise<Response> => {\n const sessionId = this.getSessionId(request);\n if (sessionId) await this.repository.deleteById(sessionId);\n const response = Response.json({});\n deleteCookie(response, this.cookieName);\n return response;\n };\n\n logged = async (request: Request, onLogged?: LoggedHandler): Promise<Response> => {\n // 1. get session from header or cookie\n const sessionId = this.getSessionId(request);\n if (!sessionId) return Response.json({ data: false });\n const session = await this.repository.findById(sessionId);\n if (!session) return Response.json({ data: false });\n\n // 2. get principal name from session\n const name = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n session.setLastAccessedTime(Date.now());\n await this.repository.save(session);\n\n // 3. call onLogged\n if (name !== null) await onLogged?.({ name: String(name) });\n\n // 4. renew cookie\n const response = Response.json({ data: name !== null });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n return response;\n };\n\n private createPkceParameters(): PkceParameters {\n const code_verifier = randomUUID();\n const code_challenge = createHash('sha256')\n .update(code_verifier)\n .digest()\n .toString('base64url');\n\n return { code_verifier, code_challenge, code_challenge_method: 'S256' };\n }\n\n private getOauth2StateKey(state: string) {\n return `oauth2:state:${state}`;\n }\n\n private getOauth2NonceKey(nonce: string) {\n return `oauth2:nonce:${nonce}`;\n }\n\n private getEmailVerificationCodeKey(email: string) {\n return `email:verification:${email}`;\n }\n\n private getPhoneVerificationCodeKey(phone: string) {\n return `phone:verification:${phone}`;\n }\n\n private getDesktopCodeKey(code: string) {\n return `desktop:code:${code}`;\n }\n\n oauth2State = async (request: Request): Promise<Response> => {\n const { registrationId } = param(request, PATH.OAUTH2_STATE);\n const state = randomUUID();\n await this.kv.setItem(this.getOauth2StateKey(state), registrationId, 10 * 60);\n return Response.json({ state });\n };\n\n oauth2Nonce = async (request: Request): Promise<Response> => {\n const { registrationId } = param(request, PATH.OAUTH2_NONCE);\n const nonce = randomUUID();\n await this.kv.setItem(this.getOauth2NonceKey(nonce), registrationId, 10 * 60);\n return Response.json({ nonce });\n };\n\n oauth2Authorization = async (request: Request): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n const { registrationId } = param(request, PATH.OAUTH2_AUTHORIZATION);\n const state = randomUUID();\n const pkce = this.createPkceParameters();\n const uri = await this.oauth2Client.createAuthorizationUri({ registrationId, state, pkce });\n const sessionId = this.getSessionId(request);\n const session = sessionId\n ? ((await this.repository.findById(sessionId)) ?? this.repository.createSession())\n : this.repository.createSession();\n mark('fetch_session');\n\n const authorizationRequest: OAuth2AuthorizationRequest = {\n state,\n registrationId,\n authorizationRequestUri: uri.href,\n additionalParameters: pkce,\n };\n\n const value = JSON.stringify(authorizationRequest);\n session.setAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST, value);\n await this.repository.save(session);\n mark('save_session');\n\n const response = new Response(null, { status: 302, headers: { location: uri.href } });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n private redirect = (error: OAuth2ErrorType, description?: string): Response => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const uri = new URL(this.oauth2Client.errorUri);\n uri.searchParams.set('error', error);\n if (description) uri.searchParams.set('error_description', description);\n return Response.redirect(uri.href, 302);\n };\n\n loginOAuth2Code = async (request: Request, handler: AuthorizedHandler): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n\n // 1. get session from header or cookie\n const sessionId = this.getSessionId(request);\n const session = sessionId ? await this.repository.findById(sessionId) : null;\n if (!session) {\n return this.redirect('invalid_request', 'session not found');\n }\n mark('load_session');\n\n // 2. get cached authorization request from session\n const json = session.getAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST) as string | null;\n if (!json) {\n return this.redirect('invalid_request', 'authorization request not found');\n }\n const cached: OAuth2AuthorizationRequest = JSON.parse(json);\n\n // 3. validate redirect query/formdata\n const { registrationId } = param(request, PATH.LOGIN_OAUTH2_CODE);\n let data: Record<string, string>;\n // apple redirect: response_mode=form_post\n if (\n request.method === 'POST' &&\n request.headers.get('content-type')?.toLowerCase() === 'application/x-www-form-urlencoded'\n ) {\n const params = new URLSearchParams(await request.text());\n data = Object.fromEntries(params.entries());\n } else {\n data = query(request);\n }\n\n const parsed = oauth2RedirectQuerySchema.safeParse(data);\n if (!parsed.success) {\n return this.redirect('invalid_request', 'invalid redirect query');\n }\n if (!parsed.data.code || !parsed.data.state) {\n return this.redirect('invalid_request', 'invalid redirect query');\n }\n if (parsed.data.state !== cached.state) {\n return this.redirect('invalid_request', 'redirect state mismatch');\n }\n if (registrationId !== cached.registrationId) {\n return this.redirect('invalid_request', 'redirect registration mismatch');\n }\n mark('validate_session');\n\n // 4. exchange authorization code for token and get user info\n const { code } = parsed.data;\n\n const token = await this.oauth2Client.exchangeAuthorizationCode({\n registrationId,\n code,\n pkce: cached.additionalParameters,\n });\n const userInfo = await this.oauth2Client.getUserInfo({ registrationId, token });\n mark('exchange_code');\n\n // 5. create or update principal\n const principal = await handler(request, registrationId, userInfo, token);\n mark('save_principal');\n\n // 6. update session\n session.setLastAccessedTime(Date.now());\n session.removeAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST);\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n mark('update_session');\n\n const uri = new URL(this.oauth2Client.successUri);\n uri.searchParams.set('status', 'success');\n uri.searchParams.set('registrationId', registrationId);\n\n const response = new Response(null, { status: 302, headers: { location: uri.href } });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n loginOAuth2Native = async (request: Request, handler: AuthorizedHandler): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n const { registrationId } = param(request, PATH.LOGIN_OAUTH2_NATIVE);\n const credentials = (await request.json()) as NativeCredential;\n const { userInfo, token } = await this.oauth2Client.loginOAuth2Native({\n registrationId,\n credentials,\n });\n mark('login_oauth2_native');\n\n const { nonce } = userInfo.data as { nonce?: string };\n if (nonce) {\n const value = await this.kv.getItem(this.getOauth2NonceKey(nonce));\n if (value !== registrationId) {\n const json = { error: 'invalid_request', error_description: 'nonce mismatch' };\n return Response.json(json, { status: 400 });\n }\n await this.kv.removeItem(this.getOauth2NonceKey(nonce));\n mark('check_nonce');\n }\n\n const principal = await handler(request, registrationId, userInfo, token);\n mark('save_principal');\n\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n loginOAuth2Onetap = async (\n request: Request,\n handler: AuthorizedHandler,\n registrationId = 'google'\n ): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n\n // should support application/x-www-form-urlencoded and application/json\n let body: Record<string, string>;\n if (request.headers.get('Content-Type')?.includes('application/x-www-form-urlencoded')) {\n const text = await request.text();\n body = Object.fromEntries(new URLSearchParams(text));\n } else if (request.headers.get('Content-Type')?.includes('application/json')) {\n body = (await request.json()) as Record<string, string>;\n } else {\n const json = { error: 'invalid_request', error_description: 'invalid content type' };\n return Response.json(json, { status: 400 });\n }\n\n const parsed = googleOneTapSchema.parse(body);\n\n // csrf token is not supported in FedCM mode\n // const csrf_cookie = getCookie(request, 'g_csrf_token');\n // const csrf_request = parsed.g_csrf_token;\n // if (!csrf_request || csrf_cookie !== csrf_request) {\n // const json = { error: 'invalid_request', error_description: 'invalid csrf token' };\n // return Response.json(json, { status: 400 });\n // }\n\n let principal: Principal;\n if (parsed.credential) {\n const { token, userInfo } = await google.getTokenInfo(parsed.credential);\n principal = await handler(request, registrationId, userInfo, token);\n } else if (parsed.code) {\n const token = await this.oauth2Client.exchangeAuthorizationCode({\n registrationId,\n code: parsed.code,\n });\n const userInfo = await this.oauth2Client.getUserInfo({ registrationId, token });\n principal = await handler(request, registrationId, userInfo, token);\n } else {\n throw new Error('invalid request');\n }\n\n mark('save_principal');\n\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n sendEmailVerificationCode = async (\n request: Request,\n sender: (data: { email: string; verificationCode: string }) => Promise<void>\n ): Promise<Response> => {\n invariant(this.turnstileSecretKey, 'turnstileSecretKey is not configured');\n\n const { data, error } = valid(sendEmailVerificationCodeSchema, await request.json());\n if (error) return Response.json(error, { status: error.error.code });\n const { email, turnstileToken } = data;\n\n const result = await verifyTurnstileToken(this.turnstileSecretKey, turnstileToken);\n if (result.error) return Response.json(result.error, { status: result.error.error.code });\n\n try {\n const verificationCode = randomInt(1_000_000).toString().padStart(6, '0');\n await sender({ email, verificationCode });\n await this.kv.setItem(this.getEmailVerificationCodeKey(email), verificationCode, 10 * 60);\n return Response.json({});\n } catch (e) {\n console.error('Failed to send email verification code', e);\n return Response.json(invalidArgument('EMAIL_DELIVERY_FAILED'), { status: 400 });\n }\n };\n\n loginEmail = async (request: Request, handler: AuthorizedHandler): Promise<Response> => {\n const { data, error } = valid(loginEmailSchema, await request.json());\n if (error) return Response.json(error, { status: error.error.code });\n const { email, verificationCode } = data;\n\n const key = this.getEmailVerificationCodeKey(email);\n const stored = await this.kv.getItem(key);\n if (\n typeof stored !== 'string' ||\n stored.length !== verificationCode.length ||\n !timingSafeEqual(Buffer.from(stored), Buffer.from(verificationCode))\n ) {\n return Response.json(invalidArgument('INVALID_VERIFICATION_CODE'), { status: 400 });\n }\n await this.kv.removeItem(key);\n const claims: StandardClaims = { sub: email, email, email_verified: true };\n const principal = await handler(request, Provider.EMAIL, { claims, data: {} });\n\n // save session\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n\n return response;\n };\n\n /**\n * Step 1 of the desktop / native app sign-in handshake. The caller already\n * completed normal sign-in on the web (so a cookie session exists); this\n * issues a 5-minute single-use code that the web page hands off to the\n * desktop client via a loopback redirect.\n *\n * PKCE (RFC 7636, S256 only): the desktop generates a random `code_verifier`\n * before opening the browser, derives `code_challenge = base64url(sha256(verifier))`,\n * and threads the challenge through the URL → web page → here. We pin\n * the challenge to the code in KV; `desktopExchange` enforces that the\n * caller knows the matching verifier. This blocks an attacker who only\n * intercepts the auth code (browser history, malicious extension, packet\n * capture on loopback) from exchanging it.\n *\n * Requires an authenticated cookie session — anonymous callers get 401.\n * Throttling / abuse protection on this endpoint is the API layer's job\n * (rate-limit per principal); the SDK doesn't gate it because the\n * upstream sign-in flow already passed CAPTCHA / OAuth / verification.\n */\n desktopAuthorize = async (request: Request): Promise<Response> => {\n const principal = await this.getPrincipal(request);\n if (!principal) {\n const body = {\n error: {\n code: 401,\n status: 'UNAUTHENTICATED',\n message: 'sign in first',\n details: [],\n },\n };\n return Response.json(body, { status: 401 });\n }\n\n let body: { code_challenge?: unknown; code_challenge_method?: unknown };\n try {\n body = (await request.json()) as {\n code_challenge?: unknown;\n code_challenge_method?: unknown;\n };\n } catch {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const codeChallenge = typeof body.code_challenge === 'string' ? body.code_challenge : null;\n if (\n !codeChallenge ||\n body.code_challenge_method !== 'S256' ||\n !PKCE_FORMAT.test(codeChallenge)\n ) {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const code = randomUUID();\n const record: DesktopCodeRecord = { name: principal.name, cc: codeChallenge };\n await this.kv.setItem(this.getDesktopCodeKey(code), JSON.stringify(record), 5 * 60);\n\n return Response.json({ code });\n };\n\n /**\n * Step 2 of the desktop / native app sign-in handshake. The desktop\n * client hands back the auth code it received via the loopback callback,\n * and we mint a fresh session for it — independent of the web cookie\n * session that originally `authorize`'d the code, so logging out on\n * either side doesn't cascade to the other.\n *\n * Cookie attributes: defaults to `SameSite=None; Secure` so the cookie\n * survives cross-site fetches from the desktop renderer's `file://` /\n * `app://` origin. The configured base `cookieOptions` provide\n * everything else (`domain`, `path`, `httpOnly`, `maxAge`); the override\n * is targeted at the SameSite/Secure pair only. In a non-HTTPS dev\n * backend this combination is rejected by Chromium — see the desktop\n * sign-in PRD for workarounds.\n *\n * No CSRF check needed — the request carries no cookie, the auth code\n * itself is the credential, and KV's single-use removal blocks replay.\n */\n desktopExchange = async (request: Request): Promise<Response> => {\n let body: { code?: unknown; code_verifier?: unknown };\n try {\n body = (await request.json()) as { code?: unknown; code_verifier?: unknown };\n } catch {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const code = typeof body.code === 'string' ? body.code : null;\n const verifier = typeof body.code_verifier === 'string' ? body.code_verifier : null;\n if (!code || !verifier || !PKCE_FORMAT.test(verifier)) {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const key = this.getDesktopCodeKey(code);\n const raw = await this.kv.getItem(key);\n if (typeof raw !== 'string') {\n return Response.json(invalidArgument('INVALID_DESKTOP_CODE'), { status: 400 });\n }\n let record: DesktopCodeRecord;\n try {\n record = JSON.parse(raw) as DesktopCodeRecord;\n } catch {\n // Malformed KV record (shouldn't happen with our own writes) — drop it\n // and surface as an invalid code; the caller can retry the whole flow.\n await this.kv.removeItem(key);\n return Response.json(invalidArgument('INVALID_DESKTOP_CODE'), { status: 400 });\n }\n\n // Verify PKCE: base64url(sha256(verifier)) === stored challenge.\n // Don't burn the code on a verifier mismatch — that would let an\n // attacker who races a network retry brick a legit exchange. The code\n // is single-use on success and 5-min TTL bounded; brute-forcing the\n // 256-bit verifier in that window is infeasible.\n const computed = createHash('sha256').update(verifier).digest();\n const stored = Buffer.from(record.cc, 'base64url');\n if (stored.length !== computed.length || !timingSafeEqual(computed, stored)) {\n return Response.json(invalidArgument('INVALID_DESKTOP_CODE'), { status: 400 });\n }\n\n // One-time use: remove BEFORE creating the session so a concurrent\n // duplicate exchange (e.g. retry after timeout) sees the code as\n // consumed even if our session save races it.\n await this.kv.removeItem(key);\n\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, record.name);\n session.setAttribute('client_type', 'desktop');\n await this.repository.save(session);\n\n const response = Response.json({ ok: true });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), {\n ...this.cookieOptions,\n // Override sameSite/secure for desktop: the renderer's origin\n // (file:// / app://) is cross-site to the API, so SameSite=Lax/Strict\n // would block the cookie on subsequent fetches.\n sameSite: 'none',\n secure: true,\n maxAge,\n });\n return response;\n };\n\n sendPhoneVerificationCode = async (\n _request: Request,\n _sender: (data: { phone: string; verificationCode: string }) => Promise<void>\n ): Promise<Response> => {\n return Response.json({ error: 'not_implemented' }, { status: 501 });\n };\n\n loginPhone = async (_request: Request, _handler: AuthorizedHandler): Promise<Response> => {\n return Response.json({ error: 'not_implemented' }, { status: 501 });\n };\n\n kick = async (principal: Principal): Promise<void> => {\n const sessions = await this.repository.findByPrincipalName(principal.name);\n for (const sessionId of sessions.keys()) {\n await this.repository.deleteById(sessionId);\n }\n };\n\n isAuthenticated = async (request: Request): Promise<boolean> => {\n const sessionId = this.getSessionId(request);\n if (!sessionId) return false;\n const session = await this.repository.findById(sessionId);\n if (!session) return false;\n const principalName = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n if (!principalName) return false;\n return true;\n };\n\n getSession = async <T extends boolean>(\n request: Request,\n create: T\n ): Promise<T extends true ? Session : Session | null> => {\n const sessionId = this.getSessionId(request);\n if (!sessionId) {\n if (create) {\n const session = this.repository.createSession();\n await this.repository.save(session);\n return session;\n } else {\n return null as T extends true ? Session : Session | null;\n }\n }\n const session = await this.repository.findById(sessionId);\n if (!session) {\n if (create) {\n const session = this.repository.createSession();\n await this.repository.save(session);\n return session;\n } else {\n return null as T extends true ? Session : Session | null;\n }\n }\n return session;\n };\n\n deleteSession = async (sessionId: string): Promise<void> => {\n await this.repository.deleteById(sessionId);\n };\n\n getPrincipal = async (request: Request): Promise<Principal | null> => {\n const sessionId = this.getSessionId(request);\n if (!sessionId) return null;\n const session = await this.repository.findById(sessionId);\n if (!session) return null;\n const name = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n if (!name) return null;\n return { name: String(name) };\n };\n\n listSessions = async (principal: Principal): Promise<Session[]> => {\n const sessions = await this.repository.findByPrincipalName(principal.name);\n return Array.from(sessions.values());\n };\n\n cleanupExpiredSessions = async (cleanupCount?: number): Promise<Response> => {\n await this.repository.cleanupExpiredSessions(cleanupCount);\n return Response.json({});\n };\n}\n"],"mappings":";AAAA,SAAS,YAAY,WAAW,YAAY,uBAAuB;AACnE,SAAS,iBAAiB;AAC1B,SAAyB,gBAAgB;AACzC,SAAS,kBAAkB,uCAAuC;AAClE,SAAS,cAAc,oBAAoB,iCAAiC;AAE5E,SAAS,cAAc;AAOvB,SAAS,iCAAiC;AAE1C;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,cAAc;AACvB,SAAS,iBAAiB,OAAO,4BAA4B;AAQ7D,IAAM,cAAc;AASb,IAAM,OAAO;AAAA,EAClB,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EAER,cAAc;AAAA,EACd,cAAc;AAAA,EACd,sBAAsB;AAAA,EACtB,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EAErB,aAAa;AAAA,EACb,aAAa;AAAA,EACb,8BAA8B;AAAA,EAC9B,8BAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS9B,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAElB,0BAA0B;AAC5B;AAEO,IAAM,OAAN,MAAkC;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,oCAAoC;AAAA,EAErD,YAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAAA;AAAA,EACF,GAAe;AACb,SAAK,SAASA,WAAU;AACxB,SAAK,KAAK;AACV,SAAK,aAAa;AAClB,SAAK,qBAAqB;AAC1B,UAAM,EAAE,MAAM,GAAG,cAAc,IAAI,UAAU,CAAC;AAC9C,SAAK,aAAa,QAAQ;AAC1B,SAAK,gBAAgB;AAAA,MACnB,MAAM;AAAA,MACN,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,GAAG;AAAA,IACL;AACA,SAAK,eAAe,QAAQ,SAAS,IAAI,aAAa,OAAO,MAAM,IAAI;AAAA,EACzE;AAAA,EAEQ,aAAa,SAAsC;AACzD,QAAI;AACJ,UAAM,SAAS,QAAQ,QAAQ,IAAI,eAAe;AAClD,QAAI,QAAQ,WAAW,SAAS,GAAG;AACjC,kBAAY,OAAO,QAAQ,WAAW,EAAE;AAAA,IAC1C;AACA,QAAI,CAAC,WAAW;AACd,kBAAY,UAAU,SAAS,KAAK,UAAU;AAAA,IAChD;AACA,WAAO;AAAA,EACT;AAAA,EAEA,OAAO,OAAO,aAAyC;AACrD,UAAM,QAAQ,WAAW;AACzB,UAAM,WAAW,SAAS,KAAK;AAAA,MAC7B;AAAA,MACA,eAAe;AAAA,MACf,YAAY;AAAA,IACd,CAAC;AAGD,cAAU,UAAU,cAAc,OAAO;AAAA,MACvC,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,UAAU;AAAA,MACV,QAAQ,KAAK,cAAc;AAAA,IAC7B,CAAC;AACD,WAAO;AAAA,EACT;AAAA,EAEA,SAAS,OAAO,YAAwC;AACtD,UAAM,YAAY,KAAK,aAAa,OAAO;AAC3C,QAAI,UAAW,OAAM,KAAK,WAAW,WAAW,SAAS;AACzD,UAAM,WAAW,SAAS,KAAK,CAAC,CAAC;AACjC,iBAAa,UAAU,KAAK,UAAU;AACtC,WAAO;AAAA,EACT;AAAA,EAEA,SAAS,OAAO,SAAkB,aAAgD;AAEhF,UAAM,YAAY,KAAK,aAAa,OAAO;AAC3C,QAAI,CAAC,UAAW,QAAO,SAAS,KAAK,EAAE,MAAM,MAAM,CAAC;AACpD,UAAM,UAAU,MAAM,KAAK,WAAW,SAAS,SAAS;AACxD,QAAI,CAAC,QAAS,QAAO,SAAS,KAAK,EAAE,MAAM,MAAM,CAAC;AAGlD,UAAM,OAAO,QAAQ,aAAa,yBAAyB;AAC3D,YAAQ,oBAAoB,KAAK,IAAI,CAAC;AACtC,UAAM,KAAK,WAAW,KAAK,OAAO;AAGlC,QAAI,SAAS,KAAM,OAAM,WAAW,EAAE,MAAM,OAAO,IAAI,EAAE,CAAC;AAG1D,UAAM,WAAW,SAAS,KAAK,EAAE,MAAM,SAAS,KAAK,CAAC;AACtD,UAAM,SAAS,QAAQ,uBAAuB;AAC9C,cAAU,UAAU,KAAK,YAAY,QAAQ,MAAM,GAAG,EAAE,GAAG,KAAK,eAAe,OAAO,CAAC;AACvF,WAAO;AAAA,EACT;AAAA,EAEQ,uBAAuC;AAC7C,UAAM,gBAAgB,WAAW;AACjC,UAAM,iBAAiB,WAAW,QAAQ,EACvC,OAAO,aAAa,EACpB,OAAO,EACP,SAAS,WAAW;AAEvB,WAAO,EAAE,eAAe,gBAAgB,uBAAuB,OAAO;AAAA,EACxE;AAAA,EAEQ,kBAAkB,OAAe;AACvC,WAAO,gBAAgB,KAAK;AAAA,EAC9B;AAAA,EAEQ,kBAAkB,OAAe;AACvC,WAAO,gBAAgB,KAAK;AAAA,EAC9B;AAAA,EAEQ,4BAA4B,OAAe;AACjD,WAAO,sBAAsB,KAAK;AAAA,EACpC;AAAA,EAEQ,4BAA4B,OAAe;AACjD,WAAO,sBAAsB,KAAK;AAAA,EACpC;AAAA,EAEQ,kBAAkB,MAAc;AACtC,WAAO,gBAAgB,IAAI;AAAA,EAC7B;AAAA,EAEA,cAAc,OAAO,YAAwC;AAC3D,UAAM,EAAE,eAAe,IAAI,MAAM,SAAS,KAAK,YAAY;AAC3D,UAAM,QAAQ,WAAW;AACzB,UAAM,KAAK,GAAG,QAAQ,KAAK,kBAAkB,KAAK,GAAG,gBAAgB,KAAK,EAAE;AAC5E,WAAO,SAAS,KAAK,EAAE,MAAM,CAAC;AAAA,EAChC;AAAA,EAEA,cAAc,OAAO,YAAwC;AAC3D,UAAM,EAAE,eAAe,IAAI,MAAM,SAAS,KAAK,YAAY;AAC3D,UAAM,QAAQ,WAAW;AACzB,UAAM,KAAK,GAAG,QAAQ,KAAK,kBAAkB,KAAK,GAAG,gBAAgB,KAAK,EAAE;AAC5E,WAAO,SAAS,KAAK,EAAE,MAAM,CAAC;AAAA,EAChC;AAAA,EAEA,sBAAsB,OAAO,YAAwC;AACnE,cAAU,KAAK,cAAc,iCAAiC;AAC9D,UAAM,EAAE,MAAM,UAAU,IAAI,OAAO,EAAE,SAAS,KAAK,OAAO,CAAC;AAC3D,UAAM,EAAE,eAAe,IAAI,MAAM,SAAS,KAAK,oBAAoB;AACnE,UAAM,QAAQ,WAAW;AACzB,UAAM,OAAO,KAAK,qBAAqB;AACvC,UAAM,MAAM,MAAM,KAAK,aAAa,uBAAuB,EAAE,gBAAgB,OAAO,KAAK,CAAC;AAC1F,UAAM,YAAY,KAAK,aAAa,OAAO;AAC3C,UAAM,UAAU,YACV,MAAM,KAAK,WAAW,SAAS,SAAS,KAAM,KAAK,WAAW,cAAc,IAC9E,KAAK,WAAW,cAAc;AAClC,SAAK,eAAe;AAEpB,UAAM,uBAAmD;AAAA,MACvD;AAAA,MACA;AAAA,MACA,yBAAyB,IAAI;AAAA,MAC7B,sBAAsB;AAAA,IACxB;AAEA,UAAM,QAAQ,KAAK,UAAU,oBAAoB;AACjD,YAAQ,aAAa,KAAK,mCAAmC,KAAK;AAClE,UAAM,KAAK,WAAW,KAAK,OAAO;AAClC,SAAK,cAAc;AAEnB,UAAM,WAAW,IAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,SAAS,EAAE,UAAU,IAAI,KAAK,EAAE,CAAC;AACpF,UAAM,SAAS,QAAQ,uBAAuB;AAC9C,cAAU,UAAU,KAAK,YAAY,QAAQ,MAAM,GAAG,EAAE,GAAG,KAAK,eAAe,OAAO,CAAC;AACvF,cAAU,QAAQ;AAElB,WAAO;AAAA,EACT;AAAA,EAEQ,WAAW,CAAC,OAAwB,gBAAmC;AAC7E,cAAU,KAAK,cAAc,iCAAiC;AAC9D,UAAM,MAAM,IAAI,IAAI,KAAK,aAAa,QAAQ;AAC9C,QAAI,aAAa,IAAI,SAAS,KAAK;AACnC,QAAI,YAAa,KAAI,aAAa,IAAI,qBAAqB,WAAW;AACtE,WAAO,SAAS,SAAS,IAAI,MAAM,GAAG;AAAA,EACxC;AAAA,EAEA,kBAAkB,OAAO,SAAkB,YAAkD;AAC3F,cAAU,KAAK,cAAc,iCAAiC;AAC9D,UAAM,EAAE,MAAM,UAAU,IAAI,OAAO,EAAE,SAAS,KAAK,OAAO,CAAC;AAG3D,UAAM,YAAY,KAAK,aAAa,OAAO;AAC3C,UAAM,UAAU,YAAY,MAAM,KAAK,WAAW,SAAS,SAAS,IAAI;AACxE,QAAI,CAAC,SAAS;AACZ,aAAO,KAAK,SAAS,mBAAmB,mBAAmB;AAAA,IAC7D;AACA,SAAK,cAAc;AAGnB,UAAM,OAAO,QAAQ,aAAa,KAAK,iCAAiC;AACxE,QAAI,CAAC,MAAM;AACT,aAAO,KAAK,SAAS,mBAAmB,iCAAiC;AAAA,IAC3E;AACA,UAAM,SAAqC,KAAK,MAAM,IAAI;AAG1D,UAAM,EAAE,eAAe,IAAI,MAAM,SAAS,KAAK,iBAAiB;AAChE,QAAI;AAEJ,QACE,QAAQ,WAAW,UACnB,QAAQ,QAAQ,IAAI,cAAc,GAAG,YAAY,MAAM,qCACvD;AACA,YAAM,SAAS,IAAI,gBAAgB,MAAM,QAAQ,KAAK,CAAC;AACvD,aAAO,OAAO,YAAY,OAAO,QAAQ,CAAC;AAAA,IAC5C,OAAO;AACL,aAAO,MAAM,OAAO;AAAA,IACtB;AAEA,UAAM,SAAS,0BAA0B,UAAU,IAAI;AACvD,QAAI,CAAC,OAAO,SAAS;AACnB,aAAO,KAAK,SAAS,mBAAmB,wBAAwB;AAAA,IAClE;AACA,QAAI,CAAC,OAAO,KAAK,QAAQ,CAAC,OAAO,KAAK,OAAO;AAC3C,aAAO,KAAK,SAAS,mBAAmB,wBAAwB;AAAA,IAClE;AACA,QAAI,OAAO,KAAK,UAAU,OAAO,OAAO;AACtC,aAAO,KAAK,SAAS,mBAAmB,yBAAyB;AAAA,IACnE;AACA,QAAI,mBAAmB,OAAO,gBAAgB;AAC5C,aAAO,KAAK,SAAS,mBAAmB,gCAAgC;AAAA,IAC1E;AACA,SAAK,kBAAkB;AAGvB,UAAM,EAAE,KAAK,IAAI,OAAO;AAExB,UAAM,QAAQ,MAAM,KAAK,aAAa,0BAA0B;AAAA,MAC9D;AAAA,MACA;AAAA,MACA,MAAM,OAAO;AAAA,IACf,CAAC;AACD,UAAM,WAAW,MAAM,KAAK,aAAa,YAAY,EAAE,gBAAgB,MAAM,CAAC;AAC9E,SAAK,eAAe;AAGpB,UAAM,YAAY,MAAM,QAAQ,SAAS,gBAAgB,UAAU,KAAK;AACxE,SAAK,gBAAgB;AAGrB,YAAQ,oBAAoB,KAAK,IAAI,CAAC;AACtC,YAAQ,gBAAgB,KAAK,iCAAiC;AAC9D,YAAQ,aAAa,2BAA2B,UAAU,IAAI;AAC9D,UAAM,KAAK,WAAW,KAAK,OAAO;AAClC,SAAK,gBAAgB;AAErB,UAAM,MAAM,IAAI,IAAI,KAAK,aAAa,UAAU;AAChD,QAAI,aAAa,IAAI,UAAU,SAAS;AACxC,QAAI,aAAa,IAAI,kBAAkB,cAAc;AAErD,UAAM,WAAW,IAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,SAAS,EAAE,UAAU,IAAI,KAAK,EAAE,CAAC;AACpF,UAAM,SAAS,QAAQ,uBAAuB;AAC9C,cAAU,UAAU,KAAK,YAAY,QAAQ,MAAM,GAAG,EAAE,GAAG,KAAK,eAAe,OAAO,CAAC;AACvF,cAAU,QAAQ;AAElB,WAAO;AAAA,EACT;AAAA,EAEA,oBAAoB,OAAO,SAAkB,YAAkD;AAC7F,cAAU,KAAK,cAAc,iCAAiC;AAC9D,UAAM,EAAE,MAAM,UAAU,IAAI,OAAO,EAAE,SAAS,KAAK,OAAO,CAAC;AAC3D,UAAM,EAAE,eAAe,IAAI,MAAM,SAAS,KAAK,mBAAmB;AAClE,UAAM,cAAe,MAAM,QAAQ,KAAK;AACxC,UAAM,EAAE,UAAU,MAAM,IAAI,MAAM,KAAK,aAAa,kBAAkB;AAAA,MACpE;AAAA,MACA;AAAA,IACF,CAAC;AACD,SAAK,qBAAqB;AAE1B,UAAM,EAAE,MAAM,IAAI,SAAS;AAC3B,QAAI,OAAO;AACT,YAAM,QAAQ,MAAM,KAAK,GAAG,QAAQ,KAAK,kBAAkB,KAAK,CAAC;AACjE,UAAI,UAAU,gBAAgB;AAC5B,cAAM,OAAO,EAAE,OAAO,mBAAmB,mBAAmB,iBAAiB;AAC7E,eAAO,SAAS,KAAK,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,MAC5C;AACA,YAAM,KAAK,GAAG,WAAW,KAAK,kBAAkB,KAAK,CAAC;AACtD,WAAK,aAAa;AAAA,IACpB;AAEA,UAAM,YAAY,MAAM,QAAQ,SAAS,gBAAgB,UAAU,KAAK;AACxE,SAAK,gBAAgB;AAErB,UAAM,UAAU,KAAK,WAAW,cAAc;AAC9C,YAAQ,aAAa,2BAA2B,UAAU,IAAI;AAC9D,UAAM,KAAK,WAAW,KAAK,OAAO;AAClC,UAAM,WAAW,IAAI,SAAS,KAAK,UAAU,SAAS,GAAG;AAAA,MACvD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAChD,CAAC;AACD,UAAM,SAAS,QAAQ,uBAAuB;AAC9C,cAAU,UAAU,KAAK,YAAY,QAAQ,MAAM,GAAG,EAAE,GAAG,KAAK,eAAe,OAAO,CAAC;AACvF,cAAU,QAAQ;AAElB,WAAO;AAAA,EACT;AAAA,EAEA,oBAAoB,OAClB,SACA,SACA,iBAAiB,aACK;AACtB,cAAU,KAAK,cAAc,iCAAiC;AAC9D,UAAM,EAAE,MAAM,UAAU,IAAI,OAAO,EAAE,SAAS,KAAK,OAAO,CAAC;AAG3D,QAAI;AACJ,QAAI,QAAQ,QAAQ,IAAI,cAAc,GAAG,SAAS,mCAAmC,GAAG;AACtF,YAAM,OAAO,MAAM,QAAQ,KAAK;AAChC,aAAO,OAAO,YAAY,IAAI,gBAAgB,IAAI,CAAC;AAAA,IACrD,WAAW,QAAQ,QAAQ,IAAI,cAAc,GAAG,SAAS,kBAAkB,GAAG;AAC5E,aAAQ,MAAM,QAAQ,KAAK;AAAA,IAC7B,OAAO;AACL,YAAM,OAAO,EAAE,OAAO,mBAAmB,mBAAmB,uBAAuB;AACnF,aAAO,SAAS,KAAK,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC5C;AAEA,UAAM,SAAS,mBAAmB,MAAM,IAAI;AAU5C,QAAI;AACJ,QAAI,OAAO,YAAY;AACrB,YAAM,EAAE,OAAO,SAAS,IAAI,MAAM,OAAO,aAAa,OAAO,UAAU;AACvE,kBAAY,MAAM,QAAQ,SAAS,gBAAgB,UAAU,KAAK;AAAA,IACpE,WAAW,OAAO,MAAM;AACtB,YAAM,QAAQ,MAAM,KAAK,aAAa,0BAA0B;AAAA,QAC9D;AAAA,QACA,MAAM,OAAO;AAAA,MACf,CAAC;AACD,YAAM,WAAW,MAAM,KAAK,aAAa,YAAY,EAAE,gBAAgB,MAAM,CAAC;AAC9E,kBAAY,MAAM,QAAQ,SAAS,gBAAgB,UAAU,KAAK;AAAA,IACpE,OAAO;AACL,YAAM,IAAI,MAAM,iBAAiB;AAAA,IACnC;AAEA,SAAK,gBAAgB;AAErB,UAAM,UAAU,KAAK,WAAW,cAAc;AAC9C,YAAQ,aAAa,2BAA2B,UAAU,IAAI;AAC9D,UAAM,KAAK,WAAW,KAAK,OAAO;AAClC,UAAM,WAAW,IAAI,SAAS,KAAK,UAAU,SAAS,GAAG;AAAA,MACvD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAChD,CAAC;AACD,UAAM,SAAS,QAAQ,uBAAuB;AAC9C,cAAU,UAAU,KAAK,YAAY,QAAQ,MAAM,GAAG,EAAE,GAAG,KAAK,eAAe,OAAO,CAAC;AACvF,cAAU,QAAQ;AAElB,WAAO;AAAA,EACT;AAAA,EAEA,4BAA4B,OAC1B,SACA,WACsB;AACtB,cAAU,KAAK,oBAAoB,sCAAsC;AAEzE,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,iCAAiC,MAAM,QAAQ,KAAK,CAAC;AACnF,QAAI,MAAO,QAAO,SAAS,KAAK,OAAO,EAAE,QAAQ,MAAM,MAAM,KAAK,CAAC;AACnE,UAAM,EAAE,OAAO,eAAe,IAAI;AAElC,UAAM,SAAS,MAAM,qBAAqB,KAAK,oBAAoB,cAAc;AACjF,QAAI,OAAO,MAAO,QAAO,SAAS,KAAK,OAAO,OAAO,EAAE,QAAQ,OAAO,MAAM,MAAM,KAAK,CAAC;AAExF,QAAI;AACF,YAAM,mBAAmB,UAAU,GAAS,EAAE,SAAS,EAAE,SAAS,GAAG,GAAG;AACxE,YAAM,OAAO,EAAE,OAAO,iBAAiB,CAAC;AACxC,YAAM,KAAK,GAAG,QAAQ,KAAK,4BAA4B,KAAK,GAAG,kBAAkB,KAAK,EAAE;AACxF,aAAO,SAAS,KAAK,CAAC,CAAC;AAAA,IACzB,SAAS,GAAG;AACV,cAAQ,MAAM,0CAA0C,CAAC;AACzD,aAAO,SAAS,KAAK,gBAAgB,uBAAuB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAChF;AAAA,EACF;AAAA,EAEA,aAAa,OAAO,SAAkB,YAAkD;AACtF,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,kBAAkB,MAAM,QAAQ,KAAK,CAAC;AACpE,QAAI,MAAO,QAAO,SAAS,KAAK,OAAO,EAAE,QAAQ,MAAM,MAAM,KAAK,CAAC;AACnE,UAAM,EAAE,OAAO,iBAAiB,IAAI;AAEpC,UAAM,MAAM,KAAK,4BAA4B,KAAK;AAClD,UAAM,SAAS,MAAM,KAAK,GAAG,QAAQ,GAAG;AACxC,QACE,OAAO,WAAW,YAClB,OAAO,WAAW,iBAAiB,UACnC,CAAC,gBAAgB,OAAO,KAAK,MAAM,GAAG,OAAO,KAAK,gBAAgB,CAAC,GACnE;AACA,aAAO,SAAS,KAAK,gBAAgB,2BAA2B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpF;AACA,UAAM,KAAK,GAAG,WAAW,GAAG;AAC5B,UAAM,SAAyB,EAAE,KAAK,OAAO,OAAO,gBAAgB,KAAK;AACzE,UAAM,YAAY,MAAM,QAAQ,SAAS,SAAS,OAAO,EAAE,QAAQ,MAAM,CAAC,EAAE,CAAC;AAG7E,UAAM,UAAU,KAAK,WAAW,cAAc;AAC9C,YAAQ,aAAa,2BAA2B,UAAU,IAAI;AAC9D,UAAM,KAAK,WAAW,KAAK,OAAO;AAClC,UAAM,WAAW,IAAI,SAAS,KAAK,UAAU,SAAS,GAAG;AAAA,MACvD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAChD,CAAC;AACD,UAAM,SAAS,QAAQ,uBAAuB;AAC9C,cAAU,UAAU,KAAK,YAAY,QAAQ,MAAM,GAAG,EAAE,GAAG,KAAK,eAAe,OAAO,CAAC;AAEvF,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,mBAAmB,OAAO,YAAwC;AAChE,UAAM,YAAY,MAAM,KAAK,aAAa,OAAO;AACjD,QAAI,CAAC,WAAW;AACd,YAAMC,QAAO;AAAA,QACX,OAAO;AAAA,UACL,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,SAAS,CAAC;AAAA,QACZ;AAAA,MACF;AACA,aAAO,SAAS,KAAKA,OAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC5C;AAEA,QAAI;AACJ,QAAI;AACF,aAAQ,MAAM,QAAQ,KAAK;AAAA,IAI7B,QAAQ;AACN,aAAO,SAAS,KAAK,gBAAgB,kBAAkB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3E;AAEA,UAAM,gBAAgB,OAAO,KAAK,mBAAmB,WAAW,KAAK,iBAAiB;AACtF,QACE,CAAC,iBACD,KAAK,0BAA0B,UAC/B,CAAC,YAAY,KAAK,aAAa,GAC/B;AACA,aAAO,SAAS,KAAK,gBAAgB,kBAAkB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3E;AAEA,UAAM,OAAO,WAAW;AACxB,UAAM,SAA4B,EAAE,MAAM,UAAU,MAAM,IAAI,cAAc;AAC5E,UAAM,KAAK,GAAG,QAAQ,KAAK,kBAAkB,IAAI,GAAG,KAAK,UAAU,MAAM,GAAG,IAAI,EAAE;AAElF,WAAO,SAAS,KAAK,EAAE,KAAK,CAAC;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,kBAAkB,OAAO,YAAwC;AAC/D,QAAI;AACJ,QAAI;AACF,aAAQ,MAAM,QAAQ,KAAK;AAAA,IAC7B,QAAQ;AACN,aAAO,SAAS,KAAK,gBAAgB,kBAAkB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3E;AAEA,UAAM,OAAO,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO;AACzD,UAAM,WAAW,OAAO,KAAK,kBAAkB,WAAW,KAAK,gBAAgB;AAC/E,QAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,YAAY,KAAK,QAAQ,GAAG;AACrD,aAAO,SAAS,KAAK,gBAAgB,kBAAkB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3E;AAEA,UAAM,MAAM,KAAK,kBAAkB,IAAI;AACvC,UAAM,MAAM,MAAM,KAAK,GAAG,QAAQ,GAAG;AACrC,QAAI,OAAO,QAAQ,UAAU;AAC3B,aAAO,SAAS,KAAK,gBAAgB,sBAAsB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC/E;AACA,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,MAAM,GAAG;AAAA,IACzB,QAAQ;AAGN,YAAM,KAAK,GAAG,WAAW,GAAG;AAC5B,aAAO,SAAS,KAAK,gBAAgB,sBAAsB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC/E;AAOA,UAAM,WAAW,WAAW,QAAQ,EAAE,OAAO,QAAQ,EAAE,OAAO;AAC9D,UAAM,SAAS,OAAO,KAAK,OAAO,IAAI,WAAW;AACjD,QAAI,OAAO,WAAW,SAAS,UAAU,CAAC,gBAAgB,UAAU,MAAM,GAAG;AAC3E,aAAO,SAAS,KAAK,gBAAgB,sBAAsB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC/E;AAKA,UAAM,KAAK,GAAG,WAAW,GAAG;AAE5B,UAAM,UAAU,KAAK,WAAW,cAAc;AAC9C,YAAQ,aAAa,2BAA2B,OAAO,IAAI;AAC3D,YAAQ,aAAa,eAAe,SAAS;AAC7C,UAAM,KAAK,WAAW,KAAK,OAAO;AAElC,UAAM,WAAW,SAAS,KAAK,EAAE,IAAI,KAAK,CAAC;AAC3C,UAAM,SAAS,QAAQ,uBAAuB;AAC9C,cAAU,UAAU,KAAK,YAAY,QAAQ,MAAM,GAAG;AAAA,MACpD,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA,MAIR,UAAU;AAAA,MACV,QAAQ;AAAA,MACR;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AAAA,EAEA,4BAA4B,OAC1B,UACA,YACsB;AACtB,WAAO,SAAS,KAAK,EAAE,OAAO,kBAAkB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACpE;AAAA,EAEA,aAAa,OAAO,UAAmB,aAAmD;AACxF,WAAO,SAAS,KAAK,EAAE,OAAO,kBAAkB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACpE;AAAA,EAEA,OAAO,OAAO,cAAwC;AACpD,UAAM,WAAW,MAAM,KAAK,WAAW,oBAAoB,UAAU,IAAI;AACzE,eAAW,aAAa,SAAS,KAAK,GAAG;AACvC,YAAM,KAAK,WAAW,WAAW,SAAS;AAAA,IAC5C;AAAA,EACF;AAAA,EAEA,kBAAkB,OAAO,YAAuC;AAC9D,UAAM,YAAY,KAAK,aAAa,OAAO;AAC3C,QAAI,CAAC,UAAW,QAAO;AACvB,UAAM,UAAU,MAAM,KAAK,WAAW,SAAS,SAAS;AACxD,QAAI,CAAC,QAAS,QAAO;AACrB,UAAM,gBAAgB,QAAQ,aAAa,yBAAyB;AACpE,QAAI,CAAC,cAAe,QAAO;AAC3B,WAAO;AAAA,EACT;AAAA,EAEA,aAAa,OACX,SACA,WACuD;AACvD,UAAM,YAAY,KAAK,aAAa,OAAO;AAC3C,QAAI,CAAC,WAAW;AACd,UAAI,QAAQ;AACV,cAAMC,WAAU,KAAK,WAAW,cAAc;AAC9C,cAAM,KAAK,WAAW,KAAKA,QAAO;AAClC,eAAOA;AAAA,MACT,OAAO;AACL,eAAO;AAAA,MACT;AAAA,IACF;AACA,UAAM,UAAU,MAAM,KAAK,WAAW,SAAS,SAAS;AACxD,QAAI,CAAC,SAAS;AACZ,UAAI,QAAQ;AACV,cAAMA,WAAU,KAAK,WAAW,cAAc;AAC9C,cAAM,KAAK,WAAW,KAAKA,QAAO;AAClC,eAAOA;AAAA,MACT,OAAO;AACL,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,gBAAgB,OAAO,cAAqC;AAC1D,UAAM,KAAK,WAAW,WAAW,SAAS;AAAA,EAC5C;AAAA,EAEA,eAAe,OAAO,YAAgD;AACpE,UAAM,YAAY,KAAK,aAAa,OAAO;AAC3C,QAAI,CAAC,UAAW,QAAO;AACvB,UAAM,UAAU,MAAM,KAAK,WAAW,SAAS,SAAS;AACxD,QAAI,CAAC,QAAS,QAAO;AACrB,UAAM,OAAO,QAAQ,aAAa,yBAAyB;AAC3D,QAAI,CAAC,KAAM,QAAO;AAClB,WAAO,EAAE,MAAM,OAAO,IAAI,EAAE;AAAA,EAC9B;AAAA,EAEA,eAAe,OAAO,cAA6C;AACjE,UAAM,WAAW,MAAM,KAAK,WAAW,oBAAoB,UAAU,IAAI;AACzE,WAAO,MAAM,KAAK,SAAS,OAAO,CAAC;AAAA,EACrC;AAAA,EAEA,yBAAyB,OAAO,iBAA6C;AAC3E,UAAM,KAAK,WAAW,uBAAuB,YAAY;AACzD,WAAO,SAAS,KAAK,CAAC,CAAC;AAAA,EACzB;AACF;","names":["timing","body","session"]}
1
+ {"version":3,"sources":["../../src/auth/index.ts"],"sourcesContent":["import { createHash, randomInt, randomUUID, timingSafeEqual } from 'node:crypto';\nimport { invariant } from '@shware/utils';\nimport { type Principal, Provider } from '../core/index';\nimport { loginEmailSchema, sendEmailVerificationCodeSchema } from '../email/schema';\nimport { OAuth2Client, googleOneTapSchema, oauth2RedirectQuerySchema } from '../oauth2/client';\nimport type { OAuth2ErrorType } from '../oauth2/error';\nimport { google } from '../oauth2/provider/index';\nimport type {\n NativeCredential,\n OAuth2AuthorizationRequest,\n PkceParameters,\n StandardClaims,\n} from '../oauth2/types';\nimport { PRINCIPAL_NAME_INDEX_NAME } from '../session/common';\nimport type { KVRepository, Session, SessionRepository } from '../session/types';\nimport {\n type CookieOptions,\n deleteCookie,\n getCookie,\n param,\n query,\n setCookie,\n} from '../utils/http';\nimport { timing } from '../utils/timing';\nimport { invalidArgument, valid, verifyTurnstileToken } from '../utils/valid';\nimport type { AuthConfig, AuthService, AuthorizedHandler, LoggedHandler } from './types';\n\n// PKCE per RFC 7636: code_verifier / code_challenge are 43-128 chars from\n// the unreserved set. We restrict to base64url (drops \".\" and \"~\") since\n// that's what S256 produces and what callers should be sending; rejecting\n// the rest shrinks the accepted input shape without losing legit clients.\n// A 32-byte random verifier base64url-encodes to exactly 43 chars.\nconst PKCE_FORMAT = /^[A-Za-z0-9_-]{43,128}$/;\n\ninterface DesktopCodeRecord {\n /** principal.name from the upstream cookie session */\n name: string;\n /** code_challenge (S256, base64url) — verifier must hash to this */\n cc: string;\n}\n\nexport const PATH = {\n CSRF: '/csrf',\n LOGOUT: '/logout',\n LOGGED: '/logged',\n\n OAUTH2_STATE: '/oauth2/state/:registrationId',\n OAUTH2_NONCE: '/oauth2/nonce/:registrationId',\n OAUTH2_AUTHORIZATION: '/oauth2/authorization/:registrationId',\n LOGIN_OAUTH2_CODE: '/login/oauth2/code/:registrationId',\n LOGIN_OAUTH2_NATIVE: '/login/oauth2/native/:registrationId',\n LOGIN_OAUTH2_ONETAP: '/login/oauth2/onetap/google',\n\n LOGIN_EMAIL: '/login/email',\n LOGIN_PHONE: '/login/phone',\n SEND_EMAIL_VERIFICATION_CODE: '/auth/sendEmailVerificationCode',\n SEND_PHONE_VERIFICATION_CODE: '/auth/sendPhoneVerificationCode',\n\n // Desktop / native app sign-in via web loopback (RFC 8252 + OAuth 2.1 Auth Code Flow):\n // 1. user already authenticated with cookie session → POST DESKTOP_AUTHORIZE → { code }\n // 2. web redirects to desktop's loopback callback with the code\n // 3. desktop renderer POSTs DESKTOP_EXCHANGE with the code → server returns\n // Set-Cookie carrying a fresh session, single-use code is invalidated\n // The two-step flow keeps the actual session id off the redirect URL — only a\n // 5-minute single-use code transits via the loopback.\n DESKTOP_AUTHORIZE: '/auth/desktop/authorize',\n DESKTOP_EXCHANGE: '/auth/desktop/exchange',\n\n CLEANUP_EXPIRED_SESSIONS: '/sessions/expired/cleanup',\n} as const;\n\nexport class Auth implements AuthService {\n private readonly timing: boolean;\n private readonly cookieName;\n private readonly cookieOptions: CookieOptions;\n private readonly kv: KVRepository;\n private readonly repository: SessionRepository;\n private readonly turnstileSecretKey?: string;\n private readonly oauth2Client: OAuth2Client | null;\n private readonly ATTR_OAUTH2_AUTHORIZATION_REQUEST = 'oauth2AuthorizationRequest';\n\n constructor({\n sessionRepository,\n kvRepository,\n turnstileSecretKey,\n oauth2,\n cookie,\n timing,\n }: AuthConfig) {\n this.timing = timing ?? false;\n this.kv = kvRepository;\n this.repository = sessionRepository;\n this.turnstileSecretKey = turnstileSecretKey;\n const { name, ...cookieOptions } = cookie ?? {};\n this.cookieName = name ?? 'SESSION';\n this.cookieOptions = {\n path: '/',\n sameSite: 'none',\n secure: true,\n httpOnly: true,\n ...cookieOptions,\n };\n this.oauth2Client = oauth2?.client ? new OAuth2Client(oauth2.client) : null;\n }\n\n private getSessionId(request: Request): string | undefined {\n let sessionId: string | undefined;\n const header = request.headers.get('Authorization');\n if (header?.startsWith('Bearer ')) {\n sessionId = header.replace('Bearer ', '');\n }\n if (!sessionId) {\n sessionId = getCookie(request, this.cookieName);\n }\n return sessionId;\n }\n\n csrf = async (_request: Request): Promise<Response> => {\n const token = randomUUID();\n const response = Response.json({\n token,\n parameterName: '_csrf',\n headerName: 'X-XSRF-TOKEN',\n });\n\n // Double Submit Cookie Protection\n setCookie(response, 'XSRF-TOKEN', token, {\n path: '/',\n secure: true,\n httpOnly: false,\n sameSite: 'none',\n domain: this.cookieOptions.domain,\n });\n return response;\n };\n\n logout = async (request: Request): Promise<Response> => {\n const sessionId = this.getSessionId(request);\n if (sessionId) await this.repository.deleteById(sessionId);\n const response = Response.json({});\n deleteCookie(response, this.cookieName);\n return response;\n };\n\n logged = async (request: Request, onLogged?: LoggedHandler): Promise<Response> => {\n // 1. get session from header or cookie\n const sessionId = this.getSessionId(request);\n if (!sessionId) return Response.json({ data: false });\n const session = await this.repository.findById(sessionId);\n if (!session) return Response.json({ data: false });\n\n // 2. get principal name from session\n const name = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n session.setLastAccessedTime(Date.now());\n await this.repository.save(session);\n\n // 3. call onLogged\n if (name !== null) await onLogged?.({ name: String(name) });\n\n // 4. renew cookie\n const response = Response.json({ data: name !== null });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n return response;\n };\n\n private createPkceParameters(): PkceParameters {\n const code_verifier = randomUUID();\n const code_challenge = createHash('sha256')\n .update(code_verifier)\n .digest()\n .toString('base64url');\n\n return { code_verifier, code_challenge, code_challenge_method: 'S256' };\n }\n\n private getOauth2StateKey(state: string) {\n return `oauth2:state:${state}`;\n }\n\n private getOauth2NonceKey(nonce: string) {\n return `oauth2:nonce:${nonce}`;\n }\n\n private getEmailVerificationCodeKey(email: string) {\n return `email:verification:${email}`;\n }\n\n private getPhoneVerificationCodeKey(phone: string) {\n return `phone:verification:${phone}`;\n }\n\n private getDesktopCodeKey(code: string) {\n return `desktop:code:${code}`;\n }\n\n oauth2State = async (request: Request): Promise<Response> => {\n const { registrationId } = param(request, PATH.OAUTH2_STATE);\n const state = randomUUID();\n await this.kv.setItem(this.getOauth2StateKey(state), registrationId, 10 * 60);\n return Response.json({ state });\n };\n\n oauth2Nonce = async (request: Request): Promise<Response> => {\n const { registrationId } = param(request, PATH.OAUTH2_NONCE);\n const nonce = randomUUID();\n await this.kv.setItem(this.getOauth2NonceKey(nonce), registrationId, 10 * 60);\n return Response.json({ nonce });\n };\n\n oauth2Authorization = async (request: Request): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n const { registrationId } = param(request, PATH.OAUTH2_AUTHORIZATION);\n const state = randomUUID();\n const pkce = this.createPkceParameters();\n const uri = await this.oauth2Client.createAuthorizationUri({ registrationId, state, pkce });\n const sessionId = this.getSessionId(request);\n const session = sessionId\n ? ((await this.repository.findById(sessionId)) ?? this.repository.createSession())\n : this.repository.createSession();\n mark('fetch_session');\n\n const authorizationRequest: OAuth2AuthorizationRequest = {\n state,\n registrationId,\n authorizationRequestUri: uri.href,\n additionalParameters: pkce,\n };\n\n const value = JSON.stringify(authorizationRequest);\n session.setAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST, value);\n await this.repository.save(session);\n mark('save_session');\n\n const response = new Response(null, { status: 302, headers: { location: uri.href } });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n private redirect = (error: OAuth2ErrorType, description?: string): Response => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const uri = new URL(this.oauth2Client.errorUri);\n uri.searchParams.set('error', error);\n if (description) uri.searchParams.set('error_description', description);\n return Response.redirect(uri.href, 302);\n };\n\n loginOAuth2Code = async (request: Request, handler: AuthorizedHandler): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n\n // 1. get session from header or cookie\n const sessionId = this.getSessionId(request);\n const session = sessionId ? await this.repository.findById(sessionId) : null;\n if (!session) {\n return this.redirect('invalid_request', 'session not found');\n }\n mark('load_session');\n\n // 2. get cached authorization request from session\n const json = session.getAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST) as string | null;\n if (!json) {\n return this.redirect('invalid_request', 'authorization request not found');\n }\n const cached: OAuth2AuthorizationRequest = JSON.parse(json);\n\n // 3. validate redirect query/formdata\n const { registrationId } = param(request, PATH.LOGIN_OAUTH2_CODE);\n let data: Record<string, string>;\n // apple redirect: response_mode=form_post\n if (\n request.method === 'POST' &&\n request.headers.get('content-type')?.toLowerCase() === 'application/x-www-form-urlencoded'\n ) {\n const params = new URLSearchParams(await request.text());\n data = Object.fromEntries(params.entries());\n } else {\n data = query(request);\n }\n\n const parsed = oauth2RedirectQuerySchema.safeParse(data);\n if (!parsed.success) {\n return this.redirect('invalid_request', 'invalid redirect query');\n }\n if (!parsed.data.code || !parsed.data.state) {\n return this.redirect('invalid_request', 'invalid redirect query');\n }\n if (parsed.data.state !== cached.state) {\n return this.redirect('invalid_request', 'redirect state mismatch');\n }\n if (registrationId !== cached.registrationId) {\n return this.redirect('invalid_request', 'redirect registration mismatch');\n }\n mark('validate_session');\n\n // 4. exchange authorization code for token and get user info\n const { code } = parsed.data;\n\n const token = await this.oauth2Client.exchangeAuthorizationCode({\n registrationId,\n code,\n pkce: cached.additionalParameters,\n });\n const userInfo = await this.oauth2Client.getUserInfo({ registrationId, token });\n mark('exchange_code');\n\n // 5. create or update principal\n const principal = await handler(request, registrationId, userInfo, token);\n mark('save_principal');\n\n // 6. update session\n session.setLastAccessedTime(Date.now());\n session.removeAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST);\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n mark('update_session');\n\n const uri = new URL(this.oauth2Client.successUri);\n uri.searchParams.set('status', 'success');\n uri.searchParams.set('registrationId', registrationId);\n\n const response = new Response(null, { status: 302, headers: { location: uri.href } });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n loginOAuth2Native = async (request: Request, handler: AuthorizedHandler): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n const { registrationId } = param(request, PATH.LOGIN_OAUTH2_NATIVE);\n const credentials = (await request.json()) as NativeCredential;\n const { userInfo, token } = await this.oauth2Client.loginOAuth2Native({\n registrationId,\n credentials,\n });\n mark('login_oauth2_native');\n\n const { nonce } = userInfo.data as { nonce?: string };\n if (nonce) {\n const value = await this.kv.getItem(this.getOauth2NonceKey(nonce));\n if (value !== registrationId) {\n const json = { error: 'invalid_request', error_description: 'nonce mismatch' };\n return Response.json(json, { status: 400 });\n }\n await this.kv.removeItem(this.getOauth2NonceKey(nonce));\n mark('check_nonce');\n }\n\n const principal = await handler(request, registrationId, userInfo, token);\n mark('save_principal');\n\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n loginOAuth2Onetap = async (\n request: Request,\n handler: AuthorizedHandler,\n registrationId = 'google'\n ): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n\n // should support application/x-www-form-urlencoded and application/json\n let body: Record<string, string>;\n if (request.headers.get('Content-Type')?.includes('application/x-www-form-urlencoded')) {\n const text = await request.text();\n body = Object.fromEntries(new URLSearchParams(text));\n } else if (request.headers.get('Content-Type')?.includes('application/json')) {\n body = (await request.json()) as Record<string, string>;\n } else {\n const json = { error: 'invalid_request', error_description: 'invalid content type' };\n return Response.json(json, { status: 400 });\n }\n\n const parsed = googleOneTapSchema.parse(body);\n\n // csrf token is not supported in FedCM mode\n // const csrf_cookie = getCookie(request, 'g_csrf_token');\n // const csrf_request = parsed.g_csrf_token;\n // if (!csrf_request || csrf_cookie !== csrf_request) {\n // const json = { error: 'invalid_request', error_description: 'invalid csrf token' };\n // return Response.json(json, { status: 400 });\n // }\n\n let principal: Principal;\n if (parsed.credential) {\n const { token, userInfo } = await google.getTokenInfo(parsed.credential);\n principal = await handler(request, registrationId, userInfo, token);\n } else if (parsed.code) {\n const token = await this.oauth2Client.exchangeAuthorizationCode({\n registrationId,\n code: parsed.code,\n });\n const userInfo = await this.oauth2Client.getUserInfo({ registrationId, token });\n principal = await handler(request, registrationId, userInfo, token);\n } else {\n throw new Error('invalid request');\n }\n\n mark('save_principal');\n\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n sendEmailVerificationCode = async (\n request: Request,\n sender: (data: { email: string; verificationCode: string }) => Promise<void>\n ): Promise<Response> => {\n invariant(this.turnstileSecretKey, 'turnstileSecretKey is not configured');\n\n const { data, error } = valid(sendEmailVerificationCodeSchema, await request.json());\n if (error) return Response.json(error, { status: error.error.code });\n const { email, turnstileToken } = data;\n\n const result = await verifyTurnstileToken(this.turnstileSecretKey, turnstileToken);\n if (result.error) return Response.json(result.error, { status: result.error.error.code });\n\n try {\n const verificationCode = randomInt(1_000_000).toString().padStart(6, '0');\n await sender({ email, verificationCode });\n await this.kv.setItem(this.getEmailVerificationCodeKey(email), verificationCode, 10 * 60);\n return Response.json({});\n } catch (e) {\n console.error('Failed to send email verification code', e);\n return Response.json(invalidArgument('EMAIL_DELIVERY_FAILED'), { status: 400 });\n }\n };\n\n loginEmail = async (request: Request, handler: AuthorizedHandler): Promise<Response> => {\n const { data, error } = valid(loginEmailSchema, await request.json());\n if (error) return Response.json(error, { status: error.error.code });\n const { email, verificationCode } = data;\n\n const key = this.getEmailVerificationCodeKey(email);\n const stored = await this.kv.getItem(key);\n if (\n typeof stored !== 'string' ||\n stored.length !== verificationCode.length ||\n !timingSafeEqual(Buffer.from(stored), Buffer.from(verificationCode))\n ) {\n return Response.json(invalidArgument('INVALID_VERIFICATION_CODE'), { status: 400 });\n }\n await this.kv.removeItem(key);\n const claims: StandardClaims = { sub: email, email, email_verified: true };\n const principal = await handler(request, Provider.EMAIL, { claims, data: {} });\n\n // save session\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n\n return response;\n };\n\n /**\n * Step 1 of the desktop / native app sign-in handshake. The caller already\n * completed normal sign-in on the web (so a cookie session exists); this\n * issues a 5-minute single-use code that the web page hands off to the\n * desktop client via a loopback redirect.\n *\n * PKCE (RFC 7636, S256 only): the desktop generates a random `code_verifier`\n * before opening the browser, derives `code_challenge = base64url(sha256(verifier))`,\n * and threads the challenge through the URL → web page → here. We pin\n * the challenge to the code in KV; `desktopExchange` enforces that the\n * caller knows the matching verifier. This blocks an attacker who only\n * intercepts the auth code (browser history, malicious extension, packet\n * capture on loopback) from exchanging it.\n *\n * Requires an authenticated cookie session — anonymous callers get 401.\n * Throttling / abuse protection on this endpoint is the API layer's job\n * (rate-limit per principal); the SDK doesn't gate it because the\n * upstream sign-in flow already passed CAPTCHA / OAuth / verification.\n */\n desktopAuthorize = async (request: Request): Promise<Response> => {\n const principal = await this.getPrincipal(request);\n if (!principal) {\n const body = {\n error: {\n code: 401,\n status: 'UNAUTHENTICATED',\n message: 'sign in first',\n details: [],\n },\n };\n return Response.json(body, { status: 401 });\n }\n\n let body: { code_challenge?: unknown; code_challenge_method?: unknown };\n try {\n body = (await request.json()) as {\n code_challenge?: unknown;\n code_challenge_method?: unknown;\n };\n } catch {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const codeChallenge = typeof body.code_challenge === 'string' ? body.code_challenge : null;\n if (\n !codeChallenge ||\n body.code_challenge_method !== 'S256' ||\n !PKCE_FORMAT.test(codeChallenge)\n ) {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const code = randomUUID();\n const record: DesktopCodeRecord = { name: principal.name, cc: codeChallenge };\n await this.kv.setItem(this.getDesktopCodeKey(code), JSON.stringify(record), 5 * 60);\n\n return Response.json({ code });\n };\n\n /**\n * Step 2 of the desktop / native app sign-in handshake. The desktop\n * client hands back the auth code it received via the loopback callback,\n * and we mint a fresh session for it — independent of the web cookie\n * session that originally `authorize`'d the code, so logging out on\n * either side doesn't cascade to the other.\n *\n * Cookie attributes: defaults to `SameSite=None; Secure` so the cookie\n * survives cross-site fetches from the desktop renderer's `file://` /\n * `app://` origin. The configured base `cookieOptions` provide\n * everything else (`domain`, `path`, `httpOnly`, `maxAge`); the override\n * is targeted at the SameSite/Secure pair only. In a non-HTTPS dev\n * backend this combination is rejected by Chromium — see the desktop\n * sign-in PRD for workarounds.\n *\n * No CSRF check needed — the request carries no cookie, the auth code\n * itself is the credential, and KV's single-use removal blocks replay.\n */\n desktopExchange = async (request: Request): Promise<Response> => {\n let body: { code?: unknown; code_verifier?: unknown };\n try {\n body = (await request.json()) as { code?: unknown; code_verifier?: unknown };\n } catch {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const code = typeof body.code === 'string' ? body.code : null;\n const verifier = typeof body.code_verifier === 'string' ? body.code_verifier : null;\n if (!code || !verifier || !PKCE_FORMAT.test(verifier)) {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const key = this.getDesktopCodeKey(code);\n const raw = await this.kv.getItem(key);\n if (typeof raw !== 'string') {\n return Response.json(invalidArgument('INVALID_DESKTOP_CODE'), { status: 400 });\n }\n let record: DesktopCodeRecord;\n try {\n record = JSON.parse(raw) as DesktopCodeRecord;\n } catch {\n // Malformed KV record (shouldn't happen with our own writes) — drop it\n // and surface as an invalid code; the caller can retry the whole flow.\n await this.kv.removeItem(key);\n return Response.json(invalidArgument('INVALID_DESKTOP_CODE'), { status: 400 });\n }\n\n // Verify PKCE: base64url(sha256(verifier)) === stored challenge.\n // Don't burn the code on a verifier mismatch — that would let an\n // attacker who races a network retry brick a legit exchange. The code\n // is single-use on success and 5-min TTL bounded; brute-forcing the\n // 256-bit verifier in that window is infeasible.\n const computed = createHash('sha256').update(verifier).digest();\n const stored = Buffer.from(record.cc, 'base64url');\n if (stored.length !== computed.length || !timingSafeEqual(computed, stored)) {\n return Response.json(invalidArgument('INVALID_DESKTOP_CODE'), { status: 400 });\n }\n\n // One-time use: remove BEFORE creating the session so a concurrent\n // duplicate exchange (e.g. retry after timeout) sees the code as\n // consumed even if our session save races it.\n await this.kv.removeItem(key);\n\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, record.name);\n session.setAttribute('client_type', 'desktop');\n await this.repository.save(session);\n\n const response = Response.json({ ok: true });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), {\n ...this.cookieOptions,\n // Override sameSite/secure for desktop: the renderer's origin\n // (file:// / app://) is cross-site to the API, so SameSite=Lax/Strict\n // would block the cookie on subsequent fetches.\n sameSite: 'none',\n secure: true,\n maxAge,\n });\n return response;\n };\n\n sendPhoneVerificationCode = async (\n _request: Request,\n _sender: (data: { phone: string; verificationCode: string }) => Promise<void>\n ): Promise<Response> => {\n return Response.json({ error: 'not_implemented' }, { status: 501 });\n };\n\n loginPhone = async (_request: Request, _handler: AuthorizedHandler): Promise<Response> => {\n return Response.json({ error: 'not_implemented' }, { status: 501 });\n };\n\n kick = async (principal: Principal): Promise<void> => {\n const sessions = await this.repository.findByPrincipalName(principal.name);\n for (const sessionId of sessions.keys()) {\n await this.repository.deleteById(sessionId);\n }\n };\n\n isAuthenticated = async (request: Request): Promise<boolean> => {\n const sessionId = this.getSessionId(request);\n if (!sessionId) return false;\n const session = await this.repository.findById(sessionId);\n if (!session) return false;\n const principalName = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n if (!principalName) return false;\n return true;\n };\n\n getSession = async <T extends boolean>(\n request: Request,\n create: T\n ): Promise<T extends true ? Session : Session | null> => {\n const sessionId = this.getSessionId(request);\n if (!sessionId) {\n if (create) {\n const session = this.repository.createSession();\n await this.repository.save(session);\n return session;\n } else {\n return null as T extends true ? Session : Session | null;\n }\n }\n const session = await this.repository.findById(sessionId);\n if (!session) {\n if (create) {\n const session = this.repository.createSession();\n await this.repository.save(session);\n return session;\n } else {\n return null as T extends true ? Session : Session | null;\n }\n }\n return session;\n };\n\n deleteSession = async (sessionId: string): Promise<void> => {\n await this.repository.deleteById(sessionId);\n };\n\n getPrincipal = async (request: Request): Promise<Principal | null> => {\n const sessionId = this.getSessionId(request);\n if (!sessionId) return null;\n const session = await this.repository.findById(sessionId);\n if (!session) return null;\n const name = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n if (!name) return null;\n return { name: String(name) };\n };\n\n listSessions = async (principal: Principal): Promise<Session[]> => {\n const sessions = await this.repository.findByPrincipalName(principal.name);\n return Array.from(sessions.values());\n };\n\n cleanupExpiredSessions = async (cleanupCount?: number): Promise<Response> => {\n await this.repository.cleanupExpiredSessions(cleanupCount);\n return Response.json({});\n };\n}\n"],"mappings":"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","names":["timing","body","session"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/oauth2/types.ts"],"sourcesContent":["// reference: https://datatracker.ietf.org/doc/html/rfc7636\nexport interface PkceParameters {\n code_verifier: string;\n code_challenge: string;\n code_challenge_method: 'S256' | 'plain';\n}\n\nexport interface OAuth2AuthorizationRequest {\n state: string;\n registrationId: string;\n authorizationRequestUri: string;\n additionalParameters: PkceParameters;\n}\n\ninterface CreateAuthorizationUriParams {\n state: string;\n clientId: string;\n redirectUri: string;\n scope: string[] | undefined;\n pkce?: Omit<PkceParameters, 'code_verifier'>;\n}\n\nexport interface ExchangeCodeParams {\n code: string;\n clientId: string;\n clientSecret: string;\n redirectUri: string;\n pkce?: Omit<PkceParameters, 'code_challenge' | 'code_challenge_method'>;\n}\n\nexport interface RefreshTokenParams {\n clientId: string;\n clientSecret: string;\n refreshToken: string;\n}\n\nexport interface RevokeTokenParams {\n token: string;\n clientId: string;\n clientSecret: string;\n}\n\nexport interface LoginOAuth2NativeParams {\n clientId: string;\n clientSecret: string;\n redirectUri: string;\n pkce?: PkceParameters;\n credentials: NativeCredential;\n}\n\n// reference: https://datatracker.ietf.org/doc/html/rfc6749#section-5.1\nexport interface OAuth2Token {\n /**\n * REQUIRED.\n * The access token issued by the authorization server.\n */\n access_token: string;\n\n /**\n * REQUIRED.\n * The type of the token issued as described in Section 7.1. Value is case insensitive.\n */\n token_type: 'Bearer' | 'bearer' | 'mac' | (string & {});\n\n /**\n * RECOMMENDED.\n * The lifetime in seconds of the access token. For example, the value \"3600\" denotes that the\n * access token will expire in one hour from the time the response was generated. If omitted, the\n * authorization server SHOULD provide the expiration time via other means or document the default\n * value.\n */\n expires_in?: number;\n\n /**\n * OPTIONAL.\n * The refresh token, which can be used to obtain new access tokens using the same authorization\n * grant as described in Section 6.\n */\n refresh_token?: string;\n\n /**\n * OPTIONAL.\n * If identical to the scope requested by the client; otherwise, REQUIRED. The scope of the\n * access token as described by Section 3.3.\n */\n scope?: string;\n\n id_token?: string;\n\n [key: string]: unknown;\n}\n\nexport interface OidcToken extends OAuth2Token {\n id_token: string;\n}\n\nexport type NativeCredential = {\n state?: string;\n code?: string;\n client_id?: string;\n redirect_uri?: string;\n id_token?: string;\n access_token?: string;\n};\n\nexport enum OidcScopes {\n openid = 'openid',\n profile = 'profile',\n email = 'email',\n phone = 'phone',\n address = 'address',\n offline_access = 'offline_access',\n}\n\n// https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims\nexport interface StandardClaims {\n sub: string;\n name?: string;\n given_name?: string;\n family_name?: string;\n middle_name?: string;\n nickname?: string;\n preferred_username?: string;\n profile?: string;\n picture?: string;\n website?: string;\n email?: string;\n email_verified?: boolean;\n gender?: 'female' | 'male' | string;\n birthdate?: string; // ISO 8601-1 [ISO8601‑1] YYYY-MM-DD format.\n zoneinfo?: string; // IANA Time Zone Database [IANA.time‑zones]\n locale?: string; // BCP47 [RFC5646] language tag.\n phone_number?: string; // E.164\n phone_number_verified?: boolean;\n address?: {\n formatted: string;\n street_address: string;\n locality: string;\n region: string;\n postal_code: string;\n country: string;\n }; // [RFC8259] JSON object\n updated_at?: number;\n}\n\nexport interface UserInfo<T = unknown> {\n claims: StandardClaims;\n data: T;\n}\n\nexport interface Provider {\n /** Authorization URI for the provider. */\n authorizationUri: string;\n\n /** Token URI for the provider. */\n tokenUri: string;\n\n /** Token revoke URI for the provider. */\n tokenRevokeUri?: string;\n\n /** Token refresh URI for the provider. */\n tokenRefreshUri?: string;\n\n /** User info URI for the provider. */\n userInfoUri?: string;\n\n /** User info authentication method for the provider. */\n userInfoAuthenticationMethod?: string;\n\n /**\n * Name of the attribute that will be used to extract the username from the call to 'userInfoUri'.\n * */\n userNameAttribute?: string;\n\n /** JWK set URI for the provider */\n jwkSetUri?: string;\n\n /**\n * URI that can either be an OpenID Connect discovery endpoint or an OAuth 2.0 Authorization\n * Server Metadata endpoint defined by RFC 8414.\n * */\n issuerUri?: string;\n\n /** Default scopes for the provider. */\n defaultScope: string[];\n\n /** step 1: generate authorization uri and redirect user to it */\n createAuthorizationUri: (params: CreateAuthorizationUriParams) => URL;\n\n /** step 2: exchange code for token */\n exchangeAuthorizationCode: (params: ExchangeCodeParams) => Promise<OAuth2Token>;\n\n /** step 3: get user info */\n getUserInfo: (token: OAuth2Token) => Promise<UserInfo>;\n\n /** others: refresh access token */\n refreshAccessToken?: (params: RefreshTokenParams) => Promise<OAuth2Token>;\n\n /** others: revoke token */\n revokeToken?: (params: RevokeTokenParams) => Promise<void>;\n\n loginOAuth2Native?: (\n params: LoginOAuth2NativeParams\n ) => Promise<{ token: OAuth2Token; userInfo: UserInfo }>;\n}\n\nexport interface OneTapProvider extends Provider {\n getTokenInfo: (id_token: string) => Promise<{ token: OAuth2Token; userInfo: UserInfo }>;\n}\n\nexport interface Registration {\n /**\n * Reference to the OAuth 2.0 provider to use. May reference an element from the 'provider'\n * property or used one of the commonly used providers (google, github, facebook, okta).\n * */\n provider?: string;\n\n /** Client ID for the registration. */\n clientId: string;\n\n /** Client secret of the registration. */\n clientSecret: string;\n\n /** Client authentication method. May be left blank when using a pre-defined provider. */\n clientAuthenticationMethod?: string;\n\n /** Authorization grant type. May be left blank when using a pre-defined provider. */\n authorizationGrantType?:\n | 'authorization_code' // default\n | 'client_credentials'\n | 'device_code'\n | 'jwt_bearer'\n // Deprecated. The latest OAuth 2.0 Security Best Current Practice disallows the use of the\n // Resource Owner Password Credentials grant.\n | 'password'\n | 'refresh_token'\n | 'token_exchange';\n\n /** Redirect URI. May be left blank when using a pre-defined provider. */\n redirectUri?: string;\n\n /** Authorization scopes. When left blank the provider's default scopes, if any, will be used. */\n scope?: string[];\n\n /** Client name. May be left blank when using a pre-defined provider. */\n clientName?: string;\n}\n\nexport interface OAuth2ClientConfig {\n baseUri: string;\n errorUri: string;\n successUri: string;\n provider: { [name: string]: Provider | undefined };\n registration:\n | { [name: string]: Registration | undefined }\n | (() => Promise<{ [name: string]: Registration | undefined }>);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAyGO,IAAK,aAAL,kBAAKA,gBAAL;AACL,EAAAA,YAAA,YAAS;AACT,EAAAA,YAAA,aAAU;AACV,EAAAA,YAAA,WAAQ;AACR,EAAAA,YAAA,WAAQ;AACR,EAAAA,YAAA,aAAU;AACV,EAAAA,YAAA,oBAAiB;AANP,SAAAA;AAAA,GAAA;","names":["OidcScopes"]}
1
+ {"version":3,"sources":["../../src/oauth2/types.ts"],"sourcesContent":["// reference: https://datatracker.ietf.org/doc/html/rfc7636\nexport interface PkceParameters {\n code_verifier: string;\n code_challenge: string;\n code_challenge_method: 'S256' | 'plain';\n}\n\nexport interface OAuth2AuthorizationRequest {\n state: string;\n registrationId: string;\n authorizationRequestUri: string;\n additionalParameters: PkceParameters;\n}\n\ninterface CreateAuthorizationUriParams {\n state: string;\n clientId: string;\n redirectUri: string;\n scope: string[] | undefined;\n pkce?: Omit<PkceParameters, 'code_verifier'>;\n}\n\nexport interface ExchangeCodeParams {\n code: string;\n clientId: string;\n clientSecret: string;\n redirectUri: string;\n pkce?: Omit<PkceParameters, 'code_challenge' | 'code_challenge_method'>;\n}\n\nexport interface RefreshTokenParams {\n clientId: string;\n clientSecret: string;\n refreshToken: string;\n}\n\nexport interface RevokeTokenParams {\n token: string;\n clientId: string;\n clientSecret: string;\n}\n\nexport interface LoginOAuth2NativeParams {\n clientId: string;\n clientSecret: string;\n redirectUri: string;\n pkce?: PkceParameters;\n credentials: NativeCredential;\n}\n\n// reference: https://datatracker.ietf.org/doc/html/rfc6749#section-5.1\nexport interface OAuth2Token {\n /**\n * REQUIRED.\n * The access token issued by the authorization server.\n */\n access_token: string;\n\n /**\n * REQUIRED.\n * The type of the token issued as described in Section 7.1. Value is case insensitive.\n */\n token_type: 'Bearer' | 'bearer' | 'mac' | (string & {});\n\n /**\n * RECOMMENDED.\n * The lifetime in seconds of the access token. For example, the value \"3600\" denotes that the\n * access token will expire in one hour from the time the response was generated. If omitted, the\n * authorization server SHOULD provide the expiration time via other means or document the default\n * value.\n */\n expires_in?: number;\n\n /**\n * OPTIONAL.\n * The refresh token, which can be used to obtain new access tokens using the same authorization\n * grant as described in Section 6.\n */\n refresh_token?: string;\n\n /**\n * OPTIONAL.\n * If identical to the scope requested by the client; otherwise, REQUIRED. The scope of the\n * access token as described by Section 3.3.\n */\n scope?: string;\n\n id_token?: string;\n\n [key: string]: unknown;\n}\n\nexport interface OidcToken extends OAuth2Token {\n id_token: string;\n}\n\nexport type NativeCredential = {\n state?: string;\n code?: string;\n client_id?: string;\n redirect_uri?: string;\n id_token?: string;\n access_token?: string;\n};\n\nexport enum OidcScopes {\n openid = 'openid',\n profile = 'profile',\n email = 'email',\n phone = 'phone',\n address = 'address',\n offline_access = 'offline_access',\n}\n\n// https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims\nexport interface StandardClaims {\n sub: string;\n name?: string;\n given_name?: string;\n family_name?: string;\n middle_name?: string;\n nickname?: string;\n preferred_username?: string;\n profile?: string;\n picture?: string;\n website?: string;\n email?: string;\n email_verified?: boolean;\n gender?: 'female' | 'male' | (string & {});\n birthdate?: string; // ISO 8601-1 [ISO8601‑1] YYYY-MM-DD format.\n zoneinfo?: string; // IANA Time Zone Database [IANA.time‑zones]\n locale?: string; // BCP47 [RFC5646] language tag.\n phone_number?: string; // E.164\n phone_number_verified?: boolean;\n address?: {\n formatted: string;\n street_address: string;\n locality: string;\n region: string;\n postal_code: string;\n country: string;\n }; // [RFC8259] JSON object\n updated_at?: number;\n}\n\nexport interface UserInfo<T = unknown> {\n claims: StandardClaims;\n data: T;\n}\n\nexport interface Provider {\n /** Authorization URI for the provider. */\n authorizationUri: string;\n\n /** Token URI for the provider. */\n tokenUri: string;\n\n /** Token revoke URI for the provider. */\n tokenRevokeUri?: string;\n\n /** Token refresh URI for the provider. */\n tokenRefreshUri?: string;\n\n /** User info URI for the provider. */\n userInfoUri?: string;\n\n /** User info authentication method for the provider. */\n userInfoAuthenticationMethod?: string;\n\n /**\n * Name of the attribute that will be used to extract the username from the call to 'userInfoUri'.\n * */\n userNameAttribute?: string;\n\n /** JWK set URI for the provider */\n jwkSetUri?: string;\n\n /**\n * URI that can either be an OpenID Connect discovery endpoint or an OAuth 2.0 Authorization\n * Server Metadata endpoint defined by RFC 8414.\n * */\n issuerUri?: string;\n\n /** Default scopes for the provider. */\n defaultScope: string[];\n\n /** step 1: generate authorization uri and redirect user to it */\n createAuthorizationUri: (params: CreateAuthorizationUriParams) => URL;\n\n /** step 2: exchange code for token */\n exchangeAuthorizationCode: (params: ExchangeCodeParams) => Promise<OAuth2Token>;\n\n /** step 3: get user info */\n getUserInfo: (token: OAuth2Token) => Promise<UserInfo>;\n\n /** others: refresh access token */\n refreshAccessToken?: (params: RefreshTokenParams) => Promise<OAuth2Token>;\n\n /** others: revoke token */\n revokeToken?: (params: RevokeTokenParams) => Promise<void>;\n\n loginOAuth2Native?: (\n params: LoginOAuth2NativeParams\n ) => Promise<{ token: OAuth2Token; userInfo: UserInfo }>;\n}\n\nexport interface OneTapProvider extends Provider {\n getTokenInfo: (id_token: string) => Promise<{ token: OAuth2Token; userInfo: UserInfo }>;\n}\n\nexport interface Registration {\n /**\n * Reference to the OAuth 2.0 provider to use. May reference an element from the 'provider'\n * property or used one of the commonly used providers (google, github, facebook, okta).\n * */\n provider?: string;\n\n /** Client ID for the registration. */\n clientId: string;\n\n /** Client secret of the registration. */\n clientSecret: string;\n\n /** Client authentication method. May be left blank when using a pre-defined provider. */\n clientAuthenticationMethod?: string;\n\n /** Authorization grant type. May be left blank when using a pre-defined provider. */\n authorizationGrantType?:\n | 'authorization_code' // default\n | 'client_credentials'\n | 'device_code'\n | 'jwt_bearer'\n // Deprecated. The latest OAuth 2.0 Security Best Current Practice disallows the use of the\n // Resource Owner Password Credentials grant.\n | 'password'\n | 'refresh_token'\n | 'token_exchange';\n\n /** Redirect URI. May be left blank when using a pre-defined provider. */\n redirectUri?: string;\n\n /** Authorization scopes. When left blank the provider's default scopes, if any, will be used. */\n scope?: string[];\n\n /** Client name. May be left blank when using a pre-defined provider. */\n clientName?: string;\n}\n\nexport interface OAuth2ClientConfig {\n baseUri: string;\n errorUri: string;\n successUri: string;\n provider: { [name: string]: Provider | undefined };\n registration:\n | { [name: string]: Registration | undefined }\n | (() => Promise<{ [name: string]: Registration | undefined }>);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAyGO,IAAK,aAAL,kBAAKA,gBAAL;AACL,EAAAA,YAAA,YAAS;AACT,EAAAA,YAAA,aAAU;AACV,EAAAA,YAAA,WAAQ;AACR,EAAAA,YAAA,WAAQ;AACR,EAAAA,YAAA,aAAU;AACV,EAAAA,YAAA,oBAAiB;AANP,SAAAA;AAAA,GAAA;","names":["OidcScopes"]}
@@ -106,7 +106,7 @@ interface StandardClaims {
106
106
  website?: string;
107
107
  email?: string;
108
108
  email_verified?: boolean;
109
- gender?: 'female' | 'male' | string;
109
+ gender?: 'female' | 'male' | (string & {});
110
110
  birthdate?: string;
111
111
  zoneinfo?: string;
112
112
  locale?: string;
@@ -106,7 +106,7 @@ interface StandardClaims {
106
106
  website?: string;
107
107
  email?: string;
108
108
  email_verified?: boolean;
109
- gender?: 'female' | 'male' | string;
109
+ gender?: 'female' | 'male' | (string & {});
110
110
  birthdate?: string;
111
111
  zoneinfo?: string;
112
112
  locale?: string;