import { Injectable, Logger, UnauthorizedException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { ApiKey } from '../database/entities/api-key.entity'; import * as crypto from 'crypto'; @Injectable() export class ApiKeysService { private readonly logger = new Logger(ApiKeysService.name); constructor( @InjectRepository(ApiKey) private readonly apiKeyRepo: Repository, ) {} async createApiKey( name: string, expiresDays?: number, ): Promise<{ plainKey: string; entity: ApiKey }> { const prefix = 'pm_'; const randomPart = crypto.randomBytes(24).toString('hex'); // 48 chars hex const plainKey = `${prefix}${randomPart}`; const keyHash = this.hashKey(plainKey); const apiKey = this.apiKeyRepo.create({ name, keyPrefix: prefix, keyHash, expiresAt: expiresDays ? new Date(Date.now() + expiresDays * 24 * 60 * 60 * 1000) : null, }); const savedKey = await this.apiKeyRepo.save(apiKey); return { plainKey, entity: savedKey, }; } async validateKey(plainKey: string): Promise { const keyHash = this.hashKey(plainKey); const apiKey = await this.apiKeyRepo.findOne({ where: { keyHash }, }); if (!apiKey) { throw new UnauthorizedException('Invalid API Key'); } if (apiKey.expiresAt && apiKey.expiresAt < new Date()) { throw new UnauthorizedException('API Key has expired'); } // Update last used timestamp (async, don't wait for it to return response faster) apiKey.lastUsedAt = new Date(); this.apiKeyRepo .save(apiKey) .catch((err) => this.logger.error('Fehler beim Aktualisieren von lastUsedAt', err), ); return apiKey; } async listKeys(): Promise { return this.apiKeyRepo.find({ order: { createdAt: 'DESC' }, }); } async deleteKey(id: string): Promise { await this.apiKeyRepo.delete(id); } private hashKey(key: string): string { return crypto.createHash('sha256').update(key).digest('hex'); } }