@lalternative/auth 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,7 +26,13 @@ interface SessionState {
26
26
  status?: number;
27
27
  } | null;
28
28
  }
29
- type UrbangateAuthClient = Omit<AuthClientSurface, "admin"> & {
29
+ type UrbangateAuthClient = Omit<AuthClientSurface, "admin" | "signIn"> & {
30
+ signIn: AuthClientSurface["signIn"] & {
31
+ emailOtp(input: {
32
+ email: string;
33
+ otp: string;
34
+ }): Promise<AuthClientResult>;
35
+ };
30
36
  signOut(): Promise<AuthClientResult>;
31
37
  getSession(): Promise<{
32
38
  data: UrbangateClientSession | null;
@@ -32,6 +32,7 @@ function createUrbangateAuthClient(config = {}) {
32
32
  const client = {
33
33
  signIn: {
34
34
  email: (input) => call("POST", "sign-in/email", input),
35
+ emailOtp: (input) => call("POST", "sign-in/email-otp", input),
35
36
  social: async () => ({ error: { code: "not_supported", status: 501 } })
36
37
  },
37
38
  signUp: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/urbangate/client.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport type { AuthClientResult, AuthClientSurface } from \"../types\";\n\nexport interface UrbangateAuthClientConfig {\n baseURL?: string;\n}\n\nexport interface UrbangateClientUser {\n id: string;\n identityId: string;\n email: string;\n emailVerified: boolean;\n name: string;\n role: \"admin\" | \"user\";\n}\n\nexport interface UrbangateClientSession {\n user: UrbangateClientUser;\n session: { expiresAt: string };\n}\n\nexport interface SessionState {\n data: UrbangateClientSession | null;\n isPending: boolean;\n error: { message?: string; status?: number } | null;\n}\n\nexport type UrbangateAuthClient = Omit<AuthClientSurface, \"admin\"> & {\n signOut(): Promise<AuthClientResult>;\n getSession(): Promise<{\n data: UrbangateClientSession | null;\n error: AuthClientResult[\"error\"];\n }>;\n useSession(): SessionState;\n};\n\ntype Result<T = unknown> = { data: T | null; error: AuthClientResult[\"error\"] };\n\nexport function createUrbangateAuthClient(\n config: UrbangateAuthClientConfig = {},\n): UrbangateAuthClient {\n const base = (\n config.baseURL ??\n (typeof window !== \"undefined\" ? window.location.origin : \"\")\n ).replace(/\\/$/, \"\");\n\n async function call<T = unknown>(\n method: \"GET\" | \"POST\",\n path: string,\n body?: unknown,\n ): Promise<Result<T>> {\n try {\n const res = await fetch(`${base}/api/auth/${path}`, {\n method,\n credentials: \"include\",\n headers:\n body === undefined ? {} : { \"content-type\": \"application/json\" },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n const parsed = (await res.json().catch(() => null)) as\n { error?: AuthClientResult[\"error\"] } | T | null;\n if (!res.ok) {\n const error = (parsed as { error?: AuthClientResult[\"error\"] } | null)\n ?.error;\n return {\n data: null,\n error: error ?? { status: res.status, message: res.statusText },\n };\n }\n return { data: parsed as T, error: null };\n } catch (err) {\n return {\n data: null,\n error: {\n message: err instanceof Error ? err.message : \"network\",\n status: 0,\n },\n };\n }\n }\n\n const client: UrbangateAuthClient = {\n signIn: {\n email: (input) => call(\"POST\", \"sign-in/email\", input),\n social: async () => ({ error: { code: \"not_supported\", status: 501 } }),\n },\n signUp: {\n email: (input) => call(\"POST\", \"sign-up/email\", input),\n },\n emailOtp: {\n sendVerificationOtp: (input) =>\n call(\"POST\", \"email-otp/send-verification-otp\", input),\n verifyEmail: (input) => call(\"POST\", \"email-otp/verify-email\", input),\n resetPassword: (input) => call(\"POST\", \"email-otp/reset-password\", input),\n },\n signOut: () => call(\"POST\", \"sign-out\"),\n getSession: () => call<UrbangateClientSession | null>(\"GET\", \"get-session\"),\n useSession() {\n const [state, setState] = useState<SessionState>({\n data: null,\n isPending: true,\n error: null,\n });\n useEffect(() => {\n let live = true;\n void client.getSession().then(({ data, error }) => {\n if (live) setState({ data, isPending: false, error: error ?? null });\n });\n return () => {\n live = false;\n };\n }, []);\n return state;\n },\n };\n return client;\n}\n"],"mappings":";AAAA,SAAS,WAAW,gBAAgB;AAsC7B,SAAS,0BACd,SAAoC,CAAC,GAChB;AACrB,QAAM,QACJ,OAAO,YACN,OAAO,WAAW,cAAc,OAAO,SAAS,SAAS,KAC1D,QAAQ,OAAO,EAAE;AAEnB,iBAAe,KACb,QACA,MACA,MACoB;AACpB,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,IAAI,aAAa,IAAI,IAAI;AAAA,QAClD;AAAA,QACA,aAAa;AAAA,QACb,SACE,SAAS,SAAY,CAAC,IAAI,EAAE,gBAAgB,mBAAmB;AAAA,QACjE,MAAM,SAAS,SAAY,SAAY,KAAK,UAAU,IAAI;AAAA,MAC5D,CAAC;AACD,YAAM,SAAU,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI;AAEjD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,QAAS,QACX;AACJ,eAAO;AAAA,UACL,MAAM;AAAA,UACN,OAAO,SAAS,EAAE,QAAQ,IAAI,QAAQ,SAAS,IAAI,WAAW;AAAA,QAChE;AAAA,MACF;AACA,aAAO,EAAE,MAAM,QAAa,OAAO,KAAK;AAAA,IAC1C,SAAS,KAAK;AACZ,aAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO;AAAA,UACL,SAAS,eAAe,QAAQ,IAAI,UAAU;AAAA,UAC9C,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAA8B;AAAA,IAClC,QAAQ;AAAA,MACN,OAAO,CAAC,UAAU,KAAK,QAAQ,iBAAiB,KAAK;AAAA,MACrD,QAAQ,aAAa,EAAE,OAAO,EAAE,MAAM,iBAAiB,QAAQ,IAAI,EAAE;AAAA,IACvE;AAAA,IACA,QAAQ;AAAA,MACN,OAAO,CAAC,UAAU,KAAK,QAAQ,iBAAiB,KAAK;AAAA,IACvD;AAAA,IACA,UAAU;AAAA,MACR,qBAAqB,CAAC,UACpB,KAAK,QAAQ,mCAAmC,KAAK;AAAA,MACvD,aAAa,CAAC,UAAU,KAAK,QAAQ,0BAA0B,KAAK;AAAA,MACpE,eAAe,CAAC,UAAU,KAAK,QAAQ,4BAA4B,KAAK;AAAA,IAC1E;AAAA,IACA,SAAS,MAAM,KAAK,QAAQ,UAAU;AAAA,IACtC,YAAY,MAAM,KAAoC,OAAO,aAAa;AAAA,IAC1E,aAAa;AACX,YAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB;AAAA,QAC/C,MAAM;AAAA,QACN,WAAW;AAAA,QACX,OAAO;AAAA,MACT,CAAC;AACD,gBAAU,MAAM;AACd,YAAI,OAAO;AACX,aAAK,OAAO,WAAW,EAAE,KAAK,CAAC,EAAE,MAAM,MAAM,MAAM;AACjD,cAAI,KAAM,UAAS,EAAE,MAAM,WAAW,OAAO,OAAO,SAAS,KAAK,CAAC;AAAA,QACrE,CAAC;AACD,eAAO,MAAM;AACX,iBAAO;AAAA,QACT;AAAA,MACF,GAAG,CAAC,CAAC;AACL,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/urbangate/client.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport type { AuthClientResult, AuthClientSurface } from \"../types\";\n\nexport interface UrbangateAuthClientConfig {\n baseURL?: string;\n}\n\nexport interface UrbangateClientUser {\n id: string;\n identityId: string;\n email: string;\n emailVerified: boolean;\n name: string;\n role: \"admin\" | \"user\";\n}\n\nexport interface UrbangateClientSession {\n user: UrbangateClientUser;\n session: { expiresAt: string };\n}\n\nexport interface SessionState {\n data: UrbangateClientSession | null;\n isPending: boolean;\n error: { message?: string; status?: number } | null;\n}\n\nexport type UrbangateAuthClient = Omit<\n AuthClientSurface,\n \"admin\" | \"signIn\"\n> & {\n signIn: AuthClientSurface[\"signIn\"] & {\n emailOtp(input: { email: string; otp: string }): Promise<AuthClientResult>;\n };\n signOut(): Promise<AuthClientResult>;\n getSession(): Promise<{\n data: UrbangateClientSession | null;\n error: AuthClientResult[\"error\"];\n }>;\n useSession(): SessionState;\n};\n\ntype Result<T = unknown> = { data: T | null; error: AuthClientResult[\"error\"] };\n\nexport function createUrbangateAuthClient(\n config: UrbangateAuthClientConfig = {},\n): UrbangateAuthClient {\n const base = (\n config.baseURL ??\n (typeof window !== \"undefined\" ? window.location.origin : \"\")\n ).replace(/\\/$/, \"\");\n\n async function call<T = unknown>(\n method: \"GET\" | \"POST\",\n path: string,\n body?: unknown,\n ): Promise<Result<T>> {\n try {\n const res = await fetch(`${base}/api/auth/${path}`, {\n method,\n credentials: \"include\",\n headers:\n body === undefined ? {} : { \"content-type\": \"application/json\" },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n const parsed = (await res.json().catch(() => null)) as\n { error?: AuthClientResult[\"error\"] } | T | null;\n if (!res.ok) {\n const error = (parsed as { error?: AuthClientResult[\"error\"] } | null)\n ?.error;\n return {\n data: null,\n error: error ?? { status: res.status, message: res.statusText },\n };\n }\n return { data: parsed as T, error: null };\n } catch (err) {\n return {\n data: null,\n error: {\n message: err instanceof Error ? err.message : \"network\",\n status: 0,\n },\n };\n }\n }\n\n const client: UrbangateAuthClient = {\n signIn: {\n email: (input) => call(\"POST\", \"sign-in/email\", input),\n emailOtp: (input) => call(\"POST\", \"sign-in/email-otp\", input),\n social: async () => ({ error: { code: \"not_supported\", status: 501 } }),\n },\n signUp: {\n email: (input) => call(\"POST\", \"sign-up/email\", input),\n },\n emailOtp: {\n sendVerificationOtp: (input) =>\n call(\"POST\", \"email-otp/send-verification-otp\", input),\n verifyEmail: (input) => call(\"POST\", \"email-otp/verify-email\", input),\n resetPassword: (input) => call(\"POST\", \"email-otp/reset-password\", input),\n },\n signOut: () => call(\"POST\", \"sign-out\"),\n getSession: () => call<UrbangateClientSession | null>(\"GET\", \"get-session\"),\n useSession() {\n const [state, setState] = useState<SessionState>({\n data: null,\n isPending: true,\n error: null,\n });\n useEffect(() => {\n let live = true;\n void client.getSession().then(({ data, error }) => {\n if (live) setState({ data, isPending: false, error: error ?? null });\n });\n return () => {\n live = false;\n };\n }, []);\n return state;\n },\n };\n return client;\n}\n"],"mappings":";AAAA,SAAS,WAAW,gBAAgB;AA4C7B,SAAS,0BACd,SAAoC,CAAC,GAChB;AACrB,QAAM,QACJ,OAAO,YACN,OAAO,WAAW,cAAc,OAAO,SAAS,SAAS,KAC1D,QAAQ,OAAO,EAAE;AAEnB,iBAAe,KACb,QACA,MACA,MACoB;AACpB,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,IAAI,aAAa,IAAI,IAAI;AAAA,QAClD;AAAA,QACA,aAAa;AAAA,QACb,SACE,SAAS,SAAY,CAAC,IAAI,EAAE,gBAAgB,mBAAmB;AAAA,QACjE,MAAM,SAAS,SAAY,SAAY,KAAK,UAAU,IAAI;AAAA,MAC5D,CAAC;AACD,YAAM,SAAU,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI;AAEjD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,QAAS,QACX;AACJ,eAAO;AAAA,UACL,MAAM;AAAA,UACN,OAAO,SAAS,EAAE,QAAQ,IAAI,QAAQ,SAAS,IAAI,WAAW;AAAA,QAChE;AAAA,MACF;AACA,aAAO,EAAE,MAAM,QAAa,OAAO,KAAK;AAAA,IAC1C,SAAS,KAAK;AACZ,aAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO;AAAA,UACL,SAAS,eAAe,QAAQ,IAAI,UAAU;AAAA,UAC9C,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAA8B;AAAA,IAClC,QAAQ;AAAA,MACN,OAAO,CAAC,UAAU,KAAK,QAAQ,iBAAiB,KAAK;AAAA,MACrD,UAAU,CAAC,UAAU,KAAK,QAAQ,qBAAqB,KAAK;AAAA,MAC5D,QAAQ,aAAa,EAAE,OAAO,EAAE,MAAM,iBAAiB,QAAQ,IAAI,EAAE;AAAA,IACvE;AAAA,IACA,QAAQ;AAAA,MACN,OAAO,CAAC,UAAU,KAAK,QAAQ,iBAAiB,KAAK;AAAA,IACvD;AAAA,IACA,UAAU;AAAA,MACR,qBAAqB,CAAC,UACpB,KAAK,QAAQ,mCAAmC,KAAK;AAAA,MACvD,aAAa,CAAC,UAAU,KAAK,QAAQ,0BAA0B,KAAK;AAAA,MACpE,eAAe,CAAC,UAAU,KAAK,QAAQ,4BAA4B,KAAK;AAAA,IAC1E;AAAA,IACA,SAAS,MAAM,KAAK,QAAQ,UAAU;AAAA,IACtC,YAAY,MAAM,KAAoC,OAAO,aAAa;AAAA,IAC1E,aAAa;AACX,YAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB;AAAA,QAC/C,MAAM;AAAA,QACN,WAAW;AAAA,QACX,OAAO;AAAA,MACT,CAAC;AACD,gBAAU,MAAM;AACd,YAAI,OAAO;AACX,aAAK,OAAO,WAAW,EAAE,KAAK,CAAC,EAAE,MAAM,MAAM,MAAM;AACjD,cAAI,KAAM,UAAS,EAAE,MAAM,WAAW,OAAO,OAAO,SAAS,KAAK,CAAC;AAAA,QACrE,CAAC;AACD,eAAO,MAAM;AACX,iBAAO;AAAA,QACT;AAAA,MACF,GAAG,CAAC,CAAC;AACL,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
package/dist/urbangate.js CHANGED
@@ -176,6 +176,7 @@ var INVALID_CREDENTIALS = 4000006;
176
176
  var INVALID_CODE = /* @__PURE__ */ new Set([4010008, 4060006, 4070006]);
177
177
  var CODE_SENT = /* @__PURE__ */ new Set([1010014, 1040005, 1060003, 1070003, 1080003]);
178
178
  var ALREADY_REGISTERED = 4000007;
179
+ var ACCOUNT_NOT_FOUND = 4000035;
179
180
  var PASSWORD_POLICY = /* @__PURE__ */ new Set([4000005, 4000031, 4000032, 4000033, 4000034]);
180
181
  var FLOW_EXPIRED = /* @__PURE__ */ new Set([4010001, 4040001, 4060005, 4070005]);
181
182
  function failureOf(status, body) {
@@ -197,6 +198,7 @@ function failureOf(status, body) {
197
198
  if (m.id === INVALID_CREDENTIALS) return { status: "invalid_credentials" };
198
199
  if (INVALID_CODE.has(m.id ?? -1)) return { status: "invalid_code" };
199
200
  if (m.id === ALREADY_REGISTERED) return { status: "already_registered" };
201
+ if (m.id === ACCOUNT_NOT_FOUND) return { status: "account_not_found" };
200
202
  if (PASSWORD_POLICY.has(m.id ?? -1)) {
201
203
  return { status: "password_refused", message: m.text ?? "" };
202
204
  }
@@ -298,6 +300,7 @@ var FAILURE_STATUS = {
298
300
  second_factor_required: 403,
299
301
  account_disabled: 403,
300
302
  already_registered: 409,
303
+ account_not_found: 404,
301
304
  password_refused: 422,
302
305
  invalid_input: 400,
303
306
  flow_expired: 410,
@@ -461,17 +464,34 @@ function createUrbangateAuth(config) {
461
464
  const kind = FLOW_OF_TYPE[type];
462
465
  if (!email || !kind)
463
466
  return failure("invalid_input", 400, "email and type are required");
467
+ const sent = await sendCode(kind, email);
468
+ return json(200, { sent: true }, [
469
+ cookie(names.flow, `${sent.kind}:${sent.flowId}`, FLOW_MAX_AGE)
470
+ ]);
471
+ }
472
+ async function sendCode(kind, email) {
473
+ try {
474
+ return await sendCodeOn(kind, email);
475
+ } catch (error) {
476
+ if (kind !== "login" || !(error instanceof KratosError) || error.failure.status !== "account_not_found") {
477
+ throw error;
478
+ }
479
+ return sendCodeOn("registration", email);
480
+ }
481
+ }
482
+ async function sendCodeOn(kind, email) {
464
483
  const flow = await kratos.start(kind);
465
484
  const submitted = await kratos.submit(kind, flow.id, {
466
485
  method: "code",
467
- ...kind === "login" ? { identifier: email } : { email }
486
+ ...kind === "login" ? { identifier: email } : kind === "registration" ? { traits: { email } } : { email }
468
487
  });
469
488
  if (!codeWasSent(submitted) && submitted.state !== "sent_email") {
470
- return failure("invalid_input", 400, "the code could not be sent");
489
+ throw new KratosError({
490
+ status: "invalid_input",
491
+ message: "the code could not be sent"
492
+ });
471
493
  }
472
- return json(200, { sent: true }, [
473
- cookie(names.flow, `${kind}:${flow.id}`, FLOW_MAX_AGE)
474
- ]);
494
+ return { kind, flowId: flow.id };
475
495
  }
476
496
  function pendingFlow(headers, kind) {
477
497
  const raw = readCookie(headers, names.flow) ?? "";
@@ -482,14 +502,20 @@ function createUrbangateAuth(config) {
482
502
  const b = await body(request);
483
503
  const email = str(b, "email");
484
504
  const code = str(b, "otp");
485
- const flowId = pendingFlow(request.headers, "login");
505
+ const login = pendingFlow(request.headers, "login");
506
+ const registration = pendingFlow(request.headers, "registration");
507
+ const flowId = login ?? registration;
486
508
  if (!flowId) return failure("flow_expired", 410);
487
509
  if (!email || !code)
488
510
  return failure("invalid_input", 400, "email and otp are required");
489
- const result = await kratos.submit("login", flowId, {
511
+ const result = login ? await kratos.submit("login", flowId, {
490
512
  method: "code",
491
513
  identifier: email,
492
514
  code
515
+ }) : await kratos.submit("registration", flowId, {
516
+ method: "code",
517
+ traits: { email },
518
+ code
493
519
  });
494
520
  return json(
495
521
  200,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/urbangate/cookies.ts","../src/urbangate/exchange.ts","../src/urbangate/kratos.ts","../src/urbangate/server.ts"],"sourcesContent":["export interface CookieOptions {\n maxAge: number;\n secure: boolean;\n}\n\nexport function readCookie(headers: Headers, name: string): string | undefined {\n const raw = headers.get(\"cookie\") ?? \"\";\n for (const part of raw.split(\";\")) {\n const [k, ...v] = part.trim().split(\"=\");\n if (k === name) return decodeURIComponent(v.join(\"=\"));\n }\n return undefined;\n}\n\nexport function serializeCookie(\n name: string,\n value: string,\n o: CookieOptions,\n): string {\n const attrs = [\n `${name}=${encodeURIComponent(value)}`,\n \"Path=/\",\n \"HttpOnly\",\n \"SameSite=Lax\",\n `Max-Age=${value ? o.maxAge : 0}`,\n ];\n if (o.secure) attrs.push(\"Secure\");\n return attrs.join(\"; \");\n}\n\nexport function clearCookie(name: string, secure: boolean): string {\n return serializeCookie(name, \"\", { maxAge: 0, secure });\n}\n","import type { Fetch } from \"./kratos\";\n\nexport interface ExchangeConfig {\n product: string;\n issuerUrl: string;\n provisioner: { clientId: string; clientSecret: string };\n admin: { clientId: string; clientSecret: string };\n}\n\nexport interface PersonToken {\n accessToken: string;\n expiresAt: number;\n identityId: string;\n roles: Array<string>;\n}\n\nexport type ExchangeOutcome =\n | { status: \"ok\"; token: PersonToken }\n | { status: \"session_gone\" }\n | { status: \"inactive\" }\n | { status: \"unavailable\"; reason: string };\n\nconst JWT_BEARER = \"urn:ietf:params:oauth:grant-type:jwt-bearer\";\nconst EXCHANGE_SCOPE = \"urbangate:sessions:exchange\";\nconst REFRESH_MARGIN_MS = 60_000;\n\nfunction basic(id: string, secret: string) {\n return \"Basic \" + btoa(`${id}:${secret}`);\n}\n\n/**\n * Turns a Kratos session token into the person's Hydra access token, in the\n * three calls ADR 0009 of urbangate describes: a machine token for the\n * provisioner, the assertion from urbangate, the token from Hydra with the\n * product's own admin client. Hydra issues no refresh token on this grant,\n * so the caller exchanges again when the token nears its end.\n */\nexport class Exchange {\n private machine: { token: string; expiresAt: number } | null = null;\n\n private readonly config: ExchangeConfig;\n private readonly fetchImpl: Fetch;\n\n constructor(config: ExchangeConfig, fetchImpl: Fetch = fetch) {\n this.config = config;\n this.fetchImpl = fetchImpl;\n }\n\n needsRefresh(token: PersonToken | null, now = Date.now()): boolean {\n return !token || token.expiresAt - now < REFRESH_MARGIN_MS;\n }\n\n async exchange(sessionToken: string): Promise<ExchangeOutcome> {\n const machine = await this.machineToken();\n if (!machine) return { status: \"unavailable\", reason: \"machine_token\" };\n const url = new URL(\n \"/api/machine/sessions/exchange\",\n this.config.issuerUrl,\n );\n let res: Response;\n try {\n res = await this.fetchImpl(url, {\n method: \"POST\",\n headers: {\n authorization: `Bearer ${machine}`,\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({ session_token: sessionToken }),\n });\n } catch {\n return { status: \"unavailable\", reason: \"exchange\" };\n }\n if (res.status === 401) {\n const body = (await res.json().catch(() => ({}))) as { error?: string };\n // Our own credential was refused: fetch a fresh one next time.\n if (body.error === \"invalid_token\") this.machine = null;\n return body.error === \"session\"\n ? { status: \"session_gone\" }\n : { status: \"unavailable\", reason: \"exchange_401\" };\n }\n if (res.status === 403) return { status: \"inactive\" };\n if (!res.ok)\n return { status: \"unavailable\", reason: `exchange_${res.status}` };\n const answer = (await res.json()) as {\n assertion: string;\n identity_id: string;\n roles: Array<string>;\n };\n return this.hydraToken(answer);\n }\n\n private async hydraToken(answer: {\n assertion: string;\n identity_id: string;\n roles: Array<string>;\n }): Promise<ExchangeOutcome> {\n // An -admin client authenticates with client_secret_post, unlike the\n // provisioner: Hydra refuses its credentials in a Basic header.\n const form = new URLSearchParams({\n grant_type: JWT_BEARER,\n assertion: answer.assertion,\n audience: this.config.product,\n client_id: this.config.admin.clientId,\n client_secret: this.config.admin.clientSecret,\n });\n let res: Response;\n try {\n res = await this.fetchImpl(\n new URL(\"/oauth2/token\", this.config.issuerUrl),\n {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/x-www-form-urlencoded\",\n },\n body: form,\n },\n );\n } catch {\n return { status: \"unavailable\", reason: \"hydra\" };\n }\n if (!res.ok)\n return { status: \"unavailable\", reason: `hydra_${res.status}` };\n const body = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n return {\n status: \"ok\",\n token: {\n accessToken: body.access_token,\n expiresAt: Date.now() + body.expires_in * 1000,\n identityId: answer.identity_id,\n roles: answer.roles,\n },\n };\n }\n\n private async machineToken(): Promise<string | null> {\n if (\n this.machine &&\n this.machine.expiresAt - Date.now() > REFRESH_MARGIN_MS\n ) {\n return this.machine.token;\n }\n let res: Response;\n try {\n res = await this.fetchImpl(\n new URL(\"/oauth2/token\", this.config.issuerUrl),\n {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/x-www-form-urlencoded\",\n authorization: basic(\n this.config.provisioner.clientId,\n this.config.provisioner.clientSecret,\n ),\n },\n body: new URLSearchParams({\n grant_type: \"client_credentials\",\n scope: EXCHANGE_SCOPE,\n audience: \"urbangate\",\n }),\n },\n );\n } catch {\n return null;\n }\n if (!res.ok) return null;\n const body = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n this.machine = {\n token: body.access_token,\n expiresAt: Date.now() + body.expires_in * 1000,\n };\n return this.machine.token;\n }\n}\n\nexport function decodeToken(\n raw: string,\n): { identityId: string; roles: Array<string>; expiresAt: number } | null {\n const parts = raw.split(\".\");\n if (parts.length !== 3) return null;\n try {\n const b64 = parts[1].replace(/-/g, \"+\").replace(/_/g, \"/\");\n const payload = JSON.parse(decodeURIComponent(escape(atob(b64)))) as {\n sub?: string;\n roles?: unknown;\n exp?: number;\n };\n if (!payload.sub || !payload.exp) return null;\n return {\n identityId: payload.sub,\n roles: Array.isArray(payload.roles)\n ? payload.roles.filter((r): r is string => typeof r === \"string\")\n : [],\n expiresAt: payload.exp * 1000,\n };\n } catch {\n return null;\n }\n}\n","export type Fetch = typeof fetch;\n\nexport interface KratosFlow {\n id: string;\n ui?: {\n messages?: Array<KratosMessage>;\n nodes?: Array<{\n attributes?: { name?: string; value?: unknown };\n messages?: Array<KratosMessage>;\n }>;\n };\n state?: string;\n}\n\nexport interface KratosMessage {\n id?: number;\n text?: string;\n type?: string;\n}\n\nexport interface KratosSession {\n id: string;\n active?: boolean;\n expires_at?: string;\n authenticated_at?: string;\n identity?: {\n id: string;\n state?: string;\n traits?: { email?: string; name?: string };\n verifiable_addresses?: Array<{ value: string; verified: boolean }>;\n };\n}\n\nexport interface KratosSessionResult {\n session_token?: string;\n session?: KratosSession;\n continue_with?: Array<ContinueWith>;\n}\n\nexport type ContinueWith =\n | { action: \"show_verification_ui\"; flow: { id: string } }\n | { action: \"show_settings_ui\"; flow: { id: string } }\n | { action: \"set_ory_session_token\"; ory_session_token: string }\n | { action: string };\n\nexport type KratosFailure =\n | { status: \"invalid_credentials\" }\n | { status: \"invalid_code\" }\n | { status: \"email_not_verified\" }\n | { status: \"second_factor_required\" }\n | { status: \"account_disabled\" }\n | { status: \"already_registered\" }\n | { status: \"password_refused\"; message: string }\n | { status: \"invalid_input\"; message: string }\n | { status: \"flow_expired\" }\n | { status: \"unavailable\" };\n\nexport class KratosError extends Error {\n failure: KratosFailure;\n constructor(failure: KratosFailure) {\n super(failure.status);\n this.failure = failure;\n }\n}\n\n// Kratos' message ids, from its text catalogue. A wrong password and an\n// unknown address share one id on purpose.\nconst INVALID_CREDENTIALS = 4000006;\nconst INVALID_CODE = new Set([4010008, 4060006, 4070006]);\nconst CODE_SENT = new Set([1010014, 1040005, 1060003, 1070003, 1080003]);\nconst ALREADY_REGISTERED = 4000007;\nconst PASSWORD_POLICY = new Set([4000005, 4000031, 4000032, 4000033, 4000034]);\nconst FLOW_EXPIRED = new Set([4010001, 4040001, 4060005, 4070005]);\n\nexport function failureOf(status: number, body: unknown): KratosFailure {\n const b = (body ?? {}) as {\n error?: { id?: string; code?: number; message?: string };\n ui?: KratosFlow[\"ui\"];\n };\n if (status === 410 || FLOW_EXPIRED.has(b.error?.code ?? -1)) {\n return { status: \"flow_expired\" };\n }\n if (b.error?.id === \"session_aal2_required\") {\n return { status: \"second_factor_required\" };\n }\n if (b.error?.id === \"self_service_flow_expired\")\n return { status: \"flow_expired\" };\n if (status >= 500 || status === 0) return { status: \"unavailable\" };\n const messages = [\n ...(b.ui?.messages ?? []),\n ...(b.ui?.nodes ?? []).flatMap((n) => n.messages ?? []),\n ].filter((m) => m.type === \"error\");\n for (const m of messages) {\n if (m.id === INVALID_CREDENTIALS) return { status: \"invalid_credentials\" };\n if (INVALID_CODE.has(m.id ?? -1)) return { status: \"invalid_code\" };\n if (m.id === ALREADY_REGISTERED) return { status: \"already_registered\" };\n if (PASSWORD_POLICY.has(m.id ?? -1)) {\n return { status: \"password_refused\", message: m.text ?? \"\" };\n }\n if (FLOW_EXPIRED.has(m.id ?? -1)) return { status: \"flow_expired\" };\n }\n if (b.error?.id === \"account_disabled\" || status === 401) {\n return { status: \"account_disabled\" };\n }\n const first = messages[0]?.text ?? b.error?.message ?? \"\";\n return { status: \"invalid_input\", message: first };\n}\n\nexport function codeWasSent(flow: KratosFlow): boolean {\n return (flow.ui?.messages ?? []).some((m) => CODE_SENT.has(m.id ?? -1));\n}\n\nexport function csrfOf(flow: KratosFlow): string | undefined {\n const node = flow.ui?.nodes?.find((n) => n.attributes?.name === \"csrf_token\");\n return typeof node?.attributes?.value === \"string\"\n ? node.attributes.value\n : undefined;\n}\n\nexport type FlowKind =\n \"login\" | \"registration\" | \"verification\" | \"recovery\" | \"settings\";\n\n/**\n * Kratos' native (API) flows, the ones an app that renders its own screens\n * drives from its server: no cookies, no CSRF, a session token in the answer.\n */\nexport class KratosFlows {\n private readonly baseUrl: string;\n private readonly fetchImpl: Fetch;\n\n constructor(baseUrl: string, fetchImpl: Fetch = fetch) {\n this.baseUrl = baseUrl;\n this.fetchImpl = fetchImpl;\n }\n\n async start(\n kind: FlowKind,\n sessionToken?: string,\n params: Record<string, string> = {},\n ): Promise<KratosFlow> {\n const url = new URL(`/self-service/${kind}/api`, this.baseUrl);\n for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);\n const res = await this.call(url, { method: \"GET\" }, sessionToken);\n return (await this.body(res)) as KratosFlow;\n }\n\n async submit<T = KratosFlow>(\n kind: FlowKind,\n flowId: string,\n body: Record<string, unknown>,\n sessionToken?: string,\n ): Promise<T> {\n const url = new URL(`/self-service/${kind}`, this.baseUrl);\n url.searchParams.set(\"flow\", flowId);\n const res = await this.call(\n url,\n {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n },\n sessionToken,\n );\n return (await this.body(res)) as T;\n }\n\n async whoami(sessionToken: string): Promise<KratosSession | null> {\n const url = new URL(\"/sessions/whoami\", this.baseUrl);\n let res: Response;\n try {\n res = await this.fetchImpl(url, {\n headers: { \"x-session-token\": sessionToken },\n });\n } catch {\n throw new KratosError({ status: \"unavailable\" });\n }\n if (res.status === 401 || res.status === 403) return null;\n if (!res.ok) throw new KratosError({ status: \"unavailable\" });\n return (await res.json()) as KratosSession;\n }\n\n async logout(sessionToken: string): Promise<void> {\n const url = new URL(\"/self-service/logout/api\", this.baseUrl);\n try {\n await this.fetchImpl(url, {\n method: \"DELETE\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ session_token: sessionToken }),\n });\n } catch {\n // The cookie is dropped regardless; a token Kratos still knows expires\n // on its own, and the person is signed out of this app either way.\n }\n }\n\n private async call(\n url: URL,\n init: RequestInit,\n sessionToken?: string,\n ): Promise<Response> {\n const headers = new Headers(init.headers);\n headers.set(\"accept\", \"application/json\");\n if (sessionToken) headers.set(\"x-session-token\", sessionToken);\n try {\n return await this.fetchImpl(url, { ...init, headers });\n } catch {\n throw new KratosError({ status: \"unavailable\" });\n }\n }\n\n private async body(res: Response): Promise<unknown> {\n const body = await res.json().catch(() => null);\n // A recovery code that verified answers 422 with what to do next, which\n // is a success shaped as a refusal.\n if (\n res.ok ||\n (res.status === 422 && body && \"continue_with\" in (body as object))\n ) {\n return body;\n }\n // A submitted flow that still has errors comes back as the flow itself\n // with a 400, and the messages say which field.\n throw new KratosError(failureOf(res.status, body));\n }\n}\n","import { forwardHeaders } from \"../core-proxy.ts\";\nimport { clearCookie, readCookie, serializeCookie } from \"./cookies.ts\";\nimport { Exchange, decodeToken } from \"./exchange.ts\";\nimport type { ExchangeConfig, PersonToken } from \"./exchange.ts\";\nimport { KratosError, KratosFlows, codeWasSent } from \"./kratos.ts\";\nimport type {\n ContinueWith,\n Fetch,\n KratosFailure,\n KratosSession,\n KratosSessionResult,\n} from \"./kratos.ts\";\n\nexport interface UrbangateAuthConfig {\n product: string;\n /** Kratos' public API as the server reaches it, e.g. http://kratos:4433 or https://id.urbangate.dev. */\n kratosUrl: string;\n urbangate: Omit<ExchangeConfig, \"product\">;\n cookie?: { name?: string; secure?: boolean };\n fetch?: Fetch;\n}\n\nexport interface UrbangateUser {\n id: string;\n identityId: string;\n email: string;\n emailVerified: boolean;\n name: string;\n role: \"admin\" | \"user\";\n}\n\nexport interface UrbangateSession {\n user: UrbangateUser;\n session: { expiresAt: string };\n}\n\nexport interface AccessToken {\n token: string;\n setCookie?: string;\n}\n\nexport interface UrbangateCoreProxyOptions {\n coreUrl: string;\n adminOnly?: boolean;\n}\n\nexport interface UrbangateAuth {\n handler(request: Request): Promise<Response>;\n getSession(headers: Headers): Promise<UrbangateSession | null>;\n accessToken(headers: Headers): Promise<AccessToken | null>;\n coreProxy(\n options: UrbangateCoreProxyOptions,\n ): (request: Request) => Promise<Response>;\n}\n\nconst SESSION_MAX_AGE = 30 * 24 * 60 * 60;\nconst FLOW_MAX_AGE = 600;\nconst TOKEN_MAX_AGE = 15 * 60;\n\ntype OtpType = \"email-verification\" | \"forget-password\" | \"sign-in\";\n\nconst FLOW_OF_TYPE: Record<OtpType, \"verification\" | \"recovery\" | \"login\"> = {\n \"email-verification\": \"verification\",\n \"forget-password\": \"recovery\",\n \"sign-in\": \"login\",\n};\n\nconst FAILURE_STATUS: Record<KratosFailure[\"status\"], number> = {\n invalid_credentials: 401,\n invalid_code: 400,\n email_not_verified: 403,\n second_factor_required: 403,\n account_disabled: 403,\n already_registered: 409,\n password_refused: 422,\n invalid_input: 400,\n flow_expired: 410,\n unavailable: 503,\n};\n\nfunction json(\n status: number,\n body: unknown,\n cookies: Array<string> = [],\n): Response {\n const headers = new Headers({ \"content-type\": \"application/json\" });\n for (const c of cookies) headers.append(\"set-cookie\", c);\n return new Response(JSON.stringify(body), { status, headers });\n}\n\nfunction failure(code: string, status: number, message?: string): Response {\n return json(status, {\n error: { code, status, ...(message ? { message } : {}) },\n });\n}\n\nfunction failed(error: unknown): Response {\n if (error instanceof KratosError) {\n const f = error.failure;\n return failure(\n f.status,\n FAILURE_STATUS[f.status],\n \"message\" in f ? f.message : undefined,\n );\n }\n return failure(\"unavailable\", 503);\n}\n\nfunction userOf(\n session: KratosSession,\n roles: Array<string>,\n product: string,\n): UrbangateUser | null {\n const identity = session.identity;\n if (!identity) return null;\n const email = identity.traits?.email ?? \"\";\n const address = identity.verifiable_addresses?.find((a) => a.value === email);\n return {\n id: identity.id,\n identityId: identity.id,\n email,\n emailVerified: address?.verified ?? false,\n name: identity.traits?.name ?? \"\",\n role: roles.includes(`${product}:admin`) ? \"admin\" : \"user\",\n };\n}\n\nfunction continueWith<A extends ContinueWith[\"action\"]>(\n list: Array<ContinueWith> | undefined,\n action: A,\n): Extract<ContinueWith, { action: A }> | undefined {\n return list?.find(\n (c): c is Extract<ContinueWith, { action: A }> => c.action === action,\n );\n}\n\nexport function createUrbangateAuth(\n config: UrbangateAuthConfig,\n): UrbangateAuth {\n const product = config.product;\n const kratos = new KratosFlows(config.kratosUrl, config.fetch);\n const exchange = new Exchange({ ...config.urbangate, product }, config.fetch);\n const names = {\n session: config.cookie?.name ?? `${product}_session`,\n token: `${product}_token`,\n flow: `${product}_flow`,\n };\n const secure =\n config.cookie?.secure ?? config.urbangate.issuerUrl.startsWith(\"https://\");\n\n const cookie = (name: string, value: string, maxAge: number) =>\n serializeCookie(name, value, { maxAge, secure });\n\n const signedIn = (result: KratosSessionResult): Array<string> => {\n const token = result.session_token;\n if (!token) return [];\n return [\n cookie(names.session, token, SESSION_MAX_AGE),\n clearCookie(names.flow, secure),\n ];\n };\n\n const readToken = (headers: Headers): PersonToken | null => {\n const raw = readCookie(headers, names.token);\n const decoded = raw ? decodeToken(raw) : null;\n return raw && decoded ? { accessToken: raw, ...decoded } : null;\n };\n\n async function accessToken(headers: Headers): Promise<AccessToken | null> {\n const sessionToken = readCookie(headers, names.session);\n if (!sessionToken) return null;\n const held = readToken(headers);\n if (held && !exchange.needsRefresh(held))\n return { token: held.accessToken };\n const outcome = await exchange.exchange(sessionToken);\n if (outcome.status === \"session_gone\" || outcome.status === \"inactive\")\n return null;\n if (outcome.status === \"unavailable\")\n throw new KratosError({ status: \"unavailable\" });\n return {\n token: outcome.token.accessToken,\n setCookie: cookie(names.token, outcome.token.accessToken, TOKEN_MAX_AGE),\n };\n }\n\n async function getSession(\n headers: Headers,\n ): Promise<UrbangateSession | null> {\n const sessionToken = readCookie(headers, names.session);\n if (!sessionToken) return null;\n const session = await kratos.whoami(sessionToken);\n if (!session?.active) return null;\n const held = readToken(headers);\n const user = userOf(session, held?.roles ?? [], product);\n if (!user) return null;\n return { user, session: { expiresAt: session.expires_at ?? \"\" } };\n }\n\n async function body(request: Request): Promise<Record<string, unknown>> {\n const raw = (await request.json().catch(() => null)) as Record<\n string,\n unknown\n > | null;\n return raw ?? {};\n }\n\n const str = (b: Record<string, unknown>, key: string) =>\n typeof b[key] === \"string\" ? (b[key] as string).trim() : \"\";\n\n async function signInEmail(request: Request): Promise<Response> {\n const b = await body(request);\n const email = str(b, \"email\");\n const password = typeof b.password === \"string\" ? b.password : \"\";\n if (!email || !password)\n return failure(\"invalid_input\", 400, \"email and password are required\");\n const flow = await kratos.start(\"login\");\n const result = await kratos.submit<KratosSessionResult>(\"login\", flow.id, {\n method: \"password\",\n identifier: email,\n password,\n });\n return json(\n 200,\n { user: result.session ? userOf(result.session, [], product) : null },\n signedIn(result),\n );\n }\n\n async function signUpEmail(request: Request): Promise<Response> {\n const b = await body(request);\n const email = str(b, \"email\");\n const password = typeof b.password === \"string\" ? b.password : \"\";\n if (!email || !password)\n return failure(\"invalid_input\", 400, \"email and password are required\");\n const flow = await kratos.start(\"registration\");\n const result = await kratos.submit<KratosSessionResult>(\n \"registration\",\n flow.id,\n {\n method: \"password\",\n traits: { email, ...(str(b, \"name\") ? { name: str(b, \"name\") } : {}) },\n password,\n },\n );\n const verification = continueWith(\n result.continue_with,\n \"show_verification_ui\",\n );\n const cookies = signedIn(result);\n if (verification)\n cookies.push(\n cookie(\n names.flow,\n `verification:${verification.flow.id}`,\n FLOW_MAX_AGE,\n ),\n );\n return json(\n 200,\n {\n user: result.session ? userOf(result.session, [], product) : null,\n ...(verification\n ? { verification: { flowId: verification.flow.id } }\n : {}),\n },\n cookies,\n );\n }\n\n async function sendOtp(request: Request): Promise<Response> {\n const b = await body(request);\n const email = str(b, \"email\");\n const type = str(b, \"type\") as OtpType;\n const kind = FLOW_OF_TYPE[type];\n if (!email || !kind)\n return failure(\"invalid_input\", 400, \"email and type are required\");\n const flow = await kratos.start(kind);\n const submitted = await kratos.submit(kind, flow.id, {\n method: \"code\",\n ...(kind === \"login\" ? { identifier: email } : { email }),\n });\n if (!codeWasSent(submitted) && submitted.state !== \"sent_email\") {\n return failure(\"invalid_input\", 400, \"the code could not be sent\");\n }\n return json(200, { sent: true }, [\n cookie(names.flow, `${kind}:${flow.id}`, FLOW_MAX_AGE),\n ]);\n }\n\n function pendingFlow(headers: Headers, kind: string): string | null {\n const raw = readCookie(headers, names.flow) ?? \"\";\n const [k, id] = raw.split(\":\");\n return k === kind && id ? id : null;\n }\n\n async function signInOtp(request: Request): Promise<Response> {\n const b = await body(request);\n const email = str(b, \"email\");\n const code = str(b, \"otp\");\n const flowId = pendingFlow(request.headers, \"login\");\n if (!flowId) return failure(\"flow_expired\", 410);\n if (!email || !code)\n return failure(\"invalid_input\", 400, \"email and otp are required\");\n const result = await kratos.submit<KratosSessionResult>(\"login\", flowId, {\n method: \"code\",\n identifier: email,\n code,\n });\n return json(\n 200,\n { user: result.session ? userOf(result.session, [], product) : null },\n signedIn(result),\n );\n }\n\n async function verifyEmail(request: Request): Promise<Response> {\n const b = await body(request);\n const code = str(b, \"otp\");\n const flowId = pendingFlow(request.headers, \"verification\");\n if (!flowId) return failure(\"flow_expired\", 410);\n if (!code) return failure(\"invalid_input\", 400, \"otp is required\");\n const flow = await kratos.submit(\"verification\", flowId, {\n method: \"code\",\n code,\n });\n if (flow.state !== \"passed_challenge\") return failure(\"invalid_code\", 400);\n return json(200, { verified: true }, [clearCookie(names.flow, secure)]);\n }\n\n async function resetPassword(request: Request): Promise<Response> {\n const b = await body(request);\n const code = str(b, \"otp\");\n const password = typeof b.password === \"string\" ? b.password : \"\";\n const flowId = pendingFlow(request.headers, \"recovery\");\n if (!flowId) return failure(\"flow_expired\", 410);\n if (!code || !password)\n return failure(\"invalid_input\", 400, \"otp and password are required\");\n const recovered = await kratos.submit<KratosSessionResult>(\n \"recovery\",\n flowId,\n { method: \"code\", code },\n );\n const token = continueWith(\n recovered.continue_with,\n \"set_ory_session_token\",\n )?.ory_session_token;\n const settings = continueWith(recovered.continue_with, \"show_settings_ui\");\n if (!token || !settings) return failure(\"invalid_code\", 400);\n await kratos.submit(\n \"settings\",\n settings.flow.id,\n { method: \"password\", password },\n token,\n );\n return json(200, { reset: true }, [\n cookie(names.session, token, SESSION_MAX_AGE),\n clearCookie(names.flow, secure),\n ]);\n }\n\n async function signOut(request: Request): Promise<Response> {\n const token = readCookie(request.headers, names.session);\n if (token) await kratos.logout(token);\n return json(200, { signedOut: true }, [\n clearCookie(names.session, secure),\n clearCookie(names.token, secure),\n clearCookie(names.flow, secure),\n ]);\n }\n\n async function session(request: Request): Promise<Response> {\n const s = await getSession(request.headers);\n return json(200, s);\n }\n\n const routes: Record<string, (request: Request) => Promise<Response>> = {\n \"POST sign-in/email\": signInEmail,\n \"POST sign-in/email-otp\": signInOtp,\n \"POST sign-up/email\": signUpEmail,\n \"POST email-otp/send-verification-otp\": sendOtp,\n \"POST email-otp/verify-email\": verifyEmail,\n \"POST email-otp/reset-password\": resetPassword,\n \"POST sign-out\": signOut,\n \"GET get-session\": session,\n };\n\n async function handler(request: Request): Promise<Response> {\n const path = new URL(request.url).pathname.replace(/\\/$/, \"\");\n const suffix = path.slice(path.indexOf(\"/api/auth/\") + \"/api/auth/\".length);\n if (suffix === \"sign-in/social\") return failure(\"not_supported\", 501);\n const route = routes[`${request.method} ${suffix}`];\n if (!route) return failure(\"not_found\", 404);\n try {\n return await route(request);\n } catch (error) {\n return failed(error);\n }\n }\n\n function coreProxy(options: UrbangateCoreProxyOptions) {\n const coreUrl = options.coreUrl.replace(/\\/$/, \"\");\n return async (request: Request): Promise<Response> => {\n let s: UrbangateSession | null;\n let token: AccessToken | null;\n try {\n token = await accessToken(request.headers);\n s = token\n ? await getSession(\n new Headers({ ...cookieHeader(request.headers, names, token) }),\n )\n : null;\n } catch {\n return failure(\"identity_provider_unavailable\", 503);\n }\n if (!token || !s) return failure(\"sign_in_required\", 401);\n if (options.adminOnly && s.user.role !== \"admin\")\n return failure(\"forbidden\", 403);\n const url = new URL(request.url);\n const headers = forwardHeaders(request.headers);\n headers.set(\"authorization\", `Bearer ${token.token}`);\n const upstream = await fetch(`${coreUrl}${url.pathname}${url.search}`, {\n method: request.method,\n headers,\n body:\n request.method === \"GET\" || request.method === \"HEAD\"\n ? undefined\n : request.body,\n duplex: \"half\",\n redirect: \"manual\",\n } as RequestInit);\n const out = forwardHeaders(upstream.headers);\n if (token.setCookie) out.append(\"set-cookie\", token.setCookie);\n return new Response(upstream.body, {\n status: upstream.status,\n headers: out,\n });\n };\n }\n\n return { handler, getSession, accessToken, coreProxy };\n}\n\n// A token just exchanged is not in the request's cookie yet; the session\n// lookup reads the roles off it as if it were.\nfunction cookieHeader(\n headers: Headers,\n names: { session: string; token: string },\n token: AccessToken,\n) {\n const session = readCookie(headers, names.session) ?? \"\";\n return {\n cookie: `${names.session}=${encodeURIComponent(session)}; ${names.token}=${encodeURIComponent(token.token)}`,\n };\n}\n"],"mappings":"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","names":["failure","session"]}
1
+ {"version":3,"sources":["../src/urbangate/cookies.ts","../src/urbangate/exchange.ts","../src/urbangate/kratos.ts","../src/urbangate/server.ts"],"sourcesContent":["export interface CookieOptions {\n maxAge: number;\n secure: boolean;\n}\n\nexport function readCookie(headers: Headers, name: string): string | undefined {\n const raw = headers.get(\"cookie\") ?? \"\";\n for (const part of raw.split(\";\")) {\n const [k, ...v] = part.trim().split(\"=\");\n if (k === name) return decodeURIComponent(v.join(\"=\"));\n }\n return undefined;\n}\n\nexport function serializeCookie(\n name: string,\n value: string,\n o: CookieOptions,\n): string {\n const attrs = [\n `${name}=${encodeURIComponent(value)}`,\n \"Path=/\",\n \"HttpOnly\",\n \"SameSite=Lax\",\n `Max-Age=${value ? o.maxAge : 0}`,\n ];\n if (o.secure) attrs.push(\"Secure\");\n return attrs.join(\"; \");\n}\n\nexport function clearCookie(name: string, secure: boolean): string {\n return serializeCookie(name, \"\", { maxAge: 0, secure });\n}\n","import type { Fetch } from \"./kratos\";\n\nexport interface ExchangeConfig {\n product: string;\n issuerUrl: string;\n provisioner: { clientId: string; clientSecret: string };\n admin: { clientId: string; clientSecret: string };\n}\n\nexport interface PersonToken {\n accessToken: string;\n expiresAt: number;\n identityId: string;\n roles: Array<string>;\n}\n\nexport type ExchangeOutcome =\n | { status: \"ok\"; token: PersonToken }\n | { status: \"session_gone\" }\n | { status: \"inactive\" }\n | { status: \"unavailable\"; reason: string };\n\nconst JWT_BEARER = \"urn:ietf:params:oauth:grant-type:jwt-bearer\";\nconst EXCHANGE_SCOPE = \"urbangate:sessions:exchange\";\nconst REFRESH_MARGIN_MS = 60_000;\n\nfunction basic(id: string, secret: string) {\n return \"Basic \" + btoa(`${id}:${secret}`);\n}\n\n/**\n * Turns a Kratos session token into the person's Hydra access token, in the\n * three calls ADR 0009 of urbangate describes: a machine token for the\n * provisioner, the assertion from urbangate, the token from Hydra with the\n * product's own admin client. Hydra issues no refresh token on this grant,\n * so the caller exchanges again when the token nears its end.\n */\nexport class Exchange {\n private machine: { token: string; expiresAt: number } | null = null;\n\n private readonly config: ExchangeConfig;\n private readonly fetchImpl: Fetch;\n\n constructor(config: ExchangeConfig, fetchImpl: Fetch = fetch) {\n this.config = config;\n this.fetchImpl = fetchImpl;\n }\n\n needsRefresh(token: PersonToken | null, now = Date.now()): boolean {\n return !token || token.expiresAt - now < REFRESH_MARGIN_MS;\n }\n\n async exchange(sessionToken: string): Promise<ExchangeOutcome> {\n const machine = await this.machineToken();\n if (!machine) return { status: \"unavailable\", reason: \"machine_token\" };\n const url = new URL(\n \"/api/machine/sessions/exchange\",\n this.config.issuerUrl,\n );\n let res: Response;\n try {\n res = await this.fetchImpl(url, {\n method: \"POST\",\n headers: {\n authorization: `Bearer ${machine}`,\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({ session_token: sessionToken }),\n });\n } catch {\n return { status: \"unavailable\", reason: \"exchange\" };\n }\n if (res.status === 401) {\n const body = (await res.json().catch(() => ({}))) as { error?: string };\n // Our own credential was refused: fetch a fresh one next time.\n if (body.error === \"invalid_token\") this.machine = null;\n return body.error === \"session\"\n ? { status: \"session_gone\" }\n : { status: \"unavailable\", reason: \"exchange_401\" };\n }\n if (res.status === 403) return { status: \"inactive\" };\n if (!res.ok)\n return { status: \"unavailable\", reason: `exchange_${res.status}` };\n const answer = (await res.json()) as {\n assertion: string;\n identity_id: string;\n roles: Array<string>;\n };\n return this.hydraToken(answer);\n }\n\n private async hydraToken(answer: {\n assertion: string;\n identity_id: string;\n roles: Array<string>;\n }): Promise<ExchangeOutcome> {\n // An -admin client authenticates with client_secret_post, unlike the\n // provisioner: Hydra refuses its credentials in a Basic header.\n const form = new URLSearchParams({\n grant_type: JWT_BEARER,\n assertion: answer.assertion,\n audience: this.config.product,\n client_id: this.config.admin.clientId,\n client_secret: this.config.admin.clientSecret,\n });\n let res: Response;\n try {\n res = await this.fetchImpl(\n new URL(\"/oauth2/token\", this.config.issuerUrl),\n {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/x-www-form-urlencoded\",\n },\n body: form,\n },\n );\n } catch {\n return { status: \"unavailable\", reason: \"hydra\" };\n }\n if (!res.ok)\n return { status: \"unavailable\", reason: `hydra_${res.status}` };\n const body = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n return {\n status: \"ok\",\n token: {\n accessToken: body.access_token,\n expiresAt: Date.now() + body.expires_in * 1000,\n identityId: answer.identity_id,\n roles: answer.roles,\n },\n };\n }\n\n private async machineToken(): Promise<string | null> {\n if (\n this.machine &&\n this.machine.expiresAt - Date.now() > REFRESH_MARGIN_MS\n ) {\n return this.machine.token;\n }\n let res: Response;\n try {\n res = await this.fetchImpl(\n new URL(\"/oauth2/token\", this.config.issuerUrl),\n {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/x-www-form-urlencoded\",\n authorization: basic(\n this.config.provisioner.clientId,\n this.config.provisioner.clientSecret,\n ),\n },\n body: new URLSearchParams({\n grant_type: \"client_credentials\",\n scope: EXCHANGE_SCOPE,\n audience: \"urbangate\",\n }),\n },\n );\n } catch {\n return null;\n }\n if (!res.ok) return null;\n const body = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n this.machine = {\n token: body.access_token,\n expiresAt: Date.now() + body.expires_in * 1000,\n };\n return this.machine.token;\n }\n}\n\nexport function decodeToken(\n raw: string,\n): { identityId: string; roles: Array<string>; expiresAt: number } | null {\n const parts = raw.split(\".\");\n if (parts.length !== 3) return null;\n try {\n const b64 = parts[1].replace(/-/g, \"+\").replace(/_/g, \"/\");\n const payload = JSON.parse(decodeURIComponent(escape(atob(b64)))) as {\n sub?: string;\n roles?: unknown;\n exp?: number;\n };\n if (!payload.sub || !payload.exp) return null;\n return {\n identityId: payload.sub,\n roles: Array.isArray(payload.roles)\n ? payload.roles.filter((r): r is string => typeof r === \"string\")\n : [],\n expiresAt: payload.exp * 1000,\n };\n } catch {\n return null;\n }\n}\n","export type Fetch = typeof fetch;\n\nexport interface KratosFlow {\n id: string;\n ui?: {\n messages?: Array<KratosMessage>;\n nodes?: Array<{\n attributes?: { name?: string; value?: unknown };\n messages?: Array<KratosMessage>;\n }>;\n };\n state?: string;\n}\n\nexport interface KratosMessage {\n id?: number;\n text?: string;\n type?: string;\n}\n\nexport interface KratosSession {\n id: string;\n active?: boolean;\n expires_at?: string;\n authenticated_at?: string;\n identity?: {\n id: string;\n state?: string;\n traits?: { email?: string; name?: string };\n verifiable_addresses?: Array<{ value: string; verified: boolean }>;\n };\n}\n\nexport interface KratosSessionResult {\n session_token?: string;\n session?: KratosSession;\n continue_with?: Array<ContinueWith>;\n}\n\nexport type ContinueWith =\n | { action: \"show_verification_ui\"; flow: { id: string } }\n | { action: \"show_settings_ui\"; flow: { id: string } }\n | { action: \"set_ory_session_token\"; ory_session_token: string }\n | { action: string };\n\nexport type KratosFailure =\n | { status: \"invalid_credentials\" }\n | { status: \"invalid_code\" }\n | { status: \"email_not_verified\" }\n | { status: \"second_factor_required\" }\n | { status: \"account_disabled\" }\n | { status: \"already_registered\" }\n | { status: \"account_not_found\" }\n | { status: \"password_refused\"; message: string }\n | { status: \"invalid_input\"; message: string }\n | { status: \"flow_expired\" }\n | { status: \"unavailable\" };\n\nexport class KratosError extends Error {\n failure: KratosFailure;\n constructor(failure: KratosFailure) {\n super(failure.status);\n this.failure = failure;\n }\n}\n\n// Kratos' message ids, from its text catalogue. A wrong password and an\n// unknown address share one id on purpose.\nconst INVALID_CREDENTIALS = 4000006;\nconst INVALID_CODE = new Set([4010008, 4060006, 4070006]);\nconst CODE_SENT = new Set([1010014, 1040005, 1060003, 1070003, 1080003]);\nconst ALREADY_REGISTERED = 4000007;\nconst ACCOUNT_NOT_FOUND = 4000035;\nconst PASSWORD_POLICY = new Set([4000005, 4000031, 4000032, 4000033, 4000034]);\nconst FLOW_EXPIRED = new Set([4010001, 4040001, 4060005, 4070005]);\n\nexport function failureOf(status: number, body: unknown): KratosFailure {\n const b = (body ?? {}) as {\n error?: { id?: string; code?: number; message?: string };\n ui?: KratosFlow[\"ui\"];\n };\n if (status === 410 || FLOW_EXPIRED.has(b.error?.code ?? -1)) {\n return { status: \"flow_expired\" };\n }\n if (b.error?.id === \"session_aal2_required\") {\n return { status: \"second_factor_required\" };\n }\n if (b.error?.id === \"self_service_flow_expired\")\n return { status: \"flow_expired\" };\n if (status >= 500 || status === 0) return { status: \"unavailable\" };\n const messages = [\n ...(b.ui?.messages ?? []),\n ...(b.ui?.nodes ?? []).flatMap((n) => n.messages ?? []),\n ].filter((m) => m.type === \"error\");\n for (const m of messages) {\n if (m.id === INVALID_CREDENTIALS) return { status: \"invalid_credentials\" };\n if (INVALID_CODE.has(m.id ?? -1)) return { status: \"invalid_code\" };\n if (m.id === ALREADY_REGISTERED) return { status: \"already_registered\" };\n if (m.id === ACCOUNT_NOT_FOUND) return { status: \"account_not_found\" };\n if (PASSWORD_POLICY.has(m.id ?? -1)) {\n return { status: \"password_refused\", message: m.text ?? \"\" };\n }\n if (FLOW_EXPIRED.has(m.id ?? -1)) return { status: \"flow_expired\" };\n }\n if (b.error?.id === \"account_disabled\" || status === 401) {\n return { status: \"account_disabled\" };\n }\n const first = messages[0]?.text ?? b.error?.message ?? \"\";\n return { status: \"invalid_input\", message: first };\n}\n\nexport function codeWasSent(flow: KratosFlow): boolean {\n return (flow.ui?.messages ?? []).some((m) => CODE_SENT.has(m.id ?? -1));\n}\n\nexport function csrfOf(flow: KratosFlow): string | undefined {\n const node = flow.ui?.nodes?.find((n) => n.attributes?.name === \"csrf_token\");\n return typeof node?.attributes?.value === \"string\"\n ? node.attributes.value\n : undefined;\n}\n\nexport type FlowKind =\n \"login\" | \"registration\" | \"verification\" | \"recovery\" | \"settings\";\n\n/**\n * Kratos' native (API) flows, the ones an app that renders its own screens\n * drives from its server: no cookies, no CSRF, a session token in the answer.\n */\nexport class KratosFlows {\n private readonly baseUrl: string;\n private readonly fetchImpl: Fetch;\n\n constructor(baseUrl: string, fetchImpl: Fetch = fetch) {\n this.baseUrl = baseUrl;\n this.fetchImpl = fetchImpl;\n }\n\n async start(\n kind: FlowKind,\n sessionToken?: string,\n params: Record<string, string> = {},\n ): Promise<KratosFlow> {\n const url = new URL(`/self-service/${kind}/api`, this.baseUrl);\n for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);\n const res = await this.call(url, { method: \"GET\" }, sessionToken);\n return (await this.body(res)) as KratosFlow;\n }\n\n async submit<T = KratosFlow>(\n kind: FlowKind,\n flowId: string,\n body: Record<string, unknown>,\n sessionToken?: string,\n ): Promise<T> {\n const url = new URL(`/self-service/${kind}`, this.baseUrl);\n url.searchParams.set(\"flow\", flowId);\n const res = await this.call(\n url,\n {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n },\n sessionToken,\n );\n return (await this.body(res)) as T;\n }\n\n async whoami(sessionToken: string): Promise<KratosSession | null> {\n const url = new URL(\"/sessions/whoami\", this.baseUrl);\n let res: Response;\n try {\n res = await this.fetchImpl(url, {\n headers: { \"x-session-token\": sessionToken },\n });\n } catch {\n throw new KratosError({ status: \"unavailable\" });\n }\n if (res.status === 401 || res.status === 403) return null;\n if (!res.ok) throw new KratosError({ status: \"unavailable\" });\n return (await res.json()) as KratosSession;\n }\n\n async logout(sessionToken: string): Promise<void> {\n const url = new URL(\"/self-service/logout/api\", this.baseUrl);\n try {\n await this.fetchImpl(url, {\n method: \"DELETE\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ session_token: sessionToken }),\n });\n } catch {\n // The cookie is dropped regardless; a token Kratos still knows expires\n // on its own, and the person is signed out of this app either way.\n }\n }\n\n private async call(\n url: URL,\n init: RequestInit,\n sessionToken?: string,\n ): Promise<Response> {\n const headers = new Headers(init.headers);\n headers.set(\"accept\", \"application/json\");\n if (sessionToken) headers.set(\"x-session-token\", sessionToken);\n try {\n return await this.fetchImpl(url, { ...init, headers });\n } catch {\n throw new KratosError({ status: \"unavailable\" });\n }\n }\n\n private async body(res: Response): Promise<unknown> {\n const body = await res.json().catch(() => null);\n // A recovery code that verified answers 422 with what to do next, which\n // is a success shaped as a refusal.\n if (\n res.ok ||\n (res.status === 422 && body && \"continue_with\" in (body as object))\n ) {\n return body;\n }\n // A submitted flow that still has errors comes back as the flow itself\n // with a 400, and the messages say which field.\n throw new KratosError(failureOf(res.status, body));\n }\n}\n","import { forwardHeaders } from \"../core-proxy.ts\";\nimport { clearCookie, readCookie, serializeCookie } from \"./cookies.ts\";\nimport { Exchange, decodeToken } from \"./exchange.ts\";\nimport type { ExchangeConfig, PersonToken } from \"./exchange.ts\";\nimport { KratosError, KratosFlows, codeWasSent } from \"./kratos.ts\";\nimport type {\n ContinueWith,\n Fetch,\n KratosFailure,\n KratosSession,\n KratosSessionResult,\n} from \"./kratos.ts\";\n\nexport interface UrbangateAuthConfig {\n product: string;\n /** Kratos' public API as the server reaches it, e.g. http://kratos:4433 or https://id.urbangate.dev. */\n kratosUrl: string;\n urbangate: Omit<ExchangeConfig, \"product\">;\n cookie?: { name?: string; secure?: boolean };\n fetch?: Fetch;\n}\n\nexport interface UrbangateUser {\n id: string;\n identityId: string;\n email: string;\n emailVerified: boolean;\n name: string;\n role: \"admin\" | \"user\";\n}\n\nexport interface UrbangateSession {\n user: UrbangateUser;\n session: { expiresAt: string };\n}\n\nexport interface AccessToken {\n token: string;\n setCookie?: string;\n}\n\nexport interface UrbangateCoreProxyOptions {\n coreUrl: string;\n adminOnly?: boolean;\n}\n\nexport interface UrbangateAuth {\n handler(request: Request): Promise<Response>;\n getSession(headers: Headers): Promise<UrbangateSession | null>;\n accessToken(headers: Headers): Promise<AccessToken | null>;\n coreProxy(\n options: UrbangateCoreProxyOptions,\n ): (request: Request) => Promise<Response>;\n}\n\nconst SESSION_MAX_AGE = 30 * 24 * 60 * 60;\nconst FLOW_MAX_AGE = 600;\nconst TOKEN_MAX_AGE = 15 * 60;\n\ntype OtpType = \"email-verification\" | \"forget-password\" | \"sign-in\";\n\nconst FLOW_OF_TYPE: Record<OtpType, \"verification\" | \"recovery\" | \"login\"> = {\n \"email-verification\": \"verification\",\n \"forget-password\": \"recovery\",\n \"sign-in\": \"login\",\n};\n\nconst FAILURE_STATUS: Record<KratosFailure[\"status\"], number> = {\n invalid_credentials: 401,\n invalid_code: 400,\n email_not_verified: 403,\n second_factor_required: 403,\n account_disabled: 403,\n already_registered: 409,\n account_not_found: 404,\n password_refused: 422,\n invalid_input: 400,\n flow_expired: 410,\n unavailable: 503,\n};\n\nfunction json(\n status: number,\n body: unknown,\n cookies: Array<string> = [],\n): Response {\n const headers = new Headers({ \"content-type\": \"application/json\" });\n for (const c of cookies) headers.append(\"set-cookie\", c);\n return new Response(JSON.stringify(body), { status, headers });\n}\n\nfunction failure(code: string, status: number, message?: string): Response {\n return json(status, {\n error: { code, status, ...(message ? { message } : {}) },\n });\n}\n\nfunction failed(error: unknown): Response {\n if (error instanceof KratosError) {\n const f = error.failure;\n return failure(\n f.status,\n FAILURE_STATUS[f.status],\n \"message\" in f ? f.message : undefined,\n );\n }\n return failure(\"unavailable\", 503);\n}\n\nfunction userOf(\n session: KratosSession,\n roles: Array<string>,\n product: string,\n): UrbangateUser | null {\n const identity = session.identity;\n if (!identity) return null;\n const email = identity.traits?.email ?? \"\";\n const address = identity.verifiable_addresses?.find((a) => a.value === email);\n return {\n id: identity.id,\n identityId: identity.id,\n email,\n emailVerified: address?.verified ?? false,\n name: identity.traits?.name ?? \"\",\n role: roles.includes(`${product}:admin`) ? \"admin\" : \"user\",\n };\n}\n\nfunction continueWith<A extends ContinueWith[\"action\"]>(\n list: Array<ContinueWith> | undefined,\n action: A,\n): Extract<ContinueWith, { action: A }> | undefined {\n return list?.find(\n (c): c is Extract<ContinueWith, { action: A }> => c.action === action,\n );\n}\n\nexport function createUrbangateAuth(\n config: UrbangateAuthConfig,\n): UrbangateAuth {\n const product = config.product;\n const kratos = new KratosFlows(config.kratosUrl, config.fetch);\n const exchange = new Exchange({ ...config.urbangate, product }, config.fetch);\n const names = {\n session: config.cookie?.name ?? `${product}_session`,\n token: `${product}_token`,\n flow: `${product}_flow`,\n };\n const secure =\n config.cookie?.secure ?? config.urbangate.issuerUrl.startsWith(\"https://\");\n\n const cookie = (name: string, value: string, maxAge: number) =>\n serializeCookie(name, value, { maxAge, secure });\n\n const signedIn = (result: KratosSessionResult): Array<string> => {\n const token = result.session_token;\n if (!token) return [];\n return [\n cookie(names.session, token, SESSION_MAX_AGE),\n clearCookie(names.flow, secure),\n ];\n };\n\n const readToken = (headers: Headers): PersonToken | null => {\n const raw = readCookie(headers, names.token);\n const decoded = raw ? decodeToken(raw) : null;\n return raw && decoded ? { accessToken: raw, ...decoded } : null;\n };\n\n async function accessToken(headers: Headers): Promise<AccessToken | null> {\n const sessionToken = readCookie(headers, names.session);\n if (!sessionToken) return null;\n const held = readToken(headers);\n if (held && !exchange.needsRefresh(held))\n return { token: held.accessToken };\n const outcome = await exchange.exchange(sessionToken);\n if (outcome.status === \"session_gone\" || outcome.status === \"inactive\")\n return null;\n if (outcome.status === \"unavailable\")\n throw new KratosError({ status: \"unavailable\" });\n return {\n token: outcome.token.accessToken,\n setCookie: cookie(names.token, outcome.token.accessToken, TOKEN_MAX_AGE),\n };\n }\n\n async function getSession(\n headers: Headers,\n ): Promise<UrbangateSession | null> {\n const sessionToken = readCookie(headers, names.session);\n if (!sessionToken) return null;\n const session = await kratos.whoami(sessionToken);\n if (!session?.active) return null;\n const held = readToken(headers);\n const user = userOf(session, held?.roles ?? [], product);\n if (!user) return null;\n return { user, session: { expiresAt: session.expires_at ?? \"\" } };\n }\n\n async function body(request: Request): Promise<Record<string, unknown>> {\n const raw = (await request.json().catch(() => null)) as Record<\n string,\n unknown\n > | null;\n return raw ?? {};\n }\n\n const str = (b: Record<string, unknown>, key: string) =>\n typeof b[key] === \"string\" ? (b[key] as string).trim() : \"\";\n\n async function signInEmail(request: Request): Promise<Response> {\n const b = await body(request);\n const email = str(b, \"email\");\n const password = typeof b.password === \"string\" ? b.password : \"\";\n if (!email || !password)\n return failure(\"invalid_input\", 400, \"email and password are required\");\n const flow = await kratos.start(\"login\");\n const result = await kratos.submit<KratosSessionResult>(\"login\", flow.id, {\n method: \"password\",\n identifier: email,\n password,\n });\n return json(\n 200,\n { user: result.session ? userOf(result.session, [], product) : null },\n signedIn(result),\n );\n }\n\n async function signUpEmail(request: Request): Promise<Response> {\n const b = await body(request);\n const email = str(b, \"email\");\n const password = typeof b.password === \"string\" ? b.password : \"\";\n if (!email || !password)\n return failure(\"invalid_input\", 400, \"email and password are required\");\n const flow = await kratos.start(\"registration\");\n const result = await kratos.submit<KratosSessionResult>(\n \"registration\",\n flow.id,\n {\n method: \"password\",\n traits: { email, ...(str(b, \"name\") ? { name: str(b, \"name\") } : {}) },\n password,\n },\n );\n const verification = continueWith(\n result.continue_with,\n \"show_verification_ui\",\n );\n const cookies = signedIn(result);\n if (verification)\n cookies.push(\n cookie(\n names.flow,\n `verification:${verification.flow.id}`,\n FLOW_MAX_AGE,\n ),\n );\n return json(\n 200,\n {\n user: result.session ? userOf(result.session, [], product) : null,\n ...(verification\n ? { verification: { flowId: verification.flow.id } }\n : {}),\n },\n cookies,\n );\n }\n\n async function sendOtp(request: Request): Promise<Response> {\n const b = await body(request);\n const email = str(b, \"email\");\n const type = str(b, \"type\") as OtpType;\n const kind = FLOW_OF_TYPE[type];\n if (!email || !kind)\n return failure(\"invalid_input\", 400, \"email and type are required\");\n const sent = await sendCode(kind, email);\n return json(200, { sent: true }, [\n cookie(names.flow, `${sent.kind}:${sent.flowId}`, FLOW_MAX_AGE),\n ]);\n }\n\n // A code asked for an address Kratos does not know is a sign-up, not a\n // refusal: the person typed their address on the product's screen and\n // expects a code either way, and a registration by code opens the session\n // exactly as a login does.\n async function sendCode(\n kind: \"verification\" | \"recovery\" | \"login\",\n email: string,\n ): Promise<{ kind: string; flowId: string }> {\n try {\n return await sendCodeOn(kind, email);\n } catch (error) {\n if (\n kind !== \"login\" ||\n !(error instanceof KratosError) ||\n error.failure.status !== \"account_not_found\"\n ) {\n throw error;\n }\n return sendCodeOn(\"registration\", email);\n }\n }\n\n async function sendCodeOn(\n kind: \"verification\" | \"recovery\" | \"login\" | \"registration\",\n email: string,\n ): Promise<{ kind: string; flowId: string }> {\n const flow = await kratos.start(kind);\n const submitted = await kratos.submit(kind, flow.id, {\n method: \"code\",\n ...(kind === \"login\"\n ? { identifier: email }\n : kind === \"registration\"\n ? { traits: { email } }\n : { email }),\n });\n if (!codeWasSent(submitted) && submitted.state !== \"sent_email\") {\n throw new KratosError({\n status: \"invalid_input\",\n message: \"the code could not be sent\",\n });\n }\n return { kind, flowId: flow.id };\n }\n\n function pendingFlow(headers: Headers, kind: string): string | null {\n const raw = readCookie(headers, names.flow) ?? \"\";\n const [k, id] = raw.split(\":\");\n return k === kind && id ? id : null;\n }\n\n async function signInOtp(request: Request): Promise<Response> {\n const b = await body(request);\n const email = str(b, \"email\");\n const code = str(b, \"otp\");\n const login = pendingFlow(request.headers, \"login\");\n const registration = pendingFlow(request.headers, \"registration\");\n const flowId = login ?? registration;\n if (!flowId) return failure(\"flow_expired\", 410);\n if (!email || !code)\n return failure(\"invalid_input\", 400, \"email and otp are required\");\n const result = login\n ? await kratos.submit<KratosSessionResult>(\"login\", flowId, {\n method: \"code\",\n identifier: email,\n code,\n })\n : await kratos.submit<KratosSessionResult>(\"registration\", flowId, {\n method: \"code\",\n traits: { email },\n code,\n });\n return json(\n 200,\n { user: result.session ? userOf(result.session, [], product) : null },\n signedIn(result),\n );\n }\n\n async function verifyEmail(request: Request): Promise<Response> {\n const b = await body(request);\n const code = str(b, \"otp\");\n const flowId = pendingFlow(request.headers, \"verification\");\n if (!flowId) return failure(\"flow_expired\", 410);\n if (!code) return failure(\"invalid_input\", 400, \"otp is required\");\n const flow = await kratos.submit(\"verification\", flowId, {\n method: \"code\",\n code,\n });\n if (flow.state !== \"passed_challenge\") return failure(\"invalid_code\", 400);\n return json(200, { verified: true }, [clearCookie(names.flow, secure)]);\n }\n\n async function resetPassword(request: Request): Promise<Response> {\n const b = await body(request);\n const code = str(b, \"otp\");\n const password = typeof b.password === \"string\" ? b.password : \"\";\n const flowId = pendingFlow(request.headers, \"recovery\");\n if (!flowId) return failure(\"flow_expired\", 410);\n if (!code || !password)\n return failure(\"invalid_input\", 400, \"otp and password are required\");\n const recovered = await kratos.submit<KratosSessionResult>(\n \"recovery\",\n flowId,\n { method: \"code\", code },\n );\n const token = continueWith(\n recovered.continue_with,\n \"set_ory_session_token\",\n )?.ory_session_token;\n const settings = continueWith(recovered.continue_with, \"show_settings_ui\");\n if (!token || !settings) return failure(\"invalid_code\", 400);\n await kratos.submit(\n \"settings\",\n settings.flow.id,\n { method: \"password\", password },\n token,\n );\n return json(200, { reset: true }, [\n cookie(names.session, token, SESSION_MAX_AGE),\n clearCookie(names.flow, secure),\n ]);\n }\n\n async function signOut(request: Request): Promise<Response> {\n const token = readCookie(request.headers, names.session);\n if (token) await kratos.logout(token);\n return json(200, { signedOut: true }, [\n clearCookie(names.session, secure),\n clearCookie(names.token, secure),\n clearCookie(names.flow, secure),\n ]);\n }\n\n async function session(request: Request): Promise<Response> {\n const s = await getSession(request.headers);\n return json(200, s);\n }\n\n const routes: Record<string, (request: Request) => Promise<Response>> = {\n \"POST sign-in/email\": signInEmail,\n \"POST sign-in/email-otp\": signInOtp,\n \"POST sign-up/email\": signUpEmail,\n \"POST email-otp/send-verification-otp\": sendOtp,\n \"POST email-otp/verify-email\": verifyEmail,\n \"POST email-otp/reset-password\": resetPassword,\n \"POST sign-out\": signOut,\n \"GET get-session\": session,\n };\n\n async function handler(request: Request): Promise<Response> {\n const path = new URL(request.url).pathname.replace(/\\/$/, \"\");\n const suffix = path.slice(path.indexOf(\"/api/auth/\") + \"/api/auth/\".length);\n if (suffix === \"sign-in/social\") return failure(\"not_supported\", 501);\n const route = routes[`${request.method} ${suffix}`];\n if (!route) return failure(\"not_found\", 404);\n try {\n return await route(request);\n } catch (error) {\n return failed(error);\n }\n }\n\n function coreProxy(options: UrbangateCoreProxyOptions) {\n const coreUrl = options.coreUrl.replace(/\\/$/, \"\");\n return async (request: Request): Promise<Response> => {\n let s: UrbangateSession | null;\n let token: AccessToken | null;\n try {\n token = await accessToken(request.headers);\n s = token\n ? await getSession(\n new Headers({ ...cookieHeader(request.headers, names, token) }),\n )\n : null;\n } catch {\n return failure(\"identity_provider_unavailable\", 503);\n }\n if (!token || !s) return failure(\"sign_in_required\", 401);\n if (options.adminOnly && s.user.role !== \"admin\")\n return failure(\"forbidden\", 403);\n const url = new URL(request.url);\n const headers = forwardHeaders(request.headers);\n headers.set(\"authorization\", `Bearer ${token.token}`);\n const upstream = await fetch(`${coreUrl}${url.pathname}${url.search}`, {\n method: request.method,\n headers,\n body:\n request.method === \"GET\" || request.method === \"HEAD\"\n ? undefined\n : request.body,\n duplex: \"half\",\n redirect: \"manual\",\n } as RequestInit);\n const out = forwardHeaders(upstream.headers);\n if (token.setCookie) out.append(\"set-cookie\", token.setCookie);\n return new Response(upstream.body, {\n status: upstream.status,\n headers: out,\n });\n };\n }\n\n return { handler, getSession, accessToken, coreProxy };\n}\n\n// A token just exchanged is not in the request's cookie yet; the session\n// lookup reads the roles off it as if it were.\nfunction cookieHeader(\n headers: Headers,\n names: { session: string; token: string },\n token: AccessToken,\n) {\n const session = readCookie(headers, names.session) ?? \"\";\n return {\n cookie: `${names.session}=${encodeURIComponent(session)}; ${names.token}=${encodeURIComponent(token.token)}`,\n };\n}\n"],"mappings":"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","names":["failure","session"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lalternative/auth",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Shared Better Auth wrapper for L'Alternative apps (server + React client + auth UI)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",