@ofidj/generator-fidj 3.9.2 → 3.10.1

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.
@@ -15,7 +15,7 @@
15
15
  "privacy:rehearse": "npm run build && node --test test/server.test.mjs"
16
16
  },
17
17
  "dependencies": {
18
- "@ofidj/node": "^3.9.2"
18
+ "@ofidj/node": "^3.10.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^22.0.0",
@@ -308,6 +308,44 @@ function offerTheWindowBack(providerWindow: ProviderWindow) {
308
308
  waiting.insertAdjacentElement("afterend", cancel);
309
309
  }
310
310
 
311
+ // Being recognised by Fidj is not the same as having signed in here. An app's
312
+ // door writes the session the provider holds, and Fidj's own entry used to read
313
+ // none of it — so somebody who had just joined an app was asked for a password
314
+ // by the account provider itself, one screen later.
315
+ //
316
+ // `prompt=none` asks the provider to answer without showing anything: a code
317
+ // when it knows this browser, `login_required` when it does not. Those are
318
+ // exactly the two cases to tell apart, and neither costs a screen.
319
+ //
320
+ // Only where Fidj is the app. An app with a door of its own must keep it: taking
321
+ // that door is where the app's own agreement is asked and recorded, and signing
322
+ // somebody in around it would skip a decision they never made.
323
+ const RECOGNITION_ASKED = "fidj.oidc.recognition-asked";
324
+ // A page that is public is exactly where a session is not what somebody came
325
+ // for. Asking there sent an anonymous visitor to the provider and back, and what
326
+ // they had asked to read never rendered.
327
+ const PUBLIC_ROUTE = "pub";
328
+ async function askWhetherFidjKnowsThisBrowser() {
329
+ if (!oidc || !isFidjItself || sdk.isLoggedIn() || oidc.signedOutHere())
330
+ return false;
331
+ if ((moduleRoute() || "").split("/")[0] === PUBLIC_ROUTE) return false;
332
+ // Once per document: the answer comes back as a redirect to this same page,
333
+ // so without this a refusal would ask again, and again.
334
+ try {
335
+ if (sessionStorage.getItem(RECOGNITION_ASKED) === "true") return false;
336
+ sessionStorage.setItem(RECOGNITION_ASKED, "true");
337
+ } catch {
338
+ return false;
339
+ }
340
+ try {
341
+ window.location.assign(await oidc.beginLogin({ silent: true }));
342
+ return true;
343
+ } catch {
344
+ // No provider, or it would not say. The entry is still there to be used.
345
+ return false;
346
+ }
347
+ }
348
+
311
349
  function signInThroughProvider(
312
350
  trigger: HTMLElement | null,
313
351
  options: { silent?: boolean; prompt?: string } = {},
@@ -651,6 +689,29 @@ function render() {
651
689
  } catch (error) {
652
690
  throw new Error(signInErrorMessage(error));
653
691
  }
692
+ // Fidj collects the credential on its own page rather than sending itself
693
+ // through its own door, and that sign-in created no session the provider
694
+ // could see: the first app opened afterwards asked for the password again,
695
+ // by the account provider itself. This turns the sign-in the API has just
696
+ // verified into the session every other app is recognised by. Only Fidj
697
+ // asks — the API refuses any other app, because a session that speaks for
698
+ // every app must not be mintable by one that collects its own passwords.
699
+ if (isFidjItself) {
700
+ try {
701
+ await sdk.sendOnEndpoint({
702
+ verb: "POST",
703
+ key: "me",
704
+ relativePath: "oidc/session",
705
+ // The whole point of the call is the cookie it comes back with, and
706
+ // a cross-origin response's Set-Cookie is dropped without this.
707
+ withCredentials: true,
708
+ });
709
+ } catch {
710
+ // Being signed in here still worked. The person is inside Fidj; what
711
+ // they lose is being recognised by the next app without typing again,
712
+ // and that is not worth refusing them the console over.
713
+ }
714
+ }
654
715
  await refresh();
655
716
  anonymous = false;
656
717
  // A new account belongs where a returning one lands: inside the app.
@@ -1077,7 +1138,27 @@ function boot() {
1077
1138
  if (oidc && new URL(window.location.href).searchParams.has("state")) {
1078
1139
  const callback = new URL(window.location.href);
1079
1140
  window.history.replaceState(null, "", window.location.pathname + "#/content");
1080
- await oidc.completeLogin(callback);
1141
+ try {
1142
+ await oidc.completeLogin(callback);
1143
+ } catch (refusal) {
1144
+ const why = (refusal as {code?: string; silentRefusal?: boolean}) || {};
1145
+ // A silent question answered "no" is an answer, not a failure. But the
1146
+ // two ways of saying no mean opposite things here.
1147
+ //
1148
+ // `login_required` — the provider does not know this browser, and the
1149
+ // entry is the right next screen.
1150
+ //
1151
+ // `consent_required` — it knows exactly who this is; what it has not got
1152
+ // is this client's permission, because the account was made through
1153
+ // another app and Fidj's console is a client of its own. Asking again
1154
+ // without `prompt=none` gets the screen that collects it, and no
1155
+ // password: the person already typed theirs once.
1156
+ if (why.code === "consent_required") {
1157
+ window.location.assign(await oidc.beginLogin());
1158
+ return;
1159
+ }
1160
+ if (!why.silentRefusal) throw refusal;
1161
+ }
1081
1162
  try {
1082
1163
  sessionStorage.removeItem("fidj.interaction.email");
1083
1164
  } catch {}
@@ -1090,6 +1171,9 @@ function boot() {
1090
1171
  await refresh();
1091
1172
  if (!moduleRoute() && !accountRoutes.includes(currentRoute()))
1092
1173
  navigate("content");
1174
+ return;
1093
1175
  }
1176
+ // Nothing held here. Fidj may still know this browser from an app.
1177
+ await askWhetherFidjKnowsThisBrowser();
1094
1178
  });
1095
1179
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ofidj/generator-fidj",
3
- "version": "3.9.2",
3
+ "version": "3.10.1",
4
4
  "description": "Generate a TypeScript app with Fidj sign-in, live server-side roles and per-app privacy.",
5
5
  "homepage": "https://fidj.ovh",
6
6
  "bugs": {