feat(security): implement dynamic tactical license gate, instant key revocation, and clean hardcoded credentials

This commit is contained in:
Hamza-Ayed
2026-08-18 13:46:02 +03:00
parent 8375773898
commit cb384765fb
17 changed files with 439 additions and 38 deletions
+5 -4
View File
@@ -30,9 +30,10 @@ export class AuthModule implements OnModuleInit {
* دمج مفتاح الأمان الافتراضي من الإعدادات لمنع توقف الرقابة الحالية
*/
async onModuleInit() {
const defaultKey = this.configService.get<string>('MAP_API_KEY') || 'zP9vL5mK2nQ8xR7jT4wS1yB6hG3fV0cX';
await this.authService.seedDefaultKey('Default System', 'admin@intaleq.xyz', defaultKey);
await this.authService.seedDefaultKey('Default Fallback', 'support@intaleq.xyz', 'intaleq_secret_2026');
console.log('✅ System API Keys seeded successfully');
const defaultKey = this.configService.get<string>('MAP_API_KEY');
if (defaultKey) {
await this.authService.seedDefaultKey('Default System', 'admin@intaleq.xyz', defaultKey);
console.log('✅ System API Key from environment seeded successfully');
}
}
}
+20 -5
View File
@@ -1,4 +1,4 @@
import { Injectable, UnauthorizedException, Logger, NotFoundException, ConflictException } from '@nestjs/common';
import { Injectable, UnauthorizedException, Logger, NotFoundException, ConflictException, ForbiddenException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { createHash } from 'crypto';
@@ -126,6 +126,21 @@ export class AuthService {
}
}
/**
* Delete / Revoke an API key for a tenant with strict IDOR verification
*/
async deleteApiKey(tenantId: string, keyId: string): Promise<void> {
const key = await this.apiKeyRepository.findOne({ where: { id: keyId } });
if (!key) {
throw new NotFoundException('API Key not found');
}
if (key.tenantId !== tenantId) {
throw new ForbiddenException('Access denied: You cannot delete an API key belonging to another tenant');
}
await this.redisService.del(`auth:apikey:${key.key}`);
await this.apiKeyRepository.delete(keyId);
}
/**
* Fetch all API keys for a specific tenant
*/
@@ -141,16 +156,16 @@ export class AuthService {
*/
async createApiKey(tenantId: string, name: string, rateLimit?: number, allowedOrigins?: string[]): Promise<ApiKey> {
const existingKeysCount = await this.apiKeyRepository.count({ where: { tenantId } });
if (existingKeysCount >= 1) {
throw new ConflictException('Limit reached: Only 1 API key allowed per developer currently.');
if (existingKeysCount >= 5) {
throw new ConflictException('Limit reached: Maximum 5 API keys allowed per tenant.');
}
const key = `in_${createHash('md5').update(Math.random().toString()).digest('hex').substring(0, 24)}`;
const key = `in_${createHash('sha256').update(tenantId + Date.now().toString() + Math.random().toString()).digest('hex').substring(0, 28)}`;
const apiKey = this.apiKeyRepository.create({
key,
secretHash: this.hashSecret(key),
name,
name: name || 'Production Key',
tenantId,
rateLimit: rateLimit || 100,
allowedOrigins: allowedOrigins || [],
+42 -1
View File
@@ -1,4 +1,4 @@
import { Controller, Get, Post, Body, Param, UseGuards, Req, ForbiddenException } from '@nestjs/common';
import { Controller, Get, Post, Delete, Body, Param, UseGuards, Req, ForbiddenException } from '@nestjs/common';
import { AuthService } from './auth.service';
import { CreateKeyDto } from './dto/management/create-key.dto';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
@@ -30,6 +30,21 @@ export class TenantController {
);
}
@Post('keys/:tenantId')
@ApiOperation({ summary: 'Create a new API key for specified tenant (IDOR Protected)' })
async createKeyForTenant(
@Req() req: any,
@Param('tenantId') tenantId: string,
@Body() dto: CreateKeyDto
) {
// IDOR Protection: Tenant can only create keys for their own tenant ID
if (req.tenant.id !== tenantId) {
throw new ForbiddenException('Access denied: Cannot create API keys for another tenant.');
}
return this.createKey(req, dto);
}
@Get('keys')
@ApiOperation({ summary: 'Get all API keys for the authenticated tenant' })
async getKeys(@Req() req: any) {
@@ -37,9 +52,35 @@ export class TenantController {
return this.authService.getApiKeys(tenantId);
}
@Get('keys/:tenantId')
@ApiOperation({ summary: 'Get all API keys for specified tenant (IDOR Protected)' })
async getKeysForTenant(
@Req() req: any,
@Param('tenantId') tenantId: string
) {
// IDOR Protection: Tenant can only access their own keys
if (req.tenant.id !== tenantId) {
throw new ForbiddenException('Access denied: Cannot view API keys for another tenant.');
}
return this.authService.getApiKeys(req.tenant.id);
}
@Delete('keys/:keyId')
@ApiOperation({ summary: 'Revoke / Delete an API key (IDOR Protected)' })
async deleteKey(
@Req() req: any,
@Param('keyId') keyId: string
) {
const tenantId = req.tenant.id;
await this.authService.deleteApiKey(tenantId, keyId);
return { success: true, message: 'API key revoked successfully' };
}
@Get('me')
@ApiOperation({ summary: 'Get current authenticated tenant info' })
async getMe(@Req() req: any) {
return req.tenant;
}
}
@@ -6,6 +6,7 @@ import {
HttpStatus,
Post,
Query,
Req,
UseGuards,
} from '@nestjs/common';
import { ApiHeader, ApiOperation, ApiTags } from '@nestjs/swagger';
@@ -26,6 +27,21 @@ import { TacticalService } from './tactical.service';
export class TacticalController {
constructor(private readonly tacticalService: TacticalService) {}
@Get('verify-license')
@ApiOperation({ summary: 'Verify tactical clearance and military license' })
async verifyLicense(@Req() req: any) {
const tenant = req.tenant;
const apiKey = req.apiKey;
return {
valid: true,
tenantName: tenant?.name || 'Authorized Tactical Operator',
plan: tenant?.plan || 'ENTERPRISE',
keyName: apiKey?.name || 'Tactical Defense Key',
rateLimit: req.rateLimit || 1000,
timestamp: new Date().toISOString(),
};
}
@Get('line-of-sight')
@ApiOperation({
summary: 'Calculate Tactical Line of Sight & Intervisibility (تبادل الرؤية العسكري)',