@frontmcp/auth 1.11.1-rc.2 → 1.11.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/index.mjs CHANGED
@@ -242,6 +242,7 @@ import {
242
242
  AUTH_PROVIDERS_REGISTRY,
243
243
  AuthProvidersVault,
244
244
  AUTH_PROVIDERS_VAULT,
245
+ UNIDENTIFIED_SESSION_PREFIX,
245
246
  EagerCredentialLoader,
246
247
  LazyCredentialLoader
247
248
  } from "./vault/index.mjs";
@@ -383,6 +384,7 @@ export {
383
384
  TransparentAuthorization,
384
385
  TransportIdGenerator,
385
386
  TypedStorage,
387
+ UNIDENTIFIED_SESSION_PREFIX,
386
388
  VaultEncryption,
387
389
  VaultLoadError,
388
390
  VaultNotFoundError,
@@ -1,6 +1,6 @@
1
1
  // libs/auth/src/vault/auth-providers.accessor.impl.ts
2
- import { extractCredentialExpiry } from "./credential-helpers.mjs";
3
2
  import { base64Encode } from "@frontmcp/utils";
3
+ import { extractCredentialExpiry } from "./credential-helpers.mjs";
4
4
  var AuthProvidersAccessorImpl = class {
5
5
  constructor(registry, vault, cache, loader, context, logger) {
6
6
  this.registry = registry;
@@ -180,7 +180,8 @@ var AuthProvidersAccessorImpl = class {
180
180
  providerName,
181
181
  resolved.credential,
182
182
  config.scope,
183
- this.context.userSub
183
+ this.context.userSub,
184
+ config.cacheTtl
184
185
  );
185
186
  } catch (error) {
186
187
  this.logger?.warn(`Failed to store in vault "${providerName}":`, error);
@@ -1,26 +1,52 @@
1
1
  // libs/auth/src/vault/auth-providers.vault.ts
2
- import { extractCredentialExpiry } from "./credential-helpers.mjs";
2
+ import {
3
+ EncryptedTypedStorage,
4
+ hkdfSha256,
5
+ MAX_TTL_SECONDS,
6
+ randomBytes,
7
+ sha256Hex
8
+ } from "@frontmcp/utils";
3
9
  import { AuthInvalidInputError, CredentialStorageError } from "../errors/auth-internal.errors.mjs";
10
+ import { extractCredentialExpiry } from "./credential-helpers.mjs";
11
+ var UNIDENTIFIED_SESSION_PREFIX = "unidentified:";
12
+ var DEFAULT_CREDENTIAL_TTL_MS = 36e5;
13
+ var ENCRYPTION_KEY_INFO = new TextEncoder().encode("frontmcp-auth-providers-vault-v1");
14
+ function positiveOr(value, fallback) {
15
+ return value !== void 0 && value > 0 ? value : fallback;
16
+ }
17
+ function encryptionKeyFrom(pepper) {
18
+ if (!pepper) return randomBytes(32);
19
+ return hkdfSha256(new TextEncoder().encode(pepper), new Uint8Array(0), ENCRYPTION_KEY_INFO, 32);
20
+ }
4
21
  var AuthProvidersVault = class {
5
- constructor(baseVault, namespace = "authproviders:", logger) {
6
- this.baseVault = baseVault;
7
- this.namespace = namespace;
8
- this.logger = logger;
9
- }
10
- baseVault;
22
+ credentials;
11
23
  namespace;
24
+ defaultCacheTtl;
12
25
  logger;
26
+ constructor(storage, options = {}) {
27
+ this.credentials = new EncryptedTypedStorage(storage, {
28
+ keys: [{ kid: "authproviders-v1", key: encryptionKeyFrom(options.pepper) }]
29
+ });
30
+ this.namespace = options.namespace ?? "authproviders:";
31
+ this.defaultCacheTtl = positiveOr(options.defaultCacheTtl, DEFAULT_CREDENTIAL_TTL_MS);
32
+ this.logger = options.logger;
33
+ }
13
34
  /**
14
- * Store a credential in the vault
35
+ * Store a credential in the vault, until it expires; one without an expiry for `cacheTtl`, else `defaultCacheTtl`
15
36
  *
16
37
  * @param sessionId - Current session ID
17
38
  * @param providerId - Provider name
18
39
  * @param credential - Credential to store
19
40
  * @param scope - Credential scope
20
41
  * @param userId - User ID (required for user scope)
42
+ * @param cacheTtl - How long to keep a credential without an expiry, in ms
21
43
  */
22
- async storeCredential(sessionId, providerId, credential, scope, userId) {
23
- const vaultKey = this.buildVaultKey(sessionId, scope, userId);
44
+ async storeCredential(sessionId, providerId, credential, scope, userId, cacheTtl) {
45
+ if (scope === "session" && sessionId.startsWith(UNIDENTIFIED_SESSION_PREFIX)) {
46
+ this.logger?.debug(`Not storing credential "${providerId}": the caller has no verified session`);
47
+ return;
48
+ }
49
+ const key = this.credentialKey(sessionId, providerId, scope, userId);
24
50
  const appCredential = {
25
51
  appId: this.namespace,
26
52
  providerId,
@@ -30,7 +56,7 @@ var AuthProvidersVault = class {
30
56
  expiresAt: extractCredentialExpiry(credential)
31
57
  };
32
58
  try {
33
- await this.baseVault.addAppCredential(vaultKey, appCredential);
59
+ await this.write(key, appCredential, cacheTtl);
34
60
  this.logger?.debug(`Stored credential "${providerId}" with scope "${scope}"`);
35
61
  } catch (error) {
36
62
  this.logger?.warn(`Failed to store credential "${providerId}":`, error);
@@ -47,9 +73,9 @@ var AuthProvidersVault = class {
47
73
  * @returns Credential or null if not found
48
74
  */
49
75
  async getCredential(sessionId, providerId, scope, userId) {
50
- const vaultKey = this.buildVaultKey(sessionId, scope, userId);
76
+ const key = this.credentialKey(sessionId, providerId, scope, userId);
51
77
  try {
52
- const appCredential = await this.baseVault.getCredential(vaultKey, this.namespace, providerId);
78
+ const appCredential = await this.read(key);
53
79
  if (!appCredential) {
54
80
  return null;
55
81
  }
@@ -81,9 +107,9 @@ var AuthProvidersVault = class {
81
107
  * @param userId - User ID (required for user scope)
82
108
  */
83
109
  async removeCredential(sessionId, providerId, scope, userId) {
84
- const vaultKey = this.buildVaultKey(sessionId, scope, userId);
110
+ const key = this.credentialKey(sessionId, providerId, scope, userId);
85
111
  try {
86
- await this.baseVault.removeAppCredential(vaultKey, this.namespace, providerId);
112
+ await this.credentials.delete(key);
87
113
  this.logger?.debug(`Removed credential "${providerId}"`);
88
114
  } catch (error) {
89
115
  this.logger?.warn(`Failed to remove credential "${providerId}":`, error);
@@ -99,9 +125,11 @@ var AuthProvidersVault = class {
99
125
  * @param userId - User ID (required for user scope)
100
126
  */
101
127
  async invalidateCredential(sessionId, providerId, scope, reason, userId) {
102
- const vaultKey = this.buildVaultKey(sessionId, scope, userId);
128
+ const key = this.credentialKey(sessionId, providerId, scope, userId);
103
129
  try {
104
- await this.baseVault.invalidateCredential(vaultKey, this.namespace, providerId, reason);
130
+ const appCredential = await this.read(key);
131
+ if (!appCredential) return;
132
+ await this.write(key, { ...appCredential, isValid: false, invalidReason: reason });
105
133
  this.logger?.debug(`Invalidated credential "${providerId}": ${reason}`);
106
134
  } catch (error) {
107
135
  this.logger?.warn(`Failed to invalidate credential "${providerId}":`, error);
@@ -117,9 +145,25 @@ var AuthProvidersVault = class {
117
145
  * @param userId - User ID (required for user scope)
118
146
  */
119
147
  async refreshOAuthCredential(sessionId, providerId, scope, tokens, userId) {
120
- const vaultKey = this.buildVaultKey(sessionId, scope, userId);
148
+ const key = this.credentialKey(sessionId, providerId, scope, userId);
121
149
  try {
122
- await this.baseVault.refreshOAuthCredential(vaultKey, this.namespace, providerId, tokens);
150
+ const appCredential = await this.read(key);
151
+ if (!appCredential) return;
152
+ const credential = appCredential.credential;
153
+ if (credential.type !== "oauth" && credential.type !== "oauth_pkce") return;
154
+ const refreshed = {
155
+ ...appCredential,
156
+ credential: {
157
+ ...credential,
158
+ accessToken: tokens.accessToken,
159
+ refreshToken: tokens.refreshToken ?? credential.refreshToken,
160
+ expiresAt: tokens.expiresAt ?? credential.expiresAt
161
+ },
162
+ expiresAt: tokens.expiresAt ?? appCredential.expiresAt,
163
+ isValid: true,
164
+ invalidReason: void 0
165
+ };
166
+ await this.write(key, refreshed);
123
167
  this.logger?.debug(`Refreshed OAuth credential "${providerId}"`);
124
168
  } catch (error) {
125
169
  this.logger?.warn(`Failed to refresh OAuth credential "${providerId}":`, error);
@@ -135,49 +179,47 @@ var AuthProvidersVault = class {
135
179
  */
136
180
  async getAllCredentials(sessionId, scope, userId) {
137
181
  if (scope) {
138
- const vaultKey = this.buildVaultKey(sessionId, scope, userId);
139
- try {
140
- return await this.baseVault.getAppCredentials(vaultKey, this.namespace);
141
- } catch {
142
- return [];
143
- }
182
+ return this.listCredentials(this.buildVaultKey(sessionId, scope, userId));
144
183
  }
145
- const credentials = [];
184
+ const scopes = userId ? ["session", "user", "global"] : ["session", "global"];
185
+ const credentialsPerScope = await Promise.all(
186
+ scopes.map((eachScope) => this.listCredentials(this.buildVaultKey(sessionId, eachScope, userId)))
187
+ );
188
+ return credentialsPerScope.flat();
189
+ }
190
+ async listCredentials(vaultKey) {
146
191
  try {
147
- const sessionCreds = await this.baseVault.getAppCredentials(
148
- this.buildVaultKey(sessionId, "session"),
149
- this.namespace
150
- );
151
- credentials.push(...sessionCreds);
192
+ const keys = await this.credentials.keys(`${vaultKey}:*`);
193
+ const records = await this.credentials.mget(keys);
194
+ return records.flatMap((record, index) => record?.storageKey === keys[index] ? [record.appCredential] : []);
152
195
  } catch {
196
+ return [];
153
197
  }
154
- if (userId) {
155
- try {
156
- const userCreds = await this.baseVault.getAppCredentials(
157
- this.buildVaultKey(sessionId, "user", userId),
158
- this.namespace
159
- );
160
- credentials.push(...userCreds);
161
- } catch {
162
- }
163
- }
164
- try {
165
- const globalCreds = await this.baseVault.getAppCredentials(
166
- this.buildVaultKey(sessionId, "global"),
167
- this.namespace
168
- );
169
- credentials.push(...globalCreds);
170
- } catch {
198
+ }
199
+ async read(key) {
200
+ const record = await this.credentials.get(key);
201
+ return record?.storageKey === key ? record.appCredential : null;
202
+ }
203
+ /** Writes until the credential expires, else for `cacheTtl` or `defaultCacheTtl`; an expired one is deleted. */
204
+ async write(key, appCredential, cacheTtl) {
205
+ const ttlMs = appCredential.expiresAt === void 0 ? positiveOr(cacheTtl, this.defaultCacheTtl) : appCredential.expiresAt - Date.now();
206
+ if (ttlMs <= 0) {
207
+ await this.credentials.delete(key);
208
+ return;
171
209
  }
172
- return credentials;
210
+ const ttlSeconds = Math.min(Math.ceil(ttlMs / 1e3), MAX_TTL_SECONDS);
211
+ await this.credentials.set(key, { storageKey: key, appCredential }, { ttlSeconds });
212
+ }
213
+ credentialKey(sessionId, providerId, scope, userId) {
214
+ return `${this.buildVaultKey(sessionId, scope, userId)}:${providerId}`;
173
215
  }
174
216
  /**
175
- * Build vault key based on scope
217
+ * Build vault key based on scope; user and session ids are hashed, so no id can reach into another's keys
176
218
  *
177
219
  * Key patterns:
178
220
  * - global: `authproviders:global`
179
- * - user: `authproviders:user:{userId}`
180
- * - session: `authproviders:session:{sessionId}`
221
+ * - user: `authproviders:user:{sha256(userId)}`
222
+ * - session: `authproviders:session:{sha256(sessionId)}`
181
223
  */
182
224
  buildVaultKey(sessionId, scope, userId) {
183
225
  switch (scope) {
@@ -189,9 +231,9 @@ var AuthProvidersVault = class {
189
231
  `userId is required for user-scoped credentials (namespace: ${this.namespace})`
190
232
  );
191
233
  }
192
- return `${this.namespace}user:${userId}`;
234
+ return `${this.namespace}user:${sha256Hex(userId)}`;
193
235
  case "session":
194
- return `${this.namespace}session:${sessionId}`;
236
+ return `${this.namespace}session:${sha256Hex(sessionId)}`;
195
237
  default: {
196
238
  const _exhaustive = scope;
197
239
  throw new AuthInvalidInputError(`Unknown credential scope: ${_exhaustive}`);
@@ -202,5 +244,6 @@ var AuthProvidersVault = class {
202
244
  var AUTH_PROVIDERS_VAULT = /* @__PURE__ */ Symbol.for("frontmcp:AUTH_PROVIDERS_VAULT");
203
245
  export {
204
246
  AUTH_PROVIDERS_VAULT,
205
- AuthProvidersVault
247
+ AuthProvidersVault,
248
+ UNIDENTIFIED_SESSION_PREFIX
206
249
  };
@@ -12,7 +12,11 @@ import { CredentialCache } from "./credential-cache.mjs";
12
12
  import { AUTH_PROVIDERS_ACCESSOR } from "./auth-providers.accessor.mjs";
13
13
  import { AuthProvidersAccessorImpl } from "./auth-providers.accessor.impl.mjs";
14
14
  import { AuthProvidersRegistry, AUTH_PROVIDERS_REGISTRY } from "./auth-providers.registry.mjs";
15
- import { AuthProvidersVault, AUTH_PROVIDERS_VAULT } from "./auth-providers.vault.mjs";
15
+ import {
16
+ AuthProvidersVault,
17
+ AUTH_PROVIDERS_VAULT,
18
+ UNIDENTIFIED_SESSION_PREFIX
19
+ } from "./auth-providers.vault.mjs";
16
20
  import { EagerCredentialLoader } from "./credential-loaders/eager-loader.mjs";
17
21
  import { LazyCredentialLoader } from "./credential-loaders/lazy-loader.mjs";
18
22
  export {
@@ -25,6 +29,7 @@ export {
25
29
  CredentialCache,
26
30
  EagerCredentialLoader,
27
31
  LazyCredentialLoader,
32
+ UNIDENTIFIED_SESSION_PREFIX,
28
33
  authProviderMappingSchema,
29
34
  authProvidersVaultOptionsSchema,
30
35
  credentialProviderConfigSchema,