UNPKG

caplib

Version:

Credentialless Authentication Protocol Library for Web Applications

312 lines (309 loc) 9.28 kB
import { getDefaultRole, isValidRole } from "./chunk-7AEYXUST.mjs"; // src/server/db/encrypted-json-db.ts import { promises as fs } from "fs"; import { join } from "path"; import { createCipheriv, createDecipheriv, randomBytes } from "crypto"; var defaultSchema = { users: [] }; var EncryptedJSONDatabase = class _EncryptedJSONDatabase { static instance; dbPath; data = defaultSchema; initialized = false; encryptionKey; constructor(dbPath) { this.dbPath = dbPath || join(process.cwd(), "data", "caplib-users.encrypted.json"); const key = process.env.DB_ENCRYPTION_KEY || randomBytes(32).toString("hex"); this.encryptionKey = Buffer.from(key, "hex"); } static getInstance(dbPath) { if (!_EncryptedJSONDatabase.instance) { _EncryptedJSONDatabase.instance = new _EncryptedJSONDatabase(dbPath); } return _EncryptedJSONDatabase.instance; } async encrypt(data) { const iv = randomBytes(16); const cipher = createCipheriv("aes-256-gcm", this.encryptionKey, iv); let encrypted = cipher.update(data, "utf8", "hex"); encrypted += cipher.final("hex"); const authTag = cipher.getAuthTag(); return JSON.stringify({ iv: iv.toString("hex"), data: encrypted, authTag: authTag.toString("hex") }); } async decrypt(encryptedData) { const { iv, data, authTag } = JSON.parse(encryptedData); const decipher = createDecipheriv( "aes-256-gcm", this.encryptionKey, Buffer.from(iv, "hex") ); decipher.setAuthTag(Buffer.from(authTag, "hex")); let decrypted = decipher.update(data, "hex", "utf8"); decrypted += decipher.final("utf8"); return decrypted; } async ensureDirectoryExists() { const dir = this.dbPath.substring(0, this.dbPath.lastIndexOf("/")); try { await fs.access(dir); } catch { await fs.mkdir(dir, { recursive: true }); } } async loadData() { try { const encryptedContent = await fs.readFile(this.dbPath, "utf-8"); const decryptedContent = await this.decrypt(encryptedContent); this.data = JSON.parse(decryptedContent); } catch (error) { if (error.code === "ENOENT") { this.data = defaultSchema; await this.saveData(); } else { throw error; } } } async saveData() { await this.ensureDirectoryExists(); const encryptedData = await this.encrypt(JSON.stringify(this.data)); await fs.writeFile(this.dbPath, encryptedData); } async initialize() { if (this.initialized) return; try { await this.loadData(); this.initialized = true; } catch (error) { console.error("Failed to initialize database:", error); throw error; } } async findUser(walletAddress) { await this.initialize(); const user = this.data.users.find( (u) => u.wallet_address.toLowerCase() === walletAddress.toLowerCase() ); return user || null; } async createUser(userData) { await this.initialize(); const existingUser = await this.findUser(userData.wallet_address); if (existingUser) { throw new Error("User already exists"); } const now = (/* @__PURE__ */ new Date()).toISOString(); const newUser = { ...userData, role: userData.role || "User", account_type: userData.account_type || "human", created_at: now }; this.data.users.push(newUser); await this.saveData(); return newUser; } async updateUser(walletAddress, updates) { await this.initialize(); const index = this.data.users.findIndex( (u) => u.wallet_address.toLowerCase() === walletAddress.toLowerCase() ); if (index === -1) return null; const updatedUser = { ...this.data.users[index], ...updates, wallet_address: this.data.users[index].wallet_address }; this.data.users[index] = updatedUser; await this.saveData(); return updatedUser; } async deleteUser(walletAddress) { await this.initialize(); const initialLength = this.data.users.length; this.data.users = this.data.users.filter( (u) => u.wallet_address.toLowerCase() !== walletAddress.toLowerCase() ); if (this.data.users.length !== initialLength) { await this.saveData(); return true; } return false; } async close() { await this.saveData(); this.initialized = false; } }; // src/server/handlers/wallet-auth.ts var getEnvVariable = (name, fallbackName) => { let value = process.env[name]; if (!value && fallbackName) { value = process.env[fallbackName]; } if (!value) { throw new Error(`Required environment variable ${name}${fallbackName ? ` or ${fallbackName}` : ""} is not set`); } return value; }; var createAuthHandler = (config) => { const db = EncryptedJSONDatabase.getInstance(); let isInitialized = false; const initialize = async () => { if (!isInitialized) { try { await db.initialize(); isInitialized = true; } catch (error) { console.error("Failed to initialize database:", error); } } }; const GET = async () => { try { const API_URL = getEnvVariable("CAPLIB_API_URL"); const API_KEY = getEnvVariable("CAPLIB_API_KEY"); const CONTRACT_ID = getEnvVariable("NEXT_PUBLIC_AUTH_CONTRACT_ID", "AUTH_CONTRACT_ID"); await initialize(); const response = await fetch(`${API_URL}?action=generateNonce&contractId=${CONTRACT_ID}`, { headers: { "X-API-Key": API_KEY } }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); return new Response( JSON.stringify({ nonce: data.nonce }), { status: 200, headers: { "Content-Type": "application/json" } } ); } catch (error) { console.error("Error generating nonce:", error); return new Response( JSON.stringify({ error: "Failed to generate nonce", nonce: "" }), { status: 500, headers: { "Content-Type": "application/json" } } ); } }; const POST = async (req) => { try { const API_URL = getEnvVariable("CAPLIB_API_URL"); const API_KEY = getEnvVariable("CAPLIB_API_KEY"); const CONTRACT_ID = getEnvVariable("NEXT_PUBLIC_AUTH_CONTRACT_ID", "AUTH_CONTRACT_ID"); await initialize(); const body = await req.json(); const { nonce, userData } = body; const verifyResponse = await fetch(API_URL, { method: "POST", headers: { "X-API-Key": API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ action: "verifyAuthentication", contractId: CONTRACT_ID, nonce }) }); if (!verifyResponse.ok) { throw new Error(`Verification failed: ${verifyResponse.status}`); } const verifyData = await verifyResponse.json(); const { authenticated, userAddress } = verifyData; if (!authenticated || !userAddress) { return new Response( JSON.stringify({ authenticated: false, error: "Authentication failed" }), { status: 401, headers: { "Content-Type": "application/json" } } ); } if (config?.customValidation) { const isValid = await config.customValidation(userAddress); if (!isValid) { return new Response( JSON.stringify({ authenticated: false, error: "Custom validation failed" }), { status: 403, headers: { "Content-Type": "application/json" } } ); } } try { let user = await db.findUser(userAddress); if (userData?.role && !isValidRole(userData.role)) { userData.role = getDefaultRole(); } if (!user && userData) { user = await db.createUser({ wallet_address: userAddress, ...userData }); } return new Response( JSON.stringify({ authenticated: true, userAddress, user: user || void 0 }), { status: 200, headers: { "Content-Type": "application/json" } } ); } catch (error) { console.error("Database error:", error); return new Response( JSON.stringify({ authenticated: true, userAddress, error: "Database operation failed" }), { status: 200, headers: { "Content-Type": "application/json" } } ); } } catch (error) { console.error("Error verifying authentication:", error); return new Response( JSON.stringify({ authenticated: false, error: "Authentication verification failed" }), { status: 500, headers: { "Content-Type": "application/json" } } ); } }; return { GET, POST }; }; export { EncryptedJSONDatabase, createAuthHandler }; //# sourceMappingURL=chunk-RW7N3RE6.mjs.map