fix: Add additional checks for disabled projects
This commit is contained in:
@@ -7,7 +7,7 @@ import type {NextFunction, Request, Response} from 'express';
|
|||||||
import {DASHBOARD_URI, STRIPE_ENABLED, STRIPE_PRICE_EMAIL_USAGE, STRIPE_PRICE_ONBOARDING} from '../app/constants.js';
|
import {DASHBOARD_URI, STRIPE_ENABLED, STRIPE_PRICE_EMAIL_USAGE, STRIPE_PRICE_ONBOARDING} from '../app/constants.js';
|
||||||
import {stripe} from '../app/stripe.js';
|
import {stripe} from '../app/stripe.js';
|
||||||
import {prisma} from '../database/prisma.js';
|
import {prisma} from '../database/prisma.js';
|
||||||
import {NotAuthenticated, NotFound} from '../exceptions/index.js';
|
import {ErrorCode, HttpException, NotAuthenticated, NotFound} from '../exceptions/index.js';
|
||||||
import type {AuthResponse} from '../middleware/auth.js';
|
import type {AuthResponse} from '../middleware/auth.js';
|
||||||
import {isAuthenticated} from '../middleware/auth.js';
|
import {isAuthenticated} from '../middleware/auth.js';
|
||||||
import {BillingLimitService} from '../services/BillingLimitService.js';
|
import {BillingLimitService} from '../services/BillingLimitService.js';
|
||||||
@@ -63,6 +63,16 @@ export class Users {
|
|||||||
throw new NotAuthenticated();
|
throw new NotAuthenticated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if user is a member of any disabled project
|
||||||
|
const {hasDisabledProject, disabledProjectNames} = await SecurityService.userHasDisabledProject(auth.userId);
|
||||||
|
if (hasDisabledProject) {
|
||||||
|
throw new HttpException(
|
||||||
|
403,
|
||||||
|
`You cannot create new projects while you are a member of disabled projects: ${disabledProjectNames.join(', ')}. Please contact support to resolve security violations.`,
|
||||||
|
ErrorCode.PROJECT_DISABLED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const {name} = ProjectSchemas.create.parse(req.body);
|
const {name} = ProjectSchemas.create.parse(req.body);
|
||||||
|
|
||||||
// Generate unique API keys
|
// Generate unique API keys
|
||||||
@@ -692,6 +702,16 @@ export class Users {
|
|||||||
throw new NotFound('Project not found or you do not have permission to reset it');
|
throw new NotFound('Project not found or you do not have permission to reset it');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if project is disabled - block reset operation
|
||||||
|
const isDisabled = await SecurityService.isProjectDisabled(id);
|
||||||
|
if (isDisabled) {
|
||||||
|
throw new HttpException(
|
||||||
|
403,
|
||||||
|
'Cannot reset a disabled project. Please contact support to resolve security violations before making changes.',
|
||||||
|
ErrorCode.PROJECT_DISABLED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Delete all project data in a transaction
|
// Delete all project data in a transaction
|
||||||
await prisma.$transaction(async tx => {
|
await prisma.$transaction(async tx => {
|
||||||
// Delete all emails
|
// Delete all emails
|
||||||
@@ -770,13 +790,14 @@ export class Users {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get project to check for active subscription
|
// Get project to check for active subscription and disabled status
|
||||||
const project = await prisma.project.findUnique({
|
const project = await prisma.project.findUnique({
|
||||||
where: {id},
|
where: {id},
|
||||||
select: {
|
select: {
|
||||||
name: true,
|
name: true,
|
||||||
subscription: true,
|
subscription: true,
|
||||||
customer: true,
|
customer: true,
|
||||||
|
disabled: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -784,6 +805,15 @@ export class Users {
|
|||||||
throw new NotFound('Project not found');
|
throw new NotFound('Project not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if project is disabled - block delete operation
|
||||||
|
if (project.disabled) {
|
||||||
|
throw new HttpException(
|
||||||
|
403,
|
||||||
|
'Cannot delete a disabled project. Please contact support to resolve security violations.',
|
||||||
|
ErrorCode.PROJECT_DISABLED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// If project has an active subscription, cancel it first
|
// If project has an active subscription, cancel it first
|
||||||
if (STRIPE_ENABLED && stripe && project.subscription) {
|
if (STRIPE_ENABLED && stripe && project.subscription) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -147,6 +147,48 @@ export class SecurityService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a user is a member of any disabled project
|
||||||
|
* Users with disabled projects cannot create new projects
|
||||||
|
*/
|
||||||
|
public static async userHasDisabledProject(userId: string): Promise<{
|
||||||
|
hasDisabledProject: boolean;
|
||||||
|
disabledProjectNames: string[];
|
||||||
|
}> {
|
||||||
|
const disabledMemberships = await prisma.membership.findMany({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
project: {
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
project: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasDisabledProject: disabledMemberships.length > 0,
|
||||||
|
disabledProjectNames: disabledMemberships.map(m => m.project.name),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a specific project is disabled
|
||||||
|
*/
|
||||||
|
public static async isProjectDisabled(projectId: string): Promise<boolean> {
|
||||||
|
const project = await prisma.project.findUnique({
|
||||||
|
where: {id: projectId},
|
||||||
|
select: {disabled: true},
|
||||||
|
});
|
||||||
|
|
||||||
|
return project?.disabled ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a project's security metrics (for admin/dashboard display)
|
* Get a project's security metrics (for admin/dashboard display)
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user