@opensyber/tokenforge 0.1.0 → 0.1.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/dist/adapters/hono.d.ts +7 -3
- package/dist/adapters/hono.d.ts.map +1 -1
- package/dist/adapters/hono.js +6 -3
- package/dist/adapters/hono.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/server/index.d.ts +0 -4
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +4 -4
- package/dist/server/index.js.map +1 -1
- package/package.json +6 -14
- package/dist/server/binding.d.ts +0 -19
- package/dist/server/binding.d.ts.map +0 -1
- package/dist/server/binding.js +0 -87
- package/dist/server/binding.js.map +0 -1
- package/dist/server/step-up.d.ts +0 -19
- package/dist/server/step-up.d.ts.map +0 -1
- package/dist/server/step-up.js +0 -139
- package/dist/server/step-up.js.map +0 -1
- package/dist/server/storage/index.d.ts +0 -3
- package/dist/server/storage/index.d.ts.map +0 -1
- package/dist/server/storage/index.js +0 -2
- package/dist/server/storage/index.js.map +0 -1
- package/dist/server/storage/interface.d.ts +0 -61
- package/dist/server/storage/interface.d.ts.map +0 -1
- package/dist/server/storage/interface.js +0 -2
- package/dist/server/storage/interface.js.map +0 -1
- package/dist/server/storage/memory.d.ts +0 -40
- package/dist/server/storage/memory.d.ts.map +0 -1
- package/dist/server/storage/memory.js +0 -107
- package/dist/server/storage/memory.js.map +0 -1
- package/dist/server/totp.d.ts +0 -53
- package/dist/server/totp.d.ts.map +0 -1
- package/dist/server/totp.js +0 -114
- package/dist/server/totp.js.map +0 -1
package/dist/adapters/hono.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hono adapter — re-export
|
|
2
|
+
* Hono adapter — re-export of the cloud-managed verification middleware.
|
|
3
3
|
* Usage: import { tokenForgeMiddleware } from '@opensyber/tokenforge/hono'
|
|
4
|
+
*
|
|
5
|
+
* Self-hosted routes (createTokenForgeRoutes, createStepUpRoutes) are kept
|
|
6
|
+
* internal — they require the full crypto stack which is excluded from the
|
|
7
|
+
* published tarball. Self-hosting customers should use the source repo
|
|
8
|
+
* directly instead of the npm package.
|
|
4
9
|
*/
|
|
5
10
|
export { tokenForgeMiddleware } from '../server/middleware.js';
|
|
6
|
-
export {
|
|
7
|
-
export { createStepUpRoutes } from '../server/step-up.js';
|
|
11
|
+
export type { TokenForgeOptions } from '../server/middleware.js';
|
|
8
12
|
//# sourceMappingURL=hono.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../../src/adapters/hono.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../../src/adapters/hono.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC"}
|
package/dist/adapters/hono.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hono adapter — re-export
|
|
2
|
+
* Hono adapter — re-export of the cloud-managed verification middleware.
|
|
3
3
|
* Usage: import { tokenForgeMiddleware } from '@opensyber/tokenforge/hono'
|
|
4
|
+
*
|
|
5
|
+
* Self-hosted routes (createTokenForgeRoutes, createStepUpRoutes) are kept
|
|
6
|
+
* internal — they require the full crypto stack which is excluded from the
|
|
7
|
+
* published tarball. Self-hosting customers should use the source repo
|
|
8
|
+
* directly instead of the npm package.
|
|
4
9
|
*/
|
|
5
10
|
export { tokenForgeMiddleware } from '../server/middleware.js';
|
|
6
|
-
export { createTokenForgeRoutes } from '../server/binding.js';
|
|
7
|
-
export { createStepUpRoutes } from '../server/step-up.js';
|
|
8
11
|
//# sourceMappingURL=hono.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono.js","sourceRoot":"","sources":["../../src/adapters/hono.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"hono.js","sourceRoot":"","sources":["../../src/adapters/hono.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC"}
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { tokenForgeMiddleware as honoMiddleware
|
|
1
|
+
export { tokenForgeMiddleware as honoMiddleware } from './hono.js';
|
|
2
2
|
export { tokenForgeMiddleware as expressMiddleware } from './express.js';
|
|
3
3
|
export { withTokenForge, tokenForgeCheck } from './nextjs.js';
|
|
4
4
|
export { tokenForgePlugin } from './fastify.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,IAAI,cAAc,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,IAAI,cAAc,EAAE,MAAM,WAAW,CAAC;AACnE,OAAO,EAAE,oBAAoB,IAAI,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/adapters/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { tokenForgeMiddleware as honoMiddleware
|
|
1
|
+
export { tokenForgeMiddleware as honoMiddleware } from './hono.js';
|
|
2
2
|
export { tokenForgeMiddleware as expressMiddleware } from './express.js';
|
|
3
3
|
export { withTokenForge, tokenForgeCheck } from './nextjs.js';
|
|
4
4
|
export { tokenForgePlugin } from './fastify.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,IAAI,cAAc,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,IAAI,cAAc,EAAE,MAAM,WAAW,CAAC;AACnE,OAAO,EAAE,oBAAoB,IAAI,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
1
|
export { tokenForgeMiddleware } from './middleware.js';
|
|
2
2
|
export type { TokenForgeOptions } from './middleware.js';
|
|
3
|
-
export { createTokenForgeRoutes, tokenForgeRoutes } from './binding.js';
|
|
4
|
-
export { createStepUpRoutes, stepUpRoutes } from './step-up.js';
|
|
5
|
-
export { verifyTotp, generateTotp } from './totp.js';
|
|
6
|
-
export type { TokenForgeStorage, StepUpChallengeRecord } from './storage/interface.js';
|
|
7
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/server/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// Public API — shipped in the npm package
|
|
1
|
+
// Public API — shipped in the npm package.
|
|
2
|
+
// The cloud-managed verification middleware is the only published entry point.
|
|
3
|
+
// Self-hosted routes (binding, step-up) and internal crypto utilities (trust-score,
|
|
4
|
+
// verify, crypto) are kept internal and excluded via package.json `files`.
|
|
2
5
|
export { tokenForgeMiddleware } from './middleware.js';
|
|
3
|
-
export { createTokenForgeRoutes, tokenForgeRoutes } from './binding.js';
|
|
4
|
-
export { createStepUpRoutes, stepUpRoutes } from './step-up.js';
|
|
5
|
-
export { verifyTotp, generateTotp } from './totp.js';
|
|
6
6
|
//# sourceMappingURL=index.js.map
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,+EAA+E;AAC/E,oFAAoF;AACpF,2EAA2E;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opensyber/tokenforge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Device-bound session security — ECDSA P-256 signatures for every request after login",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -60,18 +60,6 @@
|
|
|
60
60
|
"./fastify": {
|
|
61
61
|
"import": "./dist/adapters/fastify.js",
|
|
62
62
|
"types": "./dist/adapters/fastify.d.ts"
|
|
63
|
-
},
|
|
64
|
-
"./storage": {
|
|
65
|
-
"import": "./dist/server/storage/index.js",
|
|
66
|
-
"types": "./dist/server/storage/index.d.ts"
|
|
67
|
-
},
|
|
68
|
-
"./storage/internal": {
|
|
69
|
-
"import": "./src/server/storage/internal.ts",
|
|
70
|
-
"types": "./src/server/storage/internal.ts"
|
|
71
|
-
},
|
|
72
|
-
"./server/internal": {
|
|
73
|
-
"import": "./src/server/internal.ts",
|
|
74
|
-
"types": "./src/server/internal.ts"
|
|
75
63
|
}
|
|
76
64
|
},
|
|
77
65
|
"files": [
|
|
@@ -84,8 +72,12 @@
|
|
|
84
72
|
"!dist/server/crypto.*",
|
|
85
73
|
"!dist/server/trust-score.*",
|
|
86
74
|
"!dist/server/verify.*",
|
|
75
|
+
"!dist/server/binding.*",
|
|
76
|
+
"!dist/server/step-up.*",
|
|
77
|
+
"!dist/server/totp.*",
|
|
87
78
|
"!dist/server/middleware-internal.*",
|
|
88
79
|
"!dist/server/internal.*",
|
|
80
|
+
"!dist/server/storage/**",
|
|
89
81
|
"README.md",
|
|
90
82
|
"LICENSE.md"
|
|
91
83
|
],
|
|
@@ -98,7 +90,7 @@
|
|
|
98
90
|
"prepublishOnly": "pnpm run clean && pnpm run build"
|
|
99
91
|
},
|
|
100
92
|
"dependencies": {
|
|
101
|
-
"hono": "^4.
|
|
93
|
+
"hono": "^4.12.14"
|
|
102
94
|
},
|
|
103
95
|
"peerDependencies": {
|
|
104
96
|
"react": ">=18.0.0"
|
package/dist/server/binding.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Hono } from 'hono';
|
|
2
|
-
import type { TokenForgeServerOptions } from '../shared/types.js';
|
|
3
|
-
interface Variables {
|
|
4
|
-
userId: string;
|
|
5
|
-
sessionId: string;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* TokenForge binding and session management routes.
|
|
9
|
-
* Mount at /api/tf on the main Hono app.
|
|
10
|
-
* @param options - Server options (storage, thresholds, callbacks).
|
|
11
|
-
* @returns A Hono sub-app with /bind, /sessions, /events, /trust-score routes.
|
|
12
|
-
*/
|
|
13
|
-
export declare function createTokenForgeRoutes(options: TokenForgeServerOptions): Hono<{
|
|
14
|
-
Variables: Variables;
|
|
15
|
-
}>;
|
|
16
|
-
/** @deprecated Use createTokenForgeRoutes(options) instead */
|
|
17
|
-
export declare const tokenForgeRoutes: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
18
|
-
export {};
|
|
19
|
-
//# sourceMappingURL=binding.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../../src/server/binding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,uBAAuB,EAAiB,MAAM,oBAAoB,CAAC;AAEjF,UAAU,SAAS;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAAC;IAAE,SAAS,EAAE,SAAS,CAAA;CAAE,CAAC,CAsFvG;AAED,8DAA8D;AAC9D,eAAO,MAAM,gBAAgB,4EAAa,CAAC"}
|
package/dist/server/binding.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { Hono } from 'hono';
|
|
2
|
-
import { hashFingerprint } from './trust-score.js';
|
|
3
|
-
/**
|
|
4
|
-
* TokenForge binding and session management routes.
|
|
5
|
-
* Mount at /api/tf on the main Hono app.
|
|
6
|
-
* @param options - Server options (storage, thresholds, callbacks).
|
|
7
|
-
* @returns A Hono sub-app with /bind, /sessions, /events, /trust-score routes.
|
|
8
|
-
*/
|
|
9
|
-
export function createTokenForgeRoutes(options) {
|
|
10
|
-
const tf = new Hono();
|
|
11
|
-
tf.post('/bind', async (c) => {
|
|
12
|
-
const userId = c.get('userId');
|
|
13
|
-
const sessionId = c.get('sessionId');
|
|
14
|
-
if (!userId || !sessionId)
|
|
15
|
-
return c.json({ error: 'unauthorized' }, 401);
|
|
16
|
-
const body = await c.req.json();
|
|
17
|
-
if (body.sessionId !== sessionId)
|
|
18
|
-
return c.json({ error: 'session_mismatch' }, 400);
|
|
19
|
-
if (!body.publicKey?.kty || body.publicKey.kty !== 'EC' || body.publicKey.crv !== 'P-256') {
|
|
20
|
-
return c.json({ error: 'invalid_key_format' }, 400);
|
|
21
|
-
}
|
|
22
|
-
const deviceId = crypto.randomUUID().replace(/-/g, '');
|
|
23
|
-
const fingerprint = hashFingerprint(body.metadata?.userAgent || '');
|
|
24
|
-
const expiresAt = new Date(Date.now() + options.sessionMaxAge * 1000).toISOString();
|
|
25
|
-
const ipAddress = options.getIpAddress?.(c.req.raw) ?? c.req.header('cf-connecting-ip') ?? '';
|
|
26
|
-
const countryCode = options.getCountryCode?.(c.req.raw) ?? c.req.header('cf-ipcountry') ?? '';
|
|
27
|
-
await options.storage.revokeUserSessions(userId);
|
|
28
|
-
const session = {
|
|
29
|
-
id: deviceId, session_id: sessionId, user_id: userId,
|
|
30
|
-
public_key: JSON.stringify(body.publicKey),
|
|
31
|
-
device_fingerprint: fingerprint, ip_address: ipAddress,
|
|
32
|
-
country_code: countryCode, trust_score: 100,
|
|
33
|
-
bound_at: new Date().toISOString(), last_verified_at: new Date().toISOString(),
|
|
34
|
-
expires_at: expiresAt, revoked: 0, revoked_reason: null,
|
|
35
|
-
created_at: new Date().toISOString(),
|
|
36
|
-
};
|
|
37
|
-
await options.storage.createSession(session);
|
|
38
|
-
await options.storage.logEvent({
|
|
39
|
-
id: crypto.randomUUID(), sessionId, userId,
|
|
40
|
-
eventType: 'DEVICE_BOUND', trustScoreBefore: 0, trustScoreAfter: 100,
|
|
41
|
-
ipAddress, countryCode,
|
|
42
|
-
userAgent: options.getUserAgent?.(c.req.raw) ?? c.req.header('user-agent') ?? '',
|
|
43
|
-
metadata: { deviceId, fingerprint, metadata: body.metadata },
|
|
44
|
-
});
|
|
45
|
-
return c.json({ deviceId, expiresAt });
|
|
46
|
-
});
|
|
47
|
-
tf.get('/sessions', async (c) => {
|
|
48
|
-
const userId = c.get('userId');
|
|
49
|
-
if (!userId)
|
|
50
|
-
return c.json({ error: 'unauthorized' }, 401);
|
|
51
|
-
const sessions = await options.storage.listUserSessions(userId, 20);
|
|
52
|
-
return c.json({ sessions });
|
|
53
|
-
});
|
|
54
|
-
tf.delete('/sessions/:id', async (c) => {
|
|
55
|
-
const userId = c.get('userId');
|
|
56
|
-
if (!userId)
|
|
57
|
-
return c.json({ error: 'unauthorized' }, 401);
|
|
58
|
-
await options.storage.revokeSession(c.req.param('id'), 'user_revoked');
|
|
59
|
-
return c.json({ revoked: true });
|
|
60
|
-
});
|
|
61
|
-
tf.get('/events', async (c) => {
|
|
62
|
-
const userId = c.get('userId');
|
|
63
|
-
if (!userId)
|
|
64
|
-
return c.json({ error: 'unauthorized' }, 401);
|
|
65
|
-
const limit = parseInt(c.req.query('limit') || '50', 10);
|
|
66
|
-
const offset = parseInt(c.req.query('offset') || '0', 10);
|
|
67
|
-
const events = await options.storage.listEvents(userId, limit, offset);
|
|
68
|
-
return c.json({ events });
|
|
69
|
-
});
|
|
70
|
-
tf.get('/trust-score', async (c) => {
|
|
71
|
-
const userId = c.get('userId');
|
|
72
|
-
const sessionId = c.get('sessionId');
|
|
73
|
-
const deviceId = c.req.header('X-TF-Device-ID');
|
|
74
|
-
if (!userId)
|
|
75
|
-
return c.json({ error: 'unauthorized' }, 401);
|
|
76
|
-
if (!deviceId || !sessionId)
|
|
77
|
-
return c.json({ trustScore: 0, isBound: false, deviceId: null });
|
|
78
|
-
const session = await options.storage.getSession(sessionId, deviceId);
|
|
79
|
-
if (!session)
|
|
80
|
-
return c.json({ trustScore: 0, isBound: false, deviceId: null });
|
|
81
|
-
return c.json({ trustScore: session.trust_score, isBound: true, deviceId });
|
|
82
|
-
});
|
|
83
|
-
return tf;
|
|
84
|
-
}
|
|
85
|
-
/** @deprecated Use createTokenForgeRoutes(options) instead */
|
|
86
|
-
export const tokenForgeRoutes = new Hono();
|
|
87
|
-
//# sourceMappingURL=binding.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"binding.js","sourceRoot":"","sources":["../../src/server/binding.ts"],"names":[],"mappings":"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"}
|
package/dist/server/step-up.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Hono } from 'hono';
|
|
2
|
-
import type { TokenForgeServerOptions } from '../shared/types.js';
|
|
3
|
-
interface Variables {
|
|
4
|
-
userId: string;
|
|
5
|
-
sessionId: string;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* Step-up authentication routes for TokenForge.
|
|
9
|
-
* Mount at /api/tf/step-up on the main Hono app.
|
|
10
|
-
* @param options - Server options (storage, email sender, passkey verifier).
|
|
11
|
-
* @returns A Hono sub-app with /initiate and /complete routes.
|
|
12
|
-
*/
|
|
13
|
-
export declare function createStepUpRoutes(options: TokenForgeServerOptions): Hono<{
|
|
14
|
-
Variables: Variables;
|
|
15
|
-
}>;
|
|
16
|
-
/** @deprecated Use createStepUpRoutes(options) instead */
|
|
17
|
-
export declare const stepUpRoutes: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
18
|
-
export {};
|
|
19
|
-
//# sourceMappingURL=step-up.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"step-up.d.ts","sourceRoot":"","sources":["../../src/server/step-up.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAIlE,UAAU,SAAS;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAMD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,uBAAuB,GAC/B,IAAI,CAAC;IAAE,SAAS,EAAE,SAAS,CAAA;CAAE,CAAC,CAiGhC;AA8DD,0DAA0D;AAC1D,eAAO,MAAM,YAAY,4EAAa,CAAC"}
|
package/dist/server/step-up.js
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { Hono } from 'hono';
|
|
2
|
-
import { verifyTotp } from './totp.js';
|
|
3
|
-
const MAX_CHALLENGES_PER_WINDOW = 5;
|
|
4
|
-
const CHALLENGE_WINDOW_MINUTES = 15;
|
|
5
|
-
const CHALLENGE_TTL_SECONDS = 300; // 5 minutes
|
|
6
|
-
/**
|
|
7
|
-
* Step-up authentication routes for TokenForge.
|
|
8
|
-
* Mount at /api/tf/step-up on the main Hono app.
|
|
9
|
-
* @param options - Server options (storage, email sender, passkey verifier).
|
|
10
|
-
* @returns A Hono sub-app with /initiate and /complete routes.
|
|
11
|
-
*/
|
|
12
|
-
export function createStepUpRoutes(options) {
|
|
13
|
-
const stepUp = new Hono();
|
|
14
|
-
stepUp.post('/initiate', async (c) => {
|
|
15
|
-
const userId = c.get('userId');
|
|
16
|
-
const sessionId = c.get('sessionId');
|
|
17
|
-
if (!userId || !sessionId)
|
|
18
|
-
return c.json({ error: 'unauthorized' }, 401);
|
|
19
|
-
const count = await options.storage.countRecentChallenges(userId, CHALLENGE_WINDOW_MINUTES);
|
|
20
|
-
if (count >= MAX_CHALLENGES_PER_WINDOW) {
|
|
21
|
-
return c.json({ error: 'rate_limited', message: 'Too many step-up attempts. Try again later.' }, 429);
|
|
22
|
-
}
|
|
23
|
-
const { method } = await c.req.json();
|
|
24
|
-
const challengeMethod = (method || 'totp');
|
|
25
|
-
const challengeId = crypto.randomUUID();
|
|
26
|
-
const now = new Date();
|
|
27
|
-
const expiresAt = new Date(now.getTime() + CHALLENGE_TTL_SECONDS * 1000).toISOString();
|
|
28
|
-
const challenge = {
|
|
29
|
-
id: challengeId, sessionId, userId,
|
|
30
|
-
reason: 'trust_score_drop', method: challengeMethod,
|
|
31
|
-
status: 'pending', expiresAt,
|
|
32
|
-
createdAt: now.toISOString(), completedAt: null,
|
|
33
|
-
};
|
|
34
|
-
await options.storage.createChallenge(challenge);
|
|
35
|
-
if (challengeMethod === 'email_otp') {
|
|
36
|
-
const otp = generateOtp();
|
|
37
|
-
await options.storage.storeOtp(challengeId, otp, CHALLENGE_TTL_SECONDS);
|
|
38
|
-
if (options.sendEmail) {
|
|
39
|
-
await options.sendEmail(userId, otp);
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
throw new Error('sendEmail handler not configured — cannot deliver OTP');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (challengeMethod === 'passkey') {
|
|
46
|
-
// Generate WebAuthn challenge and return it
|
|
47
|
-
const webauthnChallenge = generateWebAuthnChallenge();
|
|
48
|
-
return c.json({ challengeId, method: challengeMethod, expiresAt, webauthnChallenge });
|
|
49
|
-
}
|
|
50
|
-
return c.json({ challengeId, method: challengeMethod, expiresAt });
|
|
51
|
-
});
|
|
52
|
-
stepUp.post('/complete', async (c) => {
|
|
53
|
-
const userId = c.get('userId');
|
|
54
|
-
if (!userId)
|
|
55
|
-
return c.json({ error: 'unauthorized' }, 401);
|
|
56
|
-
const { challengeId, code, credential } = await c.req.json();
|
|
57
|
-
const challenge = await options.storage.getChallenge(challengeId, userId);
|
|
58
|
-
if (!challenge)
|
|
59
|
-
return c.json({ error: 'invalid_challenge' }, 400);
|
|
60
|
-
if (new Date(challenge.expiresAt) < new Date()) {
|
|
61
|
-
await options.storage.updateChallengeStatus(challengeId, 'expired');
|
|
62
|
-
return c.json({ error: 'challenge_expired' }, 400);
|
|
63
|
-
}
|
|
64
|
-
let verified = false;
|
|
65
|
-
if (challenge.method === 'email_otp') {
|
|
66
|
-
const storedOtp = await options.storage.getOtp(challengeId);
|
|
67
|
-
verified = storedOtp !== null && storedOtp === code;
|
|
68
|
-
if (verified)
|
|
69
|
-
await options.storage.deleteOtp(challengeId);
|
|
70
|
-
}
|
|
71
|
-
else if (challenge.method === 'totp' && code) {
|
|
72
|
-
verified = await verifyTotp(code, userId, options);
|
|
73
|
-
}
|
|
74
|
-
else if (challenge.method === 'passkey' && credential) {
|
|
75
|
-
const storedKey = options.getPasskeyPublicKey
|
|
76
|
-
? await options.getPasskeyPublicKey(userId, credential.id)
|
|
77
|
-
: null;
|
|
78
|
-
if (storedKey) {
|
|
79
|
-
verified = await verifyPasskeyCredential(credential, storedKey, challengeId);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (!verified) {
|
|
83
|
-
await options.storage.updateChallengeStatus(challengeId, 'failed');
|
|
84
|
-
return c.json({ error: 'verification_failed' }, 401);
|
|
85
|
-
}
|
|
86
|
-
await options.storage.updateChallengeStatus(challengeId, 'completed', new Date().toISOString());
|
|
87
|
-
const deviceId = c.req.header('X-TF-Device-ID');
|
|
88
|
-
if (deviceId) {
|
|
89
|
-
await options.storage.restoreTrust(deviceId, userId);
|
|
90
|
-
}
|
|
91
|
-
return c.json({ verified: true, trustScore: 100 });
|
|
92
|
-
});
|
|
93
|
-
return stepUp;
|
|
94
|
-
}
|
|
95
|
-
/** Generate a 6-digit OTP using CSPRNG */
|
|
96
|
-
function generateOtp() {
|
|
97
|
-
const randomBytes = new Uint32Array(1);
|
|
98
|
-
crypto.getRandomValues(randomBytes);
|
|
99
|
-
return ((randomBytes[0] % 900000) + 100000).toString();
|
|
100
|
-
}
|
|
101
|
-
/** Generate a WebAuthn challenge (32 random bytes, base64url encoded) */
|
|
102
|
-
function generateWebAuthnChallenge() {
|
|
103
|
-
const bytes = new Uint8Array(32);
|
|
104
|
-
crypto.getRandomValues(bytes);
|
|
105
|
-
return btoa(String.fromCharCode(...bytes))
|
|
106
|
-
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Verify a WebAuthn passkey credential against the stored public key.
|
|
110
|
-
* Uses ECDSA P-256 signature verification via Web Crypto API.
|
|
111
|
-
*/
|
|
112
|
-
async function verifyPasskeyCredential(credential, publicKeySpki, expectedChallenge) {
|
|
113
|
-
try {
|
|
114
|
-
if (!credential.id || !credential.response?.signature || !credential.response?.authenticatorData) {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
const clientDataJSON = atob(credential.response.clientDataJSON);
|
|
118
|
-
const clientData = JSON.parse(clientDataJSON);
|
|
119
|
-
if (clientData.type !== 'webauthn.get')
|
|
120
|
-
return false;
|
|
121
|
-
const decodedChallenge = atob(clientData.challenge?.replace(/-/g, '+').replace(/_/g, '/') ?? '');
|
|
122
|
-
if (decodedChallenge !== expectedChallenge)
|
|
123
|
-
return false;
|
|
124
|
-
const key = await crypto.subtle.importKey('spki', publicKeySpki, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
|
|
125
|
-
const authData = Uint8Array.from(atob(credential.response.authenticatorData), (ch) => ch.charCodeAt(0));
|
|
126
|
-
const clientDataHash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(clientDataJSON));
|
|
127
|
-
const signedData = new Uint8Array(authData.length + clientDataHash.byteLength);
|
|
128
|
-
signedData.set(authData, 0);
|
|
129
|
-
signedData.set(new Uint8Array(clientDataHash), authData.length);
|
|
130
|
-
const sigBytes = Uint8Array.from(atob(credential.response.signature), (ch) => ch.charCodeAt(0));
|
|
131
|
-
return await crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, key, sigBytes, signedData);
|
|
132
|
-
}
|
|
133
|
-
catch {
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
/** @deprecated Use createStepUpRoutes(options) instead */
|
|
138
|
-
export const stepUpRoutes = new Hono();
|
|
139
|
-
//# sourceMappingURL=step-up.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"step-up.js","sourceRoot":"","sources":["../../src/server/step-up.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/storage/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/server/storage/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import type { DeviceSession, SecurityEvent } from '../../shared/types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Storage interface for TokenForge server.
|
|
4
|
-
* Implement this to use any database/cache backend.
|
|
5
|
-
*/
|
|
6
|
-
export interface TokenForgeStorage {
|
|
7
|
-
/** Find an active (non-revoked) session by sessionId + deviceId */
|
|
8
|
-
getSession(sessionId: string, deviceId: string): Promise<DeviceSession | null>;
|
|
9
|
-
/** Create a new device session */
|
|
10
|
-
createSession(session: DeviceSession): Promise<void>;
|
|
11
|
-
/** Update trust score and last_verified_at for a session */
|
|
12
|
-
updateTrustScore(deviceId: string, trustScore: number): Promise<void>;
|
|
13
|
-
/** Mark a session as revoked */
|
|
14
|
-
revokeSession(deviceId: string, reason: string): Promise<void>;
|
|
15
|
-
/** Revoke all sessions for a user (called before re-binding) */
|
|
16
|
-
revokeUserSessions(userId: string): Promise<void>;
|
|
17
|
-
/** List a user's sessions (most recent first) */
|
|
18
|
-
listUserSessions(userId: string, limit: number): Promise<DeviceSession[]>;
|
|
19
|
-
/** Restore trust score to 100 after step-up */
|
|
20
|
-
restoreTrust(deviceId: string, userId: string): Promise<void>;
|
|
21
|
-
/** Check if nonce has been used */
|
|
22
|
-
hasNonce(nonce: string): Promise<boolean>;
|
|
23
|
-
/** Store a used nonce with TTL in seconds */
|
|
24
|
-
storeNonce(nonce: string, ttlSeconds: number): Promise<void>;
|
|
25
|
-
/** Insert a security event record */
|
|
26
|
-
logEvent(event: SecurityEvent & {
|
|
27
|
-
id: string;
|
|
28
|
-
}): Promise<void>;
|
|
29
|
-
/** List security events for a user (for dashboard) */
|
|
30
|
-
listEvents(userId: string, limit: number, offset: number): Promise<(SecurityEvent & {
|
|
31
|
-
id: string;
|
|
32
|
-
created_at: string;
|
|
33
|
-
})[]>;
|
|
34
|
-
/** Create a step-up challenge */
|
|
35
|
-
createChallenge(challenge: StepUpChallengeRecord): Promise<void>;
|
|
36
|
-
/** Get a pending challenge by ID + userId */
|
|
37
|
-
getChallenge(challengeId: string, userId: string): Promise<StepUpChallengeRecord | null>;
|
|
38
|
-
/** Update challenge status */
|
|
39
|
-
updateChallengeStatus(challengeId: string, status: string, completedAt?: string): Promise<void>;
|
|
40
|
-
/** Count recent challenges for rate limiting */
|
|
41
|
-
countRecentChallenges(userId: string, windowMinutes: number): Promise<number>;
|
|
42
|
-
/** Store OTP code for a challenge with TTL */
|
|
43
|
-
storeOtp(challengeId: string, code: string, ttlSeconds: number): Promise<void>;
|
|
44
|
-
/** Retrieve and consume OTP code */
|
|
45
|
-
getOtp(challengeId: string): Promise<string | null>;
|
|
46
|
-
/** Delete OTP after verification */
|
|
47
|
-
deleteOtp(challengeId: string): Promise<void>;
|
|
48
|
-
}
|
|
49
|
-
/** Step-up challenge stored in database */
|
|
50
|
-
export interface StepUpChallengeRecord {
|
|
51
|
-
id: string;
|
|
52
|
-
sessionId: string;
|
|
53
|
-
userId: string;
|
|
54
|
-
reason: string;
|
|
55
|
-
method: 'totp' | 'email_otp' | 'passkey';
|
|
56
|
-
status: 'pending' | 'completed' | 'expired' | 'failed';
|
|
57
|
-
expiresAt: string;
|
|
58
|
-
createdAt: string;
|
|
59
|
-
completedAt: string | null;
|
|
60
|
-
}
|
|
61
|
-
//# sourceMappingURL=interface.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../../src/server/storage/interface.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"interface.js","sourceRoot":"","sources":["../../../src/server/storage/interface.ts"],"names":[],"mappings":""}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import type { TokenForgeStorage, StepUpChallengeRecord } from './interface.js';
|
|
2
|
-
import type { DeviceSession, SecurityEvent } from '../../shared/types.js';
|
|
3
|
-
/**
|
|
4
|
-
* In-memory storage for testing and development.
|
|
5
|
-
* Not suitable for production — data is lost on restart.
|
|
6
|
-
*/
|
|
7
|
-
export declare class MemoryStorage implements TokenForgeStorage {
|
|
8
|
-
sessions: DeviceSession[];
|
|
9
|
-
events: (SecurityEvent & {
|
|
10
|
-
id: string;
|
|
11
|
-
created_at: string;
|
|
12
|
-
})[];
|
|
13
|
-
challenges: StepUpChallengeRecord[];
|
|
14
|
-
private nonces;
|
|
15
|
-
private otps;
|
|
16
|
-
getSession(sessionId: string, deviceId: string): Promise<DeviceSession | null>;
|
|
17
|
-
createSession(session: DeviceSession): Promise<void>;
|
|
18
|
-
updateTrustScore(deviceId: string, trustScore: number): Promise<void>;
|
|
19
|
-
revokeSession(deviceId: string, reason: string): Promise<void>;
|
|
20
|
-
revokeUserSessions(userId: string): Promise<void>;
|
|
21
|
-
listUserSessions(userId: string, limit: number): Promise<DeviceSession[]>;
|
|
22
|
-
restoreTrust(deviceId: string, userId: string): Promise<void>;
|
|
23
|
-
hasNonce(nonce: string): Promise<boolean>;
|
|
24
|
-
storeNonce(nonce: string, ttlSeconds: number): Promise<void>;
|
|
25
|
-
logEvent(event: SecurityEvent & {
|
|
26
|
-
id: string;
|
|
27
|
-
}): Promise<void>;
|
|
28
|
-
listEvents(userId: string, limit: number, offset: number): Promise<(SecurityEvent & {
|
|
29
|
-
id: string;
|
|
30
|
-
created_at: string;
|
|
31
|
-
})[]>;
|
|
32
|
-
createChallenge(challenge: StepUpChallengeRecord): Promise<void>;
|
|
33
|
-
getChallenge(challengeId: string, userId: string): Promise<StepUpChallengeRecord | null>;
|
|
34
|
-
updateChallengeStatus(challengeId: string, status: string, completedAt?: string): Promise<void>;
|
|
35
|
-
countRecentChallenges(userId: string, windowMinutes: number): Promise<number>;
|
|
36
|
-
storeOtp(challengeId: string, code: string, ttlSeconds: number): Promise<void>;
|
|
37
|
-
getOtp(challengeId: string): Promise<string | null>;
|
|
38
|
-
deleteOtp(challengeId: string): Promise<void>;
|
|
39
|
-
}
|
|
40
|
-
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../../src/server/storage/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC/E,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE1E;;;GAGG;AACH,qBAAa,aAAc,YAAW,iBAAiB;IACrD,QAAQ,EAAE,aAAa,EAAE,CAAM;IAC/B,MAAM,EAAE,CAAC,aAAa,GAAG;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,CAAM;IACpE,UAAU,EAAE,qBAAqB,EAAE,CAAM;IACzC,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,IAAI,CAA0D;IAEhE,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAM9E,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpD,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQrE,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ9D,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASjD,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAOzE,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK7D,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUzC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5D,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9D,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,aAAa,GAAG;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,CAAC;IAO1H,eAAe,CAAC,SAAS,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhE,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC;IAMxF,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ/F,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAO7E,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9E,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAUnD,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGpD"}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* In-memory storage for testing and development.
|
|
3
|
-
* Not suitable for production — data is lost on restart.
|
|
4
|
-
*/
|
|
5
|
-
export class MemoryStorage {
|
|
6
|
-
sessions = [];
|
|
7
|
-
events = [];
|
|
8
|
-
challenges = [];
|
|
9
|
-
nonces = new Map(); // nonce → expiry timestamp
|
|
10
|
-
otps = new Map();
|
|
11
|
-
async getSession(sessionId, deviceId) {
|
|
12
|
-
return this.sessions.find((s) => s.session_id === sessionId && s.id === deviceId && s.revoked === 0) ?? null;
|
|
13
|
-
}
|
|
14
|
-
async createSession(session) {
|
|
15
|
-
this.sessions.push({ ...session });
|
|
16
|
-
}
|
|
17
|
-
async updateTrustScore(deviceId, trustScore) {
|
|
18
|
-
const s = this.sessions.find((s) => s.id === deviceId);
|
|
19
|
-
if (s) {
|
|
20
|
-
s.trust_score = trustScore;
|
|
21
|
-
s.last_verified_at = new Date().toISOString();
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
async revokeSession(deviceId, reason) {
|
|
25
|
-
const s = this.sessions.find((s) => s.id === deviceId);
|
|
26
|
-
if (s) {
|
|
27
|
-
s.revoked = 1;
|
|
28
|
-
s.revoked_reason = reason;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
async revokeUserSessions(userId) {
|
|
32
|
-
for (const s of this.sessions) {
|
|
33
|
-
if (s.user_id === userId && s.revoked === 0) {
|
|
34
|
-
s.revoked = 1;
|
|
35
|
-
s.revoked_reason = 'rebind';
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
async listUserSessions(userId, limit) {
|
|
40
|
-
return this.sessions
|
|
41
|
-
.filter((s) => s.user_id === userId)
|
|
42
|
-
.sort((a, b) => b.bound_at.localeCompare(a.bound_at))
|
|
43
|
-
.slice(0, limit);
|
|
44
|
-
}
|
|
45
|
-
async restoreTrust(deviceId, userId) {
|
|
46
|
-
const s = this.sessions.find((s) => s.id === deviceId && s.user_id === userId);
|
|
47
|
-
if (s)
|
|
48
|
-
s.trust_score = 100;
|
|
49
|
-
}
|
|
50
|
-
async hasNonce(nonce) {
|
|
51
|
-
const expiry = this.nonces.get(nonce);
|
|
52
|
-
if (!expiry)
|
|
53
|
-
return false;
|
|
54
|
-
if (Date.now() > expiry) {
|
|
55
|
-
this.nonces.delete(nonce);
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
async storeNonce(nonce, ttlSeconds) {
|
|
61
|
-
this.nonces.set(nonce, Date.now() + ttlSeconds * 1000);
|
|
62
|
-
}
|
|
63
|
-
async logEvent(event) {
|
|
64
|
-
this.events.push({ ...event, created_at: new Date().toISOString() });
|
|
65
|
-
}
|
|
66
|
-
async listEvents(userId, limit, offset) {
|
|
67
|
-
return this.events
|
|
68
|
-
.filter((e) => e.userId === userId)
|
|
69
|
-
.sort((a, b) => b.created_at.localeCompare(a.created_at))
|
|
70
|
-
.slice(offset, offset + limit);
|
|
71
|
-
}
|
|
72
|
-
async createChallenge(challenge) {
|
|
73
|
-
this.challenges.push({ ...challenge });
|
|
74
|
-
}
|
|
75
|
-
async getChallenge(challengeId, userId) {
|
|
76
|
-
return this.challenges.find((c) => c.id === challengeId && c.userId === userId && c.status === 'pending') ?? null;
|
|
77
|
-
}
|
|
78
|
-
async updateChallengeStatus(challengeId, status, completedAt) {
|
|
79
|
-
const c = this.challenges.find((c) => c.id === challengeId);
|
|
80
|
-
if (c) {
|
|
81
|
-
c.status = status;
|
|
82
|
-
if (completedAt)
|
|
83
|
-
c.completedAt = completedAt;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
async countRecentChallenges(userId, windowMinutes) {
|
|
87
|
-
const cutoff = new Date(Date.now() - windowMinutes * 60 * 1000).toISOString();
|
|
88
|
-
return this.challenges.filter((c) => c.userId === userId && c.createdAt > cutoff).length;
|
|
89
|
-
}
|
|
90
|
-
async storeOtp(challengeId, code, ttlSeconds) {
|
|
91
|
-
this.otps.set(challengeId, { code, expiresAt: Date.now() + ttlSeconds * 1000 });
|
|
92
|
-
}
|
|
93
|
-
async getOtp(challengeId) {
|
|
94
|
-
const entry = this.otps.get(challengeId);
|
|
95
|
-
if (!entry)
|
|
96
|
-
return null;
|
|
97
|
-
if (Date.now() > entry.expiresAt) {
|
|
98
|
-
this.otps.delete(challengeId);
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
return entry.code;
|
|
102
|
-
}
|
|
103
|
-
async deleteOtp(challengeId) {
|
|
104
|
-
this.otps.delete(challengeId);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
//# sourceMappingURL=memory.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../../src/server/storage/memory.ts"],"names":[],"mappings":"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"}
|
package/dist/server/totp.d.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import type { TokenForgeServerOptions } from '../shared/types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Callback surface for TOTP attempt rate-limiting. Callers plug in a
|
|
4
|
-
* storage-backed counter (KV, Redis, D1); when absent, lockout is
|
|
5
|
-
* disabled and the caller MUST enforce rate-limiting elsewhere.
|
|
6
|
-
*
|
|
7
|
-
* - `getFailedAttempts(userId)` returns the number of failed TOTP
|
|
8
|
-
* verifications for the user within the current lockout window.
|
|
9
|
-
* - `recordFailedAttempt(userId)` atomically increments the counter
|
|
10
|
-
* and sets a TTL of `windowSeconds`.
|
|
11
|
-
* - `resetFailedAttempts(userId)` clears the counter on a successful
|
|
12
|
-
* verification.
|
|
13
|
-
*/
|
|
14
|
-
export interface TotpRateLimiter {
|
|
15
|
-
getFailedAttempts(userId: string): Promise<number>;
|
|
16
|
-
recordFailedAttempt(userId: string, windowSeconds: number): Promise<void>;
|
|
17
|
-
resetFailedAttempts(userId: string): Promise<void>;
|
|
18
|
-
}
|
|
19
|
-
export declare const TOTP_MAX_ATTEMPTS = 5;
|
|
20
|
-
export declare const TOTP_LOCKOUT_WINDOW_SECONDS = 300;
|
|
21
|
-
export declare class TotpLockedOutError extends Error {
|
|
22
|
-
readonly retryAfterSeconds: number;
|
|
23
|
-
constructor(retryAfterSeconds: number);
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* TOTP verification (RFC 6238) using HMAC-SHA1.
|
|
27
|
-
* Time step: 30 seconds, window: +/-1 step tolerance.
|
|
28
|
-
*
|
|
29
|
-
* Rate-limiting: when `options.rateLimiter` is provided, the user is
|
|
30
|
-
* locked out for TOTP_LOCKOUT_WINDOW_SECONDS after TOTP_MAX_ATTEMPTS
|
|
31
|
-
* failures. A TotpLockedOutError is thrown while locked so callers can
|
|
32
|
-
* return a distinct 429 to the client instead of a generic "invalid
|
|
33
|
-
* code". A successful verification resets the counter immediately.
|
|
34
|
-
*
|
|
35
|
-
* @param code - The 6-digit TOTP code from the user.
|
|
36
|
-
* @param userId - User whose TOTP secret to look up.
|
|
37
|
-
* @param options - Server options; must include `getTotpSecret` callback.
|
|
38
|
-
* @returns True if the code is valid for the current or adjacent time window.
|
|
39
|
-
* @throws TotpLockedOutError if the user is currently locked out.
|
|
40
|
-
*/
|
|
41
|
-
export declare function verifyTotp(code: string, userId: string, options: TokenForgeServerOptions & {
|
|
42
|
-
getTotpSecret?: (userId: string) => Promise<string | null>;
|
|
43
|
-
rateLimiter?: TotpRateLimiter;
|
|
44
|
-
}): Promise<boolean>;
|
|
45
|
-
/**
|
|
46
|
-
* Generate a TOTP code for a given counter value.
|
|
47
|
-
* Uses HMAC-SHA1 per RFC 4226, with 6-digit output.
|
|
48
|
-
* @param secretBase32 - Base32-encoded shared secret.
|
|
49
|
-
* @param counter - HMAC counter value (typically `floor(time / 30)`).
|
|
50
|
-
* @returns A 6-digit zero-padded TOTP code.
|
|
51
|
-
*/
|
|
52
|
-
export declare function generateTotp(secretBase32: string, counter: number): Promise<string>;
|
|
53
|
-
//# sourceMappingURL=totp.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"totp.d.ts","sourceRoot":"","sources":["../../src/server/totp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAElE;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,eAAe;IAC9B,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACnD,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpD;AAED,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C,qBAAa,kBAAmB,SAAQ,KAAK;IAC/B,QAAQ,CAAC,iBAAiB,EAAE,MAAM;gBAAzB,iBAAiB,EAAE,MAAM;CAI/C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,uBAAuB,GAAG;IACjC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC3D,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B,GACA,OAAO,CAAC,OAAO,CAAC,CAgClB;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAqBzF"}
|
package/dist/server/totp.js
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
export const TOTP_MAX_ATTEMPTS = 5;
|
|
2
|
-
export const TOTP_LOCKOUT_WINDOW_SECONDS = 300; // 5 min — matches the OWASP ASVS V6.2.6 guidance
|
|
3
|
-
export class TotpLockedOutError extends Error {
|
|
4
|
-
retryAfterSeconds;
|
|
5
|
-
constructor(retryAfterSeconds) {
|
|
6
|
-
super(`Too many failed TOTP attempts; locked out for ${retryAfterSeconds}s`);
|
|
7
|
-
this.retryAfterSeconds = retryAfterSeconds;
|
|
8
|
-
this.name = 'TotpLockedOutError';
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* TOTP verification (RFC 6238) using HMAC-SHA1.
|
|
13
|
-
* Time step: 30 seconds, window: +/-1 step tolerance.
|
|
14
|
-
*
|
|
15
|
-
* Rate-limiting: when `options.rateLimiter` is provided, the user is
|
|
16
|
-
* locked out for TOTP_LOCKOUT_WINDOW_SECONDS after TOTP_MAX_ATTEMPTS
|
|
17
|
-
* failures. A TotpLockedOutError is thrown while locked so callers can
|
|
18
|
-
* return a distinct 429 to the client instead of a generic "invalid
|
|
19
|
-
* code". A successful verification resets the counter immediately.
|
|
20
|
-
*
|
|
21
|
-
* @param code - The 6-digit TOTP code from the user.
|
|
22
|
-
* @param userId - User whose TOTP secret to look up.
|
|
23
|
-
* @param options - Server options; must include `getTotpSecret` callback.
|
|
24
|
-
* @returns True if the code is valid for the current or adjacent time window.
|
|
25
|
-
* @throws TotpLockedOutError if the user is currently locked out.
|
|
26
|
-
*/
|
|
27
|
-
export async function verifyTotp(code, userId, options) {
|
|
28
|
-
if (!options.getTotpSecret)
|
|
29
|
-
return false;
|
|
30
|
-
const limiter = options.rateLimiter;
|
|
31
|
-
if (limiter) {
|
|
32
|
-
const attempts = await limiter.getFailedAttempts(userId);
|
|
33
|
-
if (attempts >= TOTP_MAX_ATTEMPTS) {
|
|
34
|
-
throw new TotpLockedOutError(TOTP_LOCKOUT_WINDOW_SECONDS);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
const secret = await options.getTotpSecret(userId);
|
|
38
|
-
if (!secret) {
|
|
39
|
-
if (limiter)
|
|
40
|
-
await limiter.recordFailedAttempt(userId, TOTP_LOCKOUT_WINDOW_SECONDS);
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
const now = Math.floor(Date.now() / 1000);
|
|
44
|
-
const timeStep = 30;
|
|
45
|
-
// Check current window and ±1 step for clock skew tolerance
|
|
46
|
-
for (const offset of [-1, 0, 1]) {
|
|
47
|
-
const counter = Math.floor((now + offset * timeStep) / timeStep);
|
|
48
|
-
const expected = await generateTotp(secret, counter);
|
|
49
|
-
if (timingSafeEqual(code, expected)) {
|
|
50
|
-
if (limiter)
|
|
51
|
-
await limiter.resetFailedAttempts(userId);
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
if (limiter)
|
|
56
|
-
await limiter.recordFailedAttempt(userId, TOTP_LOCKOUT_WINDOW_SECONDS);
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Generate a TOTP code for a given counter value.
|
|
61
|
-
* Uses HMAC-SHA1 per RFC 4226, with 6-digit output.
|
|
62
|
-
* @param secretBase32 - Base32-encoded shared secret.
|
|
63
|
-
* @param counter - HMAC counter value (typically `floor(time / 30)`).
|
|
64
|
-
* @returns A 6-digit zero-padded TOTP code.
|
|
65
|
-
*/
|
|
66
|
-
export async function generateTotp(secretBase32, counter) {
|
|
67
|
-
const keyBytes = base32Decode(secretBase32);
|
|
68
|
-
const counterBytes = new ArrayBuffer(8);
|
|
69
|
-
const view = new DataView(counterBytes);
|
|
70
|
-
view.setBigUint64(0, BigInt(counter));
|
|
71
|
-
const key = await crypto.subtle.importKey('raw', keyBytes.buffer, { name: 'HMAC', hash: 'SHA-1' }, false, ['sign']);
|
|
72
|
-
const hmac = await crypto.subtle.sign('HMAC', key, counterBytes);
|
|
73
|
-
const hmacArray = new Uint8Array(hmac);
|
|
74
|
-
// Dynamic truncation (RFC 4226 section 5.3)
|
|
75
|
-
const offset = hmacArray[hmacArray.length - 1] & 0x0f;
|
|
76
|
-
const binary = ((hmacArray[offset] & 0x7f) << 24) |
|
|
77
|
-
((hmacArray[offset + 1] & 0xff) << 16) |
|
|
78
|
-
((hmacArray[offset + 2] & 0xff) << 8) |
|
|
79
|
-
(hmacArray[offset + 3] & 0xff);
|
|
80
|
-
return (binary % 1000000).toString().padStart(6, '0');
|
|
81
|
-
}
|
|
82
|
-
/** Decode a base32-encoded string (RFC 4648, no padding required) */
|
|
83
|
-
function base32Decode(input) {
|
|
84
|
-
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
|
85
|
-
const clean = input.replace(/[= ]/g, '').toUpperCase();
|
|
86
|
-
const bits = [];
|
|
87
|
-
for (const char of clean) {
|
|
88
|
-
const val = alphabet.indexOf(char);
|
|
89
|
-
if (val === -1)
|
|
90
|
-
continue;
|
|
91
|
-
for (let i = 4; i >= 0; i--) {
|
|
92
|
-
bits.push((val >> i) & 1);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
const bytes = new Uint8Array(Math.floor(bits.length / 8));
|
|
96
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
97
|
-
let byte = 0;
|
|
98
|
-
for (let j = 0; j < 8; j++) {
|
|
99
|
-
byte = (byte << 1) | (bits[i * 8 + j] ?? 0);
|
|
100
|
-
}
|
|
101
|
-
bytes[i] = byte;
|
|
102
|
-
}
|
|
103
|
-
return bytes;
|
|
104
|
-
}
|
|
105
|
-
/** Constant-time string comparison to prevent timing attacks */
|
|
106
|
-
function timingSafeEqual(a, b) {
|
|
107
|
-
const maxLen = Math.max(a.length, b.length);
|
|
108
|
-
let result = a.length ^ b.length; // length mismatch contributes to failure
|
|
109
|
-
for (let i = 0; i < maxLen; i++) {
|
|
110
|
-
result |= (a.charCodeAt(i) || 0) ^ (b.charCodeAt(i) || 0);
|
|
111
|
-
}
|
|
112
|
-
return result === 0;
|
|
113
|
-
}
|
|
114
|
-
//# sourceMappingURL=totp.js.map
|
package/dist/server/totp.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"totp.js","sourceRoot":"","sources":["../../src/server/totp.ts"],"names":[],"mappings":"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"}
|