{"version":3,"file":"index.mjs","names":["getApiKey","deleteApiKey","listApiKeys","ERROR_CODES","APIError","BaseAPIError","APIError","APIError","ERROR_CODES","APIError","ERROR_CODES","deleteApiKeyFromStorage","APIError","ERROR_CODES","listApiKeysFromStorage","APIError","ERROR_CODES","ERROR_CODES","getApiKey","APIError","ERROR_CODES","deleteApiKey"],"sources":["../src/adapter.ts","../src/org-authorization.ts","../src/utils.ts","../src/routes/create-api-key.ts","../src/routes/delete-all-expired-api-keys.ts","../src/routes/delete-api-key.ts","../src/routes/get-api-key.ts","../src/routes/list-api-keys.ts","../src/routes/update-api-key.ts","../src/rate-limit.ts","../src/routes/verify-api-key.ts","../src/routes/index.ts","../src/schema.ts","../src/index.ts"],"sourcesContent":["import type { GenericEndpointContext } from \"@better-auth/core\";\nimport type { SecondaryStorage } from \"@better-auth/core/db\";\nimport { safeJSONParse } from \"@better-auth/core/utils/json\";\nimport type { PredefinedApiKeyOptions } from \"./routes\";\nimport type { ApiKey } from \"./types\";\n\n/**\n * Parses double-stringified metadata synchronously without updating the database.\n * Use this for reading metadata, then call migrateLegacyMetadataInBackground for DB updates.\n *\n * @returns The properly parsed metadata object, or the original if already an object\n */\nexport function parseDoubleStringifiedMetadata(\n\tmetadata: ApiKey[\"metadata\"],\n): Record<string, any> | null {\n\t// If metadata is null/undefined, return null\n\tif (metadata == null) {\n\t\treturn null;\n\t}\n\n\t// If metadata is already an object, no migration needed\n\tif (typeof metadata === \"object\") {\n\t\treturn metadata;\n\t}\n\n\t// Metadata is a string - this is legacy double-stringified data\n\t// Parse it to get the actual object\n\treturn safeJSONParse<Record<string, any>>(metadata);\n}\n\n/**\n * Checks if metadata needs migration (is a string instead of object)\n */\nfunction needsMetadataMigration(metadata: ApiKey[\"metadata\"]): boolean {\n\treturn metadata != null && typeof metadata === \"string\";\n}\n\n/**\n * Batch migrates double-stringified metadata for multiple API keys.\n * Runs all updates in parallel to avoid N sequential database calls.\n */\nexport async function batchMigrateLegacyMetadata(\n\tctx: GenericEndpointContext,\n\tapiKeys: ApiKey[],\n\topts: PredefinedApiKeyOptions,\n): Promise<void> {\n\t// Only migrate for database storage\n\tif (opts.storage !== \"database\" && !opts.fallbackToDatabase) {\n\t\treturn;\n\t}\n\n\t// Filter keys that need migration\n\tconst keysToMigrate = apiKeys.filter((key) =>\n\t\tneedsMetadataMigration(key.metadata),\n\t);\n\n\tif (keysToMigrate.length === 0) {\n\t\treturn;\n\t}\n\n\t// Run migrations in parallel (not sequentially)\n\tconst migrationPromises = keysToMigrate.map(async (apiKey) => {\n\t\tconst parsed = parseDoubleStringifiedMetadata(apiKey.metadata);\n\t\ttry {\n\t\t\tawait ctx.context.adapter.update({\n\t\t\t\tmodel: \"apikey\",\n\t\t\t\twhere: [{ field: \"id\", value: apiKey.id }],\n\t\t\t\tupdate: { metadata: parsed },\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tctx.context.logger.warn(\n\t\t\t\t`Failed to migrate double-stringified metadata for API key ${apiKey.id}:`,\n\t\t\t\terror,\n\t\t\t);\n\t\t}\n\t});\n\n\tawait Promise.all(migrationPromises);\n}\n\n/**\n * Migrates double-stringified metadata to properly parsed object.\n *\n * This handles legacy data where metadata was incorrectly double-stringified.\n * If metadata is a string (should be object after adapter's transform.output),\n * it parses it and optionally updates the database.\n *\n * @returns The properly parsed metadata object\n */\nexport async function migrateDoubleStringifiedMetadata(\n\tctx: GenericEndpointContext,\n\tapiKey: ApiKey,\n\topts: PredefinedApiKeyOptions,\n): Promise<Record<string, any> | null> {\n\tconst parsed = parseDoubleStringifiedMetadata(apiKey.metadata);\n\n\t// Update the database to fix the legacy data (only for database storage)\n\tif (\n\t\tneedsMetadataMigration(apiKey.metadata) &&\n\t\t(opts.storage === \"database\" || opts.fallbackToDatabase)\n\t) {\n\t\ttry {\n\t\t\tawait ctx.context.adapter.update({\n\t\t\t\tmodel: \"apikey\",\n\t\t\t\twhere: [{ field: \"id\", value: apiKey.id }],\n\t\t\t\tupdate: { metadata: parsed },\n\t\t\t});\n\t\t} catch (error) {\n\t\t\t// Log but don't fail the request if migration update fails\n\t\t\tctx.context.logger.warn(\n\t\t\t\t`Failed to migrate double-stringified metadata for API key ${apiKey.id}:`,\n\t\t\t\terror,\n\t\t\t);\n\t\t}\n\t}\n\n\treturn parsed;\n}\n\n/**\n * Generate storage key for API key by hashed key\n */\nfunction getStorageKeyByHashedKey(hashedKey: string): string {\n\treturn `api-key:${hashedKey}`;\n}\n\n/**\n * Generate storage key for API key by ID\n */\nfunction getStorageKeyById(id: string): string {\n\treturn `api-key:by-id:${id}`;\n}\n\n/**\n * Generate storage key for reference's API key list (user or org)\n */\nfunction getStorageKeyByReferenceId(referenceId: string): string {\n\treturn `api-key:by-ref:${referenceId}`;\n}\n\n/**\n * Serialize API key for storage\n */\nfunction serializeApiKey(apiKey: ApiKey): string {\n\treturn JSON.stringify({\n\t\t...apiKey,\n\t\tcreatedAt: apiKey.createdAt.toISOString(),\n\t\tupdatedAt: apiKey.updatedAt.toISOString(),\n\t\texpiresAt: apiKey.expiresAt?.toISOString() ?? null,\n\t\tlastRefillAt: apiKey.lastRefillAt?.toISOString() ?? null,\n\t\tlastRequest: apiKey.lastRequest?.toISOString() ?? null,\n\t});\n}\n\n/**\n * Deserialize API key from storage\n */\nfunction deserializeApiKey(data: unknown): ApiKey | null {\n\tif (!data || typeof data !== \"string\") {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst parsed = JSON.parse(data);\n\t\treturn {\n\t\t\t...parsed,\n\t\t\tcreatedAt: new Date(parsed.createdAt),\n\t\t\tupdatedAt: new Date(parsed.updatedAt),\n\t\t\texpiresAt: parsed.expiresAt ? new Date(parsed.expiresAt) : null,\n\t\t\tlastRefillAt: parsed.lastRefillAt ? new Date(parsed.lastRefillAt) : null,\n\t\t\tlastRequest: parsed.lastRequest ? new Date(parsed.lastRequest) : null,\n\t\t} as ApiKey;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Get the storage instance to use (custom methods take precedence)\n */\nfunction getStorageInstance(\n\tctx: GenericEndpointContext,\n\topts: PredefinedApiKeyOptions,\n): SecondaryStorage | null {\n\tif (opts.customStorage) {\n\t\treturn opts.customStorage as SecondaryStorage;\n\t}\n\treturn ctx.context.secondaryStorage || null;\n}\n\n/**\n * Calculate TTL in seconds for an API key\n */\nfunction calculateTTL(apiKey: ApiKey): number | undefined {\n\tif (apiKey.expiresAt) {\n\t\tconst now = Date.now();\n\t\tconst expiresAt = new Date(apiKey.expiresAt).getTime();\n\t\tconst ttlSeconds = Math.floor((expiresAt - now) / 1000);\n\t\t// Only set TTL if expiration is in the future\n\t\tif (ttlSeconds > 0) {\n\t\t\treturn ttlSeconds;\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Get API key from secondary storage by hashed key\n */\nasync function getApiKeyFromStorage(\n\tctx: GenericEndpointContext,\n\thashedKey: string,\n\tstorage: SecondaryStorage,\n): Promise<ApiKey | null> {\n\tconst key = getStorageKeyByHashedKey(hashedKey);\n\tconst data = await storage.get(key);\n\treturn deserializeApiKey(data);\n}\n\n/**\n * Get API key from secondary storage by ID\n */\nasync function getApiKeyByIdFromStorage(\n\tctx: GenericEndpointContext,\n\tid: string,\n\tstorage: SecondaryStorage,\n): Promise<ApiKey | null> {\n\tconst key = getStorageKeyById(id);\n\tconst data = await storage.get(key);\n\treturn deserializeApiKey(data);\n}\n\n/**\n * Store API key in secondary storage\n */\nasync function setApiKeyInStorage(\n\tctx: GenericEndpointContext,\n\tapiKey: ApiKey,\n\tstorage: SecondaryStorage,\n\tttl?: number | undefined,\n): Promise<void> {\n\tconst serialized = serializeApiKey(apiKey);\n\tconst hashedKey = apiKey.key;\n\tconst id = apiKey.id;\n\n\t// Store by hashed key (primary lookup)\n\tawait storage.set(getStorageKeyByHashedKey(hashedKey), serialized, ttl);\n\n\t// Store by ID (for ID-based lookups)\n\tawait storage.set(getStorageKeyById(id), serialized, ttl);\n\n\t// Update reference's API key list (user or org)\n\tconst refKey = getStorageKeyByReferenceId(apiKey.referenceId);\n\tconst refListData = await storage.get(refKey);\n\tlet keyIds: string[] = [];\n\n\tif (refListData && typeof refListData === \"string\") {\n\t\ttry {\n\t\t\tkeyIds = JSON.parse(refListData);\n\t\t} catch {\n\t\t\tkeyIds = [];\n\t\t}\n\t} else if (Array.isArray(refListData)) {\n\t\tkeyIds = refListData;\n\t}\n\n\tif (!keyIds.includes(id)) {\n\t\tkeyIds.push(id);\n\t\tawait storage.set(refKey, JSON.stringify(keyIds));\n\t}\n}\n\n/**\n * Delete API key from secondary storage\n */\nasync function deleteApiKeyFromStorage(\n\tctx: GenericEndpointContext,\n\tapiKey: ApiKey,\n\tstorage: SecondaryStorage,\n): Promise<void> {\n\tconst hashedKey = apiKey.key;\n\tconst id = apiKey.id;\n\tconst referenceId = apiKey.referenceId;\n\n\t// Delete by hashed key\n\tawait storage.delete(getStorageKeyByHashedKey(hashedKey));\n\n\t// Delete by ID\n\tawait storage.delete(getStorageKeyById(id));\n\n\t// Update reference's API key list\n\tconst refKey = getStorageKeyByReferenceId(referenceId);\n\tconst refListData = await storage.get(refKey);\n\tlet keyIds: string[] = [];\n\n\tif (refListData && typeof refListData === \"string\") {\n\t\ttry {\n\t\t\tkeyIds = JSON.parse(refListData);\n\t\t} catch {\n\t\t\tkeyIds = [];\n\t\t}\n\t} else if (Array.isArray(refListData)) {\n\t\tkeyIds = refListData;\n\t}\n\n\tconst filteredIds = keyIds.filter((keyId) => keyId !== id);\n\tif (filteredIds.length === 0) {\n\t\tawait storage.delete(refKey);\n\t} else {\n\t\tawait storage.set(refKey, JSON.stringify(filteredIds));\n\t}\n}\n\n/**\n * Unified getter for API keys with support for all storage modes\n */\nexport async function getApiKey(\n\tctx: GenericEndpointContext,\n\thashedKey: string,\n\topts: PredefinedApiKeyOptions,\n): Promise<ApiKey | null> {\n\tconst storage = getStorageInstance(ctx, opts);\n\n\t// Database mode only\n\tif (opts.storage === \"database\") {\n\t\treturn await ctx.context.adapter.findOne<ApiKey>({\n\t\t\tmodel: \"apikey\",\n\t\t\twhere: [\n\t\t\t\t{\n\t\t\t\t\tfield: \"key\",\n\t\t\t\t\tvalue: hashedKey,\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t}\n\n\t// Secondary storage mode with fallback\n\tif (opts.storage === \"secondary-storage\" && opts.fallbackToDatabase) {\n\t\tif (storage) {\n\t\t\tconst cached = await getApiKeyFromStorage(ctx, hashedKey, storage);\n\t\t\tif (cached) {\n\t\t\t\treturn cached;\n\t\t\t}\n\t\t}\n\t\tconst dbKey = await ctx.context.adapter.findOne<ApiKey>({\n\t\t\tmodel: \"apikey\",\n\t\t\twhere: [\n\t\t\t\t{\n\t\t\t\t\tfield: \"key\",\n\t\t\t\t\tvalue: hashedKey,\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\n\t\tif (dbKey && storage) {\n\t\t\t// Populate secondary storage for future reads\n\t\t\tconst ttl = calculateTTL(dbKey);\n\t\t\tawait setApiKeyInStorage(ctx, dbKey, storage, ttl);\n\t\t}\n\n\t\treturn dbKey;\n\t}\n\n\t// Secondary storage mode only\n\tif (opts.storage === \"secondary-storage\") {\n\t\tif (!storage) {\n\t\t\treturn null;\n\t\t}\n\t\treturn await getApiKeyFromStorage(ctx, hashedKey, storage);\n\t}\n\n\t// Default fallback\n\treturn await ctx.context.adapter.findOne<ApiKey>({\n\t\tmodel: \"apikey\",\n\t\twhere: [\n\t\t\t{\n\t\t\t\tfield: \"key\",\n\t\t\t\tvalue: hashedKey,\n\t\t\t},\n\t\t],\n\t});\n}\n\n/**\n * Unified getter for API keys by ID\n */\nexport async function getApiKeyById(\n\tctx: GenericEndpointContext,\n\tid: string,\n\topts: PredefinedApiKeyOptions,\n): Promise<ApiKey | null> {\n\tconst storage = getStorageInstance(ctx, opts);\n\n\t// Database mode only\n\tif (opts.storage === \"database\") {\n\t\treturn await ctx.context.adapter.findOne<ApiKey>({\n\t\t\tmodel: \"apikey\",\n\t\t\twhere: [\n\t\t\t\t{\n\t\t\t\t\tfield: \"id\",\n\t\t\t\t\tvalue: id,\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t}\n\n\t// Secondary storage mode with fallback\n\tif (opts.storage === \"secondary-storage\" && opts.fallbackToDatabase) {\n\t\tif (storage) {\n\t\t\tconst cached = await getApiKeyByIdFromStorage(ctx, id, storage);\n\t\t\tif (cached) {\n\t\t\t\treturn cached;\n\t\t\t}\n\t\t}\n\t\tconst dbKey = await ctx.context.adapter.findOne<ApiKey>({\n\t\t\tmodel: \"apikey\",\n\t\t\twhere: [\n\t\t\t\t{\n\t\t\t\t\tfield: \"id\",\n\t\t\t\t\tvalue: id,\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\n\t\tif (dbKey && storage) {\n\t\t\t// Populate secondary storage for future reads\n\t\t\tconst ttl = calculateTTL(dbKey);\n\t\t\tawait setApiKeyInStorage(ctx, dbKey, storage, ttl);\n\t\t}\n\n\t\treturn dbKey;\n\t}\n\n\t// Secondary storage mode only\n\tif (opts.storage === \"secondary-storage\") {\n\t\tif (!storage) {\n\t\t\treturn null;\n\t\t}\n\t\treturn await getApiKeyByIdFromStorage(ctx, id, storage);\n\t}\n\n\t// Default fallback\n\treturn await ctx.context.adapter.findOne<ApiKey>({\n\t\tmodel: \"apikey\",\n\t\twhere: [\n\t\t\t{\n\t\t\t\tfield: \"id\",\n\t\t\t\tvalue: id,\n\t\t\t},\n\t\t],\n\t});\n}\n\n/**\n * Unified setter for API keys with support for all storage modes\n */\nexport async function setApiKey(\n\tctx: GenericEndpointContext,\n\tapiKey: ApiKey,\n\topts: PredefinedApiKeyOptions,\n): Promise<void> {\n\tconst storage = getStorageInstance(ctx, opts);\n\tconst ttl = calculateTTL(apiKey);\n\n\t// Database mode only - handled by adapter in route handlers\n\tif (opts.storage === \"database\") {\n\t\treturn;\n\t}\n\n\t// Secondary storage mode (with or without fallback)\n\tif (opts.storage === \"secondary-storage\") {\n\t\tif (!storage) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Secondary storage is required when storage mode is 'secondary-storage'\",\n\t\t\t);\n\t\t}\n\t\tawait setApiKeyInStorage(ctx, apiKey, storage, ttl);\n\t\treturn;\n\t}\n}\n\n/**\n * Unified deleter for API keys with support for all storage modes\n */\nexport async function deleteApiKey(\n\tctx: GenericEndpointContext,\n\tapiKey: ApiKey,\n\topts: PredefinedApiKeyOptions,\n): Promise<void> {\n\tconst storage = getStorageInstance(ctx, opts);\n\n\t// Database mode only - handled by adapter in route handlers\n\tif (opts.storage === \"database\") {\n\t\treturn;\n\t}\n\n\t// Secondary storage mode (with or without fallback)\n\tif (opts.storage === \"secondary-storage\") {\n\t\tif (!storage) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Secondary storage is required when storage mode is 'secondary-storage'\",\n\t\t\t);\n\t\t}\n\t\tawait deleteApiKeyFromStorage(ctx, apiKey, storage);\n\t\treturn;\n\t}\n}\n\nexport interface ListApiKeysOptions {\n\tlimit?: number;\n\toffset?: number;\n\tsortBy?: string;\n\tsortDirection?: \"asc\" | \"desc\";\n}\n\nexport interface ListApiKeysResult {\n\tapiKeys: ApiKey[];\n\ttotal: number;\n}\n\n/**\n * Apply sorting and pagination to an array of API keys in memory\n * Used for secondary storage mode where we can't rely on database operations\n */\nfunction applySortingAndPagination(\n\tapiKeys: ApiKey[],\n\tsortBy?: string,\n\tsortDirection?: \"asc\" | \"desc\",\n\tlimit?: number,\n\toffset?: number,\n): ApiKey[] {\n\tlet result = [...apiKeys];\n\n\t// Apply sorting if sortBy is specified\n\tif (sortBy) {\n\t\tconst direction = sortDirection || \"asc\";\n\t\tresult.sort((a, b) => {\n\t\t\tconst aValue = a[sortBy as keyof ApiKey];\n\t\t\tconst bValue = b[sortBy as keyof ApiKey];\n\n\t\t\t// Handle null/undefined values\n\t\t\tif (aValue == null && bValue == null) return 0;\n\t\t\tif (aValue == null) return direction === \"asc\" ? -1 : 1;\n\t\t\tif (bValue == null) return direction === \"asc\" ? 1 : -1;\n\n\t\t\t// Compare values\n\t\t\tif (aValue < bValue) return direction === \"asc\" ? -1 : 1;\n\t\t\tif (aValue > bValue) return direction === \"asc\" ? 1 : -1;\n\t\t\treturn 0;\n\t\t});\n\t}\n\n\t// Apply pagination\n\tif (offset !== undefined) {\n\t\tresult = result.slice(offset);\n\t}\n\tif (limit !== undefined) {\n\t\tresult = result.slice(0, limit);\n\t}\n\n\treturn result;\n}\n\n/**\n * List API keys for a reference (user or org) with support for all storage modes\n */\nexport async function listApiKeys(\n\tctx: GenericEndpointContext,\n\treferenceId: string,\n\topts: PredefinedApiKeyOptions,\n\tpaginationOpts?: ListApiKeysOptions,\n): Promise<ListApiKeysResult> {\n\tconst storage = getStorageInstance(ctx, opts);\n\tconst { limit, offset, sortBy, sortDirection } = paginationOpts || {};\n\n\t// Database mode only\n\tif (opts.storage === \"database\") {\n\t\tconst [apiKeys, total] = await Promise.all([\n\t\t\tctx.context.adapter.findMany<ApiKey>({\n\t\t\t\tmodel: \"apikey\",\n\t\t\t\twhere: [\n\t\t\t\t\t{\n\t\t\t\t\t\tfield: \"referenceId\",\n\t\t\t\t\t\tvalue: referenceId,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tlimit,\n\t\t\t\toffset,\n\t\t\t\tsortBy: sortBy\n\t\t\t\t\t? { field: sortBy, direction: sortDirection || \"asc\" }\n\t\t\t\t\t: undefined,\n\t\t\t}),\n\t\t\tctx.context.adapter.count({\n\t\t\t\tmodel: \"apikey\",\n\t\t\t\twhere: [\n\t\t\t\t\t{\n\t\t\t\t\t\tfield: \"referenceId\",\n\t\t\t\t\t\tvalue: referenceId,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}),\n\t\t]);\n\t\treturn { apiKeys, total };\n\t}\n\n\t// Secondary storage mode with fallback\n\tif (opts.storage === \"secondary-storage\" && opts.fallbackToDatabase) {\n\t\tconst refKey = getStorageKeyByReferenceId(referenceId);\n\n\t\tif (storage) {\n\t\t\tconst refListData = await storage.get(refKey);\n\t\t\tlet keyIds: string[] = [];\n\n\t\t\tif (refListData && typeof refListData === \"string\") {\n\t\t\t\ttry {\n\t\t\t\t\tkeyIds = JSON.parse(refListData);\n\t\t\t\t} catch {\n\t\t\t\t\tkeyIds = [];\n\t\t\t\t}\n\t\t\t} else if (Array.isArray(refListData)) {\n\t\t\t\tkeyIds = refListData;\n\t\t\t}\n\n\t\t\tif (keyIds.length > 0) {\n\t\t\t\tconst apiKeys: ApiKey[] = [];\n\t\t\t\tfor (const id of keyIds) {\n\t\t\t\t\tconst apiKey = await getApiKeyByIdFromStorage(ctx, id, storage);\n\t\t\t\t\tif (apiKey) {\n\t\t\t\t\t\tapiKeys.push(apiKey);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Apply sorting and pagination in memory for secondary storage\n\t\t\t\tconst sortedKeys = applySortingAndPagination(\n\t\t\t\t\tapiKeys,\n\t\t\t\t\tsortBy,\n\t\t\t\t\tsortDirection,\n\t\t\t\t\tlimit,\n\t\t\t\t\toffset,\n\t\t\t\t);\n\t\t\t\treturn { apiKeys: sortedKeys, total: apiKeys.length };\n\t\t\t}\n\t\t}\n\t\t// Fallback to database\n\t\tconst [dbKeys, total] = await Promise.all([\n\t\t\tctx.context.adapter.findMany<ApiKey>({\n\t\t\t\tmodel: \"apikey\",\n\t\t\t\twhere: [\n\t\t\t\t\t{\n\t\t\t\t\t\tfield: \"referenceId\",\n\t\t\t\t\t\tvalue: referenceId,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tlimit,\n\t\t\t\toffset,\n\t\t\t\tsortBy: sortBy\n\t\t\t\t\t? { field: sortBy, direction: sortDirection || \"asc\" }\n\t\t\t\t\t: undefined,\n\t\t\t}),\n\t\t\tctx.context.adapter.count({\n\t\t\t\tmodel: \"apikey\",\n\t\t\t\twhere: [\n\t\t\t\t\t{\n\t\t\t\t\t\tfield: \"referenceId\",\n\t\t\t\t\t\tvalue: referenceId,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}),\n\t\t]);\n\n\t\t// Populate secondary storage with fetched keys\n\t\tif (storage && dbKeys.length > 0) {\n\t\t\tconst keyIds: string[] = [];\n\t\t\tfor (const apiKey of dbKeys) {\n\t\t\t\t// Store each key in secondary storage\n\t\t\t\tconst ttl = calculateTTL(apiKey);\n\t\t\t\tawait setApiKeyInStorage(ctx, apiKey, storage, ttl);\n\t\t\t\tkeyIds.push(apiKey.id);\n\t\t\t}\n\t\t\t// Update reference's key list in secondary storage\n\t\t\tawait storage.set(refKey, JSON.stringify(keyIds));\n\t\t}\n\n\t\treturn { apiKeys: dbKeys, total };\n\t}\n\n\t// Secondary storage mode only\n\tif (opts.storage === \"secondary-storage\") {\n\t\tif (!storage) {\n\t\t\treturn { apiKeys: [], total: 0 };\n\t\t}\n\n\t\tconst refKey = getStorageKeyByReferenceId(referenceId);\n\t\tconst refListData = await storage.get(refKey);\n\t\tlet keyIds: string[] = [];\n\n\t\tif (refListData && typeof refListData === \"string\") {\n\t\t\ttry {\n\t\t\t\tkeyIds = JSON.parse(refListData);\n\t\t\t} catch {\n\t\t\t\treturn { apiKeys: [], total: 0 };\n\t\t\t}\n\t\t} else if (Array.isArray(refListData)) {\n\t\t\tkeyIds = refListData;\n\t\t} else {\n\t\t\treturn { apiKeys: [], total: 0 };\n\t\t}\n\n\t\tconst apiKeys: ApiKey[] = [];\n\t\tfor (const id of keyIds) {\n\t\t\tconst apiKey = await getApiKeyByIdFromStorage(ctx, id, storage);\n\t\t\tif (apiKey) {\n\t\t\t\tapiKeys.push(apiKey);\n\t\t\t}\n\t\t}\n\n\t\t// Apply sorting and pagination in memory for secondary storage\n\t\tconst sortedKeys = applySortingAndPagination(\n\t\t\tapiKeys,\n\t\t\tsortBy,\n\t\t\tsortDirection,\n\t\t\tlimit,\n\t\t\toffset,\n\t\t);\n\t\treturn { apiKeys: sortedKeys, total: apiKeys.length };\n\t}\n\n\t// Default fallback\n\tconst [apiKeys, total] = await Promise.all([\n\t\tctx.context.adapter.findMany<ApiKey>({\n\t\t\tmodel: \"apikey\",\n\t\t\twhere: [\n\t\t\t\t{\n\t\t\t\t\tfield: \"referenceId\",\n\t\t\t\t\tvalue: referenceId,\n\t\t\t\t},\n\t\t\t],\n\t\t\tlimit,\n\t\t\toffset,\n\t\t\tsortBy: sortBy\n\t\t\t\t? { field: sortBy, direction: sortDirection || \"asc\" }\n\t\t\t\t: undefined,\n\t\t}),\n\t\tctx.context.adapter.count({\n\t\t\tmodel: \"apikey\",\n\t\t\twhere: [\n\t\t\t\t{\n\t\t\t\t\tfield: \"referenceId\",\n\t\t\t\t\tvalue: referenceId,\n\t\t\t\t},\n\t\t\t],\n\t\t}),\n\t]);\n\treturn { apiKeys, total };\n}\n","import type { GenericEndpointContext } from \"@better-auth/core\";\nimport { APIError } from \"@better-auth/core/error\";\nimport type { OrganizationOptions } from \"better-auth/plugins/organization\";\nimport { API_KEY_ERROR_CODES as ERROR_CODES } from \".\";\n\n/**\n * API Key permission actions that can be configured in organization roles.\n * Users must add these permissions to their organization role definitions.\n */\nexport const API_KEY_PERMISSIONS = {\n\tcreate: \"create\",\n\tread: \"read\",\n\tupdate: \"update\",\n\tdelete: \"delete\",\n} as const;\n\nexport type ApiKeyPermissionAction =\n\t(typeof API_KEY_PERMISSIONS)[keyof typeof API_KEY_PERMISSIONS];\n\ninterface Member {\n\tid: string;\n\tuserId: string;\n\torganizationId: string;\n\trole: string;\n\tcreatedAt: Date;\n}\n\n/**\n * Gets the organization plugin options from the context.\n * Returns null if the organization plugin is not installed.\n */\nfunction getOrgOptions(\n\tctx: GenericEndpointContext,\n): OrganizationOptions | null {\n\tconst context = ctx.context;\n\tif (\"orgOptions\" in context && context.orgOptions) {\n\t\treturn context.orgOptions as OrganizationOptions;\n\t}\n\n\tconst orgPlugin = context.getPlugin?.(\"organization\");\n\tif (orgPlugin && \"options\" in orgPlugin) {\n\t\treturn orgPlugin.options as OrganizationOptions;\n\t}\n\n\treturn null;\n}\n\n/**\n * Checks if a user is a member of an organization and has the required permission.\n * This is used for organization-owned API keys to validate access.\n *\n * @param ctx - The endpoint context\n * @param userId - The ID of the user to check\n * @param organizationId - The ID of the organization (from API key's referenceId)\n * @param requiredAction - The action the user is trying to perform (create, read, update, delete)\n * @returns The member object if authorized\n * @throws APIError if not authorized\n */\nexport async function checkOrgApiKeyPermission(\n\tctx: GenericEndpointContext,\n\tuserId: string,\n\torganizationId: string,\n\trequiredAction: ApiKeyPermissionAction,\n): Promise<Member> {\n\t// Check if organization plugin is installed\n\tconst orgOptions = getOrgOptions(ctx);\n\tif (!orgOptions) {\n\t\tconst msg = ERROR_CODES.ORGANIZATION_PLUGIN_REQUIRED;\n\t\tthrow APIError.from(\"INTERNAL_SERVER_ERROR\", msg);\n\t}\n\n\t// Query the member table to check if user is a member of the organization\n\tconst member = await ctx.context.adapter.findOne<Member>({\n\t\tmodel: \"member\",\n\t\twhere: [\n\t\t\t{\n\t\t\t\tfield: \"userId\",\n\t\t\t\tvalue: userId,\n\t\t\t},\n\t\t\t{\n\t\t\t\tfield: \"organizationId\",\n\t\t\t\tvalue: organizationId,\n\t\t\t},\n\t\t],\n\t});\n\n\tif (!member) {\n\t\tconst msg = ERROR_CODES.USER_NOT_MEMBER_OF_ORGANIZATION;\n\t\tthrow APIError.from(\"FORBIDDEN\", msg);\n\t}\n\n\t// Check permission using the organization's permission system\n\tconst hasPermissionResult = await checkPermission(\n\t\tctx,\n\t\tmember.role,\n\t\torganizationId,\n\t\trequiredAction,\n\t\torgOptions,\n\t);\n\n\tif (!hasPermissionResult) {\n\t\tconst msg = ERROR_CODES.INSUFFICIENT_API_KEY_PERMISSIONS;\n\t\tthrow APIError.from(\"FORBIDDEN\", msg);\n\t}\n\n\treturn member;\n}\n\n/**\n * Checks if a role has the required permission for API key operations.\n * Uses the organization's access control system.\n *\n * Organization owners (determined by orgOptions.creatorRole, default \"owner\")\n * are granted full access to API key operations.\n */\nasync function checkPermission(\n\tctx: GenericEndpointContext,\n\trole: string,\n\torganizationId: string,\n\taction: ApiKeyPermissionAction,\n\torgOptions: OrganizationOptions,\n): Promise<boolean> {\n\t// Import hasPermission dynamically to avoid circular dependencies\n\tconst { hasPermission } = await import(\"better-auth/plugins/organization\");\n\n\ttry {\n\t\tconst result = await hasPermission(\n\t\t\t{\n\t\t\t\trole,\n\t\t\t\toptions: orgOptions,\n\t\t\t\tpermissions: {\n\t\t\t\t\tapiKey: [action],\n\t\t\t\t},\n\t\t\t\torganizationId,\n\t\t\t\t// Allow organization owners full access to API keys\n\t\t\t\tallowCreatorAllPermissions: true,\n\t\t\t},\n\t\t\tctx,\n\t\t);\n\n\t\treturn result;\n\t} catch {\n\t\treturn false;\n\t}\n}\n","import { APIError } from \"@better-auth/core/error\";\nimport { APIError as BaseAPIError } from \"better-auth/api\";\n\nexport const getDate = (span: number, unit: \"sec\" | \"ms\" = \"ms\") => {\n\treturn new Date(Date.now() + (unit === \"sec\" ? span * 1000 : span));\n};\n\nexport function isAPIError(error: unknown): error is APIError {\n\treturn (\n\t\terror instanceof BaseAPIError ||\n\t\terror instanceof APIError ||\n\t\t(error as any)?.name === \"APIError\"\n\t);\n}\n\nimport type { BetterAuthOptions } from \"@better-auth/core\";\nimport { isDevelopment, isTest } from \"@better-auth/core/env\";\nimport { isValidIP, normalizeIP } from \"@better-auth/core/utils/ip\";\n\n// Localhost IP used for test and development environments\nconst LOCALHOST_IP = \"127.0.0.1\";\n\nexport function getIp(\n\treq: Request | Headers,\n\toptions: BetterAuthOptions,\n): string | null {\n\tif (options.advanced?.ipAddress?.disableIpTracking) {\n\t\treturn null;\n\t}\n\n\tconst headers = \"headers\" in req ? req.headers : req;\n\n\tconst defaultHeaders = [\"x-forwarded-for\"];\n\n\tconst ipHeaders =\n\t\toptions.advanced?.ipAddress?.ipAddressHeaders || defaultHeaders;\n\n\tfor (const key of ipHeaders) {\n\t\tconst value = \"get\" in headers ? headers.get(key) : headers[key];\n\t\tif (typeof value === \"string\") {\n\t\t\tconst ip = value.split(\",\")[0]!.trim();\n\t\t\tif (isValidIP(ip)) {\n\t\t\t\treturn normalizeIP(ip, {\n\t\t\t\t\tipv6Subnet: options.advanced?.ipAddress?.ipv6Subnet,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Fallback to localhost IP in development/test environments when no IP found in headers\n\tif (isTest() || isDevelopment()) {\n\t\treturn LOCALHOST_IP;\n\t}\n\n\treturn null;\n}\n","import type { AuthContext, Awaitable } from \"@better-auth/core\";\nimport { createAuthEndpoint } from \"@better-auth/core/api\";\nimport { APIError } from \"@better-auth/core/error\";\nimport { generateId } from \"@better-auth/core/utils/id\";\nimport { safeJSONParse } from \"@better-auth/core/utils/json\";\nimport { getSessionFromCtx } from \"better-auth/api\";\nimport * as z from \"zod\";\nimport { API_KEY_TABLE_NAME, API_KEY_ERROR_CODES as ERROR_CODES } from \"..\";\nimport { defaultKeyHasher } from \"../\";\nimport { setApiKey } from \"../adapter\";\nimport { checkOrgApiKeyPermission } from \"../org-authorization\";\nimport type { apiKeySchema } from \"../schema\";\nimport type { ApiKey } from \"../types\";\nimport { getDate } from \"../utils\";\nimport type { PredefinedApiKeyOptions } from \".\";\nimport { resolveConfiguration } from \".\";\n\nconst createApiKeyBodySchema = z.object({\n\tconfigId: z\n\t\t.string()\n\t\t.meta({\n\t\t\tdescription:\n\t\t\t\t\"The configuration ID to use for the API key. If not provided, the default configuration will be used.\",\n\t\t})\n\t\t.optional(),\n\tname: z.string().meta({ description: \"Name of the Api Key\" }).optional(),\n\texpiresIn: z\n\t\t.number()\n\t\t.meta({\n\t\t\tdescription: \"Expiration time of the Api Key in seconds\",\n\t\t})\n\t\t.min(1)\n\t\t.optional()\n\t\t.nullable()\n\t\t.default(null),\n\tprefix: z\n\t\t.string()\n\t\t.meta({ description: \"Prefix of the Api Key\" })\n\t\t.regex(/^[a-zA-Z0-9_-]+$/, {\n\t\t\tmessage:\n\t\t\t\t\"Invalid prefix format, must be alphanumeric and contain only underscores and hyphens.\",\n\t\t})\n\t\t.optional(),\n\tremaining: z\n\t\t.number()\n\t\t.meta({\n\t\t\tdescription: \"Remaining number of requests. Server side only\",\n\t\t})\n\t\t.min(0)\n\t\t.optional()\n\t\t.nullable()\n\t\t.default(null),\n\tmetadata: z.any().optional(),\n\trefillAmount: z\n\t\t.number()\n\t\t.meta({\n\t\t\tdescription:\n\t\t\t\t\"Amount to refill the remaining count of the Api Key. server-only. Eg: 100\",\n\t\t})\n\t\t.min(1)\n\t\t.optional(),\n\trefillInterval: z\n\t\t.number()\n\t\t.meta({\n\t\t\tdescription:\n\t\t\t\t\"Interval to refill the Api Key in milliseconds. server-only. Eg: 1000\",\n\t\t})\n\t\t.optional(),\n\trateLimitTimeWindow: z\n\t\t.number()\n\t\t.meta({\n\t\t\tdescription:\n\t\t\t\t\"The duration in milliseconds where each request is counted. Once the `maxRequests` is reached, the request will be rejected until the `timeWindow` has passed, at which point the `timeWindow` will be reset. server-only. Eg: 1000\",\n\t\t})\n\t\t.optional(),\n\trateLimitMax: z\n\t\t.number()\n\t\t.meta({\n\t\t\tdescription:\n\t\t\t\t\"Maximum amount of requests allowed within a window. Once the `maxRequests` is reached, the request will be rejected until the `timeWindow` has passed, at which point the `timeWindow` will be reset. server-only. Eg: 100\",\n\t\t})\n\t\t.optional(),\n\trateLimitEnabled: z\n\t\t.boolean()\n\t\t.meta({\n\t\t\tdescription:\n\t\t\t\t\"Whether the key has rate limiting enabled. server-only. Eg: true\",\n\t\t})\n\t\t.optional(),\n\tpermissions: z\n\t\t.record(z.string(), z.array(z.string()))\n\t\t.meta({\n\t\t\tdescription: \"Permissions of the Api Key.\",\n\t\t})\n\t\t.optional(),\n\tuserId: z.coerce\n\t\t.string()\n\t\t.meta({\n\t\t\tdescription:\n\t\t\t\t'User Id of the user that the Api Key belongs to. server-only. Eg: \"user-id\"',\n\t\t})\n\t\t.optional(),\n\torganizationId: z.coerce\n\t\t.string()\n\t\t.meta({\n\t\t\tdescription:\n\t\t\t\t\"Organization Id of the organization that the Api Key belongs to. Eg: 'org-id'\",\n\t\t})\n\t\t.optional(),\n});\n\nexport function createApiKey({\n\tdefaultKeyGenerator,\n\tconfigurations,\n\tschema,\n\tdeleteAllExpiredApiKeys,\n}: {\n\tdefaultKeyGenerator: (options: {\n\t\tlength: number;\n\t\tprefix: string | undefined;\n\t}) => Awaitable<string>;\n\tconfigurations: PredefinedApiKeyOptions[];\n\tschema: ReturnType<typeof apiKeySchema>;\n\tdeleteAllExpiredApiKeys(\n\t\tctx: AuthContext,\n\t\tbyPassLastCheckTime?: boolean | undefined,\n\t): void;\n}) {\n\treturn createAuthEndpoint(\n\t\t\"/api-key/create\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: createApiKeyBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tdescription: \"Create a new API key for a user\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t\"200\": {\n\t\t\t\t\t\t\tdescription: \"API key created successfully\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tid: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Unique identifier of the API key\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tcreatedAt: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Creation timestamp\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tupdatedAt: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Last update timestamp\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tname: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Name of the API key\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tprefix: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Prefix of the API key\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tstart: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Starting characters of the key (if configured)\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tkey: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"The full API key (only returned on creation)\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tenabled: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Whether the key is enabled\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\texpiresAt: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Expiration timestamp\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\treferenceId: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"ID of the reference owning the key\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tlastRefillAt: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Last refill timestamp\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tlastRequest: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Last request timestamp\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tmetadata: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Metadata associated with the key\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trateLimitMax: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Maximum requests in time window\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trateLimitTimeWindow: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Rate limit time window in milliseconds\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tremaining: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Remaining requests\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trefillAmount: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Amount to refill\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trefillInterval: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Refill interval in milliseconds\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trateLimitEnabled: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Whether rate limiting is enabled\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trequestCount: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Current request count in window\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tpermissions: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tadditionalProperties: {\n\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\t\t\t\t\t\t\titems: { type: \"string\" },\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Permissions associated with the key\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\trequired: [\n\t\t\t\t\t\t\t\t\t\t\t\"id\",\n\t\t\t\t\t\t\t\t\t\t\t\"createdAt\",\n\t\t\t\t\t\t\t\t\t\t\t\"updatedAt\",\n\t\t\t\t\t\t\t\t\t\t\t\"key\",\n\t\t\t\t\t\t\t\t\t\t\t\"enabled\",\n\t\t\t\t\t\t\t\t\t\t\t\"referenceId\",\n\t\t\t\t\t\t\t\t\t\t\t\"rateLimitEnabled\",\n\t\t\t\t\t\t\t\t\t\t\t\"requestCount\",\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst {\n\t\t\t\tconfigId,\n\t\t\t\tname,\n\t\t\t\texpiresIn,\n\t\t\t\tprefix,\n\t\t\t\tremaining,\n\t\t\t\tmetadata,\n\t\t\t\trefillAmount,\n\t\t\t\trefillInterval,\n\t\t\t\tpermissions,\n\t\t\t\trateLimitMax,\n\t\t\t\trateLimitTimeWindow,\n\t\t\t\trateLimitEnabled,\n\t\t\t} = ctx.body;\n\n\t\t\tconst opts = resolveConfiguration(ctx.context, configurations, configId);\n\t\t\tconst keyGenerator = opts.customKeyGenerator || defaultKeyGenerator;\n\t\t\tconst session = await getSessionFromCtx(ctx);\n\t\t\tconst isClientRequest = ctx.request || ctx.headers;\n\n\t\t\t// if this endpoint was being called from the client,\n\t\t\t// we must make sure they can't use server-only properties.\n\t\t\tconst isUsingServerOnlyProps = (() => {\n\t\t\t\treturn (\n\t\t\t\t\trefillAmount !== undefined ||\n\t\t\t\t\trefillInterval !== undefined ||\n\t\t\t\t\trateLimitMax !== undefined ||\n\t\t\t\t\trateLimitTimeWindow !== undefined ||\n\t\t\t\t\trateLimitEnabled !== undefined ||\n\t\t\t\t\tpermissions !== undefined ||\n\t\t\t\t\tremaining !== null\n\t\t\t\t);\n\t\t\t})();\n\n\t\t\t// client: can't use server-only properties\n\t\t\tif (isClientRequest && isUsingServerOnlyProps) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.SERVER_ONLY_PROPERTY);\n\t\t\t}\n\n\t\t\t// client: can't specify userId - it's derived from session\n\t\t\t// Only check for actual HTTP requests (ctx.request is set), not direct API calls with just headers\n\t\t\tif (ctx.request && ctx.body.userId !== undefined) {\n\t\t\t\tthrow APIError.from(\"UNAUTHORIZED\", ERROR_CODES.UNAUTHORIZED_SESSION);\n\t\t\t}\n\n\t\t\t// Determine the reference type and ID based on configuration\n\t\t\tconst referencesType = opts.references ?? \"user\";\n\t\t\tlet referenceId: string;\n\n\t\t\tif (referencesType === \"organization\") {\n\t\t\t\t// Organization-owned API keys\n\t\t\t\tconst orgId = ctx.body.organizationId;\n\t\t\t\tif (!orgId) {\n\t\t\t\t\tconst msg = ERROR_CODES.ORGANIZATION_ID_REQUIRED;\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", msg);\n\t\t\t\t}\n\n\t\t\t\t// Get user ID from session or body\n\t\t\t\tconst userId = session?.user.id || ctx.body.userId;\n\t\t\t\tif (!userId) {\n\t\t\t\t\tthrow APIError.from(\"UNAUTHORIZED\", ERROR_CODES.UNAUTHORIZED_SESSION);\n\t\t\t\t}\n\n\t\t\t\t// Verify membership and permission\n\t\t\t\tawait checkOrgApiKeyPermission(ctx, userId, orgId, \"create\");\n\n\t\t\t\treferenceId = orgId;\n\t\t\t} else {\n\t\t\t\t// User-owned API keys (default)\n\t\t\t\tif (isClientRequest) {\n\t\t\t\t\tif (!session?.user.id) {\n\t\t\t\t\t\tconst msg = ERROR_CODES.UNAUTHORIZED_SESSION;\n\t\t\t\t\t\tthrow APIError.from(\"UNAUTHORIZED\", msg);\n\t\t\t\t\t}\n\t\t\t\t\treferenceId = session.user.id;\n\t\t\t\t} else {\n\t\t\t\t\tconst ctxUserId = ctx.body.userId;\n\t\t\t\t\tconst sessionUserId = session?.user.id;\n\t\t\t\t\tif (!sessionUserId && !ctxUserId) {\n\t\t\t\t\t\tconst msg = ERROR_CODES.UNAUTHORIZED_SESSION;\n\t\t\t\t\t\tthrow APIError.from(\"UNAUTHORIZED\", msg);\n\t\t\t\t\t}\n\t\t\t\t\t// ensures no mismatching user IDs between session headers and request body\n\t\t\t\t\tif (session && ctxUserId && sessionUserId !== ctxUserId) {\n\t\t\t\t\t\tconst msg = ERROR_CODES.UNAUTHORIZED_SESSION;\n\t\t\t\t\t\tthrow APIError.from(\"UNAUTHORIZED\", msg);\n\t\t\t\t\t}\n\t\t\t\t\treferenceId = (sessionUserId || ctxUserId) as string;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// if metadata is defined, than check that it's an object.\n\t\t\tif (metadata) {\n\t\t\t\tif (opts.enableMetadata === false) {\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.METADATA_DISABLED);\n\t\t\t\t}\n\t\t\t\tif (typeof metadata !== \"object\") {\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_METADATA_TYPE);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// make sure that if they pass a refill amount, they also pass a refill interval\n\t\t\tif (refillAmount && !refillInterval) {\n\t\t\t\tconst msg = ERROR_CODES.REFILL_AMOUNT_AND_INTERVAL_REQUIRED;\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", msg);\n\t\t\t}\n\t\t\t// make sure that if they pass a refill interval, they also pass a refill amount\n\t\t\tif (refillInterval && !refillAmount) {\n\t\t\t\tconst msg = ERROR_CODES.REFILL_INTERVAL_AND_AMOUNT_REQUIRED;\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", msg);\n\t\t\t}\n\n\t\t\tif (expiresIn) {\n\t\t\t\tif (opts.keyExpiration.disableCustomExpiresTime === true) {\n\t\t\t\t\tconst msg = ERROR_CODES.KEY_DISABLED_EXPIRATION;\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", msg);\n\t\t\t\t}\n\n\t\t\t\tconst expiresIn_in_days = expiresIn / (60 * 60 * 24);\n\n\t\t\t\tif (opts.keyExpiration.minExpiresIn > expiresIn_in_days) {\n\t\t\t\t\tconst msg = ERROR_CODES.EXPIRES_IN_IS_TOO_SMALL;\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", msg);\n\t\t\t\t} else if (opts.keyExpiration.maxExpiresIn < expiresIn_in_days) {\n\t\t\t\t\tconst msg = ERROR_CODES.EXPIRES_IN_IS_TOO_LARGE;\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", msg);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (prefix) {\n\t\t\t\tif (prefix.length < opts.minimumPrefixLength) {\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_PREFIX_LENGTH);\n\t\t\t\t}\n\t\t\t\tif (prefix.length > opts.maximumPrefixLength) {\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_PREFIX_LENGTH);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (name) {\n\t\t\t\tif (name.length < opts.minimumNameLength) {\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_NAME_LENGTH);\n\t\t\t\t}\n\t\t\t\tif (name.length > opts.maximumNameLength) {\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_NAME_LENGTH);\n\t\t\t\t}\n\t\t\t} else if (opts.requireName) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.NAME_REQUIRED);\n\t\t\t}\n\n\t\t\tdeleteAllExpiredApiKeys(ctx.context);\n\n\t\t\tconst key = await keyGenerator({\n\t\t\t\tlength: opts.defaultKeyLength,\n\t\t\t\tprefix: prefix || opts.defaultPrefix,\n\t\t\t});\n\n\t\t\tconst hashed = opts.disableKeyHashing ? key : await defaultKeyHasher(key);\n\n\t\t\tlet start: string | null = null;\n\n\t\t\tif (opts.startingCharactersConfig.shouldStore) {\n\t\t\t\tstart = key.substring(\n\t\t\t\t\t0,\n\t\t\t\t\topts.startingCharactersConfig.charactersLength,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst defaultPermissions = opts.permissions?.defaultPermissions\n\t\t\t\t? typeof opts.permissions.defaultPermissions === \"function\"\n\t\t\t\t\t? await opts.permissions.defaultPermissions(referenceId, ctx)\n\t\t\t\t\t: opts.permissions.defaultPermissions\n\t\t\t\t: undefined;\n\t\t\tconst permissionsToApply = permissions\n\t\t\t\t? JSON.stringify(permissions)\n\t\t\t\t: defaultPermissions\n\t\t\t\t\t? JSON.stringify(defaultPermissions)\n\t\t\t\t\t: undefined;\n\n\t\t\tconst resolvedConfigId = opts.configId ?? \"default\";\n\n\t\t\tconst data: Omit<ApiKey, \"id\"> = {\n\t\t\t\tconfigId: resolvedConfigId,\n\t\t\t\tcreatedAt: new Date(),\n\t\t\t\tupdatedAt: new Date(),\n\t\t\t\tname: name ?? null,\n\t\t\t\tprefix: prefix ?? opts.defaultPrefix ?? null,\n\t\t\t\tstart: start,\n\t\t\t\tkey: hashed,\n\t\t\t\tenabled: true,\n\t\t\t\texpiresAt: expiresIn\n\t\t\t\t\t? getDate(expiresIn, \"sec\")\n\t\t\t\t\t: opts.keyExpiration.defaultExpiresIn\n\t\t\t\t\t\t? getDate(opts.keyExpiration.defaultExpiresIn, \"sec\")\n\t\t\t\t\t\t: null,\n\t\t\t\treferenceId: referenceId,\n\t\t\t\tlastRefillAt: null,\n\t\t\t\tlastRequest: null,\n\t\t\t\tmetadata: null,\n\t\t\t\trateLimitMax: rateLimitMax ?? opts.rateLimit.maxRequests ?? null,\n\t\t\t\trateLimitTimeWindow:\n\t\t\t\t\trateLimitTimeWindow ?? opts.rateLimit.timeWindow ?? null,\n\t\t\t\tremaining:\n\t\t\t\t\tremaining === null ? remaining : (remaining ?? refillAmount ?? null),\n\t\t\t\trefillAmount: refillAmount ?? null,\n\t\t\t\trefillInterval: refillInterval ?? null,\n\t\t\t\trateLimitEnabled:\n\t\t\t\t\trateLimitEnabled === undefined\n\t\t\t\t\t\t? (opts.rateLimit.enabled ?? true)\n\t\t\t\t\t\t: rateLimitEnabled,\n\t\t\t\trequestCount: 0,\n\t\t\t\t//@ts-expect-error - we intentionally save the permissions as string on DB.\n\t\t\t\tpermissions: permissionsToApply,\n\t\t\t};\n\n\t\t\tif (metadata) {\n\t\t\t\t// The adapter will automatically apply the schema transform to stringify\n\t\t\t\tdata.metadata = metadata;\n\t\t\t}\n\n\t\t\tlet apiKey: ApiKey;\n\n\t\t\tif (opts.storage === \"secondary-storage\" && opts.fallbackToDatabase) {\n\t\t\t\tapiKey = await ctx.context.adapter.create<Omit<ApiKey, \"id\">, ApiKey>({\n\t\t\t\t\tmodel: API_KEY_TABLE_NAME,\n\t\t\t\t\tdata: data,\n\t\t\t\t});\n\t\t\t\tawait setApiKey(ctx, apiKey, opts);\n\t\t\t} else if (opts.storage === \"secondary-storage\") {\n\t\t\t\tconst generatedId = ctx.context.generateId({\n\t\t\t\t\tmodel: API_KEY_TABLE_NAME,\n\t\t\t\t});\n\t\t\t\tconst id = generatedId || generateId();\n\t\t\t\tapiKey = {\n\t\t\t\t\t...data,\n\t\t\t\t\tid,\n\t\t\t\t} as ApiKey;\n\t\t\t\tawait setApiKey(ctx, apiKey, opts);\n\t\t\t} else {\n\t\t\t\tapiKey = await ctx.context.adapter.create<Omit<ApiKey, \"id\">, ApiKey>({\n\t\t\t\t\tmodel: API_KEY_TABLE_NAME,\n\t\t\t\t\tdata: data,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn ctx.json({\n\t\t\t\t...(apiKey as ApiKey),\n\t\t\t\tkey: key,\n\t\t\t\tmetadata: metadata ?? null,\n\t\t\t\tpermissions: apiKey.permissions\n\t\t\t\t\t? safeJSONParse(apiKey.permissions)\n\t\t\t\t\t: null,\n\t\t\t});\n\t\t},\n\t);\n}\n","import type { AuthContext } from \"@better-auth/core\";\nimport { createAuthEndpoint } from \"@better-auth/core/api\";\n\nexport function deleteAllExpiredApiKeysEndpoint({\n\tdeleteAllExpiredApiKeys,\n}: {\n\tdeleteAllExpiredApiKeys(\n\t\tctx: AuthContext,\n\t\tbyPassLastCheckTime?: boolean | undefined,\n\t): Promise<void>;\n}) {\n\treturn createAuthEndpoint(\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t},\n\t\tasync (ctx) => {\n\t\t\ttry {\n\t\t\t\tawait deleteAllExpiredApiKeys(ctx.context, true);\n\t\t\t} catch (error) {\n\t\t\t\tctx.context.logger.error(\n\t\t\t\t\t\"[API KEY PLUGIN] Failed to delete expired API keys:\",\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t\treturn ctx.json({\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: error,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn ctx.json({ success: true, error: null });\n\t\t},\n\t);\n}\n","import type { AuthContext } from \"@better-auth/core\";\nimport { createAuthEndpoint } from \"@better-auth/core/api\";\nimport { APIError } from \"@better-auth/core/error\";\nimport { sessionMiddleware } from \"better-auth/api\";\nimport * as z from \"zod\";\nimport { API_KEY_TABLE_NAME, API_KEY_ERROR_CODES as ERROR_CODES } from \"..\";\nimport {\n\tdeleteApiKey as deleteApiKeyFromStorage,\n\tgetApiKeyById,\n} from \"../adapter\";\nimport { checkOrgApiKeyPermission } from \"../org-authorization\";\nimport type { apiKeySchema } from \"../schema\";\nimport type { ApiKey } from \"../types\";\nimport type { PredefinedApiKeyOptions } from \".\";\nimport { configIdMatches, resolveConfiguration } from \".\";\n\nconst deleteApiKeyBodySchema = z.object({\n\tconfigId: z\n\t\t.string()\n\t\t.meta({\n\t\t\tdescription:\n\t\t\t\t\"The configuration ID to use for the API key lookup. If not provided, the default configuration will be used.\",\n\t\t})\n\t\t.optional(),\n\tkeyId: z.string().meta({\n\t\tdescription: \"The id of the Api Key\",\n\t}),\n});\n\nexport function deleteApiKey({\n\tconfigurations,\n\tschema,\n\tdeleteAllExpiredApiKeys,\n}: {\n\tconfigurations: PredefinedApiKeyOptions[];\n\tschema: ReturnType<typeof apiKeySchema>;\n\tdeleteAllExpiredApiKeys(\n\t\tctx: AuthContext,\n\t\tbyPassLastCheckTime?: boolean | undefined,\n\t): void;\n}) {\n\treturn createAuthEndpoint(\n\t\t\"/api-key/delete\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: deleteApiKeyBodySchema,\n\t\t\tuse: [sessionMiddleware],\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tdescription: \"Delete an existing API key\",\n\t\t\t\t\trequestBody: {\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\tkeyId: {\n\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\tdescription: \"The id of the API key to delete\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\trequired: [\"keyId\"],\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t\"200\": {\n\t\t\t\t\t\t\tdescription: \"API key deleted successfully\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tsuccess: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Indicates if the API key was successfully deleted\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\trequired: [\"success\"],\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst { configId, keyId } = ctx.body;\n\t\t\tconst session = ctx.context.session;\n\t\t\tif (session.user.banned === true) {\n\t\t\t\tthrow APIError.from(\"UNAUTHORIZED\", ERROR_CODES.USER_BANNED);\n\t\t\t}\n\n\t\t\t// Use provided configId or fall back to default config for initial lookup\n\t\t\tconst lookupOpts = resolveConfiguration(\n\t\t\t\tctx.context,\n\t\t\t\tconfigurations,\n\t\t\t\tconfigId,\n\t\t\t);\n\t\t\tlet apiKey: ApiKey | null = null;\n\n\t\t\tapiKey = await getApiKeyById(ctx, keyId, lookupOpts);\n\n\t\t\tif (!apiKey) {\n\t\t\t\tthrow APIError.from(\"NOT_FOUND\", ERROR_CODES.KEY_NOT_FOUND);\n\t\t\t}\n\n\t\t\tif (!configIdMatches(apiKey.configId, lookupOpts.configId)) {\n\t\t\t\tthrow APIError.from(\"NOT_FOUND\", ERROR_CODES.KEY_NOT_FOUND);\n\t\t\t}\n\n\t\t\t// Resolve the correct config based on the API key's configId\n\t\t\tconst opts = resolveConfiguration(\n\t\t\t\tctx.context,\n\t\t\t\tconfigurations,\n\t\t\t\tapiKey.configId,\n\t\t\t);\n\n\t\t\t// Verify ownership - user can only delete their own user-owned keys\n\t\t\tconst referencesType = opts.references ?? \"user\";\n\t\t\tif (referencesType === \"organization\") {\n\t\t\t\t// For organization-owned keys, verify membership and permission\n\t\t\t\tawait checkOrgApiKeyPermission(\n\t\t\t\t\tctx,\n\t\t\t\t\tsession.user.id,\n\t\t\t\t\tapiKey.referenceId,\n\t\t\t\t\t\"delete\",\n\t\t\t\t);\n\t\t\t} else if (apiKey.referenceId !== session.user.id) {\n\t\t\t\tthrow APIError.from(\"NOT_FOUND\", ERROR_CODES.KEY_NOT_FOUND);\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tif (opts.storage === \"secondary-storage\" && opts.fallbackToDatabase) {\n\t\t\t\t\tawait deleteApiKeyFromStorage(ctx, apiKey, opts);\n\t\t\t\t\tawait ctx.context.adapter.delete<ApiKey>({\n\t\t\t\t\t\tmodel: API_KEY_TABLE_NAME,\n\t\t\t\t\t\twhere: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tfield: \"id\",\n\t\t\t\t\t\t\t\tvalue: apiKey.id,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t} else if (opts.storage === \"database\") {\n\t\t\t\t\tawait ctx.context.adapter.delete<ApiKey>({\n\t\t\t\t\t\tmodel: API_KEY_TABLE_NAME,\n\t\t\t\t\t\twhere: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tfield: \"id\",\n\t\t\t\t\t\t\t\tvalue: apiKey.id,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tawait deleteApiKeyFromStorage(ctx, apiKey, opts);\n\t\t\t\t}\n\t\t\t} catch (error: any) {\n\t\t\t\tthrow APIError.fromStatus(\"INTERNAL_SERVER_ERROR\", {\n\t\t\t\t\tmessage: error?.message,\n\t\t\t\t});\n\t\t\t}\n\t\t\tdeleteAllExpiredApiKeys(ctx.context);\n\t\t\treturn ctx.json({\n\t\t\t\tsuccess: true,\n\t\t\t});\n\t\t},\n\t);\n}\n","import type { AuthContext } from \"@better-auth/core\";\nimport { createAuthEndpoint } from \"@better-auth/core/api\";\nimport { APIError } from \"@better-auth/core/error\";\nimport { safeJSONParse } from \"@better-auth/core/utils/json\";\nimport { sessionMiddleware } from \"better-auth/api\";\nimport * as z from \"zod\";\nimport { API_KEY_ERROR_CODES as ERROR_CODES } from \"..\";\nimport { getApiKeyById, migrateDoubleStringifiedMetadata } from \"../adapter\";\nimport { checkOrgApiKeyPermission } from \"../org-authorization\";\nimport type { apiKeySchema } from \"../schema\";\nimport type { ApiKey } from \"../types\";\nimport type { PredefinedApiKeyOptions } from \".\";\nimport { configIdMatches, resolveConfiguration } from \".\";\n\nconst getApiKeyQuerySchema = z.object({\n\tconfigId: z\n\t\t.string()\n\t\t.meta({\n\t\t\tdescription:\n\t\t\t\t\"The configuration ID to use for the API key lookup. If not provided, the default configuration will be used.\",\n\t\t})\n\t\t.optional(),\n\tid: z.string().meta({\n\t\tdescription: \"The id of the Api Key\",\n\t}),\n});\n\nexport function getApiKey({\n\tconfigurations,\n\tschema,\n\tdeleteAllExpiredApiKeys,\n}: {\n\tconfigurations: PredefinedApiKeyOptions[];\n\tschema: ReturnType<typeof apiKeySchema>;\n\tdeleteAllExpiredApiKeys(\n\t\tctx: AuthContext,\n\t\tbyPassLastCheckTime?: boolean | undefined,\n\t): void;\n}) {\n\treturn createAuthEndpoint(\n\t\t\"/api-key/get\",\n\t\t{\n\t\t\tmethod: \"GET\",\n\t\t\tquery: getApiKeyQuerySchema,\n\t\t\tuse: [sessionMiddleware],\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tdescription: \"Retrieve an existing API key by ID\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t\"200\": {\n\t\t\t\t\t\t\tdescription: \"API key retrieved successfully\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tid: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"ID\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tname: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"The name of the key\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tstart: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Shows the first few characters of the API key, including the prefix. This allows you to show those few characters in the UI to make it easier for users to identify the API key.\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tprefix: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"The API Key prefix. Stored as plain text.\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tuserId: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"The owner of the user id\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trefillInterval: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"The interval in milliseconds between refills of the `remaining` count. Example: 3600000 // refill every hour (3600000ms = 1h)\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trefillAmount: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"The amount to refill\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tlastRefillAt: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"The last refill date\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tenabled: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Sets if key is enabled or disabled\",\n\t\t\t\t\t\t\t\t\t\t\t\tdefault: true,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trateLimitEnabled: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Whether the key has rate limiting enabled\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trateLimitTimeWindow: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"The duration in milliseconds\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trateLimitMax: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Maximum amount of requests allowed within a window\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trequestCount: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"The number of requests made within the rate limit time window\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tremaining: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Remaining requests (every time api key is used this should updated and should be updated on refill as well)\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tlastRequest: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"When last request occurred\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\texpiresAt: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Expiry date of a key\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tcreatedAt: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"created at\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tupdatedAt: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"updated at\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tmetadata: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Extra metadata about the apiKey\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tpermissions: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Permissions for the api key (stored as JSON string)\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\trequired: [\n\t\t\t\t\t\t\t\t\t\t\t\"id\",\n\t\t\t\t\t\t\t\t\t\t\t\"userId\",\n\t\t\t\t\t\t\t\t\t\t\t\"enabled\",\n\t\t\t\t\t\t\t\t\t\t\t\"rateLimitEnabled\",\n\t\t\t\t\t\t\t\t\t\t\t\"requestCount\",\n\t\t\t\t\t\t\t\t\t\t\t\"createdAt\",\n\t\t\t\t\t\t\t\t\t\t\t\"updatedAt\",\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst { configId, id } = ctx.query;\n\n\t\t\tconst session = ctx.context.session;\n\n\t\t\t// Use provided configId or fall back to default config for initial lookup\n\t\t\tconst lookupOpts = resolveConfiguration(\n\t\t\t\tctx.context,\n\t\t\t\tconfigurations,\n\t\t\t\tconfigId,\n\t\t\t);\n\t\t\tlet apiKey: ApiKey | null = null;\n\n\t\t\tapiKey = await getApiKeyById(ctx, id, lookupOpts);\n\n\t\t\tif (!apiKey) {\n\t\t\t\tthrow APIError.from(\"NOT_FOUND\", ERROR_CODES.KEY_NOT_FOUND);\n\t\t\t}\n\n\t\t\tif (!configIdMatches(apiKey.configId, lookupOpts.configId)) {\n\t\t\t\tthrow APIError.from(\"NOT_FOUND\", ERROR_CODES.KEY_NOT_FOUND);\n\t\t\t}\n\n\t\t\t// Resolve the correct config based on the API key's configId\n\t\t\tconst opts = resolveConfiguration(\n\t\t\t\tctx.context,\n\t\t\t\tconfigurations,\n\t\t\t\tapiKey.configId,\n\t\t\t);\n\n\t\t\t// Verify ownership based on config's references type\n\t\t\tconst referencesType = opts.references ?? \"user\";\n\t\t\tif (referencesType === \"organization\") {\n\t\t\t\t// For organization-owned keys, verify membership and permission\n\t\t\t\tawait checkOrgApiKeyPermission(\n\t\t\t\t\tctx,\n\t\t\t\t\tsession.user.id,\n\t\t\t\t\tapiKey.referenceId,\n\t\t\t\t\t\"read\",\n\t\t\t\t);\n\t\t\t} else if (apiKey.referenceId !== session.user.id) {\n\t\t\t\t// User-owned keys - verify user owns the key\n\t\t\t\tthrow APIError.from(\"NOT_FOUND\", ERROR_CODES.KEY_NOT_FOUND);\n\t\t\t}\n\n\t\t\tdeleteAllExpiredApiKeys(ctx.context);\n\n\t\t\t// Migrate legacy double-stringified metadata if needed\n\t\t\tconst metadata = await migrateDoubleStringifiedMetadata(\n\t\t\t\tctx,\n\t\t\t\tapiKey,\n\t\t\t\topts,\n\t\t\t);\n\n\t\t\tconst { key: _key, ...returningApiKey } = apiKey;\n\n\t\t\treturn ctx.json({\n\t\t\t\t...returningApiKey,\n\t\t\t\tmetadata,\n\t\t\t\tpermissions: returningApiKey.permissions\n\t\t\t\t\t? safeJSONParse<{\n\t\t\t\t\t\t\t[key: string]: string[];\n\t\t\t\t\t\t}>(returningApiKey.permissions)\n\t\t\t\t\t: null,\n\t\t\t});\n\t\t},\n\t);\n}\n","import type { AuthContext } from \"@better-auth/core\";\nimport { createAuthEndpoint } from \"@better-auth/core/api\";\nimport { safeJSONParse } from \"@better-auth/core/utils/json\";\nimport { sessionMiddleware } from \"better-auth/api\";\nimport * as z from \"zod\";\nimport {\n\tbatchMigrateLegacyMetadata,\n\tlistApiKeys as listApiKeysFromStorage,\n\tparseDoubleStringifiedMetadata,\n} from \"../adapter\";\nimport { checkOrgApiKeyPermission } from \"../org-authorization\";\nimport type { apiKeySchema } from \"../schema\";\nimport type { ApiKey } from \"../types\";\nimport type { PredefinedApiKeyOptions } from \".\";\nimport { configIdMatches, isDefaultConfigId, resolveConfiguration } from \".\";\n\n/**\n * Generate a unique identifier for a configuration's storage backend.\n * Used to group configurations that share the same storage and avoid duplicate queries.\n */\nfunction getStorageIdentifier(config: PredefinedApiKeyOptions): string {\n\tif (config.storage === \"database\") {\n\t\treturn \"database\";\n\t}\n\tif (config.customStorage) {\n\t\treturn `custom:${config.configId ?? \"default\"}`;\n\t}\n\treturn config.fallbackToDatabase\n\t\t? \"secondary-storage-with-fallback\"\n\t\t: \"secondary-storage\";\n}\n\nconst listApiKeysQuerySchema = z\n\t.object({\n\t\tconfigId: z\n\t\t\t.string()\n\t\t\t.meta({\n\t\t\t\tdescription:\n\t\t\t\t\t\"Filter by configuration ID. If not provided, returns keys from all configurations.\",\n\t\t\t})\n\t\t\t.optional(),\n\t\torganizationId: z\n\t\t\t.string()\n\t\t\t.meta({\n\t\t\t\tdescription:\n\t\t\t\t\t\"Organization ID to list keys for. If provided, returns organization-owned keys. If not provided, returns user-owned keys.\",\n\t\t\t})\n\t\t\t.optional(),\n\t\tlimit: z.coerce\n\t\t\t.number()\n\t\t\t.int()\n\t\t\t.nonnegative()\n\t\t\t.meta({\n\t\t\t\tdescription: \"The number of API keys to return\",\n\t\t\t})\n\t\t\t.optional(),\n\t\toffset: z.coerce\n\t\t\t.number()\n\t\t\t.int()\n\t\t\t.nonnegative()\n\t\t\t.meta({\n\t\t\t\tdescription: \"The offset to start from\",\n\t\t\t})\n\t\t\t.optional(),\n\t\tsortBy: z\n\t\t\t.string()\n\t\t\t.meta({\n\t\t\t\tdescription: \"The field to sort by (e.g., createdAt, name, expiresAt)\",\n\t\t\t})\n\t\t\t.optional(),\n\t\tsortDirection: z\n\t\t\t.enum([\"asc\", \"desc\"])\n\t\t\t.meta({\n\t\t\t\tdescription: \"The direction to sort by\",\n\t\t\t})\n\t\t\t.optional(),\n\t})\n\t.optional();\n\nexport function listApiKeys({\n\tconfigurations,\n\tschema,\n\tdeleteAllExpiredApiKeys,\n}: {\n\tconfigurations: PredefinedApiKeyOptions[];\n\tschema: ReturnType<typeof apiKeySchema>;\n\tdeleteAllExpiredApiKeys(\n\t\tctx: AuthContext,\n\t\tbyPassLastCheckTime?: boolean | undefined,\n\t): void;\n}) {\n\treturn createAuthEndpoint(\n\t\t\"/api-key/list\",\n\t\t{\n\t\t\tmethod: \"GET\",\n\t\t\tuse: [sessionMiddleware],\n\t\t\tquery: listApiKeysQuerySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"List all API keys for the authenticated user or for a specific organization\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t\"200\": {\n\t\t\t\t\t\t\tdescription: \"API keys retrieved successfully\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tapiKeys: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tid: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"ID\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tname: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"The name of the key\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstart: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"Shows the first few characters of the API key, including the prefix. This allows you to show those few characters in the UI to make it easier for users to identify the API key.\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tprefix: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"The API Key prefix. Stored as plain text.\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tuserId: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"The owner of the user id\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\trefillInterval: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"The interval in milliseconds between refills of the `remaining` count. Example: 3600000 // refill every hour (3600000ms = 1h)\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\trefillAmount: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"The amount to refill\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlastRefillAt: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"The last refill date\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tenabled: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Sets if key is enabled or disabled\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdefault: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\trateLimitEnabled: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"Whether the key has rate limiting enabled\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\trateLimitTimeWindow: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"The duration in milliseconds\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\trateLimitMax: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"Maximum amount of requests allowed within a window\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\trequestCount: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"The number of requests made within the rate limit time window\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tremaining: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"Remaining requests (every time api key is used this should updated and should be updated on refill as well)\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlastRequest: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"When last request occurred\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\texpiresAt: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Expiry date of a key\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcreatedAt: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"created at\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tupdatedAt: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"updated at\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tmetadata: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Extra metadata about the apiKey\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tpermissions: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"Permissions for the api key (stored as JSON string)\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\trequired: [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"id\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"userId\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"enabled\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"rateLimitEnabled\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"requestCount\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"createdAt\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"updatedAt\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\ttotal: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Total number of API keys\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tlimit: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"The limit used for pagination\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\toffset: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"The offset used for pagination\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\trequired: [\"apiKeys\", \"total\"],\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst session = ctx.context.session;\n\t\t\tconst configId = ctx.query?.configId;\n\t\t\tconst organizationId = ctx.query?.organizationId;\n\t\t\tconst limit =\n\t\t\t\tctx.query?.limit != null ? Number(ctx.query.limit) : undefined;\n\t\t\tconst offset =\n\t\t\t\tctx.query?.offset != null ? Number(ctx.query.offset) : undefined;\n\n\t\t\t// For organization-owned keys, verify membership and permission\n\t\t\tif (organizationId) {\n\t\t\t\tawait checkOrgApiKeyPermission(\n\t\t\t\t\tctx,\n\t\t\t\t\tsession.user.id,\n\t\t\t\t\torganizationId,\n\t\t\t\t\t\"read\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst referenceId = organizationId ?? session.user.id;\n\t\t\tconst expectedReferencesType = organizationId ? \"organization\" : \"user\";\n\n\t\t\tlet allApiKeys: ApiKey[] = [];\n\n\t\t\tif (configId) {\n\t\t\t\tconst resolvedConfig = resolveConfiguration(\n\t\t\t\t\tctx.context,\n\t\t\t\t\tconfigurations,\n\t\t\t\t\tconfigId,\n\t\t\t\t);\n\n\t\t\t\tconst { apiKeys } = await listApiKeysFromStorage(\n\t\t\t\t\tctx,\n\t\t\t\t\treferenceId,\n\t\t\t\t\tresolvedConfig,\n\t\t\t\t\t{\n\t\t\t\t\t\tlimit: undefined,\n\t\t\t\t\t\toffset: undefined,\n\t\t\t\t\t\tsortBy: ctx.query?.sortBy,\n\t\t\t\t\t\tsortDirection: ctx.query?.sortDirection,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tallApiKeys = apiKeys;\n\t\t\t} else {\n\t\t\t\t// When no configId specified, query each unique storage backend\n\t\t\t\t// Group configurations by their effective storage to avoid duplicate queries\n\t\t\t\tconst storageGroups = new Map<string, PredefinedApiKeyOptions>();\n\t\t\t\tfor (const config of configurations) {\n\t\t\t\t\tconst storageKey = getStorageIdentifier(config);\n\t\t\t\t\tif (!storageGroups.has(storageKey)) {\n\t\t\t\t\t\tstorageGroups.set(storageKey, config);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Query each unique storage backend and merge results\n\t\t\t\tconst seenIds = new Set<string>();\n\t\t\t\tfor (const opts of storageGroups.values()) {\n\t\t\t\t\tconst { apiKeys } = await listApiKeysFromStorage(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\treferenceId,\n\t\t\t\t\t\topts,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlimit: undefined,\n\t\t\t\t\t\t\toffset: undefined,\n\t\t\t\t\t\t\tsortBy: ctx.query?.sortBy,\n\t\t\t\t\t\t\tsortDirection: ctx.query?.sortDirection,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t\tfor (const key of apiKeys) {\n\t\t\t\t\t\tif (!seenIds.has(key.id)) {\n\t\t\t\t\t\t\tseenIds.add(key.id);\n\t\t\t\t\t\t\tallApiKeys.push(key);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Filter by ownership type (user or organization) based on config's references setting\n\t\t\tlet filteredApiKeys = allApiKeys.filter((key) => {\n\t\t\t\tconst keyConfig = configurations.find((c) => {\n\t\t\t\t\t// Keys with configId null, undefined, or \"default\" all match the default config\n\t\t\t\t\tif (isDefaultConfigId(key.configId)) {\n\t\t\t\t\t\treturn isDefaultConfigId(c.configId);\n\t\t\t\t\t}\n\t\t\t\t\treturn c.configId === key.configId;\n\t\t\t\t});\n\t\t\t\tconst referencesType = keyConfig?.references ?? \"user\";\n\t\t\t\treturn (\n\t\t\t\t\treferencesType === expectedReferencesType &&\n\t\t\t\t\tkey.referenceId === referenceId\n\t\t\t\t);\n\t\t\t});\n\n\t\t\tif (configId) {\n\t\t\t\tfilteredApiKeys = filteredApiKeys.filter((key) =>\n\t\t\t\t\tconfigIdMatches(key.configId, configId),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst total = filteredApiKeys.length;\n\n\t\t\t// Apply pagination after filtering\n\t\t\tlet paginatedApiKeys = filteredApiKeys;\n\t\t\tif (offset !== undefined) {\n\t\t\t\tpaginatedApiKeys = paginatedApiKeys.slice(offset);\n\t\t\t}\n\t\t\tif (limit !== undefined) {\n\t\t\t\tpaginatedApiKeys = paginatedApiKeys.slice(0, limit);\n\t\t\t}\n\n\t\t\tdeleteAllExpiredApiKeys(ctx.context);\n\n\t\t\t// Build response with parsed metadata (synchronous, no DB calls)\n\t\t\tconst returningApiKeys = paginatedApiKeys.map((apiKey) => {\n\t\t\t\tconst { key: _key, ...rest } = apiKey;\n\t\t\t\treturn {\n\t\t\t\t\t...rest,\n\t\t\t\t\tmetadata: parseDoubleStringifiedMetadata(apiKey.metadata),\n\t\t\t\t\tpermissions: rest.permissions\n\t\t\t\t\t\t? safeJSONParse<{\n\t\t\t\t\t\t\t\t[key: string]: string[];\n\t\t\t\t\t\t\t}>(rest.permissions)\n\t\t\t\t\t\t: null,\n\t\t\t\t};\n\t\t\t});\n\n\t\t\t// Batch migrate legacy metadata - use first config with database storage\n\t\t\tconst dbConfig = configurations.find(\n\t\t\t\t(c) => c.storage === \"database\" || c.fallbackToDatabase,\n\t\t\t);\n\t\t\tif (dbConfig) {\n\t\t\t\tawait ctx.context.runInBackgroundOrAwait(\n\t\t\t\t\tbatchMigrateLegacyMetadata(ctx, paginatedApiKeys, dbConfig),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn ctx.json({\n\t\t\t\tapiKeys: returningApiKeys,\n\t\t\t\ttotal,\n\t\t\t\tlimit,\n\t\t\t\toffset,\n\t\t\t});\n\t\t},\n\t);\n}\n","import type { AuthContext } from \"@better-auth/core\";\nimport { createAuthEndpoint } from \"@better-auth/core/api\";\nimport { APIError } from \"@better-auth/core/error\";\nimport { safeJSONParse } from \"@better-auth/core/utils/json\";\nimport { getSessionFromCtx } from \"better-auth/api\";\nimport * as z from \"zod\";\nimport { API_KEY_TABLE_NAME, API_KEY_ERROR_CODES as ERROR_CODES } from \"..\";\nimport {\n\tgetApiKeyById,\n\tmigrateDoubleStringifiedMetadata,\n\tsetApiKey,\n} from \"../adapter\";\nimport { checkOrgApiKeyPermission } from \"../org-authorization\";\nimport type { apiKeySchema } from \"../schema\";\nimport type { ApiKey } from \"../types\";\nimport { getDate } from \"../utils\";\nimport type { PredefinedApiKeyOptions } from \".\";\nimport { configIdMatches, resolveConfiguration } from \".\";\n\nconst updateApiKeyBodySchema = z.object({\n\tconfigId: z\n\t\t.string()\n\t\t.meta({\n\t\t\tdescription:\n\t\t\t\t\"The configuration ID to use for the API key lookup. If not provided, the default configuration will be used.\",\n\t\t})\n\t\t.optional(),\n\tkeyId: z.string().meta({\n\t\tdescription: \"The id of the Api Key\",\n\t}),\n\tuserId: z.coerce\n\t\t.string()\n\t\t.meta({\n\t\t\tdescription:\n\t\t\t\t'The id of the user which the api key belongs to. server-only. Eg: \"some-user-id\"',\n\t\t})\n\t\t.optional(),\n\tname: z\n\t\t.string()\n\t\t.meta({\n\t\t\tdescription: \"The name of the key\",\n\t\t})\n\t\t.optional(),\n\tenabled: z\n\t\t.boolean()\n\t\t.meta({\n\t\t\tdescription: \"Whether the Api Key is enabled or not\",\n\t\t})\n\t\t.optional(),\n\tremaining: z\n\t\t.number()\n\t\t.meta({\n\t\t\tdescription: \"The number of remaining requests\",\n\t\t})\n\t\t.min(1)\n\t\t.optional(),\n\trefillAmount: z\n\t\t.number()\n\t\t.meta({\n\t\t\tdescription: \"The refill amount\",\n\t\t})\n\t\t.optional(),\n\trefillInterval: z\n\t\t.number()\n\t\t.meta({\n\t\t\tdescription: \"The refill interval\",\n\t\t})\n\t\t.optional(),\n\tmetadata: z.any().optional(),\n\texpiresIn: z\n\t\t.number()\n\t\t.meta({\n\t\t\tdescription: \"Expiration time of the Api Key in seconds\",\n\t\t})\n\t\t.min(1)\n\t\t.optional()\n\t\t.nullable(),\n\trateLimitEnabled: z\n\t\t.boolean()\n\t\t.meta({\n\t\t\tdescription: \"Whether the key has rate limiting enabled.\",\n\t\t})\n\t\t.optional(),\n\trateLimitTimeWindow: z\n\t\t.number()\n\t\t.meta({\n\t\t\tdescription:\n\t\t\t\t\"The duration in milliseconds where each request is counted. server-only. Eg: 1000\",\n\t\t})\n\t\t.optional(),\n\trateLimitMax: z\n\t\t.number()\n\t\t.meta({\n\t\t\tdescription:\n\t\t\t\t\"Maximum amount of requests allowed within a window. Once the `maxRequests` is reached, the request will be rejected until the `timeWindow` has passed, at which point the `timeWindow` will be reset. server-only. Eg: 100\",\n\t\t})\n\t\t.optional(),\n\tpermissions: z\n\t\t.record(z.string(), z.array(z.string()))\n\t\t.meta({\n\t\t\tdescription: \"Update the permissions on the API Key. server-only.\",\n\t\t})\n\t\t.optional()\n\t\t.nullable(),\n});\n\nexport function updateApiKey({\n\tconfigurations,\n\tschema,\n\tdeleteAllExpiredApiKeys,\n}: {\n\tconfigurations: PredefinedApiKeyOptions[];\n\tschema: ReturnType<typeof apiKeySchema>;\n\tdeleteAllExpiredApiKeys(\n\t\tctx: AuthContext,\n\t\tbyPassLastCheckTime?: boolean | undefined,\n\t): void;\n}) {\n\treturn createAuthEndpoint(\n\t\t\"/api-key/update\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: updateApiKeyBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tdescription: \"Update an existing API key by ID\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t\"200\": {\n\t\t\t\t\t\t\tdescription: \"API key updated successfully\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tid: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"ID\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tname: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"The name of the key\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tstart: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Shows the first few characters of the API key, including the prefix. This allows you to show those few characters in the UI to make it easier for users to identify the API key.\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tprefix: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"The API Key prefix. Stored as plain text.\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tuserId: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"The owner of the user id\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trefillInterval: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"The interval in milliseconds between refills of the `remaining` count. Example: 3600000 // refill every hour (3600000ms = 1h)\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trefillAmount: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"The amount to refill\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tlastRefillAt: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"The last refill date\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tenabled: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Sets if key is enabled or disabled\",\n\t\t\t\t\t\t\t\t\t\t\t\tdefault: true,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trateLimitEnabled: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Whether the key has rate limiting enabled\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trateLimitTimeWindow: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"The duration in milliseconds\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trateLimitMax: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Maximum amount of requests allowed within a window\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trequestCount: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"The number of requests made within the rate limit time window\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tremaining: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Remaining requests (every time api key is used this should updated and should be updated on refill as well)\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tlastRequest: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"When last request occurred\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\texpiresAt: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Expiry date of a key\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tcreatedAt: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"created at\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tupdatedAt: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tformat: \"date-time\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"updated at\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tmetadata: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription: \"Extra metadata about the apiKey\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tpermissions: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Permissions for the api key (stored as JSON string)\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\trequired: [\n\t\t\t\t\t\t\t\t\t\t\t\"id\",\n\t\t\t\t\t\t\t\t\t\t\t\"userId\",\n\t\t\t\t\t\t\t\t\t\t\t\"enabled\",\n\t\t\t\t\t\t\t\t\t\t\t\"rateLimitEnabled\",\n\t\t\t\t\t\t\t\t\t\t\t\"requestCount\",\n\t\t\t\t\t\t\t\t\t\t\t\"createdAt\",\n\t\t\t\t\t\t\t\t\t\t\t\"updatedAt\",\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst {\n\t\t\t\tconfigId,\n\t\t\t\tkeyId,\n\t\t\t\texpiresIn,\n\t\t\t\tenabled,\n\t\t\t\tmetadata,\n\t\t\t\trefillAmount,\n\t\t\t\trefillInterval,\n\t\t\t\tremaining,\n\t\t\t\tname,\n\t\t\t\tpermissions,\n\t\t\t\trateLimitEnabled,\n\t\t\t\trateLimitTimeWindow,\n\t\t\t\trateLimitMax,\n\t\t\t} = ctx.body;\n\n\t\t\tconst session = await getSessionFromCtx(ctx);\n\t\t\tconst authRequired = ctx.request || ctx.headers;\n\t\t\tconst user =\n\t\t\t\tauthRequired && !session\n\t\t\t\t\t? null\n\t\t\t\t\t: session?.user || { id: ctx.body.userId };\n\n\t\t\tif (!user?.id) {\n\t\t\t\tthrow APIError.from(\"UNAUTHORIZED\", ERROR_CODES.UNAUTHORIZED_SESSION);\n\t\t\t}\n\n\t\t\tif (session && ctx.body.userId && session?.user.id !== ctx.body.userId) {\n\t\t\t\tthrow APIError.from(\"UNAUTHORIZED\", ERROR_CODES.UNAUTHORIZED_SESSION);\n\t\t\t}\n\n\t\t\tif (authRequired) {\n\t\t\t\t// if this endpoint was being called from the client,\n\t\t\t\t// we must make sure they can't use server-only properties.\n\t\t\t\tif (\n\t\t\t\t\trefillAmount !== undefined ||\n\t\t\t\t\trefillInterval !== undefined ||\n\t\t\t\t\trateLimitMax !== undefined ||\n\t\t\t\t\trateLimitTimeWindow !== undefined ||\n\t\t\t\t\trateLimitEnabled !== undefined ||\n\t\t\t\t\tremaining !== undefined ||\n\t\t\t\t\tpermissions !== undefined\n\t\t\t\t) {\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.SERVER_ONLY_PROPERTY);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Use provided configId or fall back to default config for initial lookup\n\t\t\tconst lookupOpts = resolveConfiguration(\n\t\t\t\tctx.context,\n\t\t\t\tconfigurations,\n\t\t\t\tconfigId,\n\t\t\t);\n\t\t\tlet apiKey: ApiKey | null = null;\n\n\t\t\tapiKey = await getApiKeyById(ctx, keyId, lookupOpts);\n\n\t\t\tif (!apiKey) {\n\t\t\t\tthrow APIError.from(\"NOT_FOUND\", ERROR_CODES.KEY_NOT_FOUND);\n\t\t\t}\n\n\t\t\tif (!configIdMatches(apiKey.configId, lookupOpts.configId)) {\n\t\t\t\tthrow APIError.from(\"NOT_FOUND\", ERROR_CODES.KEY_NOT_FOUND);\n\t\t\t}\n\n\t\t\t// Resolve the correct config based on the API key's configId\n\t\t\tconst opts = resolveConfiguration(\n\t\t\t\tctx.context,\n\t\t\t\tconfigurations,\n\t\t\t\tapiKey.configId,\n\t\t\t);\n\n\t\t\t// Verify ownership based on config's references type\n\t\t\tconst referencesType = opts.references ?? \"user\";\n\t\t\tif (referencesType === \"organization\") {\n\t\t\t\t// For organization-owned keys, verify membership and permission\n\t\t\t\tawait checkOrgApiKeyPermission(\n\t\t\t\t\tctx,\n\t\t\t\t\tuser.id,\n\t\t\t\t\tapiKey.referenceId,\n\t\t\t\t\t\"update\",\n\t\t\t\t);\n\t\t\t} else if (apiKey.referenceId !== user.id) {\n\t\t\t\tthrow APIError.from(\"NOT_FOUND\", ERROR_CODES.KEY_NOT_FOUND);\n\t\t\t}\n\n\t\t\tconst newValues: Partial<ApiKey> = {};\n\n\t\t\tif (name !== undefined) {\n\t\t\t\tif (name.length < opts.minimumNameLength) {\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_NAME_LENGTH);\n\t\t\t\t} else if (name.length > opts.maximumNameLength) {\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_NAME_LENGTH);\n\t\t\t\t}\n\t\t\t\tnewValues.name = name;\n\t\t\t}\n\n\t\t\tif (enabled !== undefined) {\n\t\t\t\tnewValues.enabled = enabled;\n\t\t\t}\n\t\t\tif (expiresIn !== undefined) {\n\t\t\t\tif (opts.keyExpiration.disableCustomExpiresTime === true) {\n\t\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\t\"BAD_REQUEST\",\n\t\t\t\t\t\tERROR_CODES.KEY_DISABLED_EXPIRATION,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (expiresIn !== null) {\n\t\t\t\t\t// if expires is not null, check if it's under the valid range\n\t\t\t\t\t// if it IS null, this means the user wants to disable expiration time on the key\n\t\t\t\t\tconst expiresIn_in_days = expiresIn / (60 * 60 * 24);\n\n\t\t\t\t\tif (expiresIn_in_days < opts.keyExpiration.minExpiresIn) {\n\t\t\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\t\t\"BAD_REQUEST\",\n\t\t\t\t\t\t\tERROR_CODES.EXPIRES_IN_IS_TOO_SMALL,\n\t\t\t\t\t\t);\n\t\t\t\t\t} else if (expiresIn_in_days > opts.keyExpiration.maxExpiresIn) {\n\t\t\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\t\t\"BAD_REQUEST\",\n\t\t\t\t\t\t\tERROR_CODES.EXPIRES_IN_IS_TOO_LARGE,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tnewValues.expiresAt = expiresIn ? getDate(expiresIn, \"sec\") : null;\n\t\t\t}\n\n\t\t\tif (metadata !== undefined && opts.enableMetadata === true) {\n\t\t\t\tif (typeof metadata !== \"object\") {\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_METADATA_TYPE);\n\t\t\t\t}\n\t\t\t\t// The adapter will automatically apply the schema transform to stringify\n\t\t\t\tnewValues.metadata = metadata;\n\t\t\t}\n\t\t\tif (remaining !== undefined) {\n\t\t\t\tnewValues.remaining = remaining;\n\t\t\t}\n\t\t\tif (refillAmount !== undefined || refillInterval !== undefined) {\n\t\t\t\tif (refillAmount !== undefined && refillInterval === undefined) {\n\t\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\t\"BAD_REQUEST\",\n\t\t\t\t\t\tERROR_CODES.REFILL_AMOUNT_AND_INTERVAL_REQUIRED,\n\t\t\t\t\t);\n\t\t\t\t} else if (refillInterval !== undefined && refillAmount === undefined) {\n\t\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\t\"BAD_REQUEST\",\n\t\t\t\t\t\tERROR_CODES.REFILL_INTERVAL_AND_AMOUNT_REQUIRED,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tnewValues.refillAmount = refillAmount;\n\t\t\t\tnewValues.refillInterval = refillInterval;\n\t\t\t}\n\n\t\t\tif (rateLimitEnabled !== undefined) {\n\t\t\t\tnewValues.rateLimitEnabled = rateLimitEnabled;\n\t\t\t}\n\t\t\tif (rateLimitTimeWindow !== undefined) {\n\t\t\t\tnewValues.rateLimitTimeWindow = rateLimitTimeWindow;\n\t\t\t}\n\t\t\tif (rateLimitMax !== undefined) {\n\t\t\t\tnewValues.rateLimitMax = rateLimitMax;\n\t\t\t}\n\n\t\t\tif (permissions !== undefined) {\n\t\t\t\t//@ts-expect-error - we need this to be a string to save into DB.\n\t\t\t\tnewValues.permissions = JSON.stringify(permissions);\n\t\t\t}\n\n\t\t\tif (Object.keys(newValues).length === 0) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.NO_VALUES_TO_UPDATE);\n\t\t\t}\n\n\t\t\tlet newApiKey: ApiKey = apiKey;\n\t\t\ttry {\n\t\t\t\tif (opts.storage === \"secondary-storage\" && opts.fallbackToDatabase) {\n\t\t\t\t\tconst dbUpdated = await ctx.context.adapter.update<ApiKey>({\n\t\t\t\t\t\tmodel: API_KEY_TABLE_NAME,\n\t\t\t\t\t\twhere: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tfield: \"id\",\n\t\t\t\t\t\t\t\tvalue: apiKey.id,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tupdate: newValues,\n\t\t\t\t\t});\n\t\t\t\t\tif (dbUpdated) {\n\t\t\t\t\t\tawait setApiKey(ctx, dbUpdated, opts);\n\t\t\t\t\t\tnewApiKey = dbUpdated;\n\t\t\t\t\t}\n\t\t\t\t} else if (opts.storage === \"database\") {\n\t\t\t\t\tconst result = await ctx.context.adapter.update<ApiKey>({\n\t\t\t\t\t\tmodel: API_KEY_TABLE_NAME,\n\t\t\t\t\t\twhere: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tfield: \"id\",\n\t\t\t\t\t\t\t\tvalue: apiKey.id,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tupdate: newValues,\n\t\t\t\t\t});\n\t\t\t\t\tif (result) newApiKey = result;\n\t\t\t\t} else {\n\t\t\t\t\tconst updated: ApiKey = {\n\t\t\t\t\t\t...apiKey,\n\t\t\t\t\t\t...newValues,\n\t\t\t\t\t\tupdatedAt: new Date(),\n\t\t\t\t\t};\n\t\t\t\t\tawait setApiKey(ctx, updated, opts);\n\t\t\t\t\tnewApiKey = updated;\n\t\t\t\t}\n\t\t\t} catch (error: any) {\n\t\t\t\tthrow APIError.fromStatus(\"INTERNAL_SERVER_ERROR\", {\n\t\t\t\t\tmessage: error?.message,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tdeleteAllExpiredApiKeys(ctx.context);\n\n\t\t\t// Migrate legacy double-stringified metadata if needed\n\t\t\tconst migratedMetadata = await migrateDoubleStringifiedMetadata(\n\t\t\t\tctx,\n\t\t\t\tnewApiKey,\n\t\t\t\topts,\n\t\t\t);\n\n\t\t\tconst { key: _key, ...returningApiKey } = newApiKey;\n\n\t\t\treturn ctx.json({\n\t\t\t\t...returningApiKey,\n\t\t\t\tmetadata: migratedMetadata,\n\t\t\t\tpermissions: returningApiKey.permissions\n\t\t\t\t\t? safeJSONParse<{\n\t\t\t\t\t\t\t[key: string]: string[];\n\t\t\t\t\t\t}>(returningApiKey.permissions)\n\t\t\t\t\t: null,\n\t\t\t});\n\t\t},\n\t);\n}\n","import { API_KEY_ERROR_CODES as ERROR_CODES } from \".\";\nimport type { PredefinedApiKeyOptions } from \"./routes\";\nimport type { ApiKey } from \"./types\";\n\ninterface RateLimitResult {\n\tsuccess: boolean;\n\tmessage: string | null;\n\ttryAgainIn: number | null;\n\tupdate: Partial<ApiKey> | null;\n}\n\n/**\n * Determines if a request is allowed based on rate limiting parameters.\n *\n * @returns An object indicating whether the request is allowed and, if not,\n *          a message and updated ApiKey data.\n */\nexport function isRateLimited(\n\t/**\n\t * The ApiKey object containing rate limiting information\n\t */\n\tapiKey: ApiKey,\n\topts: PredefinedApiKeyOptions,\n): RateLimitResult {\n\tconst now = new Date();\n\tconst lastRequest = apiKey.lastRequest;\n\tconst rateLimitTimeWindow = apiKey.rateLimitTimeWindow;\n\tconst rateLimitMax = apiKey.rateLimitMax;\n\tlet requestCount = apiKey.requestCount;\n\n\tif (opts.rateLimit.enabled === false)\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tmessage: null,\n\t\t\tupdate: { lastRequest: now },\n\t\t\ttryAgainIn: null,\n\t\t};\n\n\tif (apiKey.rateLimitEnabled === false)\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tmessage: null,\n\t\t\tupdate: { lastRequest: now },\n\t\t\ttryAgainIn: null,\n\t\t};\n\n\tif (rateLimitTimeWindow === null || rateLimitMax === null) {\n\t\t// Rate limiting is disabled.\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tmessage: null,\n\t\t\tupdate: null,\n\t\t\ttryAgainIn: null,\n\t\t};\n\t}\n\n\tif (lastRequest === null) {\n\t\t// No previous requests, so allow the first one.\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tmessage: null,\n\t\t\tupdate: { lastRequest: now, requestCount: 1 },\n\t\t\ttryAgainIn: null,\n\t\t};\n\t}\n\n\tconst timeSinceLastRequest = now.getTime() - new Date(lastRequest).getTime();\n\n\tif (timeSinceLastRequest > rateLimitTimeWindow) {\n\t\t// Time window has passed, reset the request count.\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tmessage: null,\n\t\t\tupdate: { lastRequest: now, requestCount: 1 },\n\t\t\ttryAgainIn: null,\n\t\t};\n\t}\n\n\tif (requestCount >= rateLimitMax) {\n\t\t// Rate limit exceeded.\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\tmessage: ERROR_CODES.RATE_LIMIT_EXCEEDED.message,\n\t\t\tupdate: null,\n\t\t\ttryAgainIn: Math.ceil(rateLimitTimeWindow - timeSinceLastRequest),\n\t\t};\n\t}\n\n\t// Request is allowed.\n\trequestCount++;\n\treturn {\n\t\tsuccess: true,\n\t\tmessage: null,\n\t\ttryAgainIn: null,\n\t\tupdate: { lastRequest: now, requestCount: requestCount },\n\t};\n}\n","import type { AuthContext, GenericEndpointContext } from \"@better-auth/core\";\nimport { createAuthEndpoint } from \"@better-auth/core/api\";\nimport { APIError } from \"@better-auth/core/error\";\nimport { safeJSONParse } from \"@better-auth/core/utils/json\";\nimport { role } from \"better-auth/plugins/access\";\nimport * as z from \"zod\";\nimport { API_KEY_TABLE_NAME, API_KEY_ERROR_CODES as ERROR_CODES } from \"..\";\nimport { defaultKeyHasher } from \"../\";\nimport {\n\tdeleteApiKey,\n\tgetApiKey,\n\tmigrateDoubleStringifiedMetadata,\n\tsetApiKey,\n} from \"../adapter\";\nimport { isRateLimited } from \"../rate-limit\";\nimport type { apiKeySchema } from \"../schema\";\nimport type { ApiKey } from \"../types\";\nimport { isAPIError } from \"../utils\";\nimport type { PredefinedApiKeyOptions } from \".\";\nimport { resolveConfiguration } from \".\";\n\nexport async function validateApiKey({\n\thashedKey,\n\tctx,\n\topts,\n\tschema,\n\tpermissions,\n}: {\n\thashedKey: string;\n\topts: PredefinedApiKeyOptions;\n\tschema: ReturnType<typeof apiKeySchema>;\n\tpermissions?: Record<string, string[]> | undefined;\n\tctx: GenericEndpointContext;\n}) {\n\tconst apiKey = await getApiKey(ctx, hashedKey, opts);\n\n\tif (!apiKey) {\n\t\tthrow APIError.from(\"UNAUTHORIZED\", ERROR_CODES.INVALID_API_KEY);\n\t}\n\n\tif (apiKey.enabled === false) {\n\t\tthrow APIError.from(\"UNAUTHORIZED\", ERROR_CODES.KEY_DISABLED);\n\t}\n\n\tif (apiKey.expiresAt) {\n\t\tconst now = Date.now();\n\t\tconst expiresAt = new Date(apiKey.expiresAt).getTime();\n\t\tif (now > expiresAt) {\n\t\t\tconst deleteExpiredKey = async () => {\n\t\t\t\tif (opts.storage === \"secondary-storage\" && opts.fallbackToDatabase) {\n\t\t\t\t\tawait deleteApiKey(ctx, apiKey, opts);\n\t\t\t\t\tawait ctx.context.adapter.delete({\n\t\t\t\t\t\tmodel: API_KEY_TABLE_NAME,\n\t\t\t\t\t\twhere: [{ field: \"id\", value: apiKey.id }],\n\t\t\t\t\t});\n\t\t\t\t} else if (opts.storage === \"secondary-storage\") {\n\t\t\t\t\tawait deleteApiKey(ctx, apiKey, opts);\n\t\t\t\t} else {\n\t\t\t\t\tawait ctx.context.adapter.delete({\n\t\t\t\t\t\tmodel: API_KEY_TABLE_NAME,\n\t\t\t\t\t\twhere: [{ field: \"id\", value: apiKey.id }],\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tif (opts.deferUpdates) {\n\t\t\t\tctx.context.runInBackground(\n\t\t\t\t\tdeleteExpiredKey().catch((error) => {\n\t\t\t\t\t\tctx.context.logger.error(\"Deferred update failed:\", error);\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tawait deleteExpiredKey();\n\t\t\t}\n\n\t\t\tthrow APIError.from(\"UNAUTHORIZED\", ERROR_CODES.KEY_EXPIRED);\n\t\t}\n\t}\n\n\tif (permissions) {\n\t\tconst apiKeyPermissions = apiKey.permissions\n\t\t\t? safeJSONParse<{\n\t\t\t\t\t[key: string]: string[];\n\t\t\t\t}>(apiKey.permissions)\n\t\t\t: null;\n\n\t\tif (!apiKeyPermissions) {\n\t\t\tthrow APIError.from(\"UNAUTHORIZED\", ERROR_CODES.KEY_NOT_FOUND);\n\t\t}\n\t\tconst r = role(apiKeyPermissions as any);\n\t\tconst result = r.authorize(permissions);\n\t\tif (!result.success) {\n\t\t\tthrow APIError.from(\"UNAUTHORIZED\", ERROR_CODES.KEY_NOT_FOUND);\n\t\t}\n\t}\n\n\tlet remaining = apiKey.remaining;\n\tlet lastRefillAt = apiKey.lastRefillAt;\n\n\tif (apiKey.remaining === 0 && apiKey.refillAmount === null) {\n\t\tconst deleteExhaustedKey = async () => {\n\t\t\tif (opts.storage === \"secondary-storage\" && opts.fallbackToDatabase) {\n\t\t\t\tawait deleteApiKey(ctx, apiKey, opts);\n\t\t\t\tawait ctx.context.adapter.delete({\n\t\t\t\t\tmodel: API_KEY_TABLE_NAME,\n\t\t\t\t\twhere: [{ field: \"id\", value: apiKey.id }],\n\t\t\t\t});\n\t\t\t} else if (opts.storage === \"secondary-storage\") {\n\t\t\t\tawait deleteApiKey(ctx, apiKey, opts);\n\t\t\t} else {\n\t\t\t\tawait ctx.context.adapter.delete({\n\t\t\t\t\tmodel: API_KEY_TABLE_NAME,\n\t\t\t\t\twhere: [{ field: \"id\", value: apiKey.id }],\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\n\t\tif (opts.deferUpdates) {\n\t\t\tctx.context.runInBackground(\n\t\t\t\tdeleteExhaustedKey().catch((error) => {\n\t\t\t\t\tctx.context.logger.error(\"Deferred update failed:\", error);\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tawait deleteExhaustedKey();\n\t\t}\n\n\t\tthrow APIError.from(\"TOO_MANY_REQUESTS\", ERROR_CODES.USAGE_EXCEEDED);\n\t} else if (remaining !== null) {\n\t\tconst now = Date.now();\n\t\tconst refillInterval = apiKey.refillInterval;\n\t\tconst refillAmount = apiKey.refillAmount;\n\t\tconst lastTime = new Date(lastRefillAt ?? apiKey.createdAt).getTime();\n\n\t\tif (refillInterval && refillAmount) {\n\t\t\t// if they provide refill info, then we should refill once the interval is reached.\n\n\t\t\tconst timeSinceLastRequest = now - lastTime;\n\t\t\tif (timeSinceLastRequest > refillInterval) {\n\t\t\t\tremaining = refillAmount;\n\t\t\t\tlastRefillAt = new Date();\n\t\t\t}\n\t\t}\n\n\t\tif (remaining === 0) {\n\t\t\t// if there are no more remaining requests, than the key is invalid\n\t\t\tthrow APIError.from(\"TOO_MANY_REQUESTS\", ERROR_CODES.USAGE_EXCEEDED);\n\t\t} else {\n\t\t\tremaining--;\n\t\t}\n\t}\n\n\tconst { message, success, update, tryAgainIn } = isRateLimited(apiKey, opts);\n\n\tif (success === false) {\n\t\tthrow new APIError(\"UNAUTHORIZED\", {\n\t\t\tmessage: message ?? undefined,\n\t\t\tcode: \"RATE_LIMITED\" as const,\n\t\t\tdetails: {\n\t\t\t\ttryAgainIn,\n\t\t\t},\n\t\t});\n\t}\n\n\tconst updated: ApiKey = {\n\t\t...apiKey,\n\t\t...update,\n\t\tremaining,\n\t\tlastRefillAt,\n\t\tupdatedAt: new Date(),\n\t};\n\n\tconst performUpdate = async (): Promise<ApiKey | null> => {\n\t\tif (opts.storage === \"database\") {\n\t\t\treturn ctx.context.adapter.update<ApiKey>({\n\t\t\t\tmodel: API_KEY_TABLE_NAME,\n\t\t\t\twhere: [{ field: \"id\", value: apiKey.id }],\n\t\t\t\tupdate: { ...updated, id: undefined },\n\t\t\t});\n\t\t} else if (\n\t\t\topts.storage === \"secondary-storage\" &&\n\t\t\topts.fallbackToDatabase\n\t\t) {\n\t\t\tconst dbUpdated = await ctx.context.adapter.update<ApiKey>({\n\t\t\t\tmodel: API_KEY_TABLE_NAME,\n\t\t\t\twhere: [{ field: \"id\", value: apiKey.id }],\n\t\t\t\tupdate: { ...updated, id: undefined },\n\t\t\t});\n\t\t\tif (dbUpdated) {\n\t\t\t\tawait setApiKey(ctx, dbUpdated, opts);\n\t\t\t}\n\t\t\treturn dbUpdated;\n\t\t} else {\n\t\t\tawait setApiKey(ctx, updated, opts);\n\t\t\treturn updated;\n\t\t}\n\t};\n\n\tlet newApiKey: ApiKey | null = null;\n\n\tif (opts.deferUpdates) {\n\t\tctx.context.runInBackground(\n\t\t\tperformUpdate().catch((error) => {\n\t\t\t\tctx.context.logger.error(\"Failed to update API key:\", error);\n\t\t\t}),\n\t\t);\n\t\tnewApiKey = updated;\n\t} else {\n\t\tnewApiKey = await performUpdate();\n\t\tif (!newApiKey) {\n\t\t\tthrow APIError.from(\n\t\t\t\t\"INTERNAL_SERVER_ERROR\",\n\t\t\t\tERROR_CODES.FAILED_TO_UPDATE_API_KEY,\n\t\t\t);\n\t\t}\n\t}\n\n\treturn newApiKey;\n}\n\nconst verifyApiKeyBodySchema = z.object({\n\tconfigId: z\n\t\t.string()\n\t\t.meta({\n\t\t\tdescription:\n\t\t\t\t\"The configuration ID to use for verification. If not provided, the default configuration will be used.\",\n\t\t})\n\t\t.optional(),\n\tkey: z.string().meta({\n\t\tdescription: \"The key to verify\",\n\t}),\n\tpermissions: z\n\t\t.record(z.string(), z.array(z.string()))\n\t\t.meta({\n\t\t\tdescription: \"The permissions to verify.\",\n\t\t})\n\t\t.optional(),\n});\n\nexport function verifyApiKey({\n\tconfigurations,\n\tschema,\n\tdeleteAllExpiredApiKeys,\n}: {\n\tconfigurations: PredefinedApiKeyOptions[];\n\tschema: ReturnType<typeof apiKeySchema>;\n\tdeleteAllExpiredApiKeys(\n\t\tctx: AuthContext,\n\t\tbyPassLastCheckTime?: boolean | undefined,\n\t): Promise<void>;\n}) {\n\treturn createAuthEndpoint(\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: verifyApiKeyBodySchema,\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst { configId, key } = ctx.body;\n\n\t\t\t// Use provided configId or fall back to default config\n\t\t\tconst lookupOpts = resolveConfiguration(\n\t\t\t\tctx.context,\n\t\t\t\tconfigurations,\n\t\t\t\tconfigId,\n\t\t\t);\n\n\t\t\tif (lookupOpts.customAPIKeyValidator) {\n\t\t\t\tconst isValid = await lookupOpts.customAPIKeyValidator({ ctx, key });\n\t\t\t\tif (!isValid) {\n\t\t\t\t\treturn ctx.json({\n\t\t\t\t\t\tvalid: false,\n\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\tmessage: ERROR_CODES.INVALID_API_KEY,\n\t\t\t\t\t\t\tcode: \"KEY_NOT_FOUND\" as const,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tkey: null,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst hashed = lookupOpts.disableKeyHashing\n\t\t\t\t? key\n\t\t\t\t: await defaultKeyHasher(key);\n\n\t\t\tlet apiKey: ApiKey | null = null;\n\n\t\t\ttry {\n\t\t\t\tapiKey = await validateApiKey({\n\t\t\t\t\thashedKey: hashed,\n\t\t\t\t\tpermissions: ctx.body.permissions,\n\t\t\t\t\tctx,\n\t\t\t\t\topts: lookupOpts,\n\t\t\t\t\tschema,\n\t\t\t\t});\n\n\t\t\t\t// Resolve the correct config based on the API key's configId\n\t\t\t\tconst opts = apiKey\n\t\t\t\t\t? resolveConfiguration(ctx.context, configurations, apiKey.configId)\n\t\t\t\t\t: lookupOpts;\n\n\t\t\t\tif (opts.deferUpdates) {\n\t\t\t\t\tctx.context.runInBackground(\n\t\t\t\t\t\tdeleteAllExpiredApiKeys(ctx.context).catch((err) => {\n\t\t\t\t\t\t\tctx.context.logger.error(\n\t\t\t\t\t\t\t\t\"Failed to delete expired API keys:\",\n\t\t\t\t\t\t\t\terr,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tctx.context.logger.error(\"Failed to validate API key:\", error);\n\t\t\t\tif (isAPIError(error)) {\n\t\t\t\t\treturn ctx.json({\n\t\t\t\t\t\tvalid: false,\n\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\t...error.body,\n\t\t\t\t\t\t\tmessage: error.body?.message,\n\t\t\t\t\t\t\tcode: error.body?.code as string,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tkey: null,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\treturn ctx.json({\n\t\t\t\t\tvalid: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tmessage: ERROR_CODES.INVALID_API_KEY,\n\t\t\t\t\t\tcode: \"INVALID_API_KEY\" as const,\n\t\t\t\t\t},\n\t\t\t\t\tkey: null,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst { key: _, ...returningApiKey } = apiKey ?? {\n\t\t\t\tkey: 1,\n\t\t\t\tpermissions: undefined,\n\t\t\t};\n\n\t\t\t// Resolve the correct config for metadata migration\n\t\t\tconst opts = apiKey\n\t\t\t\t? resolveConfiguration(ctx.context, configurations, apiKey.configId)\n\t\t\t\t: lookupOpts;\n\n\t\t\t// Migrate legacy double-stringified metadata if needed\n\t\t\tlet migratedMetadata: Record<string, any> | null = null;\n\t\t\tif (apiKey) {\n\t\t\t\tmigratedMetadata = await migrateDoubleStringifiedMetadata(\n\t\t\t\t\tctx,\n\t\t\t\t\tapiKey,\n\t\t\t\t\topts,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturningApiKey.permissions = returningApiKey.permissions\n\t\t\t\t? safeJSONParse<{\n\t\t\t\t\t\t[key: string]: string[];\n\t\t\t\t\t}>(returningApiKey.permissions)\n\t\t\t\t: null;\n\n\t\t\treturn ctx.json({\n\t\t\t\tvalid: true,\n\t\t\t\terror: null,\n\t\t\t\tkey:\n\t\t\t\t\tapiKey === null\n\t\t\t\t\t\t? null\n\t\t\t\t\t\t: ({\n\t\t\t\t\t\t\t\t...returningApiKey,\n\t\t\t\t\t\t\t\tmetadata: migratedMetadata,\n\t\t\t\t\t\t\t} as Omit<ApiKey, \"key\">),\n\t\t\t});\n\t\t},\n\t);\n}\n","import type { AuthContext, Awaitable } from \"@better-auth/core\";\nimport { APIError } from \"better-auth/api\";\nimport { API_KEY_ERROR_CODES, API_KEY_TABLE_NAME } from \"..\";\nimport type { apiKeySchema } from \"../schema\";\nimport type { ApiKey, ApiKeyConfigurationOptions } from \"../types\";\nimport { createApiKey } from \"./create-api-key\";\nimport { deleteAllExpiredApiKeysEndpoint } from \"./delete-all-expired-api-keys\";\nimport { deleteApiKey } from \"./delete-api-key\";\nimport { getApiKey } from \"./get-api-key\";\nimport { listApiKeys } from \"./list-api-keys\";\nimport { updateApiKey } from \"./update-api-key\";\nimport { verifyApiKey } from \"./verify-api-key\";\n\nexport type PredefinedApiKeyOptions = ApiKeyConfigurationOptions &\n\tRequired<\n\t\tPick<\n\t\t\tApiKeyConfigurationOptions,\n\t\t\t| \"apiKeyHeaders\"\n\t\t\t| \"defaultKeyLength\"\n\t\t\t| \"keyExpiration\"\n\t\t\t| \"rateLimit\"\n\t\t\t| \"maximumPrefixLength\"\n\t\t\t| \"minimumPrefixLength\"\n\t\t\t| \"maximumNameLength\"\n\t\t\t| \"disableKeyHashing\"\n\t\t\t| \"minimumNameLength\"\n\t\t\t| \"requireName\"\n\t\t\t| \"enableMetadata\"\n\t\t\t| \"enableSessionForAPIKeys\"\n\t\t\t| \"startingCharactersConfig\"\n\t\t\t| \"storage\"\n\t\t\t| \"fallbackToDatabase\"\n\t\t\t| \"deferUpdates\"\n\t\t>\n\t> & {\n\t\tkeyExpiration: Required<\n\t\t\tNonNullable<ApiKeyConfigurationOptions[\"keyExpiration\"]>\n\t\t>;\n\t\tstartingCharactersConfig: Required<\n\t\t\tNonNullable<ApiKeyConfigurationOptions[\"startingCharactersConfig\"]>\n\t\t>;\n\t\trateLimit: Required<NonNullable<ApiKeyConfigurationOptions[\"rateLimit\"]>>;\n\t};\n\nexport function resolveConfiguration(\n\tauthContext: AuthContext,\n\tconfigurations: PredefinedApiKeyOptions[],\n\tconfigId?: string | null,\n): PredefinedApiKeyOptions {\n\t// Defined in a function to avoid running the code when not needed.\n\t// If ran unnecessarily, it could throw an error saying \"No default api-key configuration found.\" when the configId is provided.\n\tconst getDefaultConfig = () => {\n\t\tconst defaultConfig = configurations.find(\n\t\t\t(c) => !c.configId || c.configId === \"default\",\n\t\t);\n\t\tif (!defaultConfig) {\n\t\t\tconst message =\n\t\t\t\t\"No default api-key configuration found. Either provide an api-key configuration with configId 'default' or provide a configuration with no `configId` set.\";\n\t\t\tauthContext.logger.error(message);\n\t\t\tconst error = API_KEY_ERROR_CODES.NO_DEFAULT_API_KEY_CONFIGURATION_FOUND;\n\t\t\tthrow APIError.from(\"BAD_REQUEST\", error);\n\t\t}\n\t\treturn { ...defaultConfig, configId: \"default\" };\n\t};\n\tif (!configId) return getDefaultConfig();\n\treturn (\n\t\tconfigurations.find((c) => c.configId === configId) ?? getDefaultConfig()\n\t);\n}\n\n/**\n * Checks if a configId value represents the default configuration.\n * Treats null, undefined, and \"default\" as equivalent (all are default).\n * This handles backward compatibility for keys created before the configId field existed.\n */\nexport function isDefaultConfigId(\n\tconfigId: string | null | undefined,\n): boolean {\n\treturn !configId || configId === \"default\";\n}\n\n/**\n * Checks if two configId values match, treating null/undefined as \"default\".\n * This handles backward compatibility for keys created before the configId field existed.\n */\nexport function configIdMatches(\n\tkeyConfigId: string | null | undefined,\n\texpectedConfigId: string | null | undefined,\n): boolean {\n\t// Both are default (null, undefined, or \"default\")\n\tif (isDefaultConfigId(keyConfigId) && isDefaultConfigId(expectedConfigId)) {\n\t\treturn true;\n\t}\n\t// Direct match\n\treturn keyConfigId === expectedConfigId;\n}\n\nlet lastChecked: Date | null = null;\n\nexport async function deleteAllExpiredApiKeys(\n\tctx: AuthContext,\n\tbyPassLastCheckTime = false,\n): Promise<void> {\n\tif (lastChecked && !byPassLastCheckTime) {\n\t\tconst now = new Date();\n\t\tconst diff = now.getTime() - lastChecked.getTime();\n\t\tif (diff < 10000) {\n\t\t\treturn;\n\t\t}\n\t}\n\tlastChecked = new Date();\n\tawait ctx.adapter\n\t\t.deleteMany({\n\t\t\tmodel: API_KEY_TABLE_NAME,\n\t\t\twhere: [\n\t\t\t\t{\n\t\t\t\t\tfield: \"expiresAt\" satisfies keyof ApiKey,\n\t\t\t\t\toperator: \"lt\",\n\t\t\t\t\tvalue: new Date(),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tfield: \"expiresAt\",\n\t\t\t\t\toperator: \"ne\",\n\t\t\t\t\tvalue: null,\n\t\t\t\t},\n\t\t\t],\n\t\t})\n\t\t.catch((error) => {\n\t\t\tctx.logger.error(`Failed to delete expired API keys:`, error);\n\t\t});\n}\n\nexport function createApiKeyRoutes({\n\tdefaultKeyGenerator,\n\tconfigurations,\n\tschema,\n}: {\n\tdefaultKeyGenerator: (options: {\n\t\tlength: number;\n\t\tprefix: string | undefined;\n\t}) => Awaitable<string>;\n\tconfigurations: PredefinedApiKeyOptions[];\n\tschema: ReturnType<typeof apiKeySchema>;\n}) {\n\treturn {\n\t\tcreateApiKey: createApiKey({\n\t\t\tdefaultKeyGenerator,\n\t\t\tconfigurations,\n\t\t\tschema,\n\t\t\tdeleteAllExpiredApiKeys,\n\t\t}),\n\t\tverifyApiKey: verifyApiKey({\n\t\t\tconfigurations,\n\t\t\tschema,\n\t\t\tdeleteAllExpiredApiKeys,\n\t\t}),\n\t\tgetApiKey: getApiKey({ configurations, schema, deleteAllExpiredApiKeys }),\n\t\tupdateApiKey: updateApiKey({\n\t\t\tconfigurations,\n\t\t\tschema,\n\t\t\tdeleteAllExpiredApiKeys,\n\t\t}),\n\t\tdeleteApiKey: deleteApiKey({\n\t\t\tconfigurations,\n\t\t\tschema,\n\t\t\tdeleteAllExpiredApiKeys,\n\t\t}),\n\t\tlistApiKeys: listApiKeys({\n\t\t\tconfigurations,\n\t\t\tschema,\n\t\t\tdeleteAllExpiredApiKeys,\n\t\t}),\n\t\tdeleteAllExpiredApiKeys: deleteAllExpiredApiKeysEndpoint({\n\t\t\tdeleteAllExpiredApiKeys,\n\t\t}),\n\t};\n}\n","import type { BetterAuthPluginDBSchema } from \"@better-auth/core/db\";\nimport { parseJSON } from \"better-auth/client\";\n\nexport const apiKeySchema = ({\n\tdefaultRateLimitMax,\n\tdefaultTimeWindow,\n}: {\n\tdefaultTimeWindow: number;\n\tdefaultRateLimitMax: number;\n}) =>\n\t({\n\t\tapikey: {\n\t\t\tfields: {\n\t\t\t\tconfigId: {\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tdefaultValue: \"default\",\n\t\t\t\t\tinput: false,\n\t\t\t\t\tindex: true,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * The name of the key.\n\t\t\t\t */\n\t\t\t\tname: {\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: false,\n\t\t\t\t\tinput: false,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * Shows the first few characters of the API key\n\t\t\t\t * This allows you to show those few characters in the UI to make it easier for users to identify the API key.\n\t\t\t\t */\n\t\t\t\tstart: {\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: false,\n\t\t\t\t\tinput: false,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * The ID of the entity that owns this key (userId or organizationId based on config's `references` setting).\n\t\t\t\t */\n\t\t\t\treferenceId: {\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tinput: false,\n\t\t\t\t\tindex: true,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * The prefix of the key.\n\t\t\t\t */\n\t\t\t\tprefix: {\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: false,\n\t\t\t\t\tinput: false,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * The hashed key value.\n\t\t\t\t */\n\t\t\t\tkey: {\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tinput: false,\n\t\t\t\t\tindex: true,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * The interval to refill the key in milliseconds.\n\t\t\t\t */\n\t\t\t\trefillInterval: {\n\t\t\t\t\ttype: \"number\",\n\t\t\t\t\trequired: false,\n\t\t\t\t\tinput: false,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * The amount to refill the remaining count of the key.\n\t\t\t\t */\n\t\t\t\trefillAmount: {\n\t\t\t\t\ttype: \"number\",\n\t\t\t\t\trequired: false,\n\t\t\t\t\tinput: false,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * The date and time when the key was last refilled.\n\t\t\t\t */\n\t\t\t\tlastRefillAt: {\n\t\t\t\t\ttype: \"date\",\n\t\t\t\t\trequired: false,\n\t\t\t\t\tinput: false,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * Whether the key is enabled.\n\t\t\t\t */\n\t\t\t\tenabled: {\n\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\trequired: false,\n\t\t\t\t\tinput: false,\n\t\t\t\t\tdefaultValue: true,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * Whether the key has rate limiting enabled.\n\t\t\t\t */\n\t\t\t\trateLimitEnabled: {\n\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\trequired: false,\n\t\t\t\t\tinput: false,\n\t\t\t\t\tdefaultValue: true,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * The time window in milliseconds for the rate limit.\n\t\t\t\t */\n\t\t\t\trateLimitTimeWindow: {\n\t\t\t\t\ttype: \"number\",\n\t\t\t\t\trequired: false,\n\t\t\t\t\tinput: false,\n\t\t\t\t\tdefaultValue: defaultTimeWindow,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * The maximum number of requests allowed within the `rateLimitTimeWindow`.\n\t\t\t\t */\n\t\t\t\trateLimitMax: {\n\t\t\t\t\ttype: \"number\",\n\t\t\t\t\trequired: false,\n\t\t\t\t\tinput: false,\n\t\t\t\t\tdefaultValue: defaultRateLimitMax,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * The number of requests made within the rate limit time window\n\t\t\t\t */\n\t\t\t\trequestCount: {\n\t\t\t\t\ttype: \"number\",\n\t\t\t\t\trequired: false,\n\t\t\t\t\tinput: false,\n\t\t\t\t\tdefaultValue: 0,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * The remaining number of requests before the key is revoked.\n\t\t\t\t *\n\t\t\t\t * If this is null, then the key is not revoked.\n\t\t\t\t *\n\t\t\t\t * If `refillInterval` & `refillAmount` are provided, than this will refill accordingly.\n\t\t\t\t */\n\t\t\t\tremaining: {\n\t\t\t\t\ttype: \"number\",\n\t\t\t\t\trequired: false,\n\t\t\t\t\tinput: false,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * The date and time of the last request made to the key.\n\t\t\t\t */\n\t\t\t\tlastRequest: {\n\t\t\t\t\ttype: \"date\",\n\t\t\t\t\trequired: false,\n\t\t\t\t\tinput: false,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * The date and time when the key will expire.\n\t\t\t\t */\n\t\t\t\texpiresAt: {\n\t\t\t\t\ttype: \"date\",\n\t\t\t\t\trequired: false,\n\t\t\t\t\tinput: false,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * The date and time when the key was created.\n\t\t\t\t */\n\t\t\t\tcreatedAt: {\n\t\t\t\t\ttype: \"date\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tinput: false,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * The date and time when the key was last updated.\n\t\t\t\t */\n\t\t\t\tupdatedAt: {\n\t\t\t\t\ttype: \"date\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tinput: false,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * The permissions of the key.\n\t\t\t\t */\n\t\t\t\tpermissions: {\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: false,\n\t\t\t\t\tinput: false,\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * Any additional metadata you want to store with the key.\n\t\t\t\t */\n\t\t\t\tmetadata: {\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: false,\n\t\t\t\t\tinput: true,\n\t\t\t\t\ttransform: {\n\t\t\t\t\t\tinput(value) {\n\t\t\t\t\t\t\treturn JSON.stringify(value);\n\t\t\t\t\t\t},\n\t\t\t\t\t\toutput(value) {\n\t\t\t\t\t\t\tif (!value) return null;\n\t\t\t\t\t\t\treturn parseJSON<any>(value as string);\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}) satisfies BetterAuthPluginDBSchema;\n","import type { BetterAuthPlugin, HookEndpointContext } from \"@better-auth/core\";\nimport { createAuthMiddleware } from \"@better-auth/core/api\";\nimport { base64Url } from \"@better-auth/utils/base64\";\nimport { createHash } from \"@better-auth/utils/hash\";\nimport { BetterAuthError } from \"better-auth\";\nimport { APIError } from \"better-auth/api\";\nimport { generateRandomString } from \"better-auth/crypto\";\nimport { mergeSchema } from \"better-auth/db\";\nimport { API_KEY_ERROR_CODES } from \"./error-codes\";\nimport type { PredefinedApiKeyOptions } from \"./routes\";\nimport { createApiKeyRoutes, deleteAllExpiredApiKeys } from \"./routes\";\nimport { validateApiKey } from \"./routes/verify-api-key\";\nimport { apiKeySchema } from \"./schema\";\nimport type { ApiKeyConfigurationOptions, ApiKeyOptions } from \"./types\";\nimport { getDate, getIp } from \"./utils\";\n\ndeclare module \"@better-auth/core\" {\n\tinterface BetterAuthPluginRegistry<AuthOptions, Options> {\n\t\t\"api-key\": {\n\t\t\tcreator: typeof apiKey;\n\t\t};\n\t}\n}\n\nexport const defaultKeyHasher = async (key: string) => {\n\tconst hash = await createHash(\"SHA-256\").digest(\n\t\tnew TextEncoder().encode(key),\n\t);\n\tconst hashed = base64Url.encode(new Uint8Array(hash), {\n\t\tpadding: false,\n\t});\n\treturn hashed;\n};\n\nexport { API_KEY_ERROR_CODES } from \"./error-codes\";\n\nexport const API_KEY_TABLE_NAME = \"apikey\";\n\nexport function apiKey(\n\t_configurations?:\n\t\t| (ApiKeyConfigurationOptions & ApiKeyOptions)\n\t\t| ApiKeyConfigurationOptions[]\n\t\t| undefined,\n\t_options?: ApiKeyOptions | undefined,\n) {\n\tif (Array.isArray(_configurations) && _configurations.length > 0) {\n\t\tif (!_configurations.every((option) => option.configId)) {\n\t\t\tthrow new BetterAuthError(\n\t\t\t\t\"configId is required for each API key configuration in the api-key plugin.\",\n\t\t\t);\n\t\t}\n\t\tconst configIds = _configurations.map((option) => option.configId);\n\t\tif (new Set(configIds).size !== configIds.length) {\n\t\t\tthrow new BetterAuthError(\n\t\t\t\t\"configId must be unique for each API key configuration in the api-key plugin.\",\n\t\t\t);\n\t\t}\n\t}\n\n\tconst options: ApiKeyOptions = _options ?? {\n\t\tschema: Array.isArray(_configurations)\n\t\t\t? undefined\n\t\t\t: (_configurations as ApiKeyOptions | undefined)?.schema,\n\t};\n\n\tconst configurations = [\n\t\t...(Array.isArray(_configurations)\n\t\t\t? _configurations\n\t\t\t: [_configurations]\n\t\t).map((config) => ({\n\t\t\t...config,\n\t\t\tapiKeyHeaders: config?.apiKeyHeaders ?? \"x-api-key\",\n\t\t\tdefaultKeyLength: config?.defaultKeyLength || 64,\n\t\t\tmaximumPrefixLength: config?.maximumPrefixLength ?? 32,\n\t\t\tminimumPrefixLength: config?.minimumPrefixLength ?? 1,\n\t\t\tmaximumNameLength: config?.maximumNameLength ?? 32,\n\t\t\tminimumNameLength: config?.minimumNameLength ?? 1,\n\t\t\tenableMetadata: config?.enableMetadata ?? false,\n\t\t\tdisableKeyHashing: config?.disableKeyHashing ?? false,\n\t\t\trequireName: config?.requireName ?? false,\n\t\t\tstorage: config?.storage ?? \"database\",\n\t\t\trateLimit: {\n\t\t\t\tenabled:\n\t\t\t\t\tconfig?.rateLimit?.enabled === undefined\n\t\t\t\t\t\t? true\n\t\t\t\t\t\t: config?.rateLimit?.enabled,\n\t\t\t\ttimeWindow: config?.rateLimit?.timeWindow ?? 1000 * 60 * 60 * 24,\n\t\t\t\tmaxRequests: config?.rateLimit?.maxRequests ?? 10,\n\t\t\t},\n\t\t\tkeyExpiration: {\n\t\t\t\tdefaultExpiresIn: config?.keyExpiration?.defaultExpiresIn ?? null,\n\t\t\t\tdisableCustomExpiresTime:\n\t\t\t\t\tconfig?.keyExpiration?.disableCustomExpiresTime ?? false,\n\t\t\t\tmaxExpiresIn: config?.keyExpiration?.maxExpiresIn ?? 365,\n\t\t\t\tminExpiresIn: config?.keyExpiration?.minExpiresIn ?? 1,\n\t\t\t},\n\t\t\tstartingCharactersConfig: {\n\t\t\t\tshouldStore: config?.startingCharactersConfig?.shouldStore ?? true,\n\t\t\t\tcharactersLength:\n\t\t\t\t\tconfig?.startingCharactersConfig?.charactersLength ?? 6,\n\t\t\t},\n\t\t\tenableSessionForAPIKeys: config?.enableSessionForAPIKeys ?? false,\n\t\t\tfallbackToDatabase: config?.fallbackToDatabase ?? false,\n\t\t\tcustomStorage: config?.customStorage,\n\t\t\tdeferUpdates: config?.deferUpdates ?? false,\n\t\t})),\n\t] as PredefinedApiKeyOptions[];\n\n\tconst schema = mergeSchema(\n\t\tapiKeySchema({\n\t\t\tdefaultRateLimitMax:\n\t\t\t\t(configurations.length === 1\n\t\t\t\t\t? configurations[0]?.rateLimit.maxRequests\n\t\t\t\t\t: undefined) ?? 10,\n\t\t\tdefaultTimeWindow:\n\t\t\t\t(configurations.length === 1\n\t\t\t\t\t? configurations[0]?.rateLimit.timeWindow\n\t\t\t\t\t: undefined) ?? 1000 * 60 * 60 * 24,\n\t\t}),\n\t\toptions.schema,\n\t);\n\n\tconst defaultKeyGenerator = async (opts: {\n\t\tlength: number;\n\t\tprefix: string | undefined;\n\t}) => {\n\t\tconst key = generateRandomString(opts.length, \"a-z\", \"A-Z\");\n\t\treturn `${opts.prefix || \"\"}${key}`;\n\t};\n\n\tfunction getApiKeyFromConfig(\n\t\tctx: HookEndpointContext,\n\t\tconfig: PredefinedApiKeyOptions,\n\t): string | null | undefined {\n\t\tif (config.customAPIKeyGetter) {\n\t\t\treturn config.customAPIKeyGetter(ctx);\n\t\t}\n\t\tif (Array.isArray(config.apiKeyHeaders)) {\n\t\t\tfor (const header of config.apiKeyHeaders) {\n\t\t\t\tconst value = ctx.headers?.get(header);\n\t\t\t\tif (value) return value;\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t\treturn ctx.headers?.get(config.apiKeyHeaders) ?? null;\n\t}\n\n\tfunction findApiKeyAndConfig(ctx: HookEndpointContext) {\n\t\tfor (const config of configurations) {\n\t\t\tif (!config.enableSessionForAPIKeys) continue;\n\t\t\tconst key = getApiKeyFromConfig(ctx, config);\n\t\t\tif (key) return { key, config };\n\t\t}\n\t\treturn null;\n\t}\n\n\tconst routes = createApiKeyRoutes({\n\t\tdefaultKeyGenerator,\n\t\tconfigurations,\n\t\tschema,\n\t});\n\n\treturn {\n\t\tid: \"api-key\",\n\t\t$ERROR_CODES: API_KEY_ERROR_CODES,\n\t\thooks: {\n\t\t\tbefore: [\n\t\t\t\t{\n\t\t\t\t\tmatcher: (ctx) => !!findApiKeyAndConfig(ctx),\n\t\t\t\t\thandler: createAuthMiddleware(async (ctx) => {\n\t\t\t\t\t\tconst result = findApiKeyAndConfig(ctx)!;\n\t\t\t\t\t\tconst { key, config } = result;\n\n\t\t\t\t\t\tif (typeof key !== \"string\") {\n\t\t\t\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\t\t\t\"BAD_REQUEST\",\n\t\t\t\t\t\t\t\tAPI_KEY_ERROR_CODES.INVALID_API_KEY_GETTER_RETURN_TYPE,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (key.length < config.defaultKeyLength) {\n\t\t\t\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\t\t\t\"FORBIDDEN\",\n\t\t\t\t\t\t\t\tAPI_KEY_ERROR_CODES.INVALID_API_KEY,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (config.customAPIKeyValidator) {\n\t\t\t\t\t\t\tconst isValid = await config.customAPIKeyValidator({\n\t\t\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\t\tkey,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (!isValid) {\n\t\t\t\t\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\t\t\t\t\"FORBIDDEN\",\n\t\t\t\t\t\t\t\t\tAPI_KEY_ERROR_CODES.INVALID_API_KEY,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst hashed = config.disableKeyHashing\n\t\t\t\t\t\t\t? key\n\t\t\t\t\t\t\t: await defaultKeyHasher(key);\n\n\t\t\t\t\t\tconst apiKey = await validateApiKey({\n\t\t\t\t\t\t\thashedKey: hashed,\n\t\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\topts: config,\n\t\t\t\t\t\t\tschema,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst cleanupTask = deleteAllExpiredApiKeys(ctx.context).catch(\n\t\t\t\t\t\t\t(err) => {\n\t\t\t\t\t\t\t\tctx.context.logger.error(\n\t\t\t\t\t\t\t\t\t\"Failed to delete expired API keys:\",\n\t\t\t\t\t\t\t\t\terr,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (config.deferUpdates) {\n\t\t\t\t\t\t\tctx.context.runInBackground(cleanupTask);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Session mocking only works for user-owned API keys\n\t\t\t\t\t\t// Determine the reference type from the configuration\n\t\t\t\t\t\tconst referencesType = config.references ?? \"user\";\n\t\t\t\t\t\tif (referencesType !== \"user\") {\n\t\t\t\t\t\t\tconst msg = API_KEY_ERROR_CODES.INVALID_REFERENCE_ID_FROM_API_KEY;\n\t\t\t\t\t\t\tthrow APIError.from(\"UNAUTHORIZED\", msg);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst user = await ctx.context.internalAdapter.findUserById(\n\t\t\t\t\t\t\tapiKey.referenceId,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (!user) {\n\t\t\t\t\t\t\tconst msg = API_KEY_ERROR_CODES.INVALID_REFERENCE_ID_FROM_API_KEY;\n\t\t\t\t\t\t\tthrow APIError.from(\"UNAUTHORIZED\", msg);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst session = {\n\t\t\t\t\t\t\tuser,\n\t\t\t\t\t\t\tsession: {\n\t\t\t\t\t\t\t\tid: apiKey.id,\n\t\t\t\t\t\t\t\ttoken: key,\n\t\t\t\t\t\t\t\tuserId: apiKey.referenceId,\n\t\t\t\t\t\t\t\tuserAgent: ctx.request?.headers.get(\"user-agent\") ?? null,\n\t\t\t\t\t\t\t\tipAddress: ctx.request\n\t\t\t\t\t\t\t\t\t? getIp(ctx.request, ctx.context.options)\n\t\t\t\t\t\t\t\t\t: null,\n\t\t\t\t\t\t\t\tcreatedAt: new Date(),\n\t\t\t\t\t\t\t\tupdatedAt: new Date(),\n\t\t\t\t\t\t\t\texpiresAt:\n\t\t\t\t\t\t\t\t\tapiKey.expiresAt ||\n\t\t\t\t\t\t\t\t\tgetDate(\n\t\t\t\t\t\t\t\t\t\tctx.context.options.session?.expiresIn || 60 * 60 * 24 * 7, // 7 days\n\t\t\t\t\t\t\t\t\t\t\"ms\",\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tctx.context.session = session;\n\n\t\t\t\t\t\tif (ctx.path === \"/get-session\") {\n\t\t\t\t\t\t\treturn session;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontext: ctx,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\tendpoints: {\n\t\t\t/**\n\t\t\t * ### Endpoint\n\t\t\t *\n\t\t\t * POST `/api-key/create`\n\t\t\t *\n\t\t\t * ### API Methods\n\t\t\t *\n\t\t\t * **server:**\n\t\t\t * `auth.api.createApiKey`\n\t\t\t *\n\t\t\t * **client:**\n\t\t\t * `authClient.apiKey.create`\n\t\t\t *\n\t\t\t * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/api-key#api-method-api-key-create)\n\t\t\t */\n\t\t\tcreateApiKey: routes.createApiKey,\n\t\t\t/**\n\t\t\t * ### Endpoint\n\t\t\t *\n\t\t\t * POST `/api-key/verify`\n\t\t\t *\n\t\t\t * ### API Methods\n\t\t\t *\n\t\t\t * **server:**\n\t\t\t * `auth.api.verifyApiKey`\n\t\t\t *\n\t\t\t * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/api-key#api-method-api-key-verify)\n\t\t\t */\n\t\t\tverifyApiKey: routes.verifyApiKey,\n\t\t\t/**\n\t\t\t * ### Endpoint\n\t\t\t *\n\t\t\t * GET `/api-key/get`\n\t\t\t *\n\t\t\t * ### API Methods\n\t\t\t *\n\t\t\t * **server:**\n\t\t\t * `auth.api.getApiKey`\n\t\t\t *\n\t\t\t * **client:**\n\t\t\t * `authClient.apiKey.get`\n\t\t\t *\n\t\t\t * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/api-key#api-method-api-key-get)\n\t\t\t */\n\t\t\tgetApiKey: routes.getApiKey,\n\t\t\t/**\n\t\t\t * ### Endpoint\n\t\t\t *\n\t\t\t * POST `/api-key/update`\n\t\t\t *\n\t\t\t * ### API Methods\n\t\t\t *\n\t\t\t * **server:**\n\t\t\t * `auth.api.updateApiKey`\n\t\t\t *\n\t\t\t * **client:**\n\t\t\t * `authClient.apiKey.update`\n\t\t\t *\n\t\t\t * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/api-key#api-method-api-key-update)\n\t\t\t */\n\t\t\tupdateApiKey: routes.updateApiKey,\n\t\t\t/**\n\t\t\t * ### Endpoint\n\t\t\t *\n\t\t\t * POST `/api-key/delete`\n\t\t\t *\n\t\t\t * ### API Methods\n\t\t\t *\n\t\t\t * **server:**\n\t\t\t * `auth.api.deleteApiKey`\n\t\t\t *\n\t\t\t * **client:**\n\t\t\t * `authClient.apiKey.delete`\n\t\t\t *\n\t\t\t * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/api-key#api-method-api-key-delete)\n\t\t\t */\n\t\t\tdeleteApiKey: routes.deleteApiKey,\n\t\t\t/**\n\t\t\t * ### Endpoint\n\t\t\t *\n\t\t\t * GET `/api-key/list`\n\t\t\t *\n\t\t\t * ### API Methods\n\t\t\t *\n\t\t\t * **server:**\n\t\t\t * `auth.api.listApiKeys`\n\t\t\t *\n\t\t\t * **client:**\n\t\t\t * `authClient.apiKey.list`\n\t\t\t *\n\t\t\t * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/api-key#api-method-api-key-list)\n\t\t\t */\n\t\t\tlistApiKeys: routes.listApiKeys,\n\t\t\t/**\n\t\t\t * ### Endpoint\n\t\t\t *\n\t\t\t * POST `/api-key/delete-all-expired-api-keys`\n\t\t\t *\n\t\t\t * ### API Methods\n\t\t\t *\n\t\t\t * **server:**\n\t\t\t * `auth.api.deleteAllExpiredApiKeys`\n\t\t\t *\n\t\t\t * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/api-key#api-method-api-key-delete-all-expired-api-keys)\n\t\t\t */\n\t\t\tdeleteAllExpiredApiKeys: routes.deleteAllExpiredApiKeys,\n\t\t},\n\t\tschema,\n\t} satisfies BetterAuthPlugin;\n}\n\nexport type * from \"./types\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAYA,SAAgB,+BACf,UAC6B;AAE7B,KAAI,YAAY,KACf,QAAO;AAIR,KAAI,OAAO,aAAa,SACvB,QAAO;AAKR,QAAO,cAAmC,SAAS;;;;;AAMpD,SAAS,uBAAuB,UAAuC;AACtE,QAAO,YAAY,QAAQ,OAAO,aAAa;;;;;;AAOhD,eAAsB,2BACrB,KACA,SACA,MACgB;AAEhB,KAAI,KAAK,YAAY,cAAc,CAAC,KAAK,mBACxC;CAID,MAAM,gBAAgB,QAAQ,QAAQ,QACrC,uBAAuB,IAAI,SAAS,CACpC;AAED,KAAI,cAAc,WAAW,EAC5B;CAID,MAAM,oBAAoB,cAAc,IAAI,OAAO,WAAW;EAC7D,MAAM,SAAS,+BAA+B,OAAO,SAAS;AAC9D,MAAI;AACH,SAAM,IAAI,QAAQ,QAAQ,OAAO;IAChC,OAAO;IACP,OAAO,CAAC;KAAE,OAAO;KAAM,OAAO,OAAO;KAAI,CAAC;IAC1C,QAAQ,EAAE,UAAU,QAAQ;IAC5B,CAAC;WACM,OAAO;AACf,OAAI,QAAQ,OAAO,KAClB,6DAA6D,OAAO,GAAG,IACvE,MACA;;GAED;AAEF,OAAM,QAAQ,IAAI,kBAAkB;;;;;;;;;;;AAYrC,eAAsB,iCACrB,KACA,QACA,MACsC;CACtC,MAAM,SAAS,+BAA+B,OAAO,SAAS;AAG9D,KACC,uBAAuB,OAAO,SAAS,KACtC,KAAK,YAAY,cAAc,KAAK,oBAErC,KAAI;AACH,QAAM,IAAI,QAAQ,QAAQ,OAAO;GAChC,OAAO;GACP,OAAO,CAAC;IAAE,OAAO;IAAM,OAAO,OAAO;IAAI,CAAC;GAC1C,QAAQ,EAAE,UAAU,QAAQ;GAC5B,CAAC;UACM,OAAO;AAEf,MAAI,QAAQ,OAAO,KAClB,6DAA6D,OAAO,GAAG,IACvE,MACA;;AAIH,QAAO;;;;;AAMR,SAAS,yBAAyB,WAA2B;AAC5D,QAAO,WAAW;;;;;AAMnB,SAAS,kBAAkB,IAAoB;AAC9C,QAAO,iBAAiB;;;;;AAMzB,SAAS,2BAA2B,aAA6B;AAChE,QAAO,kBAAkB;;;;;AAM1B,SAAS,gBAAgB,QAAwB;AAChD,QAAO,KAAK,UAAU;EACrB,GAAG;EACH,WAAW,OAAO,UAAU,aAAa;EACzC,WAAW,OAAO,UAAU,aAAa;EACzC,WAAW,OAAO,WAAW,aAAa,IAAI;EAC9C,cAAc,OAAO,cAAc,aAAa,IAAI;EACpD,aAAa,OAAO,aAAa,aAAa,IAAI;EAClD,CAAC;;;;;AAMH,SAAS,kBAAkB,MAA8B;AACxD,KAAI,CAAC,QAAQ,OAAO,SAAS,SAC5B,QAAO;AAGR,KAAI;EACH,MAAM,SAAS,KAAK,MAAM,KAAK;AAC/B,SAAO;GACN,GAAG;GACH,WAAW,IAAI,KAAK,OAAO,UAAU;GACrC,WAAW,IAAI,KAAK,OAAO,UAAU;GACrC,WAAW,OAAO,YAAY,IAAI,KAAK,OAAO,UAAU,GAAG;GAC3D,cAAc,OAAO,eAAe,IAAI,KAAK,OAAO,aAAa,GAAG;GACpE,aAAa,OAAO,cAAc,IAAI,KAAK,OAAO,YAAY,GAAG;GACjE;SACM;AACP,SAAO;;;;;;AAOT,SAAS,mBACR,KACA,MAC0B;AAC1B,KAAI,KAAK,cACR,QAAO,KAAK;AAEb,QAAO,IAAI,QAAQ,oBAAoB;;;;;AAMxC,SAAS,aAAa,QAAoC;AACzD,KAAI,OAAO,WAAW;EACrB,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,YAAY,IAAI,KAAK,OAAO,UAAU,CAAC,SAAS;EACtD,MAAM,aAAa,KAAK,OAAO,YAAY,OAAO,IAAK;AAEvD,MAAI,aAAa,EAChB,QAAO;;;;;;AAUV,eAAe,qBACd,KACA,WACA,SACyB;CACzB,MAAM,MAAM,yBAAyB,UAAU;AAE/C,QAAO,kBADM,MAAM,QAAQ,IAAI,IAAI,CACL;;;;;AAM/B,eAAe,yBACd,KACA,IACA,SACyB;CACzB,MAAM,MAAM,kBAAkB,GAAG;AAEjC,QAAO,kBADM,MAAM,QAAQ,IAAI,IAAI,CACL;;;;;AAM/B,eAAe,mBACd,KACA,QACA,SACA,KACgB;CAChB,MAAM,aAAa,gBAAgB,OAAO;CAC1C,MAAM,YAAY,OAAO;CACzB,MAAM,KAAK,OAAO;AAGlB,OAAM,QAAQ,IAAI,yBAAyB,UAAU,EAAE,YAAY,IAAI;AAGvE,OAAM,QAAQ,IAAI,kBAAkB,GAAG,EAAE,YAAY,IAAI;CAGzD,MAAM,SAAS,2BAA2B,OAAO,YAAY;CAC7D,MAAM,cAAc,MAAM,QAAQ,IAAI,OAAO;CAC7C,IAAI,SAAmB,EAAE;AAEzB,KAAI,eAAe,OAAO,gBAAgB,SACzC,KAAI;AACH,WAAS,KAAK,MAAM,YAAY;SACzB;AACP,WAAS,EAAE;;UAEF,MAAM,QAAQ,YAAY,CACpC,UAAS;AAGV,KAAI,CAAC,OAAO,SAAS,GAAG,EAAE;AACzB,SAAO,KAAK,GAAG;AACf,QAAM,QAAQ,IAAI,QAAQ,KAAK,UAAU,OAAO,CAAC;;;;;;AAOnD,eAAe,wBACd,KACA,QACA,SACgB;CAChB,MAAM,YAAY,OAAO;CACzB,MAAM,KAAK,OAAO;CAClB,MAAM,cAAc,OAAO;AAG3B,OAAM,QAAQ,OAAO,yBAAyB,UAAU,CAAC;AAGzD,OAAM,QAAQ,OAAO,kBAAkB,GAAG,CAAC;CAG3C,MAAM,SAAS,2BAA2B,YAAY;CACtD,MAAM,cAAc,MAAM,QAAQ,IAAI,OAAO;CAC7C,IAAI,SAAmB,EAAE;AAEzB,KAAI,eAAe,OAAO,gBAAgB,SACzC,KAAI;AACH,WAAS,KAAK,MAAM,YAAY;SACzB;AACP,WAAS,EAAE;;UAEF,MAAM,QAAQ,YAAY,CACpC,UAAS;CAGV,MAAM,cAAc,OAAO,QAAQ,UAAU,UAAU,GAAG;AAC1D,KAAI,YAAY,WAAW,EAC1B,OAAM,QAAQ,OAAO,OAAO;KAE5B,OAAM,QAAQ,IAAI,QAAQ,KAAK,UAAU,YAAY,CAAC;;;;;AAOxD,eAAsBA,YACrB,KACA,WACA,MACyB;CACzB,MAAM,UAAU,mBAAmB,KAAK,KAAK;AAG7C,KAAI,KAAK,YAAY,WACpB,QAAO,MAAM,IAAI,QAAQ,QAAQ,QAAgB;EAChD,OAAO;EACP,OAAO,CACN;GACC,OAAO;GACP,OAAO;GACP,CACD;EACD,CAAC;AAIH,KAAI,KAAK,YAAY,uBAAuB,KAAK,oBAAoB;AACpE,MAAI,SAAS;GACZ,MAAM,SAAS,MAAM,qBAAqB,KAAK,WAAW,QAAQ;AAClE,OAAI,OACH,QAAO;;EAGT,MAAM,QAAQ,MAAM,IAAI,QAAQ,QAAQ,QAAgB;GACvD,OAAO;GACP,OAAO,CACN;IACC,OAAO;IACP,OAAO;IACP,CACD;GACD,CAAC;AAEF,MAAI,SAAS,QAGZ,OAAM,mBAAmB,KAAK,OAAO,SADzB,aAAa,MAAM,CACmB;AAGnD,SAAO;;AAIR,KAAI,KAAK,YAAY,qBAAqB;AACzC,MAAI,CAAC,QACJ,QAAO;AAER,SAAO,MAAM,qBAAqB,KAAK,WAAW,QAAQ;;AAI3D,QAAO,MAAM,IAAI,QAAQ,QAAQ,QAAgB;EAChD,OAAO;EACP,OAAO,CACN;GACC,OAAO;GACP,OAAO;GACP,CACD;EACD,CAAC;;;;;AAMH,eAAsB,cACrB,KACA,IACA,MACyB;CACzB,MAAM,UAAU,mBAAmB,KAAK,KAAK;AAG7C,KAAI,KAAK,YAAY,WACpB,QAAO,MAAM,IAAI,QAAQ,QAAQ,QAAgB;EAChD,OAAO;EACP,OAAO,CACN;GACC,OAAO;GACP,OAAO;GACP,CACD;EACD,CAAC;AAIH,KAAI,KAAK,YAAY,uBAAuB,KAAK,oBAAoB;AACpE,MAAI,SAAS;GACZ,MAAM,SAAS,MAAM,yBAAyB,KAAK,IAAI,QAAQ;AAC/D,OAAI,OACH,QAAO;;EAGT,MAAM,QAAQ,MAAM,IAAI,QAAQ,QAAQ,QAAgB;GACvD,OAAO;GACP,OAAO,CACN;IACC,OAAO;IACP,OAAO;IACP,CACD;GACD,CAAC;AAEF,MAAI,SAAS,QAGZ,OAAM,mBAAmB,KAAK,OAAO,SADzB,aAAa,MAAM,CACmB;AAGnD,SAAO;;AAIR,KAAI,KAAK,YAAY,qBAAqB;AACzC,MAAI,CAAC,QACJ,QAAO;AAER,SAAO,MAAM,yBAAyB,KAAK,IAAI,QAAQ;;AAIxD,QAAO,MAAM,IAAI,QAAQ,QAAQ,QAAgB;EAChD,OAAO;EACP,OAAO,CACN;GACC,OAAO;GACP,OAAO;GACP,CACD;EACD,CAAC;;;;;AAMH,eAAsB,UACrB,KACA,QACA,MACgB;CAChB,MAAM,UAAU,mBAAmB,KAAK,KAAK;CAC7C,MAAM,MAAM,aAAa,OAAO;AAGhC,KAAI,KAAK,YAAY,WACpB;AAID,KAAI,KAAK,YAAY,qBAAqB;AACzC,MAAI,CAAC,QACJ,OAAM,IAAI,MACT,yEACA;AAEF,QAAM,mBAAmB,KAAK,QAAQ,SAAS,IAAI;AACnD;;;;;;AAOF,eAAsBC,eACrB,KACA,QACA,MACgB;CAChB,MAAM,UAAU,mBAAmB,KAAK,KAAK;AAG7C,KAAI,KAAK,YAAY,WACpB;AAID,KAAI,KAAK,YAAY,qBAAqB;AACzC,MAAI,CAAC,QACJ,OAAM,IAAI,MACT,yEACA;AAEF,QAAM,wBAAwB,KAAK,QAAQ,QAAQ;AACnD;;;;;;;AAoBF,SAAS,0BACR,SACA,QACA,eACA,OACA,QACW;CACX,IAAI,SAAS,CAAC,GAAG,QAAQ;AAGzB,KAAI,QAAQ;EACX,MAAM,YAAY,iBAAiB;AACnC,SAAO,MAAM,GAAG,MAAM;GACrB,MAAM,SAAS,EAAE;GACjB,MAAM,SAAS,EAAE;AAGjB,OAAI,UAAU,QAAQ,UAAU,KAAM,QAAO;AAC7C,OAAI,UAAU,KAAM,QAAO,cAAc,QAAQ,KAAK;AACtD,OAAI,UAAU,KAAM,QAAO,cAAc,QAAQ,IAAI;AAGrD,OAAI,SAAS,OAAQ,QAAO,cAAc,QAAQ,KAAK;AACvD,OAAI,SAAS,OAAQ,QAAO,cAAc,QAAQ,IAAI;AACtD,UAAO;IACN;;AAIH,KAAI,WAAW,OACd,UAAS,OAAO,MAAM,OAAO;AAE9B,KAAI,UAAU,OACb,UAAS,OAAO,MAAM,GAAG,MAAM;AAGhC,QAAO;;;;;AAMR,eAAsBC,cACrB,KACA,aACA,MACA,gBAC6B;CAC7B,MAAM,UAAU,mBAAmB,KAAK,KAAK;CAC7C,MAAM,EAAE,OAAO,QAAQ,QAAQ,kBAAkB,kBAAkB,EAAE;AAGrE,KAAI,KAAK,YAAY,YAAY;EAChC,MAAM,CAAC,SAAS,SAAS,MAAM,QAAQ,IAAI,CAC1C,IAAI,QAAQ,QAAQ,SAAiB;GACpC,OAAO;GACP,OAAO,CACN;IACC,OAAO;IACP,OAAO;IACP,CACD;GACD;GACA;GACA,QAAQ,SACL;IAAE,OAAO;IAAQ,WAAW,iBAAiB;IAAO,GACpD;GACH,CAAC,EACF,IAAI,QAAQ,QAAQ,MAAM;GACzB,OAAO;GACP,OAAO,CACN;IACC,OAAO;IACP,OAAO;IACP,CACD;GACD,CAAC,CACF,CAAC;AACF,SAAO;GAAE;GAAS;GAAO;;AAI1B,KAAI,KAAK,YAAY,uBAAuB,KAAK,oBAAoB;EACpE,MAAM,SAAS,2BAA2B,YAAY;AAEtD,MAAI,SAAS;GACZ,MAAM,cAAc,MAAM,QAAQ,IAAI,OAAO;GAC7C,IAAI,SAAmB,EAAE;AAEzB,OAAI,eAAe,OAAO,gBAAgB,SACzC,KAAI;AACH,aAAS,KAAK,MAAM,YAAY;WACzB;AACP,aAAS,EAAE;;YAEF,MAAM,QAAQ,YAAY,CACpC,UAAS;AAGV,OAAI,OAAO,SAAS,GAAG;IACtB,MAAM,UAAoB,EAAE;AAC5B,SAAK,MAAM,MAAM,QAAQ;KACxB,MAAM,SAAS,MAAM,yBAAyB,KAAK,IAAI,QAAQ;AAC/D,SAAI,OACH,SAAQ,KAAK,OAAO;;AAWtB,WAAO;KAAE,SAPU,0BAClB,SACA,QACA,eACA,OACA,OACA;KAC6B,OAAO,QAAQ;KAAQ;;;EAIvD,MAAM,CAAC,QAAQ,SAAS,MAAM,QAAQ,IAAI,CACzC,IAAI,QAAQ,QAAQ,SAAiB;GACpC,OAAO;GACP,OAAO,CACN;IACC,OAAO;IACP,OAAO;IACP,CACD;GACD;GACA;GACA,QAAQ,SACL;IAAE,OAAO;IAAQ,WAAW,iBAAiB;IAAO,GACpD;GACH,CAAC,EACF,IAAI,QAAQ,QAAQ,MAAM;GACzB,OAAO;GACP,OAAO,CACN;IACC,OAAO;IACP,OAAO;IACP,CACD;GACD,CAAC,CACF,CAAC;AAGF,MAAI,WAAW,OAAO,SAAS,GAAG;GACjC,MAAM,SAAmB,EAAE;AAC3B,QAAK,MAAM,UAAU,QAAQ;AAG5B,UAAM,mBAAmB,KAAK,QAAQ,SAD1B,aAAa,OAAO,CACmB;AACnD,WAAO,KAAK,OAAO,GAAG;;AAGvB,SAAM,QAAQ,IAAI,QAAQ,KAAK,UAAU,OAAO,CAAC;;AAGlD,SAAO;GAAE,SAAS;GAAQ;GAAO;;AAIlC,KAAI,KAAK,YAAY,qBAAqB;AACzC,MAAI,CAAC,QACJ,QAAO;GAAE,SAAS,EAAE;GAAE,OAAO;GAAG;EAGjC,MAAM,SAAS,2BAA2B,YAAY;EACtD,MAAM,cAAc,MAAM,QAAQ,IAAI,OAAO;EAC7C,IAAI,SAAmB,EAAE;AAEzB,MAAI,eAAe,OAAO,gBAAgB,SACzC,KAAI;AACH,YAAS,KAAK,MAAM,YAAY;UACzB;AACP,UAAO;IAAE,SAAS,EAAE;IAAE,OAAO;IAAG;;WAEvB,MAAM,QAAQ,YAAY,CACpC,UAAS;MAET,QAAO;GAAE,SAAS,EAAE;GAAE,OAAO;GAAG;EAGjC,MAAM,UAAoB,EAAE;AAC5B,OAAK,MAAM,MAAM,QAAQ;GACxB,MAAM,SAAS,MAAM,yBAAyB,KAAK,IAAI,QAAQ;AAC/D,OAAI,OACH,SAAQ,KAAK,OAAO;;AAYtB,SAAO;GAAE,SAPU,0BAClB,SACA,QACA,eACA,OACA,OACA;GAC6B,OAAO,QAAQ;GAAQ;;CAItD,MAAM,CAAC,SAAS,SAAS,MAAM,QAAQ,IAAI,CAC1C,IAAI,QAAQ,QAAQ,SAAiB;EACpC,OAAO;EACP,OAAO,CACN;GACC,OAAO;GACP,OAAO;GACP,CACD;EACD;EACA;EACA,QAAQ,SACL;GAAE,OAAO;GAAQ,WAAW,iBAAiB;GAAO,GACpD;EACH,CAAC,EACF,IAAI,QAAQ,QAAQ,MAAM;EACzB,OAAO;EACP,OAAO,CACN;GACC,OAAO;GACP,OAAO;GACP,CACD;EACD,CAAC,CACF,CAAC;AACF,QAAO;EAAE;EAAS;EAAO;;;;;;;;;ACltB1B,SAAS,cACR,KAC6B;CAC7B,MAAM,UAAU,IAAI;AACpB,KAAI,gBAAgB,WAAW,QAAQ,WACtC,QAAO,QAAQ;CAGhB,MAAM,YAAY,QAAQ,YAAY,eAAe;AACrD,KAAI,aAAa,aAAa,UAC7B,QAAO,UAAU;AAGlB,QAAO;;;;;;;;;;;;;AAcR,eAAsB,yBACrB,KACA,QACA,gBACA,gBACkB;CAElB,MAAM,aAAa,cAAc,IAAI;AACrC,KAAI,CAAC,YAAY;EAChB,MAAM,MAAMC,oBAAY;AACxB,QAAMC,WAAS,KAAK,yBAAyB,IAAI;;CAIlD,MAAM,SAAS,MAAM,IAAI,QAAQ,QAAQ,QAAgB;EACxD,OAAO;EACP,OAAO,CACN;GACC,OAAO;GACP,OAAO;GACP,EACD;GACC,OAAO;GACP,OAAO;GACP,CACD;EACD,CAAC;AAEF,KAAI,CAAC,QAAQ;EACZ,MAAM,MAAMD,oBAAY;AACxB,QAAMC,WAAS,KAAK,aAAa,IAAI;;AAYtC,KAAI,CARwB,MAAM,gBACjC,KACA,OAAO,MACP,gBACA,gBACA,WACA,EAEyB;EACzB,MAAM,MAAMD,oBAAY;AACxB,QAAMC,WAAS,KAAK,aAAa,IAAI;;AAGtC,QAAO;;;;;;;;;AAUR,eAAe,gBACd,KACA,MACA,gBACA,QACA,YACmB;CAEnB,MAAM,EAAE,kBAAkB,MAAM,OAAO;AAEvC,KAAI;AAeH,SAde,MAAM,cACpB;GACC;GACA,SAAS;GACT,aAAa,EACZ,QAAQ,CAAC,OAAO,EAChB;GACD;GAEA,4BAA4B;GAC5B,EACD,IACA;SAGM;AACP,SAAO;;;;;;AC3IT,MAAa,WAAW,MAAc,OAAqB,SAAS;AACnE,QAAO,IAAI,KAAK,KAAK,KAAK,IAAI,SAAS,QAAQ,OAAO,MAAO,MAAM;;AAGpE,SAAgB,WAAW,OAAmC;AAC7D,QACC,iBAAiBC,YACjB,iBAAiBC,cAChB,OAAe,SAAS;;AAS3B,MAAM,eAAe;AAErB,SAAgB,MACf,KACA,SACgB;AAChB,KAAI,QAAQ,UAAU,WAAW,kBAChC,QAAO;CAGR,MAAM,UAAU,aAAa,MAAM,IAAI,UAAU;CAIjD,MAAM,YACL,QAAQ,UAAU,WAAW,oBAHP,CAAC,kBAAkB;AAK1C,MAAK,MAAM,OAAO,WAAW;EAC5B,MAAM,QAAQ,SAAS,UAAU,QAAQ,IAAI,IAAI,GAAG,QAAQ;AAC5D,MAAI,OAAO,UAAU,UAAU;GAC9B,MAAM,KAAK,MAAM,MAAM,IAAI,CAAC,GAAI,MAAM;AACtC,OAAI,UAAU,GAAG,CAChB,QAAO,YAAY,IAAI,EACtB,YAAY,QAAQ,UAAU,WAAW,YACzC,CAAC;;;AAML,KAAI,QAAQ,IAAI,eAAe,CAC9B,QAAO;AAGR,QAAO;;;;;ACrCR,MAAM,yBAAyB,EAAE,OAAO;CACvC,UAAU,EACR,QAAQ,CACR,KAAK,EACL,aACC,yGACD,CAAC,CACD,UAAU;CACZ,MAAM,EAAE,QAAQ,CAAC,KAAK,EAAE,aAAa,uBAAuB,CAAC,CAAC,UAAU;CACxE,WAAW,EACT,QAAQ,CACR,KAAK,EACL,aAAa,6CACb,CAAC,CACD,IAAI,EAAE,CACN,UAAU,CACV,UAAU,CACV,QAAQ,KAAK;CACf,QAAQ,EACN,QAAQ,CACR,KAAK,EAAE,aAAa,yBAAyB,CAAC,CAC9C,MAAM,oBAAoB,EAC1B,SACC,yFACD,CAAC,CACD,UAAU;CACZ,WAAW,EACT,QAAQ,CACR,KAAK,EACL,aAAa,kDACb,CAAC,CACD,IAAI,EAAE,CACN,UAAU,CACV,UAAU,CACV,QAAQ,KAAK;CACf,UAAU,EAAE,KAAK,CAAC,UAAU;CAC5B,cAAc,EACZ,QAAQ,CACR,KAAK,EACL,aACC,6EACD,CAAC,CACD,IAAI,EAAE,CACN,UAAU;CACZ,gBAAgB,EACd,QAAQ,CACR,KAAK,EACL,aACC,yEACD,CAAC,CACD,UAAU;CACZ,qBAAqB,EACnB,QAAQ,CACR,KAAK,EACL,aACC,uOACD,CAAC,CACD,UAAU;CACZ,cAAc,EACZ,QAAQ,CACR,KAAK,EACL,aACC,8NACD,CAAC,CACD,UAAU;CACZ,kBAAkB,EAChB,SAAS,CACT,KAAK,EACL,aACC,oEACD,CAAC,CACD,UAAU;CACZ,aAAa,EACX,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CACvC,KAAK,EACL,aAAa,+BACb,CAAC,CACD,UAAU;CACZ,QAAQ,EAAE,OACR,QAAQ,CACR,KAAK,EACL,aACC,iFACD,CAAC,CACD,UAAU;CACZ,gBAAgB,EAAE,OAChB,QAAQ,CACR,KAAK,EACL,aACC,iFACD,CAAC,CACD,UAAU;CACZ,CAAC;AAEF,SAAgB,aAAa,EAC5B,qBACA,gBACA,QACA,2BAYE;AACF,QAAO,mBACN,mBACA;EACC,QAAQ;EACR,MAAM;EACN,UAAU,EACT,SAAS;GACR,aAAa;GACb,WAAW,EACV,OAAO;IACN,aAAa;IACb,SAAS,EACR,oBAAoB,EACnB,QAAQ;KACP,MAAM;KACN,YAAY;MACX,IAAI;OACH,MAAM;OACN,aAAa;OACb;MACD,WAAW;OACV,MAAM;OACN,QAAQ;OACR,aAAa;OACb;MACD,WAAW;OACV,MAAM;OACN,QAAQ;OACR,aAAa;OACb;MACD,MAAM;OACL,MAAM;OACN,UAAU;OACV,aAAa;OACb;MACD,QAAQ;OACP,MAAM;OACN,UAAU;OACV,aAAa;OACb;MACD,OAAO;OACN,MAAM;OACN,UAAU;OACV,aACC;OACD;MACD,KAAK;OACJ,MAAM;OACN,aACC;OACD;MACD,SAAS;OACR,MAAM;OACN,aAAa;OACb;MACD,WAAW;OACV,MAAM;OACN,QAAQ;OACR,UAAU;OACV,aAAa;OACb;MACD,aAAa;OACZ,MAAM;OACN,aAAa;OACb;MACD,cAAc;OACb,MAAM;OACN,QAAQ;OACR,UAAU;OACV,aAAa;OACb;MACD,aAAa;OACZ,MAAM;OACN,QAAQ;OACR,UAAU;OACV,aAAa;OACb;MACD,UAAU;OACT,MAAM;OACN,UAAU;OACV,sBAAsB;OACtB,aAAa;OACb;MACD,cAAc;OACb,MAAM;OACN,UAAU;OACV,aAAa;OACb;MACD,qBAAqB;OACpB,MAAM;OACN,UAAU;OACV,aAAa;OACb;MACD,WAAW;OACV,MAAM;OACN,UAAU;OACV,aAAa;OACb;MACD,cAAc;OACb,MAAM;OACN,UAAU;OACV,aAAa;OACb;MACD,gBAAgB;OACf,MAAM;OACN,UAAU;OACV,aAAa;OACb;MACD,kBAAkB;OACjB,MAAM;OACN,aAAa;OACb;MACD,cAAc;OACb,MAAM;OACN,aAAa;OACb;MACD,aAAa;OACZ,MAAM;OACN,UAAU;OACV,sBAAsB;QACrB,MAAM;QACN,OAAO,EAAE,MAAM,UAAU;QACzB;OACD,aAAa;OACb;MACD;KACD,UAAU;MACT;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;KACD,EACD,EACD;IACD,EACD;GACD,EACD;EACD,EACD,OAAO,QAAQ;EACd,MAAM,EACL,UACA,MACA,WACA,QACA,WACA,UACA,cACA,gBACA,aACA,cACA,qBACA,qBACG,IAAI;EAER,MAAM,OAAO,qBAAqB,IAAI,SAAS,gBAAgB,SAAS;EACxE,MAAM,eAAe,KAAK,sBAAsB;EAChD,MAAM,UAAU,MAAM,kBAAkB,IAAI;EAC5C,MAAM,kBAAk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ACf,MAAM;UACN,UAAU;UACV,aACC;UACD;SACD,cAAc;UACb,MAAM;UACN,UAAU;UACV,aAAa;UACb;SACD,cAAc;UACb,MAAM;UACN,QAAQ;UACR,UAAU;UACV,aAAa;UACb;SACD,SAAS;UACR,MAAM;UACN,aAAa;UACb,SAAS;UACT;SACD,kBAAkB;UACjB,MAAM;UACN,aACC;UACD;SACD,qBAAqB;UACpB,MAAM;UACN,UAAU;UACV,aAAa;UACb;SACD,cAAc;UACb,MAAM;UACN,UAAU;UACV,aACC;UACD;SACD,cAAc;UACb,MAAM;UACN,aACC;UACD;SACD,WAAW;UACV,MAAM;UACN,UAAU;UACV,aACC;UACD;SACD,aAAa;UACZ,MAAM;UACN,QAAQ;UACR,UAAU;UACV,aAAa;UACb;SACD,WAAW;UACV,MAAM;UACN,QAAQ;UACR,UAAU;UACV,aAAa;UACb;SACD,WAAW;UACV,MAAM;UACN,QAAQ;UACR,aAAa;UACb;SACD,WAAW;UACV,MAAM;UACN,QAAQ;UACR,aAAa;UACb;SACD,UAAU;UACT,MAAM;UACN,UAAU;UACV,sBAAsB;UACtB,aAAa;UACb;SACD,aAAa;UACZ,MAAM;UACN,UAAU;UACV,aACC;UACD;SACD;QACD,UAAU;SACT;SACA;SACA;SACA;SACA;SACA;SACA;SACA;QACD;OACD;MACD,OAAO;OACN,MAAM;OACN,aAAa;OACb;MACD,OAAO;OACN,MAAM;OACN,UAAU;OACV,aAAa;OACb;MACD,QAAQ;OACP,MAAM;OACN,UAAU;OACV,aAAa;OACb;MACD;KACD,UAAU,CAAC,WAAW,QAAQ;KAC9B,EACD,EACD;IACD,EACD;GACD,EACD;EACD,EACD,OAAO,QAAQ;EACd,MAAM,UAAU,IAAI,QAAQ;EAC5B,MAAM,WAAW,IAAI,OAAO;EAC5B,MAAM,iBAAiB,IAAI,OAAO;EAClC,MAAM,QACL,IAAI,OAAO,SAAS,OAAO,OAAO,IAAI,MAAM,MAAM,GAAG;EACtD,MAAM,SACL,IAAI,OAAO,UAAU,OAAO,OAAO,IAAI,MAAM,OAAO,GAAG;AAGxD,MAAI,eACH,OAAM,yBACL,KAC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AC,CAAC,CACvC,KAAK,EACL,aAAa,uDACb,CAAC,CACD,UAAU,CACV,UAAU;CACZ,CAAC;AAEF,SAAgB,aAAa,EAC5B,gBACA,QACA,2BAQE;AACF,QAAO,mBACN,mBACA;EACC,QAAQ;EACR,MAAM;EACN,UAAU,EACT,SAAS;GACR,aAAa;GACb,WAAW,EACV,OAAO;IACN,aAAa;IACb,SAAS,EACR,oBAAoB,EACnB,QAAQ;KACP,MAAM;KACN,YAAY;MACX,IAAI;OACH,MAAM;OACN,aAAa;OACb;MACD,MAAM;OACL,MAAM;OACN,UAAU;OACV,aAAa;OACb;MACD,OAAO;OACN,MAAM;OACN,UAAU;OACV,aACC;OACD;MACD,QAAQ;OACP,MAAM;OACN,UAAU;OACV,aACC;OACD;MACD,QAAQ;OACP,MAAM;OACN,aAAa;OACb;MACD,gBAAgB;OACf,MAAM;OACN,UAAU;OACV,aACC;OACD;MACD,cAAc;OACb,MAAM;OACN,UAAU;OACV,aAAa;OACb;MACD,cAAc;OACb,MAAM;OACN,QAAQ;OACR,UAAU;OACV,aAAa;OACb;MACD,SAAS;OACR,MAAM;OACN,aAAa;OACb,SAAS;OACT;MACD,kBAAkB;OACjB,MAAM;OACN,aACC;OACD;MACD,qBAAqB;OACpB,MAAM;OACN,UAAU;OACV,aAAa;OACb;MACD,cAAc;OACb,MAAM;OACN,UAAU;OACV,aACC;OACD;MACD,cAAc;OACb,MAAM;OACN,aACC;OACD;MACD,WAAW;OACV,MAAM;OACN,UAAU;OACV,aACC;OACD;MACD,aAAa;OACZ,MAAM;OACN,QAAQ;OACR,UAAU;OACV,aAAa;OACb;MACD,WAAW;OACV,MAAM;OACN,QAAQ;OACR,UAAU;OACV,aAAa;OACb;MACD,WAAW;OACV,MAAM;OACN,QAAQ;OACR,aAAa;OACb;MACD,WAAW;OACV,MAAM;OACN,QAAQ;OACR,aAAa;OACb;MACD,UAAU;OACT,MAAM;OACN,UAAU;OACV,sBAAsB;OACtB,aAAa;OACb;MACD,aAAa;OACZ,MAAM;OACN,UAAU;OACV,aACC;OACD;MACD;KACD,UAAU;MACT;MACA;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"}