@broberg/sso 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/hono.d.cts CHANGED
@@ -291,6 +291,11 @@ interface SessionPayload {
291
291
  * reference, so a changed picture propagates without every app storing a
292
292
  * copy that then goes stale. */
293
293
  picture?: string;
294
+ /**
295
+ * Whether BID has verified `email` (F084.152). Absent when BID did not say.
296
+ * Bind access to an address only when this is `true`.
297
+ */
298
+ email_verified?: boolean;
294
299
  }
295
300
 
296
301
  /**
@@ -318,6 +323,15 @@ interface BackchannelOptions {
318
323
  onStoreError?: "reject" | "allow";
319
324
  }
320
325
  interface SsoRoutesOptions {
326
+ /**
327
+ * What «Log ud» in the app does (F084.152).
328
+ * "app" (default): end THIS app's session and send the user to BID's login
329
+ * dialog (prompt=login). They stay signed in to BID — its own «Log ud» is
330
+ * a separate action (decision D-376ffa) — and BID shows the dialog anyway.
331
+ * "central": the pre-0.7.0 behaviour — RP-initiated logout at BID's
332
+ * end-session endpoint, which also ends the BID session.
333
+ */
334
+ logout?: "app" | "central";
321
335
  /**
322
336
  * Receive «Log ud overalt» from BID at POST <mount>/backchannel-logout.
323
337
  * Nothing arrives until BID has registered that URL for this app (ship dark),
package/dist/hono.d.ts CHANGED
@@ -291,6 +291,11 @@ interface SessionPayload {
291
291
  * reference, so a changed picture propagates without every app storing a
292
292
  * copy that then goes stale. */
293
293
  picture?: string;
294
+ /**
295
+ * Whether BID has verified `email` (F084.152). Absent when BID did not say.
296
+ * Bind access to an address only when this is `true`.
297
+ */
298
+ email_verified?: boolean;
294
299
  }
295
300
 
296
301
  /**
@@ -318,6 +323,15 @@ interface BackchannelOptions {
318
323
  onStoreError?: "reject" | "allow";
319
324
  }
320
325
  interface SsoRoutesOptions {
326
+ /**
327
+ * What «Log ud» in the app does (F084.152).
328
+ * "app" (default): end THIS app's session and send the user to BID's login
329
+ * dialog (prompt=login). They stay signed in to BID — its own «Log ud» is
330
+ * a separate action (decision D-376ffa) — and BID shows the dialog anyway.
331
+ * "central": the pre-0.7.0 behaviour — RP-initiated logout at BID's
332
+ * end-session endpoint, which also ends the BID session.
333
+ */
334
+ logout?: "app" | "central";
321
335
  /**
322
336
  * Receive «Log ud overalt» from BID at POST <mount>/backchannel-logout.
323
337
  * Nothing arrives until BID has registered that URL for this app (ship dark),
package/dist/hono.js CHANGED
@@ -667,9 +667,8 @@ function ssoRoutes(options = {}) {
667
667
  return c.json({ error: "invalid_request", error_description: msg }, 400);
668
668
  }
669
669
  try {
670
- if (await backchannel.store.useJti(verified.jti, verified.exp)) {
671
- await backchannel.store.revokeSubBefore(verified.sub, verified.iat);
672
- }
670
+ await backchannel.store.revokeSubBefore(verified.sub, verified.iat);
671
+ await backchannel.store.useJti(verified.jti, verified.exp);
673
672
  } catch {
674
673
  return c.json({ error: "store_unavailable" }, 503);
675
674
  }
@@ -679,7 +678,7 @@ function ssoRoutes(options = {}) {
679
678
  app.get("/login", async (c) => {
680
679
  const prompt = c.req.query("prompt");
681
680
  const start = await client.beginLogin(
682
- prompt === "none" ? { prompt: "none" } : {}
681
+ prompt === "none" || prompt === "login" ? { prompt } : {}
683
682
  );
684
683
  const tx = JSON.stringify({
685
684
  state: start.state,
@@ -739,7 +738,9 @@ function ssoRoutes(options = {}) {
739
738
  iat: Math.floor(Date.now() / 1e3),
740
739
  ...result.claims.email ? { email: result.claims.email } : {},
741
740
  ...result.claims.name ? { name: result.claims.name } : {},
742
- ...result.claims.picture ? { picture: result.claims.picture } : {}
741
+ ...result.claims.picture ? { picture: result.claims.picture } : {},
742
+ // Only when BID actually said so — absent means "not stated", never false.
743
+ ...typeof result.claims.email_verified === "boolean" ? { email_verified: result.claims.email_verified } : {}
743
744
  };
744
745
  c.header(
745
746
  "Set-Cookie",
@@ -777,6 +778,9 @@ function ssoRoutes(options = {}) {
777
778
  cookieHeader(idTokenCookie, "", { maxAge: 0, secure: isSecure(c) }),
778
779
  { append: true }
779
780
  );
781
+ if ((options.logout ?? "app") === "app") {
782
+ return c.redirect(`${loginPath}/login?prompt=login`, 302);
783
+ }
780
784
  return c.redirect(await client.logoutUrl(idTokenHint ? { idTokenHint } : {}), 302);
781
785
  });
782
786
  const attach = async (c, next) => {