UNPKG

@gftdcojp/auth

Version:

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

606 lines 25.4 kB
"use strict"; /** * Auth0統合管理 * * ⚡ 機能: * - JWT Token検証とクレーム解析 * - Management API(組織、ユーザー、ロール管理) * - Authorization Extension API(グループ、権限) * - Standard Auth0 Authentication Flow * - セキュリティログとAudit Trail * - 🆕 Auth0 Organizations(orgid)完全対応 * * 🔐 統一認証: auth.gftd.ai ドメインをデフォルトに設定 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.auth0 = exports.Auth0Integration = void 0; const types_1 = require("./types"); const jose_1 = require("jose"); const logger_1 = require("./utils/logger"); /** * Auth0統合マネージャー */ class Auth0Integration { constructor() { this.extensionAccessToken = null; this.extensionTokenExpiry = 0; /** 🆕 Management APIトークンキャッシュ */ this.managementAccessToken = null; this.managementTokenExpiry = 0; // 🔐 統一認証ドメイン設定: auth.gftd.ai をデフォルトに const authDomain = process.env.GFTD_AUTH0_DOMAIN || process.env.AUTH0_DOMAIN || 'auth.gftd.ai'; this.config = { domain: authDomain, audience: process.env.GFTD_AUTH0_AUDIENCE || process.env.AUTH0_AUDIENCE || `https://${authDomain}/api/v2/`, clientId: process.env.GFTD_AUTH0_CLIENT_ID || process.env.AUTH0_CLIENT_ID || 'k0ziPQ6IkDxE1AUSvzx5PwXtnf4y81x0', jwksUri: process.env.GFTD_AUTH0_JWKS_URI || process.env.AUTH0_JWKS_URI, // 🔐 NEW: Authorization Extension設定 authorizationExtension: { url: this.buildExtensionUrl(authDomain, process.env.GFTD_AUTH0_REGION || 'us-west'), clientId: process.env.GFTD_AUTH0_EXT_CLIENT_ID || '', clientSecret: process.env.GFTD_AUTH0_EXT_CLIENT_SECRET || '', audience: process.env.GFTD_AUTH0_EXT_AUDIENCE || 'auth0-authorization-extension-api', region: process.env.GFTD_AUTH0_REGION || 'us-west', }, }; // JWKS URIが指定されていない場合はドメインから自動生成 if (!this.config.jwksUri) { this.config.jwksUri = `https://${this.config.domain}/.well-known/jwks.json`; } // JWKSクライアントを初期化(シンプル設定で互換性確保) const jwksUri = this.config.jwksUri || `https://${this.config.domain}/.well-known/jwks.json`; this.jwksClient = (0, jose_1.createRemoteJWKSet)(new URL(jwksUri)); logger_1.log.info(`Auth0 Integration initialized with domain: ${this.config.domain}`); } /** * シングルトンインスタンスを取得 */ static getInstance(customConfig) { if (!Auth0Integration.instance) { Auth0Integration.instance = new Auth0Integration(); } // カスタム設定で上書き if (customConfig) { Auth0Integration.instance.config = { ...Auth0Integration.instance.config, ...customConfig, }; } return Auth0Integration.instance; } /** * Authorization Extension URLを構築 */ buildExtensionUrl(domain, region) { const regionMap = { 'us-west': 'us', 'europe': 'eu', 'australia': 'au', }; const regionCode = regionMap[region] || 'us'; return `https://${domain.replace('.auth0.com', '')}.${regionCode}.webtask.io/adf6e2f2b84784b57522e3b19dfc9201`; } /** * Extension APIのアクセストークンを取得 */ async getExtensionAccessToken() { const { authorizationExtension } = this.config; if (!authorizationExtension?.clientId || !authorizationExtension?.clientSecret) { logger_1.log.warn('Authorization Extension credentials not configured'); return null; } // キャッシュされたトークンが有効な場合はそれを返す if (this.extensionAccessToken && Date.now() < this.extensionTokenExpiry - 60000) { return this.extensionAccessToken; } try { const response = await fetch(`https://${this.config.domain}/oauth/token`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ client_id: authorizationExtension.clientId, client_secret: authorizationExtension.clientSecret, audience: authorizationExtension.audience, grant_type: 'client_credentials', }), }); if (!response.ok) { throw new Error(`Failed to get extension token: ${response.status}`); } const tokenData = await response.json(); this.extensionAccessToken = tokenData.access_token; this.extensionTokenExpiry = Date.now() + (tokenData.expires_in * 1000); logger_1.log.info('Extension access token obtained successfully'); return this.extensionAccessToken; } catch (error) { logger_1.log.error(`Failed to get extension access token: ${error}`); return null; } } /** * Management APIのアクセストークンを取得 */ async getManagementAccessToken() { // キャッシュされたトークンが有効な場合はそれを返す if (this.managementAccessToken && Date.now() < this.managementTokenExpiry - 60000) { return this.managementAccessToken; } try { const response = await fetch(`https://${this.config.domain}/oauth/token`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ client_id: this.config.clientId, client_secret: process.env.GFTD_AUTH0_CLIENT_SECRET || process.env.AUTH0_CLIENT_SECRET, audience: `https://${this.config.domain}/api/v2/`, grant_type: 'client_credentials', }), }); if (!response.ok) { throw new Error(`Failed to get management token: ${response.status}`); } const tokenData = await response.json(); this.managementAccessToken = tokenData.access_token; this.managementTokenExpiry = Date.now() + (tokenData.expires_in * 1000); return this.managementAccessToken; } catch (error) { logger_1.log.error(`Failed to get management access token: ${error}`); return null; } } /** * Auth0 JWTトークンを検証 */ async verifyAuth0Token(token) { try { const { payload } = await (0, jose_1.jwtVerify)(token, this.jwksClient, { issuer: `https://${this.config.domain}/`, audience: this.config.audience, }); return payload; } catch (error) { logger_1.log.error(`Auth0 token verification failed: ${error}`); return null; } } /** * Auth0クレームをGFTD ORMユーザーペイロードに変換(🆕 組織対応) */ mapAuth0ToUserPayload(auth0Claims) { // カスタムクレームからロールとテナントIDを取得 const roles = auth0Claims['https://your-app.com/roles'] || []; const permissions = auth0Claims['https://your-app.com/permissions'] || []; const tenantId = auth0Claims['https://your-app.com/tenant_id'] || 'default'; // 🆕 組織関連クレーム const organizationId = auth0Claims.org_id; const organizationName = auth0Claims.org_name; const organizationRoles = auth0Claims['https://your-app.com/org_roles'] || []; const organizationPermissions = auth0Claims['https://your-app.com/org_permissions'] || []; const organizations = auth0Claims['https://your-app.com/organizations'] || []; // ロールの決定(最初に見つかったロールを使用) let userRole = 'authenticated'; if (roles.includes('admin') || roles.includes('service_role')) { userRole = 'service_role'; } else if (roles.includes('user') || auth0Claims.email_verified) { userRole = 'authenticated'; } const userPayload = { sub: auth0Claims.sub, email: auth0Claims.email, role: userRole, tenant_id: tenantId, organization_id: organizationId, // 🆕 metadata: { auth0_user_id: auth0Claims.sub, email_verified: auth0Claims.email_verified, name: auth0Claims.name, picture: auth0Claims.picture, nickname: auth0Claims.nickname, roles, permissions, // 🆕 組織関連メタデータ organization: organizationId && organizationName ? { id: organizationId, name: organizationName, display_name: organizationName, } : undefined, organization_roles: organizationRoles, organization_permissions: organizationPermissions, }, app_metadata: { provider: 'auth0', domain: this.config.domain, client_id: this.config.clientId, // 🆕 組織関連アプリメタデータ organization_id: organizationId, organizations, }, user_metadata: { email: auth0Claims.email, name: auth0Claims.name, picture: auth0Claims.picture, }, }; return userPayload; } /** * Auth0トークンからGFTD ORMユーザーを認証 */ async authenticateWithAuth0(token) { try { // Auth0トークンを検証 const auth0Claims = await this.verifyAuth0Token(token); if (!auth0Claims) { return { success: false, error: 'Invalid Auth0 token', }; } // GFTD ORMユーザーペイロードに変換 const user = this.mapAuth0ToUserPayload(auth0Claims); // 監査ログ記録 types_1.AuditLogManager.log({ level: types_1.AuditLogLevel.INFO, eventType: types_1.AuditEventType.AUTH_LOGIN, userId: user.sub, tenantId: user.tenant_id, organizationId: user.organization_id, // 🆕 result: 'SUCCESS', message: `Auth0 authentication successful for user ${user.sub}`, details: { auth0_domain: this.config.domain, email: user.email, roles: user.metadata?.roles, organization_id: user.organization_id, organization_roles: user.metadata?.organization_roles, }, }); logger_1.log.info(`Auth0 authentication successful for user: ${user.sub}, org: ${user.organization_id}`); return { success: true, user, }; } catch (error) { logger_1.log.error(`Auth0 authentication failed: ${error}`); types_1.AuditLogManager.log({ level: types_1.AuditLogLevel.ERROR, eventType: types_1.AuditEventType.AUTH_FAILED, result: 'FAILURE', message: `Auth0 authentication failed: ${error}`, details: { token: token.substring(0, 50) + '...' }, }); return { success: false, error: String(error), }; } } // 🆕 ============ 組織管理メソッド ============ /** * 組織を取得 */ async getOrganization(organizationId) { try { const accessToken = await this.getManagementAccessToken(); if (!accessToken) { throw new Error('Failed to get management access token'); } const response = await fetch(`https://${this.config.domain}/api/v2/organizations/${organizationId}`, { headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, }); if (!response.ok) { if (response.status === 404) { return null; } throw new Error(`Failed to get organization: ${response.status}`); } const organization = await response.json(); logger_1.log.info(`Retrieved organization: ${organizationId}`); return organization; } catch (error) { logger_1.log.error(`Failed to get organization ${organizationId}: ${error}`); return null; } } /** * ユーザーの組織一覧を取得 */ async getUserOrganizations(userId) { try { const accessToken = await this.getManagementAccessToken(); if (!accessToken) { throw new Error('Failed to get management access token'); } const response = await fetch(`https://${this.config.domain}/api/v2/users/${userId}/organizations`, { headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, }); if (!response.ok) { throw new Error(`Failed to get user organizations: ${response.status}`); } const organizations = await response.json(); logger_1.log.info(`Retrieved ${organizations.length} organizations for user: ${userId}`); return organizations; } catch (error) { logger_1.log.error(`Failed to get organizations for user ${userId}: ${error}`); return []; } } /** * 組織メンバー一覧を取得 */ async getOrganizationMembers(organizationId) { try { const accessToken = await this.getManagementAccessToken(); if (!accessToken) { throw new Error('Failed to get management access token'); } const response = await fetch(`https://${this.config.domain}/api/v2/organizations/${organizationId}/members`, { headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, }); if (!response.ok) { throw new Error(`Failed to get organization members: ${response.status}`); } const members = await response.json(); logger_1.log.info(`Retrieved ${members.length} members for organization: ${organizationId}`); return members; } catch (error) { logger_1.log.error(`Failed to get members for organization ${organizationId}: ${error}`); return []; } } /** * 組織にメンバーを追加 */ async addOrganizationMember(organizationId, userId, roles) { try { const accessToken = await this.getManagementAccessToken(); if (!accessToken) { throw new Error('Failed to get management access token'); } const response = await fetch(`https://${this.config.domain}/api/v2/organizations/${organizationId}/members`, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ users: [userId], roles: roles || [], }), }); if (!response.ok) { throw new Error(`Failed to add organization member: ${response.status}`); } // 監査ログ記録 types_1.AuditLogManager.log({ level: types_1.AuditLogLevel.INFO, eventType: types_1.AuditEventType.ORG_MEMBER_ADDED, userId, organizationId, result: 'SUCCESS', message: `User ${userId} added to organization ${organizationId}`, details: { roles }, }); logger_1.log.info(`Added user ${userId} to organization ${organizationId}`); return true; } catch (error) { logger_1.log.error(`Failed to add user ${userId} to organization ${organizationId}: ${error}`); types_1.AuditLogManager.log({ level: types_1.AuditLogLevel.ERROR, eventType: types_1.AuditEventType.ORG_MEMBER_ADDED, userId, organizationId, result: 'FAILURE', message: `Failed to add user ${userId} to organization ${organizationId}: ${error}`, }); return false; } } /** * 組織からメンバーを削除 */ async removeOrganizationMember(organizationId, userId) { try { const accessToken = await this.getManagementAccessToken(); if (!accessToken) { throw new Error('Failed to get management access token'); } const response = await fetch(`https://${this.config.domain}/api/v2/organizations/${organizationId}/members`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ users: [userId], }), }); if (!response.ok) { throw new Error(`Failed to remove organization member: ${response.status}`); } // 監査ログ記録 types_1.AuditLogManager.log({ level: types_1.AuditLogLevel.INFO, eventType: types_1.AuditEventType.ORG_MEMBER_REMOVED, userId, organizationId, result: 'SUCCESS', message: `User ${userId} removed from organization ${organizationId}`, }); logger_1.log.info(`Removed user ${userId} from organization ${organizationId}`); return true; } catch (error) { logger_1.log.error(`Failed to remove user ${userId} from organization ${organizationId}: ${error}`); types_1.AuditLogManager.log({ level: types_1.AuditLogLevel.ERROR, eventType: types_1.AuditEventType.ORG_MEMBER_REMOVED, userId, organizationId, result: 'FAILURE', message: `Failed to remove user ${userId} from organization ${organizationId}: ${error}`, }); return false; } } /** * 組織招待を送信 */ async createOrganizationInvitation(organizationId, email, options) { try { const accessToken = await this.getManagementAccessToken(); if (!accessToken) { throw new Error('Failed to get management access token'); } const response = await fetch(`https://${this.config.domain}/api/v2/organizations/${organizationId}/invitations`, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ invitee: { email }, inviter: { name: 'GFTD System' }, client_id: this.config.clientId, roles: options?.roles || [], send_invitation_email: options?.sendEmail ?? true, ttl_sec: options?.ttlSec || 7 * 24 * 60 * 60, // 7日 app_metadata: options?.metadata || {}, }), }); if (!response.ok) { throw new Error(`Failed to create organization invitation: ${response.status}`); } const invitation = await response.json(); // 監査ログ記録 types_1.AuditLogManager.log({ level: types_1.AuditLogLevel.INFO, eventType: types_1.AuditEventType.ORG_INVITATION_SENT, organizationId, result: 'SUCCESS', message: `Organization invitation sent to ${email} for organization ${organizationId}`, details: { invitation_id: invitation.id, roles: options?.roles, ttl_sec: options?.ttlSec, }, }); logger_1.log.info(`Created organization invitation ${invitation.id} for ${email}`); return invitation; } catch (error) { logger_1.log.error(`Failed to create organization invitation for ${email}: ${error}`); types_1.AuditLogManager.log({ level: types_1.AuditLogLevel.ERROR, eventType: types_1.AuditEventType.ORG_INVITATION_SENT, organizationId, result: 'FAILURE', message: `Failed to send organization invitation to ${email}: ${error}`, }); return null; } } /** * 組織招待一覧を取得 */ async getOrganizationInvitations(organizationId) { try { const accessToken = await this.getManagementAccessToken(); if (!accessToken) { throw new Error('Failed to get management access token'); } const response = await fetch(`https://${this.config.domain}/api/v2/organizations/${organizationId}/invitations`, { headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, }); if (!response.ok) { throw new Error(`Failed to get organization invitations: ${response.status}`); } const invitations = await response.json(); logger_1.log.info(`Retrieved ${invitations.length} invitations for organization: ${organizationId}`); return invitations; } catch (error) { logger_1.log.error(`Failed to get invitations for organization ${organizationId}: ${error}`); return []; } } } exports.Auth0Integration = Auth0Integration; /** * Auth0統合のヘルパー関数 */ exports.auth0 = { /** * 統合マネージャーのインスタンスを取得 */ manager: () => Auth0Integration.getInstance(), /** * Auth0トークンで認証 */ authenticate: async (token, customConfig) => { const manager = Auth0Integration.getInstance(customConfig); return manager.authenticateWithAuth0(token); }, /** * トークン検証 */ verifyToken: async (token) => { const manager = Auth0Integration.getInstance(); return manager.verifyAuth0Token(token); }, /** * 🆕 組織管理のヘルパー関数 */ organizations: { get: async (organizationId) => { const manager = Auth0Integration.getInstance(); return manager.getOrganization(organizationId); }, getUserOrganizations: async (userId) => { const manager = Auth0Integration.getInstance(); return manager.getUserOrganizations(userId); }, getMembers: async (organizationId) => { const manager = Auth0Integration.getInstance(); return manager.getOrganizationMembers(organizationId); }, addMember: async (organizationId, userId, roles) => { const manager = Auth0Integration.getInstance(); return manager.addOrganizationMember(organizationId, userId, roles); }, removeMember: async (organizationId, userId) => { const manager = Auth0Integration.getInstance(); return manager.removeOrganizationMember(organizationId, userId); }, createInvitation: async (organizationId, email, options) => { const manager = Auth0Integration.getInstance(); return manager.createOrganizationInvitation(organizationId, email, options); }, getInvitations: async (organizationId) => { const manager = Auth0Integration.getInstance(); return manager.getOrganizationInvitations(organizationId); }, }, }; //# sourceMappingURL=auth0-integration.js.map