@flowselections/core 1.0.28 → 1.0.30
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 +11 -0
- package/dist/auth/redirect.d.ts +1 -1
- package/dist/auth/redirect.d.ts.map +1 -1
- package/dist/auth/redirect.js +3 -2
- package/dist/components/layout/AuthenticatedLayout.js +1 -1
- package/dist/components/layout/Header.js +1 -1
- package/dist/components/layout/InstellingenPage.js +2 -2
- package/dist/components/layout/LoginPage.js +1 -1
- package/dist/components/layout/Sidebar.js +1 -1
- package/dist/permissions/useModulePermission.d.ts +2 -2
- package/dist/permissions/useModulePermission.d.ts.map +1 -1
- package/dist/permissions/useModulePermission.js +4 -3
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -0
- package/dist/server/shell-runtime.d.ts +24 -0
- package/dist/server/shell-runtime.d.ts.map +1 -0
- package/dist/server/shell-runtime.js +219 -0
- package/dist/styles.css +4 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -206,3 +206,14 @@ Ook: `resetPasswordRoute({ landing })` (`/reset-wachtwoord`), `landingRoute({ la
|
|
|
206
206
|
`prepublishOnly` draait `build:pkg`, dat `dist/` eerst leegmaakt, opnieuw bouwt en met
|
|
207
207
|
`scripts/check-pkg.mjs` controleert dat `dist/` precies het pakket bevat. `npm publish` publiceert dus nooit
|
|
208
208
|
een preview-build.
|
|
209
|
+
|
|
210
|
+
## Shell-adres en Central-koppeling (`./server`)
|
|
211
|
+
|
|
212
|
+
De door het dashboard gegenereerde globale request-middleware roept per verzoek aan:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
import { runShellRuntime } from "@flowselections/core/server";
|
|
216
|
+
await runShellRuntime(); // zet PUBLIC_BASE_URL uit de database, controleert de Central-koppeling hooguit eens per uur
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Vereist `SUPABASE_URL`, `SB_SERVICE_ROLE_KEY`, `CENTRAL_AUTH_URL`, `CENTRAL_AUTH_SECRET`. Gooit nooit; de leessleutel wordt nooit gelogd of teruggegeven.
|
package/dist/auth/redirect.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export interface ModuleAccessContext {
|
|
|
23
23
|
/**
|
|
24
24
|
* Sinds 1.0.23. Moduletoegang voor een pad. Wacht max. 3 s; fout, time-out,
|
|
25
25
|
* vrijgesteld pad of geen modulerij → `edit` (fail-open).
|
|
26
|
-
* Zichtbaarheid, geen beveiliging.
|
|
26
|
+
* Superadmin → altijd `edit`; admin volgt de rechten. Zichtbaarheid, geen beveiliging.
|
|
27
27
|
*/
|
|
28
28
|
export declare function resolveModuleAccess(path: string, opts?: {
|
|
29
29
|
userId?: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redirect.d.ts","sourceRoot":"","sources":["../../src/core/auth/redirect.ts"],"names":[],"mappings":"AAEA,OAAO,EAA6B,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAG5F;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAazE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/C;AAED,oEAAoE;AACpE,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,eAAe,CAAC;CAC/B;
|
|
1
|
+
{"version":3,"file":"redirect.d.ts","sourceRoot":"","sources":["../../src/core/auth/redirect.ts"],"names":[],"mappings":"AAEA,OAAO,EAA6B,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAG5F;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAazE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/C;AAED,oEAAoE;AACpE,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,eAAe,CAAC;CAC/B;AAWD;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GACnF,OAAO,CAAC,eAAe,CAAC,CAM1B;AAED,yFAAyF;AACzF,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAC/E,OAAO,CAAC,OAAO,CAAC,CAElB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CACzB,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,eAAe,EACtC,SAAS,SAAW,GACnB,IAAI,GAAG,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAuB3D"}
|
package/dist/auth/redirect.js
CHANGED
|
@@ -38,13 +38,14 @@ export function readRedirectParam(fallback) {
|
|
|
38
38
|
function pathOf(location) {
|
|
39
39
|
return location.pathname ?? location.href.split("?")[0].split("#")[0];
|
|
40
40
|
}
|
|
41
|
+
/** Alleen superadmin heeft een vrijbrief voor modulerechten; admin volgt de rechten. */
|
|
41
42
|
function isAdminRoles(roles) {
|
|
42
|
-
return Array.isArray(roles) &&
|
|
43
|
+
return Array.isArray(roles) && roles.includes("superadmin");
|
|
43
44
|
}
|
|
44
45
|
/**
|
|
45
46
|
* Sinds 1.0.23. Moduletoegang voor een pad. Wacht max. 3 s; fout, time-out,
|
|
46
47
|
* vrijgesteld pad of geen modulerij → `edit` (fail-open).
|
|
47
|
-
* Zichtbaarheid, geen beveiliging.
|
|
48
|
+
* Superadmin → altijd `edit`; admin volgt de rechten. Zichtbaarheid, geen beveiliging.
|
|
48
49
|
*/
|
|
49
50
|
export async function resolveModuleAccess(path, opts = {}) {
|
|
50
51
|
const key = navKey(path);
|
|
@@ -45,5 +45,5 @@ export function AuthenticatedLayout({ children, header = true, permissions = tru
|
|
|
45
45
|
visibleModules,
|
|
46
46
|
permissionsLoading: loading,
|
|
47
47
|
}), [sidebarProps.shellId, sidebarProps.modules, visibleModules, loading]);
|
|
48
|
-
return (_jsx(ShellContext.Provider, { value: shell, children: _jsxs("div", { className: "min-h-screen bg-background [--core-topbar-h:3.5rem] md:[--core-topbar-h:0px]", children: [_jsx(Sidebar, { ...sidebarProps, permissions: permissions, visibleModules: visibleModules, loading: loading }), _jsxs("div", { className: "flex min-h-dvh flex-col pt-
|
|
48
|
+
return (_jsx(ShellContext.Provider, { value: shell, children: _jsxs("div", { className: "min-h-screen bg-background [--core-topbar-h:3.5rem] md:[--core-topbar-h:0px]", children: [_jsx(Sidebar, { ...sidebarProps, permissions: permissions, visibleModules: visibleModules, loading: loading }), _jsxs("div", { className: "flex min-h-dvh flex-col pt-[var(--core-topbar-h)] md:ml-64 md:pt-0", children: [header && _jsx(Header, { title: title }), _jsx("main", { className: "flex-1", children: permissions && access === "none" ? _jsx(NoAccess, {}) : (children ?? _jsx(Outlet, {})) })] })] }) }));
|
|
49
49
|
}
|
|
@@ -7,5 +7,5 @@ import { BugsDialog } from "./BugsDialog.js";
|
|
|
7
7
|
import { NotificationBell } from "./NotificationBell.js";
|
|
8
8
|
export function Header({ title }) {
|
|
9
9
|
const [bugsDialogOpen, setBugsDialogOpen] = useState(false);
|
|
10
|
-
return (_jsxs(_Fragment, { children: [_jsxs("header", { className: "sticky top-[var(--core-topbar-h,0px)] md:top-0 z-30 flex h-
|
|
10
|
+
return (_jsxs(_Fragment, { children: [_jsxs("header", { className: "sticky top-[var(--core-topbar-h,0px)] md:top-0 z-30 flex h-[var(--app-header-height)] shrink-0 items-center justify-between border-b border-border bg-card px-6", children: [_jsx("h1", { className: "text-xl font-semibold text-foreground", children: title }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "relative hidden md:block", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoeken...", className: "w-64 pl-9" })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setBugsDialogOpen(true), children: [_jsx(Bug, { className: "mr-2 h-4 w-4" }), "Bugs melden"] }), _jsx(NotificationBell, {})] })] }), _jsx(BugsDialog, { open: bugsDialogOpen, onOpenChange: setBugsDialogOpen })] }));
|
|
11
11
|
}
|
|
@@ -76,9 +76,9 @@ const coreCards = [
|
|
|
76
76
|
// ─── Main export ──────────────────────────────────────────────────────────────
|
|
77
77
|
export function InstellingenPage({ modules }) {
|
|
78
78
|
// Sinds 1.0.23: modulekaarten erven het recht van hun module (of featureId).
|
|
79
|
-
// Core-kaarten altijd. Laden/fout/
|
|
79
|
+
// Core-kaarten altijd. Laden/fout/superadmin → tonen. Zichtbaarheid, geen beveiliging.
|
|
80
80
|
const snap = usePermissionSnapshot();
|
|
81
|
-
const filterOn = snap.status === "ready" && !snap.roles.includes("
|
|
81
|
+
const filterOn = snap.status === "ready" && !snap.roles.includes("superadmin");
|
|
82
82
|
const moduleCards = modules.flatMap((m) => m.settingsCards.filter((card) => !filterOn ||
|
|
83
83
|
resolveLevel(snap.rows, card.moduleId ?? moduleKey(m), card.featureId ?? null) !== "none"));
|
|
84
84
|
const allCards = [...coreCards, ...moduleCards].sort((a, b) => a.order - b.order);
|
|
@@ -78,5 +78,5 @@ export function LoginPage({ redirectTo = "/instellingen", routeHandlesRedirect =
|
|
|
78
78
|
if (showForgot) {
|
|
79
79
|
return (_jsx("div", { className: "min-h-screen flex items-center justify-center bg-background p-4", children: _jsxs(Card, { className: "w-full max-w-md", children: [_jsxs(CardHeader, { className: "text-center", children: [_jsx(BrandLogo, { src: "/brand/logo.svg", className: "mx-auto mb-4 max-h-16 max-w-52 object-contain", fallbackClassName: "mb-4 font-display text-primary" }), _jsx(CardTitle, { className: "font-display text-2xl", children: "Wachtwoord vergeten" }), _jsx(CardDescription, { children: "Vul je e-mailadres in om een reset-link te ontvangen" })] }), _jsxs("form", { onSubmit: handleForgotPassword, children: [_jsx(CardContent, { className: "space-y-4", children: _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "forgot-email", children: "E-mailadres" }), _jsx(Input, { id: "forgot-email", type: "email", placeholder: "naam@bedrijf.nl", value: forgotEmail, onChange: (e) => setForgotEmail(e.target.value), required: true, maxLength: 255 })] }) }), _jsxs(CardFooter, { className: "flex flex-col gap-4", children: [_jsx(Button, { type: "submit", className: "w-full", disabled: forgotLoading, children: forgotLoading ? "Versturen..." : "Reset-link versturen" }), _jsx("button", { type: "button", onClick: () => setShowForgot(false), className: "text-sm text-primary hover:underline", children: "Terug naar inloggen" })] })] })] }) }));
|
|
80
80
|
}
|
|
81
|
-
return (_jsx("div", { className: "min-h-screen flex items-center justify-center bg-background p-4", children: _jsxs(Card, { className: "w-full max-w-md", children: [_jsxs(CardHeader, { className: "text-center", children: [_jsx(BrandLogo, { src: "/brand/logo.svg", className: "mx-auto mb-4 max-h-16 max-w-52 object-contain", fallbackClassName: "mb-4 font-display text-primary" }), _jsx(CardTitle, { className: "font-
|
|
81
|
+
return (_jsx("div", { className: "min-h-screen flex items-center justify-center bg-background p-4", children: _jsxs(Card, { className: "w-full max-w-md", children: [_jsxs(CardHeader, { className: "text-center", children: [_jsx(BrandLogo, { src: "/brand/logo.svg", className: "mx-auto mb-4 max-h-16 max-w-52 object-contain", fallbackClassName: "mb-4 font-display text-primary" }), _jsx(CardTitle, { className: "font-sans text-2xl font-semibold", children: "Inloggen" }), _jsx(CardDescription, { children: "Voer je gegevens in om toegang te krijgen tot het systeem" })] }), _jsxs("form", { onSubmit: handleSubmit, children: [_jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "email", children: "E-mailadres" }), _jsx(Input, { id: "email", type: "email", placeholder: "naam@bedrijf.nl", value: email, onChange: (e) => setEmail(e.target.value), required: true, maxLength: 255 })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "password", children: "Wachtwoord" }), _jsx(Input, { id: "password", type: "password", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", value: password, onChange: (e) => setPassword(e.target.value), required: true, maxLength: 128 })] })] }), _jsxs(CardFooter, { className: "flex flex-col gap-4", children: [_jsx(Button, { type: "submit", className: "w-full", disabled: isLoading, children: isLoading ? "Bezig met inloggen..." : "Inloggen" }), _jsx("button", { type: "button", onClick: () => setShowForgot(true), className: "text-sm text-muted-foreground hover:text-primary hover:underline", children: "Wachtwoord vergeten?" })] })] })] }) }));
|
|
82
82
|
}
|
|
@@ -37,7 +37,7 @@ function SidebarContent({ auth, visibleModules, loading, logoSrc, showSettings,
|
|
|
37
37
|
onNavigate?.();
|
|
38
38
|
await auth.signOut();
|
|
39
39
|
};
|
|
40
|
-
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col font-sans", children: [_jsx("div", { className: "flex shrink-0 items-center justify-center
|
|
40
|
+
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col font-sans", children: [_jsx("div", { className: "flex h-24 shrink-0 items-center justify-center px-6 py-2", children: _jsx(BrandLogo, { src: logoSrc, className: "max-h-20 w-auto max-w-full object-contain", fallbackClassName: "flex h-full max-w-full items-center text-sidebar-foreground" }) }), _jsx("nav", { className: "min-h-0 flex-1 space-y-1 px-3 py-4 overflow-y-auto sidebar-scroll", "aria-busy": loading || undefined, children: loading ? (_jsx("div", { "data-testid": "sidebar-skeleton", className: "space-y-2", children: [0, 1, 2, 3].map((i) => (_jsx(Skeleton, { className: "h-9 w-full bg-sidebar-accent" }, i))) })) : (visibleModules.map((mod) => mod.nav.children?.length ? (_jsx(SidebarGroup, { item: mod.nav, pathname: pathname, onNavigate: onNavigate }, mod.id)) : (_jsx(SidebarLink, { item: mod.nav, pathname: pathname, onNavigate: onNavigate }, mod.id)))) }), _jsxs("div", { className: "shrink-0 border-t border-sidebar-border px-3 py-4", children: [showSettings && (_jsx(SidebarLink, { item: { label: "Instellingen", href: "/instellingen", icon: Settings }, pathname: pathname, onNavigate: onNavigate })), _jsxs("button", { onClick: handleSignOut, className: "mt-1 flex w-full items-center gap-3 rounded-lg px-3 py-2.5 font-sans text-sm font-medium text-sidebar-foreground transition-colors hover:bg-sidebar-accent/50 cursor-pointer", children: [_jsx(LogOut, { className: "h-5 w-5" }), "Uitloggen"] })] })] }));
|
|
41
41
|
}
|
|
42
42
|
// ─── Main Sidebar ─────────────────────────────────────────────────────────────
|
|
43
43
|
/**
|
|
@@ -194,7 +194,7 @@ export declare function useMyModulePermissions(): {
|
|
|
194
194
|
};
|
|
195
195
|
/**
|
|
196
196
|
* Rechtenniveau voor één module (optioneel één feature). Tijdens laden, bij
|
|
197
|
-
* een fout en voor
|
|
197
|
+
* een fout en voor superadmin: `edit`. Zichtbaarheid, geen beveiliging.
|
|
198
198
|
*/
|
|
199
199
|
export declare function useModulePermission(moduleId: string, featureId?: string | null): {
|
|
200
200
|
level: PermissionLevel;
|
|
@@ -221,7 +221,7 @@ export interface CanProps {
|
|
|
221
221
|
/** Sinds 1.0.23. Rendert children als `useCan` toestaat. Zichtbaarheid, geen beveiliging. */
|
|
222
222
|
export declare function Can({ module, feature, level, fallback, children }: CanProps): import("react").FunctionComponentElement<import("react").FragmentProps>;
|
|
223
223
|
/**
|
|
224
|
-
* ModuleListTransform die zijbalkitems met `none` weglaat. Laden/fout/
|
|
224
|
+
* ModuleListTransform die zijbalkitems met `none` weglaat. Laden/fout/superadmin →
|
|
225
225
|
* ongewijzigd. Zichtbaarheid, geen beveiliging.
|
|
226
226
|
*/
|
|
227
227
|
export declare function useModulePermissionTransform(): ModuleListTransform;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useModulePermission.d.ts","sourceRoot":"","sources":["../../src/core/permissions/useModulePermission.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACrB,MAAM,cAAc,CAAC;AAGtB,YAAY,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"useModulePermission.d.ts","sourceRoot":"","sources":["../../src/core/permissions/useModulePermission.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACrB,MAAM,cAAc,CAAC;AAGtB,YAAY,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAkBzE;;;;;;;GAOG;AACH,wBAAgB,sBAAsB;;;IAuBlC,oEAAoE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAApE,oEAAoE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAApE,oEAAoE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAApE,oEAAoE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAApE,oEAAoE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAApE,oEAAoE;;;;;;;;;;;;;;;;;;;;;;;;;;;EAIvE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;;;;;EAW9E;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,GAAE,eAAwB;;;;EAGlG;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,6FAA6F;AAC7F,wBAAgB,GAAG,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,KAAc,EAAE,QAAe,EAAE,QAAQ,EAAE,EAAE,QAAQ,2EAG3F;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,IAAI,mBAAmB,CASlE;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,OAAO,UAAO;;;EAQjF"}
|
|
@@ -9,7 +9,8 @@ function getFallbackClient() {
|
|
|
9
9
|
return fallbackClient;
|
|
10
10
|
}
|
|
11
11
|
function rolesAreAdmin(roles) {
|
|
12
|
-
|
|
12
|
+
// Alleen superadmin heeft een vrijbrief; admin volgt de rechten.
|
|
13
|
+
return roles.includes("superadmin");
|
|
13
14
|
}
|
|
14
15
|
/** Snapshot → bekend? Laden = onbekend; fout/time-out = bekend (leeg → allow). */
|
|
15
16
|
function isKnown(s) {
|
|
@@ -49,7 +50,7 @@ export function useMyModulePermissions() {
|
|
|
49
50
|
}
|
|
50
51
|
/**
|
|
51
52
|
* Rechtenniveau voor één module (optioneel één feature). Tijdens laden, bij
|
|
52
|
-
* een fout en voor
|
|
53
|
+
* een fout en voor superadmin: `edit`. Zichtbaarheid, geen beveiliging.
|
|
53
54
|
*/
|
|
54
55
|
export function useModulePermission(moduleId, featureId) {
|
|
55
56
|
const snap = usePermissionSnapshot();
|
|
@@ -76,7 +77,7 @@ export function Can({ module, feature, level = "view", fallback = null, children
|
|
|
76
77
|
return createElement(Fragment, null, allowed ? children : fallback);
|
|
77
78
|
}
|
|
78
79
|
/**
|
|
79
|
-
* ModuleListTransform die zijbalkitems met `none` weglaat. Laden/fout/
|
|
80
|
+
* ModuleListTransform die zijbalkitems met `none` weglaat. Laden/fout/superadmin →
|
|
80
81
|
* ongewijzigd. Zichtbaarheid, geen beveiliging.
|
|
81
82
|
*/
|
|
82
83
|
export function useModulePermissionTransform() {
|
package/dist/server/index.d.ts
CHANGED
|
@@ -8,4 +8,5 @@ export { getRoleFlags } from "../auth/roles.js";
|
|
|
8
8
|
export { handoverExchangeHandler, HANDOVER_MAX_BODY_BYTES, HANDOVER_MAX_CODE_LENGTH, } from "./handover-handler.js";
|
|
9
9
|
export type { HandoverExchangeHandlerOptions } from "./handover-handler.js";
|
|
10
10
|
export type { PermissionLevel } from "./permissions.server.js";
|
|
11
|
+
export { getShellBaseUrl, applyShellBaseUrl, ensureCentralLink, runShellRuntime, normalizeShellBaseUrl, } from "./shell-runtime.js";
|
|
11
12
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACpG,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAClF,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,qBAAqB,EACrB,2BAA2B,EAC3B,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,8BAA8B,EAAE,MAAM,uBAAuB,CAAC;AAC5E,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACpG,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAClF,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,qBAAqB,EACrB,2BAA2B,EAC3B,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,8BAA8B,EAAE,MAAM,uBAAuB,CAAC;AAC5E,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAI/D,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,qBAAqB,GACtB,MAAM,oBAAoB,CAAC"}
|
package/dist/server/index.js
CHANGED
|
@@ -6,3 +6,6 @@ export { CentralAuthConfigError, ProviderNotConnectedError, ProviderUpstreamErro
|
|
|
6
6
|
export { requireModulePermission, isSuperadminServer } from "./permissions.server.js";
|
|
7
7
|
export { getRoleFlags } from "../auth/roles.js";
|
|
8
8
|
export { handoverExchangeHandler, HANDOVER_MAX_BODY_BYTES, HANDOVER_MAX_CODE_LENGTH, } from "./handover-handler.js";
|
|
9
|
+
// Shell-adres en Central-koppeling (alleen server; aangeroepen door de
|
|
10
|
+
// door het dashboard gegenereerde globale request-middleware).
|
|
11
|
+
export { getShellBaseUrl, applyShellBaseUrl, ensureCentralLink, runShellRuntime, normalizeShellBaseUrl, } from "./shell-runtime.js";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Absolute https-origin zonder pad, of null. */
|
|
2
|
+
export declare function normalizeShellBaseUrl(raw: unknown): string | null;
|
|
3
|
+
/**
|
|
4
|
+
* Het shell-adres uit de klantdatabase (60 s cache per isolate). Bij een fout:
|
|
5
|
+
* de laatst bekende waarde plus waarschuwing. Valt nooit terug op het adres
|
|
6
|
+
* van het binnenkomende verzoek.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getShellBaseUrl(): Promise<string | null>;
|
|
9
|
+
/**
|
|
10
|
+
* Zet process.env.PUBLIC_BASE_URL uit de database. De database wint; een
|
|
11
|
+
* afwijkende env-waarde geeft een waarschuwing. Zonder databasewaarde blijft
|
|
12
|
+
* de env ongemoeid (host-afhankelijke code faalt zoals nu).
|
|
13
|
+
*/
|
|
14
|
+
export declare function applyShellBaseUrl(): Promise<string | null>;
|
|
15
|
+
/**
|
|
16
|
+
* Hooguit eens per uur per isolate. Netwerkfout of ontbrekende RPC: niets
|
|
17
|
+
* doen, waarschuwen, niet opnieuw tot het volgende uur.
|
|
18
|
+
*/
|
|
19
|
+
export declare function ensureCentralLink(): Promise<void>;
|
|
20
|
+
/** Voor de globale request-middleware van de shell. Gooit nooit. */
|
|
21
|
+
export declare function runShellRuntime(): Promise<void>;
|
|
22
|
+
/** Alleen voor tests. */
|
|
23
|
+
export declare function __resetShellRuntimeForTests(): void;
|
|
24
|
+
//# sourceMappingURL=shell-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-runtime.d.ts","sourceRoot":"","sources":["../../src/core/server/shell-runtime.ts"],"names":[],"mappings":"AAwDA,iDAAiD;AACjD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CASjE;AAmBD;;;;GAIG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAqB9D;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAYhE;AAyDD;;;GAGG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAevD;AAED,oEAAoE;AACpE,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAMrD;AAED,yBAAyB;AACzB,wBAAgB,2BAA2B,SAM1C"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// Server-only: shell-adres en Central-koppeling via de klantdatabase.
|
|
2
|
+
// Uitsluitend bereikbaar via het "./server"-subpad. Geen serverpakket-imports:
|
|
3
|
+
// alleen fetch en process.env, gelezen binnen de functies.
|
|
4
|
+
//
|
|
5
|
+
// - getShellBaseUrl(): module_runtime_config.shell_base_url, 60 s cache per isolate.
|
|
6
|
+
// - applyShellBaseUrl(): zet process.env.PUBLIC_BASE_URL per verzoek (database wint).
|
|
7
|
+
// - ensureCentralLink(): hooguit eens per uur per isolate; haalt zo nodig de
|
|
8
|
+
// (idempotente) leessleutel bij Central en geeft hem aan de database.
|
|
9
|
+
// - runShellRuntime(): beide, bedoeld voor de door het dashboard gegenereerde
|
|
10
|
+
// globale request-middleware.
|
|
11
|
+
//
|
|
12
|
+
// De leessleutel wordt nooit gelogd, nooit teruggegeven en nooit bewaard.
|
|
13
|
+
const BASE_URL_TTL_MS = 60000;
|
|
14
|
+
const LINK_INTERVAL_MS = 60 * 60000;
|
|
15
|
+
const TIMEOUT_MS = 5000;
|
|
16
|
+
const WARN_INTERVAL_MS = 5 * 60000;
|
|
17
|
+
let baseUrlCache = null;
|
|
18
|
+
let baseUrlInflight = null;
|
|
19
|
+
let lastLinkCheck = 0;
|
|
20
|
+
let linkInflight = null;
|
|
21
|
+
const lastWarn = new Map();
|
|
22
|
+
function warn(key, message, detail) {
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
if (now - (lastWarn.get(key) ?? 0) < WARN_INTERVAL_MS)
|
|
25
|
+
return;
|
|
26
|
+
lastWarn.set(key, now);
|
|
27
|
+
console.warn(`[shell-runtime] ${message}`, detail ?? {});
|
|
28
|
+
}
|
|
29
|
+
function dbConfig() {
|
|
30
|
+
const url = process.env.SUPABASE_URL;
|
|
31
|
+
const key = process.env.SB_SERVICE_ROLE_KEY;
|
|
32
|
+
if (!url || !key)
|
|
33
|
+
return null;
|
|
34
|
+
return { url: url.replace(/\/+$/, ""), key };
|
|
35
|
+
}
|
|
36
|
+
function dbHeaders(key) {
|
|
37
|
+
const h = { apikey: key, "Content-Type": "application/json", Accept: "application/json" };
|
|
38
|
+
// Oude JWT-sleutels als Bearer; nieuwe sb_secret_-sleutels zijn geen JWT.
|
|
39
|
+
if (key.split(".").length === 3)
|
|
40
|
+
h.Authorization = `Bearer ${key}`;
|
|
41
|
+
return h;
|
|
42
|
+
}
|
|
43
|
+
async function timedFetch(url, init) {
|
|
44
|
+
const ctrl = new AbortController();
|
|
45
|
+
const t = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
|
|
46
|
+
try {
|
|
47
|
+
return await fetch(url, { ...init, signal: ctrl.signal, redirect: "error" });
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
clearTimeout(t);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Absolute https-origin zonder pad, of null. */
|
|
54
|
+
export function normalizeShellBaseUrl(raw) {
|
|
55
|
+
if (typeof raw !== "string" || !raw.trim())
|
|
56
|
+
return null;
|
|
57
|
+
try {
|
|
58
|
+
const u = new URL(raw.trim());
|
|
59
|
+
if (u.protocol !== "https:" || u.username || u.password)
|
|
60
|
+
return null;
|
|
61
|
+
return u.origin;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function readShellBaseUrl() {
|
|
68
|
+
const cfg = dbConfig();
|
|
69
|
+
if (!cfg) {
|
|
70
|
+
warn("config", "SUPABASE_URL of SB_SERVICE_ROLE_KEY ontbreekt; shell-adres onbekend");
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const res = await timedFetch(`${cfg.url}/rest/v1/module_runtime_config?select=shell_base_url&limit=1`, { method: "GET", headers: dbHeaders(cfg.key) });
|
|
74
|
+
if (!res.ok)
|
|
75
|
+
throw new Error(`database ${res.status}`);
|
|
76
|
+
const rows = (await res.json());
|
|
77
|
+
const value = normalizeShellBaseUrl(Array.isArray(rows) ? rows[0]?.shell_base_url : null);
|
|
78
|
+
if (!value)
|
|
79
|
+
warn("empty", "module_runtime_config.shell_base_url ontbreekt of is geen https-adres");
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Het shell-adres uit de klantdatabase (60 s cache per isolate). Bij een fout:
|
|
84
|
+
* de laatst bekende waarde plus waarschuwing. Valt nooit terug op het adres
|
|
85
|
+
* van het binnenkomende verzoek.
|
|
86
|
+
*/
|
|
87
|
+
export async function getShellBaseUrl() {
|
|
88
|
+
const now = Date.now();
|
|
89
|
+
if (baseUrlCache && now - baseUrlCache.at < BASE_URL_TTL_MS)
|
|
90
|
+
return baseUrlCache.value;
|
|
91
|
+
if (baseUrlInflight)
|
|
92
|
+
return baseUrlInflight;
|
|
93
|
+
baseUrlInflight = (async () => {
|
|
94
|
+
try {
|
|
95
|
+
const value = await readShellBaseUrl();
|
|
96
|
+
baseUrlCache = { value, at: Date.now() };
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
warn("read", "shell-adres lezen mislukt; laatst bekende waarde gebruikt", {
|
|
101
|
+
error: e instanceof Error ? e.message : "onbekend",
|
|
102
|
+
});
|
|
103
|
+
const last = baseUrlCache?.value ?? null;
|
|
104
|
+
baseUrlCache = { value: last, at: Date.now() };
|
|
105
|
+
return last;
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
baseUrlInflight = null;
|
|
109
|
+
}
|
|
110
|
+
})();
|
|
111
|
+
return baseUrlInflight;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Zet process.env.PUBLIC_BASE_URL uit de database. De database wint; een
|
|
115
|
+
* afwijkende env-waarde geeft een waarschuwing. Zonder databasewaarde blijft
|
|
116
|
+
* de env ongemoeid (host-afhankelijke code faalt zoals nu).
|
|
117
|
+
*/
|
|
118
|
+
export async function applyShellBaseUrl() {
|
|
119
|
+
const value = await getShellBaseUrl();
|
|
120
|
+
if (!value)
|
|
121
|
+
return null;
|
|
122
|
+
const current = process.env.PUBLIC_BASE_URL;
|
|
123
|
+
if (current && normalizeShellBaseUrl(current) !== value) {
|
|
124
|
+
warn("mismatch", "PUBLIC_BASE_URL wijkt af van de database; databasewaarde gebruikt", {
|
|
125
|
+
env: current,
|
|
126
|
+
database: value,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
process.env.PUBLIC_BASE_URL = value;
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
async function rpc(cfg, name, body) {
|
|
133
|
+
return timedFetch(`${cfg.url}/rest/v1/rpc/${name}`, {
|
|
134
|
+
method: "POST",
|
|
135
|
+
headers: dbHeaders(cfg.key),
|
|
136
|
+
body: JSON.stringify(body),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
async function linkOnce() {
|
|
140
|
+
const cfg = dbConfig();
|
|
141
|
+
const centralUrl = process.env.CENTRAL_AUTH_URL?.replace(/\/+$/, "");
|
|
142
|
+
const secret = process.env.CENTRAL_AUTH_SECRET;
|
|
143
|
+
if (!cfg || !centralUrl || !secret) {
|
|
144
|
+
warn("link-config", "configuratie voor de Central-koppeling ontbreekt");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const statusRes = await rpc(cfg, "flowselections_central_status", {});
|
|
148
|
+
if (!statusRes.ok) {
|
|
149
|
+
warn("link-status", "flowselections_central_status niet beschikbaar", { status: statusRes.status });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const raw = await statusRes.json();
|
|
153
|
+
const status = (Array.isArray(raw) ? raw[0] : raw);
|
|
154
|
+
if (!status) {
|
|
155
|
+
warn("link-status", "flowselections_central_status gaf geen resultaat");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (status.token_aanwezig !== false && status.laatste_status !== "http_401")
|
|
159
|
+
return;
|
|
160
|
+
const tokenRes = await timedFetch(`${centralUrl}/api/shell-host/read-token`, {
|
|
161
|
+
method: "POST",
|
|
162
|
+
headers: { Authorization: `Bearer ${secret}`, Accept: "application/json" },
|
|
163
|
+
});
|
|
164
|
+
if (!tokenRes.ok) {
|
|
165
|
+
warn("link-token", "leessleutel ophalen bij Central mislukt", { status: tokenRes.status });
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const { token } = (await tokenRes.json());
|
|
169
|
+
if (typeof token !== "string" || !token) {
|
|
170
|
+
warn("link-token", "Central gaf geen bruikbare leessleutel");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const setRes = await rpc(cfg, "flowselections_set_central_link", {
|
|
174
|
+
p_central_url: centralUrl,
|
|
175
|
+
p_read_token: token,
|
|
176
|
+
});
|
|
177
|
+
if (!setRes.ok) {
|
|
178
|
+
warn("link-set", "flowselections_set_central_link mislukt", { status: setRes.status });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Hooguit eens per uur per isolate. Netwerkfout of ontbrekende RPC: niets
|
|
183
|
+
* doen, waarschuwen, niet opnieuw tot het volgende uur.
|
|
184
|
+
*/
|
|
185
|
+
export async function ensureCentralLink() {
|
|
186
|
+
const now = Date.now();
|
|
187
|
+
if (linkInflight)
|
|
188
|
+
return linkInflight;
|
|
189
|
+
if (lastLinkCheck && now - lastLinkCheck < LINK_INTERVAL_MS)
|
|
190
|
+
return;
|
|
191
|
+
lastLinkCheck = now;
|
|
192
|
+
linkInflight = linkOnce()
|
|
193
|
+
.catch((e) => {
|
|
194
|
+
warn("link-error", "Central-koppeling controleren mislukt", {
|
|
195
|
+
error: e instanceof Error ? e.name : "onbekend",
|
|
196
|
+
});
|
|
197
|
+
})
|
|
198
|
+
.finally(() => {
|
|
199
|
+
linkInflight = null;
|
|
200
|
+
});
|
|
201
|
+
return linkInflight;
|
|
202
|
+
}
|
|
203
|
+
/** Voor de globale request-middleware van de shell. Gooit nooit. */
|
|
204
|
+
export async function runShellRuntime() {
|
|
205
|
+
try {
|
|
206
|
+
await Promise.all([applyShellBaseUrl(), ensureCentralLink()]);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
// helpers vangen zelf; nooit een verzoek laten falen
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/** Alleen voor tests. */
|
|
213
|
+
export function __resetShellRuntimeForTests() {
|
|
214
|
+
baseUrlCache = null;
|
|
215
|
+
baseUrlInflight = null;
|
|
216
|
+
lastLinkCheck = 0;
|
|
217
|
+
linkInflight = null;
|
|
218
|
+
lastWarn.clear();
|
|
219
|
+
}
|
package/dist/styles.css
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowselections/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.30",
|
|
4
4
|
"private": false,
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
},
|
|
100
100
|
"devDependencies": {
|
|
101
101
|
"@eslint/js": "^9.32.0",
|
|
102
|
-
"@lovable.dev/vite-tanstack-config": "2.
|
|
102
|
+
"@lovable.dev/vite-tanstack-config": "2.26.5",
|
|
103
103
|
"@types/node": "^22.16.5",
|
|
104
104
|
"@types/react": "^19.2.0",
|
|
105
105
|
"@types/react-dom": "^19.2.0",
|