@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.
- package/dist-lib/components/settings/FloricodeSettingsCard.js +1 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +385 -3
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +385 -3
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +385 -3
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.js +1 -0
- package/dist-lib/integrations/supabase/types.d.ts +389 -3
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/central-auth.server.d.ts +34 -0
- package/dist-lib/lib/central-auth.server.d.ts.map +1 -0
- package/dist-lib/lib/central-auth.server.js +105 -0
- package/dist-lib/lib/floricode-credentials.server.d.ts.map +1 -1
- package/dist-lib/lib/floricode-credentials.server.js +94 -26
- package/dist-lib/lib/floricode.functions.d.ts +2758 -83
- package/dist-lib/lib/floricode.functions.d.ts.map +1 -1
- package/dist-lib/lib/floricode.functions.js +13 -0
- package/dist-lib/lib/floriday-auction-letters.functions.d.ts +385 -3
- package/dist-lib/lib/floriday-auction-letters.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-labels.functions.d.ts +385 -3
- package/dist-lib/lib/floriday-labels.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-public.functions.d.ts +2310 -18
- package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-stickers.functions.d.ts +385 -3
- package/dist-lib/lib/floriday-stickers.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-sync.functions.d.ts +12493 -651
- package/dist-lib/lib/floriday-sync.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-sync.functions.js +2 -1
- package/dist-lib/lib/floriday-writes.functions.d.ts +2310 -18
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday.functions.d.ts +7322 -321
- package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday.functions.js +33 -35
- package/dist-lib/lib/gateway/cache-tables.d.ts +2 -0
- package/dist-lib/lib/gateway/cache-tables.d.ts.map +1 -0
- package/dist-lib/lib/gateway/cache-tables.js +27 -0
- package/dist-lib/lib/gateway/cached-list.d.ts +15 -0
- package/dist-lib/lib/gateway/cached-list.d.ts.map +1 -0
- package/dist-lib/lib/gateway/cached-list.js +35 -0
- package/dist-lib/lib/gateway/index.d.ts +4 -0
- package/dist-lib/lib/gateway/index.d.ts.map +1 -0
- package/dist-lib/lib/gateway/index.js +3 -0
- package/dist-lib/lib/gateway/shared.d.ts +2 -0
- package/dist-lib/lib/gateway/shared.d.ts.map +1 -0
- package/dist-lib/lib/gateway/shared.js +3 -0
- 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":"
|
|
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
|
|
7
|
-
// Fallback naar env-vars
|
|
8
|
-
//
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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(
|
|
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
|
|
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 `
|
|
102
|
+
return `fc1.${version}.${toBase64(iv)}.${toBase64(tag)}.${toBase64(enc)}`;
|
|
55
103
|
}
|
|
56
104
|
export async function decryptClientSecret(ciphertext) {
|
|
57
|
-
if (
|
|
58
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|