@opensyber/tokenforge 0.1.2 → 1.1.0
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/LICENSE.md +32 -0
- package/README.md +329 -52
- package/dist/adapters/astro.d.ts +64 -0
- package/dist/adapters/astro.d.ts.map +1 -0
- package/dist/adapters/astro.js +104 -0
- package/dist/adapters/astro.js.map +1 -0
- package/dist/adapters/cloud-v2-bridge.d.ts +8 -0
- package/dist/adapters/cloud-v2-bridge.d.ts.map +1 -0
- package/dist/adapters/cloud-v2-bridge.js +24 -0
- package/dist/adapters/cloud-v2-bridge.js.map +1 -0
- package/dist/adapters/express.d.ts +30 -2
- package/dist/adapters/express.d.ts.map +1 -1
- package/dist/adapters/express.js +65 -0
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/fastify.d.ts +32 -6
- package/dist/adapters/fastify.d.ts.map +1 -1
- package/dist/adapters/fastify.js +65 -0
- package/dist/adapters/fastify.js.map +1 -1
- package/dist/adapters/hono.d.ts +13 -1
- package/dist/adapters/hono.d.ts.map +1 -1
- package/dist/adapters/hono.js +14 -1
- package/dist/adapters/hono.js.map +1 -1
- package/dist/adapters/index.d.ts +5 -3
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +5 -3
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/nextjs.d.ts +31 -0
- package/dist/adapters/nextjs.d.ts.map +1 -1
- package/dist/adapters/nextjs.js +83 -30
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/sveltekit.d.ts +66 -0
- package/dist/adapters/sveltekit.d.ts.map +1 -0
- package/dist/adapters/sveltekit.js +101 -0
- package/dist/adapters/sveltekit.js.map +1 -0
- package/dist/client/action-signer.d.ts +53 -0
- package/dist/client/action-signer.d.ts.map +1 -0
- package/dist/client/action-signer.js +48 -0
- package/dist/client/action-signer.js.map +1 -0
- package/dist/client/binding.d.ts +24 -7
- package/dist/client/binding.d.ts.map +1 -1
- package/dist/client/binding.js +17 -6
- package/dist/client/binding.js.map +1 -1
- package/dist/client/cloud-device.d.ts +54 -0
- package/dist/client/cloud-device.d.ts.map +1 -0
- package/dist/client/cloud-device.js +301 -0
- package/dist/client/cloud-device.js.map +1 -0
- package/dist/client/crypto.d.ts.map +1 -1
- package/dist/client/crypto.js.map +1 -1
- package/dist/client/index.d.ts +14 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +30 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/interceptor.d.ts.map +1 -1
- package/dist/client/interceptor.js +27 -3
- package/dist/client/interceptor.js.map +1 -1
- package/dist/client/signer.d.ts +40 -3
- package/dist/client/signer.d.ts.map +1 -1
- package/dist/client/signer.js +56 -5
- package/dist/client/signer.js.map +1 -1
- package/dist/client/storage.d.ts.map +1 -1
- package/dist/client/storage.js.map +1 -1
- package/dist/client/webauthn.d.ts +45 -0
- package/dist/client/webauthn.d.ts.map +1 -0
- package/dist/client/webauthn.js +118 -0
- package/dist/client/webauthn.js.map +1 -0
- package/dist/protocol/v2.d.ts +23 -0
- package/dist/protocol/v2.d.ts.map +1 -0
- package/dist/protocol/v2.js +157 -0
- package/dist/protocol/v2.js.map +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/provider.d.ts.map +1 -1
- package/dist/react/provider.js.map +1 -1
- package/dist/server/action-verify.d.ts +56 -0
- package/dist/server/action-verify.d.ts.map +1 -0
- package/dist/server/action-verify.js +47 -0
- package/dist/server/action-verify.js.map +1 -0
- package/dist/server/activity-histogram.d.ts +47 -0
- package/dist/server/activity-histogram.d.ts.map +1 -0
- package/dist/server/activity-histogram.js +76 -0
- package/dist/server/activity-histogram.js.map +1 -0
- package/dist/server/aitm-heuristics.d.ts +57 -0
- package/dist/server/aitm-heuristics.d.ts.map +1 -0
- package/dist/server/aitm-heuristics.js +120 -0
- package/dist/server/aitm-heuristics.js.map +1 -0
- package/dist/server/bind-with-id-token.d.ts +55 -0
- package/dist/server/bind-with-id-token.d.ts.map +1 -0
- package/dist/server/bind-with-id-token.js +70 -0
- package/dist/server/bind-with-id-token.js.map +1 -0
- package/dist/server/bound-cookie.d.ts +39 -0
- package/dist/server/bound-cookie.d.ts.map +1 -0
- package/dist/server/bound-cookie.js +59 -0
- package/dist/server/bound-cookie.js.map +1 -0
- package/dist/server/cbor.d.ts +20 -0
- package/dist/server/cbor.d.ts.map +1 -0
- package/dist/server/cbor.js +110 -0
- package/dist/server/cbor.js.map +1 -0
- package/dist/server/cloud-bind.d.ts +42 -0
- package/dist/server/cloud-bind.d.ts.map +1 -0
- package/dist/server/cloud-bind.js +387 -0
- package/dist/server/cloud-bind.js.map +1 -0
- package/dist/server/cloud-middleware.d.ts +46 -0
- package/dist/server/cloud-middleware.d.ts.map +1 -0
- package/dist/server/cloud-middleware.js +344 -0
- package/dist/server/cloud-middleware.js.map +1 -0
- package/dist/server/cloud-verify.d.ts +30 -0
- package/dist/server/cloud-verify.d.ts.map +1 -0
- package/dist/server/cloud-verify.js +206 -0
- package/dist/server/cloud-verify.js.map +1 -0
- package/dist/server/dbsc-challenge.d.ts +51 -0
- package/dist/server/dbsc-challenge.d.ts.map +1 -0
- package/dist/server/dbsc-challenge.js +78 -0
- package/dist/server/dbsc-challenge.js.map +1 -0
- package/dist/server/dbsc-endpoint.d.ts +20 -0
- package/dist/server/dbsc-endpoint.d.ts.map +1 -0
- package/dist/server/dbsc-endpoint.js +85 -0
- package/dist/server/dbsc-endpoint.js.map +1 -0
- package/dist/server/dbsc-verify.d.ts +32 -0
- package/dist/server/dbsc-verify.d.ts.map +1 -0
- package/dist/server/dbsc-verify.js +65 -0
- package/dist/server/dbsc-verify.js.map +1 -0
- package/dist/server/identity-profile.d.ts +18 -0
- package/dist/server/identity-profile.d.ts.map +1 -0
- package/dist/server/identity-profile.js +113 -0
- package/dist/server/identity-profile.js.map +1 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +7 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/jws-verify.d.ts +46 -0
- package/dist/server/jws-verify.d.ts.map +1 -0
- package/dist/server/jws-verify.js +64 -0
- package/dist/server/jws-verify.js.map +1 -0
- package/dist/server/middleware.d.ts +24 -0
- package/dist/server/middleware.d.ts.map +1 -1
- package/dist/server/middleware.js +87 -20
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/oidc-verify.d.ts +71 -0
- package/dist/server/oidc-verify.d.ts.map +1 -0
- package/dist/server/oidc-verify.js +115 -0
- package/dist/server/oidc-verify.js.map +1 -0
- package/dist/server/policy.d.ts +47 -0
- package/dist/server/policy.d.ts.map +1 -0
- package/dist/server/policy.js +84 -0
- package/dist/server/policy.js.map +1 -0
- package/dist/server/step-up-policy.d.ts +36 -0
- package/dist/server/step-up-policy.d.ts.map +1 -0
- package/dist/server/step-up-policy.js +101 -0
- package/dist/server/step-up-policy.js.map +1 -0
- package/dist/server/storage/index.d.ts +3 -0
- package/dist/server/storage/index.d.ts.map +1 -0
- package/dist/server/storage/index.js +2 -0
- package/dist/server/storage/index.js.map +1 -0
- package/dist/server/storage/interface.d.ts +73 -0
- package/dist/server/storage/interface.d.ts.map +1 -0
- package/dist/server/storage/interface.js +2 -0
- package/dist/server/storage/interface.js.map +1 -0
- package/dist/server/storage/memory.d.ts +50 -0
- package/dist/server/storage/memory.d.ts.map +1 -0
- package/dist/server/storage/memory.js +121 -0
- package/dist/server/storage/memory.js.map +1 -0
- package/dist/server/webauthn-verify.d.ts +41 -0
- package/dist/server/webauthn-verify.d.ts.map +1 -0
- package/dist/server/webauthn-verify.js +175 -0
- package/dist/server/webauthn-verify.js.map +1 -0
- package/dist/server/webhooks.d.ts +42 -0
- package/dist/server/webhooks.d.ts.map +1 -0
- package/dist/server/webhooks.js +77 -0
- package/dist/server/webhooks.js.map +1 -0
- package/dist/shared/action-hash.d.ts +23 -0
- package/dist/shared/action-hash.d.ts.map +1 -0
- package/dist/shared/action-hash.js +35 -0
- package/dist/shared/action-hash.js.map +1 -0
- package/dist/shared/identity.d.ts +45 -0
- package/dist/shared/identity.d.ts.map +1 -0
- package/dist/shared/identity.js +176 -0
- package/dist/shared/identity.js.map +1 -0
- package/dist/shared/types-internal.d.ts.map +1 -1
- package/dist/shared/types-internal.js.map +1 -1
- package/dist/shared/types.d.ts +49 -2
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js +1 -0
- package/dist/shared/types.js.map +1 -1
- package/package.json +31 -33
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { PROTOCOL_V2 } from '../protocol/v2.js';
|
|
2
|
+
import { readTokenForgeProof, verifyTokenForgeCloudRequest, } from './cloud-verify.js';
|
|
3
|
+
const DEFAULT_API_URL = 'https://tokenforge-api.opensyber.cloud';
|
|
4
|
+
const DEFAULT_TIMEOUT_MS = 1_500;
|
|
5
|
+
const DEFAULT_MAX_RESPONSE_BYTES = 64 * 1024;
|
|
6
|
+
const DEFAULT_FAILURE_THRESHOLD = 3;
|
|
7
|
+
const DEFAULT_OPEN_INTERVAL_MS = 10_000;
|
|
8
|
+
const DEFAULT_MAX_OPEN_INTERVAL_MS = 5 * 60_000;
|
|
9
|
+
const DEVICE_PATTERN = /^[A-Za-z0-9_.:-]{8,256}$/;
|
|
10
|
+
class CloudVerifyFailure extends Error {
|
|
11
|
+
kind;
|
|
12
|
+
code;
|
|
13
|
+
constructor(kind, code) {
|
|
14
|
+
super(`TokenForge Cloud verification ${kind}`);
|
|
15
|
+
this.kind = kind;
|
|
16
|
+
this.code = code;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Compose local verification with TokenForge Cloud verification.
|
|
21
|
+
* Local denial always wins. Monitor observes cloud decisions; enforce requires
|
|
22
|
+
* both local and cloud acceptance.
|
|
23
|
+
*/
|
|
24
|
+
export function createTokenForgeCloudMiddleware(options) {
|
|
25
|
+
const mode = normalizeMode(options.mode);
|
|
26
|
+
const apiUrl = normalizeApiUrl(options.apiUrl ?? DEFAULT_API_URL);
|
|
27
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
28
|
+
const timeoutMs = positiveInteger(options.requestTimeoutMs, DEFAULT_TIMEOUT_MS);
|
|
29
|
+
const maxResponseBytes = positiveInteger(options.maxResponseBytes, DEFAULT_MAX_RESPONSE_BYTES);
|
|
30
|
+
const skipPaths = exactSkipPaths(options.skipPaths);
|
|
31
|
+
const breaker = new CircuitBreaker(positiveInteger(options.failureThreshold, DEFAULT_FAILURE_THRESHOLD), positiveInteger(options.openIntervalMs, DEFAULT_OPEN_INTERVAL_MS), positiveInteger(options.maxOpenIntervalMs, DEFAULT_MAX_OPEN_INTERVAL_MS));
|
|
32
|
+
if (typeof fetcher !== 'function')
|
|
33
|
+
throw new Error('TokenForge Cloud middleware requires fetch');
|
|
34
|
+
return (next) => async (request) => {
|
|
35
|
+
if (mode === 'off')
|
|
36
|
+
return next(request);
|
|
37
|
+
if (skipPaths.has(new URL(request.url).pathname))
|
|
38
|
+
return next(request);
|
|
39
|
+
const local = await verifyTokenForgeCloudRequest(request, options.local);
|
|
40
|
+
const proof = readTokenForgeProof(request);
|
|
41
|
+
const correlationId = local.binding?.correlationId;
|
|
42
|
+
let cloud = null;
|
|
43
|
+
let cloudFailure = null;
|
|
44
|
+
if (!local.allowed)
|
|
45
|
+
return localRejection(local);
|
|
46
|
+
if (!proof) {
|
|
47
|
+
cloudFailure = new CloudVerifyFailure('rejected', 'proof_invalid');
|
|
48
|
+
}
|
|
49
|
+
else if (!options.apiKey?.trim()) {
|
|
50
|
+
cloudFailure = new CloudVerifyFailure('unavailable', 'not_configured');
|
|
51
|
+
}
|
|
52
|
+
else if (!breaker.allow(now(options))) {
|
|
53
|
+
cloudFailure = new CloudVerifyFailure('unavailable', 'circuit_open');
|
|
54
|
+
emit(options, {
|
|
55
|
+
type: 'circuit_open',
|
|
56
|
+
mode,
|
|
57
|
+
outcome: 'circuit_open',
|
|
58
|
+
latencyMs: 0,
|
|
59
|
+
circuit: breaker.state(now(options)),
|
|
60
|
+
correlationId,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
const started = now(options);
|
|
65
|
+
emit(options, {
|
|
66
|
+
type: 'attempt',
|
|
67
|
+
mode,
|
|
68
|
+
outcome: 'attempt',
|
|
69
|
+
latencyMs: 0,
|
|
70
|
+
circuit: breaker.state(started),
|
|
71
|
+
correlationId,
|
|
72
|
+
});
|
|
73
|
+
try {
|
|
74
|
+
cloud = await verifyRemote(fetcher, apiUrl, options.apiKey, proof, timeoutMs, maxResponseBytes);
|
|
75
|
+
breaker.success();
|
|
76
|
+
emit(options, {
|
|
77
|
+
type: 'success',
|
|
78
|
+
mode,
|
|
79
|
+
outcome: cloud.status,
|
|
80
|
+
latencyMs: Math.max(0, now(options) - started),
|
|
81
|
+
circuit: breaker.state(now(options)),
|
|
82
|
+
correlationId,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
cloudFailure = error instanceof CloudVerifyFailure
|
|
87
|
+
? error
|
|
88
|
+
: new CloudVerifyFailure('unavailable', 'invalid_response');
|
|
89
|
+
if (cloudFailure.kind === 'rejected')
|
|
90
|
+
breaker.success();
|
|
91
|
+
else
|
|
92
|
+
breaker.failure(now(options));
|
|
93
|
+
emit(options, {
|
|
94
|
+
type: cloudFailure.kind === 'timeout' ? 'timeout' : 'error',
|
|
95
|
+
mode,
|
|
96
|
+
outcome: cloudFailure.code,
|
|
97
|
+
latencyMs: Math.max(0, now(options) - started),
|
|
98
|
+
circuit: breaker.state(now(options)),
|
|
99
|
+
correlationId,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const cloudAllows = cloud?.status === 'allow' && cloud.bound;
|
|
104
|
+
if (!cloudAllows) {
|
|
105
|
+
emit(options, {
|
|
106
|
+
type: 'would_block',
|
|
107
|
+
mode,
|
|
108
|
+
outcome: cloud?.status ?? cloudFailure?.code ?? 'unavailable',
|
|
109
|
+
latencyMs: 0,
|
|
110
|
+
circuit: breaker.state(now(options)),
|
|
111
|
+
correlationId,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (cloud && local.allowed !== cloudAllows) {
|
|
115
|
+
emit(options, {
|
|
116
|
+
type: 'local_cloud_mismatch',
|
|
117
|
+
mode,
|
|
118
|
+
outcome: 'local_cloud_mismatch',
|
|
119
|
+
latencyMs: 0,
|
|
120
|
+
circuit: breaker.state(now(options)),
|
|
121
|
+
correlationId,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (mode === 'monitor')
|
|
125
|
+
return next(request);
|
|
126
|
+
if (cloudFailure) {
|
|
127
|
+
if (cloudFailure.kind === 'rejected') {
|
|
128
|
+
return safeJson(401, { error: 'tokenforge_rejected', reason: cloudFailure.code });
|
|
129
|
+
}
|
|
130
|
+
return safeJson(503, { error: 'tokenforge_unavailable' });
|
|
131
|
+
}
|
|
132
|
+
if (!cloud || !cloud.bound || cloud.status === 'block') {
|
|
133
|
+
return safeJson(401, {
|
|
134
|
+
error: 'tokenforge_rejected',
|
|
135
|
+
reason: cloud?.reason || 'not_bound',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (cloud.status === 'step_up') {
|
|
139
|
+
return safeJson(403, { error: 'tokenforge_step_up_required' });
|
|
140
|
+
}
|
|
141
|
+
return next(request);
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
async function verifyRemote(fetcher, apiUrl, apiKey, proof, timeoutMs, maxResponseBytes) {
|
|
145
|
+
const controller = new AbortController();
|
|
146
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
147
|
+
let response;
|
|
148
|
+
let raw;
|
|
149
|
+
try {
|
|
150
|
+
response = await fetcher(`${apiUrl}/v2/edge/verify`, {
|
|
151
|
+
method: 'POST',
|
|
152
|
+
signal: controller.signal,
|
|
153
|
+
headers: {
|
|
154
|
+
Authorization: `Bearer ${apiKey}`,
|
|
155
|
+
Accept: 'application/json',
|
|
156
|
+
'Content-Type': 'application/json',
|
|
157
|
+
},
|
|
158
|
+
body: JSON.stringify(proof),
|
|
159
|
+
});
|
|
160
|
+
raw = await readBoundedResponse(response, maxResponseBytes);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
if (controller.signal.aborted)
|
|
164
|
+
throw new CloudVerifyFailure('timeout', 'timeout');
|
|
165
|
+
throw new CloudVerifyFailure('unavailable', 'transport_error');
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
clearTimeout(timeout);
|
|
169
|
+
}
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
const code = safeRemoteCode(raw);
|
|
172
|
+
const authenticationFailure = response.status === 401 && code === 'unauthorized';
|
|
173
|
+
if (response.status >= 400 && response.status < 500 && !authenticationFailure) {
|
|
174
|
+
throw new CloudVerifyFailure('rejected', code);
|
|
175
|
+
}
|
|
176
|
+
throw new CloudVerifyFailure('unavailable', code);
|
|
177
|
+
}
|
|
178
|
+
let data;
|
|
179
|
+
try {
|
|
180
|
+
const envelope = requireRecord(JSON.parse(raw));
|
|
181
|
+
data = requireRecord(envelope.data);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
throw new CloudVerifyFailure('unavailable', 'invalid_response');
|
|
185
|
+
}
|
|
186
|
+
if ((data.status !== 'allow' && data.status !== 'step_up' && data.status !== 'block') ||
|
|
187
|
+
typeof data.bound !== 'boolean' ||
|
|
188
|
+
typeof data.trustScore !== 'number' ||
|
|
189
|
+
!Number.isInteger(data.trustScore) ||
|
|
190
|
+
data.trustScore < 0 || data.trustScore > 100 ||
|
|
191
|
+
typeof data.deviceId !== 'string' || !DEVICE_PATTERN.test(data.deviceId) ||
|
|
192
|
+
data.protocol !== PROTOCOL_V2) {
|
|
193
|
+
throw new CloudVerifyFailure('unavailable', 'invalid_response');
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
status: data.status,
|
|
197
|
+
bound: data.bound,
|
|
198
|
+
trustScore: data.trustScore,
|
|
199
|
+
deviceId: data.deviceId,
|
|
200
|
+
reason: safeReason(data.reason),
|
|
201
|
+
protocol: PROTOCOL_V2,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
class CircuitBreaker {
|
|
205
|
+
threshold;
|
|
206
|
+
openInterval;
|
|
207
|
+
maxOpenInterval;
|
|
208
|
+
#failures = 0;
|
|
209
|
+
#openUntil = 0;
|
|
210
|
+
#currentInterval;
|
|
211
|
+
#halfOpenProbe = false;
|
|
212
|
+
constructor(threshold, openInterval, maxOpenInterval) {
|
|
213
|
+
this.threshold = threshold;
|
|
214
|
+
this.openInterval = openInterval;
|
|
215
|
+
this.maxOpenInterval = maxOpenInterval;
|
|
216
|
+
this.#currentInterval = openInterval;
|
|
217
|
+
}
|
|
218
|
+
allow(at) {
|
|
219
|
+
if (this.#openUntil > at)
|
|
220
|
+
return false;
|
|
221
|
+
if (this.#failures < this.threshold)
|
|
222
|
+
return true;
|
|
223
|
+
if (this.#halfOpenProbe)
|
|
224
|
+
return false;
|
|
225
|
+
this.#halfOpenProbe = true;
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
state(at) {
|
|
229
|
+
if (this.#openUntil > at)
|
|
230
|
+
return 'open';
|
|
231
|
+
if (this.#failures >= this.threshold)
|
|
232
|
+
return 'half_open';
|
|
233
|
+
return 'closed';
|
|
234
|
+
}
|
|
235
|
+
success() {
|
|
236
|
+
this.#failures = 0;
|
|
237
|
+
this.#openUntil = 0;
|
|
238
|
+
this.#currentInterval = this.openInterval;
|
|
239
|
+
this.#halfOpenProbe = false;
|
|
240
|
+
}
|
|
241
|
+
failure(at) {
|
|
242
|
+
this.#halfOpenProbe = false;
|
|
243
|
+
this.#failures += 1;
|
|
244
|
+
if (this.#failures < this.threshold)
|
|
245
|
+
return;
|
|
246
|
+
this.#openUntil = at + this.#currentInterval;
|
|
247
|
+
this.#currentInterval = Math.min(this.#currentInterval * 2, this.maxOpenInterval);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async function readBoundedResponse(response, maxBytes) {
|
|
251
|
+
if (!response.body)
|
|
252
|
+
return '';
|
|
253
|
+
const reader = response.body.getReader();
|
|
254
|
+
const chunks = [];
|
|
255
|
+
let total = 0;
|
|
256
|
+
try {
|
|
257
|
+
while (true) {
|
|
258
|
+
const { done, value } = await reader.read();
|
|
259
|
+
if (done)
|
|
260
|
+
break;
|
|
261
|
+
total += value.byteLength;
|
|
262
|
+
if (total > maxBytes)
|
|
263
|
+
throw new CloudVerifyFailure('unavailable', 'response_too_large');
|
|
264
|
+
chunks.push(value);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
finally {
|
|
268
|
+
reader.releaseLock();
|
|
269
|
+
}
|
|
270
|
+
const output = new Uint8Array(total);
|
|
271
|
+
let offset = 0;
|
|
272
|
+
for (const chunk of chunks) {
|
|
273
|
+
output.set(chunk, offset);
|
|
274
|
+
offset += chunk.byteLength;
|
|
275
|
+
}
|
|
276
|
+
return new TextDecoder().decode(output);
|
|
277
|
+
}
|
|
278
|
+
function localRejection(local) {
|
|
279
|
+
const status = local.statusCode >= 400 && local.statusCode <= 599
|
|
280
|
+
? local.statusCode
|
|
281
|
+
: 401;
|
|
282
|
+
return safeJson(status, { error: safeReason(local.code) || 'local_verification_failed' });
|
|
283
|
+
}
|
|
284
|
+
function emit(options, event) {
|
|
285
|
+
try {
|
|
286
|
+
options.telemetry?.({ ...event, operation: '/v2/edge/verify' });
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
// Telemetry must never affect the authorization result.
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function safeRemoteCode(raw) {
|
|
293
|
+
try {
|
|
294
|
+
return safeReason(requireRecord(JSON.parse(raw)).error) || 'verify_rejected';
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return 'verify_rejected';
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function safeReason(value) {
|
|
301
|
+
return typeof value === 'string' && /^[A-Za-z0-9._:-]{1,96}$/.test(value)
|
|
302
|
+
? value
|
|
303
|
+
: '';
|
|
304
|
+
}
|
|
305
|
+
function safeJson(status, value) {
|
|
306
|
+
return new Response(JSON.stringify(value), {
|
|
307
|
+
status,
|
|
308
|
+
headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' },
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
function normalizeMode(mode) {
|
|
312
|
+
return mode === 'monitor' || mode === 'enforce' ? mode : 'off';
|
|
313
|
+
}
|
|
314
|
+
function normalizeApiUrl(input) {
|
|
315
|
+
const url = new URL(input);
|
|
316
|
+
if (url.protocol !== 'https:' || !url.hostname || url.username || url.password ||
|
|
317
|
+
url.search || url.hash) {
|
|
318
|
+
throw new Error('TokenForge Cloud API URL is invalid');
|
|
319
|
+
}
|
|
320
|
+
return url.toString().replace(/\/$/, '');
|
|
321
|
+
}
|
|
322
|
+
function positiveInteger(value, fallback) {
|
|
323
|
+
return Number.isSafeInteger(value) && (value ?? 0) > 0 ? value : fallback;
|
|
324
|
+
}
|
|
325
|
+
function exactSkipPaths(paths) {
|
|
326
|
+
const result = new Set();
|
|
327
|
+
for (const path of paths ?? []) {
|
|
328
|
+
if (!path.startsWith('/') || path.includes('?') || path.includes('#')) {
|
|
329
|
+
throw new Error('TokenForge Cloud skip paths must be exact absolute URL paths');
|
|
330
|
+
}
|
|
331
|
+
result.add(path);
|
|
332
|
+
}
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
function requireRecord(input) {
|
|
336
|
+
if (typeof input !== 'object' || input === null || Array.isArray(input)) {
|
|
337
|
+
throw new Error('expected object');
|
|
338
|
+
}
|
|
339
|
+
return input;
|
|
340
|
+
}
|
|
341
|
+
function now(options) {
|
|
342
|
+
return options.now?.() ?? Date.now();
|
|
343
|
+
}
|
|
344
|
+
//# sourceMappingURL=cloud-middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud-middleware.js","sourceRoot":"../src/","sources":["server/cloud-middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type RequestProofV2 } from '../protocol/v2.js';
|
|
2
|
+
import type { CloudBinding, CloudBindingStore, CloudSessionResolver } from './cloud-bind.js';
|
|
3
|
+
/** Atomic replay store. Multi-instance services must share one implementation. */
|
|
4
|
+
export interface CloudReplayStore {
|
|
5
|
+
consume(scope: string, nonce: string, ttlMilliseconds: number): Promise<boolean>;
|
|
6
|
+
}
|
|
7
|
+
export interface CloudVerificationBindingStore extends CloudBindingStore {
|
|
8
|
+
getByCloudDeviceId(cloudDeviceId: string): Promise<CloudBinding | null>;
|
|
9
|
+
}
|
|
10
|
+
export interface CloudVerifyOptions {
|
|
11
|
+
bindingStore: CloudVerificationBindingStore;
|
|
12
|
+
replayStore: CloudReplayStore;
|
|
13
|
+
resolveSession: CloudSessionResolver;
|
|
14
|
+
timestampWindowMs?: number;
|
|
15
|
+
maxBodyBytes?: number;
|
|
16
|
+
now?: () => number;
|
|
17
|
+
}
|
|
18
|
+
export interface TokenForgeLocalVerification {
|
|
19
|
+
allowed: boolean;
|
|
20
|
+
statusCode: number;
|
|
21
|
+
code: string;
|
|
22
|
+
binding?: CloudBinding;
|
|
23
|
+
}
|
|
24
|
+
/** Verify one Cloud v2 request-bound proof and atomically consume its nonce. */
|
|
25
|
+
export declare function verifyTokenForgeCloudRequest(request: Request, options: CloudVerifyOptions): Promise<TokenForgeLocalVerification>;
|
|
26
|
+
export type TokenForgeProofWithSignature = RequestProofV2 & {
|
|
27
|
+
signature: string;
|
|
28
|
+
};
|
|
29
|
+
export declare function readTokenForgeProof(request: Request): TokenForgeProofWithSignature | null;
|
|
30
|
+
//# sourceMappingURL=cloud-verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud-verify.d.ts","sourceRoot":"../src/","sources":["server/cloud-verify.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,iBAAiB,CAAC;AAOzB,kFAAkF;AAClF,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAClF;AAED,MAAM,WAAW,6BAA8B,SAAQ,iBAAiB;IACtE,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;CACzE;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,6BAA6B,CAAC;IAC5C,WAAW,EAAE,gBAAgB,CAAC;IAC9B,cAAc,EAAE,oBAAoB,CAAC;IACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB;AAED,gFAAgF;AAChF,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,2BAA2B,CAAC,CAmHtC;AAED,MAAM,MAAM,4BAA4B,GAAG,cAAc,GAAG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAElF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,4BAA4B,GAAG,IAAI,CA8BzF"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { PROTOCOL_V2, canonicalizeOrigin, encodeRequestProof, sha256Base64Url, } from '../protocol/v2.js';
|
|
2
|
+
const DEFAULT_TIMESTAMP_WINDOW_MS = 60_000;
|
|
3
|
+
const DEFAULT_MAX_BODY_BYTES = 2 * 1024 * 1024;
|
|
4
|
+
const MAX_FUTURE_SKEW_MS = 5_000;
|
|
5
|
+
const PUBLIC_JWK_KEYS = new Set(['kty', 'crv', 'x', 'y', 'ext', 'key_ops']);
|
|
6
|
+
/** Verify one Cloud v2 request-bound proof and atomically consume its nonce. */
|
|
7
|
+
export async function verifyTokenForgeCloudRequest(request, options) {
|
|
8
|
+
if (!options.bindingStore || !options.replayStore || typeof options.resolveSession !== 'function') {
|
|
9
|
+
return reject(503, 'tokenforge_local_unavailable');
|
|
10
|
+
}
|
|
11
|
+
const timestampWindowMs = positiveInteger(options.timestampWindowMs, DEFAULT_TIMESTAMP_WINDOW_MS);
|
|
12
|
+
const maxBodyBytes = positiveInteger(options.maxBodyBytes, DEFAULT_MAX_BODY_BYTES);
|
|
13
|
+
const now = options.now?.() ?? Date.now();
|
|
14
|
+
const proof = readTokenForgeProof(request);
|
|
15
|
+
if (!proof)
|
|
16
|
+
return reject(401, 'tokenforge_proof_invalid');
|
|
17
|
+
let session;
|
|
18
|
+
try {
|
|
19
|
+
session = await options.resolveSession(request);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return reject(401, 'tokenforge_proof_invalid');
|
|
23
|
+
}
|
|
24
|
+
if (!session?.sessionId)
|
|
25
|
+
return reject(401, 'tokenforge_proof_invalid');
|
|
26
|
+
const publicSessionId = await hashOpaque('session', session.sessionId);
|
|
27
|
+
if (publicSessionId !== proof.sessionId) {
|
|
28
|
+
return reject(401, 'tokenforge_proof_invalid');
|
|
29
|
+
}
|
|
30
|
+
let binding;
|
|
31
|
+
try {
|
|
32
|
+
binding = await options.bindingStore.getByCloudDeviceId(proof.deviceId);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return reject(503, 'tokenforge_binding_unavailable');
|
|
36
|
+
}
|
|
37
|
+
if (!binding ||
|
|
38
|
+
binding.projectId !== proof.projectId ||
|
|
39
|
+
binding.sessionId !== proof.sessionId ||
|
|
40
|
+
(session.localDeviceId && binding.localDeviceId !== session.localDeviceId) ||
|
|
41
|
+
Date.parse(binding.expiresAt) <= now) {
|
|
42
|
+
return reject(401, 'tokenforge_binding_invalid', binding ?? undefined);
|
|
43
|
+
}
|
|
44
|
+
const actualOrigin = request.headers.get('Origin');
|
|
45
|
+
if (actualOrigin) {
|
|
46
|
+
try {
|
|
47
|
+
if (canonicalizeOrigin(actualOrigin) !== canonicalizeOrigin(proof.origin)) {
|
|
48
|
+
return reject(401, 'tokenforge_origin_mismatch', binding);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return reject(401, 'tokenforge_origin_mismatch', binding);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
let body;
|
|
56
|
+
try {
|
|
57
|
+
const boundedRequest = request.clone();
|
|
58
|
+
body = Uint8Array.from(await readBoundedBody(boundedRequest, maxBodyBytes));
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return reject(413, 'tokenforge_body_too_large', binding);
|
|
62
|
+
}
|
|
63
|
+
if (await sha256Base64Url(body) !== proof.bodySha256) {
|
|
64
|
+
return reject(401, 'tokenforge_body_mismatch', binding);
|
|
65
|
+
}
|
|
66
|
+
const signedAt = Number(proof.timestamp);
|
|
67
|
+
if (!Number.isSafeInteger(signedAt) ||
|
|
68
|
+
signedAt < now - timestampWindowMs ||
|
|
69
|
+
signedAt > now + MAX_FUTURE_SKEW_MS) {
|
|
70
|
+
return reject(401, 'tokenforge_timestamp_stale', binding);
|
|
71
|
+
}
|
|
72
|
+
let canonical;
|
|
73
|
+
let signature;
|
|
74
|
+
let publicKey;
|
|
75
|
+
try {
|
|
76
|
+
canonical = encodeRequestProof(proof);
|
|
77
|
+
signature = decodeBase64Url(proof.signature, 64);
|
|
78
|
+
publicKey = await importPublicVerificationKey(binding.publicKeyJwk);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return reject(401, 'tokenforge_proof_invalid', binding);
|
|
82
|
+
}
|
|
83
|
+
let signatureValid = false;
|
|
84
|
+
try {
|
|
85
|
+
signatureValid = await crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, publicKey, signature, canonical);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return reject(401, 'tokenforge_signature_invalid', binding);
|
|
89
|
+
}
|
|
90
|
+
if (!signatureValid)
|
|
91
|
+
return reject(401, 'tokenforge_signature_invalid', binding);
|
|
92
|
+
let fresh;
|
|
93
|
+
try {
|
|
94
|
+
fresh = await options.replayStore.consume(`${binding.projectId}:${binding.cloudDeviceId}`, proof.nonce, timestampWindowMs + MAX_FUTURE_SKEW_MS);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return reject(503, 'tokenforge_replay_unavailable', binding);
|
|
98
|
+
}
|
|
99
|
+
if (!fresh)
|
|
100
|
+
return reject(401, 'tokenforge_nonce_replay', binding);
|
|
101
|
+
return { allowed: true, statusCode: 200, code: 'allow', binding };
|
|
102
|
+
}
|
|
103
|
+
export function readTokenForgeProof(request) {
|
|
104
|
+
const protocol = boundedHeader(request, 'X-TF-Protocol', 32);
|
|
105
|
+
const projectId = boundedHeader(request, 'X-TF-Project-ID', 256);
|
|
106
|
+
const deviceId = boundedHeader(request, 'X-TF-Device-ID', 256);
|
|
107
|
+
const sessionId = boundedHeader(request, 'X-TF-Session-ID', 256);
|
|
108
|
+
const bodySha256 = boundedHeader(request, 'X-TF-Body-SHA256', 64);
|
|
109
|
+
const origin = boundedHeader(request, 'X-TF-Origin', 512);
|
|
110
|
+
const nonce = boundedHeader(request, 'X-TF-Nonce', 64);
|
|
111
|
+
const timestamp = boundedHeader(request, 'X-TF-Timestamp', 16);
|
|
112
|
+
const signature = boundedHeader(request, 'X-TF-Signature', 128);
|
|
113
|
+
if (protocol !== PROTOCOL_V2 || !projectId || !deviceId || !sessionId ||
|
|
114
|
+
!bodySha256 || !nonce || !timestamp || !signature) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
protocol: PROTOCOL_V2,
|
|
119
|
+
projectId,
|
|
120
|
+
deviceId,
|
|
121
|
+
sessionId,
|
|
122
|
+
method: request.method,
|
|
123
|
+
path: new URL(request.url).pathname || '/',
|
|
124
|
+
query: new URL(request.url).search.slice(1),
|
|
125
|
+
bodySha256,
|
|
126
|
+
origin,
|
|
127
|
+
nonce,
|
|
128
|
+
timestamp,
|
|
129
|
+
signature,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
async function importPublicVerificationKey(raw) {
|
|
133
|
+
const parsed = requireRecord(JSON.parse(raw));
|
|
134
|
+
rejectUnknownKeys(parsed, PUBLIC_JWK_KEYS);
|
|
135
|
+
if (parsed.kty !== 'EC' || parsed.crv !== 'P-256' ||
|
|
136
|
+
typeof parsed.x !== 'string' || typeof parsed.y !== 'string') {
|
|
137
|
+
throw new Error('invalid public key');
|
|
138
|
+
}
|
|
139
|
+
decodeBase64Url(parsed.x, 32);
|
|
140
|
+
decodeBase64Url(parsed.y, 32);
|
|
141
|
+
return crypto.subtle.importKey('jwk', parsed, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
|
|
142
|
+
}
|
|
143
|
+
async function readBoundedBody(request, maxBytes) {
|
|
144
|
+
if (!request.body)
|
|
145
|
+
return new Uint8Array();
|
|
146
|
+
const reader = request.body.getReader();
|
|
147
|
+
const chunks = [];
|
|
148
|
+
let total = 0;
|
|
149
|
+
try {
|
|
150
|
+
while (true) {
|
|
151
|
+
const { done, value } = await reader.read();
|
|
152
|
+
if (done)
|
|
153
|
+
break;
|
|
154
|
+
total += value.byteLength;
|
|
155
|
+
if (total > maxBytes)
|
|
156
|
+
throw new Error('body too large');
|
|
157
|
+
chunks.push(value);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
reader.releaseLock();
|
|
162
|
+
}
|
|
163
|
+
const body = new Uint8Array(total);
|
|
164
|
+
let offset = 0;
|
|
165
|
+
for (const chunk of chunks) {
|
|
166
|
+
body.set(chunk, offset);
|
|
167
|
+
offset += chunk.byteLength;
|
|
168
|
+
}
|
|
169
|
+
return body;
|
|
170
|
+
}
|
|
171
|
+
async function hashOpaque(purpose, value) {
|
|
172
|
+
return sha256Base64Url(new TextEncoder().encode(`tokenforge-v2:${purpose}\0${value}`));
|
|
173
|
+
}
|
|
174
|
+
function boundedHeader(request, name, maxLength) {
|
|
175
|
+
const value = request.headers.get(name) ?? '';
|
|
176
|
+
return value.length <= maxLength ? value : '';
|
|
177
|
+
}
|
|
178
|
+
function decodeBase64Url(value, expectedBytes) {
|
|
179
|
+
if (!/^[A-Za-z0-9_-]+$/.test(value))
|
|
180
|
+
throw new Error('invalid base64url');
|
|
181
|
+
const padding = '='.repeat((4 - (value.length % 4)) % 4);
|
|
182
|
+
const binary = atob(value.replace(/-/g, '+').replace(/_/g, '/') + padding);
|
|
183
|
+
const decoded = Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
184
|
+
if (decoded.byteLength !== expectedBytes)
|
|
185
|
+
throw new Error('invalid base64url length');
|
|
186
|
+
return decoded;
|
|
187
|
+
}
|
|
188
|
+
function requireRecord(input) {
|
|
189
|
+
if (typeof input !== 'object' || input === null || Array.isArray(input)) {
|
|
190
|
+
throw new Error('expected object');
|
|
191
|
+
}
|
|
192
|
+
return input;
|
|
193
|
+
}
|
|
194
|
+
function rejectUnknownKeys(input, allowed) {
|
|
195
|
+
for (const key of Object.keys(input)) {
|
|
196
|
+
if (!allowed.has(key))
|
|
197
|
+
throw new Error(`unsupported key field: ${key}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function positiveInteger(value, fallback) {
|
|
201
|
+
return Number.isSafeInteger(value) && (value ?? 0) > 0 ? value : fallback;
|
|
202
|
+
}
|
|
203
|
+
function reject(statusCode, code, binding) {
|
|
204
|
+
return { allowed: false, statusCode, code, binding };
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=cloud-verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud-verify.js","sourceRoot":"../src/","sources":["server/cloud-verify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DBSC challenge issuer — emits the random bytes the client must sign,
|
|
3
|
+
* stores their SHA-256 hash + a 60-second expiry, and consumes them
|
|
4
|
+
* exactly once.
|
|
5
|
+
*
|
|
6
|
+
* The challenge bytes never live at rest — we store only the hash so a
|
|
7
|
+
* DB read does not let an attacker pre-compute valid signatures.
|
|
8
|
+
*
|
|
9
|
+
* Pure logic; persistence is injected so this works on any storage.
|
|
10
|
+
*/
|
|
11
|
+
export interface ChallengeStore {
|
|
12
|
+
put(record: ChallengeRecord): Promise<void>;
|
|
13
|
+
takeIfFresh(hash: string, now: Date): Promise<ChallengeRecord | null>;
|
|
14
|
+
}
|
|
15
|
+
export interface ChallengeRecord {
|
|
16
|
+
id: string;
|
|
17
|
+
tenantId: string;
|
|
18
|
+
challengeHash: string;
|
|
19
|
+
purpose: 'register' | 'refresh' | 'step_up';
|
|
20
|
+
sessionId?: string | null;
|
|
21
|
+
actionHash?: string | null;
|
|
22
|
+
issuedAt: string;
|
|
23
|
+
expiresAt: string;
|
|
24
|
+
consumed: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface IssueChallengeInput {
|
|
27
|
+
tenantId: string;
|
|
28
|
+
purpose: ChallengeRecord['purpose'];
|
|
29
|
+
sessionId?: string;
|
|
30
|
+
actionHash?: string;
|
|
31
|
+
ttlSeconds?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface IssueChallengeOutput {
|
|
34
|
+
/** base64url-encoded random bytes — send to the client. */
|
|
35
|
+
challenge: string;
|
|
36
|
+
/** Stored record, returned for callers who need the id/expiry. */
|
|
37
|
+
record: ChallengeRecord;
|
|
38
|
+
}
|
|
39
|
+
export declare function issueChallenge(store: ChallengeStore, input: IssueChallengeInput): Promise<IssueChallengeOutput>;
|
|
40
|
+
export declare function consumeChallenge(store: ChallengeStore, challenge: string, expected: {
|
|
41
|
+
tenantId: string;
|
|
42
|
+
purpose: ChallengeRecord['purpose'];
|
|
43
|
+
sessionId?: string;
|
|
44
|
+
}): Promise<{
|
|
45
|
+
ok: true;
|
|
46
|
+
record: ChallengeRecord;
|
|
47
|
+
} | {
|
|
48
|
+
ok: false;
|
|
49
|
+
reason: string;
|
|
50
|
+
}>;
|
|
51
|
+
//# sourceMappingURL=dbsc-challenge.d.ts.map
|