Files
CRM_Montzar/apps/api/src/public-api/api-keys.service.ts
T

113 lines
3.2 KiB
TypeScript

import {
BadRequestException,
Injectable,
NotFoundException,
UnauthorizedException,
} from '@nestjs/common';
import { createHash, randomBytes } from 'crypto';
import type {
CreateOrganizationApiKeyRequest,
CreateOrganizationApiKeyResponse,
OrganizationApiKeyDto,
} from '@mento/shared';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class ApiKeysService {
constructor(private readonly prisma: PrismaService) {}
async list(organizationId: string): Promise<OrganizationApiKeyDto[]> {
const rows = await this.prisma.organizationApiKey.findMany({
where: { organizationId },
orderBy: { createdAt: 'desc' },
});
return rows.map((row) => this.toDto(row));
}
async create(
organizationId: string,
dto: CreateOrganizationApiKeyRequest,
): Promise<CreateOrganizationApiKeyResponse> {
const name = dto.name?.trim();
if (!name) throw new BadRequestException('نام کلید الزامی است');
const rawKey = `mk_${randomBytes(24).toString('hex')}`;
const prefix = rawKey.slice(0, 10);
const keyHash = this.hashKey(rawKey);
const row = await this.prisma.organizationApiKey.create({
data: {
organizationId,
name,
prefix,
keyHash,
},
});
return { ...this.toDto(row), rawKey };
}
async revoke(organizationId: string, id: string): Promise<OrganizationApiKeyDto> {
const existing = await this.prisma.organizationApiKey.findFirst({
where: { id, organizationId },
});
if (!existing) throw new NotFoundException('کلید پیدا نشد');
if (existing.revokedAt) return this.toDto(existing);
const row = await this.prisma.organizationApiKey.update({
where: { id },
data: { revokedAt: new Date() },
});
return this.toDto(row);
}
async authenticate(
rawKey: string,
): Promise<{ organizationId: string; apiKeyId: string; name: string; role: 'owner' }> {
const key = rawKey.trim();
if (!key.startsWith('mk_') || key.length < 20) {
throw new UnauthorizedException('کلید API نامعتبر است');
}
const keyHash = this.hashKey(key);
const row = await this.prisma.organizationApiKey.findFirst({
where: { keyHash, revokedAt: null },
select: { id: true, organizationId: true, name: true },
});
if (!row) throw new UnauthorizedException('کلید API نامعتبر است');
await this.prisma.organizationApiKey.update({
where: { id: row.id },
data: { lastUsedAt: new Date() },
});
return {
organizationId: row.organizationId,
apiKeyId: row.id,
name: row.name,
role: 'owner',
};
}
hashKey(raw: string): string {
return createHash('sha256').update(raw).digest('hex');
}
private toDto(row: {
id: string;
name: string;
prefix: string;
lastUsedAt: Date | null;
revokedAt: Date | null;
createdAt: Date;
}): OrganizationApiKeyDto {
return {
id: row.id,
name: row.name,
prefix: row.prefix,
lastUsedAt: row.lastUsedAt?.toISOString() ?? null,
revokedAt: row.revokedAt?.toISOString() ?? null,
createdAt: row.createdAt.toISOString(),
};
}
}