manicanldes-backend/src/identity/access.store.ts

45 lines
1.5 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { DatabaseService } from '../database/database.service';
import { Prisma } from '../generated/prisma/client';
import { readPrincipal } from './session.store';
import { AppError } from '../common/errors/app-error';
import type { Principal } from './identity.types';
import type { Permission } from './permissions';
@Injectable()
export class AccessStore {
constructor(private readonly db: DatabaseService) {}
async mutate<T>(
actor: Principal,
permission: Permission | null,
work: (tx: Prisma.TransactionClient, current: Principal) => Promise<T>,
lockOrganization = true,
): Promise<T> {
try {
return await this.db.$transaction(async (tx) => {
if (lockOrganization) {
await tx.$queryRaw`SELECT id FROM organizations WHERE id = ${actor.organizationId}::uuid FOR UPDATE`;
}
const current = await readPrincipal(tx, actor.sessionId);
if (
current.organizationId !== actor.organizationId ||
current.userId !== actor.userId
) {
throw new AppError('SCOPE_DENIED');
}
if (permission && !current.permissions.includes(permission))
throw new AppError('ACCESS_DENIED');
return work(tx, current);
});
} catch (error) {
if (
error instanceof Prisma.PrismaClientKnownRequestError &&
error.code === 'P2002'
) {
throw new AppError('RECORD_CONFLICT');
}
throw error;
}
}
}