caplib
Version:
Credentialless Authentication Protocol Library for Web Applications
1 lines • 18.8 kB
Source Map (JSON)
{"version":3,"sources":["../src/server/db/encrypted-json-db.ts","../src/server/handlers/wallet-auth.ts"],"sourcesContent":["import { promises as fs } from 'fs';\nimport { join } from 'path';\nimport { createCipheriv, createDecipheriv, randomBytes } from 'crypto';\nimport { User, NewUserData } from '../../types';\nimport type { DatabaseInterface } from './interface';\n\ninterface DBSchema {\n users: User[];\n}\n\nconst defaultSchema: DBSchema = {\n users: []\n};\n\nexport class EncryptedJSONDatabase implements DatabaseInterface {\n private static instance: EncryptedJSONDatabase;\n private dbPath: string;\n private data: DBSchema = defaultSchema;\n private initialized: boolean = false;\n private encryptionKey: Buffer;\n\n private constructor(dbPath?: string) {\n this.dbPath = dbPath || join(process.cwd(), 'data', 'caplib-users.encrypted.json');\n const key = process.env.DB_ENCRYPTION_KEY || randomBytes(32).toString('hex');\n this.encryptionKey = Buffer.from(key, 'hex');\n }\n\n public static getInstance(dbPath?: string): EncryptedJSONDatabase {\n if (!EncryptedJSONDatabase.instance) {\n EncryptedJSONDatabase.instance = new EncryptedJSONDatabase(dbPath);\n }\n return EncryptedJSONDatabase.instance;\n }\n\n private async encrypt(data: string): Promise<string> {\n const iv = randomBytes(16);\n const cipher = createCipheriv('aes-256-gcm', this.encryptionKey, iv);\n\n let encrypted = cipher.update(data, 'utf8', 'hex');\n encrypted += cipher.final('hex');\n\n const authTag = cipher.getAuthTag();\n\n return JSON.stringify({\n iv: iv.toString('hex'),\n data: encrypted,\n authTag: authTag.toString('hex')\n });\n }\n\n private async decrypt(encryptedData: string): Promise<string> {\n const { iv, data, authTag } = JSON.parse(encryptedData);\n\n const decipher = createDecipheriv(\n 'aes-256-gcm',\n this.encryptionKey,\n Buffer.from(iv, 'hex')\n );\n\n decipher.setAuthTag(Buffer.from(authTag, 'hex'));\n\n let decrypted = decipher.update(data, 'hex', 'utf8');\n decrypted += decipher.final('utf8');\n\n return decrypted;\n }\n\n private async ensureDirectoryExists(): Promise<void> {\n const dir = this.dbPath.substring(0, this.dbPath.lastIndexOf('/'));\n try {\n await fs.access(dir);\n } catch {\n await fs.mkdir(dir, { recursive: true });\n }\n }\n\n private async loadData(): Promise<void> {\n try {\n const encryptedContent = await fs.readFile(this.dbPath, 'utf-8');\n const decryptedContent = await this.decrypt(encryptedContent);\n this.data = JSON.parse(decryptedContent);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n this.data = defaultSchema;\n await this.saveData();\n } else {\n throw error;\n }\n }\n }\n\n private async saveData(): Promise<void> {\n await this.ensureDirectoryExists();\n const encryptedData = await this.encrypt(JSON.stringify(this.data));\n await fs.writeFile(this.dbPath, encryptedData);\n }\n\n public async initialize(): Promise<void> {\n if (this.initialized) return;\n\n try {\n await this.loadData();\n this.initialized = true;\n } catch (error) {\n console.error('Failed to initialize database:', error);\n throw error;\n }\n }\n\n public async findUser(walletAddress: string): Promise<User | null> {\n await this.initialize();\n const user = this.data.users.find(u =>\n u.wallet_address.toLowerCase() === walletAddress.toLowerCase()\n );\n return user || null;\n }\n\n public async createUser(userData: NewUserData): Promise<User> {\n await this.initialize();\n\n const existingUser = await this.findUser(userData.wallet_address);\n if (existingUser) {\n throw new Error('User already exists');\n }\n\n const now = new Date().toISOString();\n const newUser: User = {\n ...userData,\n role: userData.role || 'User',\n account_type: userData.account_type || 'human',\n created_at: now\n };\n\n this.data.users.push(newUser);\n await this.saveData();\n\n return newUser;\n }\n\n public async updateUser(walletAddress: string, updates: Partial<User>): Promise<User | null> {\n await this.initialize();\n\n const index = this.data.users.findIndex(u =>\n u.wallet_address.toLowerCase() === walletAddress.toLowerCase()\n );\n\n if (index === -1) return null;\n\n const updatedUser = {\n ...this.data.users[index],\n ...updates,\n wallet_address: this.data.users[index].wallet_address\n };\n\n this.data.users[index] = updatedUser;\n await this.saveData();\n\n return updatedUser;\n }\n\n public async deleteUser(walletAddress: string): Promise<boolean> {\n await this.initialize();\n\n const initialLength = this.data.users.length;\n this.data.users = this.data.users.filter(u =>\n u.wallet_address.toLowerCase() !== walletAddress.toLowerCase()\n );\n\n if (this.data.users.length !== initialLength) {\n await this.saveData();\n return true;\n }\n\n return false;\n }\n\n public async close(): Promise<void> {\n await this.saveData();\n this.initialized = false;\n }\n}","import { NextRequest } from 'next/server';\nimport type { AuthHandlerConfig, AuthResponse, NonceResponse } from '../../types';\nimport { EncryptedJSONDatabase } from '../db/encrypted-json-db';\nimport { isValidRole, getDefaultRole } from '../utils/roles';\n\nconst getEnvVariable = (name: string, fallbackName?: string): string => {\n let value = process.env[name];\n if (!value && fallbackName) {\n value = process.env[fallbackName];\n }\n\n if (!value) {\n throw new Error(`Required environment variable ${name}${fallbackName ? ` or ${fallbackName}` : ''} is not set`);\n }\n return value;\n};\n\nexport const createAuthHandler = (config?: AuthHandlerConfig) => {\n const db = EncryptedJSONDatabase.getInstance();\n let isInitialized = false;\n\n const initialize = async () => {\n if (!isInitialized) {\n try {\n await db.initialize();\n isInitialized = true;\n } catch (error) {\n console.error('Failed to initialize database:', error);\n }\n }\n };\n\n const GET = async (): Promise<Response> => {\n try {\n const API_URL = getEnvVariable('CAPLIB_API_URL');\n const API_KEY = getEnvVariable('CAPLIB_API_KEY');\n const CONTRACT_ID = getEnvVariable('NEXT_PUBLIC_AUTH_CONTRACT_ID', 'AUTH_CONTRACT_ID');\n\n await initialize();\n\n const response = await fetch(`${API_URL}?action=generateNonce&contractId=${CONTRACT_ID}`, {\n headers: { 'X-API-Key': API_KEY },\n });\n\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n\n const data = await response.json();\n\n return new Response(\n JSON.stringify({ nonce: data.nonce } as NonceResponse),\n {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n } catch (error) {\n console.error('Error generating nonce:', error);\n\n return new Response(\n JSON.stringify({ error: 'Failed to generate nonce', nonce: '' } as NonceResponse),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n };\n\n const POST = async (req: NextRequest): Promise<Response> => {\n try {\n const API_URL = getEnvVariable('CAPLIB_API_URL');\n const API_KEY = getEnvVariable('CAPLIB_API_KEY');\n const CONTRACT_ID = getEnvVariable('NEXT_PUBLIC_AUTH_CONTRACT_ID', 'AUTH_CONTRACT_ID');\n\n await initialize();\n\n const body = await req.json();\n const { nonce, userData } = body;\n\n const verifyResponse = await fetch(API_URL, {\n method: 'POST',\n headers: {\n 'X-API-Key': API_KEY,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n action: 'verifyAuthentication',\n contractId: CONTRACT_ID,\n nonce: nonce,\n }),\n });\n\n if (!verifyResponse.ok) {\n throw new Error(`Verification failed: ${verifyResponse.status}`);\n }\n\n const verifyData = await verifyResponse.json();\n const { authenticated, userAddress } = verifyData;\n\n if (!authenticated || !userAddress) {\n return new Response(\n JSON.stringify({\n authenticated: false,\n error: 'Authentication failed'\n } as AuthResponse),\n {\n status: 401,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n\n if (config?.customValidation) {\n const isValid = await config.customValidation(userAddress);\n if (!isValid) {\n return new Response(\n JSON.stringify({\n authenticated: false,\n error: 'Custom validation failed'\n } as AuthResponse),\n {\n status: 403,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n }\n\n try {\n let user = await db.findUser(userAddress);\n\n if (userData?.role && !isValidRole(userData.role)) {\n userData.role = getDefaultRole();\n }\n \n if (!user && userData) {\n user = await db.createUser({\n wallet_address: userAddress,\n ...userData\n });\n }\n\n return new Response(\n JSON.stringify({\n authenticated: true,\n userAddress,\n user: user || undefined\n } as AuthResponse),\n {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n } catch (error) {\n console.error('Database error:', error);\n return new Response(\n JSON.stringify({\n authenticated: true,\n userAddress,\n error: 'Database operation failed'\n } as AuthResponse),\n {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n } catch (error) {\n console.error('Error verifying authentication:', error);\n\n return new Response(\n JSON.stringify({\n authenticated: false,\n error: 'Authentication verification failed'\n } as AuthResponse),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n };\n\n return { GET, POST };\n};"],"mappings":";;;;;;AAAA,SAAS,YAAY,UAAU;AAC/B,SAAS,YAAY;AACrB,SAAS,gBAAgB,kBAAkB,mBAAmB;AAQ9D,IAAM,gBAA0B;AAAA,EAC5B,OAAO,CAAC;AACZ;AAEO,IAAM,wBAAN,MAAM,uBAAmD;AAAA,EAC5D,OAAe;AAAA,EACP;AAAA,EACA,OAAiB;AAAA,EACjB,cAAuB;AAAA,EACvB;AAAA,EAEA,YAAY,QAAiB;AACjC,SAAK,SAAS,UAAU,KAAK,QAAQ,IAAI,GAAG,QAAQ,6BAA6B;AACjF,UAAM,MAAM,QAAQ,IAAI,qBAAqB,YAAY,EAAE,EAAE,SAAS,KAAK;AAC3E,SAAK,gBAAgB,OAAO,KAAK,KAAK,KAAK;AAAA,EAC/C;AAAA,EAEA,OAAc,YAAY,QAAwC;AAC9D,QAAI,CAAC,uBAAsB,UAAU;AACjC,6BAAsB,WAAW,IAAI,uBAAsB,MAAM;AAAA,IACrE;AACA,WAAO,uBAAsB;AAAA,EACjC;AAAA,EAEA,MAAc,QAAQ,MAA+B;AACjD,UAAM,KAAK,YAAY,EAAE;AACzB,UAAM,SAAS,eAAe,eAAe,KAAK,eAAe,EAAE;AAEnE,QAAI,YAAY,OAAO,OAAO,MAAM,QAAQ,KAAK;AACjD,iBAAa,OAAO,MAAM,KAAK;AAE/B,UAAM,UAAU,OAAO,WAAW;AAElC,WAAO,KAAK,UAAU;AAAA,MAClB,IAAI,GAAG,SAAS,KAAK;AAAA,MACrB,MAAM;AAAA,MACN,SAAS,QAAQ,SAAS,KAAK;AAAA,IACnC,CAAC;AAAA,EACL;AAAA,EAEA,MAAc,QAAQ,eAAwC;AAC1D,UAAM,EAAE,IAAI,MAAM,QAAQ,IAAI,KAAK,MAAM,aAAa;AAEtD,UAAM,WAAW;AAAA,MACb;AAAA,MACA,KAAK;AAAA,MACL,OAAO,KAAK,IAAI,KAAK;AAAA,IACzB;AAEA,aAAS,WAAW,OAAO,KAAK,SAAS,KAAK,CAAC;AAE/C,QAAI,YAAY,SAAS,OAAO,MAAM,OAAO,MAAM;AACnD,iBAAa,SAAS,MAAM,MAAM;AAElC,WAAO;AAAA,EACX;AAAA,EAEA,MAAc,wBAAuC;AACjD,UAAM,MAAM,KAAK,OAAO,UAAU,GAAG,KAAK,OAAO,YAAY,GAAG,CAAC;AACjE,QAAI;AACA,YAAM,GAAG,OAAO,GAAG;AAAA,IACvB,QAAQ;AACJ,YAAM,GAAG,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IAC3C;AAAA,EACJ;AAAA,EAEA,MAAc,WAA0B;AACpC,QAAI;AACA,YAAM,mBAAmB,MAAM,GAAG,SAAS,KAAK,QAAQ,OAAO;AAC/D,YAAM,mBAAmB,MAAM,KAAK,QAAQ,gBAAgB;AAC5D,WAAK,OAAO,KAAK,MAAM,gBAAgB;AAAA,IAC3C,SAAS,OAAO;AACZ,UAAK,MAAgC,SAAS,UAAU;AACpD,aAAK,OAAO;AACZ,cAAM,KAAK,SAAS;AAAA,MACxB,OAAO;AACH,cAAM;AAAA,MACV;AAAA,IACJ;AAAA,EACJ;AAAA,EAEA,MAAc,WAA0B;AACpC,UAAM,KAAK,sBAAsB;AACjC,UAAM,gBAAgB,MAAM,KAAK,QAAQ,KAAK,UAAU,KAAK,IAAI,CAAC;AAClE,UAAM,GAAG,UAAU,KAAK,QAAQ,aAAa;AAAA,EACjD;AAAA,EAEA,MAAa,aAA4B;AACrC,QAAI,KAAK,YAAa;AAEtB,QAAI;AACA,YAAM,KAAK,SAAS;AACpB,WAAK,cAAc;AAAA,IACvB,SAAS,OAAO;AACZ,cAAQ,MAAM,kCAAkC,KAAK;AACrD,YAAM;AAAA,IACV;AAAA,EACJ;AAAA,EAEA,MAAa,SAAS,eAA6C;AAC/D,UAAM,KAAK,WAAW;AACtB,UAAM,OAAO,KAAK,KAAK,MAAM;AAAA,MAAK,OAC9B,EAAE,eAAe,YAAY,MAAM,cAAc,YAAY;AAAA,IACjE;AACA,WAAO,QAAQ;AAAA,EACnB;AAAA,EAEA,MAAa,WAAW,UAAsC;AAC1D,UAAM,KAAK,WAAW;AAEtB,UAAM,eAAe,MAAM,KAAK,SAAS,SAAS,cAAc;AAChE,QAAI,cAAc;AACd,YAAM,IAAI,MAAM,qBAAqB;AAAA,IACzC;AAEA,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,UAAM,UAAgB;AAAA,MAClB,GAAG;AAAA,MACH,MAAM,SAAS,QAAQ;AAAA,MACvB,cAAc,SAAS,gBAAgB;AAAA,MACvC,YAAY;AAAA,IAChB;AAEA,SAAK,KAAK,MAAM,KAAK,OAAO;AAC5B,UAAM,KAAK,SAAS;AAEpB,WAAO;AAAA,EACX;AAAA,EAEA,MAAa,WAAW,eAAuB,SAA8C;AACzF,UAAM,KAAK,WAAW;AAEtB,UAAM,QAAQ,KAAK,KAAK,MAAM;AAAA,MAAU,OACpC,EAAE,eAAe,YAAY,MAAM,cAAc,YAAY;AAAA,IACjE;AAEA,QAAI,UAAU,GAAI,QAAO;AAEzB,UAAM,cAAc;AAAA,MAChB,GAAG,KAAK,KAAK,MAAM,KAAK;AAAA,MACxB,GAAG;AAAA,MACH,gBAAgB,KAAK,KAAK,MAAM,KAAK,EAAE;AAAA,IAC3C;AAEA,SAAK,KAAK,MAAM,KAAK,IAAI;AACzB,UAAM,KAAK,SAAS;AAEpB,WAAO;AAAA,EACX;AAAA,EAEA,MAAa,WAAW,eAAyC;AAC7D,UAAM,KAAK,WAAW;AAEtB,UAAM,gBAAgB,KAAK,KAAK,MAAM;AACtC,SAAK,KAAK,QAAQ,KAAK,KAAK,MAAM;AAAA,MAAO,OACrC,EAAE,eAAe,YAAY,MAAM,cAAc,YAAY;AAAA,IACjE;AAEA,QAAI,KAAK,KAAK,MAAM,WAAW,eAAe;AAC1C,YAAM,KAAK,SAAS;AACpB,aAAO;AAAA,IACX;AAEA,WAAO;AAAA,EACX;AAAA,EAEA,MAAa,QAAuB;AAChC,UAAM,KAAK,SAAS;AACpB,SAAK,cAAc;AAAA,EACvB;AACJ;;;AC/KA,IAAM,iBAAiB,CAAC,MAAc,iBAAkC;AACpE,MAAI,QAAQ,QAAQ,IAAI,IAAI;AAC5B,MAAI,CAAC,SAAS,cAAc;AACxB,YAAQ,QAAQ,IAAI,YAAY;AAAA,EACpC;AAEA,MAAI,CAAC,OAAO;AACR,UAAM,IAAI,MAAM,iCAAiC,IAAI,GAAG,eAAe,OAAO,YAAY,KAAK,EAAE,aAAa;AAAA,EAClH;AACA,SAAO;AACX;AAEO,IAAM,oBAAoB,CAAC,WAA+B;AAC7D,QAAM,KAAK,sBAAsB,YAAY;AAC7C,MAAI,gBAAgB;AAEpB,QAAM,aAAa,YAAY;AAC3B,QAAI,CAAC,eAAe;AAChB,UAAI;AACA,cAAM,GAAG,WAAW;AACpB,wBAAgB;AAAA,MACpB,SAAS,OAAO;AACZ,gBAAQ,MAAM,kCAAkC,KAAK;AAAA,MACzD;AAAA,IACJ;AAAA,EACJ;AAEA,QAAM,MAAM,YAA+B;AACvC,QAAI;AACA,YAAM,UAAU,eAAe,gBAAgB;AAC/C,YAAM,UAAU,eAAe,gBAAgB;AAC/C,YAAM,cAAc,eAAe,gCAAgC,kBAAkB;AAErF,YAAM,WAAW;AAEjB,YAAM,WAAW,MAAM,MAAM,GAAG,OAAO,oCAAoC,WAAW,IAAI;AAAA,QACtF,SAAS,EAAE,aAAa,QAAQ;AAAA,MACpC,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AACd,cAAM,IAAI,MAAM,uBAAuB,SAAS,MAAM,EAAE;AAAA,MAC5D;AAEA,YAAM,OAAO,MAAM,SAAS,KAAK;AAEjC,aAAO,IAAI;AAAA,QACP,KAAK,UAAU,EAAE,OAAO,KAAK,MAAM,CAAkB;AAAA,QACrD;AAAA,UACI,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAClD;AAAA,MACJ;AAAA,IACJ,SAAS,OAAO;AACZ,cAAQ,MAAM,2BAA2B,KAAK;AAE9C,aAAO,IAAI;AAAA,QACP,KAAK,UAAU,EAAE,OAAO,4BAA4B,OAAO,GAAG,CAAkB;AAAA,QAChF;AAAA,UACI,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAClD;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAEA,QAAM,OAAO,OAAO,QAAwC;AACxD,QAAI;AACA,YAAM,UAAU,eAAe,gBAAgB;AAC/C,YAAM,UAAU,eAAe,gBAAgB;AAC/C,YAAM,cAAc,eAAe,gCAAgC,kBAAkB;AAErF,YAAM,WAAW;AAEjB,YAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,YAAM,EAAE,OAAO,SAAS,IAAI;AAE5B,YAAM,iBAAiB,MAAM,MAAM,SAAS;AAAA,QACxC,QAAQ;AAAA,QACR,SAAS;AAAA,UACL,aAAa;AAAA,UACb,gBAAgB;AAAA,QACpB;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACjB,QAAQ;AAAA,UACR,YAAY;AAAA,UACZ;AAAA,QACJ,CAAC;AAAA,MACL,CAAC;AAED,UAAI,CAAC,eAAe,IAAI;AACpB,cAAM,IAAI,MAAM,wBAAwB,eAAe,MAAM,EAAE;AAAA,MACnE;AAEA,YAAM,aAAa,MAAM,eAAe,KAAK;AAC7C,YAAM,EAAE,eAAe,YAAY,IAAI;AAEvC,UAAI,CAAC,iBAAiB,CAAC,aAAa;AAChC,eAAO,IAAI;AAAA,UACP,KAAK,UAAU;AAAA,YACX,eAAe;AAAA,YACf,OAAO;AAAA,UACX,CAAiB;AAAA,UACjB;AAAA,YACI,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAClD;AAAA,QACJ;AAAA,MACJ;AAEA,UAAI,QAAQ,kBAAkB;AAC1B,cAAM,UAAU,MAAM,OAAO,iBAAiB,WAAW;AACzD,YAAI,CAAC,SAAS;AACV,iBAAO,IAAI;AAAA,YACP,KAAK,UAAU;AAAA,cACX,eAAe;AAAA,cACf,OAAO;AAAA,YACX,CAAiB;AAAA,YACjB;AAAA,cACI,QAAQ;AAAA,cACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,YAClD;AAAA,UACJ;AAAA,QACJ;AAAA,MACJ;AAEA,UAAI;AACA,YAAI,OAAO,MAAM,GAAG,SAAS,WAAW;AAExC,YAAI,UAAU,QAAQ,CAAC,YAAY,SAAS,IAAI,GAAG;AAC/C,mBAAS,OAAO,eAAe;AAAA,QACjC;AAEF,YAAI,CAAC,QAAQ,UAAU;AACnB,iBAAO,MAAM,GAAG,WAAW;AAAA,YACvB,gBAAgB;AAAA,YAChB,GAAG;AAAA,UACP,CAAC;AAAA,QACL;AAEA,eAAO,IAAI;AAAA,UACP,KAAK,UAAU;AAAA,YACX,eAAe;AAAA,YACf;AAAA,YACA,MAAM,QAAQ;AAAA,UAClB,CAAiB;AAAA,UACjB;AAAA,YACI,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAClD;AAAA,QACJ;AAAA,MACJ,SAAS,OAAO;AACZ,gBAAQ,MAAM,mBAAmB,KAAK;AACtC,eAAO,IAAI;AAAA,UACP,KAAK,UAAU;AAAA,YACX,eAAe;AAAA,YACf;AAAA,YACA,OAAO;AAAA,UACX,CAAiB;AAAA,UACjB;AAAA,YACI,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAClD;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ,SAAS,OAAO;AACZ,cAAQ,MAAM,mCAAmC,KAAK;AAEtD,aAAO,IAAI;AAAA,QACP,KAAK,UAAU;AAAA,UACX,eAAe;AAAA,UACf,OAAO;AAAA,QACX,CAAiB;AAAA,QACjB;AAAA,UACI,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAClD;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAEA,SAAO,EAAE,KAAK,KAAK;AACvB;","names":[]}