@fitzzero/quickdraw-core 5.1.1 → 5.1.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.
Files changed (152) hide show
  1. package/README.md +1308 -219
  2. package/dist/{admin-DKwkIh5i.d.ts → admin-CS7JB_UH.d.ts} +2 -2
  3. package/dist/{adminSchemas-2DUA_hZ8.d.ts → adminSchemas-29cGgrg5.d.ts} +6 -4
  4. package/dist/{auth-BhWqdyb-.d.ts → auth-DtdYjrZ9.d.ts} +51 -21
  5. package/dist/{caller-CXTFswBO.d.ts → caller-BTwGl7vm.d.ts} +296 -33
  6. package/dist/chunk-2FDSKLBO.js +82 -0
  7. package/dist/chunk-2FDSKLBO.js.map +1 -0
  8. package/dist/{chunk-RT6OILOI.js → chunk-4D2DOGAW.js} +2 -14
  9. package/dist/chunk-4D2DOGAW.js.map +1 -0
  10. package/dist/{chunk-OECZPRPW.js → chunk-64I6MYBC.js} +162 -775
  11. package/dist/chunk-64I6MYBC.js.map +1 -0
  12. package/dist/chunk-7473HK24.js +17 -0
  13. package/dist/chunk-7473HK24.js.map +1 -0
  14. package/dist/chunk-7Y5ROPXL.js +33 -0
  15. package/dist/chunk-7Y5ROPXL.js.map +1 -0
  16. package/dist/chunk-A2RBQD66.js +7 -0
  17. package/dist/chunk-A2RBQD66.js.map +1 -0
  18. package/dist/chunk-AK23XSWD.js +16 -0
  19. package/dist/chunk-AK23XSWD.js.map +1 -0
  20. package/dist/chunk-ASAMUGHG.js +651 -0
  21. package/dist/chunk-ASAMUGHG.js.map +1 -0
  22. package/dist/{chunk-B2P7AZ25.js → chunk-CYE4UBOB.js} +6 -4
  23. package/dist/chunk-CYE4UBOB.js.map +1 -0
  24. package/dist/chunk-DKE6QO26.js +136 -0
  25. package/dist/chunk-DKE6QO26.js.map +1 -0
  26. package/dist/chunk-DNP65TD5.js +832 -0
  27. package/dist/chunk-DNP65TD5.js.map +1 -0
  28. package/dist/{chunk-H23KEFBA.js → chunk-DUBU3BB3.js} +30 -1
  29. package/dist/chunk-DUBU3BB3.js.map +1 -0
  30. package/dist/chunk-GYLWEJHW.js +19 -0
  31. package/dist/chunk-GYLWEJHW.js.map +1 -0
  32. package/dist/{chunk-SHEGLZEP.js → chunk-I3JCFS7H.js} +34 -13
  33. package/dist/chunk-I3JCFS7H.js.map +1 -0
  34. package/dist/{chunk-X5CWPX7H.js → chunk-IIQ3WMJK.js} +13 -2
  35. package/dist/chunk-IIQ3WMJK.js.map +1 -0
  36. package/dist/{chunk-RZTP4IEW.js → chunk-JVTONQFI.js} +6339 -4226
  37. package/dist/chunk-JVTONQFI.js.map +1 -0
  38. package/dist/{chunk-QEKVYG5F.js → chunk-KKQANRLG.js} +2 -2
  39. package/dist/chunk-KKQANRLG.js.map +1 -0
  40. package/dist/{chunk-CDKG4TFA.js → chunk-MPQAT55F.js} +38 -205
  41. package/dist/chunk-MPQAT55F.js.map +1 -0
  42. package/dist/chunk-NO4M6PO5.js +101 -0
  43. package/dist/chunk-NO4M6PO5.js.map +1 -0
  44. package/dist/{chunk-TBMKJ26R.js → chunk-NW6U45HX.js} +3 -1
  45. package/dist/chunk-NW6U45HX.js.map +1 -0
  46. package/dist/chunk-NX2V6SIL.js +202 -0
  47. package/dist/chunk-NX2V6SIL.js.map +1 -0
  48. package/dist/{chunk-ZIVPHYRG.js → chunk-NZHA4TZD.js} +72 -175
  49. package/dist/chunk-NZHA4TZD.js.map +1 -0
  50. package/dist/{chunk-2PWMUVC7.js → chunk-OCZJ4EYV.js} +518 -27
  51. package/dist/chunk-OCZJ4EYV.js.map +1 -0
  52. package/dist/{chunk-REX5YPHX.js → chunk-PHCURHC7.js} +76 -21
  53. package/dist/chunk-PHCURHC7.js.map +1 -0
  54. package/dist/{chunk-4YO5H5ON.js → chunk-Q4OGQM5I.js} +4 -10
  55. package/dist/chunk-Q4OGQM5I.js.map +1 -0
  56. package/dist/chunk-QSU47XEX.js +13 -0
  57. package/dist/chunk-QSU47XEX.js.map +1 -0
  58. package/dist/{chunk-DD4SKCNO.js → chunk-RZXJTDH5.js} +13 -2
  59. package/dist/chunk-RZXJTDH5.js.map +1 -0
  60. package/dist/{chunk-Z2LZ7IZ7.js → chunk-SN3OZVEQ.js} +5 -31
  61. package/dist/chunk-SN3OZVEQ.js.map +1 -0
  62. package/dist/{chunk-4DIQHASK.js → chunk-SQ5VUG77.js} +28 -8
  63. package/dist/chunk-SQ5VUG77.js.map +1 -0
  64. package/dist/chunk-STH5DLQP.js +1192 -0
  65. package/dist/chunk-STH5DLQP.js.map +1 -0
  66. package/dist/{chunk-QCHKWIRS.js → chunk-VZFJTKIW.js} +13 -13
  67. package/dist/{chunk-QCHKWIRS.js.map → chunk-VZFJTKIW.js.map} +1 -1
  68. package/dist/{chunk-VSQBHPWH.js → chunk-X4ZU4QH5.js} +54 -22
  69. package/dist/chunk-X4ZU4QH5.js.map +1 -0
  70. package/dist/chunk-XD7SRDUY.js +188 -0
  71. package/dist/chunk-XD7SRDUY.js.map +1 -0
  72. package/dist/chunk-XH2IZVLT.js +62 -0
  73. package/dist/chunk-XH2IZVLT.js.map +1 -0
  74. package/dist/chunk-Z7I6SONO.js +825 -0
  75. package/dist/chunk-Z7I6SONO.js.map +1 -0
  76. package/dist/cli/quickdraw-docs.js +132 -112
  77. package/dist/cli/quickdraw-docs.js.map +1 -1
  78. package/dist/client/core.d.ts +387 -0
  79. package/dist/client/core.js +109 -0
  80. package/dist/client/index.d.ts +52 -203
  81. package/dist/client/index.js +59 -218
  82. package/dist/client/index.js.map +1 -1
  83. package/dist/{clientTypes-Bc-G8n-U.d.ts → clientTypes-CUUNJwWs.d.ts} +3 -3
  84. package/dist/{connection-Cr1BU07W.d.ts → connection-2EeQ_Et0.d.ts} +22 -11
  85. package/dist/{createServer-CP9eDHZZ.d.ts → createServer-Bx5HOcYK.d.ts} +67 -17
  86. package/dist/{createTestApp-UKVGEmOf.d.ts → createTestApp-DAMANT79.d.ts} +46 -13
  87. package/dist/{envelope-Cp3YcVxC.d.ts → envelope-BMBaxppV.d.ts} +216 -10
  88. package/dist/{http-Djj2Inuu.d.ts → http-YzLAar3w.d.ts} +76 -60
  89. package/dist/index.d.ts +8 -7
  90. package/dist/index.js +82 -25
  91. package/dist/index.js.map +1 -1
  92. package/dist/{infer-Teo1xpsd.d.ts → infer-Br62cnyW.d.ts} +82 -11
  93. package/dist/logger-Yz93KzdV.d.ts +30 -0
  94. package/dist/{metrics-BdBu5o8G.d.ts → metrics-CTVpw0gu.d.ts} +2 -2
  95. package/dist/origins-kvpxnVcW.d.ts +8 -0
  96. package/dist/parser.js +2 -1
  97. package/dist/prisma/index.d.ts +14 -7
  98. package/dist/prisma/index.js +279 -70
  99. package/dist/prisma/index.js.map +1 -1
  100. package/dist/{rateLimit-ZMIZJUZF.js → rateLimit-5RKBFLUR.js} +2 -2
  101. package/dist/rateLimit-5RKBFLUR.js.map +1 -0
  102. package/dist/remote-N7OUAGOW.js +15 -0
  103. package/dist/remote-N7OUAGOW.js.map +1 -0
  104. package/dist/server/auth/index.d.ts +82 -33
  105. package/dist/server/auth/index.js +277 -270
  106. package/dist/server/auth/index.js.map +1 -1
  107. package/dist/server/express/index.d.ts +9 -2
  108. package/dist/server/express/index.js +1 -1
  109. package/dist/server/index.d.ts +268 -84
  110. package/dist/server/index.js +664 -418
  111. package/dist/server/index.js.map +1 -1
  112. package/dist/server/mcp/index.d.ts +89 -27
  113. package/dist/server/mcp/index.js +281 -62
  114. package/dist/server/mcp/index.js.map +1 -1
  115. package/dist/server/otel.d.ts +3 -3
  116. package/dist/{sharing-DVa8W-yR.d.ts → sharing-BrsmlDv_.d.ts} +3 -3
  117. package/dist/testing/client.d.ts +14 -14
  118. package/dist/testing/client.js +19 -10
  119. package/dist/testing/client.js.map +1 -1
  120. package/dist/testing/index.d.ts +50 -13
  121. package/dist/testing/index.js +113 -21
  122. package/dist/testing/index.js.map +1 -1
  123. package/dist/testing/mock.d.ts +5 -5
  124. package/dist/testing/mock.js +17 -9
  125. package/dist/tracked-BR5b2O-U.d.ts +43 -0
  126. package/dist/{types-Cu0vmx7c.d.ts → types-CLRCQGnO.d.ts} +4 -3
  127. package/dist/types-tMagHbjy.d.ts +151 -0
  128. package/dist/utils/index.d.ts +16 -4
  129. package/dist/utils/index.js +6 -3
  130. package/package.json +5 -1
  131. package/dist/chunk-2PWMUVC7.js.map +0 -1
  132. package/dist/chunk-4DIQHASK.js.map +0 -1
  133. package/dist/chunk-4YO5H5ON.js.map +0 -1
  134. package/dist/chunk-B2P7AZ25.js.map +0 -1
  135. package/dist/chunk-CDKG4TFA.js.map +0 -1
  136. package/dist/chunk-DD4SKCNO.js.map +0 -1
  137. package/dist/chunk-H23KEFBA.js.map +0 -1
  138. package/dist/chunk-OECZPRPW.js.map +0 -1
  139. package/dist/chunk-QEKVYG5F.js.map +0 -1
  140. package/dist/chunk-REX5YPHX.js.map +0 -1
  141. package/dist/chunk-RT6OILOI.js.map +0 -1
  142. package/dist/chunk-RZTP4IEW.js.map +0 -1
  143. package/dist/chunk-SHEGLZEP.js.map +0 -1
  144. package/dist/chunk-TBMKJ26R.js.map +0 -1
  145. package/dist/chunk-VSQBHPWH.js.map +0 -1
  146. package/dist/chunk-X5CWPX7H.js.map +0 -1
  147. package/dist/chunk-YSWH776Y.js +0 -13
  148. package/dist/chunk-YSWH776Y.js.map +0 -1
  149. package/dist/chunk-Z2LZ7IZ7.js.map +0 -1
  150. package/dist/chunk-ZIVPHYRG.js.map +0 -1
  151. package/dist/logger-BY28acW-.d.ts +0 -16
  152. /package/dist/{rateLimit-ZMIZJUZF.js.map → client/core.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/server/auth/oauth.ts","../../../src/server/auth/discord.ts","../../../src/server/auth/google.ts","../../../src/server/auth/mock.ts","../../../src/server/auth/validateOrigin.ts","../../../src/server/auth/routes/respond.ts","../../../src/server/auth/routes/origins.ts","../../../src/server/auth/routes/state.ts","../../../src/server/auth/routes/tokens.ts","../../../src/server/auth/routes/settings.ts","../../../src/server/auth/routes/guest.ts","../../../src/server/auth/routes/limits.ts","../../../src/server/auth/routes/mockFlow.ts","../../../src/server/auth/routes/oauth.ts","../../../src/server/auth/routes/session.ts","../../../src/server/auth/routes/createAuthRoutes.ts","../../../src/server/auth/routes/socketAuth.ts","../../../src/server/auth/routes/requireSession.ts","../../../src/server/auth/routes/sessions.ts","../../../src/server/auth/routes/providers.ts"],"sourcesContent":["/**\n * Generic OAuth provider interface.\n */\nexport interface OAuthProvider<TUser = unknown> {\n name: string;\n authorizationUrl: string;\n tokenUrl: string;\n userInfoUrl: string;\n scopes: string[];\n\n /**\n * Parse user info response into a standardized format.\n */\n parseUserInfo: (data: unknown) => TUser;\n}\n\n/**\n * OAuth configuration for a provider.\n */\nexport interface OAuthConfig {\n clientId: string;\n clientSecret: string;\n redirectUri: string;\n}\n\n/**\n * OAuth token response.\n */\nexport interface OAuthTokenResponse {\n access_token: string;\n token_type: string;\n expires_in?: number;\n refresh_token?: string;\n scope?: string;\n}\n\n/**\n * Create an OAuth authorization URL.\n *\n * @param provider - The OAuth provider\n * @param config - OAuth configuration\n * @param state - Optional state parameter for CSRF protection\n */\nexport function createOAuthURL(\n provider: OAuthProvider,\n config: OAuthConfig,\n state?: string,\n): string {\n const params = new URLSearchParams({\n client_id: config.clientId,\n redirect_uri: config.redirectUri,\n response_type: \"code\",\n scope: provider.scopes.join(\" \"),\n });\n\n if (state) {\n params.set(\"state\", state);\n }\n\n return `${provider.authorizationUrl}?${params.toString()}`;\n}\n\n/**\n * Exchange an OAuth authorization code for tokens and user info.\n *\n * @param provider - The OAuth provider\n * @param config - OAuth configuration\n * @param code - The authorization code from the callback\n */\nexport async function exchangeOAuthCode<TUser>(\n provider: OAuthProvider<TUser>,\n config: OAuthConfig,\n code: string,\n): Promise<{ tokens: OAuthTokenResponse; user: TUser }> {\n // Exchange code for tokens\n const tokenResponse = await fetch(provider.tokenUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: new URLSearchParams({\n client_id: config.clientId,\n client_secret: config.clientSecret,\n code,\n grant_type: \"authorization_code\",\n redirect_uri: config.redirectUri,\n }),\n });\n\n if (!tokenResponse.ok) {\n const error = await tokenResponse.text();\n throw new Error(`Token exchange failed: ${error}`);\n }\n\n const tokens = (await tokenResponse.json()) as OAuthTokenResponse;\n\n // Fetch user info\n const userResponse = await fetch(provider.userInfoUrl, {\n headers: {\n Authorization: `Bearer ${tokens.access_token}`,\n Accept: \"application/json\",\n },\n });\n\n if (!userResponse.ok) {\n const error = await userResponse.text();\n throw new Error(`User info fetch failed: ${error}`);\n }\n\n const userData = await userResponse.json();\n const user = provider.parseUserInfo(userData);\n\n return { tokens, user };\n}\n","import type { OAuthProvider } from \"./oauth\";\n\n/**\n * Discord user information.\n */\nexport interface DiscordUser {\n id: string;\n username: string;\n discriminator: string;\n email: string | null;\n avatar: string | null;\n verified: boolean;\n}\n\n/**\n * Discord OAuth provider configuration.\n */\nexport const discordProvider: OAuthProvider<DiscordUser> = {\n name: \"discord\",\n authorizationUrl: \"https://discord.com/api/oauth2/authorize\",\n tokenUrl: \"https://discord.com/api/oauth2/token\",\n userInfoUrl: \"https://discord.com/api/users/@me\",\n scopes: [\"identify\", \"email\"],\n\n parseUserInfo: (data: unknown): DiscordUser => {\n const d = data as Record<string, unknown>;\n return {\n id: String(d.id),\n username: String(d.username),\n discriminator: String(d.discriminator ?? \"0\"),\n email: d.email ? String(d.email) : null,\n avatar: d.avatar ? String(d.avatar) : null,\n verified: Boolean(d.verified),\n };\n },\n};\n\n/**\n * Get Discord avatar URL.\n */\nexport function getDiscordAvatarUrl(user: DiscordUser): string {\n if (user.avatar) {\n return `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`;\n }\n // Default avatar based on discriminator or user ID\n const index =\n user.discriminator === \"0\"\n ? (BigInt(user.id) >> BigInt(22)) % BigInt(6)\n : parseInt(user.discriminator, 10) % 5;\n return `https://cdn.discordapp.com/embed/avatars/${index}.png`;\n}\n","import type { OAuthProvider } from \"./oauth\";\n\n/**\n * Google user information.\n */\nexport interface GoogleUser {\n id: string;\n email: string;\n name: string;\n picture: string | null;\n verified_email: boolean;\n}\n\n/**\n * Google OAuth provider configuration.\n */\nexport const googleProvider: OAuthProvider<GoogleUser> = {\n name: \"google\",\n authorizationUrl: \"https://accounts.google.com/o/oauth2/v2/auth\",\n tokenUrl: \"https://oauth2.googleapis.com/token\",\n userInfoUrl: \"https://www.googleapis.com/oauth2/v2/userinfo\",\n scopes: [\"openid\", \"email\", \"profile\"],\n\n parseUserInfo: (data: unknown): GoogleUser => {\n const d = data as Record<string, unknown>;\n return {\n id: String(d.id),\n email: String(d.email),\n name: String(d.name),\n picture: d.picture ? String(d.picture) : null,\n verified_email: Boolean(d.verified_email),\n };\n },\n};\n","/**\n * Mock OAuth provider for local development and tests.\n *\n * Implements a real OAuth 2.0 authorization-code flow served by the app's own\n * API, so `createOAuthURL` and `exchangeOAuthCode` work unchanged — the mock\n * is just a provider whose authorize/token/userinfo endpoints the app mounts\n * via `registerMockOAuthProvider`. Users are supplied by the app (typically\n * seeded demo accounts); no real credentials are involved.\n *\n * Production hard-block: routes are never mounted when NODE_ENV is\n * \"production\" or ENABLE_MOCK_OAUTH !== \"true\", and every handler re-checks\n * NODE_ENV at request time. Apps should add a third layer and refuse to boot\n * in production with the flag set.\n */\n\nimport { randomBytes } from \"crypto\";\nimport { consoleLogger, type Logger } from \"../../contract/logger\";\nimport type { GoogleUser } from \"./google\";\nimport type { OAuthProvider } from \"./oauth\";\nimport { validateRedirectOrigin } from \"./validateOrigin\";\n\nexport interface MockOAuthUser {\n id: string;\n email: string;\n name: string;\n picture?: string | null;\n}\n\nexport const DEFAULT_MOCK_PATH_PREFIX = \"/auth/mock/provider\";\n\nconst CODE_TTL_MS = 5 * 60 * 1000;\nconst TOKEN_TTL_MS = 60 * 60 * 1000;\n\n/** Private-network origins allowed for redirect_uri in dev (LAN testing). */\nconst DEV_PRIVATE_ORIGIN_REGEX =\n /^http:\\/\\/(?:localhost|127\\.0\\.0\\.1|0\\.0\\.0\\.0|10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|192\\.168\\.\\d{1,3}\\.\\d{1,3}|172\\.(?:1[6-9]|2\\d|3[01])\\.\\d{1,3}\\.\\d{1,3}):\\d+$/;\n\nexport function isMockOAuthEnabled(): boolean {\n return process.env.ENABLE_MOCK_OAUTH === \"true\" && process.env.NODE_ENV !== \"production\";\n}\n\nexport interface MockOAuthProviderOptions {\n /**\n * Base URL the API server can use to reach itself for the token/userinfo\n * exchange (server-side fetch). Defaults to apiBaseUrl — override in\n * containers/codespaces where the public URL isn't self-reachable\n * (e.g. \"http://localhost:4000\").\n */\n internalBaseUrl?: string;\n /** Route prefix for the mock endpoints. Default: \"/auth/mock/provider\". */\n pathPrefix?: string;\n}\n\n/**\n * Create the mock OAuth provider definition.\n *\n * `parseUserInfo` returns a GoogleUser-compatible shape with `id` set to the\n * user's email, so `providerAccountId` stays stable across database re-seeds.\n */\nexport function createMockOAuthProvider(\n apiBaseUrl: string,\n options: MockOAuthProviderOptions = {},\n): OAuthProvider<GoogleUser> {\n const prefix = options.pathPrefix ?? DEFAULT_MOCK_PATH_PREFIX;\n const publicBase = apiBaseUrl.replace(/\\/$/, \"\");\n const internalBase = (options.internalBaseUrl ?? apiBaseUrl).replace(/\\/$/, \"\");\n\n return {\n name: \"mock\",\n authorizationUrl: `${publicBase}${prefix}/authorize`,\n tokenUrl: `${internalBase}${prefix}/token`,\n userInfoUrl: `${internalBase}${prefix}/userinfo`,\n scopes: [\"openid\", \"email\", \"profile\"],\n\n parseUserInfo: (data: unknown): GoogleUser => {\n const d = data as Record<string, unknown>;\n return {\n id: String(d.id),\n email: String(d.email),\n name: String(d.name),\n picture: d.picture ? String(d.picture) : null,\n verified_email: true,\n };\n },\n };\n}\n\ninterface MockRequest {\n url?: string;\n originalUrl?: string;\n query?: Record<string, unknown>;\n headers: Record<string, string | string[] | undefined>;\n body?: unknown;\n}\n\ninterface MockResponse {\n status(code: number): MockResponse;\n json(body: unknown): unknown;\n send(body: string): unknown;\n redirect(url: string): unknown;\n}\n\ntype MockHandler = (req: MockRequest, res: MockResponse) => void | Promise<void>;\n\nexport interface MockOAuthRouter {\n get(path: string, handler: MockHandler): unknown;\n post(path: string, handler: MockHandler): unknown;\n}\n\nexport interface RegisterMockOAuthOptions {\n /**\n * Supply the users shown in the dev login picker (typically seeded demo\n * accounts). Injected so core never touches the app's database.\n */\n listUsers: () => Promise<MockOAuthUser[]>;\n /** Route prefix — must match createMockOAuthProvider's. */\n pathPrefix?: string;\n /**\n * Extra origins allowed for redirect_uri beyond CLIENT_URL /\n * EXTRA_ALLOWED_ORIGINS / localhost / private LAN IPs.\n */\n allowedRedirectOrigins?: string[];\n logger?: Logger;\n}\n\nfunction getQueryParam(req: MockRequest, name: string): string | null {\n const fromQuery = req.query?.[name];\n if (typeof fromQuery === \"string\") return fromQuery;\n const url = req.originalUrl ?? req.url;\n if (!url) return null;\n const idx = url.indexOf(\"?\");\n if (idx === -1) return null;\n return new URLSearchParams(url.slice(idx + 1)).get(name);\n}\n\n/**\n * Parse an x-www-form-urlencoded POST body. Works without any body-parser\n * middleware by falling back to reading the raw request stream. An empty\n * object body is treated as unparsed: express.json() sets req.body = {} even\n * when it skips non-JSON content types, leaving the stream unread.\n */\nasync function readBodyParams(req: MockRequest): Promise<URLSearchParams> {\n if (typeof req.body === \"string\") return new URLSearchParams(req.body);\n if (req.body && typeof req.body === \"object\" && Object.keys(req.body).length > 0) {\n return new URLSearchParams(req.body as Record<string, string>);\n }\n const chunks: Buffer[] = [];\n for await (const chunk of req as unknown as AsyncIterable<Buffer>) {\n chunks.push(Buffer.from(chunk));\n }\n return new URLSearchParams(Buffer.concat(chunks).toString(\"utf8\"));\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n\nfunction isAllowedRedirectUri(uri: string, allowedOrigins: string[]): boolean {\n let parsed: URL;\n try {\n parsed = new URL(uri);\n } catch {\n return false;\n }\n const origin = parsed.origin;\n if (allowedOrigins.includes(origin)) return true;\n if (DEV_PRIVATE_ORIGIN_REGEX.test(origin)) return true;\n return validateRedirectOrigin(origin) !== null;\n}\n\nfunction renderPickerPage(\n users: MockOAuthUser[],\n authorizePath: string,\n redirectUri: string,\n state: string | null,\n): string {\n const links = users\n .map((user) => {\n const params = new URLSearchParams({ redirect_uri: redirectUri, email: user.email });\n if (state) params.set(\"state\", state);\n const href = `${authorizePath}?${params.toString()}`;\n return `<li><a href=\"${escapeHtml(href)}\"><strong>${escapeHtml(user.name)}</strong><span>${escapeHtml(user.email)}</span></a></li>`;\n })\n .join(\"\\n\");\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>Mock sign-in</title>\n<style>\n body { font-family: system-ui, sans-serif; background: #111; color: #eee; display: flex; min-height: 100vh; align-items: center; justify-content: center; margin: 0; }\n main { max-width: 420px; width: 100%; padding: 2rem; }\n h1 { font-size: 1.25rem; }\n p { color: #999; font-size: 0.875rem; }\n ul { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }\n a { display: flex; flex-direction: column; gap: 0.125rem; padding: 0.75rem 1rem; border: 1px solid #333; border-radius: 8px; text-decoration: none; color: inherit; }\n a:hover { border-color: #888; background: #1a1a1a; }\n a span { color: #999; font-size: 0.8125rem; }\n</style>\n</head>\n<body>\n<main>\n<h1>Mock sign-in</h1>\n<p>Development only — pick an account to continue. Run your seed script if this list is empty.</p>\n<ul>\n${links || \"<li><em>No users found.</em></li>\"}\n</ul>\n</main>\n</body>\n</html>`;\n}\n\n/** A short-lived grant for one mock user: an auth code or a bearer token. */\ninterface MockGrant {\n email: string;\n expiresAt: number;\n}\n\n/** What the three mock endpoints share once mounted. */\ninterface MockEndpoints {\n readonly options: RegisterMockOAuthOptions;\n readonly prefix: string;\n readonly allowedOrigins: string[];\n // Single-use auth codes and bearer tokens, both short-lived and in-memory.\n readonly codes: Map<string, MockGrant>;\n readonly tokens: Map<string, MockGrant>;\n}\n\nfunction sweep(store: Map<string, MockGrant>): void {\n const now = Date.now();\n for (const [key, entry] of store) {\n if (entry.expiresAt < now) store.delete(key);\n }\n}\n\nfunction guard(res: MockResponse): boolean {\n // Request-time re-check defends against env mutation after boot.\n if (process.env.NODE_ENV === \"production\") {\n res.status(404).json({ error: \"Not found\" });\n return false;\n }\n return true;\n}\n\nfunction authorizeHandler(endpoints: MockEndpoints): MockHandler {\n const { options, prefix, allowedOrigins, codes } = endpoints;\n return async (req, res) => {\n if (!guard(res)) return;\n\n const redirectUri = getQueryParam(req, \"redirect_uri\");\n const state = getQueryParam(req, \"state\");\n const email = getQueryParam(req, \"email\");\n\n if (!redirectUri || !isAllowedRedirectUri(redirectUri, allowedOrigins)) {\n res.status(400).json({ error: \"Invalid redirect_uri\" });\n return;\n }\n\n const users = await options.listUsers();\n\n if (!email) {\n res.status(200).send(renderPickerPage(users, `${prefix}/authorize`, redirectUri, state));\n return;\n }\n\n const user = users.find((u) => u.email === email);\n if (!user) {\n res.status(400).json({ error: \"Unknown mock user\" });\n return;\n }\n\n sweep(codes);\n const code = randomBytes(24).toString(\"hex\");\n codes.set(code, { email: user.email, expiresAt: Date.now() + CODE_TTL_MS });\n\n const params = new URLSearchParams({ code });\n if (state) params.set(\"state\", state);\n res.redirect(`${redirectUri}${redirectUri.includes(\"?\") ? \"&\" : \"?\"}${params.toString()}`);\n };\n}\n\nfunction tokenHandler({ codes, tokens }: MockEndpoints): MockHandler {\n return async (req, res) => {\n if (!guard(res)) return;\n\n const body = await readBodyParams(req);\n const code = body.get(\"code\");\n const entry = code ? codes.get(code) : undefined;\n if (code) codes.delete(code);\n\n if (!entry || entry.expiresAt < Date.now()) {\n res.status(400).json({ error: \"invalid_grant\" });\n return;\n }\n\n sweep(tokens);\n const accessToken = randomBytes(24).toString(\"hex\");\n tokens.set(accessToken, { email: entry.email, expiresAt: Date.now() + TOKEN_TTL_MS });\n\n res.status(200).json({\n access_token: accessToken,\n token_type: \"Bearer\",\n expires_in: Math.floor(TOKEN_TTL_MS / 1000),\n scope: \"openid email profile\",\n });\n };\n}\n\nfunction userinfoHandler({ options, tokens }: MockEndpoints): MockHandler {\n return async (req, res) => {\n if (!guard(res)) return;\n\n const auth = req.headers.authorization;\n const accessToken =\n typeof auth === \"string\" && auth.startsWith(\"Bearer \") ? auth.slice(7) : null;\n const entry = accessToken ? tokens.get(accessToken) : undefined;\n\n if (!entry || entry.expiresAt < Date.now()) {\n res.status(401).json({ error: \"invalid_token\" });\n return;\n }\n\n const users = await options.listUsers();\n const user = users.find((u) => u.email === entry.email);\n if (!user) {\n res.status(401).json({ error: \"invalid_token\" });\n return;\n }\n\n res.status(200).json({\n // providerAccountId = email: stable across re-seeds, unlike row ids.\n id: user.email,\n email: user.email,\n name: user.name,\n picture: user.picture ?? null,\n verified_email: true,\n });\n };\n}\n\n/**\n * Mount the mock OAuth provider endpoints (authorize, token, userinfo) on an\n * Express-compatible router.\n *\n * No-ops (returning false) unless `isMockOAuthEnabled()` — i.e. in production\n * or without ENABLE_MOCK_OAUTH=true the routes are never mounted.\n */\nexport function registerMockOAuthProvider(\n router: MockOAuthRouter,\n options: RegisterMockOAuthOptions,\n): boolean {\n const logger = options.logger ?? consoleLogger;\n\n if (!isMockOAuthEnabled()) {\n if (process.env.ENABLE_MOCK_OAUTH === \"true\") {\n logger.error(\"Mock OAuth is enabled but NODE_ENV is production — refusing to mount routes\");\n }\n return false;\n }\n\n const endpoints: MockEndpoints = {\n options,\n prefix: options.pathPrefix ?? DEFAULT_MOCK_PATH_PREFIX,\n allowedOrigins: options.allowedRedirectOrigins ?? [],\n codes: new Map(),\n tokens: new Map(),\n };\n\n router.get(`${endpoints.prefix}/authorize`, authorizeHandler(endpoints));\n router.post(`${endpoints.prefix}/token`, tokenHandler(endpoints));\n router.get(`${endpoints.prefix}/userinfo`, userinfoHandler(endpoints));\n\n logger.info(`Mock OAuth provider mounted at ${endpoints.prefix} (development only)`);\n return true;\n}\n","/**\n * Origin validation utility for OAuth redirect and CORS security.\n *\n * An origin is allowed when it is the client URL, one of the extra origins\n * or matches an app's pattern, or, outside production, is localhost. GitHub\n * Codespaces origins (`*.app.github.dev`) are never allowed by default since\n * 5.0.1 (finding R1.2 of the 5.0.0 review): anyone can open a Codespace, so\n * the allowance let any such page open a socket or finish a sign-in as a\n * signed-in user in production, wherever an app used this helper for CORS or\n * a cookie check. An app that wants one lists a pattern in `allowedPatterns`.\n */\n\nexport const OAUTH_RETURN_ORIGIN_COOKIE = \"oauth_return_origin\";\n\nexport interface ValidateOriginOptions {\n /**\n * The primary web client origin. Defaults to process.env.CLIENT_URL.\n */\n clientUrl?: string;\n /**\n * Additional exact-match origins. Defaults to the comma-separated\n * process.env.EXTRA_ALLOWED_ORIGINS. Lets a single API back multiple web\n * hosts (e.g. prod + staging).\n */\n extraAllowedOrigins?: string[];\n /**\n * App-specific origin patterns (e.g. preview-deploy subdomains).\n */\n allowedPatterns?: RegExp[];\n /**\n * Ignored: kept so 5.0.0 code that passes it still compiles.\n *\n * @deprecated Codespaces origins are never allowed since 5.0.1; list a pattern in allowedPatterns if one is wanted.\n */\n allowCodespaces?: boolean;\n /**\n * Allow http://localhost:* when NODE_ENV !== \"production\". Default: true.\n */\n allowLocalhostInDev?: boolean;\n}\n\nconst LOCALHOST_REGEX = /^http:\\/\\/localhost:\\d+$/;\n\nfunction envExtraOrigins(): string[] {\n const raw = process.env.EXTRA_ALLOWED_ORIGINS;\n if (!raw) return [];\n return raw\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n}\n\n/**\n * Validates if an origin is safe for OAuth redirect / CORS.\n *\n * Only the origin (scheme + host + port) is compared, never the path, to\n * prevent path-injection bypasses.\n *\n * @param origin - The origin to validate\n * @param options - Override env-derived defaults\n * @returns The validated origin if allowed, null otherwise\n */\nexport function validateRedirectOrigin(\n origin: string,\n options: ValidateOriginOptions = {},\n): string | null {\n if (!origin) return null;\n\n const clientUrl = options.clientUrl ?? process.env.CLIENT_URL;\n\n let parsedOrigin: URL;\n try {\n parsedOrigin = new URL(origin);\n } catch {\n return null;\n }\n\n const cleanOrigin = parsedOrigin.origin;\n\n if (clientUrl && cleanOrigin === clientUrl) {\n return cleanOrigin;\n }\n\n const extra = options.extraAllowedOrigins ?? envExtraOrigins();\n if (extra.includes(cleanOrigin)) {\n return cleanOrigin;\n }\n\n if (options.allowedPatterns?.some((pattern) => pattern.test(cleanOrigin))) {\n return cleanOrigin;\n }\n\n if (\n (options.allowLocalhostInDev ?? true) &&\n process.env.NODE_ENV !== \"production\" &&\n LOCALHOST_REGEX.test(cleanOrigin)\n ) {\n return cleanOrigin;\n }\n\n return null;\n}\n","// Reading and answering the auth routes' requests. The routes are mounted on\n// the app's Express app (4 or 5), so `req` and `res` are Express's. The kit\n// reads and writes them through Node's own API, and uses Express only for\n// cookies (`res.cookie` and `res.clearCookie`, as `setSessionCookie` does).\n// Failures answer `{ error: <code>, message }` with the code's HTTP status\n// (RFC 0003 section 3), and nothing the routes answer is cached.\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { httpStatus, type ErrorCode } from \"../../../protocol/errors\";\nimport type { CookieResponse } from \"../sessionCookie\";\n\n/** The request the routes read: Express's, of which they use these. */\nexport interface AuthRouteRequest extends IncomingMessage {\n /** The path the request was made to, before any `app.use(path, ...)` mount took a prefix off. */\n readonly originalUrl?: string;\n /** The client's address, as Express reports it (`trust proxy` decides). */\n readonly ip?: string;\n /** True when the request came over HTTPS, as Express reports it. */\n readonly secure?: boolean;\n /** The body the app's own JSON parser read, if any. */\n readonly body?: unknown;\n /** The cookies `cookie-parser` read, if the app uses it. */\n readonly cookies?: unknown;\n}\n\n/** The response the routes answer: Express's, which also sets cookies. */\nexport type AuthRouteResponse = ServerResponse & CookieResponse;\n\nfunction urlOf(req: AuthRouteRequest): string {\n return req.originalUrl ?? req.url ?? \"\";\n}\n\n/** The request's path, without its query. */\nexport function pathOf(req: AuthRouteRequest): string {\n return urlOf(req).split(\"?\", 1)[0] ?? \"\";\n}\n\n/** A query parameter's first value, or `null`. */\nexport function queryOf(req: AuthRouteRequest, name: string): string | null {\n const url = urlOf(req);\n const start = url.indexOf(\"?\");\n return start === -1 ? null : new URLSearchParams(url.slice(start + 1)).get(name);\n}\n\nfunction noStore(res: ServerResponse, status: number): void {\n res.statusCode = status;\n res.setHeader(\"cache-control\", \"no-store\");\n}\n\n/** Answers with a JSON body, unless the response was already sent. */\nexport function sendJson(res: ServerResponse, status: number, body: unknown): void {\n if (res.headersSent) {\n return;\n }\n noStore(res, status);\n res.setHeader(\"content-type\", \"application/json; charset=utf-8\");\n res.end(JSON.stringify(body));\n}\n\n/** Answers a failure: `{ error: code, message }` with the code's HTTP status. */\nexport function refuse(res: ServerResponse, code: ErrorCode, message: string): void {\n sendJson(res, httpStatus(code), { error: code, message });\n}\n\n/** Redirects the browser (302). */\nexport function redirect(res: ServerResponse, location: string): void {\n noStore(res, 302);\n res.setHeader(\"location\", location);\n res.end();\n}\n\n/** Answers 204 with no body. */\nexport function noContent(res: ServerResponse): void {\n noStore(res, 204);\n res.end();\n}\n","// The browser origins the auth kit trusts (RFC 0003 section 12.6): where a\n// sign-in may return to, and which pages may open a socket with the session\n// cookie. One list serves both. `validateRedirectOrigin` decides, with each\n// of its defaults turned off: no CLIENT_URL or EXTRA_ALLOWED_ORIGINS from the\n// environment, no implicit localhost (it allows no GitHub Codespaces origin\n// at all). The list the app passes is the whole allowlist.\n\nimport { validateRedirectOrigin, type ValidateOriginOptions } from \"../validateOrigin\";\n\n/**\n * An allowed origin: exact (`\"https://app.example.com\"`), or a pattern over\n * whole origins, anchored at both ends\n * (`/^https:\\/\\/[a-z0-9-]+\\.preview\\.example\\.com$/`).\n */\nexport type AllowedOrigin = string | RegExp;\n\n/** The allowlist, checked and normalized. */\nexport interface OriginAllowlist {\n /** The origin of `value` (an origin or a URL) when it is allowed, else `null`. */\n allowed(value: string | undefined): string | null;\n /** The first exact origin listed: where a sign-in returns when it names none. */\n readonly fallback: string | undefined;\n}\n\nfunction exactOrigin(entry: unknown, owner: string): string {\n let url: URL | undefined;\n try {\n url = typeof entry === \"string\" ? new URL(entry) : undefined;\n } catch {\n url = undefined;\n }\n const bare =\n url !== undefined &&\n (url.protocol === \"https:\" || url.protocol === \"http:\") &&\n url.username === \"\" &&\n url.password === \"\" &&\n url.pathname === \"/\" &&\n url.search === \"\" &&\n url.hash === \"\";\n if (url === undefined || !bare) {\n throw new TypeError(\n `${owner}: allowedOrigins entries are origins such as \"https://app.example.com\" or anchored patterns; got ${String(entry)}`,\n );\n }\n return url.origin;\n}\n\n/**\n * The pattern, refused unless it is written anchored with `^` and `$`, and\n * compiled to match whole origins whatever it holds: `^a|b$` is anchored\n * only at its ends (`^a` or `b$`), so `/^https:\\/\\/app|staging\\.example\\.com$/`\n * would allow `https://app.attacker.net`. The source is wrapped as\n * `^(?:source)$`, so every alternative must match the whole origin.\n */\nfunction anchoredPattern(pattern: RegExp, owner: string): RegExp {\n const { source } = pattern;\n const anchored = source.startsWith(\"^\") && source.endsWith(\"$\") && !source.endsWith(\"\\\\$\");\n if (!anchored || pattern.global || pattern.sticky) {\n throw new TypeError(\n `${owner}: an allowedOrigins pattern must match whole origins, anchored with ^ and $ and without the g or y flag; got ${String(pattern)}`,\n );\n }\n return new RegExp(`^(?:${source})$`, pattern.flags);\n}\n\n/**\n * Checks and normalizes an allowlist; throws a `TypeError` naming `owner`\n * for an entry that is neither an origin nor an anchored pattern, or for an\n * empty list unless `allowEmpty`.\n */\nexport function originAllowlist(\n allowedOrigins: readonly AllowedOrigin[],\n owner: string,\n allowEmpty = false,\n): OriginAllowlist {\n if (!Array.isArray(allowedOrigins) || (allowedOrigins.length === 0 && !allowEmpty)) {\n throw new TypeError(`${owner}: allowedOrigins must list the web app's origins`);\n }\n const exact: string[] = [];\n const patterns: RegExp[] = [];\n for (const entry of allowedOrigins) {\n if (entry instanceof RegExp) {\n patterns.push(anchoredPattern(entry, owner));\n } else {\n exact.push(exactOrigin(entry, owner));\n }\n }\n const options: ValidateOriginOptions = {\n // An empty client URL, not undefined: undefined falls back to process.env.CLIENT_URL.\n clientUrl: \"\",\n extraAllowedOrigins: exact,\n allowedPatterns: patterns,\n allowLocalhostInDev: false,\n };\n return Object.freeze({\n allowed: (value: string | undefined) =>\n typeof value === \"string\" && value !== \"\" ? validateRedirectOrigin(value, options) : null,\n fallback: exact[0],\n });\n}\n\n/**\n * The allowlist each `createAuthRoutes` was given, by the session store it\n * writes to: `requireSession` over the same store applies it when it is given\n * no `allowedOrigins` of its own.\n */\nconst ROUTE_ORIGINS = new WeakMap<object, OriginAllowlist>();\n\n/** Remembers the allowlist of the auth routes that write sessions to `sessions`. */\nexport function rememberRouteOrigins(sessions: object, origins: OriginAllowlist): void {\n ROUTE_ORIGINS.set(sessions, origins);\n}\n\n/** The allowlist of the auth routes that write sessions to `sessions`, if any were made. */\nexport function routeOriginsOf(sessions: object): OriginAllowlist | undefined {\n return ROUTE_ORIGINS.get(sessions);\n}\n","// The OAuth `state` of a sign-in (RFC 6749 section 10.12), bound to the\n// browser that started it and redeemable once. `start` sends the state to\n// the provider and keeps it, with the provider and the validated return\n// origin, in a short-lived HttpOnly cookie (`__Host-qd_oauth` on `/` over a\n// secure request, else `qd_oauth` on the auth routes' path). The\n// callback clears that cookie whatever happens, accepts the state only when\n// the provider sent back the cookie's own value, for the same provider and\n// within ten minutes, and remembers each redeemed state in this process until\n// it would have expired anyway, so a replayed callback is refused even when\n// its cookie is replayed with it. The return origin read back from the cookie\n// is validated again before any redirect (`createAuthRoutes`).\n\nimport { randomBytes, timingSafeEqual } from \"node:crypto\";\n\n/** The cookie that carries a sign-in in progress, on a request that is not secure. */\nexport const OAUTH_STATE_COOKIE = \"qd_oauth\";\n\n/**\n * The cookie that carries a sign-in in progress on a secure request: the\n * `__Host-` prefix keeps any other site under the same parent domain from\n * planting a state of its own (a login CSRF), and requires `Path=/`.\n */\nexport const HOST_OAUTH_STATE_COOKIE = \"__Host-qd_oauth\";\n\n/** How long a sign-in may take from `start` to its callback. */\nexport const OAUTH_STATE_TTL_MS = 10 * 60 * 1000;\n\n/** At most this many redeemed states are remembered; the oldest are forgotten first. */\nconst MAX_REDEEMED = 10_000;\n\n/** A sign-in in progress, as its cookie carries it. */\nexport interface PendingSignIn {\n /** The `state` sent to the provider. */\n readonly state: string;\n /** The provider's id. */\n readonly provider: string;\n /** The validated origin to return to. */\n readonly origin: string;\n /** When `start` issued it, in epoch milliseconds. */\n readonly issuedAt: number;\n}\n\n/** A new state: 256 random bits, base64url. */\nexport function newState(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\n/** The state cookie's value for a sign-in. */\nexport function encodePending(pending: PendingSignIn): string {\n return Buffer.from(JSON.stringify(pending), \"utf8\").toString(\"base64url\");\n}\n\n/** The sign-in a state cookie's value carries, or `null` when it carries none. */\nexport function decodePending(raw: string | undefined): PendingSignIn | null {\n if (raw === undefined || raw === \"\") {\n return null;\n }\n let value: unknown;\n try {\n value = JSON.parse(Buffer.from(raw, \"base64url\").toString(\"utf8\"));\n } catch {\n return null;\n }\n const { state, provider, origin, issuedAt } = (value ?? {}) as Partial<\n Record<keyof PendingSignIn, unknown>\n >;\n if (\n typeof state !== \"string\" ||\n typeof provider !== \"string\" ||\n typeof origin !== \"string\" ||\n typeof issuedAt !== \"number\"\n ) {\n return null;\n }\n return { state, provider, origin, issuedAt };\n}\n\nfunction sameString(left: string, right: string): boolean {\n const a = Buffer.from(left, \"utf8\");\n const b = Buffer.from(right, \"utf8\");\n return a.length === b.length && timingSafeEqual(a, b);\n}\n\n/**\n * True when the callback's `state` is the pending sign-in's own, for the\n * same provider, issued no more than {@link OAUTH_STATE_TTL_MS} ago.\n */\nexport function stateMatches(\n pending: PendingSignIn,\n provider: string,\n returned: string | null,\n now: number,\n): boolean {\n const age = now - pending.issuedAt;\n return (\n returned !== null &&\n pending.provider === provider &&\n age >= 0 &&\n age <= OAUTH_STATE_TTL_MS &&\n sameString(pending.state, returned)\n );\n}\n\n/** The states this process has redeemed, so each is redeemed once. */\nexport interface RedeemedStates {\n /** Records `state` as redeemed; false when it already was. */\n redeem(state: string, now: number): boolean;\n}\n\n/** An empty record of redeemed states, each kept until it would have expired anyway. */\nexport function redeemedStates(): RedeemedStates {\n // Insertion order is redemption order, so the oldest entries come first.\n const redeemed = new Map<string, number>();\n return {\n redeem(state, now) {\n for (const [key, until] of redeemed) {\n if (until > now && redeemed.size < MAX_REDEEMED) {\n break;\n }\n redeemed.delete(key);\n }\n if (redeemed.has(state)) {\n return false;\n }\n redeemed.set(state, now + OAUTH_STATE_TTL_MS);\n return true;\n },\n };\n}\n","// A signed-in session's credential: a JWT naming its user and its session\n// (`sid`), signed with the app's secret, valid only while the app's\n// `SessionStore` still holds that session for that user and the session has\n// not expired. The routes issue it at sign-in, and the routes and\n// `socketAuth` read it back with `liveSession`. Both are exported for an\n// app's own sign-in flows (login codes, an embedded activity), so the\n// sessions they start work with `socketAuth` like the routes' own.\n\nimport { createJWT, verifyJWT } from \"../jwt\";\nimport type { AuthSession, SessionMeta, SessionStore } from \"./sessions\";\n\n/** The shortest `jwtSecret` the kit accepts: HS256 wants a key of 256 bits or more. */\nexport const MIN_JWT_SECRET_LENGTH = 32;\n\n/** The default session lifetime: 7 days, the 4.1 JWT and cookie default. */\nexport const DEFAULT_SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000;\n\n/** Where sessions are stored, and the secret their JWTs are signed with. */\nexport interface SessionKeys {\n readonly sessions: SessionStore;\n readonly jwtSecret: string;\n}\n\n/** Throws a `TypeError` naming `owner` unless `keys` holds a usable store and secret. */\nexport function checkSessionKeys(keys: Partial<SessionKeys>, owner: string): SessionKeys {\n const { sessions, jwtSecret } = keys;\n const store = (sessions ?? {}) as Partial<Record<keyof SessionStore, unknown>>;\n const methods = [\"create\", \"get\", \"revoke\", \"revokeAll\"] as const;\n if (sessions === undefined || !methods.every((name) => typeof store[name] === \"function\")) {\n throw new TypeError(\n `${owner}: sessions must be a SessionStore (create, get, revoke, revokeAll)`,\n );\n }\n if (typeof jwtSecret !== \"string\" || jwtSecret.length < MIN_JWT_SECRET_LENGTH) {\n throw new TypeError(\n `${owner}: jwtSecret must be a secret of at least ${MIN_JWT_SECRET_LENGTH} characters`,\n );\n }\n return { sessions, jwtSecret };\n}\n\n/** A new session, stored, and its JWT, which expires with it. */\nexport interface IssuedSession {\n readonly session: AuthSession;\n readonly token: string;\n}\n\n/**\n * Starts a session for `userId`: creates it in the store, lasting `ttlMs`\n * (default 7 days), and signs its JWT. Send the token as the session cookie\n * (`setSessionCookie`) or give it to a cookie-less client for `auth.token`.\n * Throws when the store does not return the session it created.\n */\nexport async function issueSession(\n keys: SessionKeys,\n userId: string,\n meta: Omit<SessionMeta, \"expiresAt\">,\n ttlMs: number = DEFAULT_SESSION_TTL_MS,\n): Promise<IssuedSession> {\n checkSessionKeys(keys, \"issueSession\");\n if (!Number.isSafeInteger(ttlMs) || ttlMs < 1000) {\n throw new TypeError(\"issueSession: ttlMs must be a whole number of milliseconds, 1000 or more\");\n }\n const expiresAt = new Date(Date.now() + ttlMs);\n const session: Partial<AuthSession> | null | undefined = await keys.sessions.create(userId, {\n ...meta,\n expiresAt,\n });\n if (typeof session?.id !== \"string\" || session.id === \"\" || session.userId !== userId) {\n throw new TypeError(\"SessionStore.create must return the session it created, with its id\");\n }\n const seconds = Math.max(1, Math.ceil(ttlMs / 1000));\n const token = await createJWT({ userId, sid: session.id }, keys.jwtSecret, `${seconds}s`);\n return { session: session as AuthSession, token };\n}\n\n/**\n * The live session `token` stands for, or `null`: the JWT verifies with the\n * secret and has not expired, names a session, and the store still holds\n * that session, for the token's user, before its `expiresAt`.\n */\nexport async function liveSession(keys: SessionKeys, token: string): Promise<AuthSession | null> {\n checkSessionKeys(keys, \"liveSession\");\n const payload = await verifyJWT(token, keys.jwtSecret);\n if (\n payload === null ||\n typeof payload.userId !== \"string\" ||\n payload.userId === \"\" ||\n payload.sid === undefined\n ) {\n return null;\n }\n const session = await keys.sessions.get(payload.sid);\n if (session === null || session === undefined || session.userId !== payload.userId) {\n return null;\n }\n // A store may return the expiry as a string or a number; an invalid date is refused.\n const expiresAt = new Date(session.expiresAt).getTime();\n return expiresAt > Date.now() ? session : null;\n}\n","// What every auth route works from, resolved and checked once from\n// `createAuthRoutes`' options: the session keys, the origin allowlist, the\n// URLs, the cookie, and the process's record of redeemed OAuth states.\n\nimport { consoleLogger, type Logger } from \"../../../contract/logger\";\nimport {\n cookieDomainFromEnv,\n isSecureSessionRequest,\n sessionCookieNameFor,\n type CookieResponse,\n type CookieSettings,\n type SessionCookieNaming,\n type SessionCookieRequest,\n} from \"../sessionCookie\";\nimport { originAllowlist, rememberRouteOrigins, type OriginAllowlist } from \"./origins\";\nimport type { AuthRouteRequest } from \"./respond\";\nimport {\n HOST_OAUTH_STATE_COOKIE,\n OAUTH_STATE_COOKIE,\n OAUTH_STATE_TTL_MS,\n redeemedStates,\n type RedeemedStates,\n} from \"./state\";\nimport {\n checkSessionKeys,\n DEFAULT_SESSION_TTL_MS,\n issueSession,\n type IssuedSession,\n type SessionKeys,\n} from \"./tokens\";\nimport type { AuthRoutesOptions } from \"./types\";\n\n/** The session cookie, resolved. */\nexport interface ResolvedCookie {\n /** The name the app gave; without one, it depends on the domain and the request (`sessionCookieName`). */\n readonly name: string | undefined;\n readonly ttlMs: number;\n /** `cookie.domain`, else `COOKIE_DOMAIN` from the environment, else none: the API's host only. */\n readonly domain: string | undefined;\n readonly sameSite: \"lax\" | \"none\";\n readonly secure: boolean | undefined;\n}\n\n/** What the routes work from. */\nexport interface RouteSettings {\n readonly keys: SessionKeys;\n readonly origins: OriginAllowlist;\n /** The API's public URL, without a trailing slash. */\n readonly publicUrl: string;\n /** `\"\"` or a path without a trailing slash, such as `\"/auth\"`. */\n readonly basePath: string;\n readonly successPath: string;\n readonly errorPath: string;\n readonly cookie: ResolvedCookie;\n readonly onLogin: AuthRoutesOptions[\"onLogin\"];\n readonly onRevoke: AuthRoutesOptions[\"onRevoke\"];\n readonly logger: Logger;\n readonly redeemed: RedeemedStates;\n /**\n * Looks at the host a request to the routes arrived for: the first one\n * that is not a loopback address while `publicUrl` is logs an error, once\n * (finding F9.2 of the owner's QA of the template).\n */\n watchHost(req: AuthRouteRequest): void;\n}\n\nconst OWNER = \"createAuthRoutes\";\n\nfunction publicUrlOf(value: unknown): string {\n let url: URL | undefined;\n try {\n url = typeof value === \"string\" ? new URL(value) : undefined;\n } catch {\n url = undefined;\n }\n if (\n url === undefined ||\n !/^https?:$/.test(url.protocol) ||\n url.search !== \"\" ||\n url.hash !== \"\"\n ) {\n throw new TypeError(\n `${OWNER}: publicUrl must be the API's public URL, such as \"https://api.example.com\"`,\n );\n }\n return `${url.origin}${url.pathname.replace(/\\/+$/, \"\")}`;\n}\n\n/** `\"/auth/\"` and `\"auth\"` become `\"/auth\"`; `\"/\"` becomes `\"\"`. */\nfunction basePathOf(value: unknown): string {\n if (value === undefined) {\n return \"/auth\";\n }\n if (typeof value !== \"string\" || /[?#\\\\]/.test(value)) {\n throw new TypeError(`${OWNER}: basePath must be a path such as \"/auth\"`);\n }\n const trimmed = value.replace(/^\\/+|\\/+$/g, \"\");\n return trimmed === \"\" ? \"\" : `/${trimmed}`;\n}\n\n/** A path on the web app's origin; one that could lead to another origin is refused. */\nfunction landingPathOf(value: unknown, name: string, fallback: string): string {\n if (value === undefined) {\n return fallback;\n }\n const probe = \"https://origin.invalid\";\n const safe =\n typeof value === \"string\" &&\n value.startsWith(\"/\") &&\n !value.startsWith(\"//\") &&\n !value.includes(\"\\\\\") &&\n new URL(value, probe).origin === probe;\n if (!safe) {\n throw new TypeError(`${OWNER}: ${name} must be a path on the web app, such as \"/\"`);\n }\n return value;\n}\n\nfunction cookieOf(options: AuthRoutesOptions[\"cookie\"]): ResolvedCookie {\n const { name, maxAgeMs, domain, secure, sameSite } = options ?? {};\n const ttlMs = maxAgeMs ?? DEFAULT_SESSION_TTL_MS;\n if (!Number.isSafeInteger(ttlMs) || ttlMs < 1000) {\n throw new TypeError(\n `${OWNER}: cookie.maxAgeMs must be a whole number of milliseconds, 1000 or more`,\n );\n }\n if (name !== undefined && !/^[\\w!#$%&'*+.^`|~-]+$/.test(name)) {\n throw new TypeError(`${OWNER}: cookie.name must be a cookie name`);\n }\n if (sameSite !== undefined && sameSite !== \"lax\" && sameSite !== \"none\") {\n throw new TypeError(`${OWNER}: cookie.sameSite must be \"lax\" or \"none\"`);\n }\n const resolvedDomain = domain ?? process.env.COOKIE_DOMAIN;\n const cookieDomain = resolvedDomain === \"\" ? undefined : resolvedDomain;\n if (name?.startsWith(\"__Host-\") === true && cookieDomain !== undefined) {\n throw new TypeError(\n `${OWNER}: cookie.name cannot start with \"__Host-\" when the cookie has a domain, which a browser refuses`,\n );\n }\n return { name, ttlMs, domain: cookieDomain, sameSite: sameSite ?? \"lax\", secure };\n}\n\n/**\n * Warns, once at startup, when the routes and the transports would name the\n * session cookie differently: the transports know the cookie's domain only\n * from `COOKIE_DOMAIN`, so a `cookie.domain` that `COOKIE_DOMAIN` does not\n * match makes the routes set `session` over HTTPS where `socketAuth` and the\n * HTTP transport read only `__Host-session` (or the reverse), unless the app\n * names the cookie in all three places.\n */\nfunction warnOnCookieDomain(cookie: ResolvedCookie, logger: Logger): void {\n const transportsSeeDomain = cookieDomainFromEnv() !== undefined;\n if (cookie.name !== undefined || (cookie.domain !== undefined) === transportsSeeDomain) {\n return;\n }\n const why =\n cookie.domain === undefined\n ? 'cookie.domain is \"\" while COOKIE_DOMAIN is set, so over HTTPS the routes set __Host-session where socketAuth and the HTTP transport read \"session\" first'\n : 'cookie.domain is set and COOKIE_DOMAIN is not, so the routes set \"session\" on every request, which socketAuth and the HTTP transport read over HTTPS only under that name';\n logger.warn(\n `${OWNER}: ${why}. Set COOKIE_DOMAIN to the cookie's domain instead, or name the cookie in all three places: cookie.name, socketAuth's cookieName and createServer's http.cookieName.`,\n { category: \"quickdraw.auth\" },\n );\n}\n\nconst LOOPBACK = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"]);\n\n/** True for a loopback hostname: what a public deployment's `publicUrl` must not be. */\nfunction isLoopback(hostname: string): boolean {\n return LOOPBACK.has(hostname) || hostname.endsWith(\".localhost\");\n}\n\n/** The host a request arrived for: the first `X-Forwarded-Host`, else `Host`; `undefined` without either. */\nfunction requestHost(req: AuthRouteRequest): string | undefined {\n const forwarded = req.headers[\"x-forwarded-host\"];\n const first = (Array.isArray(forwarded) ? forwarded[0] : forwarded)?.split(\",\")[0]?.trim();\n const host = first !== undefined && first !== \"\" ? first : req.headers.host;\n return host === undefined || host === \"\" ? undefined : host;\n}\n\n/** Names reserved for tests and examples (RFC 2606): no deployment's page is there. */\nconst RESERVED_TLD = /\\.(test|example|invalid)$/;\n\n/**\n * Warns, once at startup, when `publicUrl` is a loopback address and every\n * allowed origin is not (finding F10.2): the routes would send a browser on\n * one of those pages to a sign-in URL only the server's own machine reaches.\n * Pages under a name reserved for tests (`.test`, `.example`, `.invalid`)\n * do not count: a test suite serves its routes on loopback.\n */\nfunction warnOnLoopbackPublicUrl(\n publicUrl: string,\n origins: readonly string[],\n logger: Logger,\n): void {\n const publicHost = new URL(publicUrl).hostname;\n const pages = origins\n .map((origin) => new URL(origin).hostname)\n .filter((hostname) => !RESERVED_TLD.test(hostname));\n if (isLoopback(publicHost) && pages.length > 0 && !pages.some(isLoopback)) {\n logger.warn(\n `${OWNER}: publicUrl is ${publicUrl}, but allowedOrigins lists only public pages (${origins.join(\", \")}): a sign-in started from them redirects to an address only this machine reaches. Set publicUrl to the API's public URL.`,\n { category: \"quickdraw.auth\" },\n );\n }\n}\n\n/**\n * The request-time check of a loopback `publicUrl` (finding F9.2): a\n * deployment that fell back to `http://localhost:<port>` for want of its API\n * URL builds every redirect to localhost. The first request that arrives for\n * another host logs an error, once.\n */\nfunction hostWatcher(publicUrl: string, logger: Logger): (req: AuthRouteRequest) => void {\n if (!isLoopback(new URL(publicUrl).hostname)) {\n return () => undefined;\n }\n let warned = false;\n return (req) => {\n const host = warned ? undefined : requestHost(req);\n if (host === undefined) {\n return;\n }\n let hostname: string;\n try {\n hostname = new URL(`http://${host}`).hostname;\n } catch {\n return;\n }\n if (!isLoopback(hostname)) {\n warned = true;\n logger.error(\n `${OWNER}: publicUrl is ${publicUrl}, but requests arrive for ${host}: set publicUrl to the API's public URL (sign-in redirects are built from it)`,\n { category: \"quickdraw.auth\", publicUrl, host },\n );\n }\n };\n}\n\n/** Resolves and checks the options every route uses; throws a `TypeError` for a bad one. */\nexport function routeSettings(options: AuthRoutesOptions): RouteSettings {\n if (typeof options.onLogin !== \"function\") {\n throw new TypeError(`${OWNER}: onLogin is required`);\n }\n if (options.onRevoke !== undefined && typeof options.onRevoke !== \"function\") {\n throw new TypeError(`${OWNER}: onRevoke must be a function of (userId, sessionId)`);\n }\n const successPath = landingPathOf(options.successPath, \"successPath\", \"/\");\n const cookie = cookieOf(options.cookie);\n const logger = options.logger ?? consoleLogger;\n const publicUrl = publicUrlOf(options.publicUrl);\n const settings: RouteSettings = Object.freeze({\n keys: checkSessionKeys(options, OWNER),\n origins: originAllowlist(options.allowedOrigins, OWNER),\n publicUrl,\n basePath: basePathOf(options.basePath),\n successPath,\n errorPath: landingPathOf(options.errorPath, \"errorPath\", successPath),\n cookie,\n onLogin: options.onLogin,\n onRevoke: options.onRevoke,\n logger,\n redeemed: redeemedStates(),\n watchHost: hostWatcher(publicUrl, logger),\n });\n warnOnCookieDomain(cookie, logger);\n warnOnLoopbackPublicUrl(\n publicUrl,\n options.allowedOrigins.filter((origin): origin is string => typeof origin === \"string\"),\n logger,\n );\n // `requireSession` over the same store applies this list when given none.\n rememberRouteOrigins(settings.keys.sessions, settings.origins);\n return settings;\n}\n\n/** Where a provider sends the browser back: `{publicUrl}{basePath}/{id}/callback`. */\nexport function redirectUriOf(settings: RouteSettings, providerId: string): string {\n return `${settings.publicUrl}${settings.basePath}/${providerId}/callback`;\n}\n\n/** Where a sign-in lands on `origin`: the success path, or the error path with `?error=`. */\nexport function landingOf(settings: RouteSettings, origin: string, error?: string): string {\n const url = new URL(error === undefined ? settings.successPath : settings.errorPath, origin);\n if (error !== undefined) {\n url.searchParams.set(\"error\", error);\n }\n return url.href;\n}\n\n/**\n * Whether a cookie the routes set on `req` is Secure, unless the app said\n * otherwise: in production, and on a request that came over HTTPS by the\n * rule the session cookie's name follows (`isSecureRequest`).\n */\nfunction secureFor(settings: RouteSettings, req: SessionCookieRequest): boolean {\n return (\n settings.cookie.secure ?? (process.env.NODE_ENV === \"production\" || isSecureSessionRequest(req))\n );\n}\n\n/** How the app named the session cookie, for `sessionCookieNameFor` and the reads. */\nexport function sessionCookieNaming(settings: RouteSettings): SessionCookieNaming {\n return { cookieName: settings.cookie.name, domain: settings.cookie.domain };\n}\n\n/**\n * The session cookie's name on `req`, by the rule `socketAuth`, the HTTP\n * transport and `setSessionCookie` share (`sessionCookieNameFor`): the name\n * the app gave; else `session` when the cookie has a domain, which cannot be\n * `__Host-`; else `__Host-session` on a request over HTTPS, which a browser\n * keeps only as a Secure, host-only cookie on `/`, so no other site under\n * the same parent domain can plant or replace it, and `session` over plain\n * HTTP (development).\n */\nexport function sessionCookieName(settings: RouteSettings, req: SessionCookieRequest): string {\n return sessionCookieNameFor(req, sessionCookieNaming(settings));\n}\n\n/**\n * The session cookie's settings on `req`: set (`live`) or cleared. Secure\n * when its name is `__Host-` (which a browser requires) or it is\n * SameSite=None (likewise), else as `secureFor` says.\n */\nfunction sessionCookieSettings(\n settings: RouteSettings,\n req: SessionCookieRequest,\n name: string,\n live: boolean,\n): CookieSettings {\n const { ttlMs, domain, sameSite } = settings.cookie;\n const hostOnly = name.startsWith(\"__Host-\");\n return {\n httpOnly: true,\n secure: hostOnly || sameSite === \"none\" || secureFor(settings, req),\n sameSite,\n path: \"/\",\n ...(domain !== undefined && !hostOnly && { domain }),\n ...(live && { maxAge: ttlMs }),\n };\n}\n\n/**\n * `req` as the page a sign-in returns to sends it: an OAuth callback is a\n * navigation from the provider and carries no `Origin`, so the return\n * origin stands in for it, and a sign-in for an `https:` page gets the\n * cookie that page's own requests read.\n */\nfunction fromPage(req: AuthRouteRequest, pageOrigin: string | undefined): SessionCookieRequest {\n if (pageOrigin === undefined || req.headers.origin !== undefined) {\n return req;\n }\n return {\n headers: { ...req.headers, origin: pageOrigin },\n secure: req.secure,\n socket: req.socket,\n };\n}\n\n/**\n * Sets the session cookie carrying `token`, for the cookie, the JWT and the\n * session to end together; `pageOrigin` is the page an OAuth callback\n * returns to.\n */\nexport function setSession(\n res: CookieResponse,\n settings: RouteSettings,\n req: AuthRouteRequest,\n token: string,\n pageOrigin?: string,\n): void {\n const from = fromPage(req, pageOrigin);\n const name = sessionCookieName(settings, from);\n res.cookie(name, token, sessionCookieSettings(settings, from, name, true));\n}\n\n/** Clears the session cookie. */\nexport function clearSession(\n res: CookieResponse,\n settings: RouteSettings,\n req: AuthRouteRequest,\n): void {\n const name = sessionCookieName(settings, req);\n res.clearCookie(name, sessionCookieSettings(settings, req, name, false));\n}\n\n/** The state cookie's name on `req`: `__Host-qd_oauth` when it is Secure, else `qd_oauth`. */\nexport function stateCookieName(settings: RouteSettings, req: AuthRouteRequest): string {\n return secureFor(settings, req) ? HOST_OAUTH_STATE_COOKIE : OAUTH_STATE_COOKIE;\n}\n\n/**\n * The state cookie's settings: HttpOnly, Lax whatever the session cookie's\n * SameSite (the callback is a cross-site navigation from the provider, which\n * a Strict cookie would not ride), and for this host only; on `/` when it is\n * Secure, as its `__Host-` name requires, else on the routes' path only.\n * Pass `live` to set it, leave it out to clear it.\n */\nexport function stateCookieOf(\n settings: RouteSettings,\n req: AuthRouteRequest,\n live = false,\n): CookieSettings {\n const secure = secureFor(settings, req);\n return {\n httpOnly: true,\n secure,\n sameSite: \"lax\",\n path: secure || settings.basePath === \"\" ? \"/\" : settings.basePath,\n ...(live && { maxAge: OAUTH_STATE_TTL_MS }),\n };\n}\n\n/** Starts a session for `userId` signed in through `provider`, recording where from. */\nexport function issueFor(\n settings: RouteSettings,\n userId: string,\n provider: string,\n req: AuthRouteRequest,\n): Promise<IssuedSession> {\n const userAgent = req.headers[\"user-agent\"];\n const meta = {\n provider,\n ...(typeof userAgent === \"string\" && { userAgent }),\n ...(typeof req.ip === \"string\" && { ip: req.ip }),\n };\n return issueSession(settings.keys, userId, meta, settings.cookie.ttlMs);\n}\n","// The guest provider of the auth routes kit (RFC 0003 section 12.6): `POST\n// {basePath}/guest` creates a user through the app's `createUser` and signs\n// it in with an ordinary session, so everything downstream (socket auth,\n// access policies) sees a real user id rather than an anonymous socket. Like\n// every POST route of the kit it needs a JSON body type, which a cross-site\n// form cannot send, so a page elsewhere cannot sign a visitor in as a guest.\n\nimport { INTERNAL_MESSAGE, QuickdrawError } from \"../../../protocol/errors\";\nimport { readJsonInput } from \"../../transports/body\";\nimport type { MaybePromise } from \"../../types\";\nimport { refuse, sendJson, type AuthRouteRequest, type AuthRouteResponse } from \"./respond\";\nimport { issueFor, setSession, type RouteSettings } from \"./settings\";\n\n/** The largest guest request body read, in bytes. */\nexport const GUEST_MAX_BODY_BYTES = 16_384;\n\n/** The guest `createUser` made: its id, and the name it ended up with (`\"Ada#4821\"` for a taken \"Ada\"). */\nexport interface GuestUser {\n readonly userId: string;\n readonly name?: string;\n}\n\n/** The guest provider's options. */\nexport interface GuestOptions {\n /**\n * Creates the guest user and returns its id, or `{ userId, name }` when\n * the name it gave differs from the one asked for (a numbered one after a\n * collision): the route answers that name. `input` is the request's JSON\n * body (`undefined` without one), unchecked: validate it here. Throw a\n * `QuickdrawError` to refuse with its code, such as `VALIDATION` for a bad\n * name or `CONFLICT` for a taken one.\n */\n readonly createUser: (\n input: unknown,\n request: AuthRouteRequest,\n ) => MaybePromise<string | GuestUser>;\n /**\n * Also answer the session's token, for clients that keep no cookies (a\n * game engine, a page in a third-party iframe) and send it as\n * `auth.token` or a bearer token. Unlike the HttpOnly cookie, the token is\n * then readable by the page's scripts. Default `false`.\n */\n readonly token?: boolean;\n}\n\n/** The guest provider: `POST {basePath}/guest`. */\nexport interface GuestProvider {\n readonly kind: \"guest\";\n readonly id: \"guest\";\n readonly options: GuestOptions;\n}\n\n/** Signing in as a new guest user: `POST {basePath}/guest` with a JSON body. */\nexport function guest(options: GuestOptions): GuestProvider {\n if (typeof options.createUser !== \"function\") {\n throw new TypeError(\"guest(): createUser is required\");\n }\n if (options.token !== undefined && typeof options.token !== \"boolean\") {\n throw new TypeError(\"guest(): token must be true or false\");\n }\n return Object.freeze({ kind: \"guest\", id: \"guest\", options });\n}\n\n/** What `createUser` returned, read: the user's id and name; a `TypeError` for anything else. */\nfunction guestUserOf(made: unknown): GuestUser {\n if (typeof made === \"string\" && made !== \"\") {\n return { userId: made };\n }\n const { userId, name } = (typeof made === \"object\" && made !== null ? made : {}) as {\n readonly userId?: unknown;\n readonly name?: unknown;\n };\n if (\n typeof userId !== \"string\" ||\n userId === \"\" ||\n (name !== undefined && typeof name !== \"string\")\n ) {\n throw new TypeError(\"guest(): createUser must return the new user's id, or { userId, name? }\");\n }\n return name === undefined ? { userId } : { userId, name };\n}\n\n/** The user `createUser` made, or the failure already answered. */\nasync function createGuest(\n settings: RouteSettings,\n provider: GuestProvider,\n req: AuthRouteRequest,\n res: AuthRouteResponse,\n): Promise<GuestUser | null> {\n try {\n const input = await readJsonInput(req, GUEST_MAX_BODY_BYTES);\n return guestUserOf(await provider.options.createUser(input, req));\n } catch (error) {\n if (error instanceof QuickdrawError && error.code !== \"INTERNAL\") {\n refuse(res, error.code, error.message);\n return null;\n }\n settings.logger.error(\"Creating a guest user failed\", {\n category: \"quickdraw.auth\",\n error: error instanceof Error ? error.message : String(error),\n });\n refuse(res, \"INTERNAL\", INTERNAL_MESSAGE);\n return null;\n }\n}\n\n/**\n * `POST {basePath}/guest`: a new guest user, signed in; answers\n * `{ userId, name? }`, plus `token` when the provider says so.\n */\nexport function guestRoute(\n settings: RouteSettings,\n provider: GuestProvider,\n): (req: AuthRouteRequest, res: AuthRouteResponse) => Promise<void> {\n return async (req, res) => {\n const user = await createGuest(settings, provider, req, res);\n if (user === null) {\n return;\n }\n const { userId } = user;\n const { token } = await issueFor(settings, userId, provider.id, req);\n setSession(res, settings, req, token);\n settings.logger.info(\"Signed in a new guest\", { category: \"quickdraw.auth\", userId });\n sendJson(res, 200, provider.options.token === true ? { ...user, token } : user);\n };\n}\n","// The rate limits on the auth routes (RFC 0003 section 12.6). By default the\n// sign-in routes (each provider's start and callback, and the guest route)\n// share `createAuthLimiter({ max: SIGN_IN_LIMIT })` (60 requests per 15\n// minutes per IP: a start and a callback per sign-in, and a shared office or\n// school address, use 4.1's 20 up quickly), the session routes (`me`,\n// `logout`, `logout-all`) share `createAuthStatusLimiter` (120), and\n// `providers` has `createPublicApiLimiter` (60 per minute) to itself, so the\n// login pages of one address cannot use up its sign-outs (finding F11.3 of\n// the quickdraw-chat migration). The presets live in `./server/express`,\n// which needs the optional peer `express-rate-limit`; the routes import them\n// only when a default is used, so importing `./server/auth` for its JWT\n// helpers alone does not need the peer.\n\n/** How many sign-in requests one IP may make per 15 minutes under the default limiter. */\nexport const SIGN_IN_LIMIT = 60;\n\n/** An Express middleware, such as an `express-rate-limit` limiter: it answers the request, or calls `next()`. */\nexport type AuthMiddleware = (req: never, res: never, next: (error?: unknown) => void) => unknown;\n\n/** The limiters of the auth routes. Each one left out gets its default. */\nexport interface AuthRateLimits {\n /**\n * Each provider's start and callback, and the guest route. Default:\n * `createAuthLimiter({ max: 60 })`, 60 requests per 15 minutes per IP.\n */\n readonly signIn?: AuthMiddleware;\n /** `me`, `logout` and `logout-all`. Default: `createAuthStatusLimiter()`. */\n readonly session?: AuthMiddleware;\n /**\n * `providers`, the sign-ins a login page offers. Default:\n * `createPublicApiLimiter()`, 60 requests per minute per IP, apart from\n * `session`'s, so loading login pages cannot use up sign-outs.\n */\n readonly providers?: AuthMiddleware;\n}\n\n/** Which limiter a route is counted by. */\nexport type LimitKind = keyof AuthRateLimits;\n\n/** The limiters in use; none with `rateLimit: false`. */\nexport type Limiters = Readonly<Partial<Record<LimitKind, AuthMiddleware>>>;\n\n/** Throws a `TypeError` unless `option` is `false`, absent, or limiters that are functions. */\nexport function checkRateLimits(option: unknown): AuthRateLimits | false {\n if (option === false) {\n return false;\n }\n if (option === undefined) {\n return {};\n }\n if (typeof option !== \"object\" || option === null) {\n throw new TypeError(\n \"createAuthRoutes: rateLimit must be { signIn?, session?, providers? } or false\",\n );\n }\n for (const [name, limiter] of Object.entries(option)) {\n if (limiter !== undefined && typeof limiter !== \"function\") {\n throw new TypeError(`createAuthRoutes: rateLimit.${name} must be an Express middleware`);\n }\n }\n return option as AuthRateLimits;\n}\n\n/**\n * The limiters the routes use: the app's, and the default presets for the\n * ones it left out. Rejects when a preset is needed and `express-rate-limit`\n * is not installed.\n */\nexport async function loadLimiters(option: AuthRateLimits | false): Promise<Limiters> {\n if (option === false) {\n return {};\n }\n if (\n option.signIn !== undefined &&\n option.session !== undefined &&\n option.providers !== undefined\n ) {\n return option;\n }\n const presets = await import(\"../../express/rateLimit\");\n return {\n signIn: option.signIn ?? presets.createAuthLimiter({ max: SIGN_IN_LIMIT }),\n session: option.session ?? presets.createAuthStatusLimiter(),\n providers: option.providers ?? presets.createPublicApiLimiter(),\n };\n}\n","// The mock provider inside the auth routes: `registerMockOAuthProvider`\n// mounts its endpoints (authorize, token, userinfo) on the routes' own table\n// under `{basePath}/mock/provider`, and the sign-in drives them like any\n// OAuth provider. Nothing is mounted unless `isMockOAuthEnabled()`, and every\n// request checks it again, as the mock's own endpoints check `NODE_ENV`.\n\nimport {\n createMockOAuthProvider,\n isMockOAuthEnabled,\n registerMockOAuthProvider,\n type MockOAuthRouter,\n} from \"../mock\";\nimport { createOAuthURL, exchangeOAuthCode, type OAuthConfig } from \"../oauth\";\nimport type { SignInFlow } from \"./oauth\";\nimport type { MockSignInProvider } from \"./providers\";\nimport type { AuthRouteRequest, AuthRouteResponse } from \"./respond\";\nimport type { RouteSettings } from \"./settings\";\n\n/** Where the routes keep a handler the mock provider registers. */\nexport type MountHandler = (\n method: \"GET\" | \"POST\",\n path: string,\n handle: (req: AuthRouteRequest, res: AuthRouteResponse) => unknown,\n) => void;\n\n/**\n * The mock provider's sign-in, its endpoints mounted through `mount`, or\n * `null` when `isMockOAuthEnabled()` is false (nothing is mounted then).\n */\nexport function mockFlow(\n provider: MockSignInProvider,\n settings: RouteSettings,\n mount: MountHandler,\n): SignInFlow | null {\n const pathPrefix = `${settings.basePath}/mock/provider`;\n // The mock's handlers take Express's request and response, which the routes are given.\n const router: MockOAuthRouter = {\n get: (path, handler) => {\n mount(\"GET\", path, (req, res) => handler(req as never, res as never));\n },\n post: (path, handler) => {\n mount(\"POST\", path, (req, res) => handler(req as never, res as never));\n },\n };\n const mounted = registerMockOAuthProvider(router, {\n listUsers: provider.options.listUsers,\n pathPrefix,\n allowedRedirectOrigins: [new URL(settings.publicUrl).origin],\n logger: settings.logger,\n });\n if (!mounted) {\n return null;\n }\n const oauth = createMockOAuthProvider(settings.publicUrl, {\n pathPrefix,\n ...(provider.options.internalUrl !== undefined && {\n internalBaseUrl: provider.options.internalUrl,\n }),\n });\n const config = (redirectUri: string): OAuthConfig => ({\n clientId: \"mock\",\n clientSecret: \"mock\",\n redirectUri,\n });\n return Object.freeze({\n kind: \"oauth\",\n id: provider.id,\n enabled: isMockOAuthEnabled,\n authorizeUrl: (state: string, redirectUri: string) =>\n createOAuthURL(oauth, config(redirectUri), state),\n async profile(code: string, redirectUri: string) {\n const { tokens, user } = await exchangeOAuthCode(oauth, config(redirectUri), code);\n return {\n providerAccountId: user.id,\n email: user.email,\n emailVerified: true,\n name: user.name,\n image: user.picture,\n tokens,\n raw: user,\n };\n },\n });\n}\n\n/**\n * Whether a mounted mock provider still serves sign-ins: its routes check\n * `isMockOAuthEnabled()` on every request, so `GET {basePath}/providers`\n * lists it only while that holds.\n */\nexport function mockServes(): boolean {\n return isMockOAuthEnabled();\n}\n","// The OAuth routes of the auth kit (RFC 0003 section 12.6): `GET\n// {basePath}/{provider}/start` and `GET {basePath}/{provider}/callback`.\n//\n// `start` validates the return origin (`?returnTo=`, an origin or a URL on\n// it; the first exact allowed origin when absent), keeps it with a new state\n// in the state cookie (`state.ts`) and redirects to the provider. The\n// callback clears that cookie first, whatever happens next; validates the\n// remembered origin again before redirecting anywhere; checks and redeems the\n// state; exchanges the code; asks the app's `onLogin` for the user id;\n// creates the session and sets its cookie; and redirects to the return\n// origin. A failure after the origin is known redirects there with\n// `?error=state` (state missing, wrong, expired or already redeemed),\n// `denied` (the provider or `onLogin` refused) or `failed` (the exchange,\n// `onLogin` or the session store failed).\n\nimport { cookiesOf } from \"../../transports/body\";\nimport type { AuthProfile, OAuthSignInProvider } from \"./providers\";\nimport {\n queryOf,\n redirect,\n refuse,\n type AuthRouteRequest,\n type AuthRouteResponse,\n} from \"./respond\";\nimport {\n issueFor,\n landingOf,\n redirectUriOf,\n setSession,\n stateCookieName,\n stateCookieOf,\n type RouteSettings,\n} from \"./settings\";\nimport { decodePending, encodePending, newState, stateMatches } from \"./state\";\n\n/** A provider the routes drive; `enabled` is checked again on every request (the mock provider's). */\nexport interface SignInFlow extends OAuthSignInProvider {\n readonly enabled?: () => boolean;\n}\n\ntype Handler = (req: AuthRouteRequest, res: AuthRouteResponse) => void | Promise<void>;\n\nconst CATEGORY = \"quickdraw.auth\";\n\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** `GET {basePath}/{provider}/start`. */\nexport function startRoute(settings: RouteSettings, flow: SignInFlow): Handler {\n return (req, res) => {\n if (flow.enabled?.() === false) {\n refuse(res, \"NOT_FOUND\", \"Not found\");\n return;\n }\n const returnTo = queryOf(req, \"returnTo\");\n const origin =\n returnTo === null ? (settings.origins.fallback ?? null) : settings.origins.allowed(returnTo);\n if (origin === null) {\n refuse(res, \"VALIDATION\", \"returnTo is not one of the allowed origins\");\n return;\n }\n const state = newState();\n const location = flow.authorizeUrl(state, redirectUriOf(settings, flow.id));\n const pending = encodePending({ state, provider: flow.id, origin, issuedAt: Date.now() });\n res.cookie(stateCookieName(settings, req), pending, stateCookieOf(settings, req, true));\n redirect(res, location);\n };\n}\n\n/** The user `onLogin` names for the callback's code, or why there is none. */\ntype SignInUser = { readonly userId: string } | { readonly error: \"denied\" | \"failed\" };\n\nasync function userFor(\n settings: RouteSettings,\n flow: SignInFlow,\n code: string,\n): Promise<SignInUser> {\n let profile: AuthProfile;\n try {\n profile = await flow.profile(code, redirectUriOf(settings, flow.id));\n } catch (error) {\n settings.logger.warn(\"A sign-in's code exchange failed\", {\n category: CATEGORY,\n provider: flow.id,\n error: messageOf(error),\n });\n return { error: \"failed\" };\n }\n try {\n const userId: unknown = await settings.onLogin(profile, flow.id);\n if (typeof userId === \"string\" && userId !== \"\") {\n return { userId };\n }\n settings.logger.info(\"onLogin refused a sign-in\", { category: CATEGORY, provider: flow.id });\n return { error: \"denied\" };\n } catch (error) {\n settings.logger.error(\"onLogin failed\", {\n category: CATEGORY,\n provider: flow.id,\n error: messageOf(error),\n });\n return { error: \"failed\" };\n }\n}\n\n/** Signs the callback's user in and lands on `origin`, or lands there with the error. */\nasync function complete(\n settings: RouteSettings,\n flow: SignInFlow,\n req: AuthRouteRequest,\n res: AuthRouteResponse,\n origin: string,\n): Promise<void> {\n const code = queryOf(req, \"code\");\n if (queryOf(req, \"error\") !== null || code === null || code === \"\") {\n redirect(res, landingOf(settings, origin, \"denied\"));\n return;\n }\n const user = await userFor(settings, flow, code);\n if (\"error\" in user) {\n redirect(res, landingOf(settings, origin, user.error));\n return;\n }\n const { userId } = user;\n let token: string;\n try {\n ({ token } = await issueFor(settings, userId, flow.id, req));\n } catch (error) {\n settings.logger.error(\"Creating a session failed\", {\n category: CATEGORY,\n error: messageOf(error),\n });\n redirect(res, landingOf(settings, origin, \"failed\"));\n return;\n }\n // The callback is a navigation with no Origin of its own: name the cookie for the return page.\n setSession(res, settings, req, token, origin);\n settings.logger.info(\"Signed in\", { category: CATEGORY, provider: flow.id, userId });\n redirect(res, landingOf(settings, origin));\n}\n\n/** `GET {basePath}/{provider}/callback`. */\nexport function callbackRoute(settings: RouteSettings, flow: SignInFlow): Handler {\n return async (req, res) => {\n if (flow.enabled?.() === false) {\n refuse(res, \"NOT_FOUND\", \"Not found\");\n return;\n }\n const name = stateCookieName(settings, req);\n // A repeated name reads as \"\": no state, since either could be a planted one.\n const pending = decodePending(cookiesOf(req)[name]);\n // Single use: the state cookie goes whatever happens next.\n res.clearCookie(name, stateCookieOf(settings, req));\n if (pending === null) {\n // Started in another browser, or more than ten minutes ago: no origin was remembered.\n const { fallback } = settings.origins;\n if (fallback === undefined) {\n refuse(res, \"FORBIDDEN\", \"This sign-in has no state in this browser; start it again\");\n } else {\n redirect(res, landingOf(settings, fallback, \"state\"));\n }\n return;\n }\n // Validated when it was stored, and again before anything redirects to it.\n const origin = settings.origins.allowed(pending.origin);\n if (origin === null) {\n refuse(res, \"VALIDATION\", \"This sign-in's return origin is not allowed\");\n return;\n }\n const now = Date.now();\n const valid = stateMatches(pending, flow.id, queryOf(req, \"state\"), now);\n if (!valid || !settings.redeemed.redeem(pending.state, now)) {\n settings.logger.debug(\"Refused a sign-in callback's state\", {\n category: CATEGORY,\n provider: flow.id,\n });\n redirect(res, landingOf(settings, origin, \"state\"));\n return;\n }\n await complete(settings, flow, req, res, origin);\n };\n}\n","// The session routes of the auth kit (RFC 0003 section 12.6). Each reads the\n// session the request carries the way the HTTP transport does: the session\n// cookie, under the names the shared rule gives the request\n// (`sessionCookieNamesFor`), else an `Authorization: Bearer` token.\n//\n// - `GET {basePath}/me`: `{ userId }` for a live session, else 401. The\n// answer is the same whether the request had no credential, a forged or\n// expired one, or one whose session was revoked.\n// - `POST {basePath}/logout`: revokes the request's session, if it names\n// one, and clears the cookie; 204 either way.\n// - `POST {basePath}/logout-all`: needs a live session; revokes every session\n// of its user and clears the cookie (204), else 401.\n//\n// Each revocation is told to the app's `onRevoke(userId, sessionId | null)`,\n// which can end the sockets still open with the session\n// (`server.access.disconnectUser`).\n\nimport { verifyJWT } from \"../jwt\";\nimport { tokenOf } from \"../../transports/body\";\nimport {\n noContent,\n refuse,\n sendJson,\n type AuthRouteRequest,\n type AuthRouteResponse,\n} from \"./respond\";\nimport type { AuthSession } from \"./sessions\";\nimport { clearSession, sessionCookieNaming, type RouteSettings } from \"./settings\";\nimport { liveSession } from \"./tokens\";\n\ntype Handler = (req: AuthRouteRequest, res: AuthRouteResponse) => Promise<void>;\n\nconst NOT_SIGNED_IN = \"Not signed in\";\n\n/** The live session the request carries, or `null`. */\nfunction sessionOf(settings: RouteSettings, req: AuthRouteRequest): Promise<AuthSession | null> {\n const token = tokenOf(req, sessionCookieNaming(settings));\n return token === null ? Promise.resolve(null) : liveSession(settings.keys, token);\n}\n\n/** `GET {basePath}/me`. */\nexport function meRoute(settings: RouteSettings): Handler {\n return async (req, res) => {\n const session = await sessionOf(settings, req);\n if (session === null) {\n refuse(res, \"UNAUTHENTICATED\", NOT_SIGNED_IN);\n return;\n }\n sendJson(res, 200, { userId: session.userId });\n };\n}\n\n/** Tells the app's `onRevoke` a session (or, `null`, every one) of `userId` ended; a failure is logged. */\nasync function revoked(\n settings: RouteSettings,\n userId: string,\n sessionId: string | null,\n): Promise<void> {\n try {\n await settings.onRevoke?.(userId, sessionId);\n } catch (error) {\n settings.logger.error(\"onRevoke failed\", {\n category: \"quickdraw.auth\",\n userId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\n/** `POST {basePath}/logout`. */\nexport function logoutRoute(settings: RouteSettings): Handler {\n return async (req, res) => {\n const token = tokenOf(req, sessionCookieNaming(settings));\n const payload = token === null ? null : await verifyJWT(token, settings.keys.jwtSecret);\n if (payload?.sid !== undefined) {\n await settings.keys.sessions.revoke(payload.sid);\n settings.logger.info(\"Signed out\", { category: \"quickdraw.auth\", userId: payload.userId });\n await revoked(settings, payload.userId, payload.sid);\n }\n clearSession(res, settings, req);\n noContent(res);\n };\n}\n\n/** `POST {basePath}/logout-all`. */\nexport function logoutAllRoute(settings: RouteSettings): Handler {\n return async (req, res) => {\n const session = await sessionOf(settings, req);\n clearSession(res, settings, req);\n if (session === null) {\n refuse(res, \"UNAUTHENTICATED\", NOT_SIGNED_IN);\n return;\n }\n await settings.keys.sessions.revokeAll(session.userId);\n settings.logger.info(\"Signed out everywhere\", {\n category: \"quickdraw.auth\",\n userId: session.userId,\n });\n await revoked(settings, session.userId, null);\n noContent(res);\n };\n}\n","// `createAuthRoutes` (RFC 0003 section 12.6): the sign-in surface every app\n// used to copy, as one Express middleware the app mounts. It assembles the\n// 4.1 helpers (OAuth URL and code exchange, the Google, Discord and mock\n// providers, JWTs, the session cookie, origin validation and the Express rate\n// limits); the app supplies how a profile becomes its user (`onLogin`) and\n// where sessions are stored (`SessionStore`). `socketAuth` authenticates the\n// sessions it issues on the server's sockets and HTTP calls.\n//\n// The routes, under `basePath` (default `/auth`):\n//\n// | Route | Limiter | Answer |\n// |---|---|---|\n// | `GET /{provider}/start?returnTo=` | sign-in | 302 to the provider |\n// | `GET /{provider}/callback` | sign-in | 302 to the return origin, with the session cookie |\n// | `POST /guest` (with `guest()`) | sign-in | `{ userId }`, with the session cookie |\n// | `GET /providers` | providers | `{ providers: [{ id, name, kind }] }`: the sign-ins served now |\n// | `GET /me` | session | `{ userId }`, or 401 |\n// | `POST /logout` | session | 204 |\n// | `POST /logout-all` | session | 204, or 401 |\n// | `/mock/provider/*` (with `mock()`, development only) | none | the mock provider |\n//\n// A POST route needs `Content-Type: application/json`, like the HTTP\n// transport's calls: a cross-site form cannot send it, and a cross-site\n// script cannot without a CORS preflight the app's CORS policy refuses.\n//\n// `GET /providers` (and `routes.providers()`) says which sign-ins are\n// served, so a login page offers only those (finding F9.1 of the owner's QA\n// of the template: a Google button answered 404 where the instance had no\n// Google credentials): a provider `google.optional` built nothing for is not\n// in it, and the mock only while it is mounted and `isMockOAuthEnabled()`.\n\nimport { INTERNAL_MESSAGE } from \"../../../protocol/errors\";\nimport { isJsonRequest } from \"../../transports/body\";\nimport { guestRoute } from \"./guest\";\nimport { checkRateLimits, loadLimiters, type LimitKind, type Limiters } from \"./limits\";\nimport { mockFlow, mockServes } from \"./mockFlow\";\nimport { callbackRoute, startRoute, type SignInFlow } from \"./oauth\";\nimport { pathOf, refuse, sendJson, type AuthRouteRequest, type AuthRouteResponse } from \"./respond\";\nimport { logoutAllRoute, logoutRoute, meRoute } from \"./session\";\nimport { routeSettings, type RouteSettings } from \"./settings\";\nimport type { AuthProvider, AuthProviderInfo, AuthRoutes, AuthRoutesOptions } from \"./types\";\n\n/** One route: its handler, the limiter that counts it, and whether it needs a JSON body type. */\ninterface Route {\n readonly handle: (req: AuthRouteRequest, res: AuthRouteResponse) => unknown;\n readonly limit?: LimitKind;\n readonly json?: boolean;\n}\n\n/** The routes by `\"{METHOD} {path}\"`. */\ntype RouteTable = Map<string, Route>;\n\nconst PROVIDER_ID = /^[a-z0-9][a-z0-9-]*$/;\n\nfunction checkProvider(provider: AuthProvider, seen: Set<string>): void {\n const { kind, id, authorizeUrl, profile } = (provider ?? {}) as Partial<\n Record<\"kind\" | \"id\" | \"authorizeUrl\" | \"profile\", unknown>\n >;\n const oauth = typeof authorizeUrl === \"function\" && typeof profile === \"function\";\n if (!(kind === \"mock\" || kind === \"guest\" || (kind === \"oauth\" && oauth))) {\n throw new TypeError(\n 'createAuthRoutes: providers are google(), discord(), mock(), guest() or { kind: \"oauth\", id, authorizeUrl, profile }',\n );\n }\n if (typeof id !== \"string\" || !PROVIDER_ID.test(id)) {\n throw new TypeError(\n `createAuthRoutes: a provider id is lowercase letters, digits and hyphens; got ${String(id)}`,\n );\n }\n if (seen.has(id)) {\n throw new TypeError(`createAuthRoutes: two providers have the id \"${id}\"`);\n }\n seen.add(id);\n}\n\n/** The names a sign-in button shows by default, by provider kind. */\nconst DEFAULT_NAMES = Object.freeze({ mock: \"Mock\", guest: \"Guest\" });\n\n/** A served provider, as `GET /providers` lists it. */\nfunction infoOf(provider: AuthProvider): AuthProviderInfo {\n const name =\n provider.kind === \"oauth\" ? (provider.name ?? provider.id) : DEFAULT_NAMES[provider.kind];\n return Object.freeze({ id: provider.id, name, kind: provider.kind });\n}\n\n/** Adds a provider's routes; the mock provider's only while it is enabled. Returns whether it was mounted. */\nfunction addProvider(table: RouteTable, settings: RouteSettings, provider: AuthProvider): boolean {\n const base = settings.basePath;\n if (provider.kind === \"guest\") {\n table.set(`POST ${base}/guest`, {\n handle: guestRoute(settings, provider),\n limit: \"signIn\",\n json: true,\n });\n return true;\n }\n const flow: SignInFlow | null =\n provider.kind === \"mock\"\n ? mockFlow(provider, settings, (method, path, handle) =>\n table.set(`${method} ${path}`, { handle }),\n )\n : provider;\n if (flow === null) {\n return false;\n }\n table.set(`GET ${base}/${flow.id}/start`, {\n handle: startRoute(settings, flow),\n limit: \"signIn\",\n });\n table.set(`GET ${base}/${flow.id}/callback`, {\n handle: callbackRoute(settings, flow),\n limit: \"signIn\",\n });\n return true;\n}\n\n/** The sign-ins served now: the providers mounted, the mock only while it is enabled. */\nfunction servedProviders(mounted: readonly AuthProvider[]): readonly AuthProviderInfo[] {\n return mounted.filter((provider) => provider.kind !== \"mock\" || mockServes()).map(infoOf);\n}\n\nfunction routeTable(\n settings: RouteSettings,\n listed: readonly (AuthProvider | null | undefined | false)[],\n): { readonly table: RouteTable; readonly mounted: readonly AuthProvider[] } {\n // An entry left out in place (`google.optional(...)` without credentials) is skipped.\n const providers = Array.isArray(listed)\n ? listed.filter(\n (provider): provider is AuthProvider =>\n provider !== undefined && provider !== null && provider !== false,\n )\n : [];\n if (providers.length === 0) {\n throw new TypeError(\"createAuthRoutes: providers must list at least one provider\");\n }\n const seen = new Set<string>();\n for (const provider of providers) {\n checkProvider(provider, seen);\n }\n const table: RouteTable = new Map();\n const mounted = providers.filter((provider) => addProvider(table, settings, provider));\n if (mounted.length === 0) {\n settings.logger.warn(\n \"createAuthRoutes: no provider can sign anyone in (the mock is mounted only while ENABLE_MOCK_OAUTH is set outside production): every sign-in route answers 404\",\n { category: \"quickdraw.auth\" },\n );\n }\n const base = settings.basePath;\n table.set(`GET ${base}/providers`, {\n handle: (_req, res) => {\n sendJson(res, 200, { providers: servedProviders(mounted) });\n },\n limit: \"providers\",\n });\n table.set(`GET ${base}/me`, { handle: meRoute(settings), limit: \"session\" });\n table.set(`POST ${base}/logout`, { handle: logoutRoute(settings), limit: \"session\", json: true });\n table.set(`POST ${base}/logout-all`, {\n handle: logoutAllRoute(settings),\n limit: \"session\",\n json: true,\n });\n return { table, mounted };\n}\n\n/** Runs a route once its limiter let it through. */\nasync function serve(route: Route, req: AuthRouteRequest, res: AuthRouteResponse): Promise<void> {\n if (route.json === true && !isJsonRequest(req)) {\n refuse(res, \"VALIDATION\", \"Send the request with Content-Type: application/json\");\n return;\n }\n await route.handle(req, res);\n}\n\nfunction dispatch(\n settings: RouteSettings,\n limiters: Limiters | null,\n route: Route,\n req: AuthRouteRequest,\n res: AuthRouteResponse,\n): void {\n const fail = (error: unknown): void => {\n settings.logger.error(\"An auth route failed\", {\n category: \"quickdraw.auth\",\n path: pathOf(req),\n error: error instanceof Error ? error.message : String(error),\n });\n refuse(res, \"INTERNAL\", INTERNAL_MESSAGE);\n };\n if (limiters === null) {\n refuse(res, \"INTERNAL\", \"The auth routes' rate limiters are unavailable\");\n return;\n }\n const limiter = route.limit === undefined ? undefined : limiters[route.limit];\n if (limiter === undefined) {\n serve(route, req, res).catch(fail);\n return;\n }\n // The limiter answers a refused request itself; it calls `next` to let one through.\n const next = (error?: unknown): void => {\n if (error === undefined) {\n serve(route, req, res).catch(fail);\n } else {\n fail(error);\n }\n };\n try {\n Promise.resolve(limiter(req as never, res as never, next)).catch(fail);\n } catch (error) {\n fail(error);\n }\n}\n\n/**\n * The auth routes, as one Express middleware: `app.use(createAuthRoutes(...))`.\n * Mount it after the app's CORS middleware (which must allow credentials for\n * the web app's origins) and before its catch-all routes.\n *\n * @example\n * app.use(\n * createAuthRoutes({\n * providers: [google({ clientId, clientSecret }), mock({ listUsers }), guest({ createUser })],\n * sessions: prismaSessions(prisma),\n * jwtSecret: env.JWT_SECRET,\n * onLogin: (profile, provider) => upsertUser(profile, provider),\n * allowedOrigins: [env.CLIENT_URL],\n * publicUrl: env.API_URL,\n * }),\n * );\n */\nexport function createAuthRoutes(options: AuthRoutesOptions): AuthRoutes {\n const settings = routeSettings(options);\n const rateLimit = checkRateLimits(options.rateLimit);\n const { table, mounted } = routeTable(settings, options.providers);\n const limiters = loadLimiters(rateLimit).catch((error: unknown) => {\n settings.logger.error(\n \"createAuthRoutes: the default rate limiters need express-rate-limit; install it, or pass rateLimit\",\n { category: \"quickdraw.auth\", error: error instanceof Error ? error.message : String(error) },\n );\n return null;\n });\n const routes = (\n req: AuthRouteRequest,\n res: AuthRouteResponse,\n next: (error?: unknown) => void,\n ) => {\n const route = table.get(`${req.method ?? \"\"} ${pathOf(req)}`);\n if (route === undefined) {\n next();\n return;\n }\n settings.watchHost(req);\n void limiters.then((loaded) => {\n dispatch(settings, loaded, route, req, res);\n });\n };\n return Object.assign(routes, { providers: () => servedProviders(mounted) });\n}\n","// `socketAuth` (RFC 0003 section 12.6): the `authenticate` function that\n// `createServer({ auth: { authenticate } })` uses for the sessions the auth\n// routes issue, on sockets and HTTP calls alike.\n//\n// A socket's credential is its handshake's `auth.token` (a bearer token, for\n// native and other cookie-less clients), else the session cookie from the\n// handshake's headers. An HTTP call's is the one the HTTP transport found,\n// the session cookie else the bearer token. No credential: anonymous. A\n// credential that does not stand for a live session is refused with\n// `UNAUTHENTICATED`, checked against the session store on every handshake and\n// call, so a revoked session fails even while its JWT has not expired. A\n// socket's session is recorded (`recordSocketSession`), so the app can end\n// that session's open sockets when it revokes it: wire `createAuthRoutes`'\n// `onRevoke` to `server.access.disconnectUser`.\n//\n// The session cookie is ambient: a browser sends it with a WebSocket\n// handshake that any page opens, and WebSockets are not subject to CORS. So a\n// socket that authenticates with the cookie must come from an allowed page:\n// its `Origin` header must be in `allowedOrigins`. A handshake with no\n// `Origin` is refused too, except a same-origin one (`Sec-Fetch-Site:\n// same-origin`, which a browser sends on its own page's long-polling requests\n// and which no other page can forge), or with `allowMissingOrigin` for native\n// clients that keep cookies. A bearer token is not ambient and needs no\n// Origin.\n//\n// An HTTP call that sends the cookie gets the same check (finding F7.1 of the\n// quickdraw-chat review; before rc.5 it relied on its required JSON content\n// type and the app's CORS alone): an `Origin` outside `allowedOrigins` is\n// answered `FORBIDDEN`. A call without `Origin` is accepted, since a browser\n// sends one with every POST: it comes from curl or a server rendering a page\n// with the user's cookie, unless `Sec-Fetch-Site` says another site sent it.\n//\n// The cookie is read under the names the auth routes' rule gives the\n// handshake (`sessionCookieNamesFor`): a configured `cookieName`; else\n// `session` when `COOKIE_DOMAIN` gives the cookie a domain; else, over HTTPS,\n// only `__Host-session` (a site under the same parent domain can plant a\n// plain `session` cookie, and a secure handshake must not take it in place of\n// the host-only one), and over plain HTTP `session`, then `__Host-session`.\n//\n// `devCredentials` (finding F2.13 of the quickdraw-chat migration) signs a\n// socket in by the user id its handshake names (`auth: { userId }`), for a\n// game editor or load-test bots in development. It cannot be used in\n// production: `socketAuth` throws when it is given and `NODE_ENV` is\n// `\"production\"`, and a handshake is refused it there whatever the\n// environment says later.\n\nimport type { IncomingHttpHeaders } from \"node:http\";\nimport { QuickdrawError } from \"../../../protocol/errors\";\nimport {\n recordSocketSession,\n type AuthenticateRequest,\n type SocketAuthenticateRequest,\n} from \"../../transports/auth\";\nimport { cookiesOf, cookieToken, transportCookieNaming } from \"../../transports/body\";\nimport type { MaybePromise, Principal } from \"../../types\";\nimport {\n sessionCookieNamesFor,\n type SessionCookieNaming,\n type SessionCookieRequest,\n} from \"../sessionCookie\";\nimport { originAllowlist, type AllowedOrigin, type OriginAllowlist } from \"./origins\";\nimport type { AuthSession, SessionStore } from \"./sessions\";\nimport { checkSessionKeys, liveSession } from \"./tokens\";\n\n/** Builds the principal of a session's user; `null` refuses the connection. */\nexport type PrincipalLoader<P extends Principal> = (\n userId: string,\n session: AuthSession,\n) => MaybePromise<P | null | undefined>;\n\n/** Options of {@link socketAuth}. */\nexport interface SocketAuthOptions<P extends Principal = Principal> {\n /** The store the auth routes write sessions to. */\n readonly sessions: SessionStore;\n /** The secret the auth routes sign session JWTs with. */\n readonly jwtSecret: string;\n /**\n * The web app's origins, the same list as `createAuthRoutes`' `allowedOrigins`:\n * the pages that may use the session cookie, on a socket and (since rc.5)\n * on an HTTP call, which is answered `FORBIDDEN` from any other `Origin`.\n */\n readonly allowedOrigins: readonly AllowedOrigin[];\n /**\n * Accept a cookie-authenticated handshake that has no `Origin` header, for\n * native clients that keep cookies. Default `false`. A handshake whose\n * `Origin` is not allowed is refused either way. HTTP calls do not need\n * it: one without `Origin` is accepted (a browser sends `Origin` with every\n * POST, so it is not a page's), unless `Sec-Fetch-Site` names another site.\n */\n readonly allowMissingOrigin?: boolean;\n /**\n * The session cookie's name. Default: the name the auth routes set on the\n * same handshake: `\"session\"` when `COOKIE_DOMAIN` gives the cookie a\n * domain; else `\"__Host-session\"` over HTTPS, where the plain name a\n * sibling site could plant is never read, and `\"session\"` (then\n * `\"__Host-session\"`) over plain HTTP. A name given here is the only one\n * read, on any handshake: give the routes' `cookie.name` here, or\n * `\"session\"` when their `cookie.domain` is set without `COOKIE_DOMAIN`. A\n * name the handshake repeats counts as no credential.\n */\n readonly cookieName?: string;\n /**\n * Builds the principal, for example with `kind` and the user's\n * `serviceAccess`; its `userId` must be the session's. Default\n * `{ userId, kind: \"user\" }`, whose grants `createServer`'s\n * `auth.loadServiceAccess` then loads.\n */\n readonly loadPrincipal?: PrincipalLoader<P>;\n /**\n * Development sign-in without a session: a socket whose handshake carries\n * `auth: { userId }` and no token is that user, as this function answers:\n * their principal (its `userId` the one named), or `null` to refuse the\n * handshake (an unknown id). For a game editor or load-test bots. Never in\n * production: `socketAuth` throws when it is given while `NODE_ENV` is\n * `\"production\"`, and refuses such a handshake there anyway. HTTP calls\n * never use it. Pass it only when the app's own flag is on:\n * `devCredentials: env.ENABLE_DEV_CREDENTIALS ? findDevUser : undefined`.\n */\n readonly devCredentials?: DevCredentials<P>;\n}\n\n/** `socketAuth`'s `devCredentials`: the principal of the user a development handshake names, or `null`. */\nexport type DevCredentials<P extends Principal> = (\n userId: string,\n) => MaybePromise<P | null | undefined>;\n\n/** What {@link socketAuth} returns: an `authenticate` for `createServer`'s `auth`. */\nexport type SessionAuthenticate<P extends Principal> = (\n request: AuthenticateRequest,\n) => Promise<P | null>;\n\ninterface Credential {\n readonly token: string;\n /** True for the ambient session cookie: a socket's, or an HTTP call's (finding F7.1). */\n readonly checkOrigin: boolean;\n}\n\n/** A socket's handshake, as its session cookie's name depends on it: its headers, and whether TLS ended here. */\nfunction handshakeOf(request: SocketAuthenticateRequest): SessionCookieRequest {\n const { handshake } = request.socket as Partial<\n Pick<SocketAuthenticateRequest[\"socket\"], \"handshake\">\n >;\n return { headers: request.headers, secure: handshake?.secure === true };\n}\n\nfunction credentialOf(\n request: AuthenticateRequest,\n naming: SessionCookieNaming,\n): Credential | null {\n const { token } = request.auth;\n if (typeof token === \"string\" && token !== \"\") {\n // The HTTP transport found it, and says whether it was the session cookie.\n return { token, checkOrigin: request.transport === \"http\" && request.credential === \"cookie\" };\n }\n if (request.transport === \"http\") {\n return null;\n }\n const names = sessionCookieNamesFor(handshakeOf(request), naming);\n const cookie = cookieToken(cookiesOf({ headers: request.headers }), names);\n return cookie === null ? null : { token: cookie, checkOrigin: true };\n}\n\nfunction originAccepted(\n headers: IncomingHttpHeaders,\n origins: OriginAllowlist,\n allowMissing: boolean,\n): boolean {\n const { origin } = headers;\n if (origin !== undefined) {\n return origins.allowed(origin) !== null;\n }\n return allowMissing || headers[\"sec-fetch-site\"] === \"same-origin\";\n}\n\n/**\n * The same rule for an HTTP call that sends the session cookie. An `Origin`\n * must be allowed, as on a socket. Without one it differs: the transport\n * serves only POST, and a browser sends `Origin` with every POST (the Fetch\n * standard; a socket's long-polling handshake is a GET, which a same-origin\n * page or an `<img>` sends without one), so a call without it is not a\n * page's: curl, or a server rendering a page with the user's forwarded\n * cookie (`createServerCaller`). It is accepted, unless the browser's fetch\n * metadata says another site sent it.\n */\nfunction httpOriginAccepted(headers: IncomingHttpHeaders, origins: OriginAllowlist): boolean {\n const { origin } = headers;\n if (origin !== undefined) {\n return origins.allowed(origin) !== null;\n }\n const site = headers[\"sec-fetch-site\"];\n return site === undefined || site === \"same-origin\";\n}\n\n/** The allowlists `cookieOriginAllowed` compiled, by the list it was given. */\nconst compiled = new WeakMap<readonly AllowedOrigin[], OriginAllowlist>();\n\n/** A request whose credential came from the session cookie: its headers, and the transport of a call. */\nexport interface CookieOriginRequest {\n readonly headers: IncomingHttpHeaders;\n /** `\"socket\"` applies the handshake's rule; anything else (an HTTP call, a REST route) the HTTP one. */\n readonly transport?: string;\n}\n\n/**\n * The rule `socketAuth` applies to a request that authenticates with the\n * session cookie, for a custom `authenticate` or route: true when the page\n * that sent it may use the cookie. Its `Origin` must be in `allowedOrigins`\n * (the `createAuthRoutes` list: exact origins or anchored patterns). Without\n * one, an HTTP request (a browser sends `Origin` with every POST, so it is\n * curl or a server forwarding the user's cookie) is accepted unless\n * `Sec-Fetch-Site` names another site, and a socket handshake only when\n * `Sec-Fetch-Site` is `same-origin` or with `allowMissingOrigin`. A bearer\n * token is not ambient: check only a credential that came from the cookie\n * (`request.credential === \"cookie\"` on an HTTP `authenticate` request).\n *\n * @example\n * authenticate: async (request) => {\n * if (request.transport === \"http\" && request.credential === \"cookie\" &&\n * !cookieOriginAllowed(request, allowedOrigins)) {\n * throw new QuickdrawError(\"FORBIDDEN\", \"This page may not use the session cookie\");\n * }\n * // ...\n * }\n */\nexport function cookieOriginAllowed(\n request: CookieOriginRequest,\n allowedOrigins: readonly AllowedOrigin[],\n options: { readonly allowMissingOrigin?: boolean } = {},\n): boolean {\n let origins = compiled.get(allowedOrigins);\n if (origins === undefined) {\n origins = originAllowlist(allowedOrigins, \"cookieOriginAllowed\", true);\n compiled.set(allowedOrigins, origins);\n }\n return request.transport === \"socket\"\n ? originAccepted(request.headers, origins, options.allowMissingOrigin === true)\n : httpOriginAccepted(request.headers, origins);\n}\n\n/** Whether an HTTP request that sends the session cookie may use it, against a compiled allowlist. */\nexport function httpCookieOriginAllowed(\n headers: IncomingHttpHeaders,\n origins: OriginAllowlist,\n): boolean {\n return httpOriginAccepted(headers, origins);\n}\n\n/** Refuses a session cookie used from a page `allowedOrigins` does not list. */\nfunction checkCookieOrigin(\n request: AuthenticateRequest,\n origins: OriginAllowlist,\n allowMissing: boolean,\n): void {\n if (request.transport === \"http\") {\n if (!httpOriginAccepted(request.headers, origins)) {\n // FORBIDDEN, which the HTTP transport answers as it is: the session is fine, the page is not.\n throw new QuickdrawError(\n \"FORBIDDEN\",\n \"This page's origin may not use the session cookie; send it from an allowed origin or use a bearer token\",\n );\n }\n return;\n }\n if (!originAccepted(request.headers, origins, allowMissing)) {\n throw refused(\"This page's origin may not use the session cookie on a socket\");\n }\n}\n\nfunction refused(message: string): QuickdrawError {\n return new QuickdrawError(\"UNAUTHENTICATED\", message);\n}\n\nfunction production(): boolean {\n return process.env.NODE_ENV === \"production\";\n}\n\n/** `devCredentials`, checked: a function, and never in production. */\nfunction checkDevCredentials(\n devCredentials: DevCredentials<Principal> | undefined,\n): DevCredentials<Principal> | undefined {\n if (devCredentials === undefined) {\n return undefined;\n }\n if (typeof devCredentials !== \"function\") {\n throw new TypeError(\"socketAuth: devCredentials must be a function of the user id\");\n }\n if (production()) {\n throw new TypeError(\n \"socketAuth: devCredentials signs sockets in by a user id alone, so it cannot be used when NODE_ENV is production\",\n );\n }\n return devCredentials;\n}\n\n/** The user a development socket handshake names (`auth: { userId }` without a token), or `undefined`. */\nfunction devUserOf(request: AuthenticateRequest): string | undefined {\n const { token, userId } = request.auth;\n const tokenless = token === undefined || token === null || token === \"\";\n return request.transport === \"socket\" && tokenless && typeof userId === \"string\" && userId !== \"\"\n ? userId\n : undefined;\n}\n\n/** The principal of a development handshake's user; refused in production and for an unknown user. */\nasync function devPrincipal(\n devCredentials: DevCredentials<Principal>,\n userId: string,\n): Promise<Principal> {\n if (production()) {\n throw refused(\"Development credentials are refused in production\");\n }\n const principal = await devCredentials(userId);\n if (principal === null || principal === undefined) {\n throw refused(\"No development user has that id\");\n }\n if (principal.userId !== userId) {\n throw new TypeError(\"socketAuth: devCredentials must return the principal of the user named\");\n }\n return principal;\n}\n\n/**\n * The `authenticate` for `createServer` over the auth routes' sessions:\n * `createServer({ auth: { authenticate: socketAuth({ sessions, jwtSecret, allowedOrigins }) } })`.\n */\nexport function socketAuth<P extends Principal>(\n options: SocketAuthOptions<P> & { readonly loadPrincipal: PrincipalLoader<P> },\n): SessionAuthenticate<P>;\nexport function socketAuth(options: SocketAuthOptions): SessionAuthenticate<Principal>;\nexport function socketAuth(options: SocketAuthOptions): SessionAuthenticate<Principal> {\n const keys = checkSessionKeys(options, \"socketAuth\");\n const origins = originAllowlist(options.allowedOrigins, \"socketAuth\", true);\n const naming = transportCookieNaming(options.cookieName);\n const allowMissing = options.allowMissingOrigin === true;\n const { loadPrincipal } = options;\n if (loadPrincipal !== undefined && typeof loadPrincipal !== \"function\") {\n throw new TypeError(\"socketAuth: loadPrincipal must be a function\");\n }\n const devCredentials = checkDevCredentials(options.devCredentials);\n return async (request) => {\n const devUser = devCredentials === undefined ? undefined : devUserOf(request);\n if (devCredentials !== undefined && devUser !== undefined) {\n return await devPrincipal(devCredentials, devUser);\n }\n const credential = credentialOf(request, naming);\n if (credential === null) {\n return null;\n }\n if (credential.checkOrigin) {\n checkCookieOrigin(request, origins, allowMissing);\n }\n const session = await liveSession(keys, credential.token);\n if (session === null) {\n throw refused(\"The session is not valid\");\n }\n if (request.transport === \"socket\") {\n // So `server.access.disconnectUser(userId, { sessionId })` can end this session's sockets.\n recordSocketSession(request.socket, session.id);\n }\n if (loadPrincipal === undefined) {\n return { userId: session.userId, kind: \"user\" };\n }\n const principal = await loadPrincipal(session.userId, session);\n if (principal === null || principal === undefined) {\n throw refused(\"The session's user has no principal\");\n }\n if (principal.userId !== session.userId) {\n throw new TypeError(\n \"socketAuth: loadPrincipal must return the principal of the session's user\",\n );\n }\n return principal;\n };\n}\n","// `requireSession(keys)` (finding F2.11 of the quickdraw-chat migration): an\n// Express middleware for the app's own REST routes over the auth routes\n// kit's sessions. It reads the request's credential the way the HTTP\n// transport and `GET /auth/me` do (the session cookie under the shared\n// naming rule, else an `Authorization: Bearer` token), verifies the JWT once\n// and asks the session store whether the session it names is still live, so\n// a signed-out session stops authenticating REST calls at once. 4.1's\n// `createRequireAuth` stays for token-keyed sessions; over the kit's\n// sessions it verified the JWT twice (its own check, then `liveSession`).\n//\n// It also builds the request's principal as `socketAuth` builds a socket's\n// (`loadPrincipal`, default `{ userId, kind: \"user\" }`), and `sessionOf(req)`\n// hands the route its user, session and principal, typed, without a cast of\n// `req`, whose Express type has no `userId` (finding F5.4). The route then\n// calls the services in process as that principal: `qd.caller(principal)`\n// loads its service-wide grants as a socket's handshake does (F5.1).\n//\n// The session cookie is ambient, so a request that authenticates with it\n// gets the Origin rule `/qd` calls get from `socketAuth` (finding F8.5 of the\n// quickdraw-chat migration, and the final review of the release candidates:\n// a cross-site form POST reached a route as the user): an `Origin` outside\n// `allowedOrigins` is answered 403 `FORBIDDEN`, and a request without one\n// (curl, a server forwarding the cookie) is accepted unless `Sec-Fetch-Site`\n// names another site. The list defaults to the one the `createAuthRoutes`\n// writing to the same session store object was given (the object, not its\n// table: a second store over the same table is another object); without\n// either, no page may use the cookie here, and the first cookie refused for\n// want of a list logs an error naming both fixes, which a page in\n// development is told too (finding F11.4 of the quickdraw-chat migration).\n// A bearer token needs no Origin.\n\nimport type { ServerResponse } from \"node:http\";\nimport { consoleLogger, type Logger } from \"../../../contract/logger\";\nimport { httpCredentialOf, transportCookieNaming, type HttpRequest } from \"../../transports/body\";\nimport type { Principal } from \"../../types\";\nimport { originAllowlist, routeOriginsOf, type AllowedOrigin } from \"./origins\";\nimport { refuse } from \"./respond\";\nimport { httpCookieOriginAllowed, type PrincipalLoader } from \"./socketAuth\";\nimport { checkSessionKeys, liveSession, type SessionKeys } from \"./tokens\";\n\n/** Options of {@link requireSession}. */\nexport interface RequireSessionOptions<P extends Principal = Principal> {\n /**\n * The session cookie's name, when the app gave the routes one\n * (`createAuthRoutes({ cookie: { name } })`). Default: the name the routes\n * set on the same request, by the shared rule (`__Host-session` over\n * HTTPS, `session` over plain HTTP or with `COOKIE_DOMAIN`).\n */\n readonly cookieName?: string;\n /**\n * Builds the principal of the session's user, as `socketAuth`'s option of\n * the same name builds a socket's: give both the same function, so a REST\n * route calls the services as the user's sockets do. Its `userId` must be\n * the session's; `null` answers 401. Default `{ userId, kind: \"user\" }`.\n */\n readonly loadPrincipal?: PrincipalLoader<P>;\n /**\n * The pages that may use the session cookie on these routes, as\n * `createAuthRoutes` and `socketAuth` take them: a request with the cookie\n * and an `Origin` outside the list is answered 403 `FORBIDDEN`. Default:\n * the list of the `createAuthRoutes` that writes to the same session store\n * object (`keys.sessions` must be the object the routes were given: a\n * second store over the same table is another object), else none, so only\n * a request without `Origin` (curl, a server forwarding the cookie) uses\n * the cookie, and the first one refused for want of a list is logged. A\n * bearer token needs no Origin.\n */\n readonly allowedOrigins?: readonly AllowedOrigin[];\n /**\n * Where a session cookie refused for want of any origin list (no\n * `allowedOrigins`, and no `createAuthRoutes` over the same session store\n * object) is logged, once, as an error naming both fixes. Default: the\n * console.\n */\n readonly logger?: Logger;\n}\n\n/** What {@link sessionOf} answers for a request `requireSession` let through. */\nexport interface RequestSession<P extends Principal = Principal> {\n /** The signed-in user. */\n readonly userId: string;\n /** The session the credential named: `server.access.disconnectUser(userId, { sessionId })` ends its sockets. */\n readonly sessionId: string;\n /**\n * The user's principal, to call the services as: `qd.caller(principal)`,\n * which loads its service-wide grants as a socket's handshake does.\n */\n readonly principal: P;\n}\n\n/** A request `requireSession` let through: its user, its session and its principal. */\nexport type SessionRequest<P extends Principal = Principal> = HttpRequest & {\n /** The signed-in user. */\n userId?: string;\n /** The session the credential named. */\n sessionId?: string;\n /** The user's principal. */\n principal?: P;\n};\n\n/** What {@link requireSession} returns: an Express (4 or 5) middleware. */\nexport type SessionMiddleware<P extends Principal = Principal> = (\n req: SessionRequest<P>,\n res: ServerResponse,\n next: (error?: unknown) => void,\n) => void;\n\n/** The sessions `requireSession` let requests through with, by request. */\nconst SESSIONS = new WeakMap<object, RequestSession>();\n\n/** No page may use the cookie: what applies without `allowedOrigins` and without the routes' list. */\nconst NO_ORIGINS = originAllowlist([], \"requireSession\", true);\n\n/** What a page whose Origin may not use the session cookie is told. */\nconst COOKIE_REFUSED =\n \"This page's origin may not use the session cookie; send it from an allowed origin or use a bearer token\";\n\n/**\n * Why the cookie is refused when there is no list at all (finding F11.4):\n * logged once, and what a page in development is told.\n */\nconst NO_LIST =\n \"requireSession has no origin list, so no page may use the session cookie: pass it the sessions object createAuthRoutes was given (the same store object: a second store over the same table is another), or pass allowedOrigins\";\n\n/**\n * A middleware that lets a request through only with a live session of the\n * auth routes kit, keeping its user, session and principal for\n * {@link sessionOf} (and setting `req.userId`, `req.sessionId` and\n * `req.principal`); anything else answers 401\n * `{ error: \"UNAUTHENTICATED\", message }`, the routes' own failure shape, and\n * the session cookie sent from a page `allowedOrigins` does not list answers\n * 403 `{ error: \"FORBIDDEN\", message }`. A failing session store or\n * `loadPrincipal` is passed to `next(error)`.\n *\n * @example\n * app.post(\"/api/push/resubscribe\", express.json(), requireSession(keys), (req, res) => {\n * const { principal } = sessionOf(req);\n * // await qd.caller(principal).pushService.subscribePush(req.body), then answer\n * });\n */\nexport function requireSession<P extends Principal>(\n keys: SessionKeys,\n options: RequireSessionOptions<P> & { readonly loadPrincipal: PrincipalLoader<P> },\n): SessionMiddleware<P>;\nexport function requireSession(\n keys: SessionKeys,\n options?: RequireSessionOptions,\n): SessionMiddleware;\nexport function requireSession(\n keys: SessionKeys,\n options: RequireSessionOptions = {},\n): SessionMiddleware {\n checkSessionKeys(keys, \"requireSession\");\n const naming = transportCookieNaming(options.cookieName);\n const { loadPrincipal } = options;\n if (loadPrincipal !== undefined && typeof loadPrincipal !== \"function\") {\n throw new TypeError(\"requireSession: loadPrincipal must be a function\");\n }\n const given =\n options.allowedOrigins === undefined\n ? undefined\n : originAllowlist(options.allowedOrigins, \"requireSession\", true);\n const logger = options.logger ?? consoleLogger;\n let toldNoList = false;\n return (req, res, next) => {\n const check = async (): Promise<void> => {\n const credential = httpCredentialOf(req, naming);\n if (credential?.from === \"cookie\") {\n // Read now: the routes may be made after this middleware.\n const routes = given === undefined ? routeOriginsOf(keys.sessions) : undefined;\n if (!httpCookieOriginAllowed(req.headers, given ?? routes ?? NO_ORIGINS)) {\n const noList = given === undefined && routes === undefined;\n if (noList && !toldNoList) {\n toldNoList = true;\n logger.error(NO_LIST, { category: \"quickdraw.auth\", origin: req.headers.origin });\n }\n const development = process.env.NODE_ENV !== \"production\";\n refuse(res, \"FORBIDDEN\", noList && development ? NO_LIST : COOKIE_REFUSED);\n return;\n }\n }\n const token = credential?.token ?? null;\n const session = token === null ? null : await liveSession(keys, token);\n if (session === null) {\n refuse(res, \"UNAUTHENTICATED\", \"Not signed in\");\n return;\n }\n const principal: Principal | null | undefined =\n loadPrincipal === undefined\n ? { userId: session.userId, kind: \"user\" }\n : await loadPrincipal(session.userId, session);\n if (principal === null || principal === undefined) {\n refuse(res, \"UNAUTHENTICATED\", \"The session's user has no principal\");\n return;\n }\n if (principal.userId !== session.userId) {\n throw new TypeError(\n \"requireSession: loadPrincipal must return the principal of the session's user\",\n );\n }\n SESSIONS.set(\n req,\n Object.freeze({ userId: session.userId, sessionId: session.id, principal }),\n );\n req.userId = session.userId;\n req.sessionId = session.id;\n req.principal = principal;\n next();\n };\n check().catch((error: unknown) => {\n next(error);\n });\n };\n}\n\n/**\n * The user, session and principal `requireSession` let `req` through with,\n * typed: `P` is the type its `loadPrincipal` returns\n * (`sessionOf<AppPrincipal>(req)`). Throws a `TypeError` for a request no\n * `requireSession` let through, a route mounted without it.\n *\n * @example\n * const { principal } = sessionOf(req);\n * res.json(await qd.caller(principal).taskService.get({ id: req.params.id }));\n */\nexport function sessionOf<P extends Principal = Principal>(req: object): RequestSession<P> {\n const session = SESSIONS.get(req);\n if (session === undefined) {\n throw new TypeError(\n \"sessionOf: no requireSession let this request through; mount requireSession(keys) before the route\",\n );\n }\n return session as RequestSession<P>;\n}\n","// Where signed-in sessions live (RFC 0003 section 12.6). The app owns the\n// store: `createAuthRoutes` creates a session at each sign-in and revokes it\n// at logout, and `socketAuth` reads it on every handshake, so a revoked\n// session stops authenticating at once, even while its JWT has not expired.\n// `createMemorySessionStore` is for development and tests; an app keeps its\n// sessions in its database (the README has a Prisma store).\n\nimport { randomUUID } from \"node:crypto\";\nimport type { MaybePromise } from \"../../types\";\n\n/** A session as the store keeps it. A store's own rows may carry more fields. */\nexport interface AuthSession {\n /** The session's id. The session's JWT names it in its `sid` claim. */\n readonly id: string;\n /** The user the session signs in. */\n readonly userId: string;\n /** When the session ends. The kit refuses it afterwards, whatever the store answers. */\n readonly expiresAt: Date;\n}\n\n/** What the kit tells the store about a session it asks it to create. */\nexport interface SessionMeta {\n /** How the user signed in: a provider's id (`\"google\"`, `\"discord\"`, `\"mock\"`) or `\"guest\"`. */\n readonly provider: string;\n /** When the session ends. Its JWT and its cookie end then too. */\n readonly expiresAt: Date;\n /** The sign-in request's `User-Agent`, for a list of where the user is signed in. */\n readonly userAgent?: string;\n /** The sign-in request's IP address as Express reports it (`req.ip`), for the same. */\n readonly ip?: string;\n}\n\n/**\n * The app's session storage. `get` answers `null` (or `undefined`) for a\n * session that does not exist or was revoked; after `revoke(id)` or\n * `revokeAll(userId)`, `get` must answer `null` for those sessions. What\n * `revoke` and `revokeAll` return is ignored.\n */\nexport interface SessionStore {\n /** Stores a new session for `userId` and returns it, with the id the store gave it. */\n create(userId: string, meta: SessionMeta): MaybePromise<AuthSession>;\n get(sessionId: string): MaybePromise<AuthSession | null | undefined>;\n revoke(sessionId: string): MaybePromise<unknown>;\n revokeAll(userId: string): MaybePromise<unknown>;\n}\n\n/** The in-memory store: a synchronous {@link SessionStore}, plus a count for tests. */\nexport interface MemorySessionStore extends SessionStore {\n create(userId: string, meta: SessionMeta): AuthSession;\n get(sessionId: string): AuthSession | null;\n revoke(sessionId: string): void;\n revokeAll(userId: string): void;\n /** How many sessions it holds, expired ones included until the next `create` drops them. */\n readonly size: number;\n}\n\n/**\n * A {@link SessionStore} in this process's memory, for development and\n * tests. Its sessions are lost on restart and are not shared between\n * processes, so production apps store them in their database.\n */\nexport function createMemorySessionStore(): MemorySessionStore {\n const sessions = new Map<string, AuthSession>();\n const dropExpired = (now: number): void => {\n for (const [id, session] of sessions) {\n if (session.expiresAt.getTime() <= now) {\n sessions.delete(id);\n }\n }\n };\n return {\n create(userId, meta) {\n dropExpired(Date.now());\n const session = Object.freeze({ id: randomUUID(), userId, expiresAt: meta.expiresAt });\n sessions.set(session.id, session);\n return session;\n },\n get: (sessionId) => sessions.get(sessionId) ?? null,\n revoke(sessionId) {\n sessions.delete(sessionId);\n },\n revokeAll(userId) {\n for (const [id, session] of sessions) {\n if (session.userId === userId) {\n sessions.delete(id);\n }\n }\n },\n get size() {\n return sessions.size;\n },\n };\n}\n","// The sign-in providers of the auth routes kit (RFC 0003 section 12.6): the\n// 4.1 Google and Discord providers and the development mock, each wrapped\n// into one shape, `{ id, authorizeUrl(state, redirectUri), profile(code,\n// redirectUri) }`, that the routes drive the same way. An app adds another\n// OAuth provider by writing that shape itself. The mock provider is served by\n// the routes themselves, and only while `isMockOAuthEnabled()`; the guest\n// provider is in `guest.ts`.\n\nimport { discordProvider, getDiscordAvatarUrl } from \"../discord\";\nimport { googleProvider } from \"../google\";\nimport type { MockOAuthUser } from \"../mock\";\nimport {\n createOAuthURL,\n exchangeOAuthCode,\n type OAuthConfig,\n type OAuthProvider,\n type OAuthTokenResponse,\n} from \"../oauth\";\n\n/** Who signed in, as every provider reports it to `onLogin`. */\nexport interface AuthProfile {\n /** The provider's id for the user: Google's and Discord's user id, the mock user's email. */\n readonly providerAccountId: string;\n readonly email: string | null;\n /** Whether the provider verified `email`. Link accounts by email only when it did. */\n readonly emailVerified: boolean;\n readonly name: string | null;\n /** An avatar URL. */\n readonly image: string | null;\n /** The provider's tokens, for an app that calls the provider's API later; encrypt them at rest. */\n readonly tokens: OAuthTokenResponse;\n /** The provider's user info as the 4.1 helper parsed it (`GoogleUser`, `DiscordUser`). */\n readonly raw: unknown;\n}\n\n/** An OAuth 2.0 authorization-code provider the routes can drive. */\nexport interface OAuthSignInProvider {\n readonly kind: \"oauth\";\n /** The provider's id: `{basePath}/{id}/start` and `{basePath}/{id}/callback`. */\n readonly id: string;\n /** Its name on a sign-in button (`GET {basePath}/providers`). Default: its id. */\n readonly name?: string;\n /** The provider's authorization URL for a sign-in with this `state`. */\n authorizeUrl(state: string, redirectUri: string): string;\n /** Exchanges the callback's `code` and reads the user's profile. */\n profile(code: string, redirectUri: string): Promise<AuthProfile>;\n}\n\n/** Client credentials and options of a hosted OAuth provider. */\nexport interface OAuthClientOptions {\n readonly clientId: string;\n readonly clientSecret: string;\n /** The scopes asked for. Default: the 4.1 provider's (`openid email profile`; `identify email`). */\n readonly scopes?: readonly string[];\n /** Extra authorization parameters, such as `{ access_type: \"offline\", prompt: \"consent\" }`. */\n readonly params?: Readonly<Record<string, string>>;\n}\n\n/** `google.optional` and `discord.optional`'s options: credentials that may be unset, as read from the environment. */\nexport type OptionalClientOptions = Omit<OAuthClientOptions, \"clientId\" | \"clientSecret\"> & {\n readonly clientId: string | undefined;\n readonly clientSecret: string | undefined;\n};\n\n/** The development mock provider's options. */\nexport interface MockSignInOptions {\n /** The users the sign-in picker offers (typically seeded demo accounts). */\n readonly listUsers: () => Promise<MockOAuthUser[]>;\n /**\n * Where the API reaches itself for the token and userinfo requests.\n * Default: `createAuthRoutes`' `publicUrl`; set it where the public URL is\n * not reachable from inside the server (containers, Codespaces), for\n * example `\"http://localhost:4000\"`.\n */\n readonly internalUrl?: string;\n}\n\n/** The development mock provider: the routes serve its endpoints while `isMockOAuthEnabled()`. */\nexport interface MockSignInProvider {\n readonly kind: \"mock\";\n readonly id: \"mock\";\n readonly options: MockSignInOptions;\n}\n\nfunction checkCredentials(options: OAuthClientOptions, owner: string): void {\n if (typeof options.clientId !== \"string\" || options.clientId === \"\") {\n throw new TypeError(`${owner}: clientId is required`);\n }\n if (typeof options.clientSecret !== \"string\" || options.clientSecret === \"\") {\n throw new TypeError(`${owner}: clientSecret is required`);\n }\n}\n\n/** The 4.1 provider with the app's scopes, and the URL and exchange config for one redirect URI. */\nfunction hosted<TUser>(\n base: OAuthProvider<TUser>,\n options: OAuthClientOptions,\n): {\n readonly provider: OAuthProvider<TUser>;\n url(state: string, redirectUri: string): string;\n config(redirectUri: string): OAuthConfig;\n} {\n const provider = options.scopes === undefined ? base : { ...base, scopes: [...options.scopes] };\n const config = (redirectUri: string): OAuthConfig => ({\n clientId: options.clientId,\n clientSecret: options.clientSecret,\n redirectUri,\n });\n return {\n provider,\n config,\n url: (state, redirectUri) => {\n const url = new URL(createOAuthURL(provider, config(redirectUri), state));\n for (const [name, value] of Object.entries(options.params ?? {})) {\n url.searchParams.set(name, value);\n }\n return url.href;\n },\n };\n}\n\n/**\n * The provider `make` builds when both credentials are set; `undefined`,\n * which `createAuthRoutes` skips, when neither is. Only one of the two is a\n * misconfiguration, refused as `make` refuses it.\n */\nfunction optionalOf(\n make: (options: OAuthClientOptions) => OAuthSignInProvider,\n): (options: OptionalClientOptions) => OAuthSignInProvider | undefined {\n return (options) => {\n const set = (value: unknown): boolean => typeof value === \"string\" && value !== \"\";\n if (!set(options.clientId) && !set(options.clientSecret)) {\n return undefined;\n }\n return make(options as OAuthClientOptions);\n };\n}\n\n/** Google sign-in. Register `{publicUrl}{basePath}/google/callback` as a redirect URI. */\nexport function google(options: OAuthClientOptions): OAuthSignInProvider {\n checkCredentials(options, \"google()\");\n const client = hosted(googleProvider, options);\n return Object.freeze({\n kind: \"oauth\",\n id: \"google\",\n name: \"Google\",\n authorizeUrl: client.url,\n async profile(code: string, redirectUri: string): Promise<AuthProfile> {\n const { tokens, user } = await exchangeOAuthCode(\n client.provider,\n client.config(redirectUri),\n code,\n );\n return {\n providerAccountId: user.id,\n email: user.email,\n emailVerified: user.verified_email,\n name: user.name,\n image: user.picture,\n tokens,\n raw: user,\n };\n },\n });\n}\n\n/**\n * Google sign-in when both credentials are set, else nothing (`undefined`,\n * which `createAuthRoutes` skips): the provider of an environment that may\n * not configure it, such as development without a Google app.\n *\n * @example\n * providers: [\n * google.optional({ clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET }),\n * discord.optional({ clientId: env.DISCORD_CLIENT_ID, clientSecret: env.DISCORD_CLIENT_SECRET }),\n * mock({ listUsers }),\n * ],\n */\ngoogle.optional = optionalOf(google);\n\n/** Discord sign-in. Register `{publicUrl}{basePath}/discord/callback` as a redirect URI. */\nexport function discord(options: OAuthClientOptions): OAuthSignInProvider {\n checkCredentials(options, \"discord()\");\n const client = hosted(discordProvider, options);\n return Object.freeze({\n kind: \"oauth\",\n id: \"discord\",\n name: \"Discord\",\n authorizeUrl: client.url,\n async profile(code: string, redirectUri: string): Promise<AuthProfile> {\n const { tokens, user } = await exchangeOAuthCode(\n client.provider,\n client.config(redirectUri),\n code,\n );\n return {\n providerAccountId: user.id,\n email: user.email,\n emailVerified: user.verified,\n name: user.username,\n image: getDiscordAvatarUrl(user),\n tokens,\n raw: user,\n };\n },\n });\n}\n\n/** Discord sign-in when both credentials are set, else nothing: see `google.optional`. */\ndiscord.optional = optionalOf(discord);\n\n/**\n * The development sign-in: a picker of the app's demo users, served by the\n * routes through `registerMockOAuthProvider`. Left out (its routes answer\n * nothing) unless `isMockOAuthEnabled()`: `ENABLE_MOCK_OAUTH=true` and\n * `NODE_ENV` other than `production`.\n */\nexport function mock(options: MockSignInOptions): MockSignInProvider {\n if (typeof options.listUsers !== \"function\") {\n throw new TypeError(\"mock(): listUsers is required\");\n }\n return Object.freeze({ kind: \"mock\", id: \"mock\", options });\n}\n"],"mappings":"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","names":["randomBytes","sessionOf"]}
1
+ {"version":3,"sources":["../../../src/server/auth/oauth.ts","../../../src/server/auth/discord.ts","../../../src/server/auth/google.ts","../../../src/server/auth/mock.ts","../../../src/server/auth/devSignIns.ts","../../../src/server/auth/routes/respond.ts","../../../src/server/auth/routes/state.ts","../../../src/server/auth/routes/tokens.ts","../../../src/server/auth/routes/settings.ts","../../../src/server/auth/routes/socketAuth.ts","../../../src/server/auth/routes/guest.ts","../../../src/server/auth/routes/limits.ts","../../../src/server/auth/routes/mockFlow.ts","../../../src/server/auth/routes/oauth.ts","../../../src/server/auth/routes/session.ts","../../../src/server/auth/routes/createAuthRoutes.ts","../../../src/server/auth/routes/bearerAuth.ts","../../../src/server/auth/routes/requireSession.ts","../../../src/server/auth/routes/sessions.ts","../../../src/server/auth/routes/providers.ts"],"sourcesContent":["/**\n * Generic OAuth provider interface.\n */\nexport interface OAuthProvider<TUser = unknown> {\n name: string;\n authorizationUrl: string;\n tokenUrl: string;\n userInfoUrl: string;\n scopes: string[];\n\n /**\n * Parse user info response into a standardized format.\n */\n parseUserInfo: (data: unknown) => TUser;\n}\n\n/**\n * OAuth configuration for a provider.\n */\nexport interface OAuthConfig {\n clientId: string;\n clientSecret: string;\n redirectUri: string;\n}\n\n/**\n * OAuth token response.\n */\nexport interface OAuthTokenResponse {\n access_token: string;\n token_type: string;\n expires_in?: number;\n refresh_token?: string;\n scope?: string;\n}\n\n/**\n * Create an OAuth authorization URL.\n *\n * @param provider - The OAuth provider\n * @param config - OAuth configuration\n * @param state - Optional state parameter for CSRF protection\n */\nexport function createOAuthURL(\n provider: OAuthProvider,\n config: OAuthConfig,\n state?: string,\n): string {\n const params = new URLSearchParams({\n client_id: config.clientId,\n redirect_uri: config.redirectUri,\n response_type: \"code\",\n scope: provider.scopes.join(\" \"),\n });\n\n if (state) {\n params.set(\"state\", state);\n }\n\n return `${provider.authorizationUrl}?${params.toString()}`;\n}\n\n/**\n * Exchange an OAuth authorization code for tokens and user info.\n *\n * @param provider - The OAuth provider\n * @param config - OAuth configuration\n * @param code - The authorization code from the callback\n */\nexport async function exchangeOAuthCode<TUser>(\n provider: OAuthProvider<TUser>,\n config: OAuthConfig,\n code: string,\n): Promise<{ tokens: OAuthTokenResponse; user: TUser }> {\n // Exchange code for tokens\n const tokenResponse = await fetch(provider.tokenUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: new URLSearchParams({\n client_id: config.clientId,\n client_secret: config.clientSecret,\n code,\n grant_type: \"authorization_code\",\n redirect_uri: config.redirectUri,\n }),\n });\n\n if (!tokenResponse.ok) {\n const error = await tokenResponse.text();\n throw new Error(`Token exchange failed: ${error}`);\n }\n\n const tokens = (await tokenResponse.json()) as OAuthTokenResponse;\n\n // Fetch user info\n const userResponse = await fetch(provider.userInfoUrl, {\n headers: {\n Authorization: `Bearer ${tokens.access_token}`,\n Accept: \"application/json\",\n },\n });\n\n if (!userResponse.ok) {\n const error = await userResponse.text();\n throw new Error(`User info fetch failed: ${error}`);\n }\n\n const userData = await userResponse.json();\n const user = provider.parseUserInfo(userData);\n\n return { tokens, user };\n}\n","import type { OAuthProvider } from \"./oauth\";\n\n/**\n * Discord user information.\n */\nexport interface DiscordUser {\n id: string;\n username: string;\n discriminator: string;\n email: string | null;\n avatar: string | null;\n verified: boolean;\n}\n\n/**\n * Discord OAuth provider configuration.\n */\nexport const discordProvider: OAuthProvider<DiscordUser> = {\n name: \"discord\",\n authorizationUrl: \"https://discord.com/api/oauth2/authorize\",\n tokenUrl: \"https://discord.com/api/oauth2/token\",\n userInfoUrl: \"https://discord.com/api/users/@me\",\n scopes: [\"identify\", \"email\"],\n\n parseUserInfo: (data: unknown): DiscordUser => {\n const d = data as Record<string, unknown>;\n return {\n id: String(d.id),\n username: String(d.username),\n discriminator: String(d.discriminator ?? \"0\"),\n email: d.email ? String(d.email) : null,\n avatar: d.avatar ? String(d.avatar) : null,\n verified: Boolean(d.verified),\n };\n },\n};\n\n/**\n * Get Discord avatar URL.\n */\nexport function getDiscordAvatarUrl(user: DiscordUser): string {\n if (user.avatar) {\n return `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`;\n }\n // Default avatar based on discriminator or user ID\n const index =\n user.discriminator === \"0\"\n ? (BigInt(user.id) >> BigInt(22)) % BigInt(6)\n : parseInt(user.discriminator, 10) % 5;\n return `https://cdn.discordapp.com/embed/avatars/${index}.png`;\n}\n","import type { OAuthProvider } from \"./oauth\";\n\n/**\n * Google user information.\n */\nexport interface GoogleUser {\n id: string;\n email: string;\n name: string;\n picture: string | null;\n verified_email: boolean;\n}\n\n/**\n * Google OAuth provider configuration.\n */\nexport const googleProvider: OAuthProvider<GoogleUser> = {\n name: \"google\",\n authorizationUrl: \"https://accounts.google.com/o/oauth2/v2/auth\",\n tokenUrl: \"https://oauth2.googleapis.com/token\",\n userInfoUrl: \"https://www.googleapis.com/oauth2/v2/userinfo\",\n scopes: [\"openid\", \"email\", \"profile\"],\n\n parseUserInfo: (data: unknown): GoogleUser => {\n const d = data as Record<string, unknown>;\n return {\n id: String(d.id),\n email: String(d.email),\n name: String(d.name),\n picture: d.picture ? String(d.picture) : null,\n verified_email: Boolean(d.verified_email),\n };\n },\n};\n","/**\n * Mock OAuth provider for local development and tests.\n *\n * Implements a real OAuth 2.0 authorization-code flow served by the app's own\n * API, so `createOAuthURL` and `exchangeOAuthCode` work unchanged — the mock\n * is just a provider whose authorize/token/userinfo endpoints the app mounts\n * via `registerMockOAuthProvider`. Users are supplied by the app (typically\n * seeded demo accounts); no real credentials are involved.\n *\n * Development only: routes are mounted only when ENABLE_MOCK_OAUTH is\n * \"true\" and NODE_ENV is \"development\" or \"test\" (an allowlist: an unset\n * NODE_ENV, \"prod\" or \"staging\" mount nothing, the 5.1 audit's AUTH-3), every\n * handler re-checks NODE_ENV at request time, and mounting them logs a\n * warning. Apps should add a third layer and refuse to boot outside\n * development with the flag set.\n */\n\nimport { randomBytes } from \"crypto\";\nimport { consoleLogger, type Logger } from \"../../contract/logger\";\nimport { developmentSignInsAllowed, nodeEnvLabel } from \"./devSignIns\";\nimport type { GoogleUser } from \"./google\";\nimport type { OAuthProvider } from \"./oauth\";\nimport { validateRedirectOrigin } from \"./validateOrigin\";\n\nexport interface MockOAuthUser {\n id: string;\n email: string;\n name: string;\n picture?: string | null;\n}\n\nexport const DEFAULT_MOCK_PATH_PREFIX = \"/auth/mock/provider\";\n\nconst CODE_TTL_MS = 5 * 60 * 1000;\nconst TOKEN_TTL_MS = 60 * 60 * 1000;\n\n/** Private-network origins allowed for redirect_uri in dev (LAN testing). */\nconst DEV_PRIVATE_ORIGIN_REGEX =\n /^http:\\/\\/(?:localhost|127\\.0\\.0\\.1|0\\.0\\.0\\.0|10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|192\\.168\\.\\d{1,3}\\.\\d{1,3}|172\\.(?:1[6-9]|2\\d|3[01])\\.\\d{1,3}\\.\\d{1,3}):\\d+$/;\n\n/** True when ENABLE_MOCK_OAUTH is \"true\" and NODE_ENV is \"development\" or \"test\". */\nexport function isMockOAuthEnabled(): boolean {\n return process.env.ENABLE_MOCK_OAUTH === \"true\" && developmentSignInsAllowed();\n}\n\nexport interface MockOAuthProviderOptions {\n /**\n * Base URL the API server can use to reach itself for the token/userinfo\n * exchange (server-side fetch). Defaults to apiBaseUrl — override in\n * containers/codespaces where the public URL isn't self-reachable\n * (e.g. \"http://localhost:4000\").\n */\n internalBaseUrl?: string;\n /** Route prefix for the mock endpoints. Default: \"/auth/mock/provider\". */\n pathPrefix?: string;\n}\n\n/**\n * Create the mock OAuth provider definition.\n *\n * `parseUserInfo` returns a GoogleUser-compatible shape with `id` set to the\n * user's email, so `providerAccountId` stays stable across database re-seeds.\n */\nexport function createMockOAuthProvider(\n apiBaseUrl: string,\n options: MockOAuthProviderOptions = {},\n): OAuthProvider<GoogleUser> {\n const prefix = options.pathPrefix ?? DEFAULT_MOCK_PATH_PREFIX;\n const publicBase = apiBaseUrl.replace(/\\/$/, \"\");\n const internalBase = (options.internalBaseUrl ?? apiBaseUrl).replace(/\\/$/, \"\");\n\n return {\n name: \"mock\",\n authorizationUrl: `${publicBase}${prefix}/authorize`,\n tokenUrl: `${internalBase}${prefix}/token`,\n userInfoUrl: `${internalBase}${prefix}/userinfo`,\n scopes: [\"openid\", \"email\", \"profile\"],\n\n parseUserInfo: (data: unknown): GoogleUser => {\n const d = data as Record<string, unknown>;\n return {\n id: String(d.id),\n email: String(d.email),\n name: String(d.name),\n picture: d.picture ? String(d.picture) : null,\n verified_email: true,\n };\n },\n };\n}\n\ninterface MockRequest {\n url?: string;\n originalUrl?: string;\n query?: Record<string, unknown>;\n headers: Record<string, string | string[] | undefined>;\n body?: unknown;\n}\n\ninterface MockResponse {\n status(code: number): MockResponse;\n json(body: unknown): unknown;\n send(body: string): unknown;\n redirect(url: string): unknown;\n}\n\ntype MockHandler = (req: MockRequest, res: MockResponse) => void | Promise<void>;\n\nexport interface MockOAuthRouter {\n get(path: string, handler: MockHandler): unknown;\n post(path: string, handler: MockHandler): unknown;\n}\n\nexport interface RegisterMockOAuthOptions {\n /**\n * Supply the users shown in the dev login picker (typically seeded demo\n * accounts). Injected so core never touches the app's database.\n */\n listUsers: () => Promise<MockOAuthUser[]>;\n /** Route prefix — must match createMockOAuthProvider's. */\n pathPrefix?: string;\n /**\n * Extra origins allowed for redirect_uri beyond CLIENT_URL /\n * EXTRA_ALLOWED_ORIGINS / localhost / private LAN IPs.\n */\n allowedRedirectOrigins?: string[];\n logger?: Logger;\n}\n\nfunction getQueryParam(req: MockRequest, name: string): string | null {\n const fromQuery = req.query?.[name];\n if (typeof fromQuery === \"string\") return fromQuery;\n const url = req.originalUrl ?? req.url;\n if (!url) return null;\n const idx = url.indexOf(\"?\");\n if (idx === -1) return null;\n return new URLSearchParams(url.slice(idx + 1)).get(name);\n}\n\n/**\n * Parse an x-www-form-urlencoded POST body. Works without any body-parser\n * middleware by falling back to reading the raw request stream. An empty\n * object body is treated as unparsed: express.json() sets req.body = {} even\n * when it skips non-JSON content types, leaving the stream unread.\n */\nasync function readBodyParams(req: MockRequest): Promise<URLSearchParams> {\n if (typeof req.body === \"string\") return new URLSearchParams(req.body);\n if (req.body && typeof req.body === \"object\" && Object.keys(req.body).length > 0) {\n return new URLSearchParams(req.body as Record<string, string>);\n }\n const chunks: Buffer[] = [];\n for await (const chunk of req as unknown as AsyncIterable<Buffer>) {\n chunks.push(Buffer.from(chunk));\n }\n return new URLSearchParams(Buffer.concat(chunks).toString(\"utf8\"));\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n\nfunction isAllowedRedirectUri(uri: string, allowedOrigins: string[]): boolean {\n let parsed: URL;\n try {\n parsed = new URL(uri);\n } catch {\n return false;\n }\n const origin = parsed.origin;\n if (allowedOrigins.includes(origin)) return true;\n if (DEV_PRIVATE_ORIGIN_REGEX.test(origin)) return true;\n return validateRedirectOrigin(origin) !== null;\n}\n\nfunction renderPickerPage(\n users: MockOAuthUser[],\n authorizePath: string,\n redirectUri: string,\n state: string | null,\n): string {\n const links = users\n .map((user) => {\n const params = new URLSearchParams({ redirect_uri: redirectUri, email: user.email });\n if (state) params.set(\"state\", state);\n const href = `${authorizePath}?${params.toString()}`;\n return `<li><a href=\"${escapeHtml(href)}\"><strong>${escapeHtml(user.name)}</strong><span>${escapeHtml(user.email)}</span></a></li>`;\n })\n .join(\"\\n\");\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>Mock sign-in</title>\n<style>\n body { font-family: system-ui, sans-serif; background: #111; color: #eee; display: flex; min-height: 100vh; align-items: center; justify-content: center; margin: 0; }\n main { max-width: 420px; width: 100%; padding: 2rem; }\n h1 { font-size: 1.25rem; }\n p { color: #999; font-size: 0.875rem; }\n ul { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }\n a { display: flex; flex-direction: column; gap: 0.125rem; padding: 0.75rem 1rem; border: 1px solid #333; border-radius: 8px; text-decoration: none; color: inherit; }\n a:hover { border-color: #888; background: #1a1a1a; }\n a span { color: #999; font-size: 0.8125rem; }\n</style>\n</head>\n<body>\n<main>\n<h1>Mock sign-in</h1>\n<p>Development only — pick an account to continue. Run your seed script if this list is empty.</p>\n<ul>\n${links || \"<li><em>No users found.</em></li>\"}\n</ul>\n</main>\n</body>\n</html>`;\n}\n\n/** A short-lived grant for one mock user: an auth code or a bearer token. */\ninterface MockGrant {\n email: string;\n expiresAt: number;\n}\n\n/** What the three mock endpoints share once mounted. */\ninterface MockEndpoints {\n readonly options: RegisterMockOAuthOptions;\n readonly prefix: string;\n readonly allowedOrigins: string[];\n // Single-use auth codes and bearer tokens, both short-lived and in-memory.\n readonly codes: Map<string, MockGrant>;\n readonly tokens: Map<string, MockGrant>;\n}\n\nfunction sweep(store: Map<string, MockGrant>): void {\n const now = Date.now();\n for (const [key, entry] of store) {\n if (entry.expiresAt < now) store.delete(key);\n }\n}\n\nfunction guard(res: MockResponse): boolean {\n // Request-time re-check defends against env mutation after boot.\n if (!developmentSignInsAllowed()) {\n res.status(404).json({ error: \"Not found\" });\n return false;\n }\n return true;\n}\n\nfunction authorizeHandler(endpoints: MockEndpoints): MockHandler {\n const { options, prefix, allowedOrigins, codes } = endpoints;\n return async (req, res) => {\n if (!guard(res)) return;\n\n const redirectUri = getQueryParam(req, \"redirect_uri\");\n const state = getQueryParam(req, \"state\");\n const email = getQueryParam(req, \"email\");\n\n if (!redirectUri || !isAllowedRedirectUri(redirectUri, allowedOrigins)) {\n res.status(400).json({ error: \"Invalid redirect_uri\" });\n return;\n }\n\n const users = await options.listUsers();\n\n if (!email) {\n res.status(200).send(renderPickerPage(users, `${prefix}/authorize`, redirectUri, state));\n return;\n }\n\n const user = users.find((u) => u.email === email);\n if (!user) {\n res.status(400).json({ error: \"Unknown mock user\" });\n return;\n }\n\n sweep(codes);\n const code = randomBytes(24).toString(\"hex\");\n codes.set(code, { email: user.email, expiresAt: Date.now() + CODE_TTL_MS });\n\n const params = new URLSearchParams({ code });\n if (state) params.set(\"state\", state);\n res.redirect(`${redirectUri}${redirectUri.includes(\"?\") ? \"&\" : \"?\"}${params.toString()}`);\n };\n}\n\nfunction tokenHandler({ codes, tokens }: MockEndpoints): MockHandler {\n return async (req, res) => {\n if (!guard(res)) return;\n\n const body = await readBodyParams(req);\n const code = body.get(\"code\");\n const entry = code ? codes.get(code) : undefined;\n if (code) codes.delete(code);\n\n if (!entry || entry.expiresAt < Date.now()) {\n res.status(400).json({ error: \"invalid_grant\" });\n return;\n }\n\n sweep(tokens);\n const accessToken = randomBytes(24).toString(\"hex\");\n tokens.set(accessToken, { email: entry.email, expiresAt: Date.now() + TOKEN_TTL_MS });\n\n res.status(200).json({\n access_token: accessToken,\n token_type: \"Bearer\",\n expires_in: Math.floor(TOKEN_TTL_MS / 1000),\n scope: \"openid email profile\",\n });\n };\n}\n\nfunction userinfoHandler({ options, tokens }: MockEndpoints): MockHandler {\n return async (req, res) => {\n if (!guard(res)) return;\n\n const auth = req.headers.authorization;\n const accessToken =\n typeof auth === \"string\" && auth.startsWith(\"Bearer \") ? auth.slice(7) : null;\n const entry = accessToken ? tokens.get(accessToken) : undefined;\n\n if (!entry || entry.expiresAt < Date.now()) {\n res.status(401).json({ error: \"invalid_token\" });\n return;\n }\n\n const users = await options.listUsers();\n const user = users.find((u) => u.email === entry.email);\n if (!user) {\n res.status(401).json({ error: \"invalid_token\" });\n return;\n }\n\n res.status(200).json({\n // providerAccountId = email: stable across re-seeds, unlike row ids.\n id: user.email,\n email: user.email,\n name: user.name,\n picture: user.picture ?? null,\n verified_email: true,\n });\n };\n}\n\n/**\n * Mount the mock OAuth provider endpoints (authorize, token, userinfo) on an\n * Express-compatible router.\n *\n * No-ops (returning false) unless `isMockOAuthEnabled()` — i.e. without\n * ENABLE_MOCK_OAUTH=true, or unless NODE_ENV is development or test, the\n * routes are never mounted. Mounting them logs a warning.\n */\nexport function registerMockOAuthProvider(\n router: MockOAuthRouter,\n options: RegisterMockOAuthOptions,\n): boolean {\n const logger = options.logger ?? consoleLogger;\n\n if (!isMockOAuthEnabled()) {\n if (process.env.ENABLE_MOCK_OAUTH === \"true\") {\n logger.error(\n `Mock OAuth is enabled but NODE_ENV is ${nodeEnvLabel()}: the mock runs only when NODE_ENV is development or test, so its routes are not mounted; set NODE_ENV=development to use it`,\n );\n }\n return false;\n }\n\n const endpoints: MockEndpoints = {\n options,\n prefix: options.pathPrefix ?? DEFAULT_MOCK_PATH_PREFIX,\n allowedOrigins: options.allowedRedirectOrigins ?? [],\n codes: new Map(),\n tokens: new Map(),\n };\n\n router.get(`${endpoints.prefix}/authorize`, authorizeHandler(endpoints));\n router.post(`${endpoints.prefix}/token`, tokenHandler(endpoints));\n router.get(`${endpoints.prefix}/userinfo`, userinfoHandler(endpoints));\n\n logger.warn(\n `Mock OAuth provider mounted at ${endpoints.prefix}: anyone who reaches it signs in as any user it lists (NODE_ENV=${process.env.NODE_ENV ?? \"\"}; development and tests only)`,\n );\n return true;\n}\n","// When the development sign-ins run: `socketAuth`'s `devCredentials` (a\n// socket handshake that names a user id) and the mock OAuth provider (any\n// user it lists). Both sign anyone who reaches them in as anyone, so they\n// run only when NODE_ENV says development or test: an allowlist, not the\n// `!== \"production\"` denylist of 5.0, under which an unset NODE_ENV,\n// `\"prod\"` or `\"staging\"` let them through (the 5.1 audit's AUTH-3).\n\n/** The NODE_ENV values under which development sign-ins run. */\nconst DEVELOPMENT_ENVS: ReadonlySet<string> = new Set([\"development\", \"test\"]);\n\n/** True only when NODE_ENV is `development` or `test`. */\nexport function developmentSignInsAllowed(): boolean {\n const env = process.env.NODE_ENV;\n return env !== undefined && DEVELOPMENT_ENVS.has(env);\n}\n\n/** NODE_ENV for a message: `unset`, or its value quoted. */\nexport function nodeEnvLabel(): string {\n const env = process.env.NODE_ENV;\n return env === undefined ? \"unset\" : JSON.stringify(env);\n}\n","// Reading and answering the auth routes' requests. The routes are mounted on\n// the app's Express app (4 or 5), so `req` and `res` are Express's. The kit\n// reads and writes them through Node's own API, and uses Express only for\n// cookies (`res.cookie` and `res.clearCookie`, as `setSessionCookie` does).\n// Failures answer `{ error: <code>, message }` with the code's HTTP status\n// (RFC 0003 section 3), and nothing the routes answer is cached.\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { errorText, type Logger } from \"../../../contract/logger\";\nimport { httpStatus, INTERNAL_MESSAGE, type ErrorCode } from \"../../../protocol/errors\";\nimport type { CookieResponse } from \"../sessionCookie\";\n\n/** The request the routes read: Express's, of which they use these. */\nexport interface AuthRouteRequest extends IncomingMessage {\n /** The path the request was made to, before any `app.use(path, ...)` mount took a prefix off. */\n readonly originalUrl?: string;\n /** The client's address, as Express reports it (`trust proxy` decides). */\n readonly ip?: string;\n /** True when the request came over HTTPS, as Express reports it. */\n readonly secure?: boolean;\n /** The body the app's own JSON parser read, if any. */\n readonly body?: unknown;\n /** The cookies `cookie-parser` read, if the app uses it. */\n readonly cookies?: unknown;\n}\n\n/** The response the routes answer: Express's, which also sets cookies. */\nexport type AuthRouteResponse = ServerResponse & CookieResponse;\n\nfunction urlOf(req: AuthRouteRequest): string {\n return req.originalUrl ?? req.url ?? \"\";\n}\n\n/** The request's path, without its query. */\nexport function pathOf(req: AuthRouteRequest): string {\n return urlOf(req).split(\"?\", 1)[0] ?? \"\";\n}\n\n/** A query parameter's first value, or `null`. */\nexport function queryOf(req: AuthRouteRequest, name: string): string | null {\n const url = urlOf(req);\n const start = url.indexOf(\"?\");\n return start === -1 ? null : new URLSearchParams(url.slice(start + 1)).get(name);\n}\n\nfunction noStore(res: ServerResponse, status: number): void {\n res.statusCode = status;\n res.setHeader(\"cache-control\", \"no-store\");\n}\n\n/** Answers with a JSON body, unless the response was already sent. */\nexport function sendJson(res: ServerResponse, status: number, body: unknown): void {\n if (res.headersSent) {\n return;\n }\n noStore(res, status);\n res.setHeader(\"content-type\", \"application/json; charset=utf-8\");\n res.end(JSON.stringify(body));\n}\n\n/** Answers a failure: `{ error: code, message }` with the code's HTTP status. */\nexport function refuse(res: ServerResponse, code: ErrorCode, message: string): void {\n sendJson(res, httpStatus(code), { error: code, message });\n}\n\n/**\n * Answers a route that failed with `INTERNAL`, and logs why: the error's\n * text, a Prisma error summarized (`errorText`), never the call its message\n * renders.\n */\nexport function failRoute(\n logger: Logger,\n req: AuthRouteRequest,\n res: ServerResponse,\n error: unknown,\n): void {\n logger.error(\"An auth route failed\", {\n category: \"quickdraw.auth\",\n path: pathOf(req),\n error: errorText(error),\n });\n refuse(res, \"INTERNAL\", INTERNAL_MESSAGE);\n}\n\n/** Redirects the browser (302). */\nexport function redirect(res: ServerResponse, location: string): void {\n noStore(res, 302);\n res.setHeader(\"location\", location);\n res.end();\n}\n\n/** Answers 204 with no body. */\nexport function noContent(res: ServerResponse): void {\n noStore(res, 204);\n res.end();\n}\n","// The OAuth `state` of a sign-in (RFC 6749 section 10.12), bound to the\n// browser that started it and redeemable once. `start` sends the state to\n// the provider and keeps it, with the provider and the validated return\n// origin, in a short-lived HttpOnly cookie: `__Host-qd_oauth` on `/` over a\n// secure request or whenever `publicUrl` is https, else `qd_oauth` on the\n// auth routes' path. The `__Host-` name is what keeps a sibling site from\n// planting a state the routes issued to an attacker, to sign the browser in\n// as them (the 5.1 audit's AUTH-2b, a login CSRF): only this host, over\n// HTTPS, can set it, so a deployment whose state cookie is `qd_oauth` keeps\n// that risk. The cookie is also signed with an HMAC-SHA256 under a key\n// derived from the JWT secret, so a payload the routes did not write (its\n// state, origin, provider or issue time) is no sign-in; the MAC does not\n// say which browser a state was issued to. The callback\n// clears that cookie whatever happens, accepts the state only when the\n// provider sent back the cookie's own value, for the same provider and\n// within ten minutes, and remembers each redeemed state in this process until\n// it would have expired anyway, so a replayed callback is refused even when\n// its cookie is replayed with it. The return origin read back from the cookie\n// is validated again before any redirect (`createAuthRoutes`).\n\nimport { createHmac, randomBytes, timingSafeEqual } from \"node:crypto\";\n\n/** The cookie that carries a sign-in in progress, on a request that is not secure. */\nexport const OAUTH_STATE_COOKIE = \"qd_oauth\";\n\n/**\n * The cookie that carries a sign-in in progress on a secure request: the\n * `__Host-` prefix keeps any other site under the same parent domain from\n * planting a state of its own (a login CSRF), and requires `Path=/`.\n */\nexport const HOST_OAUTH_STATE_COOKIE = \"__Host-qd_oauth\";\n\n/** How long a sign-in may take from `start` to its callback. */\nexport const OAUTH_STATE_TTL_MS = 10 * 60 * 1000;\n\n/** At most this many redeemed states are remembered; the oldest are forgotten first. */\nconst MAX_REDEEMED = 10_000;\n\n/** A sign-in in progress, as its cookie carries it. */\nexport interface PendingSignIn {\n /** The `state` sent to the provider. */\n readonly state: string;\n /** The provider's id. */\n readonly provider: string;\n /** The validated origin to return to. */\n readonly origin: string;\n /** When `start` issued it, in epoch milliseconds. */\n readonly issuedAt: number;\n}\n\n/** A new state: 256 random bits, base64url. */\nexport function newState(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\n/** The key the state cookie is signed with: derived from the JWT secret, for this use only. */\nexport function oauthStateKey(jwtSecret: string): Buffer {\n return createHmac(\"sha256\", jwtSecret).update(\"quickdraw:oauth-state\").digest();\n}\n\nfunction macOf(payload: string, key: Buffer): Buffer {\n return createHmac(\"sha256\", key).update(payload).digest();\n}\n\n/** The state cookie's value for a sign-in: its JSON, base64url, then `.` and its MAC under `key`. */\nexport function encodePending(pending: PendingSignIn, key: Buffer): string {\n const payload = Buffer.from(JSON.stringify(pending), \"utf8\").toString(\"base64url\");\n return `${payload}.${macOf(payload, key).toString(\"base64url\")}`;\n}\n\n/**\n * The sign-in a state cookie's value carries, or `null` when it carries\n * none: its MAC under `key` is checked (in constant time) before anything\n * is read, so a cookie the routes did not write is none.\n */\nexport function decodePending(raw: string | undefined, key: Buffer): PendingSignIn | null {\n const dot = raw === undefined ? -1 : raw.lastIndexOf(\".\");\n if (raw === undefined || dot <= 0) {\n return null;\n }\n const payload = raw.slice(0, dot);\n const mac = Buffer.from(raw.slice(dot + 1), \"base64url\");\n const expected = macOf(payload, key);\n if (mac.length !== expected.length || !timingSafeEqual(mac, expected)) {\n return null;\n }\n let value: unknown;\n try {\n value = JSON.parse(Buffer.from(payload, \"base64url\").toString(\"utf8\"));\n } catch {\n return null;\n }\n const { state, provider, origin, issuedAt } = (value ?? {}) as Partial<\n Record<keyof PendingSignIn, unknown>\n >;\n if (\n typeof state !== \"string\" ||\n typeof provider !== \"string\" ||\n typeof origin !== \"string\" ||\n typeof issuedAt !== \"number\"\n ) {\n return null;\n }\n return { state, provider, origin, issuedAt };\n}\n\nfunction sameString(left: string, right: string): boolean {\n const a = Buffer.from(left, \"utf8\");\n const b = Buffer.from(right, \"utf8\");\n return a.length === b.length && timingSafeEqual(a, b);\n}\n\n/**\n * True when the callback's `state` is the pending sign-in's own, for the\n * same provider, issued no more than {@link OAUTH_STATE_TTL_MS} ago.\n */\nexport function stateMatches(\n pending: PendingSignIn,\n provider: string,\n returned: string | null,\n now: number,\n): boolean {\n const age = now - pending.issuedAt;\n return (\n returned !== null &&\n pending.provider === provider &&\n age >= 0 &&\n age <= OAUTH_STATE_TTL_MS &&\n sameString(pending.state, returned)\n );\n}\n\n/** The states this process has redeemed, so each is redeemed once. */\nexport interface RedeemedStates {\n /** Records `state` as redeemed; false when it already was. */\n redeem(state: string, now: number): boolean;\n}\n\n/** An empty record of redeemed states, each kept until it would have expired anyway. */\nexport function redeemedStates(): RedeemedStates {\n // Insertion order is redemption order, so the oldest entries come first.\n const redeemed = new Map<string, number>();\n return {\n redeem(state, now) {\n for (const [key, until] of redeemed) {\n if (until > now && redeemed.size < MAX_REDEEMED) {\n break;\n }\n redeemed.delete(key);\n }\n if (redeemed.has(state)) {\n return false;\n }\n redeemed.set(state, now + OAUTH_STATE_TTL_MS);\n return true;\n },\n };\n}\n","// A signed-in session's credential: a JWT naming its user and its session\n// (`sid`), signed with the app's secret, valid only while the app's\n// `SessionStore` still holds that session for that user and the session has\n// not expired. The routes issue it at sign-in, and the routes and\n// `socketAuth` read it back with `liveSession`. Both are exported for an\n// app's own sign-in flows (login codes, an embedded activity), so the\n// sessions they start work with `socketAuth` like the routes' own.\n\nimport { createJWT, readVerifiedJWT } from \"../jwt\";\nimport type { AuthSession, SessionMeta, SessionStore } from \"./sessions\";\n\n/** The shortest `jwtSecret` the kit accepts: HS256 wants a key of 256 bits or more. */\nexport const MIN_JWT_SECRET_LENGTH = 32;\n\n/** The default session lifetime: 7 days, the 4.1 JWT and cookie default. */\nexport const DEFAULT_SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000;\n\n/** Where sessions are stored, and the secret their JWTs are signed with. */\nexport interface SessionKeys {\n readonly sessions: SessionStore;\n readonly jwtSecret: string;\n}\n\n/** Throws a `TypeError` naming `owner` unless `keys` holds a usable store and secret. */\nexport function checkSessionKeys(keys: Partial<SessionKeys>, owner: string): SessionKeys {\n const { sessions, jwtSecret } = keys;\n const store = (sessions ?? {}) as Partial<Record<keyof SessionStore, unknown>>;\n const methods = [\"create\", \"get\", \"revoke\", \"revokeAll\"] as const;\n if (sessions === undefined || !methods.every((name) => typeof store[name] === \"function\")) {\n throw new TypeError(\n `${owner}: sessions must be a SessionStore (create, get, revoke, revokeAll)`,\n );\n }\n if (typeof jwtSecret !== \"string\" || jwtSecret.length < MIN_JWT_SECRET_LENGTH) {\n throw new TypeError(\n `${owner}: jwtSecret must be a secret of at least ${MIN_JWT_SECRET_LENGTH} characters`,\n );\n }\n return { sessions, jwtSecret };\n}\n\n/** A new session, stored, and its JWT, which expires with it. */\nexport interface IssuedSession {\n readonly session: AuthSession;\n readonly token: string;\n}\n\n/**\n * Starts a session for `userId`: creates it in the store, lasting `ttlMs`\n * (default 7 days), and signs its JWT. Send the token as the session cookie\n * (`setSessionCookie`) or give it to a cookie-less client for `auth.token`.\n * Throws when the store does not return the session it created.\n */\nexport async function issueSession(\n keys: SessionKeys,\n userId: string,\n meta: Omit<SessionMeta, \"expiresAt\">,\n ttlMs: number = DEFAULT_SESSION_TTL_MS,\n): Promise<IssuedSession> {\n checkSessionKeys(keys, \"issueSession\");\n if (!Number.isSafeInteger(ttlMs) || ttlMs < 1000) {\n throw new TypeError(\"issueSession: ttlMs must be a whole number of milliseconds, 1000 or more\");\n }\n const expiresAt = new Date(Date.now() + ttlMs);\n const session: Partial<AuthSession> | null | undefined = await keys.sessions.create(userId, {\n ...meta,\n expiresAt,\n });\n if (typeof session?.id !== \"string\" || session.id === \"\" || session.userId !== userId) {\n throw new TypeError(\"SessionStore.create must return the session it created, with its id\");\n }\n const seconds = Math.max(1, Math.ceil(ttlMs / 1000));\n const token = await createJWT({ userId, sid: session.id }, keys.jwtSecret, `${seconds}s`);\n return { session: session as AuthSession, token };\n}\n\n/**\n * The live session `token` stands for, or `null`: the JWT verifies with the\n * secret and has not expired, names a session, and the store still holds\n * that session, for the token's user, before its `expiresAt`.\n */\nexport async function liveSession(keys: SessionKeys, token: string): Promise<AuthSession | null> {\n checkSessionKeys(keys, \"liveSession\");\n const payload = await readVerifiedJWT(token, keys.jwtSecret);\n if (\n payload === null ||\n typeof payload.userId !== \"string\" ||\n payload.userId === \"\" ||\n payload.sid === undefined\n ) {\n return null;\n }\n const session = await keys.sessions.get(payload.sid);\n if (session === null || session === undefined || session.userId !== payload.userId) {\n return null;\n }\n // A store may return the expiry as a string or a number; an invalid date is refused.\n const expiresAt = new Date(session.expiresAt).getTime();\n return expiresAt > Date.now() ? session : null;\n}\n","// What every auth route works from, resolved and checked once from\n// `createAuthRoutes`' options: the session keys, the origin allowlist, the\n// URLs, the cookie, and the process's record of redeemed OAuth states.\n\nimport { consoleLogger, type Logger } from \"../../../contract/logger\";\nimport {\n clearStaleHostSession,\n cookieDomainFromEnv,\n isSecureSessionRequest,\n sessionCookieNameFor,\n type CookieResponse,\n type CookieSettings,\n type SessionCookieNaming,\n type SessionCookieRequest,\n} from \"../sessionCookie\";\nimport { originAllowlist, rememberRouteOrigins, type OriginAllowlist } from \"./origins\";\nimport type { AuthRouteRequest } from \"./respond\";\nimport {\n HOST_OAUTH_STATE_COOKIE,\n OAUTH_STATE_COOKIE,\n OAUTH_STATE_TTL_MS,\n oauthStateKey,\n redeemedStates,\n type RedeemedStates,\n} from \"./state\";\nimport {\n checkSessionKeys,\n DEFAULT_SESSION_TTL_MS,\n issueSession,\n type IssuedSession,\n type SessionKeys,\n} from \"./tokens\";\nimport type { AuthRoutesOptions } from \"./types\";\n\n/** The session cookie, resolved. */\nexport interface ResolvedCookie {\n /** The name the app gave; without one, it depends on the domain and the request (`sessionCookieName`). */\n readonly name: string | undefined;\n readonly ttlMs: number;\n /** `cookie.domain`, else `COOKIE_DOMAIN` from the environment, else none: the API's host only. */\n readonly domain: string | undefined;\n readonly sameSite: \"lax\" | \"none\";\n readonly secure: boolean | undefined;\n}\n\n/** What the routes work from. */\nexport interface RouteSettings {\n readonly keys: SessionKeys;\n readonly origins: OriginAllowlist;\n /** The API's public URL, without a trailing slash. */\n readonly publicUrl: string;\n /** `\"\"` or a path without a trailing slash, such as `\"/auth\"`. */\n readonly basePath: string;\n readonly successPath: string;\n readonly errorPath: string;\n readonly cookie: ResolvedCookie;\n readonly onLogin: AuthRoutesOptions[\"onLogin\"];\n readonly onRevoke: AuthRoutesOptions[\"onRevoke\"];\n readonly logger: Logger;\n readonly redeemed: RedeemedStates;\n /** The key the OAuth state cookie is signed with (`oauthStateKey`). */\n readonly stateKey: Buffer;\n /**\n * Looks at the host a request to the routes arrived for: the first one\n * that is not a loopback address while `publicUrl` is logs an error, once\n * (finding F9.2 of the owner's QA of the template).\n */\n watchHost(req: AuthRouteRequest): void;\n}\n\nconst OWNER = \"createAuthRoutes\";\n\nfunction publicUrlOf(value: unknown): string {\n let url: URL | undefined;\n try {\n url = typeof value === \"string\" ? new URL(value) : undefined;\n } catch {\n url = undefined;\n }\n if (\n url === undefined ||\n !/^https?:$/.test(url.protocol) ||\n url.search !== \"\" ||\n url.hash !== \"\"\n ) {\n throw new TypeError(\n `${OWNER}: publicUrl must be the API's public URL, such as \"https://api.example.com\"`,\n );\n }\n return `${url.origin}${url.pathname.replace(/\\/+$/, \"\")}`;\n}\n\n/** `\"/auth/\"` and `\"auth\"` become `\"/auth\"`; `\"/\"` becomes `\"\"`. */\nfunction basePathOf(value: unknown): string {\n if (value === undefined) {\n return \"/auth\";\n }\n if (typeof value !== \"string\" || /[?#\\\\]/.test(value)) {\n throw new TypeError(`${OWNER}: basePath must be a path such as \"/auth\"`);\n }\n const trimmed = value.replace(/^\\/+|\\/+$/g, \"\");\n return trimmed === \"\" ? \"\" : `/${trimmed}`;\n}\n\n/** A path on the web app's origin; one that could lead to another origin is refused. */\nfunction landingPathOf(value: unknown, name: string, fallback: string): string {\n if (value === undefined) {\n return fallback;\n }\n const probe = \"https://origin.invalid\";\n const safe =\n typeof value === \"string\" &&\n value.startsWith(\"/\") &&\n !value.startsWith(\"//\") &&\n !value.includes(\"\\\\\") &&\n new URL(value, probe).origin === probe;\n if (!safe) {\n throw new TypeError(`${OWNER}: ${name} must be a path on the web app, such as \"/\"`);\n }\n return value;\n}\n\nfunction cookieOf(options: AuthRoutesOptions[\"cookie\"]): ResolvedCookie {\n const { name, maxAgeMs, domain, secure, sameSite } = options ?? {};\n const ttlMs = maxAgeMs ?? DEFAULT_SESSION_TTL_MS;\n if (!Number.isSafeInteger(ttlMs) || ttlMs < 1000) {\n throw new TypeError(\n `${OWNER}: cookie.maxAgeMs must be a whole number of milliseconds, 1000 or more`,\n );\n }\n if (name !== undefined && !/^[\\w!#$%&'*+.^`|~-]+$/.test(name)) {\n throw new TypeError(`${OWNER}: cookie.name must be a cookie name`);\n }\n if (sameSite !== undefined && sameSite !== \"lax\" && sameSite !== \"none\") {\n throw new TypeError(`${OWNER}: cookie.sameSite must be \"lax\" or \"none\"`);\n }\n const resolvedDomain = domain ?? process.env.COOKIE_DOMAIN;\n const cookieDomain = resolvedDomain === \"\" ? undefined : resolvedDomain;\n if (name?.startsWith(\"__Host-\") === true && cookieDomain !== undefined) {\n throw new TypeError(\n `${OWNER}: cookie.name cannot start with \"__Host-\" when the cookie has a domain, which a browser refuses`,\n );\n }\n return { name, ttlMs, domain: cookieDomain, sameSite: sameSite ?? \"lax\", secure };\n}\n\n/**\n * Warns, once at startup, when the routes and the transports would name the\n * session cookie differently: the transports know the cookie's domain only\n * from `COOKIE_DOMAIN`, unless the app names the cookie in all three places.\n *\n * - `cookie.domain` without `COOKIE_DOMAIN`: the routes set `session` over\n * HTTPS, where `socketAuth` and the HTTP transport read only\n * `__Host-session`.\n * - `cookie.domain: \"\"` while `COOKIE_DOMAIN` is set: the routes set\n * `__Host-session` over HTTPS, which the transports read first; but they\n * also take a `session` cookie there, which the routes never set and any\n * site under `COOKIE_DOMAIN` can, so it signs a signed-out browser in as\n * whoever planted it.\n */\nfunction warnOnCookieDomain(cookie: ResolvedCookie, logger: Logger): void {\n const transportsSeeDomain = cookieDomainFromEnv() !== undefined;\n if (cookie.name !== undefined || (cookie.domain !== undefined) === transportsSeeDomain) {\n return;\n }\n const why =\n cookie.domain === undefined\n ? 'cookie.domain is \"\" while COOKIE_DOMAIN is set, so over HTTPS the routes set __Host-session, but socketAuth and the HTTP transport also take a \"session\" cookie there, which any site under COOKIE_DOMAIN can set for a browser that holds no __Host-session. Unset COOKIE_DOMAIN'\n : 'cookie.domain is set and COOKIE_DOMAIN is not, so the routes set \"session\" on every request, which socketAuth and the HTTP transport read over HTTPS only under that name. Set COOKIE_DOMAIN to the cookie\\'s domain instead';\n logger.warn(\n `${OWNER}: ${why}, or name the cookie in all three places: cookie.name, socketAuth's cookieName and createServer's http.cookieName.`,\n { category: \"quickdraw.auth\" },\n );\n}\n\nconst LOOPBACK = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"]);\n\n/** True for a loopback hostname: what a public deployment's `publicUrl` must not be. */\nfunction isLoopback(hostname: string): boolean {\n return LOOPBACK.has(hostname) || hostname.endsWith(\".localhost\");\n}\n\n/** The host a request arrived for: the first `X-Forwarded-Host`, else `Host`; `undefined` without either. */\nfunction requestHost(req: AuthRouteRequest): string | undefined {\n const forwarded = req.headers[\"x-forwarded-host\"];\n const first = (Array.isArray(forwarded) ? forwarded[0] : forwarded)?.split(\",\")[0]?.trim();\n const host = first !== undefined && first !== \"\" ? first : req.headers.host;\n return host === undefined || host === \"\" ? undefined : host;\n}\n\n/** Names reserved for tests and examples (RFC 2606): no deployment's page is there. */\nconst RESERVED_TLD = /\\.(test|example|invalid)$/;\n\n/**\n * Warns, once at startup, when `publicUrl` is a loopback address and every\n * allowed origin is not (finding F10.2): the routes would send a browser on\n * one of those pages to a sign-in URL only the server's own machine reaches.\n * Pages under a name reserved for tests (`.test`, `.example`, `.invalid`)\n * do not count: a test suite serves its routes on loopback.\n */\nfunction warnOnLoopbackPublicUrl(\n publicUrl: string,\n origins: readonly string[],\n logger: Logger,\n): void {\n const publicHost = new URL(publicUrl).hostname;\n const pages = origins\n .map((origin) => new URL(origin).hostname)\n .filter((hostname) => !RESERVED_TLD.test(hostname));\n if (isLoopback(publicHost) && pages.length > 0 && !pages.some(isLoopback)) {\n logger.warn(\n `${OWNER}: publicUrl is ${publicUrl}, but allowedOrigins lists only public pages (${origins.join(\", \")}): a sign-in started from them redirects to an address only this machine reaches. Set publicUrl to the API's public URL.`,\n { category: \"quickdraw.auth\" },\n );\n }\n}\n\n/**\n * The request-time check of a loopback `publicUrl` (finding F9.2): a\n * deployment that fell back to `http://localhost:<port>` for want of its API\n * URL builds every redirect to localhost. The first request that arrives for\n * another host logs an error, once.\n */\nfunction hostWatcher(publicUrl: string, logger: Logger): (req: AuthRouteRequest) => void {\n if (!isLoopback(new URL(publicUrl).hostname)) {\n return () => undefined;\n }\n let warned = false;\n return (req) => {\n const host = warned ? undefined : requestHost(req);\n if (host === undefined) {\n return;\n }\n let hostname: string;\n try {\n hostname = new URL(`http://${host}`).hostname;\n } catch {\n return;\n }\n if (!isLoopback(hostname)) {\n warned = true;\n logger.error(\n `${OWNER}: publicUrl is ${publicUrl}, but requests arrive for ${host}: set publicUrl to the API's public URL (sign-in redirects are built from it)`,\n { category: \"quickdraw.auth\", publicUrl, host },\n );\n }\n };\n}\n\n/** Resolves and checks the options every route uses; throws a `TypeError` for a bad one. */\nexport function routeSettings(options: AuthRoutesOptions): RouteSettings {\n if (typeof options.onLogin !== \"function\") {\n throw new TypeError(`${OWNER}: onLogin is required`);\n }\n if (options.onRevoke !== undefined && typeof options.onRevoke !== \"function\") {\n throw new TypeError(`${OWNER}: onRevoke must be a function of (userId, sessionId)`);\n }\n const successPath = landingPathOf(options.successPath, \"successPath\", \"/\");\n const cookie = cookieOf(options.cookie);\n const logger = options.logger ?? consoleLogger;\n const publicUrl = publicUrlOf(options.publicUrl);\n const keys = checkSessionKeys(options, OWNER);\n const settings: RouteSettings = Object.freeze({\n keys,\n origins: originAllowlist(options.allowedOrigins, OWNER),\n publicUrl,\n basePath: basePathOf(options.basePath),\n successPath,\n errorPath: landingPathOf(options.errorPath, \"errorPath\", successPath),\n cookie,\n onLogin: options.onLogin,\n onRevoke: options.onRevoke,\n logger,\n redeemed: redeemedStates(),\n stateKey: oauthStateKey(keys.jwtSecret),\n watchHost: hostWatcher(publicUrl, logger),\n });\n warnOnCookieDomain(cookie, logger);\n warnOnLoopbackPublicUrl(\n publicUrl,\n options.allowedOrigins.filter((origin): origin is string => typeof origin === \"string\"),\n logger,\n );\n // `requireSession` over the same store applies this list when given none.\n rememberRouteOrigins(settings.keys.sessions, settings.origins);\n return settings;\n}\n\n/** Where a provider sends the browser back: `{publicUrl}{basePath}/{id}/callback`. */\nexport function redirectUriOf(settings: RouteSettings, providerId: string): string {\n return `${settings.publicUrl}${settings.basePath}/${providerId}/callback`;\n}\n\n/** Where a sign-in lands on `origin`: the success path, or the error path with `?error=`. */\nexport function landingOf(settings: RouteSettings, origin: string, error?: string): string {\n const url = new URL(error === undefined ? settings.successPath : settings.errorPath, origin);\n if (error !== undefined) {\n url.searchParams.set(\"error\", error);\n }\n return url.href;\n}\n\n/**\n * Whether a cookie the routes set on `req` is Secure, unless the app said\n * otherwise: in production, and on a request that came over HTTPS by the\n * rule the session cookie's name follows (`isSecureRequest`).\n */\nfunction secureFor(settings: RouteSettings, req: SessionCookieRequest): boolean {\n return (\n settings.cookie.secure ?? (process.env.NODE_ENV === \"production\" || isSecureSessionRequest(req))\n );\n}\n\n/** How the app named the session cookie, for `sessionCookieNameFor` and the reads. */\nexport function sessionCookieNaming(settings: RouteSettings): SessionCookieNaming {\n return { cookieName: settings.cookie.name, domain: settings.cookie.domain };\n}\n\n/**\n * The session cookie's name on `req`, by the rule `socketAuth`, the HTTP\n * transport and `setSessionCookie` share (`sessionCookieNameFor`): the name\n * the app gave; else `session` when the cookie has a domain, which cannot be\n * `__Host-`; else `__Host-session` on a request over HTTPS, which a browser\n * keeps only as a Secure, host-only cookie on `/`, so no other site under\n * the same parent domain can plant or replace it, and `session` over plain\n * HTTP (development).\n */\nexport function sessionCookieName(settings: RouteSettings, req: SessionCookieRequest): string {\n return sessionCookieNameFor(req, sessionCookieNaming(settings));\n}\n\n/**\n * The session cookie's settings on `req`: set (`live`) or cleared. Secure\n * when its name is `__Host-` (which a browser requires) or it is\n * SameSite=None (likewise), else as `secureFor` says.\n */\nfunction sessionCookieSettings(\n settings: RouteSettings,\n req: SessionCookieRequest,\n name: string,\n live: boolean,\n): CookieSettings {\n const { ttlMs, domain, sameSite } = settings.cookie;\n const hostOnly = name.startsWith(\"__Host-\");\n return {\n httpOnly: true,\n secure: hostOnly || sameSite === \"none\" || secureFor(settings, req),\n sameSite,\n path: \"/\",\n ...(domain !== undefined && !hostOnly && { domain }),\n ...(live && { maxAge: ttlMs }),\n };\n}\n\n/**\n * `req` as the page a sign-in returns to sends it: an OAuth callback is a\n * navigation from the provider and carries no `Origin`, so the return\n * origin stands in for it, and a sign-in for an `https:` page gets the\n * cookie that page's own requests read.\n */\nfunction fromPage(req: AuthRouteRequest, pageOrigin: string | undefined): SessionCookieRequest {\n if (pageOrigin === undefined || req.headers.origin !== undefined) {\n return req;\n }\n return {\n headers: { ...req.headers, origin: pageOrigin },\n secure: req.secure,\n socket: req.socket,\n };\n}\n\n/**\n * Sets the session cookie carrying `token`, for the cookie, the JWT and the\n * session to end together; `pageOrigin` is the page an OAuth callback\n * returns to. Clears a stale `__Host-session` that would shadow it.\n */\nexport function setSession(\n res: CookieResponse,\n settings: RouteSettings,\n req: AuthRouteRequest,\n token: string,\n pageOrigin?: string,\n): void {\n const from = fromPage(req, pageOrigin);\n const name = sessionCookieName(settings, from);\n res.cookie(name, token, sessionCookieSettings(settings, from, name, true));\n if (settings.cookie.name === undefined) {\n clearStaleHostSession(res, req, name, settings.cookie.sameSite);\n }\n}\n\n/** Clears the session cookie, and a stale `__Host-session` beside it. */\nexport function clearSession(\n res: CookieResponse,\n settings: RouteSettings,\n req: AuthRouteRequest,\n): void {\n const name = sessionCookieName(settings, req);\n res.clearCookie(name, sessionCookieSettings(settings, req, name, false));\n if (settings.cookie.name === undefined) {\n clearStaleHostSession(res, req, name, settings.cookie.sameSite);\n }\n}\n\n/**\n * Whether the state cookie is Secure: on a secure request, and whenever\n * `publicUrl` is https, whatever the request looks like (a proxy that\n * forwards no `X-Forwarded-Proto` must not turn it into a cookie a sibling\n * site could plant).\n */\nfunction stateSecure(settings: RouteSettings, req: AuthRouteRequest): boolean {\n return secureFor(settings, req) || settings.publicUrl.startsWith(\"https:\");\n}\n\n/** The state cookie's name on `req`: `__Host-qd_oauth` when it is Secure, else `qd_oauth`. */\nexport function stateCookieName(settings: RouteSettings, req: AuthRouteRequest): string {\n return stateSecure(settings, req) ? HOST_OAUTH_STATE_COOKIE : OAUTH_STATE_COOKIE;\n}\n\n/**\n * The state cookie's settings: HttpOnly, Lax whatever the session cookie's\n * SameSite (the callback is a cross-site navigation from the provider, which\n * a Strict cookie would not ride), and for this host only; on `/` when it is\n * Secure, as its `__Host-` name requires, else on the routes' path only.\n * Pass `live` to set it, leave it out to clear it.\n */\nexport function stateCookieOf(\n settings: RouteSettings,\n req: AuthRouteRequest,\n live = false,\n): CookieSettings {\n const secure = stateSecure(settings, req);\n return {\n httpOnly: true,\n secure,\n sameSite: \"lax\",\n path: secure || settings.basePath === \"\" ? \"/\" : settings.basePath,\n ...(live && { maxAge: OAUTH_STATE_TTL_MS }),\n };\n}\n\n/** Starts a session for `userId` signed in through `provider`, recording where from. */\nexport function issueFor(\n settings: RouteSettings,\n userId: string,\n provider: string,\n req: AuthRouteRequest,\n): Promise<IssuedSession> {\n const userAgent = req.headers[\"user-agent\"];\n const meta = {\n provider,\n ...(typeof userAgent === \"string\" && { userAgent }),\n ...(typeof req.ip === \"string\" && { ip: req.ip }),\n };\n return issueSession(settings.keys, userId, meta, settings.cookie.ttlMs);\n}\n","// `socketAuth` (RFC 0003 section 12.6): the `authenticate` function that\n// `createServer({ auth: { authenticate } })` uses for the sessions the auth\n// routes issue, on sockets and HTTP calls alike.\n//\n// A socket's credential is its handshake's `auth.token` (a bearer token, for\n// native and other cookie-less clients), else the session cookie from the\n// handshake's headers. An HTTP call's is the one the HTTP transport found,\n// the session cookie else the bearer token. No credential: anonymous. A\n// credential that does not stand for a live session is refused with\n// `UNAUTHENTICATED`, checked against the session store on every handshake and\n// call, so a revoked session fails even while its JWT has not expired. A\n// socket's session is recorded (`recordSocketSession`), so the app can end\n// that session's open sockets when it revokes it: wire `createAuthRoutes`'\n// `onRevoke` to `server.access.disconnectUser`.\n//\n// The session cookie is ambient: a browser sends it with a WebSocket\n// handshake that any page opens, and WebSockets are not subject to CORS. So a\n// socket that authenticates with the cookie must come from an allowed page:\n// its `Origin` header must be in `allowedOrigins`. A handshake with no\n// `Origin` is refused too, except a same-origin one (`Sec-Fetch-Site:\n// same-origin`, which a browser sends on its own page's long-polling requests\n// and which no other page can forge), or with `allowMissingOrigin` for native\n// clients that keep cookies. A bearer token is not ambient and needs no\n// Origin.\n//\n// An HTTP call that sends the cookie gets the same check (finding F7.1 of the\n// quickdraw-chat review; before rc.5 it relied on its required JSON content\n// type and the app's CORS alone): an `Origin` outside `allowedOrigins` is\n// answered `FORBIDDEN`. A call without `Origin` is accepted, since a browser\n// sends one with every POST: it comes from curl or a server rendering a page\n// with the user's cookie, unless `Sec-Fetch-Site` says another site sent it.\n//\n// The cookie is read under the names the auth routes' rule gives the\n// handshake (`sessionCookieNamesFor`): a configured `cookieName`; else\n// `session` when `COOKIE_DOMAIN` gives the cookie a domain; else, over HTTPS,\n// only `__Host-session` (a site under the same parent domain can plant a\n// plain `session` cookie, and a secure handshake must not take it in place of\n// the host-only one), and over plain HTTP `__Host-session`, then `session`:\n// the host-only cookie wins wherever it is present.\n//\n// `devCredentials` (finding F2.13 of the quickdraw-chat migration) signs a\n// socket in by the user id its handshake names (`auth: { userId }`), for a\n// game editor or load-test bots in development. It runs only when\n// `NODE_ENV` is `development` or `test` (`../devSignIns.ts`; an unset\n// `NODE_ENV`, `\"prod\"` or `\"staging\"` let it through in 5.0, the 5.1\n// audit's AUTH-3): `socketAuth` throws when it is given otherwise, a\n// handshake is refused it once `NODE_ENV` says otherwise, and `createServer`\n// warns at startup while it is on.\n\nimport type { IncomingHttpHeaders } from \"node:http\";\nimport { QuickdrawError } from \"../../../protocol/errors\";\nimport {\n markSocketAuth,\n recordSocketSession,\n type AuthenticateRequest,\n type SocketAuthenticateRequest,\n} from \"../../transports/auth\";\nimport { cookiesOf, cookieToken, transportCookieNaming } from \"../../transports/body\";\nimport type { MaybePromise, Principal } from \"../../types\";\nimport { developmentSignInsAllowed, nodeEnvLabel } from \"../devSignIns\";\nimport {\n sessionCookieNamesFor,\n type SessionCookieNaming,\n type SessionCookieRequest,\n} from \"../sessionCookie\";\nimport { originAllowlist, type AllowedOrigin, type OriginAllowlist } from \"./origins\";\nimport type { AuthSession, SessionStore } from \"./sessions\";\nimport { checkSessionKeys, liveSession } from \"./tokens\";\n\n/** Builds the principal of a session's user; `null` refuses the connection. */\nexport type PrincipalLoader<P extends Principal> = (\n userId: string,\n session: AuthSession,\n) => MaybePromise<P | null | undefined>;\n\n/** Options of {@link socketAuth}. */\nexport interface SocketAuthOptions<P extends Principal = Principal> {\n /** The store the auth routes write sessions to. */\n readonly sessions: SessionStore;\n /** The secret the auth routes sign session JWTs with. */\n readonly jwtSecret: string;\n /**\n * The web app's origins, the same list as `createAuthRoutes`' `allowedOrigins`:\n * the pages that may use the session cookie, on a socket and (since rc.5)\n * on an HTTP call, which is answered `FORBIDDEN` from any other `Origin`.\n */\n readonly allowedOrigins: readonly AllowedOrigin[];\n /**\n * Accept a cookie-authenticated handshake that has no `Origin` header, for\n * native clients that keep cookies. Default `false`. A handshake whose\n * `Origin` is not allowed is refused either way. HTTP calls do not need\n * it: one without `Origin` is accepted (a browser sends `Origin` with every\n * POST, so it is not a page's), unless `Sec-Fetch-Site` names another site.\n */\n readonly allowMissingOrigin?: boolean;\n /**\n * The session cookie's name. Default: the name the auth routes set on the\n * same handshake: `\"session\"` when `COOKIE_DOMAIN` gives the cookie a\n * domain; else `\"__Host-session\"` over HTTPS, where the plain name a\n * sibling site could plant is never read, and `\"session\"` over plain HTTP,\n * where `\"__Host-session\"` is read first all the same. A name given here is the only one\n * read, on any handshake: give the routes' `cookie.name` here, or\n * `\"session\"` when their `cookie.domain` is set without `COOKIE_DOMAIN`. A\n * name the handshake repeats counts as no credential.\n */\n readonly cookieName?: string;\n /**\n * Builds the principal, for example with `kind` and the user's\n * `serviceAccess`; its `userId` must be the session's. Default\n * `{ userId, kind: \"user\" }`, whose grants `createServer`'s\n * `auth.loadServiceAccess` then loads.\n */\n readonly loadPrincipal?: PrincipalLoader<P>;\n /**\n * Development sign-in without a session: a socket whose handshake carries\n * `auth: { userId }` and no token is that user, as this function answers:\n * their principal (its `userId` the one named), or `null` to refuse the\n * handshake (an unknown id). For a game editor or load-test bots. Only\n * while `NODE_ENV` is `development` or `test`: `socketAuth` throws when it\n * is given otherwise (an unset `NODE_ENV` included: set\n * `NODE_ENV=development`), refuses such a handshake once `NODE_ENV` says\n * otherwise, and `createServer` warns at startup while it is on. HTTP calls\n * never use it. Pass it only when the app's own flag is on:\n * `devCredentials: env.ENABLE_DEV_CREDENTIALS ? findDevUser : undefined`.\n */\n readonly devCredentials?: DevCredentials<P>;\n}\n\n/** `socketAuth`'s `devCredentials`: the principal of the user a development handshake names, or `null`. */\nexport type DevCredentials<P extends Principal> = (\n userId: string,\n) => MaybePromise<P | null | undefined>;\n\n/** What {@link socketAuth} returns: an `authenticate` for `createServer`'s `auth`. */\nexport type SessionAuthenticate<P extends Principal> = (\n request: AuthenticateRequest,\n) => Promise<P | null>;\n\ninterface Credential {\n readonly token: string;\n /** True for the ambient session cookie: a socket's, or an HTTP call's (finding F7.1). */\n readonly checkOrigin: boolean;\n}\n\n/** A socket's handshake, as its session cookie's name depends on it: its headers, and whether TLS ended here. */\nfunction handshakeOf(request: SocketAuthenticateRequest): SessionCookieRequest {\n const { handshake } = request.socket as Partial<\n Pick<SocketAuthenticateRequest[\"socket\"], \"handshake\">\n >;\n return { headers: request.headers, secure: handshake?.secure === true };\n}\n\nfunction credentialOf(\n request: AuthenticateRequest,\n naming: SessionCookieNaming,\n): Credential | null {\n if (request.transport === \"http\") {\n // The session cookie, which the transport hands every authenticate beside\n // auth.token: an app's wrapper around socketAuth, which the transport\n // does not know as socketAuth's, keeps cookie sign-in and its Origin rule.\n const { sessionCookie } = request;\n if (typeof sessionCookie === \"string\" && sessionCookie !== \"\") {\n return { token: sessionCookie, checkOrigin: true };\n }\n }\n const { token } = request.auth;\n if (typeof token === \"string\" && token !== \"\") {\n // The HTTP transport found it, and says whether it was the session cookie.\n return { token, checkOrigin: request.transport === \"http\" && request.credential === \"cookie\" };\n }\n if (request.transport === \"http\") {\n return null;\n }\n const names = sessionCookieNamesFor(handshakeOf(request), naming);\n const cookie = cookieToken(cookiesOf({ headers: request.headers }), names);\n return cookie === null ? null : { token: cookie, checkOrigin: true };\n}\n\nfunction originAccepted(\n headers: IncomingHttpHeaders,\n origins: OriginAllowlist,\n allowMissing: boolean,\n): boolean {\n const { origin } = headers;\n if (origin !== undefined) {\n return origins.allowed(origin) !== null;\n }\n return allowMissing || headers[\"sec-fetch-site\"] === \"same-origin\";\n}\n\n/**\n * The same rule for an HTTP call that sends the session cookie. An `Origin`\n * must be allowed, as on a socket. Without one it differs: the transport\n * serves only POST, and a browser sends `Origin` with every POST (the Fetch\n * standard; a socket's long-polling handshake is a GET, which a same-origin\n * page or an `<img>` sends without one), so a call without it is not a\n * page's: curl, or a server rendering a page with the user's forwarded\n * cookie (`createServerCaller`). It is accepted, unless the browser's fetch\n * metadata says another site sent it.\n */\nfunction httpOriginAccepted(headers: IncomingHttpHeaders, origins: OriginAllowlist): boolean {\n const { origin } = headers;\n if (origin !== undefined) {\n return origins.allowed(origin) !== null;\n }\n const site = headers[\"sec-fetch-site\"];\n return site === undefined || site === \"same-origin\";\n}\n\n/** The allowlists `cookieOriginAllowed` compiled, by the list it was given. */\nconst compiled = new WeakMap<readonly AllowedOrigin[], OriginAllowlist>();\n\n/** A request whose credential came from the session cookie: its headers, and the transport of a call. */\nexport interface CookieOriginRequest {\n readonly headers: IncomingHttpHeaders;\n /** `\"socket\"` applies the handshake's rule; anything else (an HTTP call, a REST route) the HTTP one. */\n readonly transport?: string;\n}\n\n/**\n * The rule `socketAuth` applies to a request that authenticates with the\n * session cookie, for a custom `authenticate` or route: true when the page\n * that sent it may use the cookie. Its `Origin` must be in `allowedOrigins`\n * (the `createAuthRoutes` list: exact origins or anchored patterns). Without\n * one, an HTTP request (a browser sends `Origin` with every POST, so it is\n * curl or a server forwarding the user's cookie) is accepted unless\n * `Sec-Fetch-Site` names another site, and a socket handshake only when\n * `Sec-Fetch-Site` is `same-origin` or with `allowMissingOrigin`. A bearer\n * token is not ambient: check only a credential that came from the cookie\n * (`request.sessionCookie` on an HTTP `authenticate` request, where the\n * transport hands an `authenticate` other than socketAuth's the cookie; it is\n * in `auth.token` only for socketAuth's). Then check the session against the\n * store, on every call (`liveSession`).\n *\n * @example\n * authenticate: async (request) => {\n * if (request.transport === \"http\" && request.sessionCookie !== undefined) {\n * if (!cookieOriginAllowed(request, allowedOrigins)) {\n * throw new QuickdrawError(\"FORBIDDEN\", \"This page may not use the session cookie\");\n * }\n * const session = await liveSession({ sessions, jwtSecret }, request.sessionCookie);\n * // ...\n * }\n * // ...\n * }\n */\nexport function cookieOriginAllowed(\n request: CookieOriginRequest,\n allowedOrigins: readonly AllowedOrigin[],\n options: { readonly allowMissingOrigin?: boolean } = {},\n): boolean {\n let origins = compiled.get(allowedOrigins);\n if (origins === undefined) {\n origins = originAllowlist(allowedOrigins, \"cookieOriginAllowed\", true);\n compiled.set(allowedOrigins, origins);\n }\n return request.transport === \"socket\"\n ? originAccepted(request.headers, origins, options.allowMissingOrigin === true)\n : httpOriginAccepted(request.headers, origins);\n}\n\n/** Whether an HTTP request that sends the session cookie may use it, against a compiled allowlist. */\nexport function httpCookieOriginAllowed(\n headers: IncomingHttpHeaders,\n origins: OriginAllowlist,\n): boolean {\n return httpOriginAccepted(headers, origins);\n}\n\n/** What a page whose Origin may not use the session cookie is told. */\nexport const COOKIE_REFUSED =\n \"This page's origin may not use the session cookie; send it from an allowed origin or use a bearer token\";\n\n/** Refuses a session cookie used from a page `allowedOrigins` does not list. */\nfunction checkCookieOrigin(\n request: AuthenticateRequest,\n origins: OriginAllowlist,\n allowMissing: boolean,\n): void {\n if (request.transport === \"http\") {\n if (!httpOriginAccepted(request.headers, origins)) {\n // FORBIDDEN, which the HTTP transport answers as it is: the session is fine, the page is not.\n throw new QuickdrawError(\"FORBIDDEN\", COOKIE_REFUSED);\n }\n return;\n }\n if (!originAccepted(request.headers, origins, allowMissing)) {\n throw refused(\"This page's origin may not use the session cookie on a socket\");\n }\n}\n\nfunction refused(message: string): QuickdrawError {\n return new QuickdrawError(\"UNAUTHENTICATED\", message);\n}\n\n/** `devCredentials`, checked: a function, and only under NODE_ENV development or test. */\nfunction checkDevCredentials(\n devCredentials: DevCredentials<Principal> | undefined,\n): DevCredentials<Principal> | undefined {\n if (devCredentials === undefined) {\n return undefined;\n }\n if (typeof devCredentials !== \"function\") {\n throw new TypeError(\"socketAuth: devCredentials must be a function of the user id\");\n }\n if (!developmentSignInsAllowed()) {\n throw new TypeError(\n `socketAuth: devCredentials signs sockets in by a user id alone, so it runs only when NODE_ENV is development or test (NODE_ENV is ${nodeEnvLabel()}); set NODE_ENV=development to use it`,\n );\n }\n return devCredentials;\n}\n\n/** The user a development socket handshake names (`auth: { userId }` without a token), or `undefined`. */\nfunction devUserOf(request: AuthenticateRequest): string | undefined {\n const { token, userId } = request.auth;\n const tokenless = token === undefined || token === null || token === \"\";\n return request.transport === \"socket\" && tokenless && typeof userId === \"string\" && userId !== \"\"\n ? userId\n : undefined;\n}\n\n/** The principal of a development handshake's user; refused outside development and tests, and for an unknown user. */\nasync function devPrincipal(\n devCredentials: DevCredentials<Principal>,\n userId: string,\n): Promise<Principal> {\n if (!developmentSignInsAllowed()) {\n throw refused(\"Development credentials are refused unless NODE_ENV is development or test\");\n }\n const principal = await devCredentials(userId);\n if (principal === null || principal === undefined) {\n throw refused(\"No development user has that id\");\n }\n if (principal.userId !== userId) {\n throw new TypeError(\"socketAuth: devCredentials must return the principal of the user named\");\n }\n return principal;\n}\n\n/**\n * The principal of a live session's user: `{ userId, kind: \"user\" }`, or\n * what `loadPrincipal` builds. `UNAUTHENTICATED` when it builds none; a\n * `TypeError` naming `owner` when it builds another user's.\n */\nexport async function principalOfSession<P extends Principal>(\n session: AuthSession,\n loadPrincipal: PrincipalLoader<P> | undefined,\n owner: string,\n): Promise<P | Principal> {\n if (loadPrincipal === undefined) {\n return { userId: session.userId, kind: \"user\" };\n }\n const principal = await loadPrincipal(session.userId, session);\n if (principal === null || principal === undefined) {\n throw refused(\"The session's user has no principal\");\n }\n if (principal.userId !== session.userId) {\n throw new TypeError(`${owner}: loadPrincipal must return the principal of the session's user`);\n }\n return principal;\n}\n\n/**\n * The `authenticate` for `createServer` over the auth routes' sessions:\n * `createServer({ auth: { authenticate: socketAuth({ sessions, jwtSecret, allowedOrigins }) } })`.\n */\nexport function socketAuth<P extends Principal>(\n options: SocketAuthOptions<P> & { readonly loadPrincipal: PrincipalLoader<P> },\n): SessionAuthenticate<P>;\nexport function socketAuth(options: SocketAuthOptions): SessionAuthenticate<Principal>;\nexport function socketAuth(options: SocketAuthOptions): SessionAuthenticate<Principal> {\n const keys = checkSessionKeys(options, \"socketAuth\");\n const origins = originAllowlist(options.allowedOrigins, \"socketAuth\", true);\n const naming = transportCookieNaming(options.cookieName);\n const allowMissing = options.allowMissingOrigin === true;\n const { loadPrincipal } = options;\n if (loadPrincipal !== undefined && typeof loadPrincipal !== \"function\") {\n throw new TypeError(\"socketAuth: loadPrincipal must be a function\");\n }\n const devCredentials = checkDevCredentials(options.devCredentials);\n const authenticate: SessionAuthenticate<Principal> = async (request) => {\n const devUser = devCredentials === undefined ? undefined : devUserOf(request);\n if (devCredentials !== undefined && devUser !== undefined) {\n return await devPrincipal(devCredentials, devUser);\n }\n const credential = credentialOf(request, naming);\n if (credential === null) {\n return null;\n }\n if (credential.checkOrigin) {\n checkCookieOrigin(request, origins, allowMissing);\n }\n const session = await liveSession(keys, credential.token);\n if (session === null) {\n throw refused(\"The session is not valid\");\n }\n if (request.transport === \"socket\") {\n // So `server.access.disconnectUser(userId, { sessionId })` can end this session's sockets.\n recordSocketSession(request.socket, session.id, session.expiresAt);\n }\n return await principalOfSession(session, loadPrincipal, \"socketAuth\");\n };\n // So the HTTP transport hands it the session cookie as auth.token, and the server knows it.\n markSocketAuth(authenticate, { devCredentials: devCredentials !== undefined });\n return authenticate;\n}\n","// The guest provider of the auth routes kit (RFC 0003 section 12.6): `POST\n// {basePath}/guest` creates a user through the app's `createUser` and signs\n// it in with an ordinary session, so everything downstream (socket auth,\n// access policies) sees a real user id rather than an anonymous socket. Like\n// every POST route of the kit it needs a JSON body type, which a cross-site\n// form cannot send, and a page `allowedOrigins` does not list is refused\n// (403) before anything is created, since the route sets the session cookie:\n// a page elsewhere cannot sign a visitor in as a guest, even with CORS help\n// (the 5.1 audit's AUTH-5).\n\nimport { INTERNAL_MESSAGE, QuickdrawError } from \"../../../protocol/errors\";\nimport { readJsonInput } from \"../../transports/body\";\nimport type { MaybePromise } from \"../../types\";\nimport { refuse, sendJson, type AuthRouteRequest, type AuthRouteResponse } from \"./respond\";\nimport { issueFor, setSession, type RouteSettings } from \"./settings\";\nimport { COOKIE_REFUSED, httpCookieOriginAllowed } from \"./socketAuth\";\nimport { errorText } from \"../../../contract/logger\";\n\n/** The largest guest request body read, in bytes. */\nexport const GUEST_MAX_BODY_BYTES = 16_384;\n\n/** The guest `createUser` made: its id, and the name it ended up with (`\"Ada#4821\"` for a taken \"Ada\"). */\nexport interface GuestUser {\n readonly userId: string;\n readonly name?: string;\n}\n\n/** The guest provider's options. */\nexport interface GuestOptions {\n /**\n * Creates the guest user and returns its id, or `{ userId, name }` when\n * the name it gave differs from the one asked for (a numbered one after a\n * collision): the route answers that name. `input` is the request's JSON\n * body (`undefined` without one), unchecked: validate it here. Throw a\n * `QuickdrawError` to refuse with its code, such as `VALIDATION` for a bad\n * name or `CONFLICT` for a taken one.\n */\n readonly createUser: (\n input: unknown,\n request: AuthRouteRequest,\n ) => MaybePromise<string | GuestUser>;\n /**\n * Also answer the session's token, for clients that keep no cookies (a\n * game engine, a page in a third-party iframe) and send it as\n * `auth.token` or a bearer token. Unlike the HttpOnly cookie, the token is\n * then readable by the page's scripts. Default `false`.\n */\n readonly token?: boolean;\n}\n\n/** The guest provider: `POST {basePath}/guest`. */\nexport interface GuestProvider {\n readonly kind: \"guest\";\n readonly id: \"guest\";\n readonly options: GuestOptions;\n}\n\n/** Signing in as a new guest user: `POST {basePath}/guest` with a JSON body. */\nexport function guest(options: GuestOptions): GuestProvider {\n if (typeof options.createUser !== \"function\") {\n throw new TypeError(\"guest(): createUser is required\");\n }\n if (options.token !== undefined && typeof options.token !== \"boolean\") {\n throw new TypeError(\"guest(): token must be true or false\");\n }\n return Object.freeze({ kind: \"guest\", id: \"guest\", options });\n}\n\n/** What `createUser` returned, read: the user's id and name; a `TypeError` for anything else. */\nfunction guestUserOf(made: unknown): GuestUser {\n if (typeof made === \"string\" && made !== \"\") {\n return { userId: made };\n }\n const { userId, name } = (typeof made === \"object\" && made !== null ? made : {}) as {\n readonly userId?: unknown;\n readonly name?: unknown;\n };\n if (\n typeof userId !== \"string\" ||\n userId === \"\" ||\n (name !== undefined && typeof name !== \"string\")\n ) {\n throw new TypeError(\"guest(): createUser must return the new user's id, or { userId, name? }\");\n }\n return name === undefined ? { userId } : { userId, name };\n}\n\n/** The user `createUser` made, or the failure already answered. */\nasync function createGuest(\n settings: RouteSettings,\n provider: GuestProvider,\n req: AuthRouteRequest,\n res: AuthRouteResponse,\n): Promise<GuestUser | null> {\n try {\n const input = await readJsonInput(req, GUEST_MAX_BODY_BYTES);\n return guestUserOf(await provider.options.createUser(input, req));\n } catch (error) {\n if (error instanceof QuickdrawError && error.code !== \"INTERNAL\") {\n refuse(res, error.code, error.message);\n return null;\n }\n settings.logger.error(\"Creating a guest user failed\", {\n category: \"quickdraw.auth\",\n error: errorText(error),\n });\n refuse(res, \"INTERNAL\", INTERNAL_MESSAGE);\n return null;\n }\n}\n\n/**\n * `POST {basePath}/guest`: a new guest user, signed in; answers\n * `{ userId, name? }`, plus `token` when the provider says so.\n */\nexport function guestRoute(\n settings: RouteSettings,\n provider: GuestProvider,\n): (req: AuthRouteRequest, res: AuthRouteResponse) => Promise<void> {\n return async (req, res) => {\n // It sets the session cookie: never for a page allowedOrigins does not list (AUTH-5).\n if (!httpCookieOriginAllowed(req.headers, settings.origins)) {\n refuse(res, \"FORBIDDEN\", COOKIE_REFUSED);\n return;\n }\n const user = await createGuest(settings, provider, req, res);\n if (user === null) {\n return;\n }\n const { userId } = user;\n const { token } = await issueFor(settings, userId, provider.id, req);\n setSession(res, settings, req, token);\n settings.logger.info(\"Signed in a new guest\", { category: \"quickdraw.auth\", userId });\n sendJson(res, 200, provider.options.token === true ? { ...user, token } : user);\n };\n}\n","// The rate limits on the auth routes (RFC 0003 section 12.6). By default the\n// sign-in routes (each provider's start and callback, and the guest route)\n// share `createAuthLimiter({ max: SIGN_IN_LIMIT })` (60 requests per 15\n// minutes per IP: a start and a callback per sign-in, and a shared office or\n// school address, use 4.1's 20 up quickly), the session routes (`me`,\n// `logout`, `logout-all`) share `createAuthStatusLimiter` (120), and\n// `providers` has `createPublicApiLimiter` (60 per minute) to itself, so the\n// login pages of one address cannot use up its sign-outs (finding F11.3 of\n// the quickdraw-chat migration). The presets live in `./server/express`,\n// which needs the optional peer `express-rate-limit`; the routes import them\n// only when a default is used, so importing `./server/auth` for its JWT\n// helpers alone does not need the peer.\n\n/** How many sign-in requests one IP may make per 15 minutes under the default limiter. */\nexport const SIGN_IN_LIMIT = 60;\n\n/** An Express middleware, such as an `express-rate-limit` limiter: it answers the request, or calls `next()`. */\nexport type AuthMiddleware = (req: never, res: never, next: (error?: unknown) => void) => unknown;\n\n/** The limiters of the auth routes. Each one left out gets its default. */\nexport interface AuthRateLimits {\n /**\n * Each provider's start and callback, and the guest route. Default:\n * `createAuthLimiter({ max: 60 })`, 60 requests per 15 minutes per IP.\n */\n readonly signIn?: AuthMiddleware;\n /** `me`, `logout` and `logout-all`. Default: `createAuthStatusLimiter()`. */\n readonly session?: AuthMiddleware;\n /**\n * `providers`, the sign-ins a login page offers. Default:\n * `createPublicApiLimiter()`, 60 requests per minute per IP, apart from\n * `session`'s, so loading login pages cannot use up sign-outs.\n */\n readonly providers?: AuthMiddleware;\n}\n\n/** Which limiter a route is counted by. */\nexport type LimitKind = keyof AuthRateLimits;\n\n/** The limiters in use; none with `rateLimit: false`. */\nexport type Limiters = Readonly<Partial<Record<LimitKind, AuthMiddleware>>>;\n\n/** Throws a `TypeError` unless `option` is `false`, absent, or limiters that are functions. */\nexport function checkRateLimits(option: unknown): AuthRateLimits | false {\n if (option === false) {\n return false;\n }\n if (option === undefined) {\n return {};\n }\n if (typeof option !== \"object\" || option === null) {\n throw new TypeError(\n \"createAuthRoutes: rateLimit must be { signIn?, session?, providers? } or false\",\n );\n }\n for (const [name, limiter] of Object.entries(option)) {\n if (limiter !== undefined && typeof limiter !== \"function\") {\n throw new TypeError(`createAuthRoutes: rateLimit.${name} must be an Express middleware`);\n }\n }\n return option as AuthRateLimits;\n}\n\n/**\n * The limiters the routes use: the app's, and the default presets for the\n * ones it left out. Rejects when a preset is needed and `express-rate-limit`\n * is not installed.\n */\nexport async function loadLimiters(option: AuthRateLimits | false): Promise<Limiters> {\n if (option === false) {\n return {};\n }\n if (\n option.signIn !== undefined &&\n option.session !== undefined &&\n option.providers !== undefined\n ) {\n return option;\n }\n const presets = await import(\"../../express/rateLimit\");\n return {\n signIn: option.signIn ?? presets.createAuthLimiter({ max: SIGN_IN_LIMIT }),\n session: option.session ?? presets.createAuthStatusLimiter(),\n providers: option.providers ?? presets.createPublicApiLimiter(),\n };\n}\n","// The mock provider inside the auth routes: `registerMockOAuthProvider`\n// mounts its endpoints (authorize, token, userinfo) on the routes' own table\n// under `{basePath}/mock/provider`, and the sign-in drives them like any\n// OAuth provider. Nothing is mounted unless `isMockOAuthEnabled()`, and every\n// request checks it again, as the mock's own endpoints check `NODE_ENV`.\n\nimport {\n createMockOAuthProvider,\n isMockOAuthEnabled,\n registerMockOAuthProvider,\n type MockOAuthRouter,\n} from \"../mock\";\nimport { createOAuthURL, exchangeOAuthCode, type OAuthConfig } from \"../oauth\";\nimport type { SignInFlow } from \"./oauth\";\nimport type { MockSignInProvider } from \"./providers\";\nimport type { AuthRouteRequest, AuthRouteResponse } from \"./respond\";\nimport type { RouteSettings } from \"./settings\";\n\n/** Where the routes keep a handler the mock provider registers. */\nexport type MountHandler = (\n method: \"GET\" | \"POST\",\n path: string,\n handle: (req: AuthRouteRequest, res: AuthRouteResponse) => unknown,\n) => void;\n\n/**\n * The mock provider's sign-in, its endpoints mounted through `mount`, or\n * `null` when `isMockOAuthEnabled()` is false (nothing is mounted then).\n */\nexport function mockFlow(\n provider: MockSignInProvider,\n settings: RouteSettings,\n mount: MountHandler,\n): SignInFlow | null {\n const pathPrefix = `${settings.basePath}/mock/provider`;\n // The mock's handlers take Express's request and response, which the routes are given.\n const router: MockOAuthRouter = {\n get: (path, handler) => {\n mount(\"GET\", path, (req, res) => handler(req as never, res as never));\n },\n post: (path, handler) => {\n mount(\"POST\", path, (req, res) => handler(req as never, res as never));\n },\n };\n const mounted = registerMockOAuthProvider(router, {\n listUsers: provider.options.listUsers,\n pathPrefix,\n allowedRedirectOrigins: [new URL(settings.publicUrl).origin],\n logger: settings.logger,\n });\n if (!mounted) {\n return null;\n }\n const oauth = createMockOAuthProvider(settings.publicUrl, {\n pathPrefix,\n ...(provider.options.internalUrl !== undefined && {\n internalBaseUrl: provider.options.internalUrl,\n }),\n });\n const config = (redirectUri: string): OAuthConfig => ({\n clientId: \"mock\",\n clientSecret: \"mock\",\n redirectUri,\n });\n return Object.freeze({\n kind: \"oauth\",\n id: provider.id,\n enabled: isMockOAuthEnabled,\n authorizeUrl: (state: string, redirectUri: string) =>\n createOAuthURL(oauth, config(redirectUri), state),\n async profile(code: string, redirectUri: string) {\n const { tokens, user } = await exchangeOAuthCode(oauth, config(redirectUri), code);\n return {\n providerAccountId: user.id,\n email: user.email,\n emailVerified: true,\n name: user.name,\n image: user.picture,\n tokens,\n raw: user,\n };\n },\n });\n}\n\n/**\n * Whether a mounted mock provider still serves sign-ins: its routes check\n * `isMockOAuthEnabled()` on every request, so `GET {basePath}/providers`\n * lists it only while that holds.\n */\nexport function mockServes(): boolean {\n return isMockOAuthEnabled();\n}\n","// The OAuth routes of the auth kit (RFC 0003 section 12.6): `GET\n// {basePath}/{provider}/start` and `GET {basePath}/{provider}/callback`.\n//\n// `start` validates the return origin (`?returnTo=`, an origin or a URL on\n// it; the first exact allowed origin when absent), keeps it with a new state\n// in the state cookie (`state.ts`) and redirects to the provider. The\n// callback clears that cookie first, whatever happens next; validates the\n// remembered origin again before redirecting anywhere; checks and redeems the\n// state; exchanges the code; asks the app's `onLogin` for the user id;\n// creates the session and sets its cookie; and redirects to the return\n// origin. A failure after the origin is known redirects there with\n// `?error=state` (state missing, wrong, expired or already redeemed),\n// `denied` (the provider or `onLogin` refused) or `failed` (the exchange,\n// `onLogin` or the session store failed).\n\nimport { cookiesOf } from \"../../transports/body\";\nimport type { AuthProfile, OAuthSignInProvider } from \"./providers\";\nimport {\n queryOf,\n redirect,\n refuse,\n type AuthRouteRequest,\n type AuthRouteResponse,\n} from \"./respond\";\nimport {\n issueFor,\n landingOf,\n redirectUriOf,\n setSession,\n stateCookieName,\n stateCookieOf,\n type RouteSettings,\n} from \"./settings\";\nimport { decodePending, encodePending, newState, stateMatches } from \"./state\";\nimport { errorText } from \"../../../contract/logger\";\n\n/** A provider the routes drive; `enabled` is checked again on every request (the mock provider's). */\nexport interface SignInFlow extends OAuthSignInProvider {\n readonly enabled?: () => boolean;\n}\n\ntype Handler = (req: AuthRouteRequest, res: AuthRouteResponse) => void | Promise<void>;\n\nconst CATEGORY = \"quickdraw.auth\";\n\n/** `GET {basePath}/{provider}/start`. */\nexport function startRoute(settings: RouteSettings, flow: SignInFlow): Handler {\n return (req, res) => {\n if (flow.enabled?.() === false) {\n refuse(res, \"NOT_FOUND\", \"Not found\");\n return;\n }\n const returnTo = queryOf(req, \"returnTo\");\n const origin =\n returnTo === null ? (settings.origins.fallback ?? null) : settings.origins.allowed(returnTo);\n if (origin === null) {\n refuse(res, \"VALIDATION\", \"returnTo is not one of the allowed origins\");\n return;\n }\n const state = newState();\n const location = flow.authorizeUrl(state, redirectUriOf(settings, flow.id));\n const pending = encodePending(\n { state, provider: flow.id, origin, issuedAt: Date.now() },\n settings.stateKey,\n );\n res.cookie(stateCookieName(settings, req), pending, stateCookieOf(settings, req, true));\n redirect(res, location);\n };\n}\n\n/** The user `onLogin` names for the callback's code, or why there is none. */\ntype SignInUser = { readonly userId: string } | { readonly error: \"denied\" | \"failed\" };\n\nasync function userFor(\n settings: RouteSettings,\n flow: SignInFlow,\n code: string,\n): Promise<SignInUser> {\n let profile: AuthProfile;\n try {\n profile = await flow.profile(code, redirectUriOf(settings, flow.id));\n } catch (error) {\n settings.logger.warn(\"A sign-in's code exchange failed\", {\n category: CATEGORY,\n provider: flow.id,\n error: errorText(error),\n });\n return { error: \"failed\" };\n }\n try {\n const userId: unknown = await settings.onLogin(profile, flow.id);\n if (typeof userId === \"string\" && userId !== \"\") {\n return { userId };\n }\n settings.logger.info(\"onLogin refused a sign-in\", { category: CATEGORY, provider: flow.id });\n return { error: \"denied\" };\n } catch (error) {\n settings.logger.error(\"onLogin failed\", {\n category: CATEGORY,\n provider: flow.id,\n error: errorText(error),\n });\n return { error: \"failed\" };\n }\n}\n\n/** Signs the callback's user in and lands on `origin`, or lands there with the error. */\nasync function complete(\n settings: RouteSettings,\n flow: SignInFlow,\n req: AuthRouteRequest,\n res: AuthRouteResponse,\n origin: string,\n): Promise<void> {\n const code = queryOf(req, \"code\");\n if (queryOf(req, \"error\") !== null || code === null || code === \"\") {\n redirect(res, landingOf(settings, origin, \"denied\"));\n return;\n }\n const user = await userFor(settings, flow, code);\n if (\"error\" in user) {\n redirect(res, landingOf(settings, origin, user.error));\n return;\n }\n const { userId } = user;\n let token: string;\n try {\n ({ token } = await issueFor(settings, userId, flow.id, req));\n } catch (error) {\n settings.logger.error(\"Creating a session failed\", {\n category: CATEGORY,\n error: errorText(error),\n });\n redirect(res, landingOf(settings, origin, \"failed\"));\n return;\n }\n // The callback is a navigation with no Origin of its own: name the cookie for the return page.\n setSession(res, settings, req, token, origin);\n settings.logger.info(\"Signed in\", { category: CATEGORY, provider: flow.id, userId });\n redirect(res, landingOf(settings, origin));\n}\n\n/** `GET {basePath}/{provider}/callback`. */\nexport function callbackRoute(settings: RouteSettings, flow: SignInFlow): Handler {\n return async (req, res) => {\n if (flow.enabled?.() === false) {\n refuse(res, \"NOT_FOUND\", \"Not found\");\n return;\n }\n const name = stateCookieName(settings, req);\n // A repeated name reads as \"\": no state, since either could be a planted one.\n const pending = decodePending(cookiesOf(req)[name], settings.stateKey);\n // Single use: the state cookie goes whatever happens next.\n res.clearCookie(name, stateCookieOf(settings, req));\n if (pending === null) {\n // Started in another browser, or more than ten minutes ago: no origin was remembered.\n const { fallback } = settings.origins;\n if (fallback === undefined) {\n refuse(res, \"FORBIDDEN\", \"This sign-in has no state in this browser; start it again\");\n } else {\n redirect(res, landingOf(settings, fallback, \"state\"));\n }\n return;\n }\n // Validated when it was stored, and again before anything redirects to it.\n const origin = settings.origins.allowed(pending.origin);\n if (origin === null) {\n refuse(res, \"VALIDATION\", \"This sign-in's return origin is not allowed\");\n return;\n }\n const now = Date.now();\n const valid = stateMatches(pending, flow.id, queryOf(req, \"state\"), now);\n if (!valid || !settings.redeemed.redeem(pending.state, now)) {\n settings.logger.debug(\"Refused a sign-in callback's state\", {\n category: CATEGORY,\n provider: flow.id,\n });\n redirect(res, landingOf(settings, origin, \"state\"));\n return;\n }\n await complete(settings, flow, req, res, origin);\n };\n}\n","// The session routes of the auth kit (RFC 0003 section 12.6). Each reads the\n// session the request carries the way the HTTP transport does: the session\n// cookie, under the names the shared rule gives the request\n// (`sessionCookieNamesFor`), else an `Authorization: Bearer` token. A cookie\n// sent from a page `allowedOrigins` does not list is answered 403\n// `FORBIDDEN`, as `socketAuth` answers it on the HTTP transport (rule F7.1;\n// the 5.1 audit's AUTH-5: a cross-site page with CORS help could read the\n// user id or sign the user out everywhere); a bearer token is not ambient,\n// so any page may send it.\n//\n// - `GET {basePath}/me`: `{ userId }` for a live session, else 401. The\n// answer is the same whether the request had no credential, a forged or\n// expired one, or one whose session was revoked.\n// - `POST {basePath}/logout`: revokes the request's session, if it names\n// one, and clears the cookie; 204 either way.\n// - `POST {basePath}/logout-all`: needs a live session; revokes every session\n// of its user and clears the cookie (204), else 401.\n//\n// Each revocation is told to the app's `onRevoke(userId, sessionId | null)`,\n// which can end the sockets still open with the session\n// (`server.access.disconnectUser`).\n\nimport { readVerifiedJWT } from \"../jwt\";\nimport { httpCredentialOf } from \"../../transports/body\";\nimport {\n noContent,\n refuse,\n sendJson,\n type AuthRouteRequest,\n type AuthRouteResponse,\n} from \"./respond\";\nimport type { AuthSession } from \"./sessions\";\nimport { clearSession, sessionCookieNaming, type RouteSettings } from \"./settings\";\nimport { COOKIE_REFUSED, httpCookieOriginAllowed } from \"./socketAuth\";\nimport { liveSession } from \"./tokens\";\nimport { errorText } from \"../../../contract/logger\";\n\ntype Handler = (req: AuthRouteRequest, res: AuthRouteResponse) => Promise<void>;\n\nconst NOT_SIGNED_IN = \"Not signed in\";\n\n/**\n * The token the request carries (the session cookie, else a bearer token),\n * or `null`; `undefined` once the route has answered 403: the cookie came\n * from a page `allowedOrigins` does not list.\n */\nfunction tokenFor(\n settings: RouteSettings,\n req: AuthRouteRequest,\n res: AuthRouteResponse,\n): string | null | undefined {\n const credential = httpCredentialOf(req, sessionCookieNaming(settings));\n if (credential?.from === \"cookie\" && !httpCookieOriginAllowed(req.headers, settings.origins)) {\n refuse(res, \"FORBIDDEN\", COOKIE_REFUSED);\n return undefined;\n }\n return credential?.token ?? null;\n}\n\n/** The live session `token` names, or `null`. */\nfunction sessionOf(settings: RouteSettings, token: string | null): Promise<AuthSession | null> {\n return token === null ? Promise.resolve(null) : liveSession(settings.keys, token);\n}\n\n/** `GET {basePath}/me`. */\nexport function meRoute(settings: RouteSettings): Handler {\n return async (req, res) => {\n const token = tokenFor(settings, req, res);\n if (token === undefined) {\n return;\n }\n const session = await sessionOf(settings, token);\n if (session === null) {\n refuse(res, \"UNAUTHENTICATED\", NOT_SIGNED_IN);\n return;\n }\n sendJson(res, 200, { userId: session.userId });\n };\n}\n\n/** Tells the app's `onRevoke` a session (or, `null`, every one) of `userId` ended; a failure is logged. */\nasync function revoked(\n settings: RouteSettings,\n userId: string,\n sessionId: string | null,\n): Promise<void> {\n try {\n await settings.onRevoke?.(userId, sessionId);\n } catch (error) {\n settings.logger.error(\"onRevoke failed\", {\n category: \"quickdraw.auth\",\n userId,\n error: errorText(error),\n });\n }\n}\n\n/** `POST {basePath}/logout`. */\nexport function logoutRoute(settings: RouteSettings): Handler {\n return async (req, res) => {\n const token = tokenFor(settings, req, res);\n if (token === undefined) {\n return;\n }\n const payload = token === null ? null : await readVerifiedJWT(token, settings.keys.jwtSecret);\n if (payload?.sid !== undefined) {\n await settings.keys.sessions.revoke(payload.sid);\n settings.logger.info(\"Signed out\", { category: \"quickdraw.auth\", userId: payload.userId });\n await revoked(settings, payload.userId, payload.sid);\n }\n clearSession(res, settings, req);\n noContent(res);\n };\n}\n\n/** `POST {basePath}/logout-all`. */\nexport function logoutAllRoute(settings: RouteSettings): Handler {\n return async (req, res) => {\n const token = tokenFor(settings, req, res);\n if (token === undefined) {\n return;\n }\n const session = await sessionOf(settings, token);\n clearSession(res, settings, req);\n if (session === null) {\n refuse(res, \"UNAUTHENTICATED\", NOT_SIGNED_IN);\n return;\n }\n await settings.keys.sessions.revokeAll(session.userId);\n settings.logger.info(\"Signed out everywhere\", {\n category: \"quickdraw.auth\",\n userId: session.userId,\n });\n await revoked(settings, session.userId, null);\n noContent(res);\n };\n}\n","// `createAuthRoutes` (RFC 0003 section 12.6): the sign-in surface every app\n// used to copy, as one Express middleware the app mounts. It assembles the\n// 4.1 helpers (OAuth URL and code exchange, the Google, Discord and mock\n// providers, JWTs, the session cookie, origin validation and the Express rate\n// limits); the app supplies how a profile becomes its user (`onLogin`) and\n// where sessions are stored (`SessionStore`). `socketAuth` authenticates the\n// sessions it issues on the server's sockets and HTTP calls.\n//\n// The routes, under `basePath` (default `/auth`):\n//\n// | Route | Limiter | Answer |\n// |---|---|---|\n// | `GET /{provider}/start?returnTo=` | sign-in | 302 to the provider |\n// | `GET /{provider}/callback` | sign-in | 302 to the return origin, with the session cookie |\n// | `POST /guest` (with `guest()`) | sign-in | `{ userId }`, with the session cookie |\n// | `GET /providers` | providers | `{ providers: [{ id, name, kind }] }`: the sign-ins served now |\n// | `GET /me` | session | `{ userId }`, or 401 |\n// | `POST /logout` | session | 204 |\n// | `POST /logout-all` | session | 204, or 401 |\n// | `/mock/provider/*` (with `mock()`, development only) | none | the mock provider |\n//\n// A POST route needs `Content-Type: application/json`, like the HTTP\n// transport's calls: a cross-site form cannot send it, and a cross-site\n// script cannot without a CORS preflight the app's CORS policy refuses.\n//\n// `GET /providers` (and `routes.providers()`) says which sign-ins are\n// served, so a login page offers only those (finding F9.1 of the owner's QA\n// of the template: a Google button answered 404 where the instance had no\n// Google credentials): a provider `google.optional` built nothing for is not\n// in it, and the mock only while it is mounted and `isMockOAuthEnabled()`.\n\nimport { isJsonRequest } from \"../../transports/body\";\nimport { guestRoute } from \"./guest\";\nimport { checkRateLimits, loadLimiters, type LimitKind, type Limiters } from \"./limits\";\nimport { mockFlow, mockServes } from \"./mockFlow\";\nimport { callbackRoute, startRoute, type SignInFlow } from \"./oauth\";\nimport {\n failRoute,\n pathOf,\n refuse,\n sendJson,\n type AuthRouteRequest,\n type AuthRouteResponse,\n} from \"./respond\";\nimport { logoutAllRoute, logoutRoute, meRoute } from \"./session\";\nimport { routeSettings, type RouteSettings } from \"./settings\";\nimport type { AuthProvider, AuthProviderInfo, AuthRoutes, AuthRoutesOptions } from \"./types\";\n\n/** One route: its handler, the limiter that counts it, and whether it needs a JSON body type. */\ninterface Route {\n readonly handle: (req: AuthRouteRequest, res: AuthRouteResponse) => unknown;\n readonly limit?: LimitKind;\n readonly json?: boolean;\n}\n\n/** The routes by `\"{METHOD} {path}\"`. */\ntype RouteTable = Map<string, Route>;\n\nconst PROVIDER_ID = /^[a-z0-9][a-z0-9-]*$/;\n\nfunction checkProvider(provider: AuthProvider, seen: Set<string>): void {\n const { kind, id, authorizeUrl, profile } = (provider ?? {}) as Partial<\n Record<\"kind\" | \"id\" | \"authorizeUrl\" | \"profile\", unknown>\n >;\n const oauth = typeof authorizeUrl === \"function\" && typeof profile === \"function\";\n if (!(kind === \"mock\" || kind === \"guest\" || (kind === \"oauth\" && oauth))) {\n throw new TypeError(\n 'createAuthRoutes: providers are google(), discord(), mock(), guest() or { kind: \"oauth\", id, authorizeUrl, profile }',\n );\n }\n if (typeof id !== \"string\" || !PROVIDER_ID.test(id)) {\n throw new TypeError(\n `createAuthRoutes: a provider id is lowercase letters, digits and hyphens; got ${String(id)}`,\n );\n }\n if (seen.has(id)) {\n throw new TypeError(`createAuthRoutes: two providers have the id \"${id}\"`);\n }\n seen.add(id);\n}\n\n/** The names a sign-in button shows by default, by provider kind. */\nconst DEFAULT_NAMES = Object.freeze({ mock: \"Mock\", guest: \"Guest\" });\n\n/** A served provider, as `GET /providers` lists it. */\nfunction infoOf(provider: AuthProvider): AuthProviderInfo {\n const name =\n provider.kind === \"oauth\" ? (provider.name ?? provider.id) : DEFAULT_NAMES[provider.kind];\n return Object.freeze({ id: provider.id, name, kind: provider.kind });\n}\n\n/** Adds a provider's routes; the mock provider's only while it is enabled. Returns whether it was mounted. */\nfunction addProvider(table: RouteTable, settings: RouteSettings, provider: AuthProvider): boolean {\n const base = settings.basePath;\n if (provider.kind === \"guest\") {\n table.set(`POST ${base}/guest`, {\n handle: guestRoute(settings, provider),\n limit: \"signIn\",\n json: true,\n });\n return true;\n }\n const flow: SignInFlow | null =\n provider.kind === \"mock\"\n ? mockFlow(provider, settings, (method, path, handle) =>\n table.set(`${method} ${path}`, { handle }),\n )\n : provider;\n if (flow === null) {\n return false;\n }\n table.set(`GET ${base}/${flow.id}/start`, {\n handle: startRoute(settings, flow),\n limit: \"signIn\",\n });\n table.set(`GET ${base}/${flow.id}/callback`, {\n handle: callbackRoute(settings, flow),\n limit: \"signIn\",\n });\n return true;\n}\n\n/** The sign-ins served now: the providers mounted, the mock only while it is enabled. */\nfunction servedProviders(mounted: readonly AuthProvider[]): readonly AuthProviderInfo[] {\n return mounted.filter((provider) => provider.kind !== \"mock\" || mockServes()).map(infoOf);\n}\n\nfunction routeTable(\n settings: RouteSettings,\n listed: readonly (AuthProvider | null | undefined | false)[],\n): { readonly table: RouteTable; readonly mounted: readonly AuthProvider[] } {\n // An entry left out in place (`google.optional(...)` without credentials) is skipped.\n const providers = Array.isArray(listed)\n ? listed.filter(\n (provider): provider is AuthProvider =>\n provider !== undefined && provider !== null && provider !== false,\n )\n : [];\n if (providers.length === 0) {\n throw new TypeError(\"createAuthRoutes: providers must list at least one provider\");\n }\n const seen = new Set<string>();\n for (const provider of providers) {\n checkProvider(provider, seen);\n }\n const table: RouteTable = new Map();\n const mounted = providers.filter((provider) => addProvider(table, settings, provider));\n if (mounted.length === 0) {\n settings.logger.warn(\n \"createAuthRoutes: no provider can sign anyone in (the mock is mounted only while ENABLE_MOCK_OAUTH is set and NODE_ENV is development or test): every sign-in route answers 404\",\n { category: \"quickdraw.auth\" },\n );\n }\n const base = settings.basePath;\n table.set(`GET ${base}/providers`, {\n handle: (_req, res) => {\n sendJson(res, 200, { providers: servedProviders(mounted) });\n },\n limit: \"providers\",\n });\n table.set(`GET ${base}/me`, { handle: meRoute(settings), limit: \"session\" });\n table.set(`POST ${base}/logout`, { handle: logoutRoute(settings), limit: \"session\", json: true });\n table.set(`POST ${base}/logout-all`, {\n handle: logoutAllRoute(settings),\n limit: \"session\",\n json: true,\n });\n return { table, mounted };\n}\n\n/** Runs a route once its limiter let it through. */\nasync function serve(route: Route, req: AuthRouteRequest, res: AuthRouteResponse): Promise<void> {\n if (route.json === true && !isJsonRequest(req)) {\n refuse(res, \"VALIDATION\", \"Send the request with Content-Type: application/json\");\n return;\n }\n await route.handle(req, res);\n}\n\nfunction dispatch(\n settings: RouteSettings,\n limiters: Limiters | null,\n route: Route,\n req: AuthRouteRequest,\n res: AuthRouteResponse,\n): void {\n const fail = (error: unknown): void => {\n failRoute(settings.logger, req, res, error);\n };\n if (limiters === null) {\n refuse(res, \"INTERNAL\", \"The auth routes' rate limiters are unavailable\");\n return;\n }\n const limiter = route.limit === undefined ? undefined : limiters[route.limit];\n if (limiter === undefined) {\n serve(route, req, res).catch(fail);\n return;\n }\n // The limiter answers a refused request itself; it calls `next` to let one through.\n const next = (error?: unknown): void => {\n if (error === undefined) {\n serve(route, req, res).catch(fail);\n } else {\n fail(error);\n }\n };\n try {\n Promise.resolve(limiter(req as never, res as never, next)).catch(fail);\n } catch (error) {\n fail(error);\n }\n}\n\n/**\n * The auth routes, as one Express middleware: `app.use(createAuthRoutes(...))`.\n * Mount it after the app's CORS middleware (which must allow credentials for\n * the web app's origins) and before its catch-all routes.\n *\n * @example\n * app.use(\n * createAuthRoutes({\n * providers: [google({ clientId, clientSecret }), mock({ listUsers }), guest({ createUser })],\n * sessions: prismaSessions(prisma),\n * jwtSecret: env.JWT_SECRET,\n * onLogin: (profile, provider) => upsertUser(profile, provider),\n * allowedOrigins: [env.CLIENT_URL],\n * publicUrl: env.API_URL,\n * }),\n * );\n */\nexport function createAuthRoutes(options: AuthRoutesOptions): AuthRoutes {\n const settings = routeSettings(options);\n const rateLimit = checkRateLimits(options.rateLimit);\n const { table, mounted } = routeTable(settings, options.providers);\n const limiters = loadLimiters(rateLimit).catch((error: unknown) => {\n settings.logger.error(\n \"createAuthRoutes: the default rate limiters need express-rate-limit; install it, or pass rateLimit\",\n { category: \"quickdraw.auth\", error: error instanceof Error ? error.message : String(error) },\n );\n return null;\n });\n const routes = (\n req: AuthRouteRequest,\n res: AuthRouteResponse,\n next: (error?: unknown) => void,\n ) => {\n const route = table.get(`${req.method ?? \"\"} ${pathOf(req)}`);\n if (route === undefined) {\n next();\n return;\n }\n settings.watchHost(req);\n void limiters.then((loaded) => {\n dispatch(settings, loaded, route, req, res);\n });\n };\n return Object.assign(routes, { providers: () => servedProviders(mounted) });\n}\n","// `bearerAuth` (RFC 0003 section 12.6): the bearer-token counterpart of\n// `socketAuth`, for the hooks that are handed a token and nothing else: the\n// MCP bridge's `principal` hook, and an app's own `authenticate` that takes\n// only `auth.token`. A token stands for a session only while the session\n// store still holds it (`liveSession`: the JWT's signature and expiry, its\n// `sid`, the store's entry for that user, and its `expiresAt`), checked on\n// every call, so signing out, logging out everywhere or revoking a session\n// stops its token at once. `verifyJWT` alone checks the signature and expiry\n// only, so a signed-out session kept working for up to 7 days (the 5.1\n// audit's AUTH-1).\n//\n// It never reads cookies: a bearer token is not ambient, so it needs no\n// Origin check. It records no socket session either: for `createServer`'s\n// `authenticate` use `socketAuth`, which reads the session cookie with the\n// Origin rule and lets `server.access.disconnectUser` end a session's\n// sockets.\n\nimport { QuickdrawError } from \"../../../protocol/errors\";\nimport type { Principal } from \"../../types\";\nimport { principalOfSession, type PrincipalLoader } from \"./socketAuth\";\nimport { checkSessionKeys, liveSession, type SessionKeys } from \"./tokens\";\n\n/** Options of {@link bearerAuth}: the auth routes' session store and secret, and how a principal is built. */\nexport interface BearerAuthOptions<P extends Principal = Principal> extends SessionKeys {\n /**\n * Builds the principal of a session's user, for example `kind: \"agent\"`\n * for an MCP caller; its `userId` must be the session's. Default\n * `{ userId, kind: \"user\" }`.\n */\n readonly loadPrincipal?: PrincipalLoader<P>;\n}\n\n/**\n * What {@link bearerAuth} returns: the principal a bearer token signs in,\n * `null` for no token, and a rejection (`UNAUTHENTICATED`) for a token that\n * is not a live session's.\n */\nexport type BearerAuthenticate<P extends Principal> = (token: unknown) => Promise<P | null>;\n\n/**\n * Checks bearer tokens against the auth routes' sessions, on every call: no\n * token (anything but a non-empty string) is anonymous (`null`); a token\n * whose session was signed out, revoked or has expired, or a JWT without a\n * session, is refused with `UNAUTHENTICATED`; a live session's token is its\n * user's principal, built as `socketAuth` builds it. It records no socket\n * session: for `createServer`'s `authenticate` use `socketAuth`.\n *\n * @example\n * const verifySession = bearerAuth({ sessions, jwtSecret, loadPrincipal: (userId) => ({ userId, kind: \"agent\" }) });\n * // the MCP bridge's principal hook\n * createMcpRegistry({ ..., principal: (request) => verifySession(request.transport === \"http\" ? request.token : process.env.AGENT_TOKEN) });\n * // an authenticate that takes bearer tokens only\n * createHttpRouter({ dispatcher, auth: { authenticate: ({ auth }) => verifySession(auth.token) } });\n */\nexport function bearerAuth<P extends Principal>(\n options: BearerAuthOptions<P> & { readonly loadPrincipal: PrincipalLoader<P> },\n): BearerAuthenticate<P>;\nexport function bearerAuth(options: BearerAuthOptions): BearerAuthenticate<Principal>;\nexport function bearerAuth(options: BearerAuthOptions): BearerAuthenticate<Principal> {\n const keys = checkSessionKeys(options, \"bearerAuth\");\n const { loadPrincipal } = options;\n if (loadPrincipal !== undefined && typeof loadPrincipal !== \"function\") {\n throw new TypeError(\"bearerAuth: loadPrincipal must be a function\");\n }\n return async (token) => {\n if (typeof token !== \"string\" || token === \"\") {\n return null;\n }\n const session = await liveSession(keys, token);\n if (session === null) {\n throw new QuickdrawError(\"UNAUTHENTICATED\", \"The session is not valid\");\n }\n return await principalOfSession(session, loadPrincipal, \"bearerAuth\");\n };\n}\n","// `requireSession(keys)` (finding F2.11 of the quickdraw-chat migration): an\n// Express middleware for the app's own REST routes over the auth routes\n// kit's sessions. It reads the request's credential the way the HTTP\n// transport and `GET /auth/me` do (the session cookie under the shared\n// naming rule, else an `Authorization: Bearer` token), verifies the JWT once\n// and asks the session store whether the session it names is still live, so\n// a signed-out session stops authenticating REST calls at once. 4.1's\n// `createRequireAuth` stays for token-keyed sessions; over the kit's\n// sessions it verified the JWT twice (its own check, then `liveSession`).\n//\n// It also builds the request's principal as `socketAuth` builds a socket's\n// (`loadPrincipal`, default `{ userId, kind: \"user\" }`), and `sessionOf(req)`\n// hands the route its user, session and principal, typed, without a cast of\n// `req`, whose Express type has no `userId` (finding F5.4). The route then\n// calls the services in process as that principal: `qd.caller(principal)`\n// loads its service-wide grants as a socket's handshake does (F5.1).\n//\n// The session cookie is ambient, so a request that authenticates with it\n// gets the Origin rule `/qd` calls get from `socketAuth` (finding F8.5 of the\n// quickdraw-chat migration, and the final review of the release candidates:\n// a cross-site form POST reached a route as the user): an `Origin` outside\n// `allowedOrigins` is answered 403 `FORBIDDEN`, and a request without one\n// (curl, a server forwarding the cookie) is accepted unless `Sec-Fetch-Site`\n// names another site. The list defaults to the one the `createAuthRoutes`\n// writing to the same session store object was given (the object, not its\n// table: a second store over the same table is another object); without\n// either, no page may use the cookie here, and the first cookie refused for\n// want of a list logs an error naming both fixes, which a page in\n// development is told too (finding F11.4 of the quickdraw-chat migration).\n// A bearer token needs no Origin.\n\nimport type { ServerResponse } from \"node:http\";\nimport { consoleLogger, type Logger } from \"../../../contract/logger\";\nimport { httpCredentialOf, transportCookieNaming, type HttpRequest } from \"../../transports/body\";\nimport type { Principal } from \"../../types\";\nimport { originAllowlist, routeOriginsOf, type AllowedOrigin } from \"./origins\";\nimport { refuse } from \"./respond\";\nimport { COOKIE_REFUSED, httpCookieOriginAllowed, type PrincipalLoader } from \"./socketAuth\";\nimport { checkSessionKeys, liveSession, type SessionKeys } from \"./tokens\";\n\n/** Options of {@link requireSession}. */\nexport interface RequireSessionOptions<P extends Principal = Principal> {\n /**\n * The session cookie's name, when the app gave the routes one\n * (`createAuthRoutes({ cookie: { name } })`). Default: the name the routes\n * set on the same request, by the shared rule (`__Host-session` over\n * HTTPS, `session` over plain HTTP or with `COOKIE_DOMAIN`).\n */\n readonly cookieName?: string;\n /**\n * Builds the principal of the session's user, as `socketAuth`'s option of\n * the same name builds a socket's: give both the same function, so a REST\n * route calls the services as the user's sockets do. Its `userId` must be\n * the session's; `null` answers 401. Default `{ userId, kind: \"user\" }`.\n */\n readonly loadPrincipal?: PrincipalLoader<P>;\n /**\n * The pages that may use the session cookie on these routes, as\n * `createAuthRoutes` and `socketAuth` take them: a request with the cookie\n * and an `Origin` outside the list is answered 403 `FORBIDDEN`. Default:\n * the list of the `createAuthRoutes` that writes to the same session store\n * object (`keys.sessions` must be the object the routes were given: a\n * second store over the same table is another object), else none, so only\n * a request without `Origin` (curl, a server forwarding the cookie) uses\n * the cookie, and the first one refused for want of a list is logged. A\n * bearer token needs no Origin.\n */\n readonly allowedOrigins?: readonly AllowedOrigin[];\n /**\n * Where a session cookie refused for want of any origin list (no\n * `allowedOrigins`, and no `createAuthRoutes` over the same session store\n * object) is logged, once, as an error naming both fixes. Default: the\n * console.\n */\n readonly logger?: Logger;\n}\n\n/** What {@link sessionOf} answers for a request `requireSession` let through. */\nexport interface RequestSession<P extends Principal = Principal> {\n /** The signed-in user. */\n readonly userId: string;\n /** The session the credential named: `server.access.disconnectUser(userId, { sessionId })` ends its sockets. */\n readonly sessionId: string;\n /**\n * The user's principal, to call the services as: `qd.caller(principal)`,\n * which loads its service-wide grants as a socket's handshake does.\n */\n readonly principal: P;\n}\n\n/** A request `requireSession` let through: its user, its session and its principal. */\nexport type SessionRequest<P extends Principal = Principal> = HttpRequest & {\n /** The signed-in user. */\n userId?: string;\n /** The session the credential named. */\n sessionId?: string;\n /** The user's principal. */\n principal?: P;\n};\n\n/** What {@link requireSession} returns: an Express (4 or 5) middleware. */\nexport type SessionMiddleware<P extends Principal = Principal> = (\n req: SessionRequest<P>,\n res: ServerResponse,\n next: (error?: unknown) => void,\n) => void;\n\n/** The sessions `requireSession` let requests through with, by request. */\nconst SESSIONS = new WeakMap<object, RequestSession>();\n\n/** No page may use the cookie: what applies without `allowedOrigins` and without the routes' list. */\nconst NO_ORIGINS = originAllowlist([], \"requireSession\", true);\n\n/**\n * Why the cookie is refused when there is no list at all (finding F11.4):\n * logged once, and what a page in development is told.\n */\nconst NO_LIST =\n \"requireSession has no origin list, so no page may use the session cookie: pass it the sessions object createAuthRoutes was given (the same store object: a second store over the same table is another), or pass allowedOrigins\";\n\n/**\n * A middleware that lets a request through only with a live session of the\n * auth routes kit, keeping its user, session and principal for\n * {@link sessionOf} (and setting `req.userId`, `req.sessionId` and\n * `req.principal`); anything else answers 401\n * `{ error: \"UNAUTHENTICATED\", message }`, the routes' own failure shape, and\n * the session cookie sent from a page `allowedOrigins` does not list answers\n * 403 `{ error: \"FORBIDDEN\", message }`. A failing session store or\n * `loadPrincipal` is passed to `next(error)`.\n *\n * @example\n * app.post(\"/api/push/resubscribe\", express.json(), requireSession(keys), (req, res) => {\n * const { principal } = sessionOf(req);\n * // await qd.caller(principal).pushService.subscribePush(req.body), then answer\n * });\n */\nexport function requireSession<P extends Principal>(\n keys: SessionKeys,\n options: RequireSessionOptions<P> & { readonly loadPrincipal: PrincipalLoader<P> },\n): SessionMiddleware<P>;\nexport function requireSession(\n keys: SessionKeys,\n options?: RequireSessionOptions,\n): SessionMiddleware;\nexport function requireSession(\n keys: SessionKeys,\n options: RequireSessionOptions = {},\n): SessionMiddleware {\n checkSessionKeys(keys, \"requireSession\");\n const naming = transportCookieNaming(options.cookieName);\n const { loadPrincipal } = options;\n if (loadPrincipal !== undefined && typeof loadPrincipal !== \"function\") {\n throw new TypeError(\"requireSession: loadPrincipal must be a function\");\n }\n const given =\n options.allowedOrigins === undefined\n ? undefined\n : originAllowlist(options.allowedOrigins, \"requireSession\", true);\n const logger = options.logger ?? consoleLogger;\n let toldNoList = false;\n return (req, res, next) => {\n const check = async (): Promise<void> => {\n const credential = httpCredentialOf(req, naming);\n if (credential?.from === \"cookie\") {\n // Read now: the routes may be made after this middleware.\n const routes = given === undefined ? routeOriginsOf(keys.sessions) : undefined;\n if (!httpCookieOriginAllowed(req.headers, given ?? routes ?? NO_ORIGINS)) {\n const noList = given === undefined && routes === undefined;\n if (noList && !toldNoList) {\n toldNoList = true;\n logger.error(NO_LIST, { category: \"quickdraw.auth\", origin: req.headers.origin });\n }\n const development = process.env.NODE_ENV !== \"production\";\n refuse(res, \"FORBIDDEN\", noList && development ? NO_LIST : COOKIE_REFUSED);\n return;\n }\n }\n const token = credential?.token ?? null;\n const session = token === null ? null : await liveSession(keys, token);\n if (session === null) {\n refuse(res, \"UNAUTHENTICATED\", \"Not signed in\");\n return;\n }\n const principal: Principal | null | undefined =\n loadPrincipal === undefined\n ? { userId: session.userId, kind: \"user\" }\n : await loadPrincipal(session.userId, session);\n if (principal === null || principal === undefined) {\n refuse(res, \"UNAUTHENTICATED\", \"The session's user has no principal\");\n return;\n }\n if (principal.userId !== session.userId) {\n throw new TypeError(\n \"requireSession: loadPrincipal must return the principal of the session's user\",\n );\n }\n SESSIONS.set(\n req,\n Object.freeze({ userId: session.userId, sessionId: session.id, principal }),\n );\n req.userId = session.userId;\n req.sessionId = session.id;\n req.principal = principal;\n next();\n };\n check().catch((error: unknown) => {\n next(error);\n });\n };\n}\n\n/**\n * The user, session and principal `requireSession` let `req` through with,\n * typed: `P` is the type its `loadPrincipal` returns\n * (`sessionOf<AppPrincipal>(req)`). Throws a `TypeError` for a request no\n * `requireSession` let through, a route mounted without it.\n *\n * @example\n * const { principal } = sessionOf(req);\n * res.json(await qd.caller(principal).taskService.get({ id: req.params.id }));\n */\nexport function sessionOf<P extends Principal = Principal>(req: object): RequestSession<P> {\n const session = SESSIONS.get(req);\n if (session === undefined) {\n throw new TypeError(\n \"sessionOf: no requireSession let this request through; mount requireSession(keys) before the route\",\n );\n }\n return session as RequestSession<P>;\n}\n","// Where signed-in sessions live (RFC 0003 section 12.6). The app owns the\n// store: `createAuthRoutes` creates a session at each sign-in and revokes it\n// at logout, and `socketAuth` reads it on every handshake, so a revoked\n// session stops authenticating at once, even while its JWT has not expired.\n// `createMemorySessionStore` is for development and tests; an app keeps its\n// sessions in its database (the README has a Prisma store).\n\nimport { randomUUID } from \"node:crypto\";\nimport type { MaybePromise } from \"../../types\";\n\n/** A session as the store keeps it. A store's own rows may carry more fields. */\nexport interface AuthSession {\n /** The session's id. The session's JWT names it in its `sid` claim. */\n readonly id: string;\n /** The user the session signs in. */\n readonly userId: string;\n /** When the session ends. The kit refuses it afterwards, whatever the store answers. */\n readonly expiresAt: Date;\n}\n\n/** What the kit tells the store about a session it asks it to create. */\nexport interface SessionMeta {\n /** How the user signed in: a provider's id (`\"google\"`, `\"discord\"`, `\"mock\"`) or `\"guest\"`. */\n readonly provider: string;\n /** When the session ends. Its JWT and its cookie end then too. */\n readonly expiresAt: Date;\n /** The sign-in request's `User-Agent`, for a list of where the user is signed in. */\n readonly userAgent?: string;\n /** The sign-in request's IP address as Express reports it (`req.ip`), for the same. */\n readonly ip?: string;\n}\n\n/**\n * The app's session storage. `get` answers `null` (or `undefined`) for a\n * session that does not exist or was revoked; after `revoke(id)` or\n * `revokeAll(userId)`, `get` must answer `null` for those sessions. What\n * `revoke` and `revokeAll` return is ignored.\n */\nexport interface SessionStore {\n /** Stores a new session for `userId` and returns it, with the id the store gave it. */\n create(userId: string, meta: SessionMeta): MaybePromise<AuthSession>;\n get(sessionId: string): MaybePromise<AuthSession | null | undefined>;\n revoke(sessionId: string): MaybePromise<unknown>;\n revokeAll(userId: string): MaybePromise<unknown>;\n}\n\n/** The in-memory store: a synchronous {@link SessionStore}, plus a count for tests. */\nexport interface MemorySessionStore extends SessionStore {\n create(userId: string, meta: SessionMeta): AuthSession;\n get(sessionId: string): AuthSession | null;\n revoke(sessionId: string): void;\n revokeAll(userId: string): void;\n /** How many sessions it holds, expired ones included until the next `create` drops them. */\n readonly size: number;\n}\n\n/**\n * A {@link SessionStore} in this process's memory, for development and\n * tests. Its sessions are lost on restart and are not shared between\n * processes, so production apps store them in their database.\n */\nexport function createMemorySessionStore(): MemorySessionStore {\n const sessions = new Map<string, AuthSession>();\n const dropExpired = (now: number): void => {\n for (const [id, session] of sessions) {\n if (session.expiresAt.getTime() <= now) {\n sessions.delete(id);\n }\n }\n };\n return {\n create(userId, meta) {\n dropExpired(Date.now());\n const session = Object.freeze({ id: randomUUID(), userId, expiresAt: meta.expiresAt });\n sessions.set(session.id, session);\n return session;\n },\n get: (sessionId) => sessions.get(sessionId) ?? null,\n revoke(sessionId) {\n sessions.delete(sessionId);\n },\n revokeAll(userId) {\n for (const [id, session] of sessions) {\n if (session.userId === userId) {\n sessions.delete(id);\n }\n }\n },\n get size() {\n return sessions.size;\n },\n };\n}\n","// The sign-in providers of the auth routes kit (RFC 0003 section 12.6): the\n// 4.1 Google and Discord providers and the development mock, each wrapped\n// into one shape, `{ id, authorizeUrl(state, redirectUri), profile(code,\n// redirectUri) }`, that the routes drive the same way. An app adds another\n// OAuth provider by writing that shape itself. The mock provider is served by\n// the routes themselves, and only while `isMockOAuthEnabled()`; the guest\n// provider is in `guest.ts`.\n\nimport { discordProvider, getDiscordAvatarUrl } from \"../discord\";\nimport { googleProvider } from \"../google\";\nimport type { MockOAuthUser } from \"../mock\";\nimport {\n createOAuthURL,\n exchangeOAuthCode,\n type OAuthConfig,\n type OAuthProvider,\n type OAuthTokenResponse,\n} from \"../oauth\";\n\n/** Who signed in, as every provider reports it to `onLogin`. */\nexport interface AuthProfile {\n /** The provider's id for the user: Google's and Discord's user id, the mock user's email. */\n readonly providerAccountId: string;\n readonly email: string | null;\n /** Whether the provider verified `email`. Link accounts by email only when it did. */\n readonly emailVerified: boolean;\n readonly name: string | null;\n /** An avatar URL. */\n readonly image: string | null;\n /** The provider's tokens, for an app that calls the provider's API later; encrypt them at rest. */\n readonly tokens: OAuthTokenResponse;\n /** The provider's user info as the 4.1 helper parsed it (`GoogleUser`, `DiscordUser`). */\n readonly raw: unknown;\n}\n\n/** An OAuth 2.0 authorization-code provider the routes can drive. */\nexport interface OAuthSignInProvider {\n readonly kind: \"oauth\";\n /** The provider's id: `{basePath}/{id}/start` and `{basePath}/{id}/callback`. */\n readonly id: string;\n /** Its name on a sign-in button (`GET {basePath}/providers`). Default: its id. */\n readonly name?: string;\n /** The provider's authorization URL for a sign-in with this `state`. */\n authorizeUrl(state: string, redirectUri: string): string;\n /** Exchanges the callback's `code` and reads the user's profile. */\n profile(code: string, redirectUri: string): Promise<AuthProfile>;\n}\n\n/** Client credentials and options of a hosted OAuth provider. */\nexport interface OAuthClientOptions {\n readonly clientId: string;\n readonly clientSecret: string;\n /** The scopes asked for. Default: the 4.1 provider's (`openid email profile`; `identify email`). */\n readonly scopes?: readonly string[];\n /** Extra authorization parameters, such as `{ access_type: \"offline\", prompt: \"consent\" }`. */\n readonly params?: Readonly<Record<string, string>>;\n}\n\n/** `google.optional` and `discord.optional`'s options: credentials that may be unset, as read from the environment. */\nexport type OptionalClientOptions = Omit<OAuthClientOptions, \"clientId\" | \"clientSecret\"> & {\n readonly clientId: string | undefined;\n readonly clientSecret: string | undefined;\n};\n\n/** The development mock provider's options. */\nexport interface MockSignInOptions {\n /** The users the sign-in picker offers (typically seeded demo accounts). */\n readonly listUsers: () => Promise<MockOAuthUser[]>;\n /**\n * Where the API reaches itself for the token and userinfo requests.\n * Default: `createAuthRoutes`' `publicUrl`; set it where the public URL is\n * not reachable from inside the server (containers, Codespaces), for\n * example `\"http://localhost:4000\"`.\n */\n readonly internalUrl?: string;\n}\n\n/** The development mock provider: the routes serve its endpoints while `isMockOAuthEnabled()`. */\nexport interface MockSignInProvider {\n readonly kind: \"mock\";\n readonly id: \"mock\";\n readonly options: MockSignInOptions;\n}\n\nfunction checkCredentials(options: OAuthClientOptions, owner: string): void {\n if (typeof options.clientId !== \"string\" || options.clientId === \"\") {\n throw new TypeError(`${owner}: clientId is required`);\n }\n if (typeof options.clientSecret !== \"string\" || options.clientSecret === \"\") {\n throw new TypeError(`${owner}: clientSecret is required`);\n }\n}\n\n/** The 4.1 provider with the app's scopes, and the URL and exchange config for one redirect URI. */\nfunction hosted<TUser>(\n base: OAuthProvider<TUser>,\n options: OAuthClientOptions,\n): {\n readonly provider: OAuthProvider<TUser>;\n url(state: string, redirectUri: string): string;\n config(redirectUri: string): OAuthConfig;\n} {\n const provider = options.scopes === undefined ? base : { ...base, scopes: [...options.scopes] };\n const config = (redirectUri: string): OAuthConfig => ({\n clientId: options.clientId,\n clientSecret: options.clientSecret,\n redirectUri,\n });\n return {\n provider,\n config,\n url: (state, redirectUri) => {\n const url = new URL(createOAuthURL(provider, config(redirectUri), state));\n for (const [name, value] of Object.entries(options.params ?? {})) {\n url.searchParams.set(name, value);\n }\n return url.href;\n },\n };\n}\n\n/**\n * The provider `make` builds when both credentials are set; `undefined`,\n * which `createAuthRoutes` skips, when neither is. Only one of the two is a\n * misconfiguration, refused as `make` refuses it.\n */\nfunction optionalOf(\n make: (options: OAuthClientOptions) => OAuthSignInProvider,\n): (options: OptionalClientOptions) => OAuthSignInProvider | undefined {\n return (options) => {\n const set = (value: unknown): boolean => typeof value === \"string\" && value !== \"\";\n if (!set(options.clientId) && !set(options.clientSecret)) {\n return undefined;\n }\n return make(options as OAuthClientOptions);\n };\n}\n\n/** Google sign-in. Register `{publicUrl}{basePath}/google/callback` as a redirect URI. */\nexport function google(options: OAuthClientOptions): OAuthSignInProvider {\n checkCredentials(options, \"google()\");\n const client = hosted(googleProvider, options);\n return Object.freeze({\n kind: \"oauth\",\n id: \"google\",\n name: \"Google\",\n authorizeUrl: client.url,\n async profile(code: string, redirectUri: string): Promise<AuthProfile> {\n const { tokens, user } = await exchangeOAuthCode(\n client.provider,\n client.config(redirectUri),\n code,\n );\n return {\n providerAccountId: user.id,\n email: user.email,\n emailVerified: user.verified_email,\n name: user.name,\n image: user.picture,\n tokens,\n raw: user,\n };\n },\n });\n}\n\n/**\n * Google sign-in when both credentials are set, else nothing (`undefined`,\n * which `createAuthRoutes` skips): the provider of an environment that may\n * not configure it, such as development without a Google app.\n *\n * @example\n * providers: [\n * google.optional({ clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET }),\n * discord.optional({ clientId: env.DISCORD_CLIENT_ID, clientSecret: env.DISCORD_CLIENT_SECRET }),\n * mock({ listUsers }),\n * ],\n */\ngoogle.optional = optionalOf(google);\n\n/** Discord sign-in. Register `{publicUrl}{basePath}/discord/callback` as a redirect URI. */\nexport function discord(options: OAuthClientOptions): OAuthSignInProvider {\n checkCredentials(options, \"discord()\");\n const client = hosted(discordProvider, options);\n return Object.freeze({\n kind: \"oauth\",\n id: \"discord\",\n name: \"Discord\",\n authorizeUrl: client.url,\n async profile(code: string, redirectUri: string): Promise<AuthProfile> {\n const { tokens, user } = await exchangeOAuthCode(\n client.provider,\n client.config(redirectUri),\n code,\n );\n return {\n providerAccountId: user.id,\n email: user.email,\n emailVerified: user.verified,\n name: user.username,\n image: getDiscordAvatarUrl(user),\n tokens,\n raw: user,\n };\n },\n });\n}\n\n/** Discord sign-in when both credentials are set, else nothing: see `google.optional`. */\ndiscord.optional = optionalOf(discord);\n\n/**\n * The development sign-in: a picker of the app's demo users, served by the\n * routes through `registerMockOAuthProvider`. Left out (its routes answer\n * nothing) unless `isMockOAuthEnabled()`: `ENABLE_MOCK_OAUTH=true` and\n * `NODE_ENV` `development` or `test` (an unset `NODE_ENV` leaves it out: set\n * `NODE_ENV=development`). Mounting it logs a warning.\n */\nexport function mock(options: MockSignInOptions): MockSignInProvider {\n if (typeof options.listUsers !== \"function\") {\n throw new TypeError(\"mock(): listUsers is required\");\n }\n return Object.freeze({ kind: \"mock\", id: \"mock\", options });\n}\n"],"mappings":"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","names":["randomBytes","sessionOf"]}