UNPKG

@gftdcojp/auth

Version:

✅ Enterprise-grade Auth0 integration for GFTD platform - 90% Complete, High Quality Implementation

235 lines 8.53 kB
"use strict"; /** * セッション管理ユーティリティ * * 機能: * - セッションの暗号化/復号化 * - セッション有効期限チェック * - セッション更新 * - セキュアなCookie設定 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.SessionManager = void 0; const jose_1 = require("jose"); const logger_1 = require("./logger"); /** * セッション管理クラス */ class SessionManager { constructor(config) { this.config = config; // セッション暗号化キーを準備 const secret = config.secret; if (!secret || secret.length < 32) { throw new Error('AUTH0_SECRET must be at least 32 characters long'); } this.secretKey = new TextEncoder().encode(secret.slice(0, 32)); } /** * セッションを暗号化してトークンにする */ async encryptSession(session) { try { const jwt = await new jose_1.EncryptJWT({ user: session.user, idToken: session.idToken, accessToken: session.accessToken, refreshToken: session.refreshToken, expiresAt: session.expiresAt, createdAt: session.createdAt, organizationContext: session.organizationContext, }) .setProtectedHeader({ alg: 'dir', enc: 'A256GCM' }) .setIssuedAt() .setExpirationTime(session.expiresAt) .setIssuer('gftd-auth') .setAudience(this.config.appBaseUrl) .encrypt(this.secretKey); return jwt; } catch (error) { logger_1.log.error(`Failed to encrypt session: ${error}`); throw new Error('Session encryption failed'); } } /** * 暗号化されたトークンからセッションを復号化する */ async decryptSession(encryptedToken) { try { const { payload } = await (0, jose_1.jwtDecrypt)(encryptedToken, this.secretKey, { issuer: 'gftd-auth', audience: this.config.appBaseUrl, }); // 有効期限チェック const now = Date.now(); if (payload.expiresAt && typeof payload.expiresAt === 'number' && payload.expiresAt < now) { logger_1.log.warn('Session expired'); return null; } return { user: payload.user, idToken: payload.idToken, accessToken: payload.accessToken, refreshToken: payload.refreshToken, expiresAt: payload.expiresAt, createdAt: payload.createdAt, organizationContext: payload.organizationContext, }; } catch (error) { logger_1.log.error(`Failed to decrypt session: ${error}`); return null; } } /** * リクエストからセッションを取得 */ async getSessionFromRequest(request) { const sessionCookie = request.cookies.get('gftd_session'); if (!sessionCookie?.value) { return null; } return this.decryptSession(sessionCookie.value); } /** * レスポンスにセッションを設定 */ async setSessionInResponse(response, session) { if (session) { const encryptedSession = await this.encryptSession(session); // セキュアなCookie設定 response.cookies.set('gftd_session', encryptedSession, { httpOnly: true, secure: this.config.session?.cookie?.secure ?? (process.env.NODE_ENV === 'production'), sameSite: this.config.session?.cookie?.sameSite ?? 'lax', path: this.config.session?.cookie?.path ?? '/', domain: this.config.session?.cookie?.domain, maxAge: this.config.session?.absoluteLifetime ?? (7 * 24 * 60 * 60), // 7日 }); } else { // セッションクリア response.cookies.delete('gftd_session'); } } /** * セッション更新(ローリングセッション対応) */ async updateSession(session) { const now = Date.now(); // ローリングセッションが有効な場合 if (this.config.session?.rolling) { const rollingDuration = this.config.session.rollingDuration ?? (24 * 60 * 60 * 1000); // 24時間 session.expiresAt = now + rollingDuration; } return session; } /** * セッションが有効かチェック */ isSessionValid(session) { if (!session) return false; const now = Date.now(); // 有効期限チェック if (session.expiresAt < now) { return false; } // 絶対有効期限チェック if (this.config.session?.absoluteLifetime) { const absoluteExpiry = session.createdAt + (this.config.session.absoluteLifetime * 1000); if (absoluteExpiry < now) { return false; } } return true; } /** * 🆕 組織コンテキストでのセッション検証 */ validateOrganizationContext(session, organizationId) { if (!session) return false; // 組織が必須の場合 if (this.config.organization?.requireOrganization && !session.user.organization_id) { return false; } // 特定組織のチェック if (organizationId && session.user.organization_id !== organizationId) { return false; } return true; } /** * セッションからアクセストークンを取得(リフレッシュも含む) */ async getAccessToken(session) { if (!session) return null; // トークンがまだ有効な場合 const tokenExpiry = this.extractTokenExpiry(session.accessToken); const now = Date.now(); if (tokenExpiry && tokenExpiry > now + 60000) { // 1分のバッファ return { accessToken: session.accessToken, session }; } // リフレッシュトークンがある場合はリフレッシュを試行 if (session.refreshToken) { const refreshedTokens = await this.refreshAccessToken(session.refreshToken); if (refreshedTokens) { const updatedSession = { ...session, accessToken: refreshedTokens.accessToken, refreshToken: refreshedTokens.refreshToken || session.refreshToken, expiresAt: now + (refreshedTokens.expiresIn * 1000), }; return { accessToken: refreshedTokens.accessToken, session: updatedSession }; } } return null; } /** * アクセストークンをリフレッシュ */ async refreshAccessToken(refreshToken) { try { const response = await fetch(`https://${this.config.domain}/oauth/token`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ grant_type: 'refresh_token', client_id: this.config.clientId, client_secret: this.config.clientSecret, refresh_token: refreshToken, }), }); if (!response.ok) { throw new Error(`Token refresh failed: ${response.status}`); } const tokens = await response.json(); return { accessToken: tokens.access_token, refreshToken: tokens.refresh_token, expiresIn: tokens.expires_in || 3600, }; } catch (error) { logger_1.log.error(`Failed to refresh access token: ${error}`); return null; } } /** * JWTトークンから有効期限を抽出 */ extractTokenExpiry(token) { try { const payload = JSON.parse(atob(token.split('.')[1])); return payload.exp ? payload.exp * 1000 : null; } catch { return null; } } } exports.SessionManager = SessionManager; //# sourceMappingURL=session-manager.js.map