fase(9): auth module with casl rbac and session management

This commit is contained in:
debian
2026-03-05 09:57:49 -05:00
parent 39a5e41f75
commit 7526a5bc15
77 changed files with 3588 additions and 41 deletions

View File

@@ -0,0 +1,170 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAuthController = createAuthController;
const express_1 = require("express");
const AuthMiddleware_1 = require("../../application/middleware/AuthMiddleware");
function createAuthController(registerCommand, loginCommand, createOrgCommand, inviteMemberCommand, createApiKeyCommand, getUserQuery, listOrgMembersQuery, sessionRepository, apiKeyRepository, userRepository) {
const router = (0, express_1.Router)();
const authMiddleware = (0, AuthMiddleware_1.createAuthMiddleware)(userRepository, sessionRepository, apiKeyRepository);
// POST /api/auth/register
router.post('/register', async (req, res) => {
const result = await registerCommand.execute({
email: req.body.email,
password: req.body.password,
name: req.body.name,
role: req.body.role,
});
if (!result.ok) {
res.status(400).json({ error: result.error });
return;
}
res.status(201).json(result.value);
});
// POST /api/auth/login
router.post('/login', async (req, res) => {
const result = await loginCommand.execute({
email: req.body.email,
password: req.body.password,
});
if (!result.ok) {
res.status(401).json({ error: result.error });
return;
}
const { sessionToken, expiresAt, ...userData } = result.value;
res.cookie('abe_session', sessionToken, {
httpOnly: true,
secure: process.env['NODE_ENV'] === 'production',
sameSite: 'lax',
expires: expiresAt,
});
res.json({ ...userData, sessionToken });
});
// POST /api/auth/logout
router.post('/logout', authMiddleware, async (req, res) => {
const token = req.cookies?.['abe_session'] ?? req.headers.authorization?.substring(7);
if (token) {
await sessionRepository.deleteByToken(token);
}
res.clearCookie('abe_session');
res.json({ success: true });
});
// GET /api/auth/me
router.get('/me', authMiddleware, async (req, res) => {
const result = await getUserQuery.execute({ userId: req.user.id });
if (!result.ok) {
res.status(404).json({ error: result.error });
return;
}
res.json(result.value);
});
// GET /api/auth/setup-required
router.get('/setup-required', async (_req, res) => {
const count = await userRepository.count();
res.json({ required: count === 0 });
});
// POST /api/auth/setup — first-run setup
router.post('/setup', async (req, res) => {
const count = await userRepository.count();
if (count > 0) {
res.status(400).json({ error: 'Setup already completed' });
return;
}
const registerResult = await registerCommand.execute({
email: req.body.email,
password: req.body.password,
name: req.body.name,
role: 'owner',
});
if (!registerResult.ok) {
res.status(400).json({ error: registerResult.error });
return;
}
const createOrgResult = await createOrgCommand.execute({
name: req.body.orgName ?? 'My Organization',
ownerId: registerResult.value.userId,
});
if (!createOrgResult.ok) {
res.status(400).json({ error: createOrgResult.error });
return;
}
res.status(201).json({
user: registerResult.value,
organization: createOrgResult.value,
});
});
// POST /api/auth/organizations — create org
router.post('/organizations', authMiddleware, async (req, res) => {
const result = await createOrgCommand.execute({
name: req.body.name,
ownerId: req.user.id,
});
if (!result.ok) {
res.status(400).json({ error: result.error });
return;
}
res.status(201).json(result.value);
});
// POST /api/auth/organizations/:orgId/members — invite member
router.post('/organizations/:orgId/members', authMiddleware, async (req, res) => {
const result = await inviteMemberCommand.execute({
orgId: String(req.params['orgId']),
inviterUserId: req.user.id,
email: req.body.email,
role: req.body.role ?? 'member',
});
if (!result.ok) {
res.status(400).json({ error: result.error });
return;
}
res.status(201).json(result.value);
});
// GET /api/auth/organizations/:orgId/members
router.get('/organizations/:orgId/members', authMiddleware, async (req, res) => {
const result = await listOrgMembersQuery.execute({ orgId: String(req.params['orgId']) });
if (!result.ok) {
res.status(404).json({ error: result.error });
return;
}
res.json(result.value);
});
// POST /api/auth/api-keys — create API key
router.post('/api-keys', authMiddleware, async (req, res) => {
const result = await createApiKeyCommand.execute({
userId: req.user.id,
orgId: req.user.orgId ?? 'default',
name: req.body.name,
permissions: req.body.permissions,
expiresAt: req.body.expiresAt ? new Date(req.body.expiresAt) : undefined,
});
if (!result.ok) {
res.status(400).json({ error: result.error });
return;
}
res.status(201).json(result.value);
});
// GET /api/auth/api-keys — list API keys
router.get('/api-keys', authMiddleware, async (req, res) => {
const keys = await apiKeyRepository.listByUser(req.user.id);
res.json(keys.map((k) => ({
id: k.id.toString(),
name: k.name,
keyPrefix: k.keyPrefix,
permissions: k.permissions,
expiresAt: k.expiresAt,
lastUsedAt: k.lastUsedAt,
createdAt: k.createdAt,
})));
});
// DELETE /api/auth/api-keys/:id — revoke API key
router.delete('/api-keys/:id', authMiddleware, async (req, res) => {
const keyId = String(req.params['id']);
const key = await apiKeyRepository.findById(keyId);
if (!key || key.userId !== req.user.id) {
res.status(404).json({ error: 'API key not found' });
return;
}
await apiKeyRepository.delete(keyId);
res.json({ success: true });
});
return router;
}