@better-auth/api-key 1.7.0-beta.1 → 1.7.0-beta.3
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/dist/client.mjs
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as API_KEY_ERROR_CODES, t as PACKAGE_VERSION } from "./version-
|
|
1
|
+
import { n as API_KEY_ERROR_CODES, t as PACKAGE_VERSION } from "./version-CmDM8fr2.mjs";
|
|
2
2
|
import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api";
|
|
3
3
|
import { base64Url } from "@better-auth/utils/base64";
|
|
4
4
|
import { createHash } from "@better-auth/utils/hash";
|
|
@@ -10,11 +10,13 @@ import { APIError as APIError$1 } from "@better-auth/core/error";
|
|
|
10
10
|
import { generateId } from "@better-auth/core/utils/id";
|
|
11
11
|
import { safeJSONParse } from "@better-auth/core/utils/json";
|
|
12
12
|
import * as z from "zod";
|
|
13
|
+
import { mapConcurrent } from "@better-auth/core/utils/async";
|
|
13
14
|
import { isDevelopment, isTest } from "@better-auth/core/env";
|
|
14
15
|
import { isValidIP, normalizeIP } from "@better-auth/core/utils/ip";
|
|
15
16
|
import { role } from "better-auth/plugins/access";
|
|
16
17
|
import { parseJSON } from "better-auth/client";
|
|
17
18
|
//#region src/adapter.ts
|
|
19
|
+
const STORAGE_CONCURRENCY = 10;
|
|
18
20
|
/**
|
|
19
21
|
* Parses double-stringified metadata synchronously without updating the database.
|
|
20
22
|
* Use this for reading metadata, then call migrateLegacyMetadataInBackground for DB updates.
|
|
@@ -165,15 +167,10 @@ async function getApiKeyByIdFromStorage(ctx, id, storage) {
|
|
|
165
167
|
return deserializeApiKey(await storage.get(key));
|
|
166
168
|
}
|
|
167
169
|
/**
|
|
168
|
-
*
|
|
170
|
+
* Read-modify-write the ref list:
|
|
171
|
+
* used only when the list is the source of truth.
|
|
169
172
|
*/
|
|
170
|
-
async function
|
|
171
|
-
const serialized = serializeApiKey(apiKey);
|
|
172
|
-
const hashedKey = apiKey.key;
|
|
173
|
-
const id = apiKey.id;
|
|
174
|
-
await storage.set(getStorageKeyByHashedKey(hashedKey), serialized, ttl);
|
|
175
|
-
await storage.set(getStorageKeyById(id), serialized, ttl);
|
|
176
|
-
const refKey = getStorageKeyByReferenceId(apiKey.referenceId);
|
|
173
|
+
async function modifyRefList(storage, refKey, modify) {
|
|
177
174
|
const refListData = await storage.get(refKey);
|
|
178
175
|
let keyIds = [];
|
|
179
176
|
if (refListData && typeof refListData === "string") try {
|
|
@@ -182,32 +179,42 @@ async function setApiKeyInStorage(ctx, apiKey, storage, ttl) {
|
|
|
182
179
|
keyIds = [];
|
|
183
180
|
}
|
|
184
181
|
else if (Array.isArray(refListData)) keyIds = refListData;
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
182
|
+
const next = modify(keyIds);
|
|
183
|
+
if (next.length === 0) await storage.delete(refKey);
|
|
184
|
+
else await storage.set(refKey, JSON.stringify(next));
|
|
185
|
+
}
|
|
186
|
+
async function setApiKeyInStorage(_ctx, apiKey, storage, ttl, opts) {
|
|
187
|
+
const serialized = serializeApiKey(apiKey);
|
|
188
|
+
const refKey = getStorageKeyByReferenceId(apiKey.referenceId);
|
|
189
|
+
if (opts.fallbackToDatabase) {
|
|
190
|
+
await Promise.all([
|
|
191
|
+
storage.set(getStorageKeyByHashedKey(apiKey.key), serialized, ttl),
|
|
192
|
+
storage.set(getStorageKeyById(apiKey.id), serialized, ttl),
|
|
193
|
+
storage.delete(refKey)
|
|
194
|
+
]);
|
|
195
|
+
return;
|
|
188
196
|
}
|
|
197
|
+
await Promise.all([storage.set(getStorageKeyByHashedKey(apiKey.key), serialized, ttl), storage.set(getStorageKeyById(apiKey.id), serialized, ttl)]);
|
|
198
|
+
await modifyRefList(storage, refKey, (ids) => ids.includes(apiKey.id) ? ids : [...ids, apiKey.id]);
|
|
189
199
|
}
|
|
190
200
|
/**
|
|
191
201
|
* Delete API key from secondary storage
|
|
192
202
|
*/
|
|
193
|
-
async function deleteApiKeyFromStorage(ctx, apiKey, storage) {
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (refListData && typeof refListData === "string") try {
|
|
203
|
-
keyIds = JSON.parse(refListData);
|
|
204
|
-
} catch {
|
|
205
|
-
keyIds = [];
|
|
203
|
+
async function deleteApiKeyFromStorage(ctx, apiKey, storage, opts) {
|
|
204
|
+
const refKey = getStorageKeyByReferenceId(apiKey.referenceId);
|
|
205
|
+
if (opts.fallbackToDatabase) {
|
|
206
|
+
await Promise.all([
|
|
207
|
+
storage.delete(getStorageKeyByHashedKey(apiKey.key)),
|
|
208
|
+
storage.delete(getStorageKeyById(apiKey.id)),
|
|
209
|
+
storage.delete(refKey)
|
|
210
|
+
]);
|
|
211
|
+
return;
|
|
206
212
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
213
|
+
await Promise.all([
|
|
214
|
+
storage.delete(getStorageKeyByHashedKey(apiKey.key)),
|
|
215
|
+
storage.delete(getStorageKeyById(apiKey.id)),
|
|
216
|
+
modifyRefList(storage, refKey, (ids) => ids.filter((keyId) => keyId !== apiKey.id))
|
|
217
|
+
]);
|
|
211
218
|
}
|
|
212
219
|
/**
|
|
213
220
|
* Unified getter for API keys with support for all storage modes
|
|
@@ -233,7 +240,7 @@ async function getApiKey$1(ctx, hashedKey, opts) {
|
|
|
233
240
|
value: hashedKey
|
|
234
241
|
}]
|
|
235
242
|
});
|
|
236
|
-
if (dbKey && storage) await setApiKeyInStorage(ctx, dbKey, storage, calculateTTL(dbKey));
|
|
243
|
+
if (dbKey && storage) await setApiKeyInStorage(ctx, dbKey, storage, calculateTTL(dbKey), opts);
|
|
237
244
|
return dbKey;
|
|
238
245
|
}
|
|
239
246
|
if (opts.storage === "secondary-storage") {
|
|
@@ -272,7 +279,7 @@ async function getApiKeyById(ctx, id, opts) {
|
|
|
272
279
|
value: id
|
|
273
280
|
}]
|
|
274
281
|
});
|
|
275
|
-
if (dbKey && storage) await setApiKeyInStorage(ctx, dbKey, storage, calculateTTL(dbKey));
|
|
282
|
+
if (dbKey && storage) await setApiKeyInStorage(ctx, dbKey, storage, calculateTTL(dbKey), opts);
|
|
276
283
|
return dbKey;
|
|
277
284
|
}
|
|
278
285
|
if (opts.storage === "secondary-storage") {
|
|
@@ -296,7 +303,7 @@ async function setApiKey(ctx, apiKey, opts) {
|
|
|
296
303
|
if (opts.storage === "database") return;
|
|
297
304
|
if (opts.storage === "secondary-storage") {
|
|
298
305
|
if (!storage) throw new Error("Secondary storage is required when storage mode is 'secondary-storage'");
|
|
299
|
-
await setApiKeyInStorage(ctx, apiKey, storage, ttl);
|
|
306
|
+
await setApiKeyInStorage(ctx, apiKey, storage, ttl, opts);
|
|
300
307
|
return;
|
|
301
308
|
}
|
|
302
309
|
}
|
|
@@ -308,7 +315,7 @@ async function deleteApiKey$1(ctx, apiKey, opts) {
|
|
|
308
315
|
if (opts.storage === "database") return;
|
|
309
316
|
if (opts.storage === "secondary-storage") {
|
|
310
317
|
if (!storage) throw new Error("Secondary storage is required when storage mode is 'secondary-storage'");
|
|
311
|
-
await deleteApiKeyFromStorage(ctx, apiKey, storage);
|
|
318
|
+
await deleteApiKeyFromStorage(ctx, apiKey, storage, opts);
|
|
312
319
|
return;
|
|
313
320
|
}
|
|
314
321
|
}
|
|
@@ -378,11 +385,7 @@ async function listApiKeys$1(ctx, referenceId, opts, paginationOpts) {
|
|
|
378
385
|
}
|
|
379
386
|
else if (Array.isArray(refListData)) keyIds = refListData;
|
|
380
387
|
if (keyIds.length > 0) {
|
|
381
|
-
const apiKeys =
|
|
382
|
-
for (const id of keyIds) {
|
|
383
|
-
const apiKey = await getApiKeyByIdFromStorage(ctx, id, storage);
|
|
384
|
-
if (apiKey) apiKeys.push(apiKey);
|
|
385
|
-
}
|
|
388
|
+
const apiKeys = (await mapConcurrent(keyIds, (id) => getApiKeyByIdFromStorage(ctx, id, storage), { concurrency: STORAGE_CONCURRENCY })).filter((key) => key !== null && key !== void 0);
|
|
386
389
|
return {
|
|
387
390
|
apiKeys: applySortingAndPagination(apiKeys, sortBy, sortDirection, limit, offset),
|
|
388
391
|
total: apiKeys.length
|
|
@@ -409,11 +412,8 @@ async function listApiKeys$1(ctx, referenceId, opts, paginationOpts) {
|
|
|
409
412
|
}]
|
|
410
413
|
})]);
|
|
411
414
|
if (storage && dbKeys.length > 0) {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
await setApiKeyInStorage(ctx, apiKey, storage, calculateTTL(apiKey));
|
|
415
|
-
keyIds.push(apiKey.id);
|
|
416
|
-
}
|
|
415
|
+
await mapConcurrent(dbKeys, (apiKey) => setApiKeyInStorage(ctx, apiKey, storage, calculateTTL(apiKey), opts), { concurrency: STORAGE_CONCURRENCY });
|
|
416
|
+
const keyIds = dbKeys.map((apiKey) => apiKey.id);
|
|
417
417
|
await storage.set(refKey, JSON.stringify(keyIds));
|
|
418
418
|
}
|
|
419
419
|
return {
|
|
@@ -442,11 +442,7 @@ async function listApiKeys$1(ctx, referenceId, opts, paginationOpts) {
|
|
|
442
442
|
apiKeys: [],
|
|
443
443
|
total: 0
|
|
444
444
|
};
|
|
445
|
-
const apiKeys =
|
|
446
|
-
for (const id of keyIds) {
|
|
447
|
-
const apiKey = await getApiKeyByIdFromStorage(ctx, id, storage);
|
|
448
|
-
if (apiKey) apiKeys.push(apiKey);
|
|
449
|
-
}
|
|
445
|
+
const apiKeys = (await mapConcurrent(keyIds, (id) => getApiKeyByIdFromStorage(ctx, id, storage), { concurrency: STORAGE_CONCURRENCY })).filter((key) => key !== null && key !== void 0);
|
|
450
446
|
return {
|
|
451
447
|
apiKeys: applySortingAndPagination(apiKeys, sortBy, sortDirection, limit, offset),
|
|
452
448
|
total: apiKeys.length
|
|
@@ -1285,18 +1281,16 @@ function listApiKeys({ configurations, schema, deleteAllExpiredApiKeys }) {
|
|
|
1285
1281
|
const storageKey = getStorageIdentifier(config);
|
|
1286
1282
|
if (!storageGroups.has(storageKey)) storageGroups.set(storageKey, config);
|
|
1287
1283
|
}
|
|
1284
|
+
const groupResults = await Promise.all([...storageGroups.values()].map((opts) => listApiKeys$1(ctx, referenceId, opts, {
|
|
1285
|
+
limit: void 0,
|
|
1286
|
+
offset: void 0,
|
|
1287
|
+
sortBy: ctx.query?.sortBy,
|
|
1288
|
+
sortDirection: ctx.query?.sortDirection
|
|
1289
|
+
})));
|
|
1288
1290
|
const seenIds = /* @__PURE__ */ new Set();
|
|
1289
|
-
for (const
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
offset: void 0,
|
|
1293
|
-
sortBy: ctx.query?.sortBy,
|
|
1294
|
-
sortDirection: ctx.query?.sortDirection
|
|
1295
|
-
});
|
|
1296
|
-
for (const key of apiKeys) if (!seenIds.has(key.id)) {
|
|
1297
|
-
seenIds.add(key.id);
|
|
1298
|
-
allApiKeys.push(key);
|
|
1299
|
-
}
|
|
1291
|
+
for (const { apiKeys } of groupResults) for (const key of apiKeys) if (!seenIds.has(key.id)) {
|
|
1292
|
+
seenIds.add(key.id);
|
|
1293
|
+
allApiKeys.push(key);
|
|
1300
1294
|
}
|
|
1301
1295
|
}
|
|
1302
1296
|
let filteredApiKeys = allApiKeys.filter((key) => {
|
|
@@ -1641,6 +1635,7 @@ function isRateLimited(apiKey, opts) {
|
|
|
1641
1635
|
async function validateApiKey({ hashedKey, ctx, opts, schema, permissions }) {
|
|
1642
1636
|
const apiKey = await getApiKey$1(ctx, hashedKey, opts);
|
|
1643
1637
|
if (!apiKey) throw APIError$1.from("UNAUTHORIZED", API_KEY_ERROR_CODES.INVALID_API_KEY);
|
|
1638
|
+
if (apiKey.configId !== (opts.configId ?? "default")) throw APIError$1.from("UNAUTHORIZED", API_KEY_ERROR_CODES.INVALID_API_KEY);
|
|
1644
1639
|
if (apiKey.enabled === false) throw APIError$1.from("UNAUTHORIZED", API_KEY_ERROR_CODES.KEY_DISABLED);
|
|
1645
1640
|
if (apiKey.expiresAt) {
|
|
1646
1641
|
if (Date.now() > new Date(apiKey.expiresAt).getTime()) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/api-key",
|
|
3
|
-
"version": "1.7.0-beta.
|
|
3
|
+
"version": "1.7.0-beta.3",
|
|
4
4
|
"description": "API Key plugin for Better Auth.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -61,13 +61,13 @@
|
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"tsdown": "0.21.1",
|
|
64
|
-
"@better-auth/core": "1.7.0-beta.
|
|
65
|
-
"better-auth": "1.7.0-beta.
|
|
64
|
+
"@better-auth/core": "1.7.0-beta.3",
|
|
65
|
+
"better-auth": "1.7.0-beta.3"
|
|
66
66
|
},
|
|
67
67
|
"peerDependencies": {
|
|
68
68
|
"@better-auth/utils": "0.4.0",
|
|
69
|
-
"@better-auth/core": "^1.7.0-beta.
|
|
70
|
-
"better-auth": "^1.7.0-beta.
|
|
69
|
+
"@better-auth/core": "^1.7.0-beta.3",
|
|
70
|
+
"better-auth": "^1.7.0-beta.3"
|
|
71
71
|
},
|
|
72
72
|
"scripts": {
|
|
73
73
|
"build": "tsdown",
|