@flowselections/floriday-authenticatie-module 1.0.22 → 1.0.24

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.
Files changed (47) hide show
  1. package/dist-lib/components/settings/FloricodeSettingsCard.js +1 -1
  2. package/dist-lib/integrations/supabase/auth-middleware.d.ts +385 -3
  3. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  4. package/dist-lib/integrations/supabase/client.d.ts +385 -3
  5. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  6. package/dist-lib/integrations/supabase/client.server.d.ts +385 -3
  7. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  8. package/dist-lib/integrations/supabase/client.server.js +1 -0
  9. package/dist-lib/integrations/supabase/types.d.ts +389 -3
  10. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  11. package/dist-lib/lib/central-auth.server.d.ts +34 -0
  12. package/dist-lib/lib/central-auth.server.d.ts.map +1 -0
  13. package/dist-lib/lib/central-auth.server.js +105 -0
  14. package/dist-lib/lib/floricode-credentials.server.d.ts.map +1 -1
  15. package/dist-lib/lib/floricode-credentials.server.js +94 -26
  16. package/dist-lib/lib/floricode.functions.d.ts +2758 -83
  17. package/dist-lib/lib/floricode.functions.d.ts.map +1 -1
  18. package/dist-lib/lib/floricode.functions.js +13 -0
  19. package/dist-lib/lib/floriday-auction-letters.functions.d.ts +385 -3
  20. package/dist-lib/lib/floriday-auction-letters.functions.d.ts.map +1 -1
  21. package/dist-lib/lib/floriday-labels.functions.d.ts +385 -3
  22. package/dist-lib/lib/floriday-labels.functions.d.ts.map +1 -1
  23. package/dist-lib/lib/floriday-public.functions.d.ts +2310 -18
  24. package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -1
  25. package/dist-lib/lib/floriday-stickers.functions.d.ts +385 -3
  26. package/dist-lib/lib/floriday-stickers.functions.d.ts.map +1 -1
  27. package/dist-lib/lib/floriday-sync.functions.d.ts +12493 -651
  28. package/dist-lib/lib/floriday-sync.functions.d.ts.map +1 -1
  29. package/dist-lib/lib/floriday-sync.functions.js +2 -1
  30. package/dist-lib/lib/floriday-writes.functions.d.ts +2310 -18
  31. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  32. package/dist-lib/lib/floriday.functions.d.ts +7322 -321
  33. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  34. package/dist-lib/lib/floriday.functions.js +33 -35
  35. package/dist-lib/lib/gateway/cache-tables.d.ts +2 -0
  36. package/dist-lib/lib/gateway/cache-tables.d.ts.map +1 -0
  37. package/dist-lib/lib/gateway/cache-tables.js +27 -0
  38. package/dist-lib/lib/gateway/cached-list.d.ts +15 -0
  39. package/dist-lib/lib/gateway/cached-list.d.ts.map +1 -0
  40. package/dist-lib/lib/gateway/cached-list.js +35 -0
  41. package/dist-lib/lib/gateway/index.d.ts +4 -0
  42. package/dist-lib/lib/gateway/index.d.ts.map +1 -0
  43. package/dist-lib/lib/gateway/index.js +3 -0
  44. package/dist-lib/lib/gateway/shared.d.ts +2 -0
  45. package/dist-lib/lib/gateway/shared.d.ts.map +1 -0
  46. package/dist-lib/lib/gateway/shared.js +3 -0
  47. package/package.json +6 -2
@@ -0,0 +1,34 @@
1
+ export type CentralFlorydayTokenResult = {
2
+ ok: true;
3
+ access_token: string;
4
+ expires_at: string;
5
+ raw: {
6
+ status: number;
7
+ body: string;
8
+ };
9
+ } | {
10
+ ok: false;
11
+ raw: {
12
+ status: number;
13
+ body: string;
14
+ };
15
+ };
16
+ export declare function getFlorydayTokenFromCentral(environment: "staging" | "live", scope: string): Promise<CentralFlorydayTokenResult>;
17
+ export type CentralSecretResult = {
18
+ ok: true;
19
+ secret_name: string;
20
+ version: number;
21
+ value: string;
22
+ raw: {
23
+ status: number;
24
+ body: string;
25
+ };
26
+ } | {
27
+ ok: false;
28
+ raw: {
29
+ status: number;
30
+ body: string;
31
+ };
32
+ };
33
+ export declare function getSecretFromCentral(name: string): Promise<CentralSecretResult>;
34
+ //# sourceMappingURL=central-auth.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"central-auth.server.d.ts","sourceRoot":"","sources":["../../src/lib/central-auth.server.ts"],"names":[],"mappings":"AAoBA,MAAM,MAAM,0BAA0B,GAClC;IACE,EAAE,EAAE,IAAI,CAAC;IACT,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACvC,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,GAAG,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACvC,CAAC;AAEN,wBAAsB,2BAA2B,CAC/C,WAAW,EAAE,SAAS,GAAG,MAAM,EAC/B,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,0BAA0B,CAAC,CAwDrC;AAkBD,MAAM,MAAM,mBAAmB,GAC3B;IACE,EAAE,EAAE,IAAI,CAAC;IACT,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACvC,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,GAAG,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACvC,CAAC;AAEN,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,mBAAmB,CAAC,CAgD9B"}
@@ -0,0 +1,105 @@
1
+ // Server-only: haalt een Floriday-token bij central-auth. Spiegelt de Bearer-
2
+ // signing van de e-mail-tak (@flowselections/core/server → provider-token.js:
3
+ // `Authorization: Bearer ${CENTRAL_AUTH_SECRET}` + `Content-Type: application/json`).
4
+ //
5
+ // Gebruikt bewust NIET core.getProviderToken: die plet de upstream-body naar
6
+ // typed errors, terwijl createFlorydayTokenError (stap B, in floriday.functions.ts)
7
+ // de originele foutsubstrings moet kunnen matchen op `raw.body`. Central plet
8
+ // een mislukte grant naar HTTP 502 { "error": "upstream_error" } — dat is exact
9
+ // waarom raw.body op het niet-ok-pad bewaard moet blijven, letterlijk, zonder
10
+ // wrap of parse.
11
+ //
12
+ // Contract:
13
+ // - Vier faalgevallen (!res.ok, niet-parseerbare body, ontbrekend access_token,
14
+ // ontbrekend expiry) → allemaal { ok: false, raw }. Geen throws.
15
+ // - Alleen een echte netwerkfout (fetch throwt vóór er een response is) gooit;
16
+ // daar bestaat geen raw.
17
+ //
18
+ // NIET importeren vanuit client-code. De .server.ts-suffix houdt dit bestand
19
+ // buiten de client-bundle (zoals bij client.server.ts).
20
+ export async function getFlorydayTokenFromCentral(environment, scope) {
21
+ const baseUrl = process.env.CENTRAL_AUTH_URL;
22
+ const secret = process.env.CENTRAL_AUTH_SECRET;
23
+ if (!baseUrl || !secret) {
24
+ throw new Error("Missing CENTRAL_AUTH_URL or CENTRAL_AUTH_SECRET environment variable.");
25
+ }
26
+ const url = `${baseUrl.replace(/\/+$/, "")}/api/token/floriday`;
27
+ // Netwerkfout (fetch throwt vóór er een response is) → doorgooien, er is
28
+ // geen raw om te retourneren. Alle andere paden gaan via raw.
29
+ const res = await fetch(url, {
30
+ method: "POST",
31
+ headers: {
32
+ Authorization: `Bearer ${secret}`,
33
+ "Content-Type": "application/json",
34
+ },
35
+ body: JSON.stringify({ environment, scope }),
36
+ });
37
+ // Response ALTIJD eerst als tekst lezen — raw.body moet op elk pad gevuld
38
+ // zijn (2xx én niet-ok). Bij niet-ok is dit het enige wat stap B heeft om
39
+ // de oorzaak te bepalen (central plet Floriday-fouten naar 502
40
+ // { "error": "upstream_error" }).
41
+ const text = await res.text();
42
+ const raw = { status: res.status, body: text };
43
+ if (!res.ok) {
44
+ return { ok: false, raw };
45
+ }
46
+ let json;
47
+ try {
48
+ json = JSON.parse(text);
49
+ }
50
+ catch {
51
+ return { ok: false, raw };
52
+ }
53
+ if (typeof json.access_token !== "string" || !json.access_token) {
54
+ return { ok: false, raw };
55
+ }
56
+ const expires_at = typeof json.expires_at === "string" && json.expires_at
57
+ ? json.expires_at
58
+ : typeof json.expires_in === "number"
59
+ ? new Date(Date.now() + json.expires_in * 1000).toISOString()
60
+ : null;
61
+ if (!expires_at) {
62
+ return { ok: false, raw };
63
+ }
64
+ return { ok: true, access_token: json.access_token, expires_at, raw };
65
+ }
66
+ export async function getSecretFromCentral(name) {
67
+ const baseUrl = process.env.CENTRAL_AUTH_URL;
68
+ const secret = process.env.CENTRAL_AUTH_SECRET;
69
+ if (!baseUrl || !secret) {
70
+ throw new Error("Missing CENTRAL_AUTH_URL or CENTRAL_AUTH_SECRET environment variable.");
71
+ }
72
+ const url = `${baseUrl.replace(/\/+$/, "")}/api/secret/${encodeURIComponent(name)}`;
73
+ const res = await fetch(url, {
74
+ method: "POST",
75
+ headers: {
76
+ Authorization: `Bearer ${secret}`,
77
+ },
78
+ });
79
+ const text = await res.text();
80
+ const raw = { status: res.status, body: text };
81
+ if (!res.ok)
82
+ return { ok: false, raw };
83
+ let json;
84
+ try {
85
+ json = JSON.parse(text);
86
+ }
87
+ catch {
88
+ return { ok: false, raw };
89
+ }
90
+ if (typeof json.secret_name !== "string" ||
91
+ json.secret_name !== name ||
92
+ typeof json.version !== "number" ||
93
+ !Number.isInteger(json.version) ||
94
+ typeof json.value !== "string" ||
95
+ !json.value) {
96
+ return { ok: false, raw };
97
+ }
98
+ return {
99
+ ok: true,
100
+ secret_name: json.secret_name,
101
+ version: json.version,
102
+ value: json.value,
103
+ raw,
104
+ };
105
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"floricode-credentials.server.d.ts","sourceRoot":"","sources":["../../src/lib/floricode-credentials.server.ts"],"names":[],"mappings":"AAgBA,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,UAAU,GAAG,KAAK,CAAC;CAC5B;AA0CD,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAc5E;AAED,wBAAsB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgB7E;AAiBD,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,WAAW,CAAC,CAWpE;AAyBD,wBAAsB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAS5E;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,IAAI,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBxG;AAED,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD"}
1
+ {"version":3,"file":"floricode-credentials.server.d.ts","sourceRoot":"","sources":["../../src/lib/floricode-credentials.server.ts"],"names":[],"mappings":"AAuBA,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,UAAU,GAAG,KAAK,CAAC;CAC5B;AA0FD,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAc5E;AAED,wBAAsB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAyC7E;AAiBD,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,WAAW,CAAC,CAWpE;AAyBD,wBAAsB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAS5E;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,IAAI,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBxG;AAED,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD"}
@@ -3,14 +3,20 @@
3
3
  // ----------------------------------------------------------------------------
4
4
  // Centrale plek voor Floricode OAuth-inloggegevens. Waarden komen uit de tabel
5
5
  // `public.floricode_connection_settings` en worden versleuteld opgeslagen met
6
- // AES-256-GCM, met de encryption key uit env-var `FLORICODE_SECRET_ENCRYPTION_KEY`.
7
- // Fallback naar env-vars `FLORICODE_CLIENT_ID/SECRET` bestaat alleen voor
8
- // migratieperiode zolang de UI nog niet ingevuld is.
6
+ // AES-256-GCM. Key-materiaal komt sinds fc1 uit central (endpoint
7
+ // /api/secret/floricode_encryption), niet meer uit env. Fallback naar env-vars
8
+ // `FLORICODE_CLIENT_ID/SECRET` bestaat alleen voor migratieperiode zolang de
9
+ // UI nog niet ingevuld is.
9
10
  // ============================================================================
10
11
  // Web Crypto API — werkt in browser, Node 15+ en Deno. Geen Node "crypto" import,
11
12
  // zodat dist-lib client-side gebundeld kan worden zonder Node builtins te resolven.
12
13
  import { supabaseAdmin } from "../integrations/supabase/client.server";
14
+ import { getSecretFromCentral } from "./central-auth.server";
13
15
  const FLORICODE_TOKEN_URL = "https://api.floricode.com/oauth/token";
16
+ // Zichtbaarheidsvenster voor rotatie: hoe snel we een nieuwe actieve
17
+ // keyversion oppikken nadat central roteert. Immutabel per versie, dus dit
18
+ // TTL geldt alleen voor de actieve-versie-resolutie.
19
+ const ACTIVE_KEY_TTL_MS = 60000;
14
20
  const tokenCache = new Map();
15
21
  function getSubtle() {
16
22
  const subtle = globalThis.crypto?.subtle;
@@ -32,44 +38,106 @@ function fromBase64(b64) {
32
38
  out[i] = bin.charCodeAt(i);
33
39
  return out;
34
40
  }
35
- async function getEncryptionKey() {
36
- const raw = process.env.FLORICODE_SECRET_ENCRYPTION_KEY;
37
- if (!raw) {
38
- throw new Error("FLORICODE_SECRET_ENCRYPTION_KEY ontbreekt — kan Floricode-credentials niet ontsleutelen.");
39
- }
41
+ // ----- Key material resolution (central-managed, version-aware) --------------
42
+ //
43
+ // Cryptografisch primitief en SHA-256-afleiding zijn ONGEWIJZIGD; alleen de
44
+ // bron van de secret-string is nu central i.p.v. env.
45
+ // Per-versie cache: immutabel (een gegeven versie levert altijd dezelfde
46
+ // afgeleide CryptoKey). Single-flight via Promise-opslag.
47
+ const keyByVersion = new Map();
48
+ // Actieve-versie-cache: welke versie is nu actief? TTL = ACTIVE_KEY_TTL_MS.
49
+ let activeCache = null;
50
+ async function deriveKey(value) {
40
51
  const subtle = getSubtle();
41
52
  // Normaliseer naar 32 bytes via SHA-256, ongeacht lengte/format van de bron.
42
- const digest = await subtle.digest("SHA-256", new TextEncoder().encode(raw));
53
+ const digest = await subtle.digest("SHA-256", new TextEncoder().encode(value));
43
54
  return subtle.importKey("raw", digest, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
44
55
  }
56
+ function getActiveKeyMaterial() {
57
+ const now = Date.now();
58
+ if (activeCache && now - activeCache.at < ACTIVE_KEY_TTL_MS) {
59
+ return activeCache.promise;
60
+ }
61
+ const promise = (async () => {
62
+ const result = await getSecretFromCentral("floricode_encryption");
63
+ if (!result.ok) {
64
+ // value staat niet in raw.body — dat is de central-foutbody.
65
+ throw new Error(`Kon de Floricode-encryptiesleutel niet ophalen bij central (status ${result.raw.status}): ${result.raw.body}`);
66
+ }
67
+ // Populate per-versie cache (single-flight).
68
+ const existing = keyByVersion.get(result.version);
69
+ const keyPromise = existing ?? deriveKey(result.value);
70
+ if (!existing)
71
+ keyByVersion.set(result.version, keyPromise);
72
+ const key = await keyPromise;
73
+ return { key, version: result.version };
74
+ })();
75
+ activeCache = { at: now, promise };
76
+ // Reset cache-slot bij falen zodat een volgende call opnieuw probeert.
77
+ promise.catch(() => {
78
+ if (activeCache && activeCache.promise === promise)
79
+ activeCache = null;
80
+ });
81
+ return promise;
82
+ }
83
+ async function getKeyMaterialForVersion(version) {
84
+ const hit = keyByVersion.get(version);
85
+ if (hit)
86
+ return hit;
87
+ const active = await getActiveKeyMaterial();
88
+ if (active.version !== version) {
89
+ throw new Error(`Floricode-credential is versleuteld met sleutelversie ${version}, actief is ${active.version} — her-versleuteling na rotatie is nog niet ondersteund; voer de credential opnieuw in.`);
90
+ }
91
+ return active.key;
92
+ }
45
93
  export async function encryptClientSecret(plaintext) {
46
94
  const subtle = getSubtle();
47
- const key = await getEncryptionKey();
95
+ const { key, version } = await getActiveKeyMaterial();
48
96
  const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
49
97
  const cipherBuf = await subtle.encrypt({ name: "AES-GCM", iv: iv }, key, new TextEncoder().encode(plaintext));
50
98
  const cipherBytes = new Uint8Array(cipherBuf);
51
99
  // AES-GCM in WebCrypto plakt de 16-byte auth tag aan het einde van de ciphertext.
52
100
  const tag = cipherBytes.slice(cipherBytes.length - 16);
53
101
  const enc = cipherBytes.slice(0, cipherBytes.length - 16);
54
- return `v1.${toBase64(iv)}.${toBase64(tag)}.${toBase64(enc)}`;
102
+ return `fc1.${version}.${toBase64(iv)}.${toBase64(tag)}.${toBase64(enc)}`;
55
103
  }
56
104
  export async function decryptClientSecret(ciphertext) {
57
- if (!ciphertext.startsWith("v1.")) {
58
- throw new Error("Onbekend ciphertext-formaat voor Floricode-credential.");
105
+ if (ciphertext.startsWith("fc1.")) {
106
+ const parts = ciphertext.split(".");
107
+ if (parts.length !== 5)
108
+ throw new Error("Ongeldig ciphertext-formaat.");
109
+ const [, versionStr, ivB64, tagB64, dataB64] = parts;
110
+ const version = Number(versionStr);
111
+ if (!Number.isInteger(version) || version < 1) {
112
+ throw new Error("Ongeldig ciphertext-formaat (versie).");
113
+ }
114
+ // Happy-path: één central-resolutie. Bij version-match hergebruiken we de
115
+ // key direct uit datzelfde resultaat — geen tweede resolutie-pad.
116
+ const active = await getActiveKeyMaterial();
117
+ let key;
118
+ if (version === active.version) {
119
+ key = active.key;
120
+ }
121
+ else {
122
+ // Alleen bij (zeldzame) mismatch: fallback via de per-versie helper,
123
+ // die momenteel op mismatch een expliciete "her-versleuteling niet
124
+ // ondersteund"-fout gooit.
125
+ key = await getKeyMaterialForVersion(version);
126
+ }
127
+ const subtle = getSubtle();
128
+ const iv = fromBase64(ivB64);
129
+ const tag = fromBase64(tagB64);
130
+ const data = fromBase64(dataB64);
131
+ const combined = new Uint8Array(data.length + tag.length);
132
+ combined.set(data, 0);
133
+ combined.set(tag, data.length);
134
+ const plainBuf = await subtle.decrypt({ name: "AES-GCM", iv: iv }, key, combined);
135
+ return new TextDecoder().decode(plainBuf);
59
136
  }
60
- const [, ivB64, tagB64, dataB64] = ciphertext.split(".");
61
- if (!ivB64 || !tagB64 || !dataB64)
62
- throw new Error("Ongeldig ciphertext-formaat.");
63
- const subtle = getSubtle();
64
- const key = await getEncryptionKey();
65
- const iv = fromBase64(ivB64);
66
- const tag = fromBase64(tagB64);
67
- const data = fromBase64(dataB64);
68
- const combined = new Uint8Array(data.length + tag.length);
69
- combined.set(data, 0);
70
- combined.set(tag, data.length);
71
- const plainBuf = await subtle.decrypt({ name: "AES-GCM", iv: iv }, key, combined);
72
- return new TextDecoder().decode(plainBuf);
137
+ if (ciphertext.startsWith("v1.")) {
138
+ throw new Error("Floricode-credential is versleuteld met een verouderde lokale sleutel — voer de Floricode-credential opnieuw in.");
139
+ }
140
+ throw new Error("Onbekend ciphertext-formaat voor Floricode-credential.");
73
141
  }
74
142
  async function loadCredentialsFromDb() {
75
143
  const { data, error } = await supabaseAdmin