Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 32 additions & 32 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "dist/index.js",
"scripts": {
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
"build": "tsc",
"build": "prisma generate && tsc",
"start": "node dist/index.js",
"test": "vitest run",
"test:watch": "vitest",
Expand All @@ -29,45 +29,45 @@
"author": "",
"license": "MIT",
"dependencies": {
"@prisma/client": "^5.18.0",
"axios": "^1.7.2",
"bcrypt": "^5.1.1",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"@prisma/client": "^7.3.0",
"axios": "^1.13.4",
"bcrypt": "^6.0.0",
"cookie-parser": "^1.4.7",
"cors": "^2.8.6",
"cron-parser": "^4.9.0",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express-validator": "^7.1.0",
"helmet": "^7.1.0",
"jsonwebtoken": "^9.0.2",
"morgan": "^1.10.0",
"node-forge": "^1.3.1",
"nodemailer": "^6.9.14",
"dotenv": "^17.2.4",
"express": "^5.2.1",
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Express v5.2.1 is specified, but as of January 2025, Express v5 is still in beta (latest stable is 4.x). Express 5.x introduces breaking changes including changes to middleware signatures, error handling, and removed methods. This is a major version upgrade that requires careful testing and potential code changes. Verify that all middleware and route handlers are compatible with Express 5, particularly error handlers which now requireasync errors to be explicitly caught.

Copilot uses AI. Check for mistakes.
"express-validator": "^7.3.1",
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.3",
"morgan": "^1.10.1",
"node-forge": "^1.3.3",
"nodemailer": "^8.0.0",
"qrcode": "^1.5.4",
"speakeasy": "^2.0.0",
"winston": "^3.13.1"
"winston": "^3.19.0"
},
"devDependencies": {
"@types/bcrypt": "^5.0.2",
"@types/cookie-parser": "^1.4.7",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.6",
"@types/morgan": "^1.9.9",
"@types/node": "^20.14.12",
"@types/node-forge": "^1.3.11",
"@types/nodemailer": "^6.4.15",
"@types/qrcode": "^1.5.5",
"@types/bcrypt": "^6.0.0",
"@types/cookie-parser": "^1.4.10",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/jsonwebtoken": "^9.0.10",
"@types/morgan": "^1.9.10",
"@types/node": "^25.2.1",
"@types/node-forge": "^1.3.14",
"@types/nodemailer": "^7.0.9",
"@types/qrcode": "^1.5.6",
"@types/speakeasy": "^2.0.10",
"@types/supertest": "^6.0.3",
"@vitest/coverage-v8": "3.2.4",
"@vitest/ui": "^3.2.4",
"prisma": "^5.18.0",
"supertest": "^7.1.4",
"@vitest/coverage-v8": "4.0.18",
"@vitest/ui": "^4.0.18",
"prisma": "^7.3.0",
"supertest": "^7.2.2",
"ts-node": "^10.9.2",
"ts-node-dev": "^2.0.0",
"typescript": "^5.5.4",
"vitest": "^3.2.4",
"zod": "^4.1.11"
"typescript": "^5.9.3",
"vitest": "^4.0.18",
"zod": "^4.3.6"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- AlterTable: Add wildcard SSL certificate support fields
ALTER TABLE "ssl_certificates"
ADD COLUMN "isWildcard" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "wildcardDomain" TEXT;

-- CreateIndex: Index on isWildcard for efficient wildcard certificate queries
CREATE INDEX "ssl_certificates_isWildcard_idx" ON "ssl_certificates"("isWildcard");
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "ssl_certificates" ADD COLUMN "dnsProvider" TEXT;
ALTER TABLE "ssl_certificates" ADD COLUMN "dnsCredentials" JSONB;
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This migration adds a dnsCredentials JSONB column that will persist DNS API credentials in plaintext form, so any SQL injection, backup leak, or over‑privileged DB access could directly expose Cloudflare (or other provider) tokens. With these tokens, an attacker could alter DNS records for associated domains, enabling domain hijacking and further attacks. Instead, store only encrypted blobs or opaque credential references here and keep the actual DNS API secrets in a dedicated secret management system separate from the primary application database.

Suggested change
ALTER TABLE "ssl_certificates" ADD COLUMN "dnsCredentials" JSONB;
ALTER TABLE "ssl_certificates" ADD COLUMN "dnsCredentialRef" TEXT;

Copilot uses AI. Check for mistakes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "NotificationChannelType" ADD VALUE 'jira';
16 changes: 16 additions & 0 deletions apps/api/prisma/prisma.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import path from 'node:path'
import { defineConfig } from 'prisma/config'

export default defineConfig({
earlyAccess: true,
schema: path.join(__dirname, 'schema.prisma'),
migrate: {
async url() {
const databaseUrl = process.env.DATABASE_URL
if (!databaseUrl) {
throw new Error('DATABASE_URL environment variable is required for migrations')
}
return databaseUrl
}
}
})
11 changes: 10 additions & 1 deletion apps/api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ generator client {

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

enum UserRole {
Expand Down Expand Up @@ -262,6 +261,14 @@ model SSLCertificate {
issuerDetails Json? // { commonName, organization, country }
serialNumber String? // Certificate serial number

// Wildcard SSL support
isWildcard Boolean @default(false) // Whether this is a wildcard certificate
wildcardDomain String? // The wildcard pattern (e.g., "*.example.com")

// DNS credentials for wildcard auto-renewal (Cloudflare DNS-01 challenge)
dnsProvider String? // DNS provider plugin name (e.g., "dns_cf")
dnsCredentials Json? // DNS API credentials for auto-renewal (e.g., { CF_Token: "...", CF_Account_ID: "..." })
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Storing sensitive information like dnsCredentials in plain text (even as JSONB) in the database is a critical security vulnerability. If the database is compromised, these credentials will be exposed, potentially giving attackers control over your DNS zones. These credentials should be encrypted at rest. Consider using a library like crypto in Node.js to encrypt/decrypt these credentials before storing and after retrieving them from the database, or integrate with a dedicated secrets management service (e.g., HashiCorp Vault, AWS Secrets Manager).

Comment on lines +269 to +270
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dnsCredentials field is intended to store DNS API credentials (e.g., CF_Token) in plaintext JSON, which means any compromise of the application database (or overly broad read access) would directly expose high‑impact DNS control secrets. An attacker who obtains these credentials could modify DNS records for managed domains, enabling traffic hijacking or further compromise. Consider storing only references to credentials held in a dedicated secret store or applying application‑level encryption so that raw tokens are never stored or readable in the database without an additional protected key.

Suggested change
dnsProvider String? // DNS provider plugin name (e.g., "dns_cf")
dnsCredentials Json? // DNS API credentials for auto-renewal (e.g., { CF_Token: "...", CF_Account_ID: "..." })
dnsProvider String? // DNS provider plugin name (e.g., "dns_cf")
// Reference/identifier to DNS API credentials stored in a dedicated secret store.
// This must not contain raw API tokens or secrets.
dnsCredentialRef String?

Copilot uses AI. Check for mistakes.

validFrom DateTime
validTo DateTime
autoRenew Boolean @default(true)
Expand All @@ -272,6 +279,7 @@ model SSLCertificate {

@@index([domainId])
@@index([validTo])
@@index([isWildcard])
@@map("ssl_certificates")
}

Expand Down Expand Up @@ -352,6 +360,7 @@ model InstallationStatus {
enum NotificationChannelType {
email
telegram
jira
}

enum AlertSeverity {
Expand Down
12 changes: 6 additions & 6 deletions apps/api/src/domains/access-lists/access-lists.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export class AccessListsController {
});
}

const { id } = req.params;
const { id } = req.params as Record<string, string>;

const accessList = await accessListsService.getAccessListById(id);

Expand Down Expand Up @@ -139,7 +139,7 @@ export class AccessListsController {
});
}

const { id } = req.params;
const { id } = req.params as Record<string, string>;
const userId = (req as any).user.id;
const username = (req as any).user.username;
const ip = req.ip || req.socket.remoteAddress || '';
Expand Down Expand Up @@ -182,7 +182,7 @@ export class AccessListsController {
});
}

const { id } = req.params;
const { id } = req.params as Record<string, string>;
const userId = (req as any).user.id;
const username = (req as any).user.username;
const ip = req.ip || req.socket.remoteAddress || '';
Expand All @@ -208,7 +208,7 @@ export class AccessListsController {
*/
async toggleAccessList(req: Request, res: Response) {
try {
const { id } = req.params;
const { id } = req.params as Record<string, string>;
const { enabled } = req.body;
const userId = (req as any).user.id;
const username = (req as any).user.username;
Expand Down Expand Up @@ -292,7 +292,7 @@ export class AccessListsController {
});
}

const { accessListId, domainId } = req.params;
const { accessListId, domainId } = req.params as Record<string, string>;
const userId = (req as any).user.id;
const username = (req as any).user.username;
const ip = req.ip || req.socket.remoteAddress || '';
Expand Down Expand Up @@ -325,7 +325,7 @@ export class AccessListsController {
*/
async getByDomain(req: Request, res: Response) {
try {
const { domainId } = req.params;
const { domainId } = req.params as Record<string, string>;

const accessLists = await accessListsService.getAccessListsByDomainId(domainId);

Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/domains/account/account.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ class AccountController {
revokeSession = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const userId = req.user?.userId;
const { sessionId } = req.params;
const { sessionId } = req.params as Record<string, string>;

if (!userId) {
res.status(401).json({
Expand Down
8 changes: 4 additions & 4 deletions apps/api/src/domains/acl/acl.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export class AclController {
*/
async getAclRule(req: Request, res: Response): Promise<void> {
try {
const { id } = req.params;
const { id } = req.params as Record<string, string>;
const rule = await aclService.getRuleById(id);

res.json({
Expand Down Expand Up @@ -97,7 +97,7 @@ export class AclController {
*/
async updateAclRule(req: Request, res: Response): Promise<void> {
try {
const { id } = req.params;
const { id } = req.params as Record<string, string>;
const dto: UpdateAclRuleDto = req.body;

// Validate DTO
Expand Down Expand Up @@ -136,7 +136,7 @@ export class AclController {
*/
async deleteAclRule(req: Request, res: Response): Promise<void> {
try {
const { id } = req.params;
const { id } = req.params as Record<string, string>;
await aclService.deleteRule(id);

res.json({
Expand All @@ -161,7 +161,7 @@ export class AclController {
*/
async toggleAclRule(req: Request, res: Response): Promise<void> {
try {
const { id } = req.params;
const { id } = req.params as Record<string, string>;
const rule = await aclService.toggleRule(id);

res.json({
Expand Down
Loading
Loading