feat(security): implement dynamic tactical license gate, instant key revocation, and clean hardcoded credentials
This commit is contained in:
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 || [],
|
||||
|
||||
@@ -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 (تبادل الرؤية العسكري)',
|
||||
|
||||
Reference in New Issue
Block a user