trustline 0.0.1 → 0.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 +21 -0
- package/README.md +339 -0
- package/dist/adapters/mysql/index.cjs +199 -0
- package/dist/adapters/mysql/index.cjs.map +1 -0
- package/dist/adapters/mysql/index.d.cts +6 -0
- package/dist/adapters/mysql/index.d.ts +6 -0
- package/dist/adapters/mysql/index.js +21 -0
- package/dist/adapters/mysql/index.js.map +1 -0
- package/dist/adapters/postgres/index.cjs +199 -0
- package/dist/adapters/postgres/index.cjs.map +1 -0
- package/dist/adapters/postgres/index.d.cts +6 -0
- package/dist/adapters/postgres/index.d.ts +6 -0
- package/dist/adapters/postgres/index.js +21 -0
- package/dist/adapters/postgres/index.js.map +1 -0
- package/dist/adapters/sqlite/index.cjs +216 -0
- package/dist/adapters/sqlite/index.cjs.map +1 -0
- package/dist/adapters/sqlite/index.d.cts +6 -0
- package/dist/adapters/sqlite/index.d.ts +6 -0
- package/dist/adapters/sqlite/index.js +28 -0
- package/dist/adapters/sqlite/index.js.map +1 -0
- package/dist/chunk-CTPFKR4O.js +157 -0
- package/dist/chunk-CTPFKR4O.js.map +1 -0
- package/dist/chunk-GF3NKEEK.js +18 -0
- package/dist/chunk-GF3NKEEK.js.map +1 -0
- package/dist/client/index.cjs +141 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.cts +18 -0
- package/dist/client/index.d.ts +18 -0
- package/dist/client/index.js +104 -0
- package/dist/client/index.js.map +1 -0
- package/dist/frameworks/express/index.cjs +121 -0
- package/dist/frameworks/express/index.cjs.map +1 -0
- package/dist/frameworks/express/index.d.cts +18 -0
- package/dist/frameworks/express/index.d.ts +18 -0
- package/dist/frameworks/express/index.js +83 -0
- package/dist/frameworks/express/index.js.map +1 -0
- package/dist/frameworks/fastify/index.cjs +158 -0
- package/dist/frameworks/fastify/index.cjs.map +1 -0
- package/dist/frameworks/fastify/index.d.cts +25 -0
- package/dist/frameworks/fastify/index.d.ts +25 -0
- package/dist/frameworks/fastify/index.js +120 -0
- package/dist/frameworks/fastify/index.js.map +1 -0
- package/dist/frameworks/hono/index.cjs +117 -0
- package/dist/frameworks/hono/index.cjs.map +1 -0
- package/dist/frameworks/hono/index.d.cts +17 -0
- package/dist/frameworks/hono/index.d.ts +17 -0
- package/dist/frameworks/hono/index.js +79 -0
- package/dist/frameworks/hono/index.js.map +1 -0
- package/dist/index-Dc4GFume.d.ts +34 -0
- package/dist/index-DqkKZOlH.d.cts +34 -0
- package/dist/index.cjs +571 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +537 -0
- package/dist/index.js.map +1 -0
- package/dist/interface-BzT_DC3u.d.cts +38 -0
- package/dist/interface-BzT_DC3u.d.ts +38 -0
- package/dist/token-BtfYGd9K.d.cts +33 -0
- package/dist/token-BtfYGd9K.d.ts +33 -0
- package/package.json +125 -3
- package/index.js +0 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AuthError
|
|
3
|
+
} from "./chunk-GF3NKEEK.js";
|
|
4
|
+
|
|
5
|
+
// src/core/token.ts
|
|
6
|
+
import { errors, jwtVerify } from "jose";
|
|
7
|
+
|
|
8
|
+
// src/core/cache.ts
|
|
9
|
+
import {
|
|
10
|
+
createLocalJWKSet
|
|
11
|
+
} from "jose";
|
|
12
|
+
var JwksCache = class {
|
|
13
|
+
ttlMs;
|
|
14
|
+
entries = /* @__PURE__ */ new Map();
|
|
15
|
+
inflight = /* @__PURE__ */ new Map();
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
this.ttlMs = options.ttlMs ?? 10 * 60 * 1e3;
|
|
18
|
+
}
|
|
19
|
+
async get(url, forceRefresh = false) {
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
const cached = this.entries.get(url);
|
|
22
|
+
if (!forceRefresh && cached && cached.expiresAt > now) {
|
|
23
|
+
return cached.jwkSet;
|
|
24
|
+
}
|
|
25
|
+
const inflight = this.inflight.get(url);
|
|
26
|
+
if (inflight) {
|
|
27
|
+
const entry = await inflight.promise;
|
|
28
|
+
return entry.jwkSet;
|
|
29
|
+
}
|
|
30
|
+
const promise = this.fetchAndCache(url);
|
|
31
|
+
this.inflight.set(url, { promise });
|
|
32
|
+
try {
|
|
33
|
+
const entry = await promise;
|
|
34
|
+
return entry.jwkSet;
|
|
35
|
+
} finally {
|
|
36
|
+
this.inflight.delete(url);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
clear(url) {
|
|
40
|
+
if (url) {
|
|
41
|
+
this.entries.delete(url);
|
|
42
|
+
this.inflight.delete(url);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.entries.clear();
|
|
46
|
+
this.inflight.clear();
|
|
47
|
+
}
|
|
48
|
+
async fetchAndCache(url) {
|
|
49
|
+
let response;
|
|
50
|
+
try {
|
|
51
|
+
response = await fetch(url);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new AuthError(
|
|
54
|
+
"jwks_fetch_failed",
|
|
55
|
+
`Failed to fetch JWKS from ${url}`,
|
|
56
|
+
401,
|
|
57
|
+
error
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
throw new AuthError(
|
|
62
|
+
"jwks_fetch_failed",
|
|
63
|
+
`Failed to fetch JWKS from ${url}`,
|
|
64
|
+
401
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const json = await response.json();
|
|
68
|
+
if (!json || !Array.isArray(json.keys)) {
|
|
69
|
+
throw new AuthError(
|
|
70
|
+
"jwks_fetch_failed",
|
|
71
|
+
`Invalid JWKS payload from ${url}`,
|
|
72
|
+
401
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
const entry = {
|
|
76
|
+
jwks: json,
|
|
77
|
+
jwkSet: createLocalJWKSet(json),
|
|
78
|
+
expiresAt: Date.now() + this.ttlMs
|
|
79
|
+
};
|
|
80
|
+
this.entries.set(url, entry);
|
|
81
|
+
return entry;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var defaultJwksCache = new JwksCache();
|
|
85
|
+
|
|
86
|
+
// src/core/scopes.ts
|
|
87
|
+
function parseScopes(scope) {
|
|
88
|
+
if (!scope) {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
return [
|
|
92
|
+
...new Set(
|
|
93
|
+
scope.split(/\s+/).map((value) => value.trim()).filter(Boolean)
|
|
94
|
+
)
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
function hasRequiredScopes(tokenScope, requiredScopes) {
|
|
98
|
+
const tokenScopes = new Set(parseScopes(tokenScope));
|
|
99
|
+
return requiredScopes.every((scope) => tokenScopes.has(scope));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/core/token.ts
|
|
103
|
+
var DEFAULT_CLOCK_TOLERANCE_SECONDS = 5;
|
|
104
|
+
function deriveJwksUrl(issuer) {
|
|
105
|
+
const normalized = issuer.endsWith("/") ? issuer.slice(0, -1) : issuer;
|
|
106
|
+
return `${normalized}/.well-known/jwks.json`;
|
|
107
|
+
}
|
|
108
|
+
async function verifyToken(token, options) {
|
|
109
|
+
if (!token) {
|
|
110
|
+
throw new AuthError("missing_token", "Missing bearer token", 401);
|
|
111
|
+
}
|
|
112
|
+
const jwksUrl = options.jwksUrl ?? deriveJwksUrl(options.issuer);
|
|
113
|
+
const jwksCache = options.jwksCache ?? defaultJwksCache;
|
|
114
|
+
try {
|
|
115
|
+
return await verifyWithCache(token, options, jwksUrl, jwksCache);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (!shouldRetryWithFreshJwks(error)) {
|
|
118
|
+
throw normalizeVerifyError(error);
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
return await verifyWithCache(token, options, jwksUrl, jwksCache, true);
|
|
122
|
+
} catch (retryError) {
|
|
123
|
+
throw normalizeVerifyError(retryError);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function verifyWithCache(token, options, jwksUrl, jwksCache, forceRefresh = false) {
|
|
128
|
+
const jwkSet = await jwksCache.get(jwksUrl, forceRefresh);
|
|
129
|
+
const { payload } = await jwtVerify(token, jwkSet, {
|
|
130
|
+
issuer: options.issuer,
|
|
131
|
+
audience: options.audience,
|
|
132
|
+
algorithms: ["RS256", "ES256"],
|
|
133
|
+
clockTolerance: options.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SECONDS
|
|
134
|
+
});
|
|
135
|
+
if (typeof payload.sub !== "string" || payload.sub.length === 0) {
|
|
136
|
+
throw new AuthError("invalid_token", "Token subject is missing", 401);
|
|
137
|
+
}
|
|
138
|
+
if (options.env && payload.env !== options.env) {
|
|
139
|
+
throw new AuthError("invalid_env", "Token environment does not match", 403);
|
|
140
|
+
}
|
|
141
|
+
if (options.scopes?.length && !hasRequiredScopes(getScopeClaim(payload), options.scopes)) {
|
|
142
|
+
throw new AuthError(
|
|
143
|
+
"invalid_scope",
|
|
144
|
+
"Token is missing required scopes",
|
|
145
|
+
403
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
clientId: payload.sub,
|
|
150
|
+
name: typeof payload.name === "string" ? payload.name : null,
|
|
151
|
+
scopes: parseScopes(getScopeClaim(payload)),
|
|
152
|
+
env: typeof payload.env === "string" ? payload.env : null,
|
|
153
|
+
raw: payload
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function shouldRetryWithFreshJwks(error) {
|
|
157
|
+
return error instanceof errors.JWKSNoMatchingKey || error instanceof errors.JWSSignatureVerificationFailed;
|
|
158
|
+
}
|
|
159
|
+
function normalizeVerifyError(error) {
|
|
160
|
+
if (error instanceof AuthError) {
|
|
161
|
+
return error;
|
|
162
|
+
}
|
|
163
|
+
if (error instanceof errors.JWTClaimValidationFailed) {
|
|
164
|
+
if (error.claim === "iss") {
|
|
165
|
+
return new AuthError(
|
|
166
|
+
"invalid_issuer",
|
|
167
|
+
"Token issuer does not match",
|
|
168
|
+
401,
|
|
169
|
+
error
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
if (error.claim === "aud") {
|
|
173
|
+
return new AuthError(
|
|
174
|
+
"invalid_audience",
|
|
175
|
+
"Token audience does not match",
|
|
176
|
+
403,
|
|
177
|
+
error
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (error instanceof errors.JOSEError || error instanceof errors.JWTInvalid || error instanceof errors.JWTExpired) {
|
|
182
|
+
return new AuthError(
|
|
183
|
+
"invalid_token",
|
|
184
|
+
"Token verification failed",
|
|
185
|
+
401,
|
|
186
|
+
error
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
return new AuthError(
|
|
190
|
+
"invalid_token",
|
|
191
|
+
"Token verification failed",
|
|
192
|
+
401,
|
|
193
|
+
error
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
function getScopeClaim(payload) {
|
|
197
|
+
return typeof payload.scope === "string" ? payload.scope : void 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/guard/index.ts
|
|
201
|
+
function createGuard(options) {
|
|
202
|
+
return {
|
|
203
|
+
verify(token) {
|
|
204
|
+
return verifyToken(token, options);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/provider/index.ts
|
|
210
|
+
import { createPrivateKey as createPrivateKey2 } from "crypto";
|
|
211
|
+
import { SignJWT } from "jose";
|
|
212
|
+
import { v7 } from "uuid";
|
|
213
|
+
|
|
214
|
+
// src/core/crypto.ts
|
|
215
|
+
import { randomBytes } from "crypto";
|
|
216
|
+
import { compare, hash } from "bcryptjs";
|
|
217
|
+
async function hashSecret(secret) {
|
|
218
|
+
return hash(secret, 10);
|
|
219
|
+
}
|
|
220
|
+
async function verifySecret(secret, hashedSecret) {
|
|
221
|
+
return compare(secret, hashedSecret);
|
|
222
|
+
}
|
|
223
|
+
function generateSecret(length = 32) {
|
|
224
|
+
return randomBytes(length).toString("base64url");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/core/keys.ts
|
|
228
|
+
import { createPrivateKey, createPublicKey, randomUUID } from "crypto";
|
|
229
|
+
import {
|
|
230
|
+
exportJWK,
|
|
231
|
+
exportPKCS8,
|
|
232
|
+
exportSPKI,
|
|
233
|
+
generateKeyPair
|
|
234
|
+
} from "jose";
|
|
235
|
+
async function createSigningKey(options = {}) {
|
|
236
|
+
const algorithm = options.algorithm ?? "ES256";
|
|
237
|
+
const keyId = options.keyId ?? `key_${randomUUID()}`;
|
|
238
|
+
if (options.privateKey) {
|
|
239
|
+
const privateKey2 = createPrivateKey(options.privateKey);
|
|
240
|
+
const publicKey2 = createPublicKey(privateKey2);
|
|
241
|
+
return {
|
|
242
|
+
keyId,
|
|
243
|
+
algorithm,
|
|
244
|
+
privateKey: privateKey2.export({
|
|
245
|
+
type: "pkcs8",
|
|
246
|
+
format: "pem"
|
|
247
|
+
}),
|
|
248
|
+
publicKey: publicKey2.export({
|
|
249
|
+
type: "spki",
|
|
250
|
+
format: "pem"
|
|
251
|
+
}),
|
|
252
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
253
|
+
retiredAt: null
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const { privateKey, publicKey } = await generateKeyPair(algorithm, {
|
|
257
|
+
extractable: true
|
|
258
|
+
});
|
|
259
|
+
return {
|
|
260
|
+
keyId,
|
|
261
|
+
algorithm,
|
|
262
|
+
privateKey: await exportPKCS8(privateKey),
|
|
263
|
+
publicKey: await exportSPKI(publicKey),
|
|
264
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
265
|
+
retiredAt: null
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
async function exportSigningKeyToJwk(signingKey) {
|
|
269
|
+
const publicKey = createPublicKey(signingKey.publicKey);
|
|
270
|
+
const jwk = await exportJWK(publicKey);
|
|
271
|
+
return {
|
|
272
|
+
...jwk,
|
|
273
|
+
use: "sig",
|
|
274
|
+
kid: signingKey.keyId,
|
|
275
|
+
alg: signingKey.algorithm
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function getActiveSigningKeys(keys) {
|
|
279
|
+
return keys.filter((key) => key.retiredAt === null);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/provider/index.ts
|
|
283
|
+
var DEFAULT_TOKEN_TTL_SECONDS = 300;
|
|
284
|
+
function createProvider(options) {
|
|
285
|
+
const provider = new TrustlineProvider(options);
|
|
286
|
+
return {
|
|
287
|
+
handle(request) {
|
|
288
|
+
return provider.handle(request);
|
|
289
|
+
},
|
|
290
|
+
clients: {
|
|
291
|
+
create(input) {
|
|
292
|
+
return provider.createClient(input);
|
|
293
|
+
},
|
|
294
|
+
list() {
|
|
295
|
+
return provider.listClients();
|
|
296
|
+
},
|
|
297
|
+
revoke(clientId) {
|
|
298
|
+
return provider.revokeClient(clientId);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
var TrustlineProvider = class {
|
|
304
|
+
constructor(options) {
|
|
305
|
+
this.options = options;
|
|
306
|
+
}
|
|
307
|
+
async handle(request) {
|
|
308
|
+
const url = new URL(request.url);
|
|
309
|
+
if (request.method === "GET" && url.pathname.endsWith("/.well-known/jwks.json")) {
|
|
310
|
+
return this.handleJwks();
|
|
311
|
+
}
|
|
312
|
+
if (request.method === "POST" && url.pathname.endsWith("/token")) {
|
|
313
|
+
return this.handleToken(request);
|
|
314
|
+
}
|
|
315
|
+
return jsonResponse({ error: "not_found", message: "Not found" }, 404);
|
|
316
|
+
}
|
|
317
|
+
async createClient(input) {
|
|
318
|
+
const clientSecret = generateSecret();
|
|
319
|
+
const client = {
|
|
320
|
+
id: v7(),
|
|
321
|
+
clientId: `svc_${v7().replaceAll("-", "")}`,
|
|
322
|
+
clientSecret: await hashSecret(clientSecret),
|
|
323
|
+
name: input.name,
|
|
324
|
+
scopes: parseScopes(input.scopes?.join(" ")),
|
|
325
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
326
|
+
lastSeenAt: null
|
|
327
|
+
};
|
|
328
|
+
await this.options.storage.createClient(client);
|
|
329
|
+
return {
|
|
330
|
+
clientId: client.clientId,
|
|
331
|
+
clientSecret
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
listClients() {
|
|
335
|
+
return this.options.storage.listClients();
|
|
336
|
+
}
|
|
337
|
+
revokeClient(clientId) {
|
|
338
|
+
return this.options.storage.deleteClient(clientId);
|
|
339
|
+
}
|
|
340
|
+
async handleJwks() {
|
|
341
|
+
const jwks = await this.getJwks();
|
|
342
|
+
return jsonResponse(jwks);
|
|
343
|
+
}
|
|
344
|
+
async handleToken(request) {
|
|
345
|
+
const contentType = request.headers.get("content-type") ?? "";
|
|
346
|
+
if (!contentType.includes("application/x-www-form-urlencoded")) {
|
|
347
|
+
return jsonResponse(
|
|
348
|
+
{
|
|
349
|
+
error: "invalid_request",
|
|
350
|
+
error_description: "Expected application/x-www-form-urlencoded body"
|
|
351
|
+
},
|
|
352
|
+
400
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
const body = new URLSearchParams(await request.text());
|
|
356
|
+
const grantType = body.get("grant_type");
|
|
357
|
+
if (grantType !== "client_credentials") {
|
|
358
|
+
return jsonResponse(
|
|
359
|
+
{
|
|
360
|
+
error: "unsupported_grant_type",
|
|
361
|
+
error_description: "Only client_credentials is supported"
|
|
362
|
+
},
|
|
363
|
+
400
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
const credentials = getBasicCredentials(
|
|
367
|
+
request.headers.get("authorization")
|
|
368
|
+
) ?? {
|
|
369
|
+
clientId: body.get("client_id"),
|
|
370
|
+
clientSecret: body.get("client_secret")
|
|
371
|
+
};
|
|
372
|
+
if (!credentials.clientId || !credentials.clientSecret) {
|
|
373
|
+
return jsonResponse(
|
|
374
|
+
{
|
|
375
|
+
error: "invalid_client",
|
|
376
|
+
error_description: "Missing client credentials"
|
|
377
|
+
},
|
|
378
|
+
401
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
const client = await this.options.storage.findClient(credentials.clientId);
|
|
382
|
+
if (!client || !await verifySecret(credentials.clientSecret, client.clientSecret)) {
|
|
383
|
+
return jsonResponse(
|
|
384
|
+
{
|
|
385
|
+
error: "invalid_client",
|
|
386
|
+
error_description: "Client authentication failed"
|
|
387
|
+
},
|
|
388
|
+
401
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
const token = await this.issueAccessToken({
|
|
392
|
+
audience: body.get("audience") ?? void 0,
|
|
393
|
+
client
|
|
394
|
+
});
|
|
395
|
+
await this.options.storage.touchClient(client.clientId, /* @__PURE__ */ new Date());
|
|
396
|
+
return jsonResponse({
|
|
397
|
+
access_token: token,
|
|
398
|
+
token_type: "Bearer",
|
|
399
|
+
expires_in: this.options.token?.ttl ?? DEFAULT_TOKEN_TTL_SECONDS,
|
|
400
|
+
scope: client.scopes.join(" ")
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
async issueAccessToken(input) {
|
|
404
|
+
const signingKey = await this.ensureActiveSigningKey();
|
|
405
|
+
const privateKey = createPrivateKey2(signingKey.privateKey);
|
|
406
|
+
const ttl = this.options.token?.ttl ?? DEFAULT_TOKEN_TTL_SECONDS;
|
|
407
|
+
const jwt = new SignJWT({
|
|
408
|
+
name: input.client.name,
|
|
409
|
+
scope: input.client.scopes.join(" "),
|
|
410
|
+
...this.options.env ? { env: this.options.env } : {}
|
|
411
|
+
}).setProtectedHeader({
|
|
412
|
+
alg: signingKey.algorithm,
|
|
413
|
+
kid: signingKey.keyId
|
|
414
|
+
}).setIssuer(this.options.issuer).setSubject(input.client.clientId).setJti(v7()).setIssuedAt().setExpirationTime(`${ttl}s`);
|
|
415
|
+
if (input.audience) {
|
|
416
|
+
jwt.setAudience(input.audience);
|
|
417
|
+
}
|
|
418
|
+
return jwt.sign(privateKey);
|
|
419
|
+
}
|
|
420
|
+
async getJwks() {
|
|
421
|
+
const keys = getActiveSigningKeys(
|
|
422
|
+
await this.options.storage.getSigningKeys()
|
|
423
|
+
);
|
|
424
|
+
if (keys.length === 0) {
|
|
425
|
+
await this.ensureActiveSigningKey();
|
|
426
|
+
return this.getJwks();
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
keys: await Promise.all(keys.map((key) => exportSigningKeyToJwk(key)))
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
async ensureActiveSigningKey() {
|
|
433
|
+
const existing = getActiveSigningKeys(
|
|
434
|
+
await this.options.storage.getSigningKeys()
|
|
435
|
+
);
|
|
436
|
+
const current = existing[0];
|
|
437
|
+
if (current) {
|
|
438
|
+
return current;
|
|
439
|
+
}
|
|
440
|
+
const key = await createSigningKey(this.options.signing);
|
|
441
|
+
await this.options.storage.addSigningKey(key);
|
|
442
|
+
return key;
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
function jsonResponse(body, status = 200) {
|
|
446
|
+
return new Response(JSON.stringify(body), {
|
|
447
|
+
status,
|
|
448
|
+
headers: {
|
|
449
|
+
"content-type": "application/json"
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
function getBasicCredentials(header) {
|
|
454
|
+
if (!header) {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
const [scheme, value] = header.split(/\s+/, 2);
|
|
458
|
+
if (scheme?.toLowerCase() !== "basic" || !value) {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
const decoded = Buffer.from(value, "base64").toString("utf8");
|
|
462
|
+
const separatorIndex = decoded.indexOf(":");
|
|
463
|
+
if (separatorIndex === -1) {
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
return {
|
|
467
|
+
clientId: decoded.slice(0, separatorIndex),
|
|
468
|
+
clientSecret: decoded.slice(separatorIndex + 1)
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/storage/memory.ts
|
|
473
|
+
function memoryStorage() {
|
|
474
|
+
const clients = /* @__PURE__ */ new Map();
|
|
475
|
+
const signingKeys = /* @__PURE__ */ new Map();
|
|
476
|
+
return {
|
|
477
|
+
async findClient(clientId) {
|
|
478
|
+
return clients.get(clientId) ?? null;
|
|
479
|
+
},
|
|
480
|
+
async createClient(client) {
|
|
481
|
+
clients.set(client.clientId, cloneClient(client));
|
|
482
|
+
},
|
|
483
|
+
async deleteClient(clientId) {
|
|
484
|
+
clients.delete(clientId);
|
|
485
|
+
},
|
|
486
|
+
async listClients() {
|
|
487
|
+
return [...clients.values()].map(cloneClient);
|
|
488
|
+
},
|
|
489
|
+
async touchClient(clientId, lastSeenAt) {
|
|
490
|
+
const client = clients.get(clientId);
|
|
491
|
+
if (!client) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
clients.set(clientId, {
|
|
495
|
+
...client,
|
|
496
|
+
lastSeenAt
|
|
497
|
+
});
|
|
498
|
+
},
|
|
499
|
+
async getSigningKeys() {
|
|
500
|
+
return [...signingKeys.values()].map(cloneSigningKey);
|
|
501
|
+
},
|
|
502
|
+
async addSigningKey(key) {
|
|
503
|
+
signingKeys.set(key.keyId, cloneSigningKey(key));
|
|
504
|
+
},
|
|
505
|
+
async retireKey(keyId) {
|
|
506
|
+
const key = signingKeys.get(keyId);
|
|
507
|
+
if (!key) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
signingKeys.set(keyId, {
|
|
511
|
+
...key,
|
|
512
|
+
retiredAt: /* @__PURE__ */ new Date()
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
function cloneClient(client) {
|
|
518
|
+
return {
|
|
519
|
+
...client,
|
|
520
|
+
scopes: [...client.scopes],
|
|
521
|
+
createdAt: new Date(client.createdAt),
|
|
522
|
+
lastSeenAt: client.lastSeenAt ? new Date(client.lastSeenAt) : null
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
function cloneSigningKey(key) {
|
|
526
|
+
return {
|
|
527
|
+
...key,
|
|
528
|
+
createdAt: new Date(key.createdAt),
|
|
529
|
+
retiredAt: key.retiredAt ? new Date(key.retiredAt) : null
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
export {
|
|
533
|
+
createGuard,
|
|
534
|
+
createProvider,
|
|
535
|
+
memoryStorage
|
|
536
|
+
};
|
|
537
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/token.ts","../src/core/cache.ts","../src/core/scopes.ts","../src/guard/index.ts","../src/provider/index.ts","../src/core/crypto.ts","../src/core/keys.ts","../src/storage/memory.ts"],"sourcesContent":["import { errors, type JWTPayload, jwtVerify } from \"jose\";\n\nimport { defaultJwksCache, type JwksCache } from \"./cache\";\nimport { AuthError } from \"./errors\";\nimport { hasRequiredScopes, parseScopes } from \"./scopes\";\n\nconst DEFAULT_CLOCK_TOLERANCE_SECONDS = 5;\n\nexport interface GuardOptions {\n issuer: string;\n jwksUrl?: string;\n audience?: string | string[];\n scopes?: string[];\n env?: string;\n clockTolerance?: number;\n jwksCache?: JwksCache;\n}\n\nexport interface ServiceIdentity {\n clientId: string;\n name: string | null;\n scopes: string[];\n env: string | null;\n raw: JWTPayload & Record<string, unknown>;\n}\n\nexport function deriveJwksUrl(issuer: string): string {\n const normalized = issuer.endsWith(\"/\") ? issuer.slice(0, -1) : issuer;\n return `${normalized}/.well-known/jwks.json`;\n}\n\nexport async function verifyToken(\n token: string,\n options: GuardOptions,\n): Promise<ServiceIdentity> {\n if (!token) {\n throw new AuthError(\"missing_token\", \"Missing bearer token\", 401);\n }\n\n const jwksUrl = options.jwksUrl ?? deriveJwksUrl(options.issuer);\n const jwksCache = options.jwksCache ?? defaultJwksCache;\n\n try {\n return await verifyWithCache(token, options, jwksUrl, jwksCache);\n } catch (error) {\n if (!shouldRetryWithFreshJwks(error)) {\n throw normalizeVerifyError(error);\n }\n\n try {\n return await verifyWithCache(token, options, jwksUrl, jwksCache, true);\n } catch (retryError) {\n throw normalizeVerifyError(retryError);\n }\n }\n}\n\nasync function verifyWithCache(\n token: string,\n options: GuardOptions,\n jwksUrl: string,\n jwksCache: JwksCache,\n forceRefresh = false,\n): Promise<ServiceIdentity> {\n const jwkSet = await jwksCache.get(jwksUrl, forceRefresh);\n const { payload } = await jwtVerify(token, jwkSet, {\n issuer: options.issuer,\n audience: options.audience,\n algorithms: [\"RS256\", \"ES256\"],\n clockTolerance: options.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SECONDS,\n });\n\n if (typeof payload.sub !== \"string\" || payload.sub.length === 0) {\n throw new AuthError(\"invalid_token\", \"Token subject is missing\", 401);\n }\n\n if (options.env && payload.env !== options.env) {\n throw new AuthError(\"invalid_env\", \"Token environment does not match\", 403);\n }\n\n if (\n options.scopes?.length &&\n !hasRequiredScopes(getScopeClaim(payload), options.scopes)\n ) {\n throw new AuthError(\n \"invalid_scope\",\n \"Token is missing required scopes\",\n 403,\n );\n }\n\n return {\n clientId: payload.sub,\n name: typeof payload.name === \"string\" ? payload.name : null,\n scopes: parseScopes(getScopeClaim(payload)),\n env: typeof payload.env === \"string\" ? payload.env : null,\n raw: payload as JWTPayload & Record<string, unknown>,\n };\n}\n\nfunction shouldRetryWithFreshJwks(error: unknown): boolean {\n return (\n error instanceof errors.JWKSNoMatchingKey ||\n error instanceof errors.JWSSignatureVerificationFailed\n );\n}\n\nfunction normalizeVerifyError(error: unknown): AuthError {\n if (error instanceof AuthError) {\n return error;\n }\n\n if (error instanceof errors.JWTClaimValidationFailed) {\n if (error.claim === \"iss\") {\n return new AuthError(\n \"invalid_issuer\",\n \"Token issuer does not match\",\n 401,\n error,\n );\n }\n\n if (error.claim === \"aud\") {\n return new AuthError(\n \"invalid_audience\",\n \"Token audience does not match\",\n 403,\n error,\n );\n }\n }\n\n if (\n error instanceof errors.JOSEError ||\n error instanceof errors.JWTInvalid ||\n error instanceof errors.JWTExpired\n ) {\n return new AuthError(\n \"invalid_token\",\n \"Token verification failed\",\n 401,\n error,\n );\n }\n\n return new AuthError(\n \"invalid_token\",\n \"Token verification failed\",\n 401,\n error,\n );\n}\n\nfunction getScopeClaim(payload: JWTPayload): string | undefined {\n return typeof payload.scope === \"string\" ? payload.scope : undefined;\n}\n","import {\n createLocalJWKSet,\n type JSONWebKeySet,\n type JWTVerifyGetKey,\n} from \"jose\";\n\nimport { AuthError } from \"./errors\";\n\ninterface CacheEntry {\n expiresAt: number;\n jwks: JSONWebKeySet;\n jwkSet: JWTVerifyGetKey;\n}\n\ninterface FetchState {\n promise: Promise<CacheEntry>;\n}\n\nexport interface JwksCacheOptions {\n ttlMs?: number;\n}\n\nexport class JwksCache {\n private readonly ttlMs: number;\n private readonly entries = new Map<string, CacheEntry>();\n private readonly inflight = new Map<string, FetchState>();\n\n constructor(options: JwksCacheOptions = {}) {\n this.ttlMs = options.ttlMs ?? 10 * 60 * 1000;\n }\n\n async get(url: string, forceRefresh = false): Promise<JWTVerifyGetKey> {\n const now = Date.now();\n const cached = this.entries.get(url);\n\n if (!forceRefresh && cached && cached.expiresAt > now) {\n return cached.jwkSet;\n }\n\n const inflight = this.inflight.get(url);\n if (inflight) {\n const entry = await inflight.promise;\n return entry.jwkSet;\n }\n\n const promise = this.fetchAndCache(url);\n this.inflight.set(url, { promise });\n\n try {\n const entry = await promise;\n return entry.jwkSet;\n } finally {\n this.inflight.delete(url);\n }\n }\n\n clear(url?: string): void {\n if (url) {\n this.entries.delete(url);\n this.inflight.delete(url);\n return;\n }\n\n this.entries.clear();\n this.inflight.clear();\n }\n\n private async fetchAndCache(url: string): Promise<CacheEntry> {\n let response: Response;\n\n try {\n response = await fetch(url);\n } catch (error) {\n throw new AuthError(\n \"jwks_fetch_failed\",\n `Failed to fetch JWKS from ${url}`,\n 401,\n error,\n );\n }\n\n if (!response.ok) {\n throw new AuthError(\n \"jwks_fetch_failed\",\n `Failed to fetch JWKS from ${url}`,\n 401,\n );\n }\n\n const json = (await response.json()) as JSONWebKeySet;\n if (!json || !Array.isArray(json.keys)) {\n throw new AuthError(\n \"jwks_fetch_failed\",\n `Invalid JWKS payload from ${url}`,\n 401,\n );\n }\n\n const entry: CacheEntry = {\n jwks: json,\n jwkSet: createLocalJWKSet(json),\n expiresAt: Date.now() + this.ttlMs,\n };\n\n this.entries.set(url, entry);\n\n return entry;\n }\n}\n\nexport const defaultJwksCache = new JwksCache();\n","export function parseScopes(scope: string | undefined): string[] {\n if (!scope) {\n return [];\n }\n\n return [\n ...new Set(\n scope\n .split(/\\s+/)\n .map((value) => value.trim())\n .filter(Boolean),\n ),\n ];\n}\n\nexport function hasRequiredScopes(\n tokenScope: string | undefined,\n requiredScopes: string[],\n): boolean {\n const tokenScopes = new Set(parseScopes(tokenScope));\n\n return requiredScopes.every((scope) => tokenScopes.has(scope));\n}\n","import type { GuardOptions, ServiceIdentity } from \"../core/token\";\nimport { verifyToken } from \"../core/token\";\n\nexport type { GuardOptions, ServiceIdentity } from \"../core/token\";\n\nexport interface Guard {\n verify(token: string): Promise<ServiceIdentity>;\n}\n\nexport function createGuard(options: GuardOptions): Guard {\n return {\n verify(token: string) {\n return verifyToken(token, options);\n },\n };\n}\n","import { createPrivateKey } from \"node:crypto\";\n\nimport { type JSONWebKeySet, SignJWT } from \"jose\";\nimport { v7 } from \"uuid\";\n\nimport { generateSecret, hashSecret, verifySecret } from \"../core/crypto\";\nimport {\n createSigningKey,\n exportSigningKeyToJwk,\n getActiveSigningKeys,\n type SigningAlgorithm,\n} from \"../core/keys\";\nimport { parseScopes } from \"../core/scopes\";\nimport type {\n ServiceClient,\n SigningKey,\n StorageAdapter,\n} from \"../storage/interface\";\n\nexport interface ProviderOptions {\n issuer: string;\n storage: StorageAdapter;\n signing?: {\n algorithm?: SigningAlgorithm;\n privateKey?: string;\n keyId?: string;\n };\n token?: {\n ttl?: number;\n };\n env?: string;\n}\n\nexport interface CreateProviderClientInput {\n name: string;\n scopes?: string[];\n}\n\nexport interface CreatedProviderClient {\n clientId: string;\n clientSecret: string;\n}\n\nexport interface Provider {\n handle(request: Request): Promise<Response>;\n clients: {\n create(input: CreateProviderClientInput): Promise<CreatedProviderClient>;\n list(): Promise<ServiceClient[]>;\n revoke(clientId: string): Promise<void>;\n };\n}\n\nconst DEFAULT_TOKEN_TTL_SECONDS = 300;\n\nexport function createProvider(options: ProviderOptions): Provider {\n const provider = new TrustlineProvider(options);\n\n return {\n handle(request) {\n return provider.handle(request);\n },\n clients: {\n create(input) {\n return provider.createClient(input);\n },\n list() {\n return provider.listClients();\n },\n revoke(clientId) {\n return provider.revokeClient(clientId);\n },\n },\n };\n}\n\nclass TrustlineProvider {\n constructor(private readonly options: ProviderOptions) {}\n\n async handle(request: Request): Promise<Response> {\n const url = new URL(request.url);\n\n if (\n request.method === \"GET\" &&\n url.pathname.endsWith(\"/.well-known/jwks.json\")\n ) {\n return this.handleJwks();\n }\n\n if (request.method === \"POST\" && url.pathname.endsWith(\"/token\")) {\n return this.handleToken(request);\n }\n\n return jsonResponse({ error: \"not_found\", message: \"Not found\" }, 404);\n }\n\n async createClient(\n input: CreateProviderClientInput,\n ): Promise<CreatedProviderClient> {\n const clientSecret = generateSecret();\n const client: ServiceClient = {\n id: v7(),\n clientId: `svc_${v7().replaceAll(\"-\", \"\")}`,\n clientSecret: await hashSecret(clientSecret),\n name: input.name,\n scopes: parseScopes(input.scopes?.join(\" \")),\n createdAt: new Date(),\n lastSeenAt: null,\n };\n\n await this.options.storage.createClient(client);\n\n return {\n clientId: client.clientId,\n clientSecret,\n };\n }\n\n listClients(): Promise<ServiceClient[]> {\n return this.options.storage.listClients();\n }\n\n revokeClient(clientId: string): Promise<void> {\n return this.options.storage.deleteClient(clientId);\n }\n\n private async handleJwks(): Promise<Response> {\n const jwks = await this.getJwks();\n return jsonResponse(jwks);\n }\n\n private async handleToken(request: Request): Promise<Response> {\n const contentType = request.headers.get(\"content-type\") ?? \"\";\n if (!contentType.includes(\"application/x-www-form-urlencoded\")) {\n return jsonResponse(\n {\n error: \"invalid_request\",\n error_description: \"Expected application/x-www-form-urlencoded body\",\n },\n 400,\n );\n }\n\n const body = new URLSearchParams(await request.text());\n const grantType = body.get(\"grant_type\");\n\n if (grantType !== \"client_credentials\") {\n return jsonResponse(\n {\n error: \"unsupported_grant_type\",\n error_description: \"Only client_credentials is supported\",\n },\n 400,\n );\n }\n\n const credentials = getBasicCredentials(\n request.headers.get(\"authorization\"),\n ) ?? {\n clientId: body.get(\"client_id\"),\n clientSecret: body.get(\"client_secret\"),\n };\n\n if (!credentials.clientId || !credentials.clientSecret) {\n return jsonResponse(\n {\n error: \"invalid_client\",\n error_description: \"Missing client credentials\",\n },\n 401,\n );\n }\n\n const client = await this.options.storage.findClient(credentials.clientId);\n if (\n !client ||\n !(await verifySecret(credentials.clientSecret, client.clientSecret))\n ) {\n return jsonResponse(\n {\n error: \"invalid_client\",\n error_description: \"Client authentication failed\",\n },\n 401,\n );\n }\n\n const token = await this.issueAccessToken({\n audience: body.get(\"audience\") ?? undefined,\n client,\n });\n\n await this.options.storage.touchClient(client.clientId, new Date());\n\n return jsonResponse({\n access_token: token,\n token_type: \"Bearer\",\n expires_in: this.options.token?.ttl ?? DEFAULT_TOKEN_TTL_SECONDS,\n scope: client.scopes.join(\" \"),\n });\n }\n\n private async issueAccessToken(input: {\n audience?: string;\n client: ServiceClient;\n }): Promise<string> {\n const signingKey = await this.ensureActiveSigningKey();\n const privateKey = createPrivateKey(signingKey.privateKey);\n const ttl = this.options.token?.ttl ?? DEFAULT_TOKEN_TTL_SECONDS;\n\n const jwt = new SignJWT({\n name: input.client.name,\n scope: input.client.scopes.join(\" \"),\n ...(this.options.env ? { env: this.options.env } : {}),\n })\n .setProtectedHeader({\n alg: signingKey.algorithm,\n kid: signingKey.keyId,\n })\n .setIssuer(this.options.issuer)\n .setSubject(input.client.clientId)\n .setJti(v7())\n .setIssuedAt()\n .setExpirationTime(`${ttl}s`);\n\n if (input.audience) {\n jwt.setAudience(input.audience);\n }\n\n return jwt.sign(privateKey);\n }\n\n private async getJwks(): Promise<JSONWebKeySet> {\n const keys = getActiveSigningKeys(\n await this.options.storage.getSigningKeys(),\n );\n if (keys.length === 0) {\n await this.ensureActiveSigningKey();\n return this.getJwks();\n }\n\n return {\n keys: await Promise.all(keys.map((key) => exportSigningKeyToJwk(key))),\n };\n }\n\n private async ensureActiveSigningKey(): Promise<SigningKey> {\n const existing = getActiveSigningKeys(\n await this.options.storage.getSigningKeys(),\n );\n const current = existing[0];\n if (current) {\n return current;\n }\n\n const key = await createSigningKey(this.options.signing);\n await this.options.storage.addSigningKey(key);\n return key;\n }\n}\n\nfunction jsonResponse(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: {\n \"content-type\": \"application/json\",\n },\n });\n}\n\nfunction getBasicCredentials(header: string | null): {\n clientId: string | null;\n clientSecret: string | null;\n} | null {\n if (!header) {\n return null;\n }\n\n const [scheme, value] = header.split(/\\s+/, 2);\n if (scheme?.toLowerCase() !== \"basic\" || !value) {\n return null;\n }\n\n const decoded = Buffer.from(value, \"base64\").toString(\"utf8\");\n const separatorIndex = decoded.indexOf(\":\");\n\n if (separatorIndex === -1) {\n return null;\n }\n\n return {\n clientId: decoded.slice(0, separatorIndex),\n clientSecret: decoded.slice(separatorIndex + 1),\n };\n}\n","import { randomBytes } from \"node:crypto\";\n\nimport { compare, hash } from \"bcryptjs\";\n\nexport async function hashSecret(secret: string): Promise<string> {\n return hash(secret, 10);\n}\n\nexport async function verifySecret(\n secret: string,\n hashedSecret: string,\n): Promise<boolean> {\n return compare(secret, hashedSecret);\n}\n\nexport function generateSecret(length = 32): string {\n return randomBytes(length).toString(\"base64url\");\n}\n","import { createPrivateKey, createPublicKey, randomUUID } from \"node:crypto\";\n\nimport {\n exportJWK,\n exportPKCS8,\n exportSPKI,\n generateKeyPair,\n type JWK,\n} from \"jose\";\n\nimport type { SigningKey } from \"../storage/interface\";\n\nexport type SigningAlgorithm = \"ES256\" | \"RS256\";\n\nexport interface CreateSigningKeyOptions {\n algorithm?: SigningAlgorithm;\n keyId?: string;\n privateKey?: string;\n}\n\nexport async function createSigningKey(\n options: CreateSigningKeyOptions = {},\n): Promise<SigningKey> {\n const algorithm = options.algorithm ?? \"ES256\";\n const keyId = options.keyId ?? `key_${randomUUID()}`;\n\n if (options.privateKey) {\n const privateKey = createPrivateKey(options.privateKey);\n const publicKey = createPublicKey(privateKey);\n\n return {\n keyId,\n algorithm,\n privateKey: privateKey.export({\n type: \"pkcs8\",\n format: \"pem\",\n }) as string,\n publicKey: publicKey.export({\n type: \"spki\",\n format: \"pem\",\n }) as string,\n createdAt: new Date(),\n retiredAt: null,\n };\n }\n\n const { privateKey, publicKey } = await generateKeyPair(algorithm, {\n extractable: true,\n });\n\n return {\n keyId,\n algorithm,\n privateKey: await exportPKCS8(privateKey),\n publicKey: await exportSPKI(publicKey),\n createdAt: new Date(),\n retiredAt: null,\n };\n}\n\nexport async function exportSigningKeyToJwk(\n signingKey: SigningKey,\n): Promise<JWK> {\n const publicKey = createPublicKey(signingKey.publicKey);\n const jwk = await exportJWK(publicKey);\n\n return {\n ...jwk,\n use: \"sig\",\n kid: signingKey.keyId,\n alg: signingKey.algorithm,\n };\n}\n\nexport function getActiveSigningKeys(keys: SigningKey[]): SigningKey[] {\n return keys.filter((key) => key.retiredAt === null);\n}\n","import type { ServiceClient, SigningKey, StorageAdapter } from \"./interface\";\n\nexport function memoryStorage(): StorageAdapter {\n const clients = new Map<string, ServiceClient>();\n const signingKeys = new Map<string, SigningKey>();\n\n return {\n async findClient(clientId) {\n return clients.get(clientId) ?? null;\n },\n async createClient(client) {\n clients.set(client.clientId, cloneClient(client));\n },\n async deleteClient(clientId) {\n clients.delete(clientId);\n },\n async listClients() {\n return [...clients.values()].map(cloneClient);\n },\n async touchClient(clientId, lastSeenAt) {\n const client = clients.get(clientId);\n if (!client) {\n return;\n }\n\n clients.set(clientId, {\n ...client,\n lastSeenAt,\n });\n },\n async getSigningKeys() {\n return [...signingKeys.values()].map(cloneSigningKey);\n },\n async addSigningKey(key) {\n signingKeys.set(key.keyId, cloneSigningKey(key));\n },\n async retireKey(keyId) {\n const key = signingKeys.get(keyId);\n if (!key) {\n return;\n }\n\n signingKeys.set(keyId, {\n ...key,\n retiredAt: new Date(),\n });\n },\n };\n}\n\nfunction cloneClient(client: ServiceClient): ServiceClient {\n return {\n ...client,\n scopes: [...client.scopes],\n createdAt: new Date(client.createdAt),\n lastSeenAt: client.lastSeenAt ? new Date(client.lastSeenAt) : null,\n };\n}\n\nfunction cloneSigningKey(key: SigningKey): SigningKey {\n return {\n ...key,\n createdAt: new Date(key.createdAt),\n retiredAt: key.retiredAt ? new Date(key.retiredAt) : null,\n };\n}\n"],"mappings":"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","names":["createPrivateKey","privateKey","publicKey","createPrivateKey"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
type SigningAlgorithm = "ES256" | "RS256";
|
|
2
|
+
|
|
3
|
+
interface ServiceClient {
|
|
4
|
+
id: string;
|
|
5
|
+
clientId: string;
|
|
6
|
+
clientSecret: string;
|
|
7
|
+
name: string;
|
|
8
|
+
scopes: string[];
|
|
9
|
+
createdAt: Date;
|
|
10
|
+
lastSeenAt: Date | null;
|
|
11
|
+
}
|
|
12
|
+
interface SigningKey {
|
|
13
|
+
keyId: string;
|
|
14
|
+
algorithm: SigningAlgorithm;
|
|
15
|
+
privateKey: string;
|
|
16
|
+
publicKey: string;
|
|
17
|
+
createdAt: Date;
|
|
18
|
+
retiredAt: Date | null;
|
|
19
|
+
}
|
|
20
|
+
interface StorageAdapter {
|
|
21
|
+
findClient(clientId: string): Promise<ServiceClient | null>;
|
|
22
|
+
createClient(client: ServiceClient): Promise<void>;
|
|
23
|
+
deleteClient(clientId: string): Promise<void>;
|
|
24
|
+
listClients(): Promise<ServiceClient[]>;
|
|
25
|
+
touchClient(clientId: string, lastSeenAt: Date): Promise<void>;
|
|
26
|
+
getSigningKeys(): Promise<SigningKey[]>;
|
|
27
|
+
addSigningKey(key: SigningKey): Promise<void>;
|
|
28
|
+
retireKey(keyId: string): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
interface SqlStorageOptions {
|
|
31
|
+
tablePrefix?: string;
|
|
32
|
+
tables?: {
|
|
33
|
+
clients?: string;
|
|
34
|
+
signingKeys?: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type { SqlStorageOptions as S, StorageAdapter as a, ServiceClient as b, SigningKey as c, SigningAlgorithm as d };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
type SigningAlgorithm = "ES256" | "RS256";
|
|
2
|
+
|
|
3
|
+
interface ServiceClient {
|
|
4
|
+
id: string;
|
|
5
|
+
clientId: string;
|
|
6
|
+
clientSecret: string;
|
|
7
|
+
name: string;
|
|
8
|
+
scopes: string[];
|
|
9
|
+
createdAt: Date;
|
|
10
|
+
lastSeenAt: Date | null;
|
|
11
|
+
}
|
|
12
|
+
interface SigningKey {
|
|
13
|
+
keyId: string;
|
|
14
|
+
algorithm: SigningAlgorithm;
|
|
15
|
+
privateKey: string;
|
|
16
|
+
publicKey: string;
|
|
17
|
+
createdAt: Date;
|
|
18
|
+
retiredAt: Date | null;
|
|
19
|
+
}
|
|
20
|
+
interface StorageAdapter {
|
|
21
|
+
findClient(clientId: string): Promise<ServiceClient | null>;
|
|
22
|
+
createClient(client: ServiceClient): Promise<void>;
|
|
23
|
+
deleteClient(clientId: string): Promise<void>;
|
|
24
|
+
listClients(): Promise<ServiceClient[]>;
|
|
25
|
+
touchClient(clientId: string, lastSeenAt: Date): Promise<void>;
|
|
26
|
+
getSigningKeys(): Promise<SigningKey[]>;
|
|
27
|
+
addSigningKey(key: SigningKey): Promise<void>;
|
|
28
|
+
retireKey(keyId: string): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
interface SqlStorageOptions {
|
|
31
|
+
tablePrefix?: string;
|
|
32
|
+
tables?: {
|
|
33
|
+
clients?: string;
|
|
34
|
+
signingKeys?: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type { SqlStorageOptions as S, StorageAdapter as a, ServiceClient as b, SigningKey as c, SigningAlgorithm as d };
|