UNPKG

@gftdcojp/gftd-orm

Version:

Enterprise-grade real-time data platform with ksqlDB, inspired by Supabase architecture

387 lines 12 kB
/** * 匿名キーシステム - Supabase風の公開キー管理 */ import { createHash } from 'crypto'; import { jwtAuth, AuditLogManager, AuditEventType, AuditLogLevel } from './types'; import { log } from './utils/logger'; /** * キーの種類 */ export var KeyType; (function (KeyType) { KeyType["ANON"] = "anon"; KeyType["SERVICE_ROLE"] = "service_role"; })(KeyType || (KeyType = {})); /** * 匿名キー管理クラス */ export class AnonKeyManager { constructor() { this.keys = new Map(); this.keyPrefix = process.env.GFTD_KEY_PREFIX || 'gftd'; this.initializeDefaultKeys(); } /** * シングルトンインスタンスを取得 */ static getInstance() { if (!AnonKeyManager.instance) { AnonKeyManager.instance = new AnonKeyManager(); } return AnonKeyManager.instance; } /** * デフォルトキーを初期化 */ initializeDefaultKeys() { // 匿名キー(公開可能) const anonKey = this.generateKey(KeyType.ANON); this.keys.set(anonKey, { key: anonKey, type: KeyType.ANON, permissions: [ 'read:public', 'read:authenticated', 'write:authenticated', ], description: 'Public anonymous key for client-side access', createdAt: new Date(), isActive: true, }); // サービスロールキー(非公開) const serviceKey = this.generateKey(KeyType.SERVICE_ROLE); this.keys.set(serviceKey, { key: serviceKey, type: KeyType.SERVICE_ROLE, permissions: [ 'read:*', 'write:*', 'delete:*', 'admin:*', ], description: 'Service role key with full privileges', createdAt: new Date(), isActive: true, }); log.info('Default keys initialized'); log.info(`Anonymous key: ${anonKey}`); log.info(`Service role key: ${serviceKey}`); } /** * キーを生成 */ generateKey(type) { const timestamp = Date.now().toString(); const randomBytes = Math.random().toString(36).substring(2, 15); const hash = createHash('sha256') .update(`${this.keyPrefix}-${type}-${timestamp}-${randomBytes}`) .digest('hex') .substring(0, 32); return `${this.keyPrefix}_${type}_${hash}`; } /** * キーを検証 */ validateKey(key) { const keyConfig = this.keys.get(key); if (!keyConfig) { log.warn(`Invalid key attempted: ${key}`); return null; } if (!keyConfig.isActive) { log.warn(`Inactive key attempted: ${key}`); return null; } if (keyConfig.expiresAt && keyConfig.expiresAt < new Date()) { log.warn(`Expired key attempted: ${key}`); return null; } return keyConfig; } /** * キーからユーザーペイロードを作成 */ createUserFromKey(key, userId) { const keyConfig = this.validateKey(key); if (!keyConfig) { return null; } const userPayload = { sub: userId || `${keyConfig.type}-${Date.now()}`, role: keyConfig.type === KeyType.ANON ? 'anon' : 'service_role', tenant_id: 'default', metadata: { keyType: keyConfig.type, permissions: keyConfig.permissions, }, app_metadata: { provider: 'key', keyId: key, }, user_metadata: {}, }; return userPayload; } /** * キーベースの認証を実行 */ authenticateWithKey(key, userId) { const keyConfig = this.validateKey(key); if (!keyConfig) { AuditLogManager.log({ level: AuditLogLevel.WARN, eventType: AuditEventType.AUTH_FAILED, result: 'FAILURE', message: `Invalid key authentication attempt: ${key}`, details: { key, userId }, }); return { success: false, error: 'Invalid key' }; } const user = this.createUserFromKey(key, userId); if (!user) { return { success: false, error: 'Failed to create user from key' }; } // JWTトークンを生成 const authResult = jwtAuth.authenticate(user); AuditLogManager.log({ level: AuditLogLevel.INFO, eventType: AuditEventType.AUTH_LOGIN, userId: user.sub, result: 'SUCCESS', message: `Key-based authentication successful for ${keyConfig.type} key`, details: { keyType: keyConfig.type, permissions: keyConfig.permissions, }, }); return { success: true, user, token: authResult.accessToken, }; } /** * 権限をチェック */ checkPermission(key, permission) { const keyConfig = this.validateKey(key); if (!keyConfig) { return false; } // ワイルドカード権限をチェック if (keyConfig.permissions.includes('*') || keyConfig.permissions.includes(`${permission.split(':')[0]}:*`)) { return true; } // 特定の権限をチェック return keyConfig.permissions.includes(permission); } /** * 新しいキーを生成 */ createKey(type, permissions, description, expiresAt) { const key = this.generateKey(type); this.keys.set(key, { key, type, permissions, description, createdAt: new Date(), expiresAt, isActive: true, }); log.info(`New ${type} key created: ${key}`); return key; } /** * キーを無効化 */ revokeKey(key) { const keyConfig = this.keys.get(key); if (!keyConfig) { return false; } keyConfig.isActive = false; log.info(`Key revoked: ${key}`); AuditLogManager.log({ level: AuditLogLevel.INFO, eventType: AuditEventType.ADMIN_POLICY_CHANGE, result: 'SUCCESS', message: `Key revoked: ${key}`, details: { keyType: keyConfig.type }, }); return true; } /** * キー一覧を取得 */ listKeys() { return Array.from(this.keys.values()); } /** * 匿名キーを取得 */ getAnonKey() { for (const [key, config] of this.keys.entries()) { if (config.type === KeyType.ANON && config.isActive) { return key; } } return null; } /** * サービスロールキーを取得 */ getServiceRoleKey() { for (const [key, config] of this.keys.entries()) { if (config.type === KeyType.SERVICE_ROLE && config.isActive) { return key; } } return null; } /** * 期限切れキーをクリーンアップ */ cleanupExpiredKeys() { const now = new Date(); let cleanedCount = 0; for (const [key, config] of this.keys.entries()) { if (config.expiresAt && config.expiresAt < now) { config.isActive = false; cleanedCount++; log.info(`Expired key deactivated: ${key}`); } } if (cleanedCount > 0) { log.info(`Cleaned up ${cleanedCount} expired keys`); } } } /** * Express.js ミドルウェア: 匿名キー認証 */ export function anonKeyAuthMiddleware(options = {}) { const keyManager = AnonKeyManager.getInstance(); const { requireAuth = true, requiredPermissions = [] } = options; return (req, res, next) => { // Authorization headerまたはAPI keyヘッダーをチェック const authHeader = req.headers.authorization; const apiKey = req.headers['x-api-key'] || req.headers['apikey']; let key = null; if (authHeader && authHeader.startsWith('Bearer ')) { // JWT トークンの場合は別のミドルウェアで処理 return next(); } else if (apiKey) { key = apiKey; } if (!key) { if (!requireAuth) { return next(); } return res.status(401).json({ error: 'Unauthorized', message: 'API key required', }); } // キーベース認証を実行 const authResult = keyManager.authenticateWithKey(key); if (!authResult.success) { return res.status(401).json({ error: 'Unauthorized', message: authResult.error, }); } // 権限チェック for (const permission of requiredPermissions) { if (!keyManager.checkPermission(key, permission)) { AuditLogManager.log({ level: AuditLogLevel.WARN, eventType: AuditEventType.UNAUTHORIZED_ACCESS, userId: authResult.user?.sub, result: 'FAILURE', message: `Insufficient permissions for ${permission}`, details: { key, permission }, }); return res.status(403).json({ error: 'Forbidden', message: 'Insufficient permissions', }); } } // ユーザー情報をリクエストに追加 req.user = authResult.user; req.apiKey = key; next(); }; } /** * 匿名キーシステムのヘルパー関数 */ export const anonKeySystem = { /** * マネージャーインスタンスを取得 */ manager: () => AnonKeyManager.getInstance(), /** * 匿名キーで認証 */ authenticateAnon: (userId) => { const manager = AnonKeyManager.getInstance(); const anonKey = manager.getAnonKey(); if (!anonKey) { return { success: false, error: 'Anonymous key not found' }; } return manager.authenticateWithKey(anonKey, userId); }, /** * サービスロールキーで認証 */ authenticateService: (userId) => { const manager = AnonKeyManager.getInstance(); const serviceKey = manager.getServiceRoleKey(); if (!serviceKey) { return { success: false, error: 'Service role key not found' }; } return manager.authenticateWithKey(serviceKey, userId); }, /** * 権限チェック */ checkPermission: (key, permission) => { const manager = AnonKeyManager.getInstance(); return manager.checkPermission(key, permission); }, /** * 新しいキーを作成 */ createKey: (type, permissions, description, expiresAt) => { const manager = AnonKeyManager.getInstance(); return manager.createKey(type, permissions, description, expiresAt); }, /** * キーを無効化 */ revokeKey: (key) => { const manager = AnonKeyManager.getInstance(); return manager.revokeKey(key); }, /** * 公開キーを取得 */ getPublicKeys: () => { const manager = AnonKeyManager.getInstance(); return { anonKey: manager.getAnonKey(), // サービスロールキーは公開しない }; }, }; //# sourceMappingURL=anon-key-system.js.map