@frontmcp/auth 1.10.16 → 1.11.0-rc.1
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,289 @@
|
|
|
1
|
+
// libs/auth/src/authorization/orchestrated.authorization.ts
|
|
2
|
+
import { NoProviderIdError, TokenNotAvailableError, TokenStoreRequiredError } from "../errors/auth-internal.errors.mjs";
|
|
3
|
+
import { deriveAuthorizationId } from "../utils/authorization-id.utils.mjs";
|
|
4
|
+
import { AuthorizationBase } from "./authorization.class.mjs";
|
|
5
|
+
var OrchestratedAuthorization = class _OrchestratedAuthorization extends AuthorizationBase {
|
|
6
|
+
mode = "orchestrated";
|
|
7
|
+
/**
|
|
8
|
+
* Primary provider ID (default for getToken)
|
|
9
|
+
*/
|
|
10
|
+
primaryProviderId;
|
|
11
|
+
/**
|
|
12
|
+
* Token store for secure token retrieval
|
|
13
|
+
*/
|
|
14
|
+
#tokenStore;
|
|
15
|
+
/**
|
|
16
|
+
* Token refresh callback
|
|
17
|
+
*/
|
|
18
|
+
#onTokenRefresh;
|
|
19
|
+
/**
|
|
20
|
+
* Provider states (encrypted tokens)
|
|
21
|
+
*/
|
|
22
|
+
#providerStates;
|
|
23
|
+
constructor(ctx) {
|
|
24
|
+
super(ctx);
|
|
25
|
+
this.primaryProviderId = ctx.primaryProviderId;
|
|
26
|
+
this.#tokenStore = ctx.tokenStore;
|
|
27
|
+
this.#onTokenRefresh = ctx.onTokenRefresh;
|
|
28
|
+
this.#providerStates = ctx.providerStates ?? /* @__PURE__ */ new Map();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Create an OrchestratedAuthorization
|
|
32
|
+
*
|
|
33
|
+
* @param ctx - Creation context
|
|
34
|
+
* @returns A new OrchestratedAuthorization instance
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const auth = OrchestratedAuthorization.create({
|
|
39
|
+
* token: localJwt,
|
|
40
|
+
* user: { sub: 'user123', name: 'John' },
|
|
41
|
+
* primaryProviderId: 'github',
|
|
42
|
+
* tokenStore: redisTokenStore,
|
|
43
|
+
* providers: {
|
|
44
|
+
* github: { id: 'github', secretRefId: 'vault:github:user123' },
|
|
45
|
+
* },
|
|
46
|
+
* });
|
|
47
|
+
*
|
|
48
|
+
* // Retrieve token securely (never exposed to LLM)
|
|
49
|
+
* const githubToken = await auth.getToken('github');
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
static create(ctx) {
|
|
53
|
+
const {
|
|
54
|
+
token,
|
|
55
|
+
user,
|
|
56
|
+
scopes = [],
|
|
57
|
+
claims,
|
|
58
|
+
expiresAt,
|
|
59
|
+
primaryProviderId,
|
|
60
|
+
tokenStore,
|
|
61
|
+
onTokenRefresh,
|
|
62
|
+
providers = {},
|
|
63
|
+
authorizedProviderIds,
|
|
64
|
+
...projections
|
|
65
|
+
} = ctx;
|
|
66
|
+
const id = deriveAuthorizationId(token);
|
|
67
|
+
const providerStates = /* @__PURE__ */ new Map();
|
|
68
|
+
const authorizedProviders = {};
|
|
69
|
+
const providerIdsFromState = [];
|
|
70
|
+
for (const [providerId, state] of Object.entries(providers)) {
|
|
71
|
+
providerStates.set(providerId, state);
|
|
72
|
+
providerIdsFromState.push(providerId);
|
|
73
|
+
authorizedProviders[providerId] = {
|
|
74
|
+
id: providerId,
|
|
75
|
+
exp: state.expiresAt,
|
|
76
|
+
embedMode: state.secretRefId ? "ref" : "store-only",
|
|
77
|
+
secretRefId: state.secretRefId,
|
|
78
|
+
refreshRefId: state.refreshRefId
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const finalAuthorizedProviderIds = authorizedProviderIds ?? (providerIdsFromState.length > 0 ? providerIdsFromState : void 0);
|
|
82
|
+
return new _OrchestratedAuthorization({
|
|
83
|
+
id,
|
|
84
|
+
isAnonymous: false,
|
|
85
|
+
user,
|
|
86
|
+
claims,
|
|
87
|
+
expiresAt,
|
|
88
|
+
scopes,
|
|
89
|
+
token,
|
|
90
|
+
primaryProviderId,
|
|
91
|
+
tokenStore,
|
|
92
|
+
onTokenRefresh,
|
|
93
|
+
providerStates,
|
|
94
|
+
authorizedProviders,
|
|
95
|
+
...projections,
|
|
96
|
+
authorizedProviderIds: finalAuthorizedProviderIds
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get access token for a provider
|
|
101
|
+
*
|
|
102
|
+
* Retrieves the decrypted token from the secure store.
|
|
103
|
+
* If the token is expired and refresh is available, attempts refresh.
|
|
104
|
+
*
|
|
105
|
+
* @param providerId - Provider ID (defaults to primaryProviderId)
|
|
106
|
+
* @returns The decrypted access token
|
|
107
|
+
* @throws If no token store or no token available
|
|
108
|
+
*/
|
|
109
|
+
async getToken(providerId) {
|
|
110
|
+
const targetProviderId = providerId ?? this.primaryProviderId;
|
|
111
|
+
if (!targetProviderId) {
|
|
112
|
+
throw new NoProviderIdError("OrchestratedAuthorization: No provider ID specified and no primary provider set");
|
|
113
|
+
}
|
|
114
|
+
if (!this.#tokenStore) {
|
|
115
|
+
throw new TokenStoreRequiredError("orchestrated token retrieval");
|
|
116
|
+
}
|
|
117
|
+
const hasToken = await this.#tokenStore.hasTokens(this.id, targetProviderId);
|
|
118
|
+
if (!hasToken) {
|
|
119
|
+
throw new TokenNotAvailableError(
|
|
120
|
+
`OrchestratedAuthorization: No tokens available for provider "${targetProviderId}"`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
const accessToken = await this.#tokenStore.getAccessToken(this.id, targetProviderId);
|
|
124
|
+
if (accessToken) {
|
|
125
|
+
const providerState = this.#providerStates.get(targetProviderId);
|
|
126
|
+
if (providerState?.expiresAt && providerState.expiresAt < Date.now()) {
|
|
127
|
+
return this.refreshAndGetToken(targetProviderId);
|
|
128
|
+
}
|
|
129
|
+
return accessToken;
|
|
130
|
+
}
|
|
131
|
+
return this.refreshAndGetToken(targetProviderId);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Refresh token and return new access token
|
|
135
|
+
*/
|
|
136
|
+
async refreshAndGetToken(providerId) {
|
|
137
|
+
if (!this.#tokenStore || !this.#onTokenRefresh) {
|
|
138
|
+
throw new TokenNotAvailableError(
|
|
139
|
+
`OrchestratedAuthorization: Token expired for provider "${providerId}" and refresh not available`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
const refreshToken = await this.#tokenStore.getRefreshToken(this.id, providerId);
|
|
143
|
+
if (!refreshToken) {
|
|
144
|
+
throw new TokenNotAvailableError(
|
|
145
|
+
`OrchestratedAuthorization: No refresh token available for provider "${providerId}"`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const result = await this.#onTokenRefresh(providerId, refreshToken);
|
|
149
|
+
await this.#tokenStore.storeTokens(this.id, providerId, {
|
|
150
|
+
accessToken: result.accessToken,
|
|
151
|
+
refreshToken: result.refreshToken ?? refreshToken,
|
|
152
|
+
expiresAt: result.expiresIn ? Date.now() + result.expiresIn * 1e3 : void 0
|
|
153
|
+
});
|
|
154
|
+
const currentState = this.#providerStates.get(providerId);
|
|
155
|
+
if (currentState) {
|
|
156
|
+
currentState.expiresAt = result.expiresIn ? Date.now() + result.expiresIn * 1e3 : void 0;
|
|
157
|
+
}
|
|
158
|
+
return result.accessToken;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Check if a provider has tokens stored
|
|
162
|
+
*/
|
|
163
|
+
hasProvider(providerId) {
|
|
164
|
+
return this.#providerStates.has(providerId);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Get all provider IDs with tokens
|
|
168
|
+
*/
|
|
169
|
+
getProviderIds() {
|
|
170
|
+
return Array.from(this.#providerStates.keys());
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Add a new provider to this authorization
|
|
174
|
+
* Used when user authorizes additional providers after initial auth
|
|
175
|
+
*/
|
|
176
|
+
async addProvider(providerId, tokens) {
|
|
177
|
+
if (!this.#tokenStore) {
|
|
178
|
+
throw new TokenStoreRequiredError("adding providers");
|
|
179
|
+
}
|
|
180
|
+
const expiresAt = tokens.expiresIn ? Date.now() + tokens.expiresIn * 1e3 : void 0;
|
|
181
|
+
await this.#tokenStore.storeTokens(this.id, providerId, {
|
|
182
|
+
accessToken: tokens.accessToken,
|
|
183
|
+
refreshToken: tokens.refreshToken,
|
|
184
|
+
expiresAt
|
|
185
|
+
});
|
|
186
|
+
this.#providerStates.set(providerId, {
|
|
187
|
+
id: providerId,
|
|
188
|
+
expiresAt,
|
|
189
|
+
secretRefId: `${this.id}:${providerId}`
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
// ============================================
|
|
193
|
+
// Progressive/Incremental Authorization
|
|
194
|
+
// ============================================
|
|
195
|
+
/**
|
|
196
|
+
* Mutable app authorization state for progressive auth.
|
|
197
|
+
* This allows expanding authorization without reissuing the session token.
|
|
198
|
+
*/
|
|
199
|
+
#mutableAuthorizedApps = new Map(
|
|
200
|
+
Object.entries(this.authorizedApps ?? {})
|
|
201
|
+
);
|
|
202
|
+
/**
|
|
203
|
+
* Add app authorization after initial auth (progressive authorization).
|
|
204
|
+
* Stores app tokens server-side and updates authorized apps without JWT reissue.
|
|
205
|
+
*
|
|
206
|
+
* @param appId - App ID to authorize
|
|
207
|
+
* @param toolIds - Tool IDs accessible through this app authorization
|
|
208
|
+
* @param tokens - OAuth tokens from the app's auth provider
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```typescript
|
|
212
|
+
* // User clicks auth link for Slack app
|
|
213
|
+
* await auth.addAppAuthorization('slack', ['slack:send_message', 'slack:list_channels'], {
|
|
214
|
+
* accessToken: slackAccessToken,
|
|
215
|
+
* refreshToken: slackRefreshToken,
|
|
216
|
+
* expiresIn: 3600,
|
|
217
|
+
* });
|
|
218
|
+
*
|
|
219
|
+
* // Now slack tools will work without re-auth
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
async addAppAuthorization(appId, toolIds, tokens) {
|
|
223
|
+
if (!this.#tokenStore) {
|
|
224
|
+
throw new TokenStoreRequiredError("progressive authorization");
|
|
225
|
+
}
|
|
226
|
+
const providerId = `app:${appId}`;
|
|
227
|
+
await this.addProvider(providerId, tokens);
|
|
228
|
+
this.#mutableAuthorizedApps.set(appId, { id: appId, toolIds });
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Get access token for a specific app (for tool execution).
|
|
232
|
+
* Retrieves the app's OAuth token from server-side storage.
|
|
233
|
+
*
|
|
234
|
+
* @param appId - App ID to get token for
|
|
235
|
+
* @returns The decrypted access token, or null if not authorized
|
|
236
|
+
*/
|
|
237
|
+
async getAppToken(appId) {
|
|
238
|
+
if (!this.#mutableAuthorizedApps.has(appId)) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const providerId = `app:${appId}`;
|
|
242
|
+
try {
|
|
243
|
+
return await this.getToken(providerId);
|
|
244
|
+
} catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Check if an app is authorized (includes progressively authorized apps).
|
|
250
|
+
* Overrides base class to include mutable app authorization state.
|
|
251
|
+
*/
|
|
252
|
+
isAppAuthorized(appId) {
|
|
253
|
+
return this.#mutableAuthorizedApps.has(appId) || super.isAppAuthorized(appId);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Get all authorized app IDs (includes progressively authorized apps).
|
|
257
|
+
*/
|
|
258
|
+
getAllAuthorizedAppIds() {
|
|
259
|
+
const baseIds = new Set(this.authorizedAppIds ?? []);
|
|
260
|
+
for (const appId of this.#mutableAuthorizedApps.keys()) {
|
|
261
|
+
baseIds.add(appId);
|
|
262
|
+
}
|
|
263
|
+
return Array.from(baseIds);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Get tool IDs authorized through an app.
|
|
267
|
+
*/
|
|
268
|
+
getAppToolIds(appId) {
|
|
269
|
+
return this.#mutableAuthorizedApps.get(appId)?.toolIds ?? this.authorizedApps?.[appId]?.toolIds;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Remove a provider from this authorization
|
|
273
|
+
*/
|
|
274
|
+
async removeProvider(providerId) {
|
|
275
|
+
if (this.#tokenStore) {
|
|
276
|
+
await this.#tokenStore.deleteTokens(this.id, providerId);
|
|
277
|
+
}
|
|
278
|
+
this.#providerStates.delete(providerId);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Get the issuer (local orchestrator)
|
|
282
|
+
*/
|
|
283
|
+
get issuer() {
|
|
284
|
+
return this.claims?.["iss"];
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
export {
|
|
288
|
+
OrchestratedAuthorization
|
|
289
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// libs/auth/src/authorization/public.authorization.ts
|
|
2
|
+
import { randomUUID } from "@frontmcp/utils";
|
|
3
|
+
import { AuthorizationBase } from "./authorization.class.mjs";
|
|
4
|
+
import { TokenNotAvailableError } from "../errors/auth-internal.errors.mjs";
|
|
5
|
+
var PublicAuthorization = class _PublicAuthorization extends AuthorizationBase {
|
|
6
|
+
mode = "public";
|
|
7
|
+
/**
|
|
8
|
+
* Issuer identifier for the anonymous authorization
|
|
9
|
+
*/
|
|
10
|
+
issuer;
|
|
11
|
+
constructor(ctx) {
|
|
12
|
+
super(ctx);
|
|
13
|
+
this.issuer = ctx.issuer;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create a new PublicAuthorization for anonymous access
|
|
17
|
+
*
|
|
18
|
+
* @param ctx - Creation context with optional configuration
|
|
19
|
+
* @returns A new PublicAuthorization instance
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const auth = PublicAuthorization.create({
|
|
24
|
+
* scopes: ['read', 'anonymous'],
|
|
25
|
+
* ttlMs: 3600000,
|
|
26
|
+
* allowedTools: ['search', 'get-docs'],
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
static create(ctx = {}) {
|
|
31
|
+
const {
|
|
32
|
+
prefix = "anon",
|
|
33
|
+
scopes = ["anonymous"],
|
|
34
|
+
ttlMs = 36e5,
|
|
35
|
+
// 1 hour default
|
|
36
|
+
issuer,
|
|
37
|
+
allowedTools = "all",
|
|
38
|
+
allowedPrompts = "all"
|
|
39
|
+
} = ctx;
|
|
40
|
+
const uuid = randomUUID();
|
|
41
|
+
const sub = `${prefix}:${uuid}`;
|
|
42
|
+
const user = {
|
|
43
|
+
sub,
|
|
44
|
+
name: "Anonymous",
|
|
45
|
+
anonymous: true
|
|
46
|
+
};
|
|
47
|
+
const expiresAt = ttlMs ? Date.now() + ttlMs : void 0;
|
|
48
|
+
const authorizedTools = {};
|
|
49
|
+
const authorizedToolIds = [];
|
|
50
|
+
if (allowedTools !== "all" && Array.isArray(allowedTools)) {
|
|
51
|
+
for (const toolId of allowedTools) {
|
|
52
|
+
authorizedTools[toolId] = {
|
|
53
|
+
executionPath: ["public", toolId]
|
|
54
|
+
};
|
|
55
|
+
authorizedToolIds.push(toolId);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const authorizedPrompts = {};
|
|
59
|
+
const authorizedPromptIds = [];
|
|
60
|
+
if (allowedPrompts !== "all" && Array.isArray(allowedPrompts)) {
|
|
61
|
+
for (const promptId of allowedPrompts) {
|
|
62
|
+
authorizedPrompts[promptId] = {
|
|
63
|
+
executionPath: ["public", promptId]
|
|
64
|
+
};
|
|
65
|
+
authorizedPromptIds.push(promptId);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return new _PublicAuthorization({
|
|
69
|
+
id: sub,
|
|
70
|
+
isAnonymous: true,
|
|
71
|
+
user,
|
|
72
|
+
scopes,
|
|
73
|
+
expiresAt,
|
|
74
|
+
issuer,
|
|
75
|
+
authorizedTools: allowedTools === "all" ? void 0 : authorizedTools,
|
|
76
|
+
authorizedToolIds: allowedTools === "all" ? void 0 : authorizedToolIds,
|
|
77
|
+
authorizedPrompts: allowedPrompts === "all" ? void 0 : authorizedPrompts,
|
|
78
|
+
authorizedPromptIds: allowedPrompts === "all" ? void 0 : authorizedPromptIds
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Anonymous users cannot access provider tokens
|
|
83
|
+
*
|
|
84
|
+
* @throws TokenNotAvailableError always - anonymous users do not have provider tokens
|
|
85
|
+
*/
|
|
86
|
+
async getToken(_providerId) {
|
|
87
|
+
throw new TokenNotAvailableError(
|
|
88
|
+
"PublicAuthorization: Anonymous users cannot access provider tokens. Use transparent or orchestrated mode for token access."
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if all tools are allowed (public access)
|
|
93
|
+
*/
|
|
94
|
+
get allowsAllTools() {
|
|
95
|
+
return this.authorizedToolIds.length === 0 && Object.keys(this.authorizedTools).length === 0;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Check if all prompts are allowed (public access)
|
|
99
|
+
*/
|
|
100
|
+
get allowsAllPrompts() {
|
|
101
|
+
return this.authorizedPromptIds.length === 0 && Object.keys(this.authorizedPrompts).length === 0;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Override canAccessTool to support 'all' mode
|
|
105
|
+
*/
|
|
106
|
+
canAccessTool(toolId) {
|
|
107
|
+
if (this.allowsAllTools) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
return super.canAccessTool(toolId);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Override canAccessPrompt to support 'all' mode
|
|
114
|
+
*/
|
|
115
|
+
canAccessPrompt(promptId) {
|
|
116
|
+
if (this.allowsAllPrompts) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
return super.canAccessPrompt(promptId);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
export {
|
|
123
|
+
PublicAuthorization
|
|
124
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// libs/auth/src/authorization/transparent.authorization.ts
|
|
2
|
+
import { sha256Hex } from "@frontmcp/utils";
|
|
3
|
+
import { AuthorizationBase } from "./authorization.class.mjs";
|
|
4
|
+
import { TokenNotAvailableError } from "../errors/auth-internal.errors.mjs";
|
|
5
|
+
var TransparentAuthorization = class _TransparentAuthorization extends AuthorizationBase {
|
|
6
|
+
mode = "transparent";
|
|
7
|
+
/**
|
|
8
|
+
* Provider ID that issued the token
|
|
9
|
+
*/
|
|
10
|
+
providerId;
|
|
11
|
+
/**
|
|
12
|
+
* Provider display name
|
|
13
|
+
*/
|
|
14
|
+
providerName;
|
|
15
|
+
constructor(ctx) {
|
|
16
|
+
super(ctx);
|
|
17
|
+
this.providerId = ctx.providerId;
|
|
18
|
+
this.providerName = ctx.providerName;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create a TransparentAuthorization from a verified JWT
|
|
22
|
+
*
|
|
23
|
+
* @param ctx - Creation context with token and verified payload
|
|
24
|
+
* @returns A new TransparentAuthorization instance
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const auth = TransparentAuthorization.fromVerifiedToken({
|
|
29
|
+
* token: bearerToken,
|
|
30
|
+
* payload: verifiedClaims,
|
|
31
|
+
* providerId: 'auth0',
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* // Pass token through to downstream
|
|
35
|
+
* const token = await auth.getToken();
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
static fromVerifiedToken(ctx) {
|
|
39
|
+
const { token, payload, providerId, providerName, ...projections } = ctx;
|
|
40
|
+
const user = {
|
|
41
|
+
sub: payload.sub,
|
|
42
|
+
name: payload.name,
|
|
43
|
+
email: payload.email,
|
|
44
|
+
picture: payload.picture,
|
|
45
|
+
anonymous: false
|
|
46
|
+
};
|
|
47
|
+
const scopes = _TransparentAuthorization.parseScopes(payload.scope);
|
|
48
|
+
const expiresAt = payload.exp ? payload.exp * 1e3 : void 0;
|
|
49
|
+
const id = _TransparentAuthorization.generateAuthorizationId(token);
|
|
50
|
+
const providerSnapshot = {
|
|
51
|
+
id: providerId,
|
|
52
|
+
exp: expiresAt,
|
|
53
|
+
payload,
|
|
54
|
+
embedMode: "plain",
|
|
55
|
+
// transparent mode keeps token in memory
|
|
56
|
+
token
|
|
57
|
+
// the original token
|
|
58
|
+
};
|
|
59
|
+
return new _TransparentAuthorization({
|
|
60
|
+
id,
|
|
61
|
+
isAnonymous: false,
|
|
62
|
+
user,
|
|
63
|
+
claims: payload,
|
|
64
|
+
expiresAt,
|
|
65
|
+
scopes,
|
|
66
|
+
token,
|
|
67
|
+
providerId,
|
|
68
|
+
providerName,
|
|
69
|
+
authorizedProviders: { [providerId]: providerSnapshot },
|
|
70
|
+
authorizedProviderIds: [providerId],
|
|
71
|
+
...projections
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get the original bearer token for pass-through
|
|
76
|
+
*
|
|
77
|
+
* In transparent mode, the same token is returned regardless of providerId
|
|
78
|
+
* since only one provider (the upstream) issued the token.
|
|
79
|
+
*
|
|
80
|
+
* @param _providerId - Ignored in transparent mode
|
|
81
|
+
* @returns The original bearer token
|
|
82
|
+
*/
|
|
83
|
+
async getToken(_providerId) {
|
|
84
|
+
if (!this.token) {
|
|
85
|
+
throw new TokenNotAvailableError("TransparentAuthorization: Token not available");
|
|
86
|
+
}
|
|
87
|
+
return this.token;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Parse scope claim from JWT payload
|
|
91
|
+
*/
|
|
92
|
+
static parseScopes(scope) {
|
|
93
|
+
if (!scope) return [];
|
|
94
|
+
if (Array.isArray(scope)) return scope;
|
|
95
|
+
return scope.split(/\s+/).filter(Boolean);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Generate authorization ID from token signature
|
|
99
|
+
* Uses SHA-256 fingerprint of the token signature for uniqueness
|
|
100
|
+
*/
|
|
101
|
+
static generateAuthorizationId(token) {
|
|
102
|
+
const parts = token.split(".");
|
|
103
|
+
const signature = parts[2] || token;
|
|
104
|
+
return sha256Hex(signature).substring(0, 16);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get the issuer from the token claims
|
|
108
|
+
*/
|
|
109
|
+
get issuer() {
|
|
110
|
+
return this.claims?.["iss"];
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get the audience from the token claims
|
|
114
|
+
*/
|
|
115
|
+
get audience() {
|
|
116
|
+
return this.claims?.["aud"];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Check if the token was issued for a specific audience
|
|
120
|
+
*/
|
|
121
|
+
hasAudience(aud) {
|
|
122
|
+
const tokenAud = this.audience;
|
|
123
|
+
if (!tokenAud) return false;
|
|
124
|
+
if (Array.isArray(tokenAud)) return tokenAud.includes(aud);
|
|
125
|
+
return tokenAud === aud;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
export {
|
|
129
|
+
TransparentAuthorization
|
|
130
|
+
};
|