@frontmcp/auth 1.10.16 → 1.11.0-rc.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/browser/authorities/authorities.context.mjs +115 -0
- package/browser/authorities/authorities.engine.mjs +272 -0
- package/browser/authorities/authorities.errors.mjs +59 -0
- package/browser/authorities/authorities.evaluator.mjs +225 -0
- package/browser/authorities/authorities.metadata-augment.mjs +18 -0
- package/browser/authorities/authorities.registry.mjs +112 -0
- package/browser/authorities/authorities.schema.mjs +92 -0
- package/browser/authorities/authorities.scope-mapping.mjs +30 -0
- package/browser/authorities/authorities.validation.mjs +208 -0
- package/browser/authorities/index.mjs +43 -0
- package/browser/authorization/authorization.class.mjs +252 -0
- package/browser/authorization/authorization.types.mjs +64 -0
- package/browser/authorization/dcr-client.registry.mjs +222 -0
- package/browser/authorization/index.mjs +41 -0
- package/browser/authorization/orchestrated.accessor.mjs +86 -0
- package/browser/authorization/orchestrated.authorization.mjs +289 -0
- package/browser/authorization/public.authorization.mjs +124 -0
- package/browser/authorization/transparent.authorization.mjs +130 -0
- package/browser/cimd/cimd-redis.cache.mjs +231 -0
- package/browser/cimd/cimd.cache.mjs +215 -0
- package/browser/cimd/cimd.errors.mjs +142 -0
- package/browser/cimd/cimd.logger.mjs +15 -0
- package/browser/cimd/cimd.service.mjs +376 -0
- package/browser/cimd/cimd.types.mjs +228 -0
- package/browser/cimd/cimd.validator.mjs +217 -0
- package/browser/cimd/index.mjs +63 -0
- package/browser/common/auth-logger.interface.mjs +14 -0
- package/browser/common/index.mjs +6 -0
- package/browser/common/jwt.types.mjs +38 -0
- package/browser/common/session-user.types.mjs +0 -0
- package/browser/common/session.types.mjs +58 -0
- package/browser/common/zod-utils.mjs +0 -0
- package/browser/consent/consent.types.mjs +94 -0
- package/browser/consent/index.mjs +17 -0
- package/browser/context/frontmcp-auth-context.factory.mjs +29 -0
- package/browser/context/frontmcp-auth-context.impl.mjs +112 -0
- package/browser/context/index.mjs +7 -0
- package/browser/detection/auth-provider-detection.mjs +176 -0
- package/browser/detection/index.mjs +19 -0
- package/browser/errors/auth-internal.error.mjs +42 -0
- package/browser/errors/auth-internal.errors.mjs +150 -0
- package/browser/errors/index.mjs +53 -0
- package/browser/fetch/fetch-credential-middleware.mjs +91 -0
- package/browser/fetch/index.mjs +19 -0
- package/browser/index.mjs +573 -0
- package/browser/jwks/index.mjs +9 -0
- package/browser/jwks/jose.loader.mjs +28 -0
- package/browser/jwks/jwks.service.mjs +521 -0
- package/browser/jwks/jwks.utils.mjs +33 -0
- package/browser/options/app-auth.schema.mjs +18 -0
- package/browser/options/index.mjs +89 -0
- package/browser/options/orchestrated.schema.mjs +128 -0
- package/browser/options/public.schema.mjs +39 -0
- package/browser/options/schema.mjs +16 -0
- package/browser/options/shared.schemas.mjs +506 -0
- package/browser/options/static.schema.mjs +41 -0
- package/browser/options/transparent.schema.mjs +43 -0
- package/browser/options/utils.mjs +47 -0
- package/browser/people/index.mjs +15 -0
- package/browser/people/loopback-token.auth.mjs +115 -0
- package/browser/people/people-auth.options.mjs +37 -0
- package/browser/session/authorization-vault.mjs +279 -0
- package/browser/session/authorization.store.mjs +336 -0
- package/browser/session/consent.store.mjs +34 -0
- package/browser/session/credential-resume-link.mjs +43 -0
- package/browser/session/credentials-accessor.impl.mjs +54 -0
- package/browser/session/credentials-accessor.mjs +5 -0
- package/browser/session/encrypted-authorization-vault.mjs +405 -0
- package/browser/session/federated-auth.session.mjs +176 -0
- package/browser/session/incremental-auth-ticket.mjs +42 -0
- package/browser/session/index.mjs +266 -0
- package/browser/session/orchestrated-token.crypto.mjs +47 -0
- package/browser/session/orchestrated-token.store.mjs +286 -0
- package/browser/session/pending-login.mjs +37 -0
- package/browser/session/redis-session.store.mjs +255 -0
- package/browser/session/secure-store-accessor.impl.mjs +78 -0
- package/browser/session/secure-store-accessor.mjs +5 -0
- package/browser/session/secure-store-backends.mjs +111 -0
- package/browser/session/secure-store.factory.mjs +77 -0
- package/browser/session/session-credential-vault.factory.mjs +18 -0
- package/browser/session/session-credential-vault.mjs +243 -0
- package/browser/session/session-crypto.mjs +39 -0
- package/browser/session/session-rate-limiter.mjs +126 -0
- package/browser/session/session.transport.mjs +16 -0
- package/browser/session/signin-binding.mjs +30 -0
- package/browser/session/storage/in-memory-authorization-vault.mjs +28 -0
- package/browser/session/storage/index.mjs +14 -0
- package/browser/session/storage/storage-authorization-vault.mjs +301 -0
- package/browser/session/storage/storage-token-store.mjs +83 -0
- package/browser/session/storage-authorization.store.mjs +134 -0
- package/browser/session/storage-consent.store.mjs +33 -0
- package/browser/session/storage-federated-auth.session.mjs +55 -0
- package/browser/session/storage-orchestrated-token.store.mjs +197 -0
- package/browser/session/token-storage.factory.mjs +58 -0
- package/browser/session/token.refresh.mjs +36 -0
- package/browser/session/token.vault.mjs +61 -0
- package/browser/session/transport-session.types.mjs +108 -0
- package/browser/session/utils/auth-token.utils.mjs +58 -0
- package/browser/session/utils/index.mjs +23 -0
- package/browser/session/utils/session-crypto.utils.mjs +49 -0
- package/browser/session/utils/tiny-ttl-cache.mjs +46 -0
- package/browser/session/vault-encryption.mjs +187 -0
- package/browser/session/vercel-kv-session.store.mjs +230 -0
- package/browser/shared-server/index.mjs +17 -0
- package/browser/shared-server/shared-server-identity.mjs +55 -0
- package/browser/ui/base-layout.mjs +184 -0
- package/browser/ui/index.mjs +51 -0
- package/browser/ui/local-login.helper.mjs +46 -0
- package/browser/ui/templates.mjs +482 -0
- package/browser/utils/audience.validator.mjs +110 -0
- package/browser/utils/authorization-id.utils.mjs +10 -0
- package/browser/utils/index.mjs +45 -0
- package/browser/utils/redirect-uri.utils.mjs +22 -0
- package/browser/utils/scope-grant.utils.mjs +36 -0
- package/browser/utils/token-scopes.utils.mjs +12 -0
- package/browser/utils/www-authenticate.utils.mjs +164 -0
- package/browser/vault/auth-providers.accessor.impl.mjs +236 -0
- package/browser/vault/auth-providers.accessor.mjs +5 -0
- package/browser/vault/auth-providers.registry.mjs +140 -0
- package/browser/vault/auth-providers.types.mjs +47 -0
- package/browser/vault/auth-providers.vault.mjs +206 -0
- package/browser/vault/credential-cache.mjs +187 -0
- package/browser/vault/credential-helpers.mjs +15 -0
- package/browser/vault/credential-loaders/eager-loader.mjs +91 -0
- package/browser/vault/credential-loaders/lazy-loader.mjs +120 -0
- package/browser/vault/index.mjs +35 -0
- package/cimd/cimd.types.d.ts +1 -1
- package/cimd/cimd.types.d.ts.map +1 -1
- package/common/jwt.types.d.ts +1 -1
- package/common/jwt.types.d.ts.map +1 -1
- package/common/session.types.d.ts +9 -1
- package/common/session.types.d.ts.map +1 -1
- package/consent/consent.types.d.ts +1 -1
- package/consent/consent.types.d.ts.map +1 -1
- package/detection/auth-provider-detection.d.ts +1 -1
- package/detection/auth-provider-detection.d.ts.map +1 -1
- package/errors/auth-internal.errors.d.ts +6 -0
- package/errors/auth-internal.errors.d.ts.map +1 -1
- package/errors/index.d.ts +1 -1
- package/errors/index.d.ts.map +1 -1
- package/esm/index.mjs +2 -2
- package/esm/package.json +21 -5
- package/index.d.ts +5 -1
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- package/options/app-auth.schema.d.ts +19 -1
- package/options/app-auth.schema.d.ts.map +1 -1
- package/options/orchestrated.schema.d.ts +37 -1
- package/options/orchestrated.schema.d.ts.map +1 -1
- package/options/public.schema.d.ts +1 -1
- package/options/public.schema.d.ts.map +1 -1
- package/options/schema.d.ts +19 -1
- package/options/schema.d.ts.map +1 -1
- package/options/shared.schemas.d.ts +10 -14
- package/options/shared.schemas.d.ts.map +1 -1
- package/options/static.schema.d.ts +1 -1
- package/options/static.schema.d.ts.map +1 -1
- package/options/transparent.schema.d.ts +1 -1
- package/options/transparent.schema.d.ts.map +1 -1
- package/package.json +21 -5
- package/people/index.d.ts +3 -0
- package/people/index.d.ts.map +1 -0
- package/people/loopback-token.auth.d.ts +75 -0
- package/people/loopback-token.auth.d.ts.map +1 -0
- package/people/people-auth.options.d.ts +36 -0
- package/people/people-auth.options.d.ts.map +1 -0
- package/session/authorization-vault.d.ts +1 -1
- package/session/authorization-vault.d.ts.map +1 -1
- package/session/encrypted-authorization-vault.d.ts +1 -1
- package/session/encrypted-authorization-vault.d.ts.map +1 -1
- package/session/secure-store.factory.d.ts.map +1 -1
- package/shared-server/index.d.ts +3 -0
- package/shared-server/index.d.ts.map +1 -0
- package/shared-server/shared-server-identity.d.ts +26 -0
- package/shared-server/shared-server-identity.d.ts.map +1 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
// libs/auth/src/session/storage/storage-authorization-vault.ts
|
|
2
|
+
import { randomUUID, TypedStorage } from "@frontmcp/utils";
|
|
3
|
+
import { authorizationVaultEntrySchema } from "../authorization-vault.mjs";
|
|
4
|
+
var StorageAuthorizationVault = class {
|
|
5
|
+
storage;
|
|
6
|
+
namespace;
|
|
7
|
+
pendingAuthTtlMs;
|
|
8
|
+
constructor(storage, options = {}) {
|
|
9
|
+
this.namespace = options.namespace ?? "vault";
|
|
10
|
+
this.pendingAuthTtlMs = options.pendingAuthTtlMs ?? 10 * 60 * 1e3;
|
|
11
|
+
const namespacedStorage = this.isNamespacedStorage(storage) ? storage.namespace(this.namespace) : storage;
|
|
12
|
+
this.storage = new TypedStorage(namespacedStorage, {
|
|
13
|
+
schema: options.validateOnRead ? authorizationVaultEntrySchema : void 0,
|
|
14
|
+
throwOnInvalid: false
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
// ============================================
|
|
18
|
+
// Core CRUD Methods
|
|
19
|
+
// ============================================
|
|
20
|
+
async create(params) {
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
const entry = {
|
|
23
|
+
id: randomUUID(),
|
|
24
|
+
userSub: params.userSub,
|
|
25
|
+
userEmail: params.userEmail,
|
|
26
|
+
userName: params.userName,
|
|
27
|
+
clientId: params.clientId,
|
|
28
|
+
createdAt: now,
|
|
29
|
+
lastAccessAt: now,
|
|
30
|
+
appCredentials: {},
|
|
31
|
+
consent: params.consent,
|
|
32
|
+
federated: params.federated,
|
|
33
|
+
pendingAuths: [],
|
|
34
|
+
authorizedAppIds: params.authorizedAppIds ?? [],
|
|
35
|
+
skippedAppIds: params.skippedAppIds ?? []
|
|
36
|
+
};
|
|
37
|
+
await this.storage.set(this.key(entry.id), entry);
|
|
38
|
+
return entry;
|
|
39
|
+
}
|
|
40
|
+
async get(id) {
|
|
41
|
+
return this.storage.get(this.key(id));
|
|
42
|
+
}
|
|
43
|
+
async update(id, updates) {
|
|
44
|
+
const entry = await this.get(id);
|
|
45
|
+
if (!entry) {
|
|
46
|
+
console.warn(`[StorageAuthorizationVault] Update failed: vault entry not found for id ${id}`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const updated = {
|
|
50
|
+
...entry,
|
|
51
|
+
...updates,
|
|
52
|
+
lastAccessAt: Date.now()
|
|
53
|
+
};
|
|
54
|
+
await this.storage.set(this.key(id), updated);
|
|
55
|
+
}
|
|
56
|
+
async delete(id) {
|
|
57
|
+
await this.storage.delete(this.key(id));
|
|
58
|
+
}
|
|
59
|
+
// ============================================
|
|
60
|
+
// Consent Methods
|
|
61
|
+
// ============================================
|
|
62
|
+
async updateConsent(vaultId, consent) {
|
|
63
|
+
const entry = await this.get(vaultId);
|
|
64
|
+
if (!entry) return;
|
|
65
|
+
entry.consent = consent;
|
|
66
|
+
entry.lastAccessAt = Date.now();
|
|
67
|
+
await this.storage.set(this.key(vaultId), entry);
|
|
68
|
+
}
|
|
69
|
+
// ============================================
|
|
70
|
+
// App Authorization Methods
|
|
71
|
+
// ============================================
|
|
72
|
+
async authorizeApp(vaultId, appId) {
|
|
73
|
+
const entry = await this.get(vaultId);
|
|
74
|
+
if (!entry) return;
|
|
75
|
+
entry.skippedAppIds = entry.skippedAppIds.filter((id) => id !== appId);
|
|
76
|
+
if (!entry.authorizedAppIds.includes(appId)) {
|
|
77
|
+
entry.authorizedAppIds.push(appId);
|
|
78
|
+
}
|
|
79
|
+
entry.lastAccessAt = Date.now();
|
|
80
|
+
await this.storage.set(this.key(vaultId), entry);
|
|
81
|
+
}
|
|
82
|
+
async isAppAuthorized(vaultId, appId) {
|
|
83
|
+
const entry = await this.get(vaultId);
|
|
84
|
+
if (!entry) return false;
|
|
85
|
+
return entry.authorizedAppIds.includes(appId);
|
|
86
|
+
}
|
|
87
|
+
// ============================================
|
|
88
|
+
// Pending Auth Methods
|
|
89
|
+
// ============================================
|
|
90
|
+
async createPendingAuth(vaultId, params) {
|
|
91
|
+
const entry = await this.get(vaultId);
|
|
92
|
+
if (!entry) {
|
|
93
|
+
throw new Error(`Vault not found: ${vaultId}`);
|
|
94
|
+
}
|
|
95
|
+
const now = Date.now();
|
|
96
|
+
const pendingAuth = {
|
|
97
|
+
id: randomUUID(),
|
|
98
|
+
appId: params.appId,
|
|
99
|
+
toolId: params.toolId,
|
|
100
|
+
authUrl: params.authUrl,
|
|
101
|
+
requiredScopes: params.requiredScopes,
|
|
102
|
+
elicitId: params.elicitId,
|
|
103
|
+
createdAt: now,
|
|
104
|
+
expiresAt: now + (params.ttlMs ?? this.pendingAuthTtlMs),
|
|
105
|
+
status: "pending"
|
|
106
|
+
};
|
|
107
|
+
entry.pendingAuths.push(pendingAuth);
|
|
108
|
+
entry.lastAccessAt = now;
|
|
109
|
+
await this.storage.set(this.key(vaultId), entry);
|
|
110
|
+
return pendingAuth;
|
|
111
|
+
}
|
|
112
|
+
async getPendingAuth(vaultId, pendingAuthId) {
|
|
113
|
+
const entry = await this.get(vaultId);
|
|
114
|
+
if (!entry) return null;
|
|
115
|
+
const pendingAuth = entry.pendingAuths.find((p) => p.id === pendingAuthId);
|
|
116
|
+
if (!pendingAuth) return null;
|
|
117
|
+
if (Date.now() > pendingAuth.expiresAt && pendingAuth.status === "pending") {
|
|
118
|
+
pendingAuth.status = "expired";
|
|
119
|
+
await this.storage.set(this.key(vaultId), entry);
|
|
120
|
+
}
|
|
121
|
+
return pendingAuth;
|
|
122
|
+
}
|
|
123
|
+
async completePendingAuth(vaultId, pendingAuthId) {
|
|
124
|
+
const entry = await this.get(vaultId);
|
|
125
|
+
if (!entry) return;
|
|
126
|
+
const pendingAuth = entry.pendingAuths.find((p) => p.id === pendingAuthId);
|
|
127
|
+
if (pendingAuth) {
|
|
128
|
+
pendingAuth.status = "completed";
|
|
129
|
+
entry.skippedAppIds = entry.skippedAppIds.filter((id) => id !== pendingAuth.appId);
|
|
130
|
+
if (!entry.authorizedAppIds.includes(pendingAuth.appId)) {
|
|
131
|
+
entry.authorizedAppIds.push(pendingAuth.appId);
|
|
132
|
+
}
|
|
133
|
+
entry.lastAccessAt = Date.now();
|
|
134
|
+
await this.storage.set(this.key(vaultId), entry);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async cancelPendingAuth(vaultId, pendingAuthId) {
|
|
138
|
+
const entry = await this.get(vaultId);
|
|
139
|
+
if (!entry) return;
|
|
140
|
+
const pendingAuth = entry.pendingAuths.find((p) => p.id === pendingAuthId);
|
|
141
|
+
if (pendingAuth) {
|
|
142
|
+
pendingAuth.status = "cancelled";
|
|
143
|
+
await this.storage.set(this.key(vaultId), entry);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async getPendingAuths(vaultId) {
|
|
147
|
+
const entry = await this.get(vaultId);
|
|
148
|
+
if (!entry) return [];
|
|
149
|
+
const now = Date.now();
|
|
150
|
+
let updated = false;
|
|
151
|
+
const pending = entry.pendingAuths.filter((p) => {
|
|
152
|
+
if (now > p.expiresAt && p.status === "pending") {
|
|
153
|
+
p.status = "expired";
|
|
154
|
+
updated = true;
|
|
155
|
+
}
|
|
156
|
+
return p.status === "pending";
|
|
157
|
+
});
|
|
158
|
+
if (updated) {
|
|
159
|
+
await this.storage.set(this.key(vaultId), entry);
|
|
160
|
+
}
|
|
161
|
+
return pending;
|
|
162
|
+
}
|
|
163
|
+
// ============================================
|
|
164
|
+
// App Credential Methods
|
|
165
|
+
// ============================================
|
|
166
|
+
credentialKey(appId, providerId) {
|
|
167
|
+
return `${appId}:${providerId}`;
|
|
168
|
+
}
|
|
169
|
+
async addAppCredential(vaultId, credential) {
|
|
170
|
+
const entry = await this.get(vaultId);
|
|
171
|
+
if (!entry) return;
|
|
172
|
+
const shouldStore = await this.shouldStoreCredential(vaultId, credential.appId);
|
|
173
|
+
if (!shouldStore) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const key = this.credentialKey(credential.appId, credential.providerId);
|
|
177
|
+
entry.appCredentials[key] = credential;
|
|
178
|
+
entry.lastAccessAt = Date.now();
|
|
179
|
+
await this.storage.set(this.key(vaultId), entry);
|
|
180
|
+
}
|
|
181
|
+
async removeAppCredential(vaultId, appId, providerId) {
|
|
182
|
+
const entry = await this.get(vaultId);
|
|
183
|
+
if (!entry) return;
|
|
184
|
+
const key = this.credentialKey(appId, providerId);
|
|
185
|
+
delete entry.appCredentials[key];
|
|
186
|
+
entry.lastAccessAt = Date.now();
|
|
187
|
+
await this.storage.set(this.key(vaultId), entry);
|
|
188
|
+
}
|
|
189
|
+
async getAppCredentials(vaultId, appId) {
|
|
190
|
+
const entry = await this.get(vaultId);
|
|
191
|
+
if (!entry) return [];
|
|
192
|
+
const prefix = `${appId}:`;
|
|
193
|
+
return Object.entries(entry.appCredentials).filter(([key]) => key.startsWith(prefix)).map(([, cred]) => cred);
|
|
194
|
+
}
|
|
195
|
+
async getCredential(vaultId, appId, providerId) {
|
|
196
|
+
const entry = await this.get(vaultId);
|
|
197
|
+
if (!entry) return null;
|
|
198
|
+
const key = this.credentialKey(appId, providerId);
|
|
199
|
+
return entry.appCredentials[key] ?? null;
|
|
200
|
+
}
|
|
201
|
+
async getAllCredentials(vaultId, filterByConsent = false) {
|
|
202
|
+
const entry = await this.get(vaultId);
|
|
203
|
+
if (!entry) return [];
|
|
204
|
+
const allCredentials = Object.values(entry.appCredentials);
|
|
205
|
+
if (!filterByConsent || !entry.consent?.enabled) {
|
|
206
|
+
return allCredentials;
|
|
207
|
+
}
|
|
208
|
+
const consentedToolIds = new Set(entry.consent.selectedToolIds);
|
|
209
|
+
return allCredentials.filter((cred) => {
|
|
210
|
+
return Array.from(consentedToolIds).some((toolId) => toolId.startsWith(`${cred.appId}:`));
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
async updateCredential(vaultId, appId, providerId, updates) {
|
|
214
|
+
const entry = await this.get(vaultId);
|
|
215
|
+
if (!entry) return;
|
|
216
|
+
const key = this.credentialKey(appId, providerId);
|
|
217
|
+
const credential = entry.appCredentials[key];
|
|
218
|
+
if (!credential) return;
|
|
219
|
+
Object.assign(credential, updates);
|
|
220
|
+
entry.lastAccessAt = Date.now();
|
|
221
|
+
await this.storage.set(this.key(vaultId), entry);
|
|
222
|
+
}
|
|
223
|
+
async shouldStoreCredential(vaultId, appId, toolIds) {
|
|
224
|
+
const entry = await this.get(vaultId);
|
|
225
|
+
if (!entry) return false;
|
|
226
|
+
const consent = entry.consent;
|
|
227
|
+
if (!consent?.enabled) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
if (toolIds && toolIds.length > 0) {
|
|
231
|
+
return toolIds.some((toolId) => consent.selectedToolIds.includes(toolId));
|
|
232
|
+
}
|
|
233
|
+
const consentedToolIds = consent.selectedToolIds;
|
|
234
|
+
return consentedToolIds.some((toolId) => toolId.startsWith(`${appId}:`));
|
|
235
|
+
}
|
|
236
|
+
async invalidateCredential(vaultId, appId, providerId, reason) {
|
|
237
|
+
await this.updateCredential(vaultId, appId, providerId, {
|
|
238
|
+
isValid: false,
|
|
239
|
+
invalidReason: reason
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
async refreshOAuthCredential(vaultId, appId, providerId, tokens) {
|
|
243
|
+
const entry = await this.get(vaultId);
|
|
244
|
+
if (!entry) return;
|
|
245
|
+
const key = this.credentialKey(appId, providerId);
|
|
246
|
+
const credential = entry.appCredentials[key];
|
|
247
|
+
if (!credential || credential.credential.type !== "oauth" && credential.credential.type !== "oauth_pkce") return;
|
|
248
|
+
credential.credential.accessToken = tokens.accessToken;
|
|
249
|
+
if (tokens.refreshToken !== void 0) {
|
|
250
|
+
credential.credential.refreshToken = tokens.refreshToken;
|
|
251
|
+
}
|
|
252
|
+
if (tokens.expiresAt !== void 0) {
|
|
253
|
+
credential.credential.expiresAt = tokens.expiresAt;
|
|
254
|
+
credential.expiresAt = tokens.expiresAt;
|
|
255
|
+
}
|
|
256
|
+
credential.isValid = true;
|
|
257
|
+
credential.invalidReason = void 0;
|
|
258
|
+
entry.lastAccessAt = Date.now();
|
|
259
|
+
await this.storage.set(this.key(vaultId), entry);
|
|
260
|
+
}
|
|
261
|
+
// ============================================
|
|
262
|
+
// Cleanup
|
|
263
|
+
// ============================================
|
|
264
|
+
async cleanup() {
|
|
265
|
+
const keys = await this.storage.keys("*");
|
|
266
|
+
const now = Date.now();
|
|
267
|
+
for (const key of keys) {
|
|
268
|
+
const entry = await this.storage.get(key);
|
|
269
|
+
if (!entry) continue;
|
|
270
|
+
const originalLength = entry.pendingAuths.length;
|
|
271
|
+
entry.pendingAuths = entry.pendingAuths.filter((p) => {
|
|
272
|
+
if (now > p.expiresAt && p.status === "pending") {
|
|
273
|
+
p.status = "expired";
|
|
274
|
+
}
|
|
275
|
+
return p.status === "pending";
|
|
276
|
+
});
|
|
277
|
+
if (entry.pendingAuths.length !== originalLength) {
|
|
278
|
+
await this.storage.set(key, entry);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// ============================================
|
|
283
|
+
// Helpers
|
|
284
|
+
// ============================================
|
|
285
|
+
/**
|
|
286
|
+
* Build the storage key for a vault ID.
|
|
287
|
+
* For non-namespaced storage, includes the namespace prefix.
|
|
288
|
+
*/
|
|
289
|
+
key(id) {
|
|
290
|
+
return this.isNamespacedStorage(this.storage.raw) ? id : `${this.namespace}:${id}`;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Type guard to check if storage is a NamespacedStorage.
|
|
294
|
+
*/
|
|
295
|
+
isNamespacedStorage(storage) {
|
|
296
|
+
return "namespace" in storage && typeof storage.namespace === "function";
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
export {
|
|
300
|
+
StorageAuthorizationVault
|
|
301
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// libs/auth/src/session/storage/storage-token-store.ts
|
|
2
|
+
import { randomUUID, TypedStorage } from "@frontmcp/utils";
|
|
3
|
+
var StorageTokenStore = class {
|
|
4
|
+
storage;
|
|
5
|
+
namespace;
|
|
6
|
+
defaultTtlSeconds;
|
|
7
|
+
/** Track if original storage was namespaced to avoid double-prefixing */
|
|
8
|
+
storageIsNamespaced;
|
|
9
|
+
constructor(storage, options = {}) {
|
|
10
|
+
this.namespace = options.namespace ?? "tok";
|
|
11
|
+
this.defaultTtlSeconds = options.defaultTtlSeconds;
|
|
12
|
+
this.storageIsNamespaced = "namespace" in storage && typeof storage.namespace === "function";
|
|
13
|
+
const namespacedStorage = this.storageIsNamespaced ? storage.namespace(this.namespace) : storage;
|
|
14
|
+
this.storage = new TypedStorage(namespacedStorage);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Allocate a new unique ID for a token record.
|
|
18
|
+
*/
|
|
19
|
+
allocId() {
|
|
20
|
+
return randomUUID();
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Store an encrypted token blob.
|
|
24
|
+
*
|
|
25
|
+
* TTL is calculated from blob.exp (epoch seconds) if present.
|
|
26
|
+
* Falls back to defaultTtlSeconds if configured.
|
|
27
|
+
*
|
|
28
|
+
* @param id - Token record ID
|
|
29
|
+
* @param blob - Encrypted token blob
|
|
30
|
+
*/
|
|
31
|
+
async put(id, blob) {
|
|
32
|
+
const record = {
|
|
33
|
+
id,
|
|
34
|
+
blob,
|
|
35
|
+
updatedAt: Date.now()
|
|
36
|
+
};
|
|
37
|
+
const ttlSeconds = this.calculateTtl(blob.exp);
|
|
38
|
+
await this.storage.set(this.key(id), record, ttlSeconds ? { ttlSeconds } : void 0);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Retrieve a token record by ID.
|
|
42
|
+
*
|
|
43
|
+
* @param id - Token record ID
|
|
44
|
+
* @returns The secret record, or undefined if not found
|
|
45
|
+
*/
|
|
46
|
+
async get(id) {
|
|
47
|
+
const record = await this.storage.get(this.key(id));
|
|
48
|
+
return record ?? void 0;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Delete a token record.
|
|
52
|
+
*
|
|
53
|
+
* @param id - Token record ID
|
|
54
|
+
*/
|
|
55
|
+
async del(id) {
|
|
56
|
+
await this.storage.delete(this.key(id));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Calculate TTL in seconds from expiration timestamp.
|
|
60
|
+
*
|
|
61
|
+
* @param exp - Expiration timestamp in epoch seconds
|
|
62
|
+
* @returns TTL in seconds, or undefined if no TTL should be applied
|
|
63
|
+
*/
|
|
64
|
+
calculateTtl(exp) {
|
|
65
|
+
if (exp) {
|
|
66
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
67
|
+
const ttl = exp - nowSeconds;
|
|
68
|
+
return ttl > 0 ? ttl : 1;
|
|
69
|
+
}
|
|
70
|
+
return this.defaultTtlSeconds;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Build the storage key for a token ID.
|
|
74
|
+
* For namespaced storage, the namespace is handled by the storage layer.
|
|
75
|
+
* For non-namespaced storage, includes the namespace prefix in the key.
|
|
76
|
+
*/
|
|
77
|
+
key(id) {
|
|
78
|
+
return this.storageIsNamespaced ? id : `${this.namespace}:${id}`;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
export {
|
|
82
|
+
StorageTokenStore
|
|
83
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// libs/auth/src/session/storage-authorization.store.ts
|
|
2
|
+
import { TypedStorage } from "@frontmcp/utils";
|
|
3
|
+
import {
|
|
4
|
+
buildCodeRecord,
|
|
5
|
+
buildPendingRecord,
|
|
6
|
+
buildRefreshTokenRecord,
|
|
7
|
+
generateAuthorizationCode,
|
|
8
|
+
generateRefreshTokenValue
|
|
9
|
+
} from "./authorization.store.mjs";
|
|
10
|
+
var StorageAuthorizationStore = class {
|
|
11
|
+
codeStore;
|
|
12
|
+
pendingStore;
|
|
13
|
+
refreshStore;
|
|
14
|
+
namespace;
|
|
15
|
+
storageIsNamespaced;
|
|
16
|
+
constructor(storage, options = {}) {
|
|
17
|
+
this.namespace = options.namespace ?? "oauth";
|
|
18
|
+
this.storageIsNamespaced = this.isNamespacedStorage(storage);
|
|
19
|
+
const base = this.storageIsNamespaced ? storage.namespace(this.namespace) : storage;
|
|
20
|
+
this.codeStore = new TypedStorage(base);
|
|
21
|
+
this.pendingStore = new TypedStorage(base);
|
|
22
|
+
this.refreshStore = new TypedStorage(base);
|
|
23
|
+
}
|
|
24
|
+
// ============================================
|
|
25
|
+
// Record builders (pure; shared with in-memory store)
|
|
26
|
+
// ============================================
|
|
27
|
+
generateCode() {
|
|
28
|
+
return generateAuthorizationCode();
|
|
29
|
+
}
|
|
30
|
+
generateRefreshToken() {
|
|
31
|
+
return generateRefreshTokenValue();
|
|
32
|
+
}
|
|
33
|
+
createCodeRecord(params) {
|
|
34
|
+
return buildCodeRecord(params);
|
|
35
|
+
}
|
|
36
|
+
createPendingRecord(params) {
|
|
37
|
+
return buildPendingRecord(params);
|
|
38
|
+
}
|
|
39
|
+
createRefreshTokenRecord(params) {
|
|
40
|
+
return buildRefreshTokenRecord(params);
|
|
41
|
+
}
|
|
42
|
+
// ============================================
|
|
43
|
+
// Authorization code operations
|
|
44
|
+
// ============================================
|
|
45
|
+
async storeAuthorizationCode(record) {
|
|
46
|
+
await this.codeStore.set(this.key("code", record.code), record, this.ttl(record.expiresAt));
|
|
47
|
+
}
|
|
48
|
+
async getAuthorizationCode(code) {
|
|
49
|
+
const record = await this.codeStore.get(this.key("code", code));
|
|
50
|
+
if (!record) return null;
|
|
51
|
+
if (Date.now() > record.expiresAt) {
|
|
52
|
+
await this.codeStore.delete(this.key("code", code));
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return record;
|
|
56
|
+
}
|
|
57
|
+
async markCodeUsed(code, issuedRefreshToken) {
|
|
58
|
+
const record = await this.getAuthorizationCode(code);
|
|
59
|
+
if (record) {
|
|
60
|
+
record.used = true;
|
|
61
|
+
if (issuedRefreshToken) record.issuedRefreshToken = issuedRefreshToken;
|
|
62
|
+
await this.codeStore.set(this.key("code", code), record, this.ttl(record.expiresAt));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async deleteAuthorizationCode(code) {
|
|
66
|
+
await this.codeStore.delete(this.key("code", code));
|
|
67
|
+
}
|
|
68
|
+
// ============================================
|
|
69
|
+
// Pending authorization operations
|
|
70
|
+
// ============================================
|
|
71
|
+
async storePendingAuthorization(record) {
|
|
72
|
+
await this.pendingStore.set(this.key("pending", record.id), record, this.ttl(record.expiresAt));
|
|
73
|
+
}
|
|
74
|
+
async getPendingAuthorization(id) {
|
|
75
|
+
const record = await this.pendingStore.get(this.key("pending", id));
|
|
76
|
+
if (!record) return null;
|
|
77
|
+
if (Date.now() > record.expiresAt) {
|
|
78
|
+
await this.pendingStore.delete(this.key("pending", id));
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return record;
|
|
82
|
+
}
|
|
83
|
+
async deletePendingAuthorization(id) {
|
|
84
|
+
await this.pendingStore.delete(this.key("pending", id));
|
|
85
|
+
}
|
|
86
|
+
// ============================================
|
|
87
|
+
// Refresh token operations
|
|
88
|
+
// ============================================
|
|
89
|
+
async storeRefreshToken(record) {
|
|
90
|
+
await this.refreshStore.set(this.key("refresh", record.token), record, this.ttl(record.expiresAt));
|
|
91
|
+
}
|
|
92
|
+
async getRefreshToken(token) {
|
|
93
|
+
const record = await this.refreshStore.get(this.key("refresh", token));
|
|
94
|
+
if (!record) return null;
|
|
95
|
+
if (Date.now() > record.expiresAt || record.revoked) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return record;
|
|
99
|
+
}
|
|
100
|
+
async revokeRefreshToken(token) {
|
|
101
|
+
const record = await this.refreshStore.get(this.key("refresh", token));
|
|
102
|
+
if (record) {
|
|
103
|
+
record.revoked = true;
|
|
104
|
+
await this.refreshStore.set(this.key("refresh", token), record, this.ttl(record.expiresAt));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async rotateRefreshToken(oldToken, newRecord) {
|
|
108
|
+
await this.revokeRefreshToken(oldToken);
|
|
109
|
+
newRecord.previousToken = oldToken;
|
|
110
|
+
await this.storeRefreshToken(newRecord);
|
|
111
|
+
}
|
|
112
|
+
// ============================================
|
|
113
|
+
// Utility
|
|
114
|
+
// ============================================
|
|
115
|
+
async cleanup() {
|
|
116
|
+
}
|
|
117
|
+
// ============================================
|
|
118
|
+
// Internals
|
|
119
|
+
// ============================================
|
|
120
|
+
ttl(expiresAtMs) {
|
|
121
|
+
const ttl = Math.ceil((expiresAtMs - Date.now()) / 1e3);
|
|
122
|
+
return { ttlSeconds: ttl > 0 ? ttl : 1 };
|
|
123
|
+
}
|
|
124
|
+
key(type, id) {
|
|
125
|
+
const composite = `${type}:${id}`;
|
|
126
|
+
return this.storageIsNamespaced ? composite : `${this.namespace}:${composite}`;
|
|
127
|
+
}
|
|
128
|
+
isNamespacedStorage(storage) {
|
|
129
|
+
return "namespace" in storage && typeof storage.namespace === "function";
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
export {
|
|
133
|
+
StorageAuthorizationStore
|
|
134
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// libs/auth/src/session/storage-consent.store.ts
|
|
2
|
+
import { TypedStorage } from "@frontmcp/utils";
|
|
3
|
+
import { consentRecordKey } from "./consent.store.mjs";
|
|
4
|
+
var StorageConsentStore = class {
|
|
5
|
+
storage;
|
|
6
|
+
namespace;
|
|
7
|
+
storageIsNamespaced;
|
|
8
|
+
constructor(storage, options = {}) {
|
|
9
|
+
this.namespace = options.namespace ?? "remembered-consent";
|
|
10
|
+
this.storageIsNamespaced = this.isNamespacedStorage(storage);
|
|
11
|
+
const namespacedStorage = this.storageIsNamespaced ? storage.namespace(this.namespace) : storage;
|
|
12
|
+
this.storage = new TypedStorage(namespacedStorage);
|
|
13
|
+
}
|
|
14
|
+
async get(userSub, clientId) {
|
|
15
|
+
return this.storage.get(this.key(userSub, clientId));
|
|
16
|
+
}
|
|
17
|
+
async set(record) {
|
|
18
|
+
await this.storage.set(this.key(record.userSub, record.clientId), record);
|
|
19
|
+
}
|
|
20
|
+
async delete(userSub, clientId) {
|
|
21
|
+
await this.storage.delete(this.key(userSub, clientId));
|
|
22
|
+
}
|
|
23
|
+
key(userSub, clientId) {
|
|
24
|
+
const base = consentRecordKey(userSub, clientId);
|
|
25
|
+
return this.storageIsNamespaced ? base : `${this.namespace}:${base}`;
|
|
26
|
+
}
|
|
27
|
+
isNamespacedStorage(storage) {
|
|
28
|
+
return "namespace" in storage && typeof storage.namespace === "function";
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
export {
|
|
32
|
+
StorageConsentStore
|
|
33
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// libs/auth/src/session/storage-federated-auth.session.ts
|
|
2
|
+
import { TypedStorage } from "@frontmcp/utils";
|
|
3
|
+
import {
|
|
4
|
+
fromSessionRecord,
|
|
5
|
+
toSessionRecord
|
|
6
|
+
} from "./federated-auth.session.mjs";
|
|
7
|
+
var StorageFederatedAuthSessionStore = class {
|
|
8
|
+
storage;
|
|
9
|
+
namespace;
|
|
10
|
+
storageIsNamespaced;
|
|
11
|
+
constructor(storage, options = {}) {
|
|
12
|
+
this.namespace = options.namespace ?? "fed";
|
|
13
|
+
this.storageIsNamespaced = this.isNamespacedStorage(storage);
|
|
14
|
+
const namespacedStorage = this.storageIsNamespaced ? storage.namespace(this.namespace) : storage;
|
|
15
|
+
this.storage = new TypedStorage(namespacedStorage);
|
|
16
|
+
}
|
|
17
|
+
async store(session) {
|
|
18
|
+
await this.put(session);
|
|
19
|
+
}
|
|
20
|
+
async update(session) {
|
|
21
|
+
await this.put(session);
|
|
22
|
+
}
|
|
23
|
+
async get(id) {
|
|
24
|
+
const record = await this.storage.get(this.key(id));
|
|
25
|
+
if (!record) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
if (Date.now() > record.expiresAt) {
|
|
29
|
+
await this.storage.delete(this.key(id));
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return fromSessionRecord(record);
|
|
33
|
+
}
|
|
34
|
+
async delete(id) {
|
|
35
|
+
await this.storage.delete(this.key(id));
|
|
36
|
+
}
|
|
37
|
+
async put(session) {
|
|
38
|
+
const record = toSessionRecord(session);
|
|
39
|
+
const ttlSeconds = this.calculateTtl(record.expiresAt);
|
|
40
|
+
await this.storage.set(this.key(session.id), record, ttlSeconds ? { ttlSeconds } : void 0);
|
|
41
|
+
}
|
|
42
|
+
calculateTtl(expiresAtMs) {
|
|
43
|
+
const ttl = Math.ceil((expiresAtMs - Date.now()) / 1e3);
|
|
44
|
+
return ttl > 0 ? ttl : 1;
|
|
45
|
+
}
|
|
46
|
+
key(id) {
|
|
47
|
+
return this.storageIsNamespaced ? id : `${this.namespace}:${id}`;
|
|
48
|
+
}
|
|
49
|
+
isNamespacedStorage(storage) {
|
|
50
|
+
return "namespace" in storage && typeof storage.namespace === "function";
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
export {
|
|
54
|
+
StorageFederatedAuthSessionStore
|
|
55
|
+
};
|