Add time-limited external access for optional services and database.
Users can open temporary NodePort access with auto-revoke via Bull jobs and a dashboard UI to manage active grants. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -27,6 +27,8 @@ import { Roles } from '../common/decorators/roles.decorator';
|
||||
import { UserRole, DatabaseType } from '../common/enums';
|
||||
import { KubernetesService } from '../kubernetes/kubernetes.service';
|
||||
import { DeploymentsService } from '../deployments/deployments.service';
|
||||
import { AccessService } from '../access/access.service';
|
||||
import { CreateServiceAccessDto } from '../access/dto/service-access.dto';
|
||||
|
||||
@ApiTags('Applications')
|
||||
@ApiBearerAuth()
|
||||
@@ -41,8 +43,17 @@ export class ApplicationsController {
|
||||
private readonly kubernetesService: KubernetesService,
|
||||
@Inject(forwardRef(() => DeploymentsService))
|
||||
private readonly deploymentsService: DeploymentsService,
|
||||
private readonly accessService: AccessService,
|
||||
) {}
|
||||
|
||||
private isStaff(role: string): boolean {
|
||||
return role === UserRole.ADMIN || role === UserRole.TECHNICAL;
|
||||
}
|
||||
|
||||
private staffUserIdFilter(req: any): string | undefined {
|
||||
return this.isStaff(req.user.role) ? undefined : req.user.id;
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Create a new application' })
|
||||
async create(@Request() req: any, @Body() dto: CreateApplicationDto) {
|
||||
@@ -268,13 +279,41 @@ export class ApplicationsController {
|
||||
@Get(':id/preview')
|
||||
@ApiOperation({ summary: 'Get preview URL for the deployed application' })
|
||||
async getPreview(@Param('id') id: string, @Request() req: any) {
|
||||
const app = await this.applicationsService.findOne(
|
||||
id,
|
||||
(req.user.role === UserRole.ADMIN || req.user.role === UserRole.TECHNICAL) ? undefined : req.user.id,
|
||||
);
|
||||
const app = await this.applicationsService.findOne(id, this.staffUserIdFilter(req));
|
||||
return this.kubernetesService.getPreviewInfo(app);
|
||||
}
|
||||
|
||||
@Post(':id/access')
|
||||
@ApiOperation({ summary: 'Open temporary external access to database, Redis, or RabbitMQ' })
|
||||
async createAccess(
|
||||
@Param('id') id: string,
|
||||
@Request() req: any,
|
||||
@Body() dto: CreateServiceAccessDto,
|
||||
) {
|
||||
await this.applicationsService.findOne(id, this.staffUserIdFilter(req));
|
||||
return this.accessService.createGrant(id, this.staffUserIdFilter(req), dto);
|
||||
}
|
||||
|
||||
@Get(':id/access')
|
||||
@ApiOperation({ summary: 'List active temporary access grants for an application' })
|
||||
async listAccess(@Param('id') id: string, @Request() req: any) {
|
||||
await this.applicationsService.findOne(id, this.staffUserIdFilter(req));
|
||||
return this.accessService.listGrants(id, this.staffUserIdFilter(req));
|
||||
}
|
||||
|
||||
@Delete(':id/access/:grantId')
|
||||
@ApiOperation({ summary: 'Revoke temporary external access' })
|
||||
async revokeAccess(
|
||||
@Param('id') id: string,
|
||||
@Param('grantId') grantId: string,
|
||||
@Request() req: any,
|
||||
) {
|
||||
const app = await this.applicationsService.findOne(id, this.staffUserIdFilter(req));
|
||||
await this.accessService.findGrantForApp(grantId, id, this.staffUserIdFilter(req));
|
||||
await this.accessService.revokeGrant(grantId, this.staffUserIdFilter(req));
|
||||
return { message: 'Access revoked' };
|
||||
}
|
||||
|
||||
// ── Custom Domain ─────────────────────────────────────────────
|
||||
|
||||
@Post('domain/check-dns')
|
||||
@@ -351,7 +390,14 @@ export class ApplicationsController {
|
||||
// 1. Get the app first
|
||||
const app = await this.applicationsService.findOne(id, isStaff ? undefined : req.user.id);
|
||||
|
||||
// 2. Delete K8s resources (deployment, service, ingress, db, secrets, PVCs)
|
||||
// 2. Revoke temporary access grants
|
||||
try {
|
||||
await this.accessService.revokeAllForApplication(app.id);
|
||||
} catch (e: any) {
|
||||
this.logger.warn(`Access grant cleanup failed for ${app.name}: ${e.message}`);
|
||||
}
|
||||
|
||||
// 3. Delete K8s resources (deployment, service, ingress, db, secrets, PVCs)
|
||||
try {
|
||||
await this.kubernetesService.deleteApplication(app);
|
||||
this.logger.log(`Deleted K8s resources for ${app.name}`);
|
||||
@@ -359,14 +405,14 @@ export class ApplicationsController {
|
||||
this.logger.warn(`K8s cleanup failed for ${app.name}: ${e.message}`);
|
||||
}
|
||||
|
||||
// 3. Delete deployment records from DB
|
||||
// 4. Delete deployment records from DB
|
||||
try {
|
||||
await this.deploymentsService.deleteAllForApplication(app.id);
|
||||
} catch (e: any) {
|
||||
this.logger.warn(`Deployment records cleanup failed for ${app.name}: ${e.message}`);
|
||||
}
|
||||
|
||||
// 4. Delete app (also deletes uploaded files)
|
||||
// 5. Delete app (also deletes uploaded files)
|
||||
await this.applicationsService.delete(id, isStaff ? app.userId : req.user.id);
|
||||
|
||||
return { message: `Application "${app.name}" and all resources deleted` };
|
||||
|
||||
Reference in New Issue
Block a user