@ofidj/generator-fidj 3.16.0 → 3.18.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.
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"privacy:rehearse": "npm run build && node --test test/server.test.mjs"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@ofidj/entry": "^3.
|
|
19
|
-
"@ofidj/node": "^3.
|
|
18
|
+
"@ofidj/entry": "^3.18.0",
|
|
19
|
+
"@ofidj/node": "^3.18.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "^22.0.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {signInErrorMessage, agreementRequired, agreementFromRefusal, verificationPending, pollVerification, rememberSignIn, forgetSignIn, signInHint, type SigninShape} from "@ofidj/entry";
|
|
2
|
-
import {agreementScreen, bindAgreementScreen, bindPasswordReveal, acceptedAgreement, verificationWait, providerEntry, showEmailEntry, showVersionBadge, escape, masthead, highlightCells, badgeStrip, credentialFields, accountForm, passkeySupported, passkeyAssertion, walletDoor, oidcInteractionMarkup, oidcInteractionStyles, bindOidcInteraction} from "@ofidj/entry/dom";
|
|
1
|
+
import {signInErrorMessage, formatDate, agreementRequired, agreementFromRefusal, verificationPending, pollVerification, rememberSignIn, forgetSignIn, signInHint, type SigninShape} from "@ofidj/entry";
|
|
2
|
+
import {agreementScreen, bindAgreementScreen, bindPasswordReveal, acceptedAgreement, verificationWait, providerEntry, showEmailEntry, showVersionBadge, escape, masthead, highlightCells, badgeStrip, credentialFields, accountForm, passkeySupported, passkeyAssertion, walletDoor, memberCard, oidcInteractionMarkup, oidcInteractionStyles, bindOidcInteraction} from "@ofidj/entry/dom";
|
|
3
3
|
import {openProviderWindow, relayProviderAnswer, type ProviderWindow} from "@ofidj/entry/window";
|
|
4
4
|
import { FidjNodeService, FidjOidcClient } from "@ofidj/node";
|
|
5
5
|
import config from "../app.config.json";
|
|
@@ -330,7 +330,10 @@ const PUBLIC_ROUTE = "pub";
|
|
|
330
330
|
// spinner's worth of ceremony, and it says what it is doing.
|
|
331
331
|
let recognising = false;
|
|
332
332
|
function mightBeRecognised() {
|
|
333
|
-
|
|
333
|
+
// Fidj itself, or an app this browser signed in to before: its session lives
|
|
334
|
+
// in the tab, so a new tab would otherwise draw "Continue as …" for somebody
|
|
335
|
+
// Fidj still recognises.
|
|
336
|
+
if (!oidc || !(isFidjItself || signInHint(config.appId))) return false;
|
|
334
337
|
if ((moduleRoute() || "").split("/")[0] === PUBLIC_ROUTE) return false;
|
|
335
338
|
// Not on a screen somebody was sent to by a link.
|
|
336
339
|
//
|
|
@@ -354,7 +357,12 @@ function mightBeRecognised() {
|
|
|
354
357
|
}
|
|
355
358
|
|
|
356
359
|
async function askWhetherFidjKnowsThisBrowser() {
|
|
357
|
-
if (
|
|
360
|
+
if (
|
|
361
|
+
!oidc ||
|
|
362
|
+
!(isFidjItself || signInHint(config.appId)) ||
|
|
363
|
+
sdk.isLoggedIn() ||
|
|
364
|
+
oidc.signedOutHere()
|
|
365
|
+
)
|
|
358
366
|
return false;
|
|
359
367
|
if ((moduleRoute() || "").split("/")[0] === PUBLIC_ROUTE) return false;
|
|
360
368
|
// Not on a screen somebody was sent to by a link.
|
|
@@ -404,16 +412,16 @@ function signInThroughProvider(
|
|
|
404
412
|
void action(async () => {
|
|
405
413
|
let url: string;
|
|
406
414
|
try {
|
|
407
|
-
//
|
|
408
|
-
//
|
|
409
|
-
//
|
|
410
|
-
//
|
|
411
|
-
//
|
|
412
|
-
//
|
|
415
|
+
// Somebody who has just signed out of this app did not ask to be walked
|
|
416
|
+
// straight back in: the provider still knows the browser and would answer
|
|
417
|
+
// with a code and no screen. So the door asks which account — the consent
|
|
418
|
+
// screen names the one Fidj recognises and offers another. It does not ask
|
|
419
|
+
// for a password: prompt=login told Fidj "this is not me" and ended the
|
|
420
|
+
// Fidj session along with the app's.
|
|
413
421
|
url = await oidc.beginLogin(
|
|
414
422
|
options.prompt || options.silent || !oidc.signedOutHere()
|
|
415
423
|
? options
|
|
416
|
-
: {...options, prompt: "
|
|
424
|
+
: {...options, prompt: "consent"},
|
|
417
425
|
);
|
|
418
426
|
} catch (error) {
|
|
419
427
|
providerWindow?.giveUp();
|
|
@@ -603,7 +611,7 @@ function render() {
|
|
|
603
611
|
// The two ways out, last and together: back to the app, or out of this
|
|
604
612
|
// session. Leaving the app itself is a different decision and stays where
|
|
605
613
|
// the things it erases are listed.
|
|
606
|
-
root.innerHTML = `<section class="content-account">${profileSummary()}<div class="card profile-body">${banner()}${accountForm("account", {linkToken, verificationConfirmed, emailVerified, accountEmail}, {compact: true})}${privacyBlock()}</div></section>`;
|
|
614
|
+
root.innerHTML = `<section class="content-account">${profileSummary()}<div class="card profile-body">${banner()}${emailVerified ? accountRows() : accountForm("account", {linkToken, verificationConfirmed, emailVerified, accountEmail}, {compact: true})}${privacyBlock()}</div></section>`;
|
|
607
615
|
renderNav("account");
|
|
608
616
|
wireAccount("account");
|
|
609
617
|
wireSignOut();
|
|
@@ -909,13 +917,15 @@ function wirePrivacy() {
|
|
|
909
917
|
navigate("signin");
|
|
910
918
|
}),
|
|
911
919
|
);
|
|
912
|
-
element("terms")?.addEventListener(
|
|
920
|
+
element("accept-terms")?.addEventListener(
|
|
913
921
|
"click",
|
|
914
922
|
() =>
|
|
915
923
|
void action(async () => {
|
|
924
|
+
// The version published now is the version recorded.
|
|
925
|
+
const agreement = await readAgreement();
|
|
916
926
|
await request(appPath + "/consents", "PUT", {
|
|
917
927
|
terms: true,
|
|
918
|
-
cguVersion:
|
|
928
|
+
...(agreement ? {cguVersion: agreement.version} : {}),
|
|
919
929
|
source: "profile",
|
|
920
930
|
});
|
|
921
931
|
await refresh();
|
|
@@ -984,27 +994,22 @@ function wirePrivacy() {
|
|
|
984
994
|
// about it. It used to be a screen of its own called My privacy, one tab
|
|
985
995
|
// away from the account it was about — so a person looking for their own
|
|
986
996
|
// data had two places to try and no way to tell which.
|
|
997
|
+
// The account, once there is nothing left to verify: one line, and the way to
|
|
998
|
+
// change it, which is on Fidj — the account is Fidj's, not this app's.
|
|
999
|
+
function accountRows() {
|
|
1000
|
+
return `<div class="member-rows"><div class="member-row"><div><strong>Email</strong><small>${escape(accountEmail)} · verified</small></div><a href="${escape(config.dashboardUrl)}/#/my/profile" target="_blank" rel="noopener noreferrer">Manage on Fidj ↗</a></div></div>`;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
987
1003
|
function privacyBlock() {
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
.map(
|
|
998
|
-
(entry) =>
|
|
999
|
-
`<p>${escape(entry.type)} · ${entry.granted ? "Accepted" : "Withdrawn"} · ${escape(entry.changedAt)}</p>`,
|
|
1000
|
-
)
|
|
1001
|
-
.join("")
|
|
1002
|
-
: "<p>No changes yet.</p>"
|
|
1003
|
-
}
|
|
1004
|
-
<button id="export">Export</button>
|
|
1005
|
-
<p>This app stores its session in this browser. The export covers Fidj-held records for this membership. There is no separate app database in this static template.</p>
|
|
1006
|
-
${roles.includes("Owner") ? "<p>Resolve app ownership before leaving.</p>" : leaving ? '<p>Confirm departure: your membership and its Fidj-held data will be removed. Your other apps remain available.</p><button id="confirm-leave" class="danger">Leave & erase</button><button id="cancel-leave">Keep my membership</button>' : '<button id="leave" class="danger">Leave & erase</button>'}
|
|
1007
|
-
<p class="leaving"><a href="${escape(config.dashboardUrl)}/#/my" target="_blank" rel="noopener">Open Fidj to manage every app you use ↗</a><br><small>Fidj is the account provider behind ${escape(config.title)}. This opens it in a new tab; you stay signed in here.</small></p>`;
|
|
1004
|
+
const version = String((consent as Record<string, unknown>).termsVersion || "");
|
|
1005
|
+
return memberCard({
|
|
1006
|
+
consent,
|
|
1007
|
+
history,
|
|
1008
|
+
agreementHref: `${config.apiEndpoint}/apps/${encodeURIComponent(config.appId)}/agreements/${encodeURIComponent(version)}`,
|
|
1009
|
+
owner: roles.includes("Owner"),
|
|
1010
|
+
leaving,
|
|
1011
|
+
manageHref: `${config.dashboardUrl}/#/my/gdpr`,
|
|
1012
|
+
});
|
|
1008
1013
|
}
|
|
1009
1014
|
|
|
1010
1015
|
// ------------------------------------------------ signing in, for the provider
|
|
@@ -1031,6 +1036,10 @@ type Interaction = {
|
|
|
1031
1036
|
agreement?: {version: string; text: string} | null;
|
|
1032
1037
|
// Who the consent screen recognises, as the provider names them.
|
|
1033
1038
|
recognisedEmail?: string;
|
|
1039
|
+
// The passkey door's challenge, on the login step.
|
|
1040
|
+
passkey?: {ticket: string; options: unknown};
|
|
1041
|
+
// The recognised person's agreement is on file at the version in force.
|
|
1042
|
+
agreementAccepted?: boolean;
|
|
1034
1043
|
action: string;
|
|
1035
1044
|
};
|
|
1036
1045
|
let interactionId = "";
|
|
@@ -1155,6 +1164,8 @@ function interactionScreen() {
|
|
|
1155
1164
|
// The agreement is a document read in the browser, like the privacy notice.
|
|
1156
1165
|
agreementHref: details.termsUri || undefined,
|
|
1157
1166
|
recognisedEmail: details.recognisedEmail || undefined,
|
|
1167
|
+
passkey: details.passkey,
|
|
1168
|
+
agreementAccepted: details.agreementAccepted,
|
|
1158
1169
|
logoSrc: "./fidj-logo.png",
|
|
1159
1170
|
})}`;
|
|
1160
1171
|
bindOidcInteraction(root);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {agreementRequired, agreementFromRefusal, signInErrorMessage, rememberSignIn, forgetSignIn, type SigninShape} from "@ofidj/entry";
|
|
2
|
-
import {acceptedAgreement, agreementScreen, bindAgreementScreen, bindPasswordReveal, providerEntry, showEmailEntry, showVersionBadge} from "@ofidj/entry/dom";
|
|
1
|
+
import {agreementRequired, agreementFromRefusal, signInErrorMessage, rememberSignIn, forgetSignIn, formatDate, type SigninShape} from "@ofidj/entry";
|
|
2
|
+
import {acceptedAgreement, agreementScreen, memberCard, bindAgreementScreen, bindPasswordReveal, providerEntry, showEmailEntry, showVersionBadge} from "@ofidj/entry/dom";
|
|
3
3
|
import {openProviderWindow, relayProviderAnswer, type ProviderWindow} from "@ofidj/entry/window";
|
|
4
4
|
import { FidjNodeService, FidjOidcClient } from "@ofidj/node";
|
|
5
5
|
import "@ofidj/entry/style.css";
|
|
@@ -223,19 +223,19 @@ function render() {
|
|
|
223
223
|
<nav><button id="workspace-tab" class="${view === "workspace" ? "selected" : ""}">My notes</button><button id="privacy-tab" class="${view === "privacy" ? "selected" : ""}">My privacy</button><button id="refresh">Refresh access</button></nav>
|
|
224
224
|
${
|
|
225
225
|
view === "workspace"
|
|
226
|
-
? `<section class="workspace"><div><div class="section-heading"><h2>Your notes</h2><span>${notes.length} saved</span></div>${notes.length ? notes.map((note) => `<article class="card note"><small>${escape(
|
|
227
|
-
: `<section class="
|
|
228
|
-
privacy?.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
226
|
+
? `<section class="workspace"><div><div class="section-heading"><h2>Your notes</h2><span>${notes.length} saved</span></div>${notes.length ? notes.map((note) => `<article class="card note"><small>${escape(formatDate(note.createdAt, "datetime"))}</small><h3>${escape(note.title)}</h3><p>${escape(note.body)}</p></article>`).join("") : `<article class="card empty"><span>✳</span><h3>Room for your next idea.</h3><p>Your saved notes will appear here. Only you can read your notes.</p></article>`}</div><form id="note" class="card"><p class="eyebrow">CAPTURE SOMETHING</p><h2>A fresh note</h2><p>${canWrite ? "You have permission to save notes." : "Ask your app owner for Editor access to save notes."}</p><label for="note-title">Title</label><input id="note-title" maxlength="120" required ${!canWrite ? "disabled" : ""}><label for="note-body">Your note</label><textarea id="note-body" rows="6" maxlength="5000" ${!canWrite ? "disabled" : ""}></textarea><button class="primary" type="submit" ${!canWrite ? "disabled" : ""}>Save note</button><small>Your notes are saved by this app and survive a server restart.</small></form></section>`
|
|
227
|
+
: `<section class="card privacy-card">${memberCard({
|
|
228
|
+
consent: privacy?.consent || {},
|
|
229
|
+
history: privacy?.history || [],
|
|
230
|
+
agreementHref: privacy?.consent.termsVersion
|
|
231
|
+
? `${settings.apiEndpoint}/apps/${encodeURIComponent(settings.appId)}/agreements/${encodeURIComponent(String(privacy.consent.termsVersion))}`
|
|
232
|
+
: undefined,
|
|
233
|
+
owner: session.roles.includes("Owner"),
|
|
234
|
+
leaving,
|
|
235
|
+
leaveScope:
|
|
236
|
+
"Your membership, your choices and your notes in this app will be erased. Your Fidj account and other apps remain available.",
|
|
237
|
+
manageHref: `${settings.dashboardUrl}/#/my/gdpr`,
|
|
238
|
+
})}</section>`
|
|
239
239
|
}`
|
|
240
240
|
}
|
|
241
241
|
<footer>Built with Fidj · One identity. Separate choices for every app.</footer></main>`;
|
|
@@ -348,7 +348,7 @@ function render() {
|
|
|
348
348
|
void action(async () => {
|
|
349
349
|
await api("privacy", "PUT", { terms: true });
|
|
350
350
|
await load();
|
|
351
|
-
notice = "
|
|
351
|
+
notice = "Service agreement accepted.";
|
|
352
352
|
}),
|
|
353
353
|
);
|
|
354
354
|
el("export")?.addEventListener(
|