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,75 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.KyselyApiKeyRepository = void 0;
const ApiKey_1 = require("../../domain/entities/ApiKey");
const UniqueId_1 = require("../../../../shared/domain/UniqueId");
class KyselyApiKeyRepository {
constructor(db) {
this.db = db;
}
async save(apiKey) {
await this.db
.insertInto('api_keys')
.values({
id: apiKey.id.toString(),
user_id: apiKey.userId,
org_id: apiKey.orgId,
name: apiKey.name,
key_hash: apiKey.keyHash,
key_prefix: apiKey.keyPrefix,
permissions: JSON.stringify(apiKey.permissions),
expires_at: apiKey.expiresAt ? apiKey.expiresAt.getTime() : null,
last_used_at: apiKey.lastUsedAt ? apiKey.lastUsedAt.getTime() : null,
created_at: apiKey.createdAt.getTime(),
})
.execute();
}
async findById(id) {
const row = await this.db
.selectFrom('api_keys')
.selectAll()
.where('id', '=', id)
.executeTakeFirst();
return row ? this.toDomain(row) : undefined;
}
async findByHash(keyHash) {
const row = await this.db
.selectFrom('api_keys')
.selectAll()
.where('key_hash', '=', keyHash)
.executeTakeFirst();
return row ? this.toDomain(row) : undefined;
}
async listByUser(userId) {
const rows = await this.db
.selectFrom('api_keys')
.selectAll()
.where('user_id', '=', userId)
.execute();
return rows.map((r) => this.toDomain(r));
}
async delete(id) {
await this.db.deleteFrom('api_keys').where('id', '=', id).execute();
}
async updateLastUsed(id, lastUsedAt) {
await this.db
.updateTable('api_keys')
.set({ last_used_at: lastUsedAt.getTime() })
.where('id', '=', id)
.execute();
}
toDomain(row) {
return ApiKey_1.ApiKey.reconstitute({
userId: row.user_id,
orgId: row.org_id,
name: row.name,
keyHash: row.key_hash,
keyPrefix: row.key_prefix,
permissions: JSON.parse(row.permissions),
expiresAt: row.expires_at ? new Date(row.expires_at) : undefined,
lastUsedAt: row.last_used_at ? new Date(row.last_used_at) : undefined,
createdAt: new Date(row.created_at),
}, UniqueId_1.UniqueId.from(row.id));
}
}
exports.KyselyApiKeyRepository = KyselyApiKeyRepository;

View File

@@ -0,0 +1,98 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.KyselyOrganizationRepository = void 0;
const Organization_1 = require("../../domain/entities/Organization");
const UniqueId_1 = require("../../../../shared/domain/UniqueId");
class KyselyOrganizationRepository {
constructor(db) {
this.db = db;
}
async save(org) {
await this.db
.insertInto('organizations')
.values({
id: org.id.toString(),
name: org.name,
slug: org.slug,
created_at: org.createdAt.getTime(),
})
.onConflict((oc) => oc.column('id').doUpdateSet({ name: org.name }))
.execute();
}
async findById(id) {
const row = await this.db
.selectFrom('organizations')
.selectAll()
.where('id', '=', id)
.executeTakeFirst();
return row ? this.toDomain(row) : undefined;
}
async findBySlug(slug) {
const row = await this.db
.selectFrom('organizations')
.selectAll()
.where('slug', '=', slug)
.executeTakeFirst();
return row ? this.toDomain(row) : undefined;
}
async findAll() {
const rows = await this.db.selectFrom('organizations').selectAll().execute();
return rows.map((r) => this.toDomain(r));
}
async addMember(member) {
await this.db
.insertInto('org_members')
.values({
id: member.id,
org_id: member.orgId,
user_id: member.userId,
role: member.role,
joined_at: member.joinedAt.getTime(),
})
.execute();
}
async getMember(orgId, userId) {
const row = await this.db
.selectFrom('org_members')
.selectAll()
.where('org_id', '=', orgId)
.where('user_id', '=', userId)
.executeTakeFirst();
return row
? { id: row.id, orgId: row.org_id, userId: row.user_id, role: row.role, joinedAt: new Date(row.joined_at) }
: undefined;
}
async listMembers(orgId) {
const rows = await this.db
.selectFrom('org_members')
.selectAll()
.where('org_id', '=', orgId)
.execute();
return rows.map((r) => ({
id: r.id,
orgId: r.org_id,
userId: r.user_id,
role: r.role,
joinedAt: new Date(r.joined_at),
}));
}
async updateMemberRole(orgId, userId, role) {
await this.db
.updateTable('org_members')
.set({ role })
.where('org_id', '=', orgId)
.where('user_id', '=', userId)
.execute();
}
async removeMember(orgId, userId) {
await this.db
.deleteFrom('org_members')
.where('org_id', '=', orgId)
.where('user_id', '=', userId)
.execute();
}
toDomain(row) {
return Organization_1.Organization.reconstitute({ name: row.name, slug: row.slug, createdAt: new Date(row.created_at) }, UniqueId_1.UniqueId.from(row.id));
}
}
exports.KyselyOrganizationRepository = KyselyOrganizationRepository;

View File

@@ -0,0 +1,46 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.KyselySessionRepository = void 0;
class KyselySessionRepository {
constructor(db) {
this.db = db;
}
async save(session) {
await this.db
.insertInto('auth_sessions')
.values({
id: session.id,
user_id: session.userId,
token: session.token,
expires_at: session.expiresAt.getTime(),
created_at: session.createdAt.getTime(),
})
.execute();
}
async findByToken(token) {
const row = await this.db
.selectFrom('auth_sessions')
.selectAll()
.where('token', '=', token)
.executeTakeFirst();
if (!row)
return undefined;
return {
id: row.id,
userId: row.user_id,
token: row.token,
expiresAt: new Date(row.expires_at),
createdAt: new Date(row.created_at),
};
}
async deleteByToken(token) {
await this.db.deleteFrom('auth_sessions').where('token', '=', token).execute();
}
async deleteExpired() {
await this.db
.deleteFrom('auth_sessions')
.where('expires_at', '<', Date.now())
.execute();
}
}
exports.KyselySessionRepository = KyselySessionRepository;

View File

@@ -0,0 +1,73 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.KyselyUserRepository = void 0;
const User_1 = require("../../domain/entities/User");
const UniqueId_1 = require("../../../../shared/domain/UniqueId");
const Email_1 = require("../../domain/value-objects/Email");
const Role_1 = require("../../domain/value-objects/Role");
class KyselyUserRepository {
constructor(db) {
this.db = db;
}
async save(user) {
const row = {
id: user.id.toString(),
email: user.email.value,
name: user.name,
password_hash: user.passwordHash,
role: user.role.value,
org_id: user.orgId ?? null,
created_at: user.createdAt.getTime(),
updated_at: user.updatedAt.getTime(),
};
await this.db
.insertInto('users')
.values(row)
.onConflict((oc) => oc.column('id').doUpdateSet({
name: row.name,
role: row.role,
org_id: row.org_id,
updated_at: row.updated_at,
}))
.execute();
}
async findById(id) {
const row = await this.db
.selectFrom('users')
.selectAll()
.where('id', '=', id)
.executeTakeFirst();
return row ? this.toDomain(row) : undefined;
}
async findByEmail(email) {
const row = await this.db
.selectFrom('users')
.selectAll()
.where('email', '=', email.toLowerCase())
.executeTakeFirst();
return row ? this.toDomain(row) : undefined;
}
async findAll() {
const rows = await this.db.selectFrom('users').selectAll().execute();
return rows.map((r) => this.toDomain(r));
}
async count() {
const result = await this.db
.selectFrom('users')
.select((eb) => eb.fn.count('id').as('count'))
.executeTakeFirstOrThrow();
return Number(result.count);
}
toDomain(row) {
return User_1.User.reconstitute({
email: Email_1.Email.create(row.email),
name: row.name,
passwordHash: row.password_hash,
role: Role_1.Role.create(row.role),
orgId: row.org_id ?? undefined,
createdAt: new Date(row.created_at),
updatedAt: new Date(row.updated_at),
}, UniqueId_1.UniqueId.from(row.id));
}
}
exports.KyselyUserRepository = KyselyUserRepository;