UNPKG

@gftdcojp/gftd-orm

Version:

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

320 lines 11 kB
"use strict"; /** * JWT認証システム - Supabase風のJWT認証実装 */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.jwtAuth = exports.JwtAuthManager = void 0; exports.jwtAuthMiddleware = jwtAuthMiddleware; const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); const uuid_1 = require("uuid"); const logger_1 = require("./utils/logger"); const audit_log_1 = require("./audit-log"); /** * JWT認証管理クラス */ class JwtAuthManager { constructor() { this.refreshTokenStore = new Map(); this.config = { secret: process.env.GFTD_JWT_SECRET || 'your-super-secret-jwt-key', expiresIn: process.env.GFTD_JWT_EXPIRES_IN || '1h', refreshExpiresIn: process.env.GFTD_JWT_REFRESH_EXPIRES_IN || '7d', issuer: process.env.GFTD_JWT_ISSUER || 'gftd-orm', audience: process.env.GFTD_JWT_AUDIENCE || 'gftd-orm-client', }; } /** * シングルトンインスタンスを取得 */ static getInstance() { if (!JwtAuthManager.instance) { JwtAuthManager.instance = new JwtAuthManager(); } return JwtAuthManager.instance; } /** * アクセストークンを生成 */ generateAccessToken(user) { const payload = { sub: user.sub, email: user.email, role: user.role, tenant_id: user.tenant_id, metadata: user.metadata, app_metadata: user.app_metadata, user_metadata: user.user_metadata, aud: this.config.audience, iss: this.config.issuer, iat: Math.floor(Date.now() / 1000), }; return jsonwebtoken_1.default.sign(payload, this.config.secret, { expiresIn: this.config.expiresIn, }); } /** * リフレッシュトークンを生成 */ generateRefreshToken(userId) { const tokenId = (0, uuid_1.v4)(); const expiresAt = Date.now() + this.parseExpiration(this.config.refreshExpiresIn); // リフレッシュトークンを保存 this.refreshTokenStore.set(tokenId, { userId, expiresAt, }); return tokenId; } /** * 認証トークンのペアを生成 */ generateAuthTokens(user) { const accessToken = this.generateAccessToken(user); const refreshToken = this.generateRefreshToken(user.sub); const decoded = jsonwebtoken_1.default.decode(accessToken); const expiresAt = decoded.exp * 1000; // ミリ秒に変換 const expiresIn = Math.floor((expiresAt - Date.now()) / 1000); logger_1.log.info(`Generated auth tokens for user: ${user.sub}`); audit_log_1.AuditLogManager.logAuthSuccess(user.sub, user.tenant_id || 'default', '', ''); return { accessToken, refreshToken, user, expiresAt, expiresIn, }; } /** * アクセストークンを検証 */ verifyAccessToken(token) { try { const decoded = jsonwebtoken_1.default.verify(token, this.config.secret); return { sub: decoded.sub, email: decoded.email, role: decoded.role, tenant_id: decoded.tenant_id, metadata: decoded.metadata, app_metadata: decoded.app_metadata, user_metadata: decoded.user_metadata, }; } catch (error) { logger_1.log.warn(`Invalid access token: ${error}`); return null; } } /** * リフレッシュトークンを検証 */ verifyRefreshToken(refreshToken) { const tokenData = this.refreshTokenStore.get(refreshToken); if (!tokenData) { logger_1.log.warn(`Invalid refresh token: ${refreshToken}`); return null; } if (Date.now() > tokenData.expiresAt) { logger_1.log.warn(`Expired refresh token: ${refreshToken}`); this.refreshTokenStore.delete(refreshToken); return null; } return tokenData.userId; } /** * リフレッシュトークンを使用してアクセストークンを更新 */ refreshAccessToken(refreshToken, currentUser) { const userId = this.verifyRefreshToken(refreshToken); if (!userId || userId !== currentUser.sub) { audit_log_1.AuditLogManager.logAuthFailure(currentUser.email || '', 'Invalid refresh token', ''); return null; } // 新しいトークンペアを生成 const authResult = this.generateAuthTokens(currentUser); // 古いリフレッシュトークンを削除 this.refreshTokenStore.delete(refreshToken); logger_1.log.info(`Refreshed access token for user: ${currentUser.sub}`); audit_log_1.AuditLogManager.log({ level: audit_log_1.AuditLogLevel.INFO, eventType: audit_log_1.AuditEventType.AUTH_TOKEN_REFRESH, userId: currentUser.sub, tenantId: currentUser.tenant_id || 'default', result: 'SUCCESS', message: `Token refreshed for user ${currentUser.sub}`, }); return authResult; } /** * 匿名ユーザーのトークンを生成 */ generateAnonymousToken(tenantId) { const anonUser = { sub: `anon-${(0, uuid_1.v4)()}`, role: 'anon', tenant_id: tenantId || 'default', metadata: {}, app_metadata: { provider: 'anonymous' }, user_metadata: {}, }; return this.generateAuthTokens(anonUser); } /** * サービスロールトークンを生成 */ generateServiceRoleToken(tenantId) { const serviceUser = { sub: `service-${(0, uuid_1.v4)()}`, role: 'service_role', tenant_id: tenantId || 'default', metadata: {}, app_metadata: { provider: 'service' }, user_metadata: {}, }; return this.generateAuthTokens(serviceUser); } /** * トークンを無効化 */ revokeToken(refreshToken) { this.refreshTokenStore.delete(refreshToken); logger_1.log.info(`Revoked refresh token: ${refreshToken}`); } /** * 期限切れのリフレッシュトークンをクリーンアップ */ cleanupExpiredTokens() { const now = Date.now(); let cleanedCount = 0; for (const [tokenId, tokenData] of this.refreshTokenStore.entries()) { if (now > tokenData.expiresAt) { this.refreshTokenStore.delete(tokenId); cleanedCount++; } } if (cleanedCount > 0) { logger_1.log.info(`Cleaned up ${cleanedCount} expired refresh tokens`); } } /** * 有効期限の文字列をミリ秒に変換 */ parseExpiration(expiration) { const units = { 's': 1000, 'm': 60 * 1000, 'h': 60 * 60 * 1000, 'd': 24 * 60 * 60 * 1000, 'w': 7 * 24 * 60 * 60 * 1000, }; const match = expiration.match(/^(\d+)([smhdw])$/); if (!match) { throw new Error(`Invalid expiration format: ${expiration}`); } const value = parseInt(match[1]); const unit = match[2]; return value * units[unit]; } } exports.JwtAuthManager = JwtAuthManager; /** * Express.js ミドルウェア: JWT認証 */ function jwtAuthMiddleware(options = {}) { const authManager = JwtAuthManager.getInstance(); const { requireAuth = true, allowAnonymous = false, requiredRole } = options; return (req, res, next) => { const authHeader = req.headers.authorization; if (!authHeader) { if (!requireAuth || allowAnonymous) { // 匿名ユーザーとして処理 req.user = { sub: `anon-${(0, uuid_1.v4)()}`, role: 'anon', tenant_id: 'default', }; return next(); } else { return res.status(401).json({ error: 'Unauthorized', message: 'Missing authorization header', }); } } const token = authHeader.replace('Bearer ', ''); const user = authManager.verifyAccessToken(token); if (!user) { audit_log_1.AuditLogManager.logAuthFailure('', 'Invalid access token', req.ip); return res.status(401).json({ error: 'Unauthorized', message: 'Invalid access token', }); } // ロールチェック if (requiredRole && user.role !== requiredRole) { audit_log_1.AuditLogManager.logSecurityViolation(user.sub, user.tenant_id, 'INSUFFICIENT_ROLE', { required: requiredRole, actual: user.role }); return res.status(403).json({ error: 'Forbidden', message: 'Insufficient role', }); } req.user = user; next(); }; } /** * JWT認証のヘルパー関数 */ exports.jwtAuth = { /** * 認証マネージャーのインスタンスを取得 */ manager: () => JwtAuthManager.getInstance(), /** * ユーザーを認証してトークンを発行 */ authenticate: (user) => { const authManager = JwtAuthManager.getInstance(); return authManager.generateAuthTokens(user); }, /** * 匿名認証トークンを発行 */ authenticateAnonymous: (tenantId) => { const authManager = JwtAuthManager.getInstance(); return authManager.generateAnonymousToken(tenantId); }, /** * サービスロール認証トークンを発行 */ authenticateServiceRole: (tenantId) => { const authManager = JwtAuthManager.getInstance(); return authManager.generateServiceRoleToken(tenantId); }, /** * トークンを検証 */ verify: (token) => { const authManager = JwtAuthManager.getInstance(); return authManager.verifyAccessToken(token); }, /** * トークンをリフレッシュ */ refresh: (refreshToken, currentUser) => { const authManager = JwtAuthManager.getInstance(); return authManager.refreshAccessToken(refreshToken, currentUser); }, /** * トークンを無効化 */ revoke: (refreshToken) => { const authManager = JwtAuthManager.getInstance(); authManager.revokeToken(refreshToken); }, }; //# sourceMappingURL=jwt-auth.js.map