@gftdcojp/auth
Version:
✅ Enterprise-grade Auth0 integration for GFTD platform - 90% Complete, High Quality Implementation
606 lines • 25.4 kB
JavaScript
;
/**
* 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