@edge-base/server 0.2.9 → 0.3.2
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/admin-build/_app/immutable/chunks/BENT96gS.js +1 -0
- package/admin-build/_app/immutable/chunks/{BfhqI0W8.js → BR-X1vqg.js} +1 -1
- package/admin-build/_app/immutable/chunks/{DncT8xH5.js → BYTXdmFz.js} +1 -1
- package/admin-build/_app/immutable/chunks/{YGUb3X-Q.js → C-_7bS5w.js} +1 -1
- package/admin-build/_app/immutable/chunks/{DUhE3Ynq.js → CCB6XzFZ.js} +1 -1
- package/admin-build/_app/immutable/chunks/{39YMyjjq.js → CWTeyOdB.js} +1 -1
- package/admin-build/_app/immutable/chunks/{DllVADtt.js → CX5CmbTG.js} +1 -1
- package/admin-build/_app/immutable/chunks/{0M5txo3l.js → CdgSuiqt.js} +3 -3
- package/admin-build/_app/immutable/chunks/{BN-pHKcH.js → Cuny2nPU.js} +1 -1
- package/admin-build/_app/immutable/chunks/{CPLcjNR9.js → D15DP4DQ.js} +1 -1
- package/admin-build/_app/immutable/chunks/{DBkor_jM.js → DAzRCtch.js} +1 -1
- package/admin-build/_app/immutable/chunks/DYF4byEY.js +1 -0
- package/admin-build/_app/immutable/chunks/{DBniE7GN.js → Dcey5WNo.js} +1 -1
- package/admin-build/_app/immutable/chunks/{C5nMidnK.js → HK54RDdv.js} +1 -1
- package/admin-build/_app/immutable/chunks/{CI1lKBjB.js → gUtOgqgM.js} +1 -1
- package/admin-build/_app/immutable/chunks/{_xa30BOD.js → pnqPwnFK.js} +1 -1
- package/admin-build/_app/immutable/entry/{app.BW-Ac3jY.js → app.DwslmBQo.js} +2 -2
- package/admin-build/_app/immutable/entry/start.Bi51W3Uj.js +1 -0
- package/admin-build/_app/immutable/nodes/{0.CRSQPtos.js → 0.SSaZIC3t.js} +1 -1
- package/admin-build/_app/immutable/nodes/{1.HmStNbZP.js → 1.CqSv9lq5.js} +1 -1
- package/admin-build/_app/immutable/nodes/{10.BVlz13nr.js → 10.BoQz_717.js} +1 -1
- package/admin-build/_app/immutable/nodes/{11.VwQdbWwH.js → 11.B-yS_gft.js} +1 -1
- package/admin-build/_app/immutable/nodes/{12.BFAAgi1f.js → 12.WsVtLoVx.js} +1 -1
- package/admin-build/_app/immutable/nodes/{13.DDdauC84.js → 13.TbqRQJKh.js} +1 -1
- package/admin-build/_app/immutable/nodes/{14.D_rXaafJ.js → 14.cldePwj3.js} +1 -1
- package/admin-build/_app/immutable/nodes/{15.rfFjjiZc.js → 15.Bec037-f.js} +1 -1
- package/admin-build/_app/immutable/nodes/{16.DDEpGJ4Z.js → 16.Dd0yTYhN.js} +1 -1
- package/admin-build/_app/immutable/nodes/{17.ChQWNtRN.js → 17.CoiqH0lF.js} +1 -1
- package/admin-build/_app/immutable/nodes/{18.Rir_JwlF.js → 18.BDXxM1Qi.js} +1 -1
- package/admin-build/_app/immutable/nodes/{19.CF76nxk1.js → 19.lo9mTPiR.js} +1 -1
- package/admin-build/_app/immutable/nodes/{20.JQer3KPM.js → 20.B7kbGqh_.js} +1 -1
- package/admin-build/_app/immutable/nodes/21.BGm1TcfO.js +1 -0
- package/admin-build/_app/immutable/nodes/{22.DUw7X6z_.js → 22.VOtCuTkf.js} +1 -1
- package/admin-build/_app/immutable/nodes/{23.DS3svA-a.js → 23.C2Uxx-kO.js} +1 -1
- package/admin-build/_app/immutable/nodes/{24.BYOsQM6B.js → 24.BxdEwsS3.js} +1 -1
- package/admin-build/_app/immutable/nodes/{25.n0pyDBRv.js → 25.CVu2qoCv.js} +1 -1
- package/admin-build/_app/immutable/nodes/{26.C-Oo8sYK.js → 26.DqYlpX7Z.js} +1 -1
- package/admin-build/_app/immutable/nodes/{27.BfxbFNkk.js → 27.emgbpP8y.js} +1 -1
- package/admin-build/_app/immutable/nodes/{28.DyHSN1q7.js → 28.BVPSPK68.js} +1 -1
- package/admin-build/_app/immutable/nodes/{29.CUAmZbCc.js → 29.BsDQQ16z.js} +1 -1
- package/admin-build/_app/immutable/nodes/{3.KQm4VJJg.js → 3.uc8joqGb.js} +1 -1
- package/admin-build/_app/immutable/nodes/{30.CQdPre5F.js → 30.D4CYqQrE.js} +1 -1
- package/admin-build/_app/immutable/nodes/{31.BP6raetq.js → 31.DvUuXrVU.js} +1 -1
- package/admin-build/_app/immutable/nodes/{4.Glf2jJHB.js → 4.BqTZ0Uoq.js} +1 -1
- package/admin-build/_app/immutable/nodes/{5.BdFtj_-X.js → 5.DAO3--6W.js} +1 -1
- package/admin-build/_app/immutable/nodes/{6.JfnwWq8s.js → 6.CBbGl_Wo.js} +1 -1
- package/admin-build/_app/immutable/nodes/{7.C-rw9qDh.js → 7.s-I8cKAh.js} +1 -1
- package/admin-build/_app/immutable/nodes/{8.CA7r1Sjs.js → 8.Ct6B-y3f.js} +1 -1
- package/admin-build/_app/immutable/nodes/{9.B2jgJDkH.js → 9.Dok_ZoeZ.js} +1 -1
- package/admin-build/_app/version.json +1 -1
- package/admin-build/index.html +7 -7
- package/openapi.json +498 -3
- package/package.json +3 -3
- package/src/__tests__/auth-token.test.ts +142 -0
- package/src/__tests__/email-provider.test.ts +82 -0
- package/src/__tests__/error-format.test.ts +21 -0
- package/src/__tests__/functions-context.test.ts +87 -0
- package/src/__tests__/functions-d1-proxy.test.ts +70 -0
- package/src/__tests__/functions-route.test.ts +15 -0
- package/src/__tests__/postgres-api-consistency.test.ts +240 -0
- package/src/__tests__/postgres-batch-compat.test.ts +33 -27
- package/src/__tests__/postgres-batch.test.ts +394 -0
- package/src/__tests__/postgres-field-ops-compat.test.ts +2 -1
- package/src/__tests__/postgres-transact.test.ts +148 -0
- package/src/__tests__/room-access-policy.test.ts +26 -0
- package/src/__tests__/room-handler-context.test.ts +2 -0
- package/src/__tests__/smoke-skip-report.test.ts +1 -1
- package/src/durable-objects/database-do.ts +455 -15
- package/src/durable-objects/room-runtime-base.ts +16 -5
- package/src/durable-objects/rooms-do.ts +4 -1
- package/src/lib/auth-d1-service.ts +37 -7
- package/src/lib/auth-token.ts +66 -0
- package/src/lib/d1-handler.ts +416 -50
- package/src/lib/email-provider.ts +115 -5
- package/src/lib/errors.ts +8 -4
- package/src/lib/functions.ts +91 -1
- package/src/lib/internal-transport.ts +25 -1
- package/src/lib/jwt.ts +2 -0
- package/src/lib/postgres-handler.ts +562 -84
- package/src/routes/admin.ts +8 -1
- package/src/routes/auth.ts +169 -65
- package/src/routes/room.ts +36 -2
- package/src/routes/storage.ts +446 -62
- package/src/routes/tables.ts +140 -0
- package/src/types.ts +10 -0
- package/admin-build/_app/immutable/chunks/B0zJ5_Wk.js +0 -1
- package/admin-build/_app/immutable/chunks/DZo4s3wn.js +0 -1
- package/admin-build/_app/immutable/entry/start.Bcf0SjPV.js +0 -1
- package/admin-build/_app/immutable/nodes/21.CzGYxjpu.js +0 -1
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import type { AuthDb } from './auth-db-adapter.js';
|
|
17
|
+
import { authSecretLookupKeys, hashAuthSecret } from './auth-token.js';
|
|
17
18
|
|
|
18
19
|
// ─── Types ───
|
|
19
20
|
|
|
@@ -660,11 +661,12 @@ export async function createEmailToken(
|
|
|
660
661
|
input: CreateEmailTokenInput,
|
|
661
662
|
): Promise<void> {
|
|
662
663
|
const now = new Date().toISOString();
|
|
664
|
+
const tokenHash = await hashAuthSecret(input.token);
|
|
663
665
|
|
|
664
666
|
await db.run(
|
|
665
667
|
`INSERT INTO _email_tokens (token, userId, type, expiresAt, createdAt)
|
|
666
668
|
VALUES (?, ?, ?, ?, ?)`,
|
|
667
|
-
[
|
|
669
|
+
[tokenHash, input.userId, input.type, input.expiresAt, now],
|
|
668
670
|
);
|
|
669
671
|
}
|
|
670
672
|
|
|
@@ -676,9 +678,10 @@ export async function getEmailToken(
|
|
|
676
678
|
db: AuthDb,
|
|
677
679
|
token: string,
|
|
678
680
|
): Promise<Record<string, unknown> | null> {
|
|
681
|
+
const lookupKeys = await authSecretLookupKeys(token);
|
|
679
682
|
const row = await db.first(
|
|
680
|
-
`SELECT * FROM _email_tokens WHERE token
|
|
681
|
-
|
|
683
|
+
`SELECT * FROM _email_tokens WHERE token IN (${lookupKeys.map(() => '?').join(', ')})`,
|
|
684
|
+
lookupKeys,
|
|
682
685
|
);
|
|
683
686
|
|
|
684
687
|
if (!row) return null;
|
|
@@ -686,7 +689,10 @@ export async function getEmailToken(
|
|
|
686
689
|
// Check expiration
|
|
687
690
|
if (new Date(row.expiresAt as string) < new Date()) {
|
|
688
691
|
// Clean up expired token
|
|
689
|
-
await db.run(
|
|
692
|
+
await db.run(
|
|
693
|
+
`DELETE FROM _email_tokens WHERE token IN (${lookupKeys.map(() => '?').join(', ')})`,
|
|
694
|
+
lookupKeys,
|
|
695
|
+
);
|
|
690
696
|
return null;
|
|
691
697
|
}
|
|
692
698
|
|
|
@@ -702,9 +708,10 @@ export async function getEmailTokenByType(
|
|
|
702
708
|
token: string,
|
|
703
709
|
type: string,
|
|
704
710
|
): Promise<Record<string, unknown> | null> {
|
|
711
|
+
const lookupKeys = await authSecretLookupKeys(token);
|
|
705
712
|
return await db.first(
|
|
706
|
-
`SELECT * FROM _email_tokens WHERE token
|
|
707
|
-
[
|
|
713
|
+
`SELECT * FROM _email_tokens WHERE token IN (${lookupKeys.map(() => '?').join(', ')}) AND type = ?`,
|
|
714
|
+
[...lookupKeys, type],
|
|
708
715
|
);
|
|
709
716
|
}
|
|
710
717
|
|
|
@@ -715,7 +722,11 @@ export async function deleteEmailToken(
|
|
|
715
722
|
db: AuthDb,
|
|
716
723
|
token: string,
|
|
717
724
|
): Promise<void> {
|
|
718
|
-
|
|
725
|
+
const lookupKeys = await authSecretLookupKeys(token);
|
|
726
|
+
await db.run(
|
|
727
|
+
`DELETE FROM _email_tokens WHERE token IN (${lookupKeys.map(() => '?').join(', ')})`,
|
|
728
|
+
lookupKeys,
|
|
729
|
+
);
|
|
719
730
|
}
|
|
720
731
|
|
|
721
732
|
/**
|
|
@@ -923,6 +934,25 @@ export async function createRecoveryCodes(
|
|
|
923
934
|
);
|
|
924
935
|
}
|
|
925
936
|
|
|
937
|
+
/**
|
|
938
|
+
* Replace all recovery codes for a user with a freshly generated batch.
|
|
939
|
+
*/
|
|
940
|
+
export async function replaceRecoveryCodes(
|
|
941
|
+
db: AuthDb,
|
|
942
|
+
userId: string,
|
|
943
|
+
codes: Array<{ id: string; codeHash: string }>,
|
|
944
|
+
): Promise<void> {
|
|
945
|
+
const now = new Date().toISOString();
|
|
946
|
+
await db.batch([
|
|
947
|
+
{ sql: `DELETE FROM _mfa_recovery_codes WHERE userId = ?`, params: [userId] },
|
|
948
|
+
...codes.map((code) => ({
|
|
949
|
+
sql: `INSERT INTO _mfa_recovery_codes (id, userId, codeHash, used, createdAt)
|
|
950
|
+
VALUES (?, ?, ?, 0, ?)`,
|
|
951
|
+
params: [code.id, userId, code.codeHash, now],
|
|
952
|
+
})),
|
|
953
|
+
]);
|
|
954
|
+
}
|
|
955
|
+
|
|
926
956
|
/**
|
|
927
957
|
* List unused recovery codes for a user.
|
|
928
958
|
*/
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const encoder = new TextEncoder();
|
|
2
|
+
|
|
3
|
+
function toHex(value: ArrayBuffer): string {
|
|
4
|
+
return Array.from(new Uint8Array(value))
|
|
5
|
+
.map((byte) => byte.toString(16).padStart(2, '0'))
|
|
6
|
+
.join('');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function timingSafeStringEqual(a: string, b: string): boolean {
|
|
10
|
+
if (a.length !== b.length) return false;
|
|
11
|
+
let result = 0;
|
|
12
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
13
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
14
|
+
}
|
|
15
|
+
return result === 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function hmacSha256Hex(key: string, value: string): Promise<string> {
|
|
19
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
20
|
+
'raw',
|
|
21
|
+
encoder.encode(key),
|
|
22
|
+
{ name: 'HMAC', hash: 'SHA-256' },
|
|
23
|
+
false,
|
|
24
|
+
['sign'],
|
|
25
|
+
);
|
|
26
|
+
const signature = await crypto.subtle.sign('HMAC', cryptoKey, encoder.encode(value));
|
|
27
|
+
return toHex(signature);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function hashAuthSecret(secret: string): Promise<string> {
|
|
31
|
+
const digest = await crypto.subtle.digest('SHA-256', encoder.encode(secret));
|
|
32
|
+
return `sha256:${toHex(digest)}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function authSecretLookupKeys(secret: string): Promise<string[]> {
|
|
36
|
+
const hashed = await hashAuthSecret(secret);
|
|
37
|
+
return secret.startsWith('sha256:') ? [secret] : [hashed, secret];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function verifyAuthSecret(secret: string, storedHash: string): Promise<boolean> {
|
|
41
|
+
const candidate = await hashAuthSecret(secret);
|
|
42
|
+
return timingSafeStringEqual(candidate, storedHash);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function hashOtpSecret(otp: string, serverSecret: string): Promise<string> {
|
|
46
|
+
const digest = await hmacSha256Hex(serverSecret, `edgebase:auth:otp:${otp}`);
|
|
47
|
+
return `hmac-sha256:${digest}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function verifyOtpSecret(
|
|
51
|
+
otp: string,
|
|
52
|
+
storedHash: string,
|
|
53
|
+
serverSecret: string,
|
|
54
|
+
): Promise<boolean> {
|
|
55
|
+
if (storedHash.startsWith('hmac-sha256:')) {
|
|
56
|
+
const candidate = await hashOtpSecret(otp, serverSecret);
|
|
57
|
+
return timingSafeStringEqual(candidate, storedHash);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Backward compatibility for short-lived OTPs stored before HMAC hashing.
|
|
61
|
+
if (storedHash.startsWith('sha256:')) {
|
|
62
|
+
return verifyAuthSecret(otp, storedHash);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return false;
|
|
66
|
+
}
|