@ofidj/generator-fidj 1.0.8 → 1.0.9
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/README.md
CHANGED
|
@@ -128,7 +128,7 @@ Readiness confirms this handler only. Register its trusted URL and independent s
|
|
|
128
128
|
|
|
129
129
|
## Beta OIDC input
|
|
130
130
|
|
|
131
|
-
`--oidc-issuer` selects the OIDC entry for content
|
|
131
|
+
`--oidc-issuer` selects the OIDC entry for any generated app — a content site, a console module, or an app with its own backend — while preserving branding and anonymous-entry configuration. The entry becomes a single **Continue with Fidj** button, no password field, and the generated `.env` carries `FIDJ_OIDC_ISSUER` so the app's own server can serve it to its page. Register the app's exact callback first (`PUT /v3/apps/:appId/oidc`), or the provider refuses the authorization. This is beta: the API ships an integrated provider from 3.6.26, but it serves no `/oidc` routes until an operator configures an issuer and signing keys, so confirm the issuer you pass actually answers discovery before offering it.
|
|
132
132
|
|
|
133
133
|
A compatible issuer, REST API and registered callback without a fragment are prerequisites. Validate that integration and coordinated SDK publication before offering it to app builders. Notes retains its existing authentication flow; no subject migration is implied.
|
|
134
134
|
|
|
@@ -320,9 +320,16 @@ if (require.main === module) {
|
|
|
320
320
|
throw new Error("Invalid Fidj API URL.");
|
|
321
321
|
const port = Number(process.env.PORT || 8200);
|
|
322
322
|
const host = process.env.HOST || "127.0.0.1";
|
|
323
|
+
// The page needs the issuer to start a redirect sign-in, and it may only be
|
|
324
|
+
// this API's own provider: an issuer on another origin would send people to
|
|
325
|
+
// type this app's credentials somewhere unrelated.
|
|
326
|
+
const oidcIssuer = process.env.FIDJ_OIDC_ISSUER || "";
|
|
327
|
+
if (oidcIssuer && new URL(oidcIssuer).href !== new URL("/oidc", api).href)
|
|
328
|
+
throw new Error("FIDJ_OIDC_ISSUER must be the Fidj API's own /oidc endpoint.");
|
|
323
329
|
const settings = {
|
|
324
330
|
appId,
|
|
325
331
|
apiEndpoint,
|
|
332
|
+
oidcIssuer,
|
|
326
333
|
dashboardUrl: process.env.FIDJ_DASHBOARD_URL || "https://fidj.ovh",
|
|
327
334
|
title: process.env.APP_TITLE || "My workspace",
|
|
328
335
|
releaseVersion: process.env.APP_VERSION || "",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {agreementMarkup, bindAgreement, acceptedAgreement, signInErrorMessage} from "./service-agreement";
|
|
2
|
-
import { FidjNodeService } from "@ofidj/node";
|
|
2
|
+
import { FidjNodeService, FidjOidcClient } from "@ofidj/node";
|
|
3
3
|
import "./style.css";
|
|
4
4
|
import { showVersionBadge } from "./version";
|
|
5
5
|
|
|
@@ -12,9 +12,13 @@ type Settings = {
|
|
|
12
12
|
dashboardUrl: string;
|
|
13
13
|
localDemo: boolean;
|
|
14
14
|
releaseVersion: string;
|
|
15
|
+
oidcIssuer?: string;
|
|
15
16
|
};
|
|
16
17
|
const root = document.querySelector<HTMLDivElement>("#app")!;
|
|
17
18
|
const sdk = new FidjNodeService();
|
|
19
|
+
// When the app is configured with a provider it never sees a password: the
|
|
20
|
+
// entry hands the person to Fidj and gets a code back.
|
|
21
|
+
let oidc: FidjOidcClient | null = null;
|
|
18
22
|
let settings: Settings;
|
|
19
23
|
let session: Session | null = null;
|
|
20
24
|
let notes: Note[] = [];
|
|
@@ -90,7 +94,7 @@ function render() {
|
|
|
90
94
|
<main>${notice ? `<p class="notice" role="status">${escape(notice)}</p>` : ""}${error ? `<p class="error" role="alert">${escape(error)}</p>` : ""}
|
|
91
95
|
${
|
|
92
96
|
!session
|
|
93
|
-
? `<section class="welcome"><div><p class="eyebrow">A LITTLE SPACE FOR YOUR IDEAS</p><h1>Good ideas<br>start here.</h1><p>Keep your notes together, with access you understand and privacy you control.</p><div class="promise"><img src="/fidj-logo.png" alt=""><span>Your account connects through Fidj.<br>Your choices belong to this app.</span></div></div><form id="signin" class="card"><h2>Welcome to ${escape(settings.title)}</h2
|
|
97
|
+
? `<section class="welcome"><div><p class="eyebrow">A LITTLE SPACE FOR YOUR IDEAS</p><h1>Good ideas<br>start here.</h1><p>Keep your notes together, with access you understand and privacy you control.</p><div class="promise"><img src="/fidj-logo.png" alt=""><span>Your account connects through Fidj.<br>Your choices belong to this app.</span></div></div><form id="signin" class="card"><h2>Welcome to ${escape(settings.title)}</h2>${oidc ? `<p>Continue securely with your Fidj account. Your password stays with Fidj.</p>${agreementMarkup()}<button class="primary" type="submit">Continue with Fidj</button>` : `<p>Sign in with your Fidj account.</p><label for="email">Email</label><input id="email" type="email" value="${escape(signInEmail)}" autocomplete="username" required><label for="password">Password</label><input id="password" type="password" value="${escape(signInPassword)}" autocomplete="current-password" required>${agreementMarkup()}<button class="primary" type="submit">Continue</button>${settings.localDemo ? `<div class="demo"><strong>Try the local example</strong><p>Alex owns the app. Maya and Sam start with the Free role.</p><button type="button" data-demo="alex">Alex · owner</button><button type="button" data-demo="maya">Maya · member</button><button type="button" data-demo="sam">Sam · member</button></div>` : ""}`}</form></section>`
|
|
94
98
|
: `
|
|
95
99
|
<div class="page-heading"><div><p class="eyebrow">YOUR WORKSPACE</p><h1>A place to think.</h1><p>${escape(session.username)} <span class="roles">${session.roles.map(escape).join(" · ") || "No assigned roles"}</span></p></div><button id="signout">Sign out</button></div>
|
|
96
100
|
<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>
|
|
@@ -115,8 +119,8 @@ function render() {
|
|
|
115
119
|
void bindAgreement(el<HTMLFormElement>("signin"), settings.title, settings.apiEndpoint, settings.appId, signInAgreementAccepted);
|
|
116
120
|
el<HTMLFormElement>("signin")?.addEventListener("submit", (event) => {
|
|
117
121
|
event.preventDefault();
|
|
118
|
-
const email = el<HTMLInputElement>("email")
|
|
119
|
-
const password = el<HTMLInputElement>("password")
|
|
122
|
+
const email = el<HTMLInputElement>("email")?.value || "";
|
|
123
|
+
const password = el<HTMLInputElement>("password")?.value || "";
|
|
120
124
|
signInEmail = email;
|
|
121
125
|
signInPassword = password;
|
|
122
126
|
signInAgreementAccepted = el<HTMLInputElement>("service-agreement").checked;
|
|
@@ -127,6 +131,10 @@ function render() {
|
|
|
127
131
|
return;
|
|
128
132
|
}
|
|
129
133
|
void action(async () => {
|
|
134
|
+
if (oidc) {
|
|
135
|
+
window.location.assign(await oidc.beginLogin());
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
130
138
|
try {
|
|
131
139
|
await sdk.login(email, password, { autoSignup: false, ...acceptance });
|
|
132
140
|
} catch (reason) {
|
|
@@ -251,6 +259,21 @@ async function start() {
|
|
|
251
259
|
try {
|
|
252
260
|
settings = await (await fetch("/api/config")).json();
|
|
253
261
|
showVersionBadge(settings.releaseVersion, settings.title === "Fidj" ? settings.apiEndpoint : undefined);
|
|
262
|
+
if (settings.oidcIssuer)
|
|
263
|
+
oidc = new FidjOidcClient({
|
|
264
|
+
issuer: settings.oidcIssuer,
|
|
265
|
+
clientId: settings.appId,
|
|
266
|
+
redirectUri: window.location.origin + window.location.pathname,
|
|
267
|
+
apiEndpoint: settings.apiEndpoint,
|
|
268
|
+
storage: sessionStorage,
|
|
269
|
+
});
|
|
270
|
+
// Coming back from Fidj: swap the code for a session, and take the code out
|
|
271
|
+
// of the address bar before anything can reload it.
|
|
272
|
+
if (oidc && new URL(window.location.href).searchParams.has("state")) {
|
|
273
|
+
const callback = new URL(window.location.href);
|
|
274
|
+
window.history.replaceState(null, "", window.location.pathname);
|
|
275
|
+
await oidc.completeLogin(callback);
|
|
276
|
+
}
|
|
254
277
|
await sdk.init(settings.appId, {
|
|
255
278
|
apiEndpoint: settings.apiEndpoint,
|
|
256
279
|
prod: !settings.localDemo,
|
package/lib/scaffold.cjs
CHANGED
|
@@ -36,7 +36,6 @@ function scaffold(destination, options) {
|
|
|
36
36
|
if (options.oidcIssuer) {
|
|
37
37
|
const issuer = new URL(options.oidcIssuer);
|
|
38
38
|
if (issuer.origin !== api.origin || issuer.pathname !== "/oidc" || issuer.hash || issuer.search || issuer.username || issuer.password) throw new Error("OIDC issuer must share the API origin and use /oidc.");
|
|
39
|
-
if (options.content === undefined && !options.module) throw new Error("OIDC currently requires a content app or console module.");
|
|
40
39
|
}
|
|
41
40
|
const title = options.title || name;
|
|
42
41
|
if (/[\r\n]/.test(title)) throw new Error("Title must be one line.");
|
|
@@ -127,7 +126,10 @@ function scaffold(destination, options) {
|
|
|
127
126
|
);
|
|
128
127
|
fs.renameSync(path.join(dir, "gitignore"), path.join(dir, ".gitignore"));
|
|
129
128
|
const releaseVersion = new Date().toISOString().slice(2, 10).replaceAll("-", ".");
|
|
130
|
-
|
|
129
|
+
// The app's own server serves this to its page: the entry cannot redirect to
|
|
130
|
+
// a provider it was never told about.
|
|
131
|
+
const issuerSetting = options.oidcIssuer ? `FIDJ_OIDC_ISSUER=${options.oidcIssuer}\n` : "";
|
|
132
|
+
const settings = `FIDJ_APP_ID=${options.appId}\nFIDJ_API_ENDPOINT=${api.href.replace(/\/$/, "")}\n${issuerSetting}FIDJ_DASHBOARD_URL=https://fidj.ovh\nAPP_TITLE=${title}\nAPP_VERSION=${releaseVersion}\nPORT=8200\nHOST=127.0.0.1\nLOCAL_DEMO=false\nFIDJ_DATA_DIR=../.fidj-data/${name}\nFIDJ_PRIVACY_ADAPTER_KEY=\n`;
|
|
131
133
|
const localApi = options.local
|
|
132
134
|
? "http://localhost:3201/v3"
|
|
133
135
|
: api.href.replace(/\/$/, "");
|