@shware/security 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/index.cjs +39 -11
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.d.cts +7 -4
- package/dist/auth/index.d.ts +7 -4
- package/dist/auth/index.mjs +39 -11
- package/dist/auth/index.mjs.map +1 -1
- package/dist/session/__tests__/redis.test.cjs +18 -18
- package/dist/session/__tests__/redis.test.cjs.map +1 -1
- package/dist/session/__tests__/redis.test.mjs +7 -7
- package/dist/session/__tests__/redis.test.mjs.map +1 -1
- package/package.json +8 -8
package/dist/auth/index.mjs.map
CHANGED
|
@@ -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\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 * The code itself is just an opaque random UUID; it carries no payload —\n * the principal name is stashed in KV under the code's key and pulled\n * during `desktopExchange`. KV TTL bounds the lifetime; `removeItem` in\n * `desktopExchange` enforces single-use semantics.\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 const code = randomUUID();\n await this.kv.setItem(this.getDesktopCodeKey(code), principal.name, 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 };\n try {\n body = (await request.json()) as { code?: 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 if (!code) {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const key = this.getDesktopCodeKey(code);\n const principalName = await this.kv.getItem(key);\n if (typeof principalName !== 'string') {\n return Response.json(invalidArgument('INVALID_DESKTOP_CODE'), { status: 400 });\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, principalName);\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","session"]}
|
|
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 { code_challenge?: unknown; code_challenge_method?: unknown };\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"]}
|
|
@@ -108,23 +108,23 @@ var require_dist = __commonJS({
|
|
|
108
108
|
// src/session/__tests__/redis.test.ts
|
|
109
109
|
var import_ioredis = __toESM(require("ioredis"), 1);
|
|
110
110
|
|
|
111
|
-
// ../../node_modules/.pnpm/vitest@4.1.
|
|
112
|
-
var
|
|
111
|
+
// ../../node_modules/.pnpm/vitest@4.1.5_@opentelemetry+api@1.9.0_@types+node@25.3.0_vite@7.3.1_@types+node@25.3.0__a1e65b0801b6e2ae9b5c0a5fc41015c1/node_modules/vitest/dist/index.js
|
|
112
|
+
var import_test_DNmyFkvJ = require("./chunks/test.DNmyFkvJ.js.cjs");
|
|
113
113
|
var import_benchmark_CX_oY03V = require("./chunks/benchmark.CX_oY03V.js.cjs");
|
|
114
114
|
var import_evaluatedModules_Dg1zASAC = require("./chunks/evaluatedModules.Dg1zASAC.js.cjs");
|
|
115
|
-
var
|
|
115
|
+
var import_index_DdgEv5B1 = require("./chunks/index.DdgEv5B1.js.cjs");
|
|
116
116
|
var import_expect_type = __toESM(require_dist(), 1);
|
|
117
117
|
|
|
118
|
-
// ../../node_modules/.pnpm/@vitest+runner@4.1.
|
|
118
|
+
// ../../node_modules/.pnpm/@vitest+runner@4.1.5/node_modules/@vitest/runner/dist/index.js
|
|
119
119
|
var import_chunk_artifact = require("./chunk-artifact.js.cjs");
|
|
120
120
|
|
|
121
|
-
// ../../node_modules/.pnpm/vitest@4.1.
|
|
121
|
+
// ../../node_modules/.pnpm/vitest@4.1.5_@opentelemetry+api@1.9.0_@types+node@25.3.0_vite@7.3.1_@types+node@25.3.0__a1e65b0801b6e2ae9b5c0a5fc41015c1/node_modules/vitest/dist/index.js
|
|
122
122
|
var import_utils_BX5Fg8C4 = require("./chunks/utils.BX5Fg8C4.js.cjs");
|
|
123
123
|
|
|
124
|
-
// ../../node_modules/.pnpm/@vitest+runner@4.1.
|
|
124
|
+
// ../../node_modules/.pnpm/@vitest+runner@4.1.5/node_modules/@vitest/runner/dist/utils.js
|
|
125
125
|
var import_chunk_artifact2 = require("./chunk-artifact.js.cjs");
|
|
126
126
|
|
|
127
|
-
// ../../node_modules/.pnpm/vitest@4.1.
|
|
127
|
+
// ../../node_modules/.pnpm/vitest@4.1.5_@opentelemetry+api@1.9.0_@types+node@25.3.0_vite@7.3.1_@types+node@25.3.0__a1e65b0801b6e2ae9b5c0a5fc41015c1/node_modules/vitest/dist/index.js
|
|
128
128
|
var import_commonjsHelpers_D26ty3Ew = require("./chunks/_commonjsHelpers.D26ty3Ew.js.cjs");
|
|
129
129
|
var import_rpc_MzXet3jl = require("./chunks/rpc.MzXet3jl.js.cjs");
|
|
130
130
|
var import_index_Chj8NDwU = require("./chunks/index.Chj8NDwU.js.cjs");
|
|
@@ -187,11 +187,11 @@ var repository = new import_redis.RedisIndexedSessionRepository(redis, "myapp:se
|
|
|
187
187
|
session.setAttribute(import_common.PRINCIPAL_NAME_INDEX_NAME, principalName);
|
|
188
188
|
await repository.save(session);
|
|
189
189
|
const foundSession = await repository.findById(sessionId);
|
|
190
|
-
(0,
|
|
190
|
+
(0, import_test_DNmyFkvJ.g)(foundSession?.getAttribute(import_common.PRINCIPAL_NAME_INDEX_NAME)).toBe(principalName);
|
|
191
191
|
});
|
|
192
192
|
(0, import_chunk_artifact.v)("should find a session by principal name", async () => {
|
|
193
193
|
const sessions = await repository.findByPrincipalName(principalName);
|
|
194
|
-
(0,
|
|
194
|
+
(0, import_test_DNmyFkvJ.g)(sessions.size).toBeGreaterThan(0);
|
|
195
195
|
});
|
|
196
196
|
(0, import_chunk_artifact.v)("should delete a session by id", async () => {
|
|
197
197
|
const sessions = await repository.findByPrincipalName(principalName);
|
|
@@ -199,7 +199,7 @@ var repository = new import_redis.RedisIndexedSessionRepository(redis, "myapp:se
|
|
|
199
199
|
const sessionId = session.getId();
|
|
200
200
|
await repository.deleteById(sessionId);
|
|
201
201
|
const foundSession = await repository.findById(sessionId);
|
|
202
|
-
(0,
|
|
202
|
+
(0, import_test_DNmyFkvJ.g)(foundSession).toBeNull();
|
|
203
203
|
}
|
|
204
204
|
});
|
|
205
205
|
});
|
|
@@ -210,12 +210,12 @@ var repository = new import_redis.RedisIndexedSessionRepository(redis, "myapp:se
|
|
|
210
210
|
const sessionId = session.getId();
|
|
211
211
|
await repository.save(session);
|
|
212
212
|
const foundSession = await repository.findById(sessionId);
|
|
213
|
-
(0,
|
|
214
|
-
(0,
|
|
213
|
+
(0, import_test_DNmyFkvJ.g)(foundSession).not.toBeNull();
|
|
214
|
+
(0, import_test_DNmyFkvJ.g)(foundSession?.getAttribute("test_key")).toBe("test_value");
|
|
215
215
|
foundSession?.removeAttribute("test_key");
|
|
216
216
|
if (foundSession) await repository.save(foundSession);
|
|
217
217
|
const foundSession2 = await repository.findById(sessionId);
|
|
218
|
-
(0,
|
|
218
|
+
(0, import_test_DNmyFkvJ.g)(foundSession2?.getAttribute("test_key")).toBeNull();
|
|
219
219
|
});
|
|
220
220
|
(0, import_chunk_artifact.v)("should change session id", async () => {
|
|
221
221
|
const session = repository.createSession();
|
|
@@ -224,14 +224,14 @@ var repository = new import_redis.RedisIndexedSessionRepository(redis, "myapp:se
|
|
|
224
224
|
const oldSessionId = session.getId();
|
|
225
225
|
await repository.save(session);
|
|
226
226
|
const oldSession = await repository.findById(oldSessionId);
|
|
227
|
-
(0,
|
|
227
|
+
(0, import_test_DNmyFkvJ.g)(oldSession).not.toBeNull();
|
|
228
228
|
if (!oldSession) throw new Error("Not found session id");
|
|
229
229
|
const newSessionId = oldSession.changeSessionId();
|
|
230
230
|
await repository.save(oldSession);
|
|
231
231
|
const foundSession1 = await repository.findById(oldSessionId);
|
|
232
|
-
(0,
|
|
232
|
+
(0, import_test_DNmyFkvJ.g)(foundSession1).toBeNull();
|
|
233
233
|
const foundSession2 = await repository.findById(newSessionId);
|
|
234
|
-
(0,
|
|
234
|
+
(0, import_test_DNmyFkvJ.g)(foundSession2).not.toBeNull();
|
|
235
235
|
});
|
|
236
236
|
});
|
|
237
237
|
(0, import_chunk_artifact.h)("redis session config", () => {
|
|
@@ -239,8 +239,8 @@ var repository = new import_redis.RedisIndexedSessionRepository(redis, "myapp:se
|
|
|
239
239
|
repository.setDefaultMaxInactiveInterval(maxInactiveInterval);
|
|
240
240
|
(0, import_chunk_artifact.v)("should set default max inactive interval", () => {
|
|
241
241
|
const session = repository.createSession();
|
|
242
|
-
(0,
|
|
243
|
-
(0,
|
|
242
|
+
(0, import_test_DNmyFkvJ.g)(session.isExpired()).toBe(false);
|
|
243
|
+
(0, import_test_DNmyFkvJ.g)(session.getMaxInactiveInterval()).toBe(maxInactiveInterval);
|
|
244
244
|
});
|
|
245
245
|
});
|
|
246
246
|
//# sourceMappingURL=redis.test.cjs.map
|