@broberg/sso 0.10.0 → 0.11.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
@@ -447,6 +447,10 @@ declare function ssoRoutes(options?: SsoRoutesOptions): {
447
447
  client: SsoClient;
448
448
  config: SsoConfig;
449
449
  getAccessToken: (c: Context) => Promise<string>;
450
+ refreshSessionProfile: (c: Context, profile: {
451
+ name: string | null;
452
+ picture: string | null;
453
+ }) => Promise<void>;
450
454
  };
451
455
  /** Why a login transaction could not be read back. Three states, three answers. */
452
456
  type TransactionFailure = "absent" | "expired" | "unreadable";
@@ -456,6 +460,15 @@ declare function getSession(c: Context): SessionPayload | null;
456
460
  interface AccountRoutesInput {
457
461
  client: Pick<SsoClient, "getProfile" | "updateProfile" | "uploadAvatar" | "removeAvatar">;
458
462
  getAccessToken(c: Context): Promise<string>;
463
+ /**
464
+ * F095.5 — re-sign the session cookie with the profile BID answered, so the
465
+ * user menu shows the new name and picture on the next page load. ssoRoutes
466
+ * provides it; optional so an older or hand-built input still works.
467
+ */
468
+ refreshSessionProfile?(c: Context, profile: {
469
+ name: string | null;
470
+ picture: string | null;
471
+ }): Promise<void>;
459
472
  }
460
473
  /**
461
474
  * The signed-in user's own name and picture, for the app's account page.
package/dist/hono.d.ts CHANGED
@@ -447,6 +447,10 @@ declare function ssoRoutes(options?: SsoRoutesOptions): {
447
447
  client: SsoClient;
448
448
  config: SsoConfig;
449
449
  getAccessToken: (c: Context) => Promise<string>;
450
+ refreshSessionProfile: (c: Context, profile: {
451
+ name: string | null;
452
+ picture: string | null;
453
+ }) => Promise<void>;
450
454
  };
451
455
  /** Why a login transaction could not be read back. Three states, three answers. */
452
456
  type TransactionFailure = "absent" | "expired" | "unreadable";
@@ -456,6 +460,15 @@ declare function getSession(c: Context): SessionPayload | null;
456
460
  interface AccountRoutesInput {
457
461
  client: Pick<SsoClient, "getProfile" | "updateProfile" | "uploadAvatar" | "removeAvatar">;
458
462
  getAccessToken(c: Context): Promise<string>;
463
+ /**
464
+ * F095.5 — re-sign the session cookie with the profile BID answered, so the
465
+ * user menu shows the new name and picture on the next page load. ssoRoutes
466
+ * provides it; optional so an older or hand-built input still works.
467
+ */
468
+ refreshSessionProfile?(c: Context, profile: {
469
+ name: string | null;
470
+ picture: string | null;
471
+ }): Promise<void>;
459
472
  }
460
473
  /**
461
474
  * The signed-in user's own name and picture, for the app's account page.
package/dist/hono.js CHANGED
@@ -1046,7 +1046,24 @@ function ssoRoutes(options = {}) {
1046
1046
  refreshing.delete(sid);
1047
1047
  }
1048
1048
  }
1049
- return { app, attach, require: require2, client, config, getAccessToken };
1049
+ async function refreshSessionProfile(c, profile) {
1050
+ const live = await liveSession(c);
1051
+ if (!live) return;
1052
+ const next = { ...live };
1053
+ if (profile.name) next.name = profile.name;
1054
+ else delete next.name;
1055
+ if (profile.picture) next.picture = profile.picture;
1056
+ else delete next.picture;
1057
+ if (next.name === live.name && next.picture === live.picture) return;
1058
+ const left = live.exp - Math.floor(Date.now() / 1e3);
1059
+ if (left <= 0) return;
1060
+ c.header(
1061
+ "Set-Cookie",
1062
+ cookieHeader(config.cookieName, await signSession(next, config.cookieSecret), { maxAge: left, secure: isSecure(c) }),
1063
+ { append: true }
1064
+ );
1065
+ }
1066
+ return { app, attach, require: require2, client, config, getAccessToken, refreshSessionProfile };
1050
1067
  }
1051
1068
  async function parseTransaction(raw, secret) {
1052
1069
  if (raw === void 0) return { ok: false, reason: "absent" };
@@ -1107,14 +1124,18 @@ function accountRoutes(sso) {
1107
1124
  }
1108
1125
  await next();
1109
1126
  });
1110
- app.get("/profile", async (c) => c.json(await client.getProfile(await sso.getAccessToken(c))));
1127
+ const answer = async (c, profile) => {
1128
+ if (sso.refreshSessionProfile) await sso.refreshSessionProfile(c, profile);
1129
+ return c.json(profile);
1130
+ };
1131
+ app.get("/profile", async (c) => answer(c, await client.getProfile(await sso.getAccessToken(c))));
1111
1132
  app.post("/profile", async (c) => {
1112
1133
  const token = await sso.getAccessToken(c);
1113
1134
  const body = await c.req.json().catch(() => null);
1114
1135
  if (typeof body?.name !== "string") {
1115
1136
  return c.json({ error: "invalid_request", error_description: '"name" must be a string' }, 400);
1116
1137
  }
1117
- return c.json(await client.updateProfile(token, body.name));
1138
+ return answer(c, await client.updateProfile(token, body.name));
1118
1139
  });
1119
1140
  app.post("/profile/avatar", async (c) => {
1120
1141
  const token = await sso.getAccessToken(c);
@@ -1128,9 +1149,9 @@ function accountRoutes(sso) {
1128
1149
  }
1129
1150
  const bytes = await readCapped(c, MAX_AVATAR_BYTES);
1130
1151
  if (!bytes) return c.json({ error: "too_large", max_bytes: MAX_AVATAR_BYTES }, 413);
1131
- return c.json(await client.uploadAvatar(token, bytes, type));
1152
+ return answer(c, await client.uploadAvatar(token, bytes, type));
1132
1153
  });
1133
- app.post("/profile/avatar/remove", async (c) => c.json(await client.removeAvatar(await sso.getAccessToken(c))));
1154
+ app.post("/profile/avatar/remove", async (c) => answer(c, await client.removeAvatar(await sso.getAccessToken(c))));
1134
1155
  app.onError((err, c) => {
1135
1156
  c.header("Cache-Control", "no-store");
1136
1157
  const e = err;