@gftdcojp/gftd-orm
Version:
Enterprise-grade real-time data platform with ksqlDB, inspired by Supabase architecture
320 lines • 11 kB
JavaScript
;
/**
* 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