diff --git a/.eslintrc.json b/.eslintrc.json index a9468a44b..98ef693e1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,6 +1,3 @@ { - "extends": [ - "next/core-web-vitals", - "next/typescript" - ] + "extends": ["next/core-web-vitals", "next/typescript"] } diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 79e80ed9a..974188b8d 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,3 @@ { "recommendations": ["esbenp.prettier-vscode"] -} \ No newline at end of file +} diff --git a/.vscode/settings.json b/.vscode/settings.json index d429abe2f..77440d967 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,4 +19,4 @@ "editor.defaultFormatter": "esbenp.prettier-vscode" }, "editor.formatOnSave": true -} \ No newline at end of file +} diff --git a/components.json b/components.json index 97d8c8c0e..13f7efefb 100644 --- a/components.json +++ b/components.json @@ -17,4 +17,4 @@ "lib": "@/lib", "hooks": "@/hooks" } -} \ No newline at end of file +} diff --git a/drizzle.pg.config.ts b/drizzle.pg.config.ts index febd5f453..ba4ca8fe5 100644 --- a/drizzle.pg.config.ts +++ b/drizzle.pg.config.ts @@ -1,9 +1,7 @@ import { defineConfig } from "drizzle-kit"; import path from "path"; -const schema = [ - path.join("server", "db", "pg", "schema"), -]; +const schema = [path.join("server", "db", "pg", "schema")]; export default defineConfig({ dialect: "postgresql", diff --git a/drizzle.sqlite.config.ts b/drizzle.sqlite.config.ts index 4912c2565..d8344f942 100644 --- a/drizzle.sqlite.config.ts +++ b/drizzle.sqlite.config.ts @@ -2,9 +2,7 @@ import { APP_PATH } from "@server/lib/consts"; import { defineConfig } from "drizzle-kit"; import path from "path"; -const schema = [ - path.join("server", "db", "sqlite", "schema"), -]; +const schema = [path.join("server", "db", "sqlite", "schema")]; export default defineConfig({ dialect: "sqlite", diff --git a/esbuild.mjs b/esbuild.mjs index 7f67fe819..0157c34ac 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -24,20 +24,20 @@ const argv = yargs(hideBin(process.argv)) alias: "e", describe: "Entry point file", type: "string", - demandOption: true, + demandOption: true }) .option("out", { alias: "o", describe: "Output file path", type: "string", - demandOption: true, + demandOption: true }) .option("build", { alias: "b", describe: "Build type (oss, saas, enterprise)", type: "string", choices: ["oss", "saas", "enterprise"], - default: "oss", + default: "oss" }) .help() .alias("help", "h").argv; @@ -66,7 +66,9 @@ function privateImportGuardPlugin() { // Check if the importing file is NOT in server/private const normalizedImporter = path.normalize(importingFile); - const isInServerPrivate = normalizedImporter.includes(path.normalize("server/private")); + const isInServerPrivate = normalizedImporter.includes( + path.normalize("server/private") + ); if (!isInServerPrivate) { const violation = { @@ -79,8 +81,8 @@ function privateImportGuardPlugin() { console.log(`PRIVATE IMPORT VIOLATION:`); console.log(` File: ${importingFile}`); console.log(` Import: ${args.path}`); - console.log(` Resolve dir: ${args.resolveDir || 'N/A'}`); - console.log(''); + console.log(` Resolve dir: ${args.resolveDir || "N/A"}`); + console.log(""); } // Return null to let the default resolver handle it @@ -89,16 +91,20 @@ function privateImportGuardPlugin() { build.onEnd((result) => { if (violations.length > 0) { - console.log(`\nSUMMARY: Found ${violations.length} private import violation(s):`); + console.log( + `\nSUMMARY: Found ${violations.length} private import violation(s):` + ); violations.forEach((v, i) => { - console.log(` ${i + 1}. ${path.relative(process.cwd(), v.file)} imports ${v.importPath}`); + console.log( + ` ${i + 1}. ${path.relative(process.cwd(), v.file)} imports ${v.importPath}` + ); }); - console.log(''); + console.log(""); result.errors.push({ text: `Private import violations detected: ${violations.length} violation(s) found`, location: null, - notes: violations.map(v => ({ + notes: violations.map((v) => ({ text: `${path.relative(process.cwd(), v.file)} imports ${v.importPath}`, location: null })) @@ -121,7 +127,9 @@ function dynamicImportGuardPlugin() { // Check if the importing file is NOT in server/private const normalizedImporter = path.normalize(importingFile); - const isInServerPrivate = normalizedImporter.includes(path.normalize("server/private")); + const isInServerPrivate = normalizedImporter.includes( + path.normalize("server/private") + ); if (isInServerPrivate) { const violation = { @@ -134,8 +142,8 @@ function dynamicImportGuardPlugin() { console.log(`DYNAMIC IMPORT VIOLATION:`); console.log(` File: ${importingFile}`); console.log(` Import: ${args.path}`); - console.log(` Resolve dir: ${args.resolveDir || 'N/A'}`); - console.log(''); + console.log(` Resolve dir: ${args.resolveDir || "N/A"}`); + console.log(""); } // Return null to let the default resolver handle it @@ -144,16 +152,20 @@ function dynamicImportGuardPlugin() { build.onEnd((result) => { if (violations.length > 0) { - console.log(`\nSUMMARY: Found ${violations.length} dynamic import violation(s):`); + console.log( + `\nSUMMARY: Found ${violations.length} dynamic import violation(s):` + ); violations.forEach((v, i) => { - console.log(` ${i + 1}. ${path.relative(process.cwd(), v.file)} imports ${v.importPath}`); + console.log( + ` ${i + 1}. ${path.relative(process.cwd(), v.file)} imports ${v.importPath}` + ); }); - console.log(''); + console.log(""); result.errors.push({ text: `Dynamic import violations detected: ${violations.length} violation(s) found`, location: null, - notes: violations.map(v => ({ + notes: violations.map((v) => ({ text: `${path.relative(process.cwd(), v.file)} imports ${v.importPath}`, location: null })) @@ -172,21 +184,28 @@ function dynamicImportSwitcherPlugin(buildValue) { const switches = []; build.onStart(() => { - console.log(`Dynamic import switcher using build type: ${buildValue}`); + console.log( + `Dynamic import switcher using build type: ${buildValue}` + ); }); build.onResolve({ filter: /^#dynamic\// }, (args) => { // Extract the path after #dynamic/ - const dynamicPath = args.path.replace(/^#dynamic\//, ''); + const dynamicPath = args.path.replace(/^#dynamic\//, ""); // Determine the replacement based on build type let replacement; if (buildValue === "oss") { replacement = `#open/${dynamicPath}`; - } else if (buildValue === "saas" || buildValue === "enterprise") { + } else if ( + buildValue === "saas" || + buildValue === "enterprise" + ) { replacement = `#closed/${dynamicPath}`; // We use #closed here so that the route guards dont complain after its been changed but this is the same as #private } else { - console.warn(`Unknown build type '${buildValue}', defaulting to #open/`); + console.warn( + `Unknown build type '${buildValue}', defaulting to #open/` + ); replacement = `#open/${dynamicPath}`; } @@ -201,8 +220,10 @@ function dynamicImportSwitcherPlugin(buildValue) { console.log(`DYNAMIC IMPORT SWITCH:`); console.log(` File: ${args.importer}`); console.log(` Original: ${args.path}`); - console.log(` Switched to: ${replacement} (build: ${buildValue})`); - console.log(''); + console.log( + ` Switched to: ${replacement} (build: ${buildValue})` + ); + console.log(""); // Rewrite the import path and let the normal resolution continue return build.resolve(replacement, { @@ -215,12 +236,18 @@ function dynamicImportSwitcherPlugin(buildValue) { build.onEnd((result) => { if (switches.length > 0) { - console.log(`\nDYNAMIC IMPORT SUMMARY: Switched ${switches.length} import(s) for build type '${buildValue}':`); + console.log( + `\nDYNAMIC IMPORT SUMMARY: Switched ${switches.length} import(s) for build type '${buildValue}':` + ); switches.forEach((s, i) => { - console.log(` ${i + 1}. ${path.relative(process.cwd(), s.file)}`); - console.log(` ${s.originalPath} → ${s.replacementPath}`); + console.log( + ` ${i + 1}. ${path.relative(process.cwd(), s.file)}` + ); + console.log( + ` ${s.originalPath} → ${s.replacementPath}` + ); }); - console.log(''); + console.log(""); } }); } @@ -235,7 +262,7 @@ esbuild format: "esm", minify: false, banner: { - js: banner, + js: banner }, platform: "node", external: ["body-parser"], @@ -244,20 +271,22 @@ esbuild dynamicImportGuardPlugin(), dynamicImportSwitcherPlugin(argv.build), nodeExternalsPlugin({ - packagePath: getPackagePaths(), - }), + packagePath: getPackagePaths() + }) ], sourcemap: "inline", - target: "node22", + target: "node22" }) .then((result) => { // Check if there were any errors in the build result if (result.errors && result.errors.length > 0) { - console.error(`Build failed with ${result.errors.length} error(s):`); + console.error( + `Build failed with ${result.errors.length} error(s):` + ); result.errors.forEach((error, i) => { console.error(`${i + 1}. ${error.text}`); if (error.notes) { - error.notes.forEach(note => { + error.notes.forEach((note) => { console.error(` - ${note.text}`); }); } diff --git a/eslint.config.js b/eslint.config.js index dfc194bca..ae921d450 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,19 +1,19 @@ -import tseslint from 'typescript-eslint'; +import tseslint from "typescript-eslint"; export default tseslint.config({ - files: ["**/*.{ts,tsx,js,jsx}"], - languageOptions: { - parser: tseslint.parser, - parserOptions: { - ecmaVersion: "latest", - sourceType: "module", - ecmaFeatures: { - jsx: true - } + files: ["**/*.{ts,tsx,js,jsx}"], + languageOptions: { + parser: tseslint.parser, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { + jsx: true + } + } + }, + rules: { + semi: "error", + "prefer-const": "warn" } - }, - rules: { - "semi": "error", - "prefer-const": "warn" - } -}); \ No newline at end of file +}); diff --git a/postcss.config.mjs b/postcss.config.mjs index 9d3299ad5..19b5e42fc 100644 --- a/postcss.config.mjs +++ b/postcss.config.mjs @@ -1,8 +1,8 @@ /** @type {import('postcss-load-config').Config} */ const config = { plugins: { - "@tailwindcss/postcss": {}, - }, + "@tailwindcss/postcss": {} + } }; export default config; diff --git a/server/auth/password.ts b/server/auth/password.ts index dd1a3d1b2..a25af4c95 100644 --- a/server/auth/password.ts +++ b/server/auth/password.ts @@ -2,13 +2,13 @@ import { hash, verify } from "@node-rs/argon2"; export async function verifyPassword( password: string, - hash: string, + hash: string ): Promise { const validPassword = await verify(hash, password, { memoryCost: 19456, timeCost: 2, outputLen: 32, - parallelism: 1, + parallelism: 1 }); return validPassword; } @@ -18,7 +18,7 @@ export async function hashPassword(password: string): Promise { memoryCost: 19456, timeCost: 2, outputLen: 32, - parallelism: 1, + parallelism: 1 }); return passwordHash; diff --git a/server/auth/passwordSchema.ts b/server/auth/passwordSchema.ts index 9c3990929..740f9a5d7 100644 --- a/server/auth/passwordSchema.ts +++ b/server/auth/passwordSchema.ts @@ -4,10 +4,13 @@ export const passwordSchema = z .string() .min(8, { message: "Password must be at least 8 characters long" }) .max(128, { message: "Password must be at most 128 characters long" }) - .regex(/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[~!`@#$%^&*()_\-+={}[\]|\\:;"'<>,.\/?]).*$/, { - message: `Your password must meet the following conditions: + .regex( + /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[~!`@#$%^&*()_\-+={}[\]|\\:;"'<>,.\/?]).*$/, + { + message: `Your password must meet the following conditions: at least one uppercase English letter, at least one lowercase English letter, at least one digit, at least one special character.` - }); + } + ); diff --git a/server/auth/sessions/newt.ts b/server/auth/sessions/newt.ts index 5e55c4911..96c378940 100644 --- a/server/auth/sessions/newt.ts +++ b/server/auth/sessions/newt.ts @@ -1,6 +1,4 @@ -import { - encodeHexLowerCase, -} from "@oslojs/encoding"; +import { encodeHexLowerCase } from "@oslojs/encoding"; import { sha256 } from "@oslojs/crypto/sha2"; import { Newt, newts, newtSessions, NewtSession } from "@server/db"; import { db } from "@server/db"; @@ -10,25 +8,25 @@ export const EXPIRES = 1000 * 60 * 60 * 24 * 30; export async function createNewtSession( token: string, - newtId: string, + newtId: string ): Promise { const sessionId = encodeHexLowerCase( - sha256(new TextEncoder().encode(token)), + sha256(new TextEncoder().encode(token)) ); const session: NewtSession = { sessionId: sessionId, newtId, - expiresAt: new Date(Date.now() + EXPIRES).getTime(), + expiresAt: new Date(Date.now() + EXPIRES).getTime() }; await db.insert(newtSessions).values(session); return session; } export async function validateNewtSessionToken( - token: string, + token: string ): Promise { const sessionId = encodeHexLowerCase( - sha256(new TextEncoder().encode(token)), + sha256(new TextEncoder().encode(token)) ); const result = await db .select({ newt: newts, session: newtSessions }) @@ -45,14 +43,12 @@ export async function validateNewtSessionToken( .where(eq(newtSessions.sessionId, session.sessionId)); return { session: null, newt: null }; } - if (Date.now() >= session.expiresAt - (EXPIRES / 2)) { - session.expiresAt = new Date( - Date.now() + EXPIRES, - ).getTime(); + if (Date.now() >= session.expiresAt - EXPIRES / 2) { + session.expiresAt = new Date(Date.now() + EXPIRES).getTime(); await db .update(newtSessions) .set({ - expiresAt: session.expiresAt, + expiresAt: session.expiresAt }) .where(eq(newtSessions.sessionId, session.sessionId)); } diff --git a/server/auth/sessions/olm.ts b/server/auth/sessions/olm.ts index 89a0e81ed..a51ec79ad 100644 --- a/server/auth/sessions/olm.ts +++ b/server/auth/sessions/olm.ts @@ -1,6 +1,4 @@ -import { - encodeHexLowerCase, -} from "@oslojs/encoding"; +import { encodeHexLowerCase } from "@oslojs/encoding"; import { sha256 } from "@oslojs/crypto/sha2"; import { Olm, olms, olmSessions, OlmSession } from "@server/db"; import { db } from "@server/db"; @@ -10,25 +8,25 @@ export const EXPIRES = 1000 * 60 * 60 * 24 * 30; export async function createOlmSession( token: string, - olmId: string, + olmId: string ): Promise { const sessionId = encodeHexLowerCase( - sha256(new TextEncoder().encode(token)), + sha256(new TextEncoder().encode(token)) ); const session: OlmSession = { sessionId: sessionId, olmId, - expiresAt: new Date(Date.now() + EXPIRES).getTime(), + expiresAt: new Date(Date.now() + EXPIRES).getTime() }; await db.insert(olmSessions).values(session); return session; } export async function validateOlmSessionToken( - token: string, + token: string ): Promise { const sessionId = encodeHexLowerCase( - sha256(new TextEncoder().encode(token)), + sha256(new TextEncoder().encode(token)) ); const result = await db .select({ olm: olms, session: olmSessions }) @@ -45,14 +43,12 @@ export async function validateOlmSessionToken( .where(eq(olmSessions.sessionId, session.sessionId)); return { session: null, olm: null }; } - if (Date.now() >= session.expiresAt - (EXPIRES / 2)) { - session.expiresAt = new Date( - Date.now() + EXPIRES, - ).getTime(); + if (Date.now() >= session.expiresAt - EXPIRES / 2) { + session.expiresAt = new Date(Date.now() + EXPIRES).getTime(); await db .update(olmSessions) .set({ - expiresAt: session.expiresAt, + expiresAt: session.expiresAt }) .where(eq(olmSessions.sessionId, session.sessionId)); } diff --git a/server/cleanup.ts b/server/cleanup.ts index a89854397..e494fcdc9 100644 --- a/server/cleanup.ts +++ b/server/cleanup.ts @@ -10,4 +10,4 @@ export async function initCleanup() { // Handle process termination process.on("SIGTERM", () => cleanup()); process.on("SIGINT", () => cleanup()); -} \ No newline at end of file +} diff --git a/server/db/countries.ts b/server/db/countries.ts index 2907fd69b..749f1183f 100644 --- a/server/db/countries.ts +++ b/server/db/countries.ts @@ -1,1014 +1,1014 @@ export const COUNTRIES = [ { - "name": "ALL COUNTRIES", - "code": "ALL" // THIS IS AN INVALID CC SO IT WILL NEVER MATCH + name: "ALL COUNTRIES", + code: "ALL" // THIS IS AN INVALID CC SO IT WILL NEVER MATCH }, { - "name": "Afghanistan", - "code": "AF" + name: "Afghanistan", + code: "AF" }, { - "name": "Albania", - "code": "AL" + name: "Albania", + code: "AL" }, { - "name": "Algeria", - "code": "DZ" + name: "Algeria", + code: "DZ" }, { - "name": "American Samoa", - "code": "AS" + name: "American Samoa", + code: "AS" }, { - "name": "Andorra", - "code": "AD" + name: "Andorra", + code: "AD" }, { - "name": "Angola", - "code": "AO" + name: "Angola", + code: "AO" }, { - "name": "Anguilla", - "code": "AI" + name: "Anguilla", + code: "AI" }, { - "name": "Antarctica", - "code": "AQ" + name: "Antarctica", + code: "AQ" }, { - "name": "Antigua and Barbuda", - "code": "AG" + name: "Antigua and Barbuda", + code: "AG" }, { - "name": "Argentina", - "code": "AR" + name: "Argentina", + code: "AR" }, { - "name": "Armenia", - "code": "AM" + name: "Armenia", + code: "AM" }, { - "name": "Aruba", - "code": "AW" + name: "Aruba", + code: "AW" }, { - "name": "Asia/Pacific Region", - "code": "AP" + name: "Asia/Pacific Region", + code: "AP" }, { - "name": "Australia", - "code": "AU" + name: "Australia", + code: "AU" }, { - "name": "Austria", - "code": "AT" + name: "Austria", + code: "AT" }, { - "name": "Azerbaijan", - "code": "AZ" + name: "Azerbaijan", + code: "AZ" }, { - "name": "Bahamas", - "code": "BS" + name: "Bahamas", + code: "BS" }, { - "name": "Bahrain", - "code": "BH" + name: "Bahrain", + code: "BH" }, { - "name": "Bangladesh", - "code": "BD" + name: "Bangladesh", + code: "BD" }, { - "name": "Barbados", - "code": "BB" + name: "Barbados", + code: "BB" }, { - "name": "Belarus", - "code": "BY" + name: "Belarus", + code: "BY" }, { - "name": "Belgium", - "code": "BE" + name: "Belgium", + code: "BE" }, { - "name": "Belize", - "code": "BZ" + name: "Belize", + code: "BZ" }, { - "name": "Benin", - "code": "BJ" + name: "Benin", + code: "BJ" }, { - "name": "Bermuda", - "code": "BM" + name: "Bermuda", + code: "BM" }, { - "name": "Bhutan", - "code": "BT" + name: "Bhutan", + code: "BT" }, { - "name": "Bolivia", - "code": "BO" + name: "Bolivia", + code: "BO" }, { - "name": "Bonaire, Sint Eustatius and Saba", - "code": "BQ" + name: "Bonaire, Sint Eustatius and Saba", + code: "BQ" }, { - "name": "Bosnia and Herzegovina", - "code": "BA" + name: "Bosnia and Herzegovina", + code: "BA" }, { - "name": "Botswana", - "code": "BW" + name: "Botswana", + code: "BW" }, { - "name": "Bouvet Island", - "code": "BV" + name: "Bouvet Island", + code: "BV" }, { - "name": "Brazil", - "code": "BR" + name: "Brazil", + code: "BR" }, { - "name": "British Indian Ocean Territory", - "code": "IO" + name: "British Indian Ocean Territory", + code: "IO" }, { - "name": "Brunei Darussalam", - "code": "BN" + name: "Brunei Darussalam", + code: "BN" }, { - "name": "Bulgaria", - "code": "BG" + name: "Bulgaria", + code: "BG" }, { - "name": "Burkina Faso", - "code": "BF" + name: "Burkina Faso", + code: "BF" }, { - "name": "Burundi", - "code": "BI" + name: "Burundi", + code: "BI" }, { - "name": "Cambodia", - "code": "KH" + name: "Cambodia", + code: "KH" }, { - "name": "Cameroon", - "code": "CM" + name: "Cameroon", + code: "CM" }, { - "name": "Canada", - "code": "CA" + name: "Canada", + code: "CA" }, { - "name": "Cape Verde", - "code": "CV" + name: "Cape Verde", + code: "CV" }, { - "name": "Cayman Islands", - "code": "KY" + name: "Cayman Islands", + code: "KY" }, { - "name": "Central African Republic", - "code": "CF" + name: "Central African Republic", + code: "CF" }, { - "name": "Chad", - "code": "TD" + name: "Chad", + code: "TD" }, { - "name": "Chile", - "code": "CL" + name: "Chile", + code: "CL" }, { - "name": "China", - "code": "CN" + name: "China", + code: "CN" }, { - "name": "Christmas Island", - "code": "CX" + name: "Christmas Island", + code: "CX" }, { - "name": "Cocos (Keeling) Islands", - "code": "CC" + name: "Cocos (Keeling) Islands", + code: "CC" }, { - "name": "Colombia", - "code": "CO" + name: "Colombia", + code: "CO" }, { - "name": "Comoros", - "code": "KM" + name: "Comoros", + code: "KM" }, { - "name": "Congo", - "code": "CG" + name: "Congo", + code: "CG" }, { - "name": "Congo, The Democratic Republic of the", - "code": "CD" + name: "Congo, The Democratic Republic of the", + code: "CD" }, { - "name": "Cook Islands", - "code": "CK" + name: "Cook Islands", + code: "CK" }, { - "name": "Costa Rica", - "code": "CR" + name: "Costa Rica", + code: "CR" }, { - "name": "Croatia", - "code": "HR" + name: "Croatia", + code: "HR" }, { - "name": "Cuba", - "code": "CU" + name: "Cuba", + code: "CU" }, { - "name": "Curaçao", - "code": "CW" + name: "Curaçao", + code: "CW" }, { - "name": "Cyprus", - "code": "CY" + name: "Cyprus", + code: "CY" }, { - "name": "Czech Republic", - "code": "CZ" + name: "Czech Republic", + code: "CZ" }, { - "name": "Côte d'Ivoire", - "code": "CI" + name: "Côte d'Ivoire", + code: "CI" }, { - "name": "Denmark", - "code": "DK" + name: "Denmark", + code: "DK" }, { - "name": "Djibouti", - "code": "DJ" + name: "Djibouti", + code: "DJ" }, { - "name": "Dominica", - "code": "DM" + name: "Dominica", + code: "DM" }, { - "name": "Dominican Republic", - "code": "DO" + name: "Dominican Republic", + code: "DO" }, { - "name": "Ecuador", - "code": "EC" + name: "Ecuador", + code: "EC" }, { - "name": "Egypt", - "code": "EG" + name: "Egypt", + code: "EG" }, { - "name": "El Salvador", - "code": "SV" + name: "El Salvador", + code: "SV" }, { - "name": "Equatorial Guinea", - "code": "GQ" + name: "Equatorial Guinea", + code: "GQ" }, { - "name": "Eritrea", - "code": "ER" + name: "Eritrea", + code: "ER" }, { - "name": "Estonia", - "code": "EE" + name: "Estonia", + code: "EE" }, { - "name": "Ethiopia", - "code": "ET" + name: "Ethiopia", + code: "ET" }, { - "name": "Falkland Islands (Malvinas)", - "code": "FK" + name: "Falkland Islands (Malvinas)", + code: "FK" }, { - "name": "Faroe Islands", - "code": "FO" + name: "Faroe Islands", + code: "FO" }, { - "name": "Fiji", - "code": "FJ" + name: "Fiji", + code: "FJ" }, { - "name": "Finland", - "code": "FI" + name: "Finland", + code: "FI" }, { - "name": "France", - "code": "FR" + name: "France", + code: "FR" }, { - "name": "French Guiana", - "code": "GF" + name: "French Guiana", + code: "GF" }, { - "name": "French Polynesia", - "code": "PF" + name: "French Polynesia", + code: "PF" }, { - "name": "French Southern Territories", - "code": "TF" + name: "French Southern Territories", + code: "TF" }, { - "name": "Gabon", - "code": "GA" + name: "Gabon", + code: "GA" }, { - "name": "Gambia", - "code": "GM" + name: "Gambia", + code: "GM" }, { - "name": "Georgia", - "code": "GE" + name: "Georgia", + code: "GE" }, { - "name": "Germany", - "code": "DE" + name: "Germany", + code: "DE" }, { - "name": "Ghana", - "code": "GH" + name: "Ghana", + code: "GH" }, { - "name": "Gibraltar", - "code": "GI" + name: "Gibraltar", + code: "GI" }, { - "name": "Greece", - "code": "GR" + name: "Greece", + code: "GR" }, { - "name": "Greenland", - "code": "GL" + name: "Greenland", + code: "GL" }, { - "name": "Grenada", - "code": "GD" + name: "Grenada", + code: "GD" }, { - "name": "Guadeloupe", - "code": "GP" + name: "Guadeloupe", + code: "GP" }, { - "name": "Guam", - "code": "GU" + name: "Guam", + code: "GU" }, { - "name": "Guatemala", - "code": "GT" + name: "Guatemala", + code: "GT" }, { - "name": "Guernsey", - "code": "GG" + name: "Guernsey", + code: "GG" }, { - "name": "Guinea", - "code": "GN" + name: "Guinea", + code: "GN" }, { - "name": "Guinea-Bissau", - "code": "GW" + name: "Guinea-Bissau", + code: "GW" }, { - "name": "Guyana", - "code": "GY" + name: "Guyana", + code: "GY" }, { - "name": "Haiti", - "code": "HT" + name: "Haiti", + code: "HT" }, { - "name": "Heard Island and Mcdonald Islands", - "code": "HM" + name: "Heard Island and Mcdonald Islands", + code: "HM" }, { - "name": "Holy See (Vatican City State)", - "code": "VA" + name: "Holy See (Vatican City State)", + code: "VA" }, { - "name": "Honduras", - "code": "HN" + name: "Honduras", + code: "HN" }, { - "name": "Hong Kong", - "code": "HK" + name: "Hong Kong", + code: "HK" }, { - "name": "Hungary", - "code": "HU" + name: "Hungary", + code: "HU" }, { - "name": "Iceland", - "code": "IS" + name: "Iceland", + code: "IS" }, { - "name": "India", - "code": "IN" + name: "India", + code: "IN" }, { - "name": "Indonesia", - "code": "ID" + name: "Indonesia", + code: "ID" }, { - "name": "Iran, Islamic Republic Of", - "code": "IR" + name: "Iran, Islamic Republic Of", + code: "IR" }, { - "name": "Iraq", - "code": "IQ" + name: "Iraq", + code: "IQ" }, { - "name": "Ireland", - "code": "IE" + name: "Ireland", + code: "IE" }, { - "name": "Isle of Man", - "code": "IM" + name: "Isle of Man", + code: "IM" }, { - "name": "Israel", - "code": "IL" + name: "Israel", + code: "IL" }, { - "name": "Italy", - "code": "IT" + name: "Italy", + code: "IT" }, { - "name": "Jamaica", - "code": "JM" + name: "Jamaica", + code: "JM" }, { - "name": "Japan", - "code": "JP" + name: "Japan", + code: "JP" }, { - "name": "Jersey", - "code": "JE" + name: "Jersey", + code: "JE" }, { - "name": "Jordan", - "code": "JO" + name: "Jordan", + code: "JO" }, { - "name": "Kazakhstan", - "code": "KZ" + name: "Kazakhstan", + code: "KZ" }, { - "name": "Kenya", - "code": "KE" + name: "Kenya", + code: "KE" }, { - "name": "Kiribati", - "code": "KI" + name: "Kiribati", + code: "KI" }, { - "name": "Korea, Republic of", - "code": "KR" + name: "Korea, Republic of", + code: "KR" }, { - "name": "Kuwait", - "code": "KW" + name: "Kuwait", + code: "KW" }, { - "name": "Kyrgyzstan", - "code": "KG" + name: "Kyrgyzstan", + code: "KG" }, { - "name": "Laos", - "code": "LA" + name: "Laos", + code: "LA" }, { - "name": "Latvia", - "code": "LV" + name: "Latvia", + code: "LV" }, { - "name": "Lebanon", - "code": "LB" + name: "Lebanon", + code: "LB" }, { - "name": "Lesotho", - "code": "LS" + name: "Lesotho", + code: "LS" }, { - "name": "Liberia", - "code": "LR" + name: "Liberia", + code: "LR" }, { - "name": "Libyan Arab Jamahiriya", - "code": "LY" + name: "Libyan Arab Jamahiriya", + code: "LY" }, { - "name": "Liechtenstein", - "code": "LI" + name: "Liechtenstein", + code: "LI" }, { - "name": "Lithuania", - "code": "LT" + name: "Lithuania", + code: "LT" }, { - "name": "Luxembourg", - "code": "LU" + name: "Luxembourg", + code: "LU" }, { - "name": "Macao", - "code": "MO" + name: "Macao", + code: "MO" }, { - "name": "Madagascar", - "code": "MG" + name: "Madagascar", + code: "MG" }, { - "name": "Malawi", - "code": "MW" + name: "Malawi", + code: "MW" }, { - "name": "Malaysia", - "code": "MY" + name: "Malaysia", + code: "MY" }, { - "name": "Maldives", - "code": "MV" + name: "Maldives", + code: "MV" }, { - "name": "Mali", - "code": "ML" + name: "Mali", + code: "ML" }, { - "name": "Malta", - "code": "MT" + name: "Malta", + code: "MT" }, { - "name": "Marshall Islands", - "code": "MH" + name: "Marshall Islands", + code: "MH" }, { - "name": "Martinique", - "code": "MQ" + name: "Martinique", + code: "MQ" }, { - "name": "Mauritania", - "code": "MR" + name: "Mauritania", + code: "MR" }, { - "name": "Mauritius", - "code": "MU" + name: "Mauritius", + code: "MU" }, { - "name": "Mayotte", - "code": "YT" + name: "Mayotte", + code: "YT" }, { - "name": "Mexico", - "code": "MX" + name: "Mexico", + code: "MX" }, { - "name": "Micronesia, Federated States of", - "code": "FM" + name: "Micronesia, Federated States of", + code: "FM" }, { - "name": "Moldova, Republic of", - "code": "MD" + name: "Moldova, Republic of", + code: "MD" }, { - "name": "Monaco", - "code": "MC" + name: "Monaco", + code: "MC" }, { - "name": "Mongolia", - "code": "MN" + name: "Mongolia", + code: "MN" }, { - "name": "Montenegro", - "code": "ME" + name: "Montenegro", + code: "ME" }, { - "name": "Montserrat", - "code": "MS" + name: "Montserrat", + code: "MS" }, { - "name": "Morocco", - "code": "MA" + name: "Morocco", + code: "MA" }, { - "name": "Mozambique", - "code": "MZ" + name: "Mozambique", + code: "MZ" }, { - "name": "Myanmar", - "code": "MM" + name: "Myanmar", + code: "MM" }, { - "name": "Namibia", - "code": "NA" + name: "Namibia", + code: "NA" }, { - "name": "Nauru", - "code": "NR" + name: "Nauru", + code: "NR" }, { - "name": "Nepal", - "code": "NP" + name: "Nepal", + code: "NP" }, { - "name": "Netherlands", - "code": "NL" + name: "Netherlands", + code: "NL" }, { - "name": "Netherlands Antilles", - "code": "AN" + name: "Netherlands Antilles", + code: "AN" }, { - "name": "New Caledonia", - "code": "NC" + name: "New Caledonia", + code: "NC" }, { - "name": "New Zealand", - "code": "NZ" + name: "New Zealand", + code: "NZ" }, { - "name": "Nicaragua", - "code": "NI" + name: "Nicaragua", + code: "NI" }, { - "name": "Niger", - "code": "NE" + name: "Niger", + code: "NE" }, { - "name": "Nigeria", - "code": "NG" + name: "Nigeria", + code: "NG" }, { - "name": "Niue", - "code": "NU" + name: "Niue", + code: "NU" }, { - "name": "Norfolk Island", - "code": "NF" + name: "Norfolk Island", + code: "NF" }, { - "name": "North Korea", - "code": "KP" + name: "North Korea", + code: "KP" }, { - "name": "North Macedonia", - "code": "MK" + name: "North Macedonia", + code: "MK" }, { - "name": "Northern Mariana Islands", - "code": "MP" + name: "Northern Mariana Islands", + code: "MP" }, { - "name": "Norway", - "code": "NO" + name: "Norway", + code: "NO" }, { - "name": "Oman", - "code": "OM" + name: "Oman", + code: "OM" }, { - "name": "Pakistan", - "code": "PK" + name: "Pakistan", + code: "PK" }, { - "name": "Palau", - "code": "PW" + name: "Palau", + code: "PW" }, { - "name": "Palestinian Territory, Occupied", - "code": "PS" + name: "Palestinian Territory, Occupied", + code: "PS" }, { - "name": "Panama", - "code": "PA" + name: "Panama", + code: "PA" }, { - "name": "Papua New Guinea", - "code": "PG" + name: "Papua New Guinea", + code: "PG" }, { - "name": "Paraguay", - "code": "PY" + name: "Paraguay", + code: "PY" }, { - "name": "Peru", - "code": "PE" + name: "Peru", + code: "PE" }, { - "name": "Philippines", - "code": "PH" + name: "Philippines", + code: "PH" }, { - "name": "Pitcairn Islands", - "code": "PN" + name: "Pitcairn Islands", + code: "PN" }, { - "name": "Poland", - "code": "PL" + name: "Poland", + code: "PL" }, { - "name": "Portugal", - "code": "PT" + name: "Portugal", + code: "PT" }, { - "name": "Puerto Rico", - "code": "PR" + name: "Puerto Rico", + code: "PR" }, { - "name": "Qatar", - "code": "QA" + name: "Qatar", + code: "QA" }, { - "name": "Reunion", - "code": "RE" + name: "Reunion", + code: "RE" }, { - "name": "Romania", - "code": "RO" + name: "Romania", + code: "RO" }, { - "name": "Russian Federation", - "code": "RU" + name: "Russian Federation", + code: "RU" }, { - "name": "Rwanda", - "code": "RW" + name: "Rwanda", + code: "RW" }, { - "name": "Saint Barthélemy", - "code": "BL" + name: "Saint Barthélemy", + code: "BL" }, { - "name": "Saint Helena", - "code": "SH" + name: "Saint Helena", + code: "SH" }, { - "name": "Saint Kitts and Nevis", - "code": "KN" + name: "Saint Kitts and Nevis", + code: "KN" }, { - "name": "Saint Lucia", - "code": "LC" + name: "Saint Lucia", + code: "LC" }, { - "name": "Saint Martin", - "code": "MF" + name: "Saint Martin", + code: "MF" }, { - "name": "Saint Pierre and Miquelon", - "code": "PM" + name: "Saint Pierre and Miquelon", + code: "PM" }, { - "name": "Saint Vincent and the Grenadines", - "code": "VC" + name: "Saint Vincent and the Grenadines", + code: "VC" }, { - "name": "Samoa", - "code": "WS" + name: "Samoa", + code: "WS" }, { - "name": "San Marino", - "code": "SM" + name: "San Marino", + code: "SM" }, { - "name": "Sao Tome and Principe", - "code": "ST" + name: "Sao Tome and Principe", + code: "ST" }, { - "name": "Saudi Arabia", - "code": "SA" + name: "Saudi Arabia", + code: "SA" }, { - "name": "Senegal", - "code": "SN" + name: "Senegal", + code: "SN" }, { - "name": "Serbia", - "code": "RS" + name: "Serbia", + code: "RS" }, { - "name": "Serbia and Montenegro", - "code": "CS" + name: "Serbia and Montenegro", + code: "CS" }, { - "name": "Seychelles", - "code": "SC" + name: "Seychelles", + code: "SC" }, { - "name": "Sierra Leone", - "code": "SL" + name: "Sierra Leone", + code: "SL" }, { - "name": "Singapore", - "code": "SG" + name: "Singapore", + code: "SG" }, { - "name": "Sint Maarten", - "code": "SX" + name: "Sint Maarten", + code: "SX" }, { - "name": "Slovakia", - "code": "SK" + name: "Slovakia", + code: "SK" }, { - "name": "Slovenia", - "code": "SI" + name: "Slovenia", + code: "SI" }, { - "name": "Solomon Islands", - "code": "SB" + name: "Solomon Islands", + code: "SB" }, { - "name": "Somalia", - "code": "SO" + name: "Somalia", + code: "SO" }, { - "name": "South Africa", - "code": "ZA" + name: "South Africa", + code: "ZA" }, { - "name": "South Georgia and the South Sandwich Islands", - "code": "GS" + name: "South Georgia and the South Sandwich Islands", + code: "GS" }, { - "name": "South Sudan", - "code": "SS" + name: "South Sudan", + code: "SS" }, { - "name": "Spain", - "code": "ES" + name: "Spain", + code: "ES" }, { - "name": "Sri Lanka", - "code": "LK" + name: "Sri Lanka", + code: "LK" }, { - "name": "Sudan", - "code": "SD" + name: "Sudan", + code: "SD" }, { - "name": "Suriname", - "code": "SR" + name: "Suriname", + code: "SR" }, { - "name": "Svalbard and Jan Mayen", - "code": "SJ" + name: "Svalbard and Jan Mayen", + code: "SJ" }, { - "name": "Swaziland", - "code": "SZ" + name: "Swaziland", + code: "SZ" }, { - "name": "Sweden", - "code": "SE" + name: "Sweden", + code: "SE" }, { - "name": "Switzerland", - "code": "CH" + name: "Switzerland", + code: "CH" }, { - "name": "Syrian Arab Republic", - "code": "SY" + name: "Syrian Arab Republic", + code: "SY" }, { - "name": "Taiwan", - "code": "TW" + name: "Taiwan", + code: "TW" }, { - "name": "Tajikistan", - "code": "TJ" + name: "Tajikistan", + code: "TJ" }, { - "name": "Tanzania, United Republic of", - "code": "TZ" + name: "Tanzania, United Republic of", + code: "TZ" }, { - "name": "Thailand", - "code": "TH" + name: "Thailand", + code: "TH" }, { - "name": "Timor-Leste", - "code": "TL" + name: "Timor-Leste", + code: "TL" }, { - "name": "Togo", - "code": "TG" + name: "Togo", + code: "TG" }, { - "name": "Tokelau", - "code": "TK" + name: "Tokelau", + code: "TK" }, { - "name": "Tonga", - "code": "TO" + name: "Tonga", + code: "TO" }, { - "name": "Trinidad and Tobago", - "code": "TT" + name: "Trinidad and Tobago", + code: "TT" }, { - "name": "Tunisia", - "code": "TN" + name: "Tunisia", + code: "TN" }, { - "name": "Turkey", - "code": "TR" + name: "Turkey", + code: "TR" }, { - "name": "Turkmenistan", - "code": "TM" + name: "Turkmenistan", + code: "TM" }, { - "name": "Turks and Caicos Islands", - "code": "TC" + name: "Turks and Caicos Islands", + code: "TC" }, { - "name": "Tuvalu", - "code": "TV" + name: "Tuvalu", + code: "TV" }, { - "name": "Uganda", - "code": "UG" + name: "Uganda", + code: "UG" }, { - "name": "Ukraine", - "code": "UA" + name: "Ukraine", + code: "UA" }, { - "name": "United Arab Emirates", - "code": "AE" + name: "United Arab Emirates", + code: "AE" }, { - "name": "United Kingdom", - "code": "GB" + name: "United Kingdom", + code: "GB" }, { - "name": "United States", - "code": "US" + name: "United States", + code: "US" }, { - "name": "United States Minor Outlying Islands", - "code": "UM" + name: "United States Minor Outlying Islands", + code: "UM" }, { - "name": "Uruguay", - "code": "UY" + name: "Uruguay", + code: "UY" }, { - "name": "Uzbekistan", - "code": "UZ" + name: "Uzbekistan", + code: "UZ" }, { - "name": "Vanuatu", - "code": "VU" + name: "Vanuatu", + code: "VU" }, { - "name": "Venezuela", - "code": "VE" + name: "Venezuela", + code: "VE" }, { - "name": "Vietnam", - "code": "VN" + name: "Vietnam", + code: "VN" }, { - "name": "Virgin Islands, British", - "code": "VG" + name: "Virgin Islands, British", + code: "VG" }, { - "name": "Virgin Islands, U.S.", - "code": "VI" + name: "Virgin Islands, U.S.", + code: "VI" }, { - "name": "Wallis and Futuna", - "code": "WF" + name: "Wallis and Futuna", + code: "WF" }, { - "name": "Western Sahara", - "code": "EH" + name: "Western Sahara", + code: "EH" }, { - "name": "Yemen", - "code": "YE" + name: "Yemen", + code: "YE" }, { - "name": "Zambia", - "code": "ZM" + name: "Zambia", + code: "ZM" }, { - "name": "Zimbabwe", - "code": "ZW" + name: "Zimbabwe", + code: "ZW" }, { - "name": "Åland Islands", - "code": "AX" + name: "Åland Islands", + code: "AX" } -]; \ No newline at end of file +]; diff --git a/server/db/names.json b/server/db/names.json index fdf545fb9..eb104691e 100644 --- a/server/db/names.json +++ b/server/db/names.json @@ -1708,4 +1708,4 @@ "Desert Box Turtle", "African Striped Weasel" ] -} \ No newline at end of file +} diff --git a/server/db/pg/schema/privateSchema.ts b/server/db/pg/schema/privateSchema.ts index 17d262c61..cb809b710 100644 --- a/server/db/pg/schema/privateSchema.ts +++ b/server/db/pg/schema/privateSchema.ts @@ -215,42 +215,56 @@ export const sessionTransferToken = pgTable("sessionTransferToken", { expiresAt: bigint("expiresAt", { mode: "number" }).notNull() }); -export const actionAuditLog = pgTable("actionAuditLog", { - id: serial("id").primaryKey(), - timestamp: bigint("timestamp", { mode: "number" }).notNull(), // this is EPOCH time in seconds - orgId: varchar("orgId") - .notNull() - .references(() => orgs.orgId, { onDelete: "cascade" }), - actorType: varchar("actorType", { length: 50 }).notNull(), - actor: varchar("actor", { length: 255 }).notNull(), - actorId: varchar("actorId", { length: 255 }).notNull(), - action: varchar("action", { length: 100 }).notNull(), - metadata: text("metadata") -}, (table) => ([ - index("idx_actionAuditLog_timestamp").on(table.timestamp), - index("idx_actionAuditLog_org_timestamp").on(table.orgId, table.timestamp) -])); +export const actionAuditLog = pgTable( + "actionAuditLog", + { + id: serial("id").primaryKey(), + timestamp: bigint("timestamp", { mode: "number" }).notNull(), // this is EPOCH time in seconds + orgId: varchar("orgId") + .notNull() + .references(() => orgs.orgId, { onDelete: "cascade" }), + actorType: varchar("actorType", { length: 50 }).notNull(), + actor: varchar("actor", { length: 255 }).notNull(), + actorId: varchar("actorId", { length: 255 }).notNull(), + action: varchar("action", { length: 100 }).notNull(), + metadata: text("metadata") + }, + (table) => [ + index("idx_actionAuditLog_timestamp").on(table.timestamp), + index("idx_actionAuditLog_org_timestamp").on( + table.orgId, + table.timestamp + ) + ] +); -export const accessAuditLog = pgTable("accessAuditLog", { - id: serial("id").primaryKey(), - timestamp: bigint("timestamp", { mode: "number" }).notNull(), // this is EPOCH time in seconds - orgId: varchar("orgId") - .notNull() - .references(() => orgs.orgId, { onDelete: "cascade" }), - actorType: varchar("actorType", { length: 50 }), - actor: varchar("actor", { length: 255 }), - actorId: varchar("actorId", { length: 255 }), - resourceId: integer("resourceId"), - ip: varchar("ip", { length: 45 }), - type: varchar("type", { length: 100 }).notNull(), - action: boolean("action").notNull(), - location: text("location"), - userAgent: text("userAgent"), - metadata: text("metadata") -}, (table) => ([ - index("idx_identityAuditLog_timestamp").on(table.timestamp), - index("idx_identityAuditLog_org_timestamp").on(table.orgId, table.timestamp) -])); +export const accessAuditLog = pgTable( + "accessAuditLog", + { + id: serial("id").primaryKey(), + timestamp: bigint("timestamp", { mode: "number" }).notNull(), // this is EPOCH time in seconds + orgId: varchar("orgId") + .notNull() + .references(() => orgs.orgId, { onDelete: "cascade" }), + actorType: varchar("actorType", { length: 50 }), + actor: varchar("actor", { length: 255 }), + actorId: varchar("actorId", { length: 255 }), + resourceId: integer("resourceId"), + ip: varchar("ip", { length: 45 }), + type: varchar("type", { length: 100 }).notNull(), + action: boolean("action").notNull(), + location: text("location"), + userAgent: text("userAgent"), + metadata: text("metadata") + }, + (table) => [ + index("idx_identityAuditLog_timestamp").on(table.timestamp), + index("idx_identityAuditLog_org_timestamp").on( + table.orgId, + table.timestamp + ) + ] +); export type Limit = InferSelectModel; export type Account = InferSelectModel; @@ -270,4 +284,4 @@ export type RemoteExitNodeSession = InferSelectModel< export type ExitNodeOrg = InferSelectModel; export type LoginPage = InferSelectModel; export type ActionAuditLog = InferSelectModel; -export type AccessAuditLog = InferSelectModel; \ No newline at end of file +export type AccessAuditLog = InferSelectModel; diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index a0020a0e8..71877f2f1 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -177,7 +177,7 @@ export const targetHealthCheck = pgTable("targetHealthCheck", { hcMethod: varchar("hcMethod").default("GET"), hcStatus: integer("hcStatus"), // http code hcHealth: text("hcHealth").default("unknown"), // "unknown", "healthy", "unhealthy" - hcTlsServerName: text("hcTlsServerName"), + hcTlsServerName: text("hcTlsServerName") }); export const exitNodes = pgTable("exitNodes", { diff --git a/server/db/queries/verifySessionQueries.ts b/server/db/queries/verifySessionQueries.ts index 85bd7cc7e..774c4e53d 100644 --- a/server/db/queries/verifySessionQueries.ts +++ b/server/db/queries/verifySessionQueries.ts @@ -52,10 +52,7 @@ export async function getResourceByDomain( resourceHeaderAuth, eq(resourceHeaderAuth.resourceId, resources.resourceId) ) - .innerJoin( - orgs, - eq(orgs.orgId, resources.orgId) - ) + .innerJoin(orgs, eq(orgs.orgId, resources.orgId)) .where(eq(resources.fullDomain, domain)) .limit(1); diff --git a/server/db/sqlite/migrate.ts b/server/db/sqlite/migrate.ts index e4a730d0a..7c337ae2d 100644 --- a/server/db/sqlite/migrate.ts +++ b/server/db/sqlite/migrate.ts @@ -8,7 +8,7 @@ const runMigrations = async () => { console.log("Running migrations..."); try { migrate(db as any, { - migrationsFolder: migrationsFolder, + migrationsFolder: migrationsFolder }); console.log("Migrations completed successfully."); } catch (error) { diff --git a/server/db/sqlite/schema/privateSchema.ts b/server/db/sqlite/schema/privateSchema.ts index 653967700..975a949b0 100644 --- a/server/db/sqlite/schema/privateSchema.ts +++ b/server/db/sqlite/schema/privateSchema.ts @@ -29,7 +29,9 @@ export const certificates = sqliteTable("certificates", { }); export const dnsChallenge = sqliteTable("dnsChallenges", { - dnsChallengeId: integer("dnsChallengeId").primaryKey({ autoIncrement: true }), + dnsChallengeId: integer("dnsChallengeId").primaryKey({ + autoIncrement: true + }), domain: text("domain").notNull(), token: text("token").notNull(), keyAuthorization: text("keyAuthorization").notNull(), @@ -61,9 +63,7 @@ export const customers = sqliteTable("customers", { }); export const subscriptions = sqliteTable("subscriptions", { - subscriptionId: text("subscriptionId") - .primaryKey() - .notNull(), + subscriptionId: text("subscriptionId").primaryKey().notNull(), customerId: text("customerId") .notNull() .references(() => customers.customerId, { onDelete: "cascade" }), @@ -75,7 +75,9 @@ export const subscriptions = sqliteTable("subscriptions", { }); export const subscriptionItems = sqliteTable("subscriptionItems", { - subscriptionItemId: integer("subscriptionItemId").primaryKey({ autoIncrement: true }), + subscriptionItemId: integer("subscriptionItemId").primaryKey({ + autoIncrement: true + }), subscriptionId: text("subscriptionId") .notNull() .references(() => subscriptions.subscriptionId, { @@ -129,7 +131,9 @@ export const limits = sqliteTable("limits", { }); export const usageNotifications = sqliteTable("usageNotifications", { - notificationId: integer("notificationId").primaryKey({ autoIncrement: true }), + notificationId: integer("notificationId").primaryKey({ + autoIncrement: true + }), orgId: text("orgId") .notNull() .references(() => orgs.orgId, { onDelete: "cascade" }), @@ -210,42 +214,56 @@ export const sessionTransferToken = sqliteTable("sessionTransferToken", { expiresAt: integer("expiresAt").notNull() }); -export const actionAuditLog = sqliteTable("actionAuditLog", { - id: integer("id").primaryKey({ autoIncrement: true }), - timestamp: integer("timestamp").notNull(), // this is EPOCH time in seconds - orgId: text("orgId") - .notNull() - .references(() => orgs.orgId, { onDelete: "cascade" }), - actorType: text("actorType").notNull(), - actor: text("actor").notNull(), - actorId: text("actorId").notNull(), - action: text("action").notNull(), - metadata: text("metadata") -}, (table) => ([ - index("idx_actionAuditLog_timestamp").on(table.timestamp), - index("idx_actionAuditLog_org_timestamp").on(table.orgId, table.timestamp) -])); +export const actionAuditLog = sqliteTable( + "actionAuditLog", + { + id: integer("id").primaryKey({ autoIncrement: true }), + timestamp: integer("timestamp").notNull(), // this is EPOCH time in seconds + orgId: text("orgId") + .notNull() + .references(() => orgs.orgId, { onDelete: "cascade" }), + actorType: text("actorType").notNull(), + actor: text("actor").notNull(), + actorId: text("actorId").notNull(), + action: text("action").notNull(), + metadata: text("metadata") + }, + (table) => [ + index("idx_actionAuditLog_timestamp").on(table.timestamp), + index("idx_actionAuditLog_org_timestamp").on( + table.orgId, + table.timestamp + ) + ] +); -export const accessAuditLog = sqliteTable("accessAuditLog", { - id: integer("id").primaryKey({ autoIncrement: true }), - timestamp: integer("timestamp").notNull(), // this is EPOCH time in seconds - orgId: text("orgId") - .notNull() - .references(() => orgs.orgId, { onDelete: "cascade" }), - actorType: text("actorType"), - actor: text("actor"), - actorId: text("actorId"), - resourceId: integer("resourceId"), - ip: text("ip"), - location: text("location"), - type: text("type").notNull(), - action: integer("action", { mode: "boolean" }).notNull(), - userAgent: text("userAgent"), - metadata: text("metadata") -}, (table) => ([ - index("idx_identityAuditLog_timestamp").on(table.timestamp), - index("idx_identityAuditLog_org_timestamp").on(table.orgId, table.timestamp) -])); +export const accessAuditLog = sqliteTable( + "accessAuditLog", + { + id: integer("id").primaryKey({ autoIncrement: true }), + timestamp: integer("timestamp").notNull(), // this is EPOCH time in seconds + orgId: text("orgId") + .notNull() + .references(() => orgs.orgId, { onDelete: "cascade" }), + actorType: text("actorType"), + actor: text("actor"), + actorId: text("actorId"), + resourceId: integer("resourceId"), + ip: text("ip"), + location: text("location"), + type: text("type").notNull(), + action: integer("action", { mode: "boolean" }).notNull(), + userAgent: text("userAgent"), + metadata: text("metadata") + }, + (table) => [ + index("idx_identityAuditLog_timestamp").on(table.timestamp), + index("idx_identityAuditLog_org_timestamp").on( + table.orgId, + table.timestamp + ) + ] +); export type Limit = InferSelectModel; export type Account = InferSelectModel; @@ -265,4 +283,4 @@ export type RemoteExitNodeSession = InferSelectModel< export type ExitNodeOrg = InferSelectModel; export type LoginPage = InferSelectModel; export type ActionAuditLog = InferSelectModel; -export type AccessAuditLog = InferSelectModel; \ No newline at end of file +export type AccessAuditLog = InferSelectModel; diff --git a/server/emails/index.ts b/server/emails/index.ts index 42cfa39c9..01cc66104 100644 --- a/server/emails/index.ts +++ b/server/emails/index.ts @@ -18,10 +18,13 @@ function createEmailClient() { host: emailConfig.smtp_host, port: emailConfig.smtp_port, secure: emailConfig.smtp_secure || false, - auth: (emailConfig.smtp_user && emailConfig.smtp_pass) ? { - user: emailConfig.smtp_user, - pass: emailConfig.smtp_pass - } : null + auth: + emailConfig.smtp_user && emailConfig.smtp_pass + ? { + user: emailConfig.smtp_user, + pass: emailConfig.smtp_pass + } + : null } as SMTPTransport.Options; if (emailConfig.smtp_tls_reject_unauthorized !== undefined) { diff --git a/server/emails/templates/NotifyUsageLimitApproaching.tsx b/server/emails/templates/NotifyUsageLimitApproaching.tsx index beab03004..161b36764 100644 --- a/server/emails/templates/NotifyUsageLimitApproaching.tsx +++ b/server/emails/templates/NotifyUsageLimitApproaching.tsx @@ -19,7 +19,13 @@ interface Props { billingLink: string; // Link to billing page } -export const NotifyUsageLimitApproaching = ({ email, limitName, currentUsage, usageLimit, billingLink }: Props) => { +export const NotifyUsageLimitApproaching = ({ + email, + limitName, + currentUsage, + usageLimit, + billingLink +}: Props) => { const previewText = `Your usage for ${limitName} is approaching the limit.`; const usagePercentage = Math.round((currentUsage / usageLimit) * 100); @@ -37,23 +43,32 @@ export const NotifyUsageLimitApproaching = ({ email, limitName, currentUsage, us Hi there, - We wanted to let you know that your usage for {limitName} is approaching your plan limit. + We wanted to let you know that your usage for{" "} + {limitName} is approaching your + plan limit. - Current Usage: {currentUsage} of {usageLimit} ({usagePercentage}%) + Current Usage: {currentUsage} of{" "} + {usageLimit} ({usagePercentage}%) - Once you reach your limit, some functionality may be restricted or your sites may disconnect until you upgrade your plan or your usage resets. + Once you reach your limit, some functionality may be + restricted or your sites may disconnect until you + upgrade your plan or your usage resets. - To avoid any interruption to your service, we recommend upgrading your plan or monitoring your usage closely. You can upgrade your plan here. + To avoid any interruption to your service, we + recommend upgrading your plan or monitoring your + usage closely. You can{" "} + upgrade your plan here. - If you have any questions or need assistance, please don't hesitate to reach out to our support team. + If you have any questions or need assistance, please + don't hesitate to reach out to our support team. diff --git a/server/emails/templates/NotifyUsageLimitReached.tsx b/server/emails/templates/NotifyUsageLimitReached.tsx index 783d1b0e6..598416702 100644 --- a/server/emails/templates/NotifyUsageLimitReached.tsx +++ b/server/emails/templates/NotifyUsageLimitReached.tsx @@ -19,7 +19,13 @@ interface Props { billingLink: string; // Link to billing page } -export const NotifyUsageLimitReached = ({ email, limitName, currentUsage, usageLimit, billingLink }: Props) => { +export const NotifyUsageLimitReached = ({ + email, + limitName, + currentUsage, + usageLimit, + billingLink +}: Props) => { const previewText = `You've reached your ${limitName} usage limit - Action required`; const usagePercentage = Math.round((currentUsage / usageLimit) * 100); @@ -32,30 +38,48 @@ export const NotifyUsageLimitReached = ({ email, limitName, currentUsage, usageL - Usage Limit Reached - Action Required + + Usage Limit Reached - Action Required + Hi there, - You have reached your usage limit for {limitName}. + You have reached your usage limit for{" "} + {limitName}. - Current Usage: {currentUsage} of {usageLimit} ({usagePercentage}%) + Current Usage: {currentUsage} of{" "} + {usageLimit} ({usagePercentage}%) - Important: Your functionality may now be restricted and your sites may disconnect until you either upgrade your plan or your usage resets. To prevent any service interruption, immediate action is recommended. + Important: Your functionality may + now be restricted and your sites may disconnect + until you either upgrade your plan or your usage + resets. To prevent any service interruption, + immediate action is recommended. What you can do: -
Upgrade your plan immediately to restore full functionality -
• Monitor your usage to stay within limits in the future +
•{" "} + + Upgrade your plan immediately + {" "} + to restore full functionality +
• Monitor your usage to stay within limits in + the future
- If you have any questions or need immediate assistance, please contact our support team right away. + If you have any questions or need immediate + assistance, please contact our support team right + away. diff --git a/server/integrationApiServer.ts b/server/integrationApiServer.ts index 3416004cb..0ef0c0afc 100644 --- a/server/integrationApiServer.ts +++ b/server/integrationApiServer.ts @@ -5,7 +5,7 @@ import config from "@server/lib/config"; import logger from "@server/logger"; import { errorHandlerMiddleware, - notFoundMiddleware, + notFoundMiddleware } from "@server/middlewares"; import { authenticated, unauthenticated } from "#dynamic/routers/integration"; import { logIncomingMiddleware } from "./middlewares/logIncoming"; diff --git a/server/lib/billing/features.ts b/server/lib/billing/features.ts index b72543cc1..d074894ab 100644 --- a/server/lib/billing/features.ts +++ b/server/lib/billing/features.ts @@ -25,16 +25,22 @@ export const FeatureMeterIdsSandbox: Record = { }; export function getFeatureMeterId(featureId: FeatureId): string { - if (process.env.ENVIRONMENT == "prod" && process.env.SANDBOX_MODE !== "true") { + if ( + process.env.ENVIRONMENT == "prod" && + process.env.SANDBOX_MODE !== "true" + ) { return FeatureMeterIds[featureId]; } else { return FeatureMeterIdsSandbox[featureId]; } } -export function getFeatureIdByMetricId(metricId: string): FeatureId | undefined { - return (Object.entries(FeatureMeterIds) as [FeatureId, string][]) - .find(([_, v]) => v === metricId)?.[0]; +export function getFeatureIdByMetricId( + metricId: string +): FeatureId | undefined { + return (Object.entries(FeatureMeterIds) as [FeatureId, string][]).find( + ([_, v]) => v === metricId + )?.[0]; } export type FeaturePriceSet = { @@ -43,7 +49,8 @@ export type FeaturePriceSet = { [FeatureId.DOMAINS]?: string; // Optional since domains are not billed }; -export const standardFeaturePriceSet: FeaturePriceSet = { // Free tier matches the freeLimitSet +export const standardFeaturePriceSet: FeaturePriceSet = { + // Free tier matches the freeLimitSet [FeatureId.SITE_UPTIME]: "price_1RrQc4D3Ee2Ir7WmaJGZ3MtF", [FeatureId.USERS]: "price_1RrQeJD3Ee2Ir7WmgveP3xea", [FeatureId.EGRESS_DATA_MB]: "price_1RrQXFD3Ee2Ir7WmvGDlgxQk", @@ -51,7 +58,8 @@ export const standardFeaturePriceSet: FeaturePriceSet = { // Free tier matches t [FeatureId.REMOTE_EXIT_NODES]: "price_1S46weD3Ee2Ir7Wm94KEHI4h" }; -export const standardFeaturePriceSetSandbox: FeaturePriceSet = { // Free tier matches the freeLimitSet +export const standardFeaturePriceSetSandbox: FeaturePriceSet = { + // Free tier matches the freeLimitSet [FeatureId.SITE_UPTIME]: "price_1RefFBDCpkOb237BPrKZ8IEU", [FeatureId.USERS]: "price_1ReNa4DCpkOb237Bc67G5muF", [FeatureId.EGRESS_DATA_MB]: "price_1Rfp9LDCpkOb237BwuN5Oiu0", @@ -60,15 +68,20 @@ export const standardFeaturePriceSetSandbox: FeaturePriceSet = { // Free tier ma }; export function getStandardFeaturePriceSet(): FeaturePriceSet { - if (process.env.ENVIRONMENT == "prod" && process.env.SANDBOX_MODE !== "true") { + if ( + process.env.ENVIRONMENT == "prod" && + process.env.SANDBOX_MODE !== "true" + ) { return standardFeaturePriceSet; } else { return standardFeaturePriceSetSandbox; } } -export function getLineItems(featurePriceSet: FeaturePriceSet): Stripe.Checkout.SessionCreateParams.LineItem[] { +export function getLineItems( + featurePriceSet: FeaturePriceSet +): Stripe.Checkout.SessionCreateParams.LineItem[] { return Object.entries(featurePriceSet).map(([featureId, priceId]) => ({ - price: priceId, + price: priceId })); -} \ No newline at end of file +} diff --git a/server/lib/billing/index.ts b/server/lib/billing/index.ts index 6c3ef792c..54c9ee2e0 100644 --- a/server/lib/billing/index.ts +++ b/server/lib/billing/index.ts @@ -2,4 +2,4 @@ export * from "./limitSet"; export * from "./features"; export * from "./limitsService"; export * from "./getOrgTierData"; -export * from "./createCustomer"; \ No newline at end of file +export * from "./createCustomer"; diff --git a/server/lib/billing/limitSet.ts b/server/lib/billing/limitSet.ts index 153d8ae8a..820b121ac 100644 --- a/server/lib/billing/limitSet.ts +++ b/server/lib/billing/limitSet.ts @@ -12,7 +12,7 @@ export const sandboxLimitSet: LimitSet = { [FeatureId.USERS]: { value: 1, description: "Sandbox limit" }, [FeatureId.EGRESS_DATA_MB]: { value: 1000, description: "Sandbox limit" }, // 1 GB [FeatureId.DOMAINS]: { value: 0, description: "Sandbox limit" }, - [FeatureId.REMOTE_EXIT_NODES]: { value: 0, description: "Sandbox limit" }, + [FeatureId.REMOTE_EXIT_NODES]: { value: 0, description: "Sandbox limit" } }; export const freeLimitSet: LimitSet = { @@ -29,7 +29,7 @@ export const freeLimitSet: LimitSet = { export const subscribedLimitSet: LimitSet = { [FeatureId.SITE_UPTIME]: { value: 2232000, - description: "Contact us to increase soft limit.", + description: "Contact us to increase soft limit." }, // 50 sites up for 31 days [FeatureId.USERS]: { value: 150, @@ -38,7 +38,7 @@ export const subscribedLimitSet: LimitSet = { [FeatureId.EGRESS_DATA_MB]: { value: 12000000, description: "Contact us to increase soft limit." - }, // 12000 GB + }, // 12000 GB [FeatureId.DOMAINS]: { value: 25, description: "Contact us to increase soft limit." diff --git a/server/lib/billing/tiers.ts b/server/lib/billing/tiers.ts index 6ccf8898d..ae49a48f2 100644 --- a/server/lib/billing/tiers.ts +++ b/server/lib/billing/tiers.ts @@ -1,22 +1,32 @@ export enum TierId { - STANDARD = "standard", + STANDARD = "standard" } export type TierPriceSet = { [key in TierId]: string; }; -export const tierPriceSet: TierPriceSet = { // Free tier matches the freeLimitSet - [TierId.STANDARD]: "price_1RrQ9cD3Ee2Ir7Wmqdy3KBa0", +export const tierPriceSet: TierPriceSet = { + // Free tier matches the freeLimitSet + [TierId.STANDARD]: "price_1RrQ9cD3Ee2Ir7Wmqdy3KBa0" }; -export const tierPriceSetSandbox: TierPriceSet = { // Free tier matches the freeLimitSet +export const tierPriceSetSandbox: TierPriceSet = { + // Free tier matches the freeLimitSet // when matching tier the keys closer to 0 index are matched first so list the tiers in descending order of value - [TierId.STANDARD]: "price_1RrAYJDCpkOb237By2s1P32m", + [TierId.STANDARD]: "price_1RrAYJDCpkOb237By2s1P32m" }; -export function getTierPriceSet(environment?: string, sandbox_mode?: boolean): TierPriceSet { - if ((process.env.ENVIRONMENT == "prod" && process.env.SANDBOX_MODE !== "true") || (environment === "prod" && sandbox_mode !== true)) { // THIS GETS LOADED CLIENT SIDE AND SERVER SIDE +export function getTierPriceSet( + environment?: string, + sandbox_mode?: boolean +): TierPriceSet { + if ( + (process.env.ENVIRONMENT == "prod" && + process.env.SANDBOX_MODE !== "true") || + (environment === "prod" && sandbox_mode !== true) + ) { + // THIS GETS LOADED CLIENT SIDE AND SERVER SIDE return tierPriceSet; } else { return tierPriceSetSandbox; diff --git a/server/lib/billing/usageService.ts b/server/lib/billing/usageService.ts index 8e6f5e9c1..0fde8eba9 100644 --- a/server/lib/billing/usageService.ts +++ b/server/lib/billing/usageService.ts @@ -19,7 +19,7 @@ import logger from "@server/logger"; import { sendToClient } from "#dynamic/routers/ws"; import { build } from "@server/build"; import { s3Client } from "@server/lib/s3"; -import cache from "@server/lib/cache"; +import cache from "@server/lib/cache"; interface StripeEvent { identifier?: string; diff --git a/server/lib/blueprints/applyNewtDockerBlueprint.ts b/server/lib/blueprints/applyNewtDockerBlueprint.ts index 0fe7c3fe3..f27cc05bb 100644 --- a/server/lib/blueprints/applyNewtDockerBlueprint.ts +++ b/server/lib/blueprints/applyNewtDockerBlueprint.ts @@ -34,7 +34,10 @@ export async function applyNewtDockerBlueprint( return; } - if (isEmptyObject(blueprint["proxy-resources"]) && isEmptyObject(blueprint["client-resources"])) { + if ( + isEmptyObject(blueprint["proxy-resources"]) && + isEmptyObject(blueprint["client-resources"]) + ) { return; } diff --git a/server/lib/blueprints/parseDockerContainers.ts b/server/lib/blueprints/parseDockerContainers.ts index 1510e6e1c..f2cdcfa22 100644 --- a/server/lib/blueprints/parseDockerContainers.ts +++ b/server/lib/blueprints/parseDockerContainers.ts @@ -84,12 +84,20 @@ export function processContainerLabels(containers: Container[]): { // Process proxy resources if (Object.keys(proxyResourceLabels).length > 0) { - processResourceLabels(proxyResourceLabels, container, result["proxy-resources"]); + processResourceLabels( + proxyResourceLabels, + container, + result["proxy-resources"] + ); } // Process client resources if (Object.keys(clientResourceLabels).length > 0) { - processResourceLabels(clientResourceLabels, container, result["client-resources"]); + processResourceLabels( + clientResourceLabels, + container, + result["client-resources"] + ); } }); @@ -161,8 +169,7 @@ function processResourceLabels( const finalTarget = { ...target }; if (!finalTarget.hostname) { finalTarget.hostname = - container.name || - container.hostname; + container.name || container.hostname; } if (!finalTarget.port) { const containerPort = diff --git a/server/lib/blueprints/types.ts b/server/lib/blueprints/types.ts index 9a184a1f2..23e2176ff 100644 --- a/server/lib/blueprints/types.ts +++ b/server/lib/blueprints/types.ts @@ -312,7 +312,7 @@ export const ConfigSchema = z }; delete (data as any)["public-resources"]; } - + // Merge private-resources into client-resources if (data["private-resources"]) { data["client-resources"] = { @@ -321,10 +321,13 @@ export const ConfigSchema = z }; delete (data as any)["private-resources"]; } - + return data as { "proxy-resources": Record>; - "client-resources": Record>; + "client-resources": Record< + string, + z.infer + >; sites: Record>; }; }) diff --git a/server/lib/cache.ts b/server/lib/cache.ts index efa7d2016..82c802802 100644 --- a/server/lib/cache.ts +++ b/server/lib/cache.ts @@ -2,4 +2,4 @@ import NodeCache from "node-cache"; export const cache = new NodeCache({ stdTTL: 3600, checkperiod: 120 }); -export default cache; \ No newline at end of file +export default cache; diff --git a/server/lib/calculateUserClientsForOrgs.ts b/server/lib/calculateUserClientsForOrgs.ts index 9cdae5470..ac3d719f7 100644 --- a/server/lib/calculateUserClientsForOrgs.ts +++ b/server/lib/calculateUserClientsForOrgs.ts @@ -166,7 +166,10 @@ export async function calculateUserClientsForOrgs( ]; // Get next available subnet - const newSubnet = await getNextAvailableClientSubnet(orgId, transaction); + const newSubnet = await getNextAvailableClientSubnet( + orgId, + transaction + ); if (!newSubnet) { logger.warn( `Skipping org ${orgId} for OLM ${olm.olmId} (user ${userId}): no available subnet found` diff --git a/server/lib/certificates.ts b/server/lib/certificates.ts index a6c51c964..f5860ff3a 100644 --- a/server/lib/certificates.ts +++ b/server/lib/certificates.ts @@ -1,4 +1,6 @@ -export async function getValidCertificatesForDomains(domains: Set): Promise< +export async function getValidCertificatesForDomains( + domains: Set +): Promise< Array<{ id: number; domain: string; @@ -10,4 +12,4 @@ export async function getValidCertificatesForDomains(domains: Set): Prom }> > { return []; // stub -} \ No newline at end of file +} diff --git a/server/lib/cleanupLogs.test.ts b/server/lib/cleanupLogs.test.ts index a65e7b01c..dc9326e1d 100644 --- a/server/lib/cleanupLogs.test.ts +++ b/server/lib/cleanupLogs.test.ts @@ -7,7 +7,10 @@ function dateToTimestamp(dateStr: string): number { // Testable version of calculateCutoffTimestamp that accepts a "now" timestamp // This matches the logic in cleanupLogs.ts but allows injecting the current time -function calculateCutoffTimestampWithNow(retentionDays: number, nowTimestamp: number): number { +function calculateCutoffTimestampWithNow( + retentionDays: number, + nowTimestamp: number +): number { if (retentionDays === 9001) { // Special case: data is erased at the end of the year following the year it was generated // This means we delete logs from 2 years ago or older (logs from year Y are deleted after Dec 31 of year Y+1) @@ -28,7 +31,7 @@ function testCalculateCutoffTimestamp() { { const now = dateToTimestamp("2025-12-06T12:00:00Z"); const result = calculateCutoffTimestampWithNow(30, now); - const expected = now - (30 * 24 * 60 * 60); + const expected = now - 30 * 24 * 60 * 60; assertEquals(result, expected, "30 days retention calculation failed"); } @@ -36,7 +39,7 @@ function testCalculateCutoffTimestamp() { { const now = dateToTimestamp("2025-06-15T00:00:00Z"); const result = calculateCutoffTimestampWithNow(90, now); - const expected = now - (90 * 24 * 60 * 60); + const expected = now - 90 * 24 * 60 * 60; assertEquals(result, expected, "90 days retention calculation failed"); } @@ -48,7 +51,11 @@ function testCalculateCutoffTimestamp() { const now = dateToTimestamp("2025-12-06T12:00:00Z"); const result = calculateCutoffTimestampWithNow(9001, now); const expected = dateToTimestamp("2024-01-01T00:00:00Z"); - assertEquals(result, expected, "9001 retention (Dec 2025) - should cutoff at Jan 1, 2024"); + assertEquals( + result, + expected, + "9001 retention (Dec 2025) - should cutoff at Jan 1, 2024" + ); } // Test 4: Special case 9001 - January 2026 @@ -58,7 +65,11 @@ function testCalculateCutoffTimestamp() { const now = dateToTimestamp("2026-01-15T12:00:00Z"); const result = calculateCutoffTimestampWithNow(9001, now); const expected = dateToTimestamp("2025-01-01T00:00:00Z"); - assertEquals(result, expected, "9001 retention (Jan 2026) - should cutoff at Jan 1, 2025"); + assertEquals( + result, + expected, + "9001 retention (Jan 2026) - should cutoff at Jan 1, 2025" + ); } // Test 5: Special case 9001 - December 31, 2025 at 23:59:59 UTC @@ -68,7 +79,11 @@ function testCalculateCutoffTimestamp() { const now = dateToTimestamp("2025-12-31T23:59:59Z"); const result = calculateCutoffTimestampWithNow(9001, now); const expected = dateToTimestamp("2024-01-01T00:00:00Z"); - assertEquals(result, expected, "9001 retention (Dec 31, 2025 23:59:59) - should cutoff at Jan 1, 2024"); + assertEquals( + result, + expected, + "9001 retention (Dec 31, 2025 23:59:59) - should cutoff at Jan 1, 2024" + ); } // Test 6: Special case 9001 - January 1, 2026 at 00:00:01 UTC @@ -78,7 +93,11 @@ function testCalculateCutoffTimestamp() { const now = dateToTimestamp("2026-01-01T00:00:01Z"); const result = calculateCutoffTimestampWithNow(9001, now); const expected = dateToTimestamp("2025-01-01T00:00:00Z"); - assertEquals(result, expected, "9001 retention (Jan 1, 2026 00:00:01) - should cutoff at Jan 1, 2025"); + assertEquals( + result, + expected, + "9001 retention (Jan 1, 2026 00:00:01) - should cutoff at Jan 1, 2025" + ); } // Test 7: Special case 9001 - Mid year 2025 @@ -87,7 +106,11 @@ function testCalculateCutoffTimestamp() { const now = dateToTimestamp("2025-06-15T12:00:00Z"); const result = calculateCutoffTimestampWithNow(9001, now); const expected = dateToTimestamp("2024-01-01T00:00:00Z"); - assertEquals(result, expected, "9001 retention (mid 2025) - should cutoff at Jan 1, 2024"); + assertEquals( + result, + expected, + "9001 retention (mid 2025) - should cutoff at Jan 1, 2024" + ); } // Test 8: Special case 9001 - Early 2024 @@ -96,14 +119,18 @@ function testCalculateCutoffTimestamp() { const now = dateToTimestamp("2024-02-01T12:00:00Z"); const result = calculateCutoffTimestampWithNow(9001, now); const expected = dateToTimestamp("2023-01-01T00:00:00Z"); - assertEquals(result, expected, "9001 retention (early 2024) - should cutoff at Jan 1, 2023"); + assertEquals( + result, + expected, + "9001 retention (early 2024) - should cutoff at Jan 1, 2023" + ); } // Test 9: 1 day retention { const now = dateToTimestamp("2025-12-06T12:00:00Z"); const result = calculateCutoffTimestampWithNow(1, now); - const expected = now - (1 * 24 * 60 * 60); + const expected = now - 1 * 24 * 60 * 60; assertEquals(result, expected, "1 day retention calculation failed"); } @@ -111,7 +138,7 @@ function testCalculateCutoffTimestamp() { { const now = dateToTimestamp("2025-12-06T12:00:00Z"); const result = calculateCutoffTimestampWithNow(365, now); - const expected = now - (365 * 24 * 60 * 60); + const expected = now - 365 * 24 * 60 * 60; assertEquals(result, expected, "365 days retention calculation failed"); } @@ -123,11 +150,19 @@ function testCalculateCutoffTimestamp() { const cutoff = calculateCutoffTimestampWithNow(9001, now); const logFromDec2023 = dateToTimestamp("2023-12-31T23:59:59Z"); const logFromJan2024 = dateToTimestamp("2024-01-01T00:00:00Z"); - + // Log from Dec 2023 should be before cutoff (deleted) - assertEquals(logFromDec2023 < cutoff, true, "Log from Dec 2023 should be deleted"); + assertEquals( + logFromDec2023 < cutoff, + true, + "Log from Dec 2023 should be deleted" + ); // Log from Jan 2024 should be at or after cutoff (kept) - assertEquals(logFromJan2024 >= cutoff, true, "Log from Jan 2024 should be kept"); + assertEquals( + logFromJan2024 >= cutoff, + true, + "Log from Jan 2024 should be kept" + ); } // Test 12: Verify 9001 in 2026 - logs from 2024 should now be deleted @@ -136,11 +171,19 @@ function testCalculateCutoffTimestamp() { const cutoff = calculateCutoffTimestampWithNow(9001, now); const logFromDec2024 = dateToTimestamp("2024-12-31T23:59:59Z"); const logFromJan2025 = dateToTimestamp("2025-01-01T00:00:00Z"); - + // Log from Dec 2024 should be before cutoff (deleted) - assertEquals(logFromDec2024 < cutoff, true, "Log from Dec 2024 should be deleted in 2026"); + assertEquals( + logFromDec2024 < cutoff, + true, + "Log from Dec 2024 should be deleted in 2026" + ); // Log from Jan 2025 should be at or after cutoff (kept) - assertEquals(logFromJan2025 >= cutoff, true, "Log from Jan 2025 should be kept in 2026"); + assertEquals( + logFromJan2025 >= cutoff, + true, + "Log from Jan 2025 should be kept in 2026" + ); } // Test 13: Edge case - exactly at year boundary for 9001 @@ -149,7 +192,11 @@ function testCalculateCutoffTimestamp() { const now = dateToTimestamp("2025-01-01T00:00:00Z"); const result = calculateCutoffTimestampWithNow(9001, now); const expected = dateToTimestamp("2024-01-01T00:00:00Z"); - assertEquals(result, expected, "9001 retention (Jan 1, 2025 00:00:00) - should cutoff at Jan 1, 2024"); + assertEquals( + result, + expected, + "9001 retention (Jan 1, 2025 00:00:00) - should cutoff at Jan 1, 2024" + ); } // Test 14: Verify data from 2024 is kept throughout 2025 when using 9001 @@ -157,18 +204,29 @@ function testCalculateCutoffTimestamp() { { // Running in June 2025 const nowJune2025 = dateToTimestamp("2025-06-15T12:00:00Z"); - const cutoffJune2025 = calculateCutoffTimestampWithNow(9001, nowJune2025); + const cutoffJune2025 = calculateCutoffTimestampWithNow( + 9001, + nowJune2025 + ); const logFromJuly2024 = dateToTimestamp("2024-07-15T12:00:00Z"); - + // Log from July 2024 should be KEPT in June 2025 - assertEquals(logFromJuly2024 >= cutoffJune2025, true, "Log from July 2024 should be kept in June 2025"); - + assertEquals( + logFromJuly2024 >= cutoffJune2025, + true, + "Log from July 2024 should be kept in June 2025" + ); + // Running in January 2026 const nowJan2026 = dateToTimestamp("2026-01-15T12:00:00Z"); const cutoffJan2026 = calculateCutoffTimestampWithNow(9001, nowJan2026); - + // Log from July 2024 should be DELETED in January 2026 - assertEquals(logFromJuly2024 < cutoffJan2026, true, "Log from July 2024 should be deleted in Jan 2026"); + assertEquals( + logFromJuly2024 < cutoffJan2026, + true, + "Log from July 2024 should be deleted in Jan 2026" + ); } // Test 15: Verify the exact requirement - data from 2024 must be purged on December 31, 2025 @@ -176,16 +234,27 @@ function testCalculateCutoffTimestamp() { // On Jan 1, 2026 (now 2026), data from 2024 can be deleted { const logFromMid2024 = dateToTimestamp("2024-06-15T12:00:00Z"); - + // Dec 31, 2025 23:59:59 - still 2025, log should be kept const nowDec31_2025 = dateToTimestamp("2025-12-31T23:59:59Z"); - const cutoffDec31 = calculateCutoffTimestampWithNow(9001, nowDec31_2025); - assertEquals(logFromMid2024 >= cutoffDec31, true, "Log from mid-2024 should be kept on Dec 31, 2025"); - + const cutoffDec31 = calculateCutoffTimestampWithNow( + 9001, + nowDec31_2025 + ); + assertEquals( + logFromMid2024 >= cutoffDec31, + true, + "Log from mid-2024 should be kept on Dec 31, 2025" + ); + // Jan 1, 2026 00:00:00 - now 2026, log can be deleted const nowJan1_2026 = dateToTimestamp("2026-01-01T00:00:00Z"); const cutoffJan1 = calculateCutoffTimestampWithNow(9001, nowJan1_2026); - assertEquals(logFromMid2024 < cutoffJan1, true, "Log from mid-2024 should be deleted on Jan 1, 2026"); + assertEquals( + logFromMid2024 < cutoffJan1, + true, + "Log from mid-2024 should be deleted on Jan 1, 2026" + ); } console.log("All calculateCutoffTimestamp tests passed!"); diff --git a/server/lib/domainUtils.ts b/server/lib/domainUtils.ts index d043ca515..3562df683 100644 --- a/server/lib/domainUtils.ts +++ b/server/lib/domainUtils.ts @@ -4,18 +4,20 @@ import { eq, and } from "drizzle-orm"; import { subdomainSchema } from "@server/lib/schemas"; import { fromError } from "zod-validation-error"; -export type DomainValidationResult = { - success: true; - fullDomain: string; - subdomain: string | null; -} | { - success: false; - error: string; -}; +export type DomainValidationResult = + | { + success: true; + fullDomain: string; + subdomain: string | null; + } + | { + success: false; + error: string; + }; /** * Validates a domain and constructs the full domain based on domain type and subdomain. - * + * * @param domainId - The ID of the domain to validate * @param orgId - The organization ID to check domain access * @param subdomain - Optional subdomain to append (for ns and wildcard domains) @@ -34,7 +36,10 @@ export async function validateAndConstructDomain( .where(eq(domains.domainId, domainId)) .leftJoin( orgDomains, - and(eq(orgDomains.orgId, orgId), eq(orgDomains.domainId, domainId)) + and( + eq(orgDomains.orgId, orgId), + eq(orgDomains.domainId, domainId) + ) ); // Check if domain exists @@ -106,7 +111,7 @@ export async function validateAndConstructDomain( } catch (error) { return { success: false, - error: `An error occurred while validating domain: ${error instanceof Error ? error.message : 'Unknown error'}` + error: `An error occurred while validating domain: ${error instanceof Error ? error.message : "Unknown error"}` }; } } diff --git a/server/lib/encryption.ts b/server/lib/encryption.ts index 7959fa4b9..79caecd1a 100644 --- a/server/lib/encryption.ts +++ b/server/lib/encryption.ts @@ -1,39 +1,39 @@ -import crypto from 'crypto'; +import crypto from "crypto"; export function encryptData(data: string, key: Buffer): string { - const algorithm = 'aes-256-gcm'; - const iv = crypto.randomBytes(16); - const cipher = crypto.createCipheriv(algorithm, key, iv); - - let encrypted = cipher.update(data, 'utf8', 'hex'); - encrypted += cipher.final('hex'); - - const authTag = cipher.getAuthTag(); - - // Combine IV, auth tag, and encrypted data - return iv.toString('hex') + ':' + authTag.toString('hex') + ':' + encrypted; + const algorithm = "aes-256-gcm"; + const iv = crypto.randomBytes(16); + const cipher = crypto.createCipheriv(algorithm, key, iv); + + let encrypted = cipher.update(data, "utf8", "hex"); + encrypted += cipher.final("hex"); + + const authTag = cipher.getAuthTag(); + + // Combine IV, auth tag, and encrypted data + return iv.toString("hex") + ":" + authTag.toString("hex") + ":" + encrypted; } // Helper function to decrypt data (you'll need this to read certificates) export function decryptData(encryptedData: string, key: Buffer): string { - const algorithm = 'aes-256-gcm'; - const parts = encryptedData.split(':'); - - if (parts.length !== 3) { - throw new Error('Invalid encrypted data format'); - } - - const iv = Buffer.from(parts[0], 'hex'); - const authTag = Buffer.from(parts[1], 'hex'); - const encrypted = parts[2]; - - const decipher = crypto.createDecipheriv(algorithm, key, iv); - decipher.setAuthTag(authTag); - - let decrypted = decipher.update(encrypted, 'hex', 'utf8'); - decrypted += decipher.final('utf8'); - - return decrypted; + const algorithm = "aes-256-gcm"; + const parts = encryptedData.split(":"); + + if (parts.length !== 3) { + throw new Error("Invalid encrypted data format"); + } + + const iv = Buffer.from(parts[0], "hex"); + const authTag = Buffer.from(parts[1], "hex"); + const encrypted = parts[2]; + + const decipher = crypto.createDecipheriv(algorithm, key, iv); + decipher.setAuthTag(authTag); + + let decrypted = decipher.update(encrypted, "hex", "utf8"); + decrypted += decipher.final("utf8"); + + return decrypted; } -// openssl rand -hex 32 > config/encryption.key \ No newline at end of file +// openssl rand -hex 32 > config/encryption.key diff --git a/server/lib/exitNodes/getCurrentExitNodeId.ts b/server/lib/exitNodes/getCurrentExitNodeId.ts index d895ce429..1e5c10e3b 100644 --- a/server/lib/exitNodes/getCurrentExitNodeId.ts +++ b/server/lib/exitNodes/getCurrentExitNodeId.ts @@ -30,4 +30,4 @@ export async function getCurrentExitNodeId(): Promise { } } return currentExitNodeId; -} \ No newline at end of file +} diff --git a/server/lib/exitNodes/index.ts b/server/lib/exitNodes/index.ts index ba30ccc20..d1477a683 100644 --- a/server/lib/exitNodes/index.ts +++ b/server/lib/exitNodes/index.ts @@ -1,4 +1,4 @@ export * from "./exitNodes"; export * from "./exitNodeComms"; export * from "./subnet"; -export * from "./getCurrentExitNodeId"; \ No newline at end of file +export * from "./getCurrentExitNodeId"; diff --git a/server/lib/exitNodes/subnet.ts b/server/lib/exitNodes/subnet.ts index c06f1d05e..49e28bd57 100644 --- a/server/lib/exitNodes/subnet.ts +++ b/server/lib/exitNodes/subnet.ts @@ -27,4 +27,4 @@ export async function getNextAvailableSubnet(): Promise { "/" + subnet.split("/")[1]; return subnet; -} \ No newline at end of file +} diff --git a/server/lib/geoip.ts b/server/lib/geoip.ts index 5bc29ef92..8eea4d6fb 100644 --- a/server/lib/geoip.ts +++ b/server/lib/geoip.ts @@ -30,4 +30,4 @@ export async function getCountryCodeForIp( } return; -} \ No newline at end of file +} diff --git a/server/lib/idp/generateRedirectUrl.ts b/server/lib/idp/generateRedirectUrl.ts index 077ac6f6c..cf55e1614 100644 --- a/server/lib/idp/generateRedirectUrl.ts +++ b/server/lib/idp/generateRedirectUrl.ts @@ -33,7 +33,11 @@ export async function generateOidcRedirectUrl( ) .limit(1); - if (res?.loginPage && res.loginPage.domainId && res.loginPage.fullDomain) { + if ( + res?.loginPage && + res.loginPage.domainId && + res.loginPage.fullDomain + ) { baseUrl = `${method}://${res.loginPage.fullDomain}`; } } diff --git a/server/lib/ip.test.ts b/server/lib/ip.test.ts index 67a2faaa0..70436e05a 100644 --- a/server/lib/ip.test.ts +++ b/server/lib/ip.test.ts @@ -4,7 +4,7 @@ import { assertEquals } from "@test/assert"; // Test cases function testFindNextAvailableCidr() { console.log("Running findNextAvailableCidr tests..."); - + // Test 0: Basic IPv4 allocation with a subnet in the wrong range { const existing = ["100.90.130.1/30", "100.90.128.4/30"]; @@ -23,7 +23,11 @@ function testFindNextAvailableCidr() { { const existing = ["10.0.0.0/16", "10.2.0.0/16"]; const result = findNextAvailableCidr(existing, 16, "10.0.0.0/8"); - assertEquals(result, "10.1.0.0/16", "Finding gap between allocations failed"); + assertEquals( + result, + "10.1.0.0/16", + "Finding gap between allocations failed" + ); } // Test 3: No available space @@ -33,7 +37,7 @@ function testFindNextAvailableCidr() { assertEquals(result, null, "No available space test failed"); } - // Test 4: Empty existing + // Test 4: Empty existing { const existing: string[] = []; const result = findNextAvailableCidr(existing, 30, "10.0.0.0/8"); @@ -137,4 +141,4 @@ try { } catch (error) { console.error("Test failed:", error); process.exit(1); -} \ No newline at end of file +} diff --git a/server/lib/ip.ts b/server/lib/ip.ts index 3949a5f6b..f3dabf436 100644 --- a/server/lib/ip.ts +++ b/server/lib/ip.ts @@ -247,7 +247,10 @@ export async function getNextAvailableClientSubnet( orgId: string, transaction: Transaction | typeof db = db ): Promise { - const [org] = await transaction.select().from(orgs).where(eq(orgs.orgId, orgId)); + const [org] = await transaction + .select() + .from(orgs) + .where(eq(orgs.orgId, orgId)); if (!org) { throw new Error(`Organization with ID ${orgId} not found`); @@ -360,7 +363,9 @@ export async function getNextAvailableOrgSubnet(): Promise { return subnet; } -export function generateRemoteSubnets(allSiteResources: SiteResource[]): string[] { +export function generateRemoteSubnets( + allSiteResources: SiteResource[] +): string[] { const remoteSubnets = allSiteResources .filter((sr) => { if (sr.mode === "cidr") return true; diff --git a/server/lib/logAccessAudit.ts b/server/lib/logAccessAudit.ts index 82ddda67e..5f3601da0 100644 --- a/server/lib/logAccessAudit.ts +++ b/server/lib/logAccessAudit.ts @@ -14,4 +14,4 @@ export async function logAccessAudit(data: { requestIp?: string; }) { return; -} \ No newline at end of file +} diff --git a/server/lib/readConfigFile.ts b/server/lib/readConfigFile.ts index ac8196194..fe6106633 100644 --- a/server/lib/readConfigFile.ts +++ b/server/lib/readConfigFile.ts @@ -14,7 +14,8 @@ export const configSchema = z .object({ app: z .object({ - dashboard_url: z.url() + dashboard_url: z + .url() .pipe(z.url()) .transform((url) => url.toLowerCase()) .optional(), @@ -255,7 +256,10 @@ export const configSchema = z .object({ block_size: z.number().positive().gt(0).optional().default(24), subnet_group: z.string().optional().default("100.90.128.0/24"), - utility_subnet_group: z.string().optional().default("100.96.128.0/24") //just hardcode this for now as well + utility_subnet_group: z + .string() + .optional() + .default("100.96.128.0/24") //just hardcode this for now as well }) .optional() .default({ diff --git a/server/lib/rebuildClientAssociations.ts b/server/lib/rebuildClientAssociations.ts index 9095b9bc5..8729edafc 100644 --- a/server/lib/rebuildClientAssociations.ts +++ b/server/lib/rebuildClientAssociations.ts @@ -32,7 +32,7 @@ import logger from "@server/logger"; import { generateAliasConfig, generateRemoteSubnets, - generateSubnetProxyTargets, + generateSubnetProxyTargets } from "@server/lib/ip"; import { addPeerData, @@ -109,21 +109,22 @@ export async function getClientSiteResourceAccess( const directClientIds = allClientSiteResources.map((row) => row.clientId); // Get full client details for directly associated clients - const directClients = directClientIds.length > 0 - ? await trx - .select({ - clientId: clients.clientId, - pubKey: clients.pubKey, - subnet: clients.subnet - }) - .from(clients) - .where( - and( - inArray(clients.clientId, directClientIds), - eq(clients.orgId, siteResource.orgId) // filter by org to prevent cross-org associations + const directClients = + directClientIds.length > 0 + ? await trx + .select({ + clientId: clients.clientId, + pubKey: clients.pubKey, + subnet: clients.subnet + }) + .from(clients) + .where( + and( + inArray(clients.clientId, directClientIds), + eq(clients.orgId, siteResource.orgId) // filter by org to prevent cross-org associations + ) ) - ) - : []; + : []; // Merge user-based clients with directly associated clients const allClientsMap = new Map( @@ -731,9 +732,10 @@ async function handleSubnetProxyTargetUpdates( ); // Only remove remote subnet if no other resource uses the same destination - const remoteSubnetsToRemove = destinationStillInUse.length > 0 - ? [] - : generateRemoteSubnets([siteResource]); + const remoteSubnetsToRemove = + destinationStillInUse.length > 0 + ? [] + : generateRemoteSubnets([siteResource]); olmJobs.push( removePeerData( @@ -817,7 +819,10 @@ export async function rebuildClientAssociationsFromClient( .from(roleSiteResources) .innerJoin( siteResources, - eq(siteResources.siteResourceId, roleSiteResources.siteResourceId) + eq( + siteResources.siteResourceId, + roleSiteResources.siteResourceId + ) ) .where( and( @@ -1277,9 +1282,10 @@ async function handleMessagesForClientResources( ); // Only remove remote subnet if no other resource uses the same destination - const remoteSubnetsToRemove = destinationStillInUse.length > 0 - ? [] - : generateRemoteSubnets([resource]); + const remoteSubnetsToRemove = + destinationStillInUse.length > 0 + ? [] + : generateRemoteSubnets([resource]); // Remove peer data from olm olmJobs.push( diff --git a/server/lib/resend.ts b/server/lib/resend.ts index 0af039bbc..0c21b1bef 100644 --- a/server/lib/resend.ts +++ b/server/lib/resend.ts @@ -1,8 +1,8 @@ export enum AudienceIds { - SignUps = "", - Subscribed = "", - Churned = "", - Newsletter = "" + SignUps = "", + Subscribed = "", + Churned = "", + Newsletter = "" } let resend; diff --git a/server/lib/response.ts b/server/lib/response.ts index ae8461ba5..fd8fa89fb 100644 --- a/server/lib/response.ts +++ b/server/lib/response.ts @@ -3,14 +3,14 @@ import { Response } from "express"; export const response = ( res: Response, - { data, success, error, message, status }: ResponseT, + { data, success, error, message, status }: ResponseT ) => { return res.status(status).send({ data, success, error, message, - status, + status }); }; diff --git a/server/lib/s3.ts b/server/lib/s3.ts index 5fc3318fd..17314ed7e 100644 --- a/server/lib/s3.ts +++ b/server/lib/s3.ts @@ -1,5 +1,5 @@ import { S3Client } from "@aws-sdk/client-s3"; export const s3Client = new S3Client({ - region: process.env.S3_REGION || "us-east-1", + region: process.env.S3_REGION || "us-east-1" }); diff --git a/server/lib/serverIpService.ts b/server/lib/serverIpService.ts index 8c16fd430..7f423f9b6 100644 --- a/server/lib/serverIpService.ts +++ b/server/lib/serverIpService.ts @@ -6,7 +6,7 @@ let serverIp: string | null = null; const services = [ "https://checkip.amazonaws.com", "https://ifconfig.io/ip", - "https://api.ipify.org", + "https://api.ipify.org" ]; export async function fetchServerIp() { @@ -17,7 +17,9 @@ export async function fetchServerIp() { logger.debug("Detected public IP: " + serverIp); return; } catch (err: any) { - console.warn(`Failed to fetch server IP from ${url}: ${err.message || err.code}`); + console.warn( + `Failed to fetch server IP from ${url}: ${err.message || err.code}` + ); } } diff --git a/server/lib/stoi.ts b/server/lib/stoi.ts index ebc789e6a..3c869858e 100644 --- a/server/lib/stoi.ts +++ b/server/lib/stoi.ts @@ -1,8 +1,7 @@ export default function stoi(val: any) { if (typeof val === "string") { - return parseInt(val); + return parseInt(val); + } else { + return val; } - else { - return val; - } -} \ No newline at end of file +} diff --git a/server/lib/traefik/TraefikConfigManager.ts b/server/lib/traefik/TraefikConfigManager.ts index 151e65179..92e08b6ad 100644 --- a/server/lib/traefik/TraefikConfigManager.ts +++ b/server/lib/traefik/TraefikConfigManager.ts @@ -195,7 +195,9 @@ export class TraefikConfigManager { state.set(domain, { exists: certExists && keyExists, - lastModified: lastModified ? Math.floor(lastModified.getTime() / 1000) : null, + lastModified: lastModified + ? Math.floor(lastModified.getTime() / 1000) + : null, expiresAt, wildcard }); @@ -464,7 +466,9 @@ export class TraefikConfigManager { config.getRawConfig().traefik.site_types, build == "oss", // filter out the namespace domains in open source build != "oss", // generate the login pages on the cloud and hybrid, - build == "saas" ? false : config.getRawConfig().traefik.allow_raw_resources // dont allow raw resources on saas otherwise use config + build == "saas" + ? false + : config.getRawConfig().traefik.allow_raw_resources // dont allow raw resources on saas otherwise use config ); const domains = new Set(); @@ -788,7 +792,10 @@ export class TraefikConfigManager { // Store the certificate expiry time if (cert.expiresAt) { - const expiresAtPath = path.join(domainDir, ".expires_at"); + const expiresAtPath = path.join( + domainDir, + ".expires_at" + ); fs.writeFileSync( expiresAtPath, cert.expiresAt.toString(), diff --git a/server/lib/traefik/index.ts b/server/lib/traefik/index.ts index 5630028c4..0fc483fab 100644 --- a/server/lib/traefik/index.ts +++ b/server/lib/traefik/index.ts @@ -1 +1 @@ -export * from "./getTraefikConfig"; \ No newline at end of file +export * from "./getTraefikConfig"; diff --git a/server/lib/traefik/traefikConfig.test.ts b/server/lib/traefik/traefikConfig.test.ts index 88e5da494..36ad4e688 100644 --- a/server/lib/traefik/traefikConfig.test.ts +++ b/server/lib/traefik/traefikConfig.test.ts @@ -2,234 +2,249 @@ import { assertEquals } from "@test/assert"; import { isDomainCoveredByWildcard } from "./TraefikConfigManager"; function runTests() { - console.log('Running wildcard domain coverage tests...'); - + console.log("Running wildcard domain coverage tests..."); + // Test case 1: Basic wildcard certificate at example.com const basicWildcardCerts = new Map([ - ['example.com', { exists: true, wildcard: true }] + ["example.com", { exists: true, wildcard: true }] ]); - + // Should match first-level subdomains assertEquals( - isDomainCoveredByWildcard('level1.example.com', basicWildcardCerts), + isDomainCoveredByWildcard("level1.example.com", basicWildcardCerts), true, - 'Wildcard cert at example.com should match level1.example.com' + "Wildcard cert at example.com should match level1.example.com" ); - + assertEquals( - isDomainCoveredByWildcard('api.example.com', basicWildcardCerts), + isDomainCoveredByWildcard("api.example.com", basicWildcardCerts), true, - 'Wildcard cert at example.com should match api.example.com' + "Wildcard cert at example.com should match api.example.com" ); - + assertEquals( - isDomainCoveredByWildcard('www.example.com', basicWildcardCerts), + isDomainCoveredByWildcard("www.example.com", basicWildcardCerts), true, - 'Wildcard cert at example.com should match www.example.com' + "Wildcard cert at example.com should match www.example.com" ); - + // Should match the root domain (exact match) assertEquals( - isDomainCoveredByWildcard('example.com', basicWildcardCerts), + isDomainCoveredByWildcard("example.com", basicWildcardCerts), true, - 'Wildcard cert at example.com should match example.com itself' + "Wildcard cert at example.com should match example.com itself" ); - + // Should NOT match second-level subdomains assertEquals( - isDomainCoveredByWildcard('level2.level1.example.com', basicWildcardCerts), + isDomainCoveredByWildcard( + "level2.level1.example.com", + basicWildcardCerts + ), false, - 'Wildcard cert at example.com should NOT match level2.level1.example.com' + "Wildcard cert at example.com should NOT match level2.level1.example.com" ); - + assertEquals( - isDomainCoveredByWildcard('deep.nested.subdomain.example.com', basicWildcardCerts), + isDomainCoveredByWildcard( + "deep.nested.subdomain.example.com", + basicWildcardCerts + ), false, - 'Wildcard cert at example.com should NOT match deep.nested.subdomain.example.com' + "Wildcard cert at example.com should NOT match deep.nested.subdomain.example.com" ); - + // Should NOT match different domains assertEquals( - isDomainCoveredByWildcard('test.otherdomain.com', basicWildcardCerts), + isDomainCoveredByWildcard("test.otherdomain.com", basicWildcardCerts), false, - 'Wildcard cert at example.com should NOT match test.otherdomain.com' + "Wildcard cert at example.com should NOT match test.otherdomain.com" ); - + assertEquals( - isDomainCoveredByWildcard('notexample.com', basicWildcardCerts), + isDomainCoveredByWildcard("notexample.com", basicWildcardCerts), false, - 'Wildcard cert at example.com should NOT match notexample.com' + "Wildcard cert at example.com should NOT match notexample.com" ); - + // Test case 2: Multiple wildcard certificates const multipleWildcardCerts = new Map([ - ['example.com', { exists: true, wildcard: true }], - ['test.org', { exists: true, wildcard: true }], - ['api.service.net', { exists: true, wildcard: true }] + ["example.com", { exists: true, wildcard: true }], + ["test.org", { exists: true, wildcard: true }], + ["api.service.net", { exists: true, wildcard: true }] ]); - + assertEquals( - isDomainCoveredByWildcard('app.example.com', multipleWildcardCerts), + isDomainCoveredByWildcard("app.example.com", multipleWildcardCerts), true, - 'Should match subdomain of first wildcard cert' + "Should match subdomain of first wildcard cert" ); - + assertEquals( - isDomainCoveredByWildcard('staging.test.org', multipleWildcardCerts), + isDomainCoveredByWildcard("staging.test.org", multipleWildcardCerts), true, - 'Should match subdomain of second wildcard cert' + "Should match subdomain of second wildcard cert" ); - + assertEquals( - isDomainCoveredByWildcard('v1.api.service.net', multipleWildcardCerts), + isDomainCoveredByWildcard("v1.api.service.net", multipleWildcardCerts), true, - 'Should match subdomain of third wildcard cert' + "Should match subdomain of third wildcard cert" ); - + assertEquals( - isDomainCoveredByWildcard('deep.nested.api.service.net', multipleWildcardCerts), + isDomainCoveredByWildcard( + "deep.nested.api.service.net", + multipleWildcardCerts + ), false, - 'Should NOT match multi-level subdomain of third wildcard cert' + "Should NOT match multi-level subdomain of third wildcard cert" ); - + // Test exact domain matches for multiple certs assertEquals( - isDomainCoveredByWildcard('example.com', multipleWildcardCerts), + isDomainCoveredByWildcard("example.com", multipleWildcardCerts), true, - 'Should match exact domain of first wildcard cert' + "Should match exact domain of first wildcard cert" ); - + assertEquals( - isDomainCoveredByWildcard('test.org', multipleWildcardCerts), + isDomainCoveredByWildcard("test.org", multipleWildcardCerts), true, - 'Should match exact domain of second wildcard cert' + "Should match exact domain of second wildcard cert" ); - + assertEquals( - isDomainCoveredByWildcard('api.service.net', multipleWildcardCerts), + isDomainCoveredByWildcard("api.service.net", multipleWildcardCerts), true, - 'Should match exact domain of third wildcard cert' + "Should match exact domain of third wildcard cert" ); - + // Test case 3: Non-wildcard certificates (should not match anything) const nonWildcardCerts = new Map([ - ['example.com', { exists: true, wildcard: false }], - ['specific.domain.com', { exists: true, wildcard: false }] + ["example.com", { exists: true, wildcard: false }], + ["specific.domain.com", { exists: true, wildcard: false }] ]); - + assertEquals( - isDomainCoveredByWildcard('sub.example.com', nonWildcardCerts), + isDomainCoveredByWildcard("sub.example.com", nonWildcardCerts), false, - 'Non-wildcard cert should not match subdomains' + "Non-wildcard cert should not match subdomains" ); - + assertEquals( - isDomainCoveredByWildcard('example.com', nonWildcardCerts), + isDomainCoveredByWildcard("example.com", nonWildcardCerts), false, - 'Non-wildcard cert should not match even exact domain via this function' + "Non-wildcard cert should not match even exact domain via this function" ); - + // Test case 4: Non-existent certificates (should not match) const nonExistentCerts = new Map([ - ['example.com', { exists: false, wildcard: true }], - ['missing.com', { exists: false, wildcard: true }] + ["example.com", { exists: false, wildcard: true }], + ["missing.com", { exists: false, wildcard: true }] ]); - + assertEquals( - isDomainCoveredByWildcard('sub.example.com', nonExistentCerts), + isDomainCoveredByWildcard("sub.example.com", nonExistentCerts), false, - 'Non-existent wildcard cert should not match' + "Non-existent wildcard cert should not match" ); - + // Test case 5: Edge cases with special domain names const specialDomainCerts = new Map([ - ['localhost', { exists: true, wildcard: true }], - ['127-0-0-1.nip.io', { exists: true, wildcard: true }], - ['xn--e1afmkfd.xn--p1ai', { exists: true, wildcard: true }] // IDN domain + ["localhost", { exists: true, wildcard: true }], + ["127-0-0-1.nip.io", { exists: true, wildcard: true }], + ["xn--e1afmkfd.xn--p1ai", { exists: true, wildcard: true }] // IDN domain ]); - + assertEquals( - isDomainCoveredByWildcard('app.localhost', specialDomainCerts), + isDomainCoveredByWildcard("app.localhost", specialDomainCerts), true, - 'Should match subdomain of localhost wildcard' + "Should match subdomain of localhost wildcard" ); - + assertEquals( - isDomainCoveredByWildcard('test.127-0-0-1.nip.io', specialDomainCerts), + isDomainCoveredByWildcard("test.127-0-0-1.nip.io", specialDomainCerts), true, - 'Should match subdomain of nip.io wildcard' + "Should match subdomain of nip.io wildcard" ); - + assertEquals( - isDomainCoveredByWildcard('sub.xn--e1afmkfd.xn--p1ai', specialDomainCerts), + isDomainCoveredByWildcard( + "sub.xn--e1afmkfd.xn--p1ai", + specialDomainCerts + ), true, - 'Should match subdomain of IDN wildcard' + "Should match subdomain of IDN wildcard" ); - + // Test case 6: Empty input and edge cases const emptyCerts = new Map(); - + assertEquals( - isDomainCoveredByWildcard('any.domain.com', emptyCerts), + isDomainCoveredByWildcard("any.domain.com", emptyCerts), false, - 'Empty certificate map should not match any domain' + "Empty certificate map should not match any domain" ); - + // Test case 7: Domains with single character components const singleCharCerts = new Map([ - ['a.com', { exists: true, wildcard: true }], - ['x.y.z', { exists: true, wildcard: true }] + ["a.com", { exists: true, wildcard: true }], + ["x.y.z", { exists: true, wildcard: true }] ]); - + assertEquals( - isDomainCoveredByWildcard('b.a.com', singleCharCerts), + isDomainCoveredByWildcard("b.a.com", singleCharCerts), true, - 'Should match single character subdomain' + "Should match single character subdomain" ); - + assertEquals( - isDomainCoveredByWildcard('w.x.y.z', singleCharCerts), + isDomainCoveredByWildcard("w.x.y.z", singleCharCerts), true, - 'Should match single character subdomain of multi-part domain' + "Should match single character subdomain of multi-part domain" ); - + assertEquals( - isDomainCoveredByWildcard('v.w.x.y.z', singleCharCerts), + isDomainCoveredByWildcard("v.w.x.y.z", singleCharCerts), false, - 'Should NOT match multi-level subdomain of single char domain' + "Should NOT match multi-level subdomain of single char domain" ); - + // Test case 8: Domains with numbers and hyphens const numericCerts = new Map([ - ['api-v2.service-1.com', { exists: true, wildcard: true }], - ['123.456.net', { exists: true, wildcard: true }] + ["api-v2.service-1.com", { exists: true, wildcard: true }], + ["123.456.net", { exists: true, wildcard: true }] ]); - + assertEquals( - isDomainCoveredByWildcard('staging.api-v2.service-1.com', numericCerts), + isDomainCoveredByWildcard("staging.api-v2.service-1.com", numericCerts), true, - 'Should match subdomain with hyphens and numbers' + "Should match subdomain with hyphens and numbers" ); - + assertEquals( - isDomainCoveredByWildcard('test.123.456.net', numericCerts), + isDomainCoveredByWildcard("test.123.456.net", numericCerts), true, - 'Should match subdomain with numeric components' + "Should match subdomain with numeric components" ); - + assertEquals( - isDomainCoveredByWildcard('deep.staging.api-v2.service-1.com', numericCerts), + isDomainCoveredByWildcard( + "deep.staging.api-v2.service-1.com", + numericCerts + ), false, - 'Should NOT match multi-level subdomain with hyphens and numbers' + "Should NOT match multi-level subdomain with hyphens and numbers" ); - - console.log('All wildcard domain coverage tests passed!'); + + console.log("All wildcard domain coverage tests passed!"); } // Run all tests try { runTests(); } catch (error) { - console.error('Test failed:', error); + console.error("Test failed:", error); process.exit(1); } diff --git a/server/lib/traefik/utils.ts b/server/lib/traefik/utils.ts index 37ebfa0b1..ec0eae5b3 100644 --- a/server/lib/traefik/utils.ts +++ b/server/lib/traefik/utils.ts @@ -31,12 +31,17 @@ export function validatePathRewriteConfig( } if (rewritePathType !== "stripPrefix") { - if ((rewritePath && !rewritePathType) || (!rewritePath && rewritePathType)) { - return { isValid: false, error: "Both rewritePath and rewritePathType must be specified together" }; + if ( + (rewritePath && !rewritePathType) || + (!rewritePath && rewritePathType) + ) { + return { + isValid: false, + error: "Both rewritePath and rewritePathType must be specified together" + }; } } - if (!rewritePath || !rewritePathType) { return { isValid: true }; } @@ -68,14 +73,14 @@ export function validatePathRewriteConfig( } } - // Additional validation for stripPrefix if (rewritePathType === "stripPrefix") { if (pathMatchType !== "prefix") { - logger.warn(`stripPrefix rewrite type is most effective with prefix path matching. Current match type: ${pathMatchType}`); + logger.warn( + `stripPrefix rewrite type is most effective with prefix path matching. Current match type: ${pathMatchType}` + ); } } return { isValid: true }; } - diff --git a/server/lib/validators.test.ts b/server/lib/validators.test.ts index e2043c74c..c4c564cf7 100644 --- a/server/lib/validators.test.ts +++ b/server/lib/validators.test.ts @@ -1,71 +1,247 @@ -import { isValidUrlGlobPattern } from "./validators"; +import { isValidUrlGlobPattern } from "./validators"; import { assertEquals } from "@test/assert"; function runTests() { - console.log('Running URL pattern validation tests...'); - + console.log("Running URL pattern validation tests..."); + // Test valid patterns - assertEquals(isValidUrlGlobPattern('simple'), true, 'Simple path segment should be valid'); - assertEquals(isValidUrlGlobPattern('simple/path'), true, 'Simple path with slash should be valid'); - assertEquals(isValidUrlGlobPattern('/leading/slash'), true, 'Path with leading slash should be valid'); - assertEquals(isValidUrlGlobPattern('path/'), true, 'Path with trailing slash should be valid'); - assertEquals(isValidUrlGlobPattern('path/*'), true, 'Path with wildcard segment should be valid'); - assertEquals(isValidUrlGlobPattern('*'), true, 'Single wildcard should be valid'); - assertEquals(isValidUrlGlobPattern('*/subpath'), true, 'Wildcard with subpath should be valid'); - assertEquals(isValidUrlGlobPattern('path/*/more'), true, 'Path with wildcard in the middle should be valid'); - + assertEquals( + isValidUrlGlobPattern("simple"), + true, + "Simple path segment should be valid" + ); + assertEquals( + isValidUrlGlobPattern("simple/path"), + true, + "Simple path with slash should be valid" + ); + assertEquals( + isValidUrlGlobPattern("/leading/slash"), + true, + "Path with leading slash should be valid" + ); + assertEquals( + isValidUrlGlobPattern("path/"), + true, + "Path with trailing slash should be valid" + ); + assertEquals( + isValidUrlGlobPattern("path/*"), + true, + "Path with wildcard segment should be valid" + ); + assertEquals( + isValidUrlGlobPattern("*"), + true, + "Single wildcard should be valid" + ); + assertEquals( + isValidUrlGlobPattern("*/subpath"), + true, + "Wildcard with subpath should be valid" + ); + assertEquals( + isValidUrlGlobPattern("path/*/more"), + true, + "Path with wildcard in the middle should be valid" + ); + // Test with special characters - assertEquals(isValidUrlGlobPattern('path-with-dash'), true, 'Path with dash should be valid'); - assertEquals(isValidUrlGlobPattern('path_with_underscore'), true, 'Path with underscore should be valid'); - assertEquals(isValidUrlGlobPattern('path.with.dots'), true, 'Path with dots should be valid'); - assertEquals(isValidUrlGlobPattern('path~with~tilde'), true, 'Path with tilde should be valid'); - assertEquals(isValidUrlGlobPattern('path!with!exclamation'), true, 'Path with exclamation should be valid'); - assertEquals(isValidUrlGlobPattern('path$with$dollar'), true, 'Path with dollar should be valid'); - assertEquals(isValidUrlGlobPattern('path&with&ersand'), true, 'Path with ampersand should be valid'); - assertEquals(isValidUrlGlobPattern("path'with'quote"), true, "Path with quote should be valid"); - assertEquals(isValidUrlGlobPattern('path(with)parentheses'), true, 'Path with parentheses should be valid'); - assertEquals(isValidUrlGlobPattern('path+with+plus'), true, 'Path with plus should be valid'); - assertEquals(isValidUrlGlobPattern('path,with,comma'), true, 'Path with comma should be valid'); - assertEquals(isValidUrlGlobPattern('path;with;semicolon'), true, 'Path with semicolon should be valid'); - assertEquals(isValidUrlGlobPattern('path=with=equals'), true, 'Path with equals should be valid'); - assertEquals(isValidUrlGlobPattern('path:with:colon'), true, 'Path with colon should be valid'); - assertEquals(isValidUrlGlobPattern('path@with@at'), true, 'Path with at should be valid'); - + assertEquals( + isValidUrlGlobPattern("path-with-dash"), + true, + "Path with dash should be valid" + ); + assertEquals( + isValidUrlGlobPattern("path_with_underscore"), + true, + "Path with underscore should be valid" + ); + assertEquals( + isValidUrlGlobPattern("path.with.dots"), + true, + "Path with dots should be valid" + ); + assertEquals( + isValidUrlGlobPattern("path~with~tilde"), + true, + "Path with tilde should be valid" + ); + assertEquals( + isValidUrlGlobPattern("path!with!exclamation"), + true, + "Path with exclamation should be valid" + ); + assertEquals( + isValidUrlGlobPattern("path$with$dollar"), + true, + "Path with dollar should be valid" + ); + assertEquals( + isValidUrlGlobPattern("path&with&ersand"), + true, + "Path with ampersand should be valid" + ); + assertEquals( + isValidUrlGlobPattern("path'with'quote"), + true, + "Path with quote should be valid" + ); + assertEquals( + isValidUrlGlobPattern("path(with)parentheses"), + true, + "Path with parentheses should be valid" + ); + assertEquals( + isValidUrlGlobPattern("path+with+plus"), + true, + "Path with plus should be valid" + ); + assertEquals( + isValidUrlGlobPattern("path,with,comma"), + true, + "Path with comma should be valid" + ); + assertEquals( + isValidUrlGlobPattern("path;with;semicolon"), + true, + "Path with semicolon should be valid" + ); + assertEquals( + isValidUrlGlobPattern("path=with=equals"), + true, + "Path with equals should be valid" + ); + assertEquals( + isValidUrlGlobPattern("path:with:colon"), + true, + "Path with colon should be valid" + ); + assertEquals( + isValidUrlGlobPattern("path@with@at"), + true, + "Path with at should be valid" + ); + // Test with percent encoding - assertEquals(isValidUrlGlobPattern('path%20with%20spaces'), true, 'Path with percent-encoded spaces should be valid'); - assertEquals(isValidUrlGlobPattern('path%2Fwith%2Fencoded%2Fslashes'), true, 'Path with percent-encoded slashes should be valid'); - + assertEquals( + isValidUrlGlobPattern("path%20with%20spaces"), + true, + "Path with percent-encoded spaces should be valid" + ); + assertEquals( + isValidUrlGlobPattern("path%2Fwith%2Fencoded%2Fslashes"), + true, + "Path with percent-encoded slashes should be valid" + ); + // Test with wildcards in segments (the fixed functionality) - assertEquals(isValidUrlGlobPattern('padbootstrap*'), true, 'Path with wildcard at the end of segment should be valid'); - assertEquals(isValidUrlGlobPattern('pad*bootstrap'), true, 'Path with wildcard in the middle of segment should be valid'); - assertEquals(isValidUrlGlobPattern('*bootstrap'), true, 'Path with wildcard at the start of segment should be valid'); - assertEquals(isValidUrlGlobPattern('multiple*wildcards*in*segment'), true, 'Path with multiple wildcards in segment should be valid'); - assertEquals(isValidUrlGlobPattern('wild*/cards/in*/different/seg*ments'), true, 'Path with wildcards in different segments should be valid'); - + assertEquals( + isValidUrlGlobPattern("padbootstrap*"), + true, + "Path with wildcard at the end of segment should be valid" + ); + assertEquals( + isValidUrlGlobPattern("pad*bootstrap"), + true, + "Path with wildcard in the middle of segment should be valid" + ); + assertEquals( + isValidUrlGlobPattern("*bootstrap"), + true, + "Path with wildcard at the start of segment should be valid" + ); + assertEquals( + isValidUrlGlobPattern("multiple*wildcards*in*segment"), + true, + "Path with multiple wildcards in segment should be valid" + ); + assertEquals( + isValidUrlGlobPattern("wild*/cards/in*/different/seg*ments"), + true, + "Path with wildcards in different segments should be valid" + ); + // Test invalid patterns - assertEquals(isValidUrlGlobPattern(''), false, 'Empty string should be invalid'); - assertEquals(isValidUrlGlobPattern('//double/slash'), false, 'Path with double slash should be invalid'); - assertEquals(isValidUrlGlobPattern('path//end'), false, 'Path with double slash in the middle should be invalid'); - assertEquals(isValidUrlGlobPattern('invalid'), false, 'Path with invalid characters should be invalid'); - assertEquals(isValidUrlGlobPattern('invalid|char'), false, 'Path with invalid pipe character should be invalid'); - assertEquals(isValidUrlGlobPattern('invalid"char'), false, 'Path with invalid quote character should be invalid'); - assertEquals(isValidUrlGlobPattern('invalid`char'), false, 'Path with invalid backtick character should be invalid'); - assertEquals(isValidUrlGlobPattern('invalid^char'), false, 'Path with invalid caret character should be invalid'); - assertEquals(isValidUrlGlobPattern('invalid\\char'), false, 'Path with invalid backslash character should be invalid'); - assertEquals(isValidUrlGlobPattern('invalid[char]'), false, 'Path with invalid square brackets should be invalid'); - assertEquals(isValidUrlGlobPattern('invalid{char}'), false, 'Path with invalid curly braces should be invalid'); - + assertEquals( + isValidUrlGlobPattern(""), + false, + "Empty string should be invalid" + ); + assertEquals( + isValidUrlGlobPattern("//double/slash"), + false, + "Path with double slash should be invalid" + ); + assertEquals( + isValidUrlGlobPattern("path//end"), + false, + "Path with double slash in the middle should be invalid" + ); + assertEquals( + isValidUrlGlobPattern("invalid"), + false, + "Path with invalid characters should be invalid" + ); + assertEquals( + isValidUrlGlobPattern("invalid|char"), + false, + "Path with invalid pipe character should be invalid" + ); + assertEquals( + isValidUrlGlobPattern('invalid"char'), + false, + "Path with invalid quote character should be invalid" + ); + assertEquals( + isValidUrlGlobPattern("invalid`char"), + false, + "Path with invalid backtick character should be invalid" + ); + assertEquals( + isValidUrlGlobPattern("invalid^char"), + false, + "Path with invalid caret character should be invalid" + ); + assertEquals( + isValidUrlGlobPattern("invalid\\char"), + false, + "Path with invalid backslash character should be invalid" + ); + assertEquals( + isValidUrlGlobPattern("invalid[char]"), + false, + "Path with invalid square brackets should be invalid" + ); + assertEquals( + isValidUrlGlobPattern("invalid{char}"), + false, + "Path with invalid curly braces should be invalid" + ); + // Test invalid percent encoding - assertEquals(isValidUrlGlobPattern('invalid%2'), false, 'Path with incomplete percent encoding should be invalid'); - assertEquals(isValidUrlGlobPattern('invalid%GZ'), false, 'Path with invalid hex in percent encoding should be invalid'); - assertEquals(isValidUrlGlobPattern('invalid%'), false, 'Path with isolated percent sign should be invalid'); - - console.log('All tests passed!'); + assertEquals( + isValidUrlGlobPattern("invalid%2"), + false, + "Path with incomplete percent encoding should be invalid" + ); + assertEquals( + isValidUrlGlobPattern("invalid%GZ"), + false, + "Path with invalid hex in percent encoding should be invalid" + ); + assertEquals( + isValidUrlGlobPattern("invalid%"), + false, + "Path with isolated percent sign should be invalid" + ); + + console.log("All tests passed!"); } // Run all tests try { runTests(); } catch (error) { - console.error('Test failed:', error); -} \ No newline at end of file + console.error("Test failed:", error); +} diff --git a/server/lib/validators.ts b/server/lib/validators.ts index 5bdd7a142..b1efe8b38 100644 --- a/server/lib/validators.ts +++ b/server/lib/validators.ts @@ -2,7 +2,9 @@ import z from "zod"; import ipaddr from "ipaddr.js"; export function isValidCIDR(cidr: string): boolean { - return z.cidrv4().safeParse(cidr).success || z.cidrv6().safeParse(cidr).success; + return ( + z.cidrv4().safeParse(cidr).success || z.cidrv6().safeParse(cidr).success + ); } export function isValidIP(ip: string): boolean { @@ -69,11 +71,11 @@ export function isUrlValid(url: string | undefined) { if (!url) return true; // the link is optional in the schema so if it's empty it's valid var pattern = new RegExp( "^(https?:\\/\\/)?" + // protocol - "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name - "((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address - "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path - "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string - "(\\#[-a-z\\d_]*)?$", + "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name + "((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address + "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path + "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string + "(\\#[-a-z\\d_]*)?$", "i" ); return !!pattern.test(url); @@ -168,14 +170,14 @@ export function validateHeaders(headers: string): boolean { } export function isSecondLevelDomain(domain: string): boolean { - if (!domain || typeof domain !== 'string') { + if (!domain || typeof domain !== "string") { return false; } const trimmedDomain = domain.trim().toLowerCase(); // Split into parts - const parts = trimmedDomain.split('.'); + const parts = trimmedDomain.split("."); // Should have exactly 2 parts for a second-level domain (e.g., "example.com") if (parts.length !== 2) { diff --git a/server/middlewares/formatError.ts b/server/middlewares/formatError.ts index e96ff2964..1e94c1f51 100644 --- a/server/middlewares/formatError.ts +++ b/server/middlewares/formatError.ts @@ -20,6 +20,6 @@ export const errorHandlerMiddleware: ErrorRequestHandler = ( error: true, message: error.message || "Internal Server Error", status: statusCode, - stack: process.env.ENVIRONMENT === "prod" ? null : error.stack, + stack: process.env.ENVIRONMENT === "prod" ? null : error.stack }); }; diff --git a/server/middlewares/getUserOrgs.ts b/server/middlewares/getUserOrgs.ts index 4d042307a..d7905700e 100644 --- a/server/middlewares/getUserOrgs.ts +++ b/server/middlewares/getUserOrgs.ts @@ -8,13 +8,13 @@ import HttpCode from "@server/types/HttpCode"; export async function getUserOrgs( req: Request, res: Response, - next: NextFunction, + next: NextFunction ) { const userId = req.user?.userId; // Assuming you have user information in the request if (!userId) { return next( - createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated"), + createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated") ); } @@ -22,7 +22,7 @@ export async function getUserOrgs( const userOrganizations = await db .select({ orgId: userOrgs.orgId, - roleId: userOrgs.roleId, + roleId: userOrgs.roleId }) .from(userOrgs) .where(eq(userOrgs.userId, userId)); @@ -38,8 +38,8 @@ export async function getUserOrgs( next( createHttpError( HttpCode.INTERNAL_SERVER_ERROR, - "Error retrieving user organizations", - ), + "Error retrieving user organizations" + ) ); } } diff --git a/server/middlewares/integration/index.ts b/server/middlewares/integration/index.ts index d44eb5a31..2e2e8ff0f 100644 --- a/server/middlewares/integration/index.ts +++ b/server/middlewares/integration/index.ts @@ -12,4 +12,4 @@ export * from "./verifyAccessTokenAccess"; export * from "./verifyApiKeyIsRoot"; export * from "./verifyApiKeyApiKeyAccess"; export * from "./verifyApiKeyClientAccess"; -export * from "./verifyApiKeySiteResourceAccess"; \ No newline at end of file +export * from "./verifyApiKeySiteResourceAccess"; diff --git a/server/middlewares/integration/verifyAccessTokenAccess.ts b/server/middlewares/integration/verifyAccessTokenAccess.ts index f5ae8746f..c9a84f188 100644 --- a/server/middlewares/integration/verifyAccessTokenAccess.ts +++ b/server/middlewares/integration/verifyAccessTokenAccess.ts @@ -97,7 +97,6 @@ export async function verifyApiKeyAccessTokenAccess( ); } - return next(); } catch (e) { return next( diff --git a/server/middlewares/integration/verifyApiKeyApiKeyAccess.ts b/server/middlewares/integration/verifyApiKeyApiKeyAccess.ts index ad5b7fc40..48fbbf872 100644 --- a/server/middlewares/integration/verifyApiKeyApiKeyAccess.ts +++ b/server/middlewares/integration/verifyApiKeyApiKeyAccess.ts @@ -11,7 +11,7 @@ export async function verifyApiKeyApiKeyAccess( next: NextFunction ) { try { - const {apiKey: callerApiKey } = req; + const { apiKey: callerApiKey } = req; const apiKeyId = req.params.apiKeyId || req.body.apiKeyId || req.query.apiKeyId; @@ -44,7 +44,10 @@ export async function verifyApiKeyApiKeyAccess( .select() .from(apiKeyOrg) .where( - and(eq(apiKeys.apiKeyId, callerApiKey.apiKeyId), eq(apiKeyOrg.orgId, orgId)) + and( + eq(apiKeys.apiKeyId, callerApiKey.apiKeyId), + eq(apiKeyOrg.orgId, orgId) + ) ) .limit(1); diff --git a/server/middlewares/integration/verifyApiKeySetResourceClients.ts b/server/middlewares/integration/verifyApiKeySetResourceClients.ts index cbcb33ae8..704f3ef5b 100644 --- a/server/middlewares/integration/verifyApiKeySetResourceClients.ts +++ b/server/middlewares/integration/verifyApiKeySetResourceClients.ts @@ -11,9 +11,12 @@ export async function verifyApiKeySetResourceClients( next: NextFunction ) { const apiKey = req.apiKey; - const singleClientId = req.params.clientId || req.body.clientId || req.query.clientId; + const singleClientId = + req.params.clientId || req.body.clientId || req.query.clientId; const { clientIds } = req.body; - const allClientIds = clientIds || (singleClientId ? [parseInt(singleClientId as string)] : []); + const allClientIds = + clientIds || + (singleClientId ? [parseInt(singleClientId as string)] : []); if (!apiKey) { return next( @@ -70,4 +73,3 @@ export async function verifyApiKeySetResourceClients( ); } } - diff --git a/server/middlewares/integration/verifyApiKeySetResourceUsers.ts b/server/middlewares/integration/verifyApiKeySetResourceUsers.ts index db73d1343..0d44aa09d 100644 --- a/server/middlewares/integration/verifyApiKeySetResourceUsers.ts +++ b/server/middlewares/integration/verifyApiKeySetResourceUsers.ts @@ -11,7 +11,8 @@ export async function verifyApiKeySetResourceUsers( next: NextFunction ) { const apiKey = req.apiKey; - const singleUserId = req.params.userId || req.body.userId || req.query.userId; + const singleUserId = + req.params.userId || req.body.userId || req.query.userId; const { userIds } = req.body; const allUserIds = userIds || (singleUserId ? [singleUserId] : []); diff --git a/server/middlewares/integration/verifyApiKeySiteResourceAccess.ts b/server/middlewares/integration/verifyApiKeySiteResourceAccess.ts index fb3d82873..1fc11c314 100644 --- a/server/middlewares/integration/verifyApiKeySiteResourceAccess.ts +++ b/server/middlewares/integration/verifyApiKeySiteResourceAccess.ts @@ -38,17 +38,12 @@ export async function verifyApiKeySiteResourceAccess( const [siteResource] = await db .select() .from(siteResources) - .where(and( - eq(siteResources.siteResourceId, siteResourceId) - )) + .where(and(eq(siteResources.siteResourceId, siteResourceId))) .limit(1); if (!siteResource) { return next( - createHttpError( - HttpCode.NOT_FOUND, - "Site resource not found" - ) + createHttpError(HttpCode.NOT_FOUND, "Site resource not found") ); } diff --git a/server/middlewares/notFound.ts b/server/middlewares/notFound.ts index 706796c90..8e0ab3328 100644 --- a/server/middlewares/notFound.ts +++ b/server/middlewares/notFound.ts @@ -5,7 +5,7 @@ import HttpCode from "@server/types/HttpCode"; export function notFoundMiddleware( req: Request, res: Response, - next: NextFunction, + next: NextFunction ) { if (req.path.startsWith("/api")) { const message = `The requests url is not found - ${req.originalUrl}`; diff --git a/server/middlewares/requestTimeout.ts b/server/middlewares/requestTimeout.ts index 8b5852b78..b0f95a083 100644 --- a/server/middlewares/requestTimeout.ts +++ b/server/middlewares/requestTimeout.ts @@ -1,30 +1,32 @@ -import { Request, Response, NextFunction } from 'express'; -import logger from '@server/logger'; -import createHttpError from 'http-errors'; -import HttpCode from '@server/types/HttpCode'; +import { Request, Response, NextFunction } from "express"; +import logger from "@server/logger"; +import createHttpError from "http-errors"; +import HttpCode from "@server/types/HttpCode"; export function requestTimeoutMiddleware(timeoutMs: number = 30000) { return (req: Request, res: Response, next: NextFunction) => { // Set a timeout for the request const timeout = setTimeout(() => { if (!res.headersSent) { - logger.error(`Request timeout: ${req.method} ${req.url} from ${req.ip}`); + logger.error( + `Request timeout: ${req.method} ${req.url} from ${req.ip}` + ); return next( createHttpError( HttpCode.REQUEST_TIMEOUT, - 'Request timeout - operation took too long to complete' + "Request timeout - operation took too long to complete" ) ); } }, timeoutMs); // Clear timeout when response finishes - res.on('finish', () => { + res.on("finish", () => { clearTimeout(timeout); }); // Clear timeout when response closes - res.on('close', () => { + res.on("close", () => { clearTimeout(timeout); }); diff --git a/server/middlewares/verifySiteAccess.ts b/server/middlewares/verifySiteAccess.ts index 05fc6d27f..98858cfb9 100644 --- a/server/middlewares/verifySiteAccess.ts +++ b/server/middlewares/verifySiteAccess.ts @@ -76,7 +76,10 @@ export async function verifySiteAccess( .select() .from(userOrgs) .where( - and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, site.orgId)) + and( + eq(userOrgs.userId, userId), + eq(userOrgs.orgId, site.orgId) + ) ) .limit(1); req.userOrg = userOrgRole[0]; diff --git a/server/nextServer.ts b/server/nextServer.ts index 5302b9c81..b862a699c 100644 --- a/server/nextServer.ts +++ b/server/nextServer.ts @@ -9,7 +9,10 @@ const nextPort = config.getRawConfig().server.next_port; export async function createNextServer() { // const app = next({ dev }); - const app = next({ dev: process.env.ENVIRONMENT !== "prod", turbopack: true }); + const app = next({ + dev: process.env.ENVIRONMENT !== "prod", + turbopack: true + }); const handle = app.getRequestHandler(); await app.prepare(); diff --git a/server/private/auth/sessions/remoteExitNode.ts b/server/private/auth/sessions/remoteExitNode.ts index fbb2ae1ff..da1fb1aa5 100644 --- a/server/private/auth/sessions/remoteExitNode.ts +++ b/server/private/auth/sessions/remoteExitNode.ts @@ -11,11 +11,14 @@ * This file is not licensed under the AGPLv3. */ -import { - encodeHexLowerCase, -} from "@oslojs/encoding"; +import { encodeHexLowerCase } from "@oslojs/encoding"; import { sha256 } from "@oslojs/crypto/sha2"; -import { RemoteExitNode, remoteExitNodes, remoteExitNodeSessions, RemoteExitNodeSession } from "@server/db"; +import { + RemoteExitNode, + remoteExitNodes, + remoteExitNodeSessions, + RemoteExitNodeSession +} from "@server/db"; import { db } from "@server/db"; import { eq } from "drizzle-orm"; @@ -23,30 +26,39 @@ export const EXPIRES = 1000 * 60 * 60 * 24 * 30; export async function createRemoteExitNodeSession( token: string, - remoteExitNodeId: string, + remoteExitNodeId: string ): Promise { const sessionId = encodeHexLowerCase( - sha256(new TextEncoder().encode(token)), + sha256(new TextEncoder().encode(token)) ); const session: RemoteExitNodeSession = { sessionId: sessionId, remoteExitNodeId, - expiresAt: new Date(Date.now() + EXPIRES).getTime(), + expiresAt: new Date(Date.now() + EXPIRES).getTime() }; await db.insert(remoteExitNodeSessions).values(session); return session; } export async function validateRemoteExitNodeSessionToken( - token: string, + token: string ): Promise { const sessionId = encodeHexLowerCase( - sha256(new TextEncoder().encode(token)), + sha256(new TextEncoder().encode(token)) ); const result = await db - .select({ remoteExitNode: remoteExitNodes, session: remoteExitNodeSessions }) + .select({ + remoteExitNode: remoteExitNodes, + session: remoteExitNodeSessions + }) .from(remoteExitNodeSessions) - .innerJoin(remoteExitNodes, eq(remoteExitNodeSessions.remoteExitNodeId, remoteExitNodes.remoteExitNodeId)) + .innerJoin( + remoteExitNodes, + eq( + remoteExitNodeSessions.remoteExitNodeId, + remoteExitNodes.remoteExitNodeId + ) + ) .where(eq(remoteExitNodeSessions.sessionId, sessionId)); if (result.length < 1) { return { session: null, remoteExitNode: null }; @@ -58,26 +70,32 @@ export async function validateRemoteExitNodeSessionToken( .where(eq(remoteExitNodeSessions.sessionId, session.sessionId)); return { session: null, remoteExitNode: null }; } - if (Date.now() >= session.expiresAt - (EXPIRES / 2)) { - session.expiresAt = new Date( - Date.now() + EXPIRES, - ).getTime(); + if (Date.now() >= session.expiresAt - EXPIRES / 2) { + session.expiresAt = new Date(Date.now() + EXPIRES).getTime(); await db .update(remoteExitNodeSessions) .set({ - expiresAt: session.expiresAt, + expiresAt: session.expiresAt }) .where(eq(remoteExitNodeSessions.sessionId, session.sessionId)); } return { session, remoteExitNode }; } -export async function invalidateRemoteExitNodeSession(sessionId: string): Promise { - await db.delete(remoteExitNodeSessions).where(eq(remoteExitNodeSessions.sessionId, sessionId)); +export async function invalidateRemoteExitNodeSession( + sessionId: string +): Promise { + await db + .delete(remoteExitNodeSessions) + .where(eq(remoteExitNodeSessions.sessionId, sessionId)); } -export async function invalidateAllRemoteExitNodeSessions(remoteExitNodeId: string): Promise { - await db.delete(remoteExitNodeSessions).where(eq(remoteExitNodeSessions.remoteExitNodeId, remoteExitNodeId)); +export async function invalidateAllRemoteExitNodeSessions( + remoteExitNodeId: string +): Promise { + await db + .delete(remoteExitNodeSessions) + .where(eq(remoteExitNodeSessions.remoteExitNodeId, remoteExitNodeId)); } export type SessionValidationResult = diff --git a/server/private/cleanup.ts b/server/private/cleanup.ts index 8bf5ea3d4..e9b305270 100644 --- a/server/private/cleanup.ts +++ b/server/private/cleanup.ts @@ -25,4 +25,4 @@ export async function initCleanup() { // Handle process termination process.on("SIGTERM", () => cleanup()); process.on("SIGINT", () => cleanup()); -} \ No newline at end of file +} diff --git a/server/private/lib/billing/index.ts b/server/private/lib/billing/index.ts index 13ca37617..c2b77d5f6 100644 --- a/server/private/lib/billing/index.ts +++ b/server/private/lib/billing/index.ts @@ -12,4 +12,4 @@ */ export * from "./getOrgTierData"; -export * from "./createCustomer"; \ No newline at end of file +export * from "./createCustomer"; diff --git a/server/private/lib/certificates.ts b/server/private/lib/certificates.ts index ec4b73ee5..06571cac3 100644 --- a/server/private/lib/certificates.ts +++ b/server/private/lib/certificates.ts @@ -55,7 +55,6 @@ export async function getValidCertificatesForDomains( domains: Set, useCache: boolean = true ): Promise> { - loadEncryptData(); // Ensure encryption key is loaded const finalResults: CertificateResult[] = []; diff --git a/server/private/lib/checkOrgAccessPolicy.ts b/server/private/lib/checkOrgAccessPolicy.ts index 2137cd72c..7a78803d5 100644 --- a/server/private/lib/checkOrgAccessPolicy.ts +++ b/server/private/lib/checkOrgAccessPolicy.ts @@ -12,14 +12,7 @@ */ import { build } from "@server/build"; -import { - db, - Org, - orgs, - ResourceSession, - sessions, - users -} from "@server/db"; +import { db, Org, orgs, ResourceSession, sessions, users } from "@server/db"; import { getOrgTierData } from "#private/lib/billing"; import { TierId } from "@server/lib/billing/tiers"; import license from "#private/license/license"; diff --git a/server/private/lib/exitNodes/exitNodeComms.ts b/server/private/lib/exitNodes/exitNodeComms.ts index 20c850a1e..faf1153f1 100644 --- a/server/private/lib/exitNodes/exitNodeComms.ts +++ b/server/private/lib/exitNodes/exitNodeComms.ts @@ -66,7 +66,9 @@ export async function sendToExitNode( // logger.debug(`Configured local exit node name: ${config.getRawConfig().gerbil.exit_node_name}`); if (exitNode.name == config.getRawConfig().gerbil.exit_node_name) { - hostname = privateConfig.getRawPrivateConfig().gerbil.local_exit_node_reachable_at; + hostname = + privateConfig.getRawPrivateConfig().gerbil + .local_exit_node_reachable_at; } if (!hostname) { diff --git a/server/private/lib/exitNodes/exitNodes.ts b/server/private/lib/exitNodes/exitNodes.ts index 77149bb08..556fdcf79 100644 --- a/server/private/lib/exitNodes/exitNodes.ts +++ b/server/private/lib/exitNodes/exitNodes.ts @@ -44,43 +44,53 @@ async function checkExitNodeOnlineStatus( const delayBetweenAttempts = 100; // 100ms delay between starting each attempt // Create promises for all attempts with staggered delays - const attemptPromises = Array.from({ length: maxAttempts }, async (_, index) => { - const attemptNumber = index + 1; - - // Add delay before each attempt (except the first) - if (index > 0) { - await new Promise((resolve) => setTimeout(resolve, delayBetweenAttempts * index)); - } + const attemptPromises = Array.from( + { length: maxAttempts }, + async (_, index) => { + const attemptNumber = index + 1; - try { - const response = await axios.get(`http://${endpoint}/ping`, { - timeout: timeoutMs, - validateStatus: (status) => status === 200 - }); - - if (response.status === 200) { - logger.debug( - `Exit node ${endpoint} is online (attempt ${attemptNumber}/${maxAttempts})` + // Add delay before each attempt (except the first) + if (index > 0) { + await new Promise((resolve) => + setTimeout(resolve, delayBetweenAttempts * index) ); - return { success: true, attemptNumber }; } - return { success: false, attemptNumber, error: 'Non-200 status' }; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : "Unknown error"; - logger.debug( - `Exit node ${endpoint} ping failed (attempt ${attemptNumber}/${maxAttempts}): ${errorMessage}` - ); - return { success: false, attemptNumber, error: errorMessage }; + + try { + const response = await axios.get(`http://${endpoint}/ping`, { + timeout: timeoutMs, + validateStatus: (status) => status === 200 + }); + + if (response.status === 200) { + logger.debug( + `Exit node ${endpoint} is online (attempt ${attemptNumber}/${maxAttempts})` + ); + return { success: true, attemptNumber }; + } + return { + success: false, + attemptNumber, + error: "Non-200 status" + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : "Unknown error"; + logger.debug( + `Exit node ${endpoint} ping failed (attempt ${attemptNumber}/${maxAttempts}): ${errorMessage}` + ); + return { success: false, attemptNumber, error: errorMessage }; + } } - }); + ); try { // Wait for the first successful response or all to fail const results = await Promise.allSettled(attemptPromises); - + // Check if any attempt succeeded for (const result of results) { - if (result.status === 'fulfilled' && result.value.success) { + if (result.status === "fulfilled" && result.value.success) { return true; } } @@ -137,7 +147,11 @@ export async function verifyExitNodeOrgAccess( return { hasAccess: false, exitNode }; } -export async function listExitNodes(orgId: string, filterOnline = false, noCloud = false) { +export async function listExitNodes( + orgId: string, + filterOnline = false, + noCloud = false +) { const allExitNodes = await db .select({ exitNodeId: exitNodes.exitNodeId, @@ -166,7 +180,10 @@ export async function listExitNodes(orgId: string, filterOnline = false, noCloud eq(exitNodes.type, "gerbil"), or( // only choose nodes that are in the same region - eq(exitNodes.region, config.getRawPrivateConfig().app.region), + eq( + exitNodes.region, + config.getRawPrivateConfig().app.region + ), isNull(exitNodes.region) // or for enterprise where region is not set ) ), @@ -191,7 +208,7 @@ export async function listExitNodes(orgId: string, filterOnline = false, noCloud // let online: boolean; // if (filterOnline && node.type == "remoteExitNode") { // try { - // const isActuallyOnline = await checkExitNodeOnlineStatus( + // const isActuallyOnline = await checkExitNodeOnlineStatus( // node.endpoint // ); @@ -225,7 +242,8 @@ export async function listExitNodes(orgId: string, filterOnline = false, noCloud node.type === "remoteExitNode" && (!filterOnline || node.online) ); const gerbilExitNodes = allExitNodes.filter( - (node) => node.type === "gerbil" && (!filterOnline || node.online) && !noCloud + (node) => + node.type === "gerbil" && (!filterOnline || node.online) && !noCloud ); // THIS PROVIDES THE FALL @@ -334,7 +352,11 @@ export function selectBestExitNode( return fallbackNode; } -export async function checkExitNodeOrg(exitNodeId: number, orgId: string, trx: Transaction | typeof db = db) { +export async function checkExitNodeOrg( + exitNodeId: number, + orgId: string, + trx: Transaction | typeof db = db +) { const [exitNodeOrg] = await trx .select() .from(exitNodeOrgs) diff --git a/server/private/lib/exitNodes/index.ts b/server/private/lib/exitNodes/index.ts index 098a05806..00113b64a 100644 --- a/server/private/lib/exitNodes/index.ts +++ b/server/private/lib/exitNodes/index.ts @@ -12,4 +12,4 @@ */ export * from "./exitNodeComms"; -export * from "./exitNodes"; \ No newline at end of file +export * from "./exitNodes"; diff --git a/server/private/lib/lock.ts b/server/private/lib/lock.ts index 4a12063b9..08496f655 100644 --- a/server/private/lib/lock.ts +++ b/server/private/lib/lock.ts @@ -177,7 +177,9 @@ export class LockManager { const exists = value !== null; const ownedByMe = exists && - value!.startsWith(`${config.getRawConfig().gerbil.exit_node_name}:`); + value!.startsWith( + `${config.getRawConfig().gerbil.exit_node_name}:` + ); const owner = exists ? value!.split(":")[0] : undefined; return { diff --git a/server/private/lib/rateLimit.test.ts b/server/private/lib/rateLimit.test.ts index 59952c8cf..96adf082f 100644 --- a/server/private/lib/rateLimit.test.ts +++ b/server/private/lib/rateLimit.test.ts @@ -14,15 +14,15 @@ // Simple test file for the rate limit service with Redis // Run with: npx ts-node rateLimitService.test.ts -import { RateLimitService } from './rateLimit'; +import { RateLimitService } from "./rateLimit"; function generateClientId() { - return 'client-' + Math.random().toString(36).substring(2, 15); + return "client-" + Math.random().toString(36).substring(2, 15); } async function runTests() { - console.log('Starting Rate Limit Service Tests...\n'); - + console.log("Starting Rate Limit Service Tests...\n"); + const rateLimitService = new RateLimitService(); let testsPassed = 0; let testsTotal = 0; @@ -47,36 +47,54 @@ async function runTests() { } // Test 1: Basic rate limiting - await test('Should allow requests under the limit', async () => { + await test("Should allow requests under the limit", async () => { const clientId = generateClientId(); const maxRequests = 5; for (let i = 0; i < maxRequests - 1; i++) { - const result = await rateLimitService.checkRateLimit(clientId, undefined, maxRequests); + const result = await rateLimitService.checkRateLimit( + clientId, + undefined, + maxRequests + ); assert(!result.isLimited, `Request ${i + 1} should be allowed`); - assert(result.totalHits === i + 1, `Expected ${i + 1} hits, got ${result.totalHits}`); + assert( + result.totalHits === i + 1, + `Expected ${i + 1} hits, got ${result.totalHits}` + ); } }); // Test 2: Rate limit blocking - await test('Should block requests over the limit', async () => { + await test("Should block requests over the limit", async () => { const clientId = generateClientId(); const maxRequests = 30; // Use up all allowed requests for (let i = 0; i < maxRequests - 1; i++) { - const result = await rateLimitService.checkRateLimit(clientId, undefined, maxRequests); + const result = await rateLimitService.checkRateLimit( + clientId, + undefined, + maxRequests + ); assert(!result.isLimited, `Request ${i + 1} should be allowed`); } // Next request should be blocked - const blockedResult = await rateLimitService.checkRateLimit(clientId, undefined, maxRequests); - assert(blockedResult.isLimited, 'Request should be blocked'); - assert(blockedResult.reason === 'global', 'Should be blocked for global reason'); + const blockedResult = await rateLimitService.checkRateLimit( + clientId, + undefined, + maxRequests + ); + assert(blockedResult.isLimited, "Request should be blocked"); + assert( + blockedResult.reason === "global", + "Should be blocked for global reason" + ); }); // Test 3: Message type limits - await test('Should handle message type limits', async () => { + await test("Should handle message type limits", async () => { const clientId = generateClientId(); const globalMax = 10; const messageTypeMax = 2; @@ -84,54 +102,64 @@ async function runTests() { // Send messages of type 'ping' up to the limit for (let i = 0; i < messageTypeMax - 1; i++) { const result = await rateLimitService.checkRateLimit( - clientId, - 'ping', - globalMax, + clientId, + "ping", + globalMax, messageTypeMax ); - assert(!result.isLimited, `Ping message ${i + 1} should be allowed`); + assert( + !result.isLimited, + `Ping message ${i + 1} should be allowed` + ); } // Next 'ping' should be blocked const blockedResult = await rateLimitService.checkRateLimit( - clientId, - 'ping', - globalMax, + clientId, + "ping", + globalMax, messageTypeMax ); - assert(blockedResult.isLimited, 'Ping message should be blocked'); - assert(blockedResult.reason === 'message_type:ping', 'Should be blocked for message type'); + assert(blockedResult.isLimited, "Ping message should be blocked"); + assert( + blockedResult.reason === "message_type:ping", + "Should be blocked for message type" + ); // Other message types should still work const otherResult = await rateLimitService.checkRateLimit( - clientId, - 'pong', - globalMax, + clientId, + "pong", + globalMax, messageTypeMax ); - assert(!otherResult.isLimited, 'Pong message should be allowed'); + assert(!otherResult.isLimited, "Pong message should be allowed"); }); // Test 4: Reset functionality - await test('Should reset client correctly', async () => { + await test("Should reset client correctly", async () => { const clientId = generateClientId(); const maxRequests = 3; // Use up some requests await rateLimitService.checkRateLimit(clientId, undefined, maxRequests); - await rateLimitService.checkRateLimit(clientId, 'test', maxRequests); + await rateLimitService.checkRateLimit(clientId, "test", maxRequests); // Reset the client await rateLimitService.resetKey(clientId); // Should be able to make fresh requests - const result = await rateLimitService.checkRateLimit(clientId, undefined, maxRequests); - assert(!result.isLimited, 'Request after reset should be allowed'); - assert(result.totalHits === 1, 'Should have 1 hit after reset'); + const result = await rateLimitService.checkRateLimit( + clientId, + undefined, + maxRequests + ); + assert(!result.isLimited, "Request after reset should be allowed"); + assert(result.totalHits === 1, "Should have 1 hit after reset"); }); // Test 5: Different clients are independent - await test('Should handle different clients independently', async () => { + await test("Should handle different clients independently", async () => { const client1 = generateClientId(); const client2 = generateClientId(); const maxRequests = 2; @@ -139,43 +167,62 @@ async function runTests() { // Client 1 uses up their limit await rateLimitService.checkRateLimit(client1, undefined, maxRequests); await rateLimitService.checkRateLimit(client1, undefined, maxRequests); - const client1Blocked = await rateLimitService.checkRateLimit(client1, undefined, maxRequests); - assert(client1Blocked.isLimited, 'Client 1 should be blocked'); + const client1Blocked = await rateLimitService.checkRateLimit( + client1, + undefined, + maxRequests + ); + assert(client1Blocked.isLimited, "Client 1 should be blocked"); // Client 2 should still be able to make requests - const client2Result = await rateLimitService.checkRateLimit(client2, undefined, maxRequests); - assert(!client2Result.isLimited, 'Client 2 should not be blocked'); - assert(client2Result.totalHits === 1, 'Client 2 should have 1 hit'); + const client2Result = await rateLimitService.checkRateLimit( + client2, + undefined, + maxRequests + ); + assert(!client2Result.isLimited, "Client 2 should not be blocked"); + assert(client2Result.totalHits === 1, "Client 2 should have 1 hit"); }); // Test 6: Decrement functionality - await test('Should decrement correctly', async () => { + await test("Should decrement correctly", async () => { const clientId = generateClientId(); const maxRequests = 5; // Make some requests await rateLimitService.checkRateLimit(clientId, undefined, maxRequests); await rateLimitService.checkRateLimit(clientId, undefined, maxRequests); - let result = await rateLimitService.checkRateLimit(clientId, undefined, maxRequests); - assert(result.totalHits === 3, 'Should have 3 hits before decrement'); + let result = await rateLimitService.checkRateLimit( + clientId, + undefined, + maxRequests + ); + assert(result.totalHits === 3, "Should have 3 hits before decrement"); // Decrement await rateLimitService.decrementRateLimit(clientId); // Next request should reflect the decrement - result = await rateLimitService.checkRateLimit(clientId, undefined, maxRequests); - assert(result.totalHits === 3, 'Should have 3 hits after decrement + increment'); + result = await rateLimitService.checkRateLimit( + clientId, + undefined, + maxRequests + ); + assert( + result.totalHits === 3, + "Should have 3 hits after decrement + increment" + ); }); // Wait a moment for any pending Redis operations - console.log('\nWaiting for Redis sync...'); - await new Promise(resolve => setTimeout(resolve, 1000)); + console.log("\nWaiting for Redis sync..."); + await new Promise((resolve) => setTimeout(resolve, 1000)); // Force sync to test Redis integration - await test('Should sync to Redis', async () => { + await test("Should sync to Redis", async () => { await rateLimitService.forceSyncAllPendingData(); // If this doesn't throw, Redis sync is working - assert(true, 'Redis sync completed'); + assert(true, "Redis sync completed"); }); // Cleanup @@ -185,18 +232,18 @@ async function runTests() { console.log(`\n--- Test Results ---`); console.log(`✅ Passed: ${testsPassed}/${testsTotal}`); console.log(`❌ Failed: ${testsTotal - testsPassed}/${testsTotal}`); - + if (testsPassed === testsTotal) { - console.log('\n🎉 All tests passed!'); + console.log("\n🎉 All tests passed!"); process.exit(0); } else { - console.log('\n💥 Some tests failed!'); + console.log("\n💥 Some tests failed!"); process.exit(1); } } // Run the tests -runTests().catch(error => { - console.error('Test runner error:', error); +runTests().catch((error) => { + console.error("Test runner error:", error); process.exit(1); -}); \ No newline at end of file +}); diff --git a/server/private/lib/rateLimit.ts b/server/private/lib/rateLimit.ts index 6d4ab44dd..984d95c62 100644 --- a/server/private/lib/rateLimit.ts +++ b/server/private/lib/rateLimit.ts @@ -40,7 +40,8 @@ interface RateLimitResult { export class RateLimitService { private localRateLimitTracker: Map = new Map(); - private localMessageTypeRateLimitTracker: Map = new Map(); + private localMessageTypeRateLimitTracker: Map = + new Map(); private cleanupInterval: NodeJS.Timeout | null = null; private forceSyncInterval: NodeJS.Timeout | null = null; @@ -68,12 +69,18 @@ export class RateLimitService { return `ratelimit:${clientId}`; } - private getMessageTypeRateLimitKey(clientId: string, messageType: string): string { + private getMessageTypeRateLimitKey( + clientId: string, + messageType: string + ): string { return `ratelimit:${clientId}:${messageType}`; } // Helper function to clean up old timestamp fields from a Redis hash - private async cleanupOldTimestamps(key: string, windowStart: number): Promise { + private async cleanupOldTimestamps( + key: string, + windowStart: number + ): Promise { if (!redisManager.isRedisEnabled()) return; try { @@ -101,10 +108,15 @@ export class RateLimitService { const batch = fieldsToDelete.slice(i, i + batchSize); await client.hdel(key, ...batch); } - logger.debug(`Cleaned up ${fieldsToDelete.length} old timestamp fields from ${key}`); + logger.debug( + `Cleaned up ${fieldsToDelete.length} old timestamp fields from ${key}` + ); } } catch (error) { - logger.error(`Failed to cleanup old timestamps for key ${key}:`, error); + logger.error( + `Failed to cleanup old timestamps for key ${key}:`, + error + ); // Don't throw - cleanup failures shouldn't block rate limiting } } @@ -114,7 +126,8 @@ export class RateLimitService { clientId: string, tracker: RateLimitTracker ): Promise { - if (!redisManager.isRedisEnabled() || tracker.pendingCount === 0) return; + if (!redisManager.isRedisEnabled() || tracker.pendingCount === 0) + return; try { const currentTime = Math.floor(Date.now() / 1000); @@ -132,7 +145,11 @@ export class RateLimitService { const newValue = ( parseInt(currentValue || "0") + tracker.pendingCount ).toString(); - await redisManager.hset(globalKey, currentTime.toString(), newValue); + await redisManager.hset( + globalKey, + currentTime.toString(), + newValue + ); // Set TTL using the client directly - this prevents the key from persisting forever if (redisManager.getClient()) { @@ -145,7 +162,9 @@ export class RateLimitService { tracker.lastSyncedCount = tracker.count; tracker.pendingCount = 0; - logger.debug(`Synced global rate limit to Redis for client ${clientId}`); + logger.debug( + `Synced global rate limit to Redis for client ${clientId}` + ); } catch (error) { logger.error("Failed to sync global rate limit to Redis:", error); } @@ -156,12 +175,16 @@ export class RateLimitService { messageType: string, tracker: RateLimitTracker ): Promise { - if (!redisManager.isRedisEnabled() || tracker.pendingCount === 0) return; + if (!redisManager.isRedisEnabled() || tracker.pendingCount === 0) + return; try { const currentTime = Math.floor(Date.now() / 1000); const windowStart = currentTime - RATE_LIMIT_WINDOW; - const messageTypeKey = this.getMessageTypeRateLimitKey(clientId, messageType); + const messageTypeKey = this.getMessageTypeRateLimitKey( + clientId, + messageType + ); // Clean up old timestamp fields before writing await this.cleanupOldTimestamps(messageTypeKey, windowStart); @@ -195,12 +218,17 @@ export class RateLimitService { `Synced message type rate limit to Redis for client ${clientId}, type ${messageType}` ); } catch (error) { - logger.error("Failed to sync message type rate limit to Redis:", error); + logger.error( + "Failed to sync message type rate limit to Redis:", + error + ); } } // Initialize local tracker from Redis data - private async initializeLocalTracker(clientId: string): Promise { + private async initializeLocalTracker( + clientId: string + ): Promise { const currentTime = Math.floor(Date.now() / 1000); const windowStart = currentTime - RATE_LIMIT_WINDOW; @@ -215,14 +243,16 @@ export class RateLimitService { try { const globalKey = this.getRateLimitKey(clientId); - + // Clean up old timestamp fields before reading await this.cleanupOldTimestamps(globalKey, windowStart); - + const globalRateLimitData = await redisManager.hgetall(globalKey); let count = 0; - for (const [timestamp, countStr] of Object.entries(globalRateLimitData)) { + for (const [timestamp, countStr] of Object.entries( + globalRateLimitData + )) { const time = parseInt(timestamp); if (time >= windowStart) { count += parseInt(countStr); @@ -236,7 +266,10 @@ export class RateLimitService { lastSyncedCount: count }; } catch (error) { - logger.error("Failed to initialize global tracker from Redis:", error); + logger.error( + "Failed to initialize global tracker from Redis:", + error + ); return { count: 0, windowStart: currentTime, @@ -263,15 +296,21 @@ export class RateLimitService { } try { - const messageTypeKey = this.getMessageTypeRateLimitKey(clientId, messageType); - + const messageTypeKey = this.getMessageTypeRateLimitKey( + clientId, + messageType + ); + // Clean up old timestamp fields before reading await this.cleanupOldTimestamps(messageTypeKey, windowStart); - - const messageTypeRateLimitData = await redisManager.hgetall(messageTypeKey); + + const messageTypeRateLimitData = + await redisManager.hgetall(messageTypeKey); let count = 0; - for (const [timestamp, countStr] of Object.entries(messageTypeRateLimitData)) { + for (const [timestamp, countStr] of Object.entries( + messageTypeRateLimitData + )) { const time = parseInt(timestamp); if (time >= windowStart) { count += parseInt(countStr); @@ -285,7 +324,10 @@ export class RateLimitService { lastSyncedCount: count }; } catch (error) { - logger.error("Failed to initialize message type tracker from Redis:", error); + logger.error( + "Failed to initialize message type tracker from Redis:", + error + ); return { count: 0, windowStart: currentTime, @@ -327,7 +369,10 @@ export class RateLimitService { isLimited: true, reason: "global", totalHits: globalTracker.count, - resetTime: new Date((globalTracker.windowStart + Math.floor(windowMs / 1000)) * 1000) + resetTime: new Date( + (globalTracker.windowStart + Math.floor(windowMs / 1000)) * + 1000 + ) }; } @@ -339,19 +384,32 @@ export class RateLimitService { // Check message type specific rate limit if messageType is provided if (messageType) { const messageTypeKey = `${clientId}:${messageType}`; - let messageTypeTracker = this.localMessageTypeRateLimitTracker.get(messageTypeKey); + let messageTypeTracker = + this.localMessageTypeRateLimitTracker.get(messageTypeKey); - if (!messageTypeTracker || messageTypeTracker.windowStart < windowStart) { + if ( + !messageTypeTracker || + messageTypeTracker.windowStart < windowStart + ) { // New window or first request for this message type - initialize from Redis if available - messageTypeTracker = await this.initializeMessageTypeTracker(clientId, messageType); + messageTypeTracker = await this.initializeMessageTypeTracker( + clientId, + messageType + ); messageTypeTracker.windowStart = currentTime; - this.localMessageTypeRateLimitTracker.set(messageTypeKey, messageTypeTracker); + this.localMessageTypeRateLimitTracker.set( + messageTypeKey, + messageTypeTracker + ); } // Increment message type counters messageTypeTracker.count++; messageTypeTracker.pendingCount++; - this.localMessageTypeRateLimitTracker.set(messageTypeKey, messageTypeTracker); + this.localMessageTypeRateLimitTracker.set( + messageTypeKey, + messageTypeTracker + ); // Check if message type limit would be exceeded if (messageTypeTracker.count >= messageTypeLimit) { @@ -359,25 +417,38 @@ export class RateLimitService { isLimited: true, reason: `message_type:${messageType}`, totalHits: messageTypeTracker.count, - resetTime: new Date((messageTypeTracker.windowStart + Math.floor(windowMs / 1000)) * 1000) + resetTime: new Date( + (messageTypeTracker.windowStart + + Math.floor(windowMs / 1000)) * + 1000 + ) }; } // Sync to Redis if threshold reached if (messageTypeTracker.pendingCount >= REDIS_SYNC_THRESHOLD) { - this.syncMessageTypeRateLimitToRedis(clientId, messageType, messageTypeTracker); + this.syncMessageTypeRateLimitToRedis( + clientId, + messageType, + messageTypeTracker + ); } } return { isLimited: false, totalHits: globalTracker.count, - resetTime: new Date((globalTracker.windowStart + Math.floor(windowMs / 1000)) * 1000) + resetTime: new Date( + (globalTracker.windowStart + Math.floor(windowMs / 1000)) * 1000 + ) }; } // Decrement function for skipSuccessfulRequests/skipFailedRequests functionality - async decrementRateLimit(clientId: string, messageType?: string): Promise { + async decrementRateLimit( + clientId: string, + messageType?: string + ): Promise { // Decrement global counter const globalTracker = this.localRateLimitTracker.get(clientId); if (globalTracker && globalTracker.count > 0) { @@ -389,7 +460,8 @@ export class RateLimitService { // Decrement message type counter if provided if (messageType) { const messageTypeKey = `${clientId}:${messageType}`; - const messageTypeTracker = this.localMessageTypeRateLimitTracker.get(messageTypeKey); + const messageTypeTracker = + this.localMessageTypeRateLimitTracker.get(messageTypeKey); if (messageTypeTracker && messageTypeTracker.count > 0) { messageTypeTracker.count--; messageTypeTracker.pendingCount--; @@ -401,7 +473,7 @@ export class RateLimitService { async resetKey(clientId: string): Promise { // Remove from local tracking this.localRateLimitTracker.delete(clientId); - + // Remove all message type entries for this client for (const [key] of this.localMessageTypeRateLimitTracker) { if (key.startsWith(`${clientId}:`)) { @@ -417,9 +489,13 @@ export class RateLimitService { // Get all message type keys for this client and delete them const client = redisManager.getClient(); if (client) { - const messageTypeKeys = await client.keys(`ratelimit:${clientId}:*`); + const messageTypeKeys = await client.keys( + `ratelimit:${clientId}:*` + ); if (messageTypeKeys.length > 0) { - await Promise.all(messageTypeKeys.map(key => redisManager.del(key))); + await Promise.all( + messageTypeKeys.map((key) => redisManager.del(key)) + ); } } } @@ -431,7 +507,10 @@ export class RateLimitService { const windowStart = currentTime - RATE_LIMIT_WINDOW; // Clean up global rate limit tracking and sync pending data - for (const [clientId, tracker] of this.localRateLimitTracker.entries()) { + for (const [ + clientId, + tracker + ] of this.localRateLimitTracker.entries()) { if (tracker.windowStart < windowStart) { // Sync any pending data before cleanup if (tracker.pendingCount > 0) { @@ -442,12 +521,19 @@ export class RateLimitService { } // Clean up message type rate limit tracking and sync pending data - for (const [key, tracker] of this.localMessageTypeRateLimitTracker.entries()) { + for (const [ + key, + tracker + ] of this.localMessageTypeRateLimitTracker.entries()) { if (tracker.windowStart < windowStart) { // Sync any pending data before cleanup if (tracker.pendingCount > 0) { const [clientId, messageType] = key.split(":", 2); - await this.syncMessageTypeRateLimitToRedis(clientId, messageType, tracker); + await this.syncMessageTypeRateLimitToRedis( + clientId, + messageType, + tracker + ); } this.localMessageTypeRateLimitTracker.delete(key); } @@ -461,17 +547,27 @@ export class RateLimitService { logger.debug("Force syncing all pending rate limit data to Redis..."); // Sync all pending global rate limits - for (const [clientId, tracker] of this.localRateLimitTracker.entries()) { + for (const [ + clientId, + tracker + ] of this.localRateLimitTracker.entries()) { if (tracker.pendingCount > 0) { await this.syncRateLimitToRedis(clientId, tracker); } } // Sync all pending message type rate limits - for (const [key, tracker] of this.localMessageTypeRateLimitTracker.entries()) { + for (const [ + key, + tracker + ] of this.localMessageTypeRateLimitTracker.entries()) { if (tracker.pendingCount > 0) { const [clientId, messageType] = key.split(":", 2); - await this.syncMessageTypeRateLimitToRedis(clientId, messageType, tracker); + await this.syncMessageTypeRateLimitToRedis( + clientId, + messageType, + tracker + ); } } @@ -504,4 +600,4 @@ export class RateLimitService { } // Export singleton instance -export const rateLimitService = new RateLimitService(); \ No newline at end of file +export const rateLimitService = new RateLimitService(); diff --git a/server/private/lib/rateLimitStore.ts b/server/private/lib/rateLimitStore.ts index 203551254..32495cd20 100644 --- a/server/private/lib/rateLimitStore.ts +++ b/server/private/lib/rateLimitStore.ts @@ -17,7 +17,10 @@ import { MemoryStore, Store } from "express-rate-limit"; import RedisStore from "#private/lib/redisStore"; export function createStore(): Store { - if (build != "oss" && privateConfig.getRawPrivateConfig().flags.enable_redis) { + if ( + build != "oss" && + privateConfig.getRawPrivateConfig().flags.enable_redis + ) { const rateLimitStore: Store = new RedisStore({ prefix: "api-rate-limit", // Optional: customize Redis key prefix skipFailedRequests: true, // Don't count failed requests diff --git a/server/private/lib/redis.ts b/server/private/lib/redis.ts index 324a6a743..6b7826ea6 100644 --- a/server/private/lib/redis.ts +++ b/server/private/lib/redis.ts @@ -19,7 +19,7 @@ import { build } from "@server/build"; class RedisManager { public client: Redis | null = null; private writeClient: Redis | null = null; // Master for writes - private readClient: Redis | null = null; // Replica for reads + private readClient: Redis | null = null; // Replica for reads private subscriber: Redis | null = null; private publisher: Redis | null = null; private isEnabled: boolean = false; @@ -46,7 +46,8 @@ class RedisManager { this.isEnabled = false; return; } - this.isEnabled = privateConfig.getRawPrivateConfig().flags.enable_redis || false; + this.isEnabled = + privateConfig.getRawPrivateConfig().flags.enable_redis || false; if (this.isEnabled) { this.initializeClients(); } @@ -63,15 +64,19 @@ class RedisManager { } private async triggerReconnectionCallbacks(): Promise { - logger.info(`Triggering ${this.reconnectionCallbacks.size} reconnection callbacks`); - - const promises = Array.from(this.reconnectionCallbacks).map(async (callback) => { - try { - await callback(); - } catch (error) { - logger.error("Error in reconnection callback:", error); + logger.info( + `Triggering ${this.reconnectionCallbacks.size} reconnection callbacks` + ); + + const promises = Array.from(this.reconnectionCallbacks).map( + async (callback) => { + try { + await callback(); + } catch (error) { + logger.error("Error in reconnection callback:", error); + } } - }); + ); await Promise.allSettled(promises); } @@ -79,13 +84,17 @@ class RedisManager { private async resubscribeToChannels(): Promise { if (!this.subscriber || this.subscribers.size === 0) return; - logger.info(`Re-subscribing to ${this.subscribers.size} channels after Redis reconnection`); - + logger.info( + `Re-subscribing to ${this.subscribers.size} channels after Redis reconnection` + ); + try { const channels = Array.from(this.subscribers.keys()); if (channels.length > 0) { await this.subscriber.subscribe(...channels); - logger.info(`Successfully re-subscribed to channels: ${channels.join(', ')}`); + logger.info( + `Successfully re-subscribed to channels: ${channels.join(", ")}` + ); } } catch (error) { logger.error("Failed to re-subscribe to channels:", error); @@ -98,7 +107,7 @@ class RedisManager { host: redisConfig.host!, port: redisConfig.port!, password: redisConfig.password, - db: redisConfig.db, + db: redisConfig.db // tls: { // rejectUnauthorized: // redisConfig.tls?.reject_unauthorized || false @@ -112,7 +121,7 @@ class RedisManager { if (!redisConfig.replicas || redisConfig.replicas.length === 0) { return null; } - + // Use the first replica for simplicity // In production, you might want to implement load balancing across replicas const replica = redisConfig.replicas[0]; @@ -120,7 +129,7 @@ class RedisManager { host: replica.host!, port: replica.port!, password: replica.password, - db: replica.db || redisConfig.db, + db: replica.db || redisConfig.db // tls: { // rejectUnauthorized: // replica.tls?.reject_unauthorized || false @@ -133,7 +142,7 @@ class RedisManager { private initializeClients(): void { const masterConfig = this.getRedisConfig(); const replicaConfig = this.getReplicaRedisConfig(); - + this.hasReplicas = replicaConfig !== null; try { @@ -144,7 +153,7 @@ class RedisManager { maxRetriesPerRequest: 3, keepAlive: 30000, connectTimeout: this.connectionTimeout, - commandTimeout: this.commandTimeout, + commandTimeout: this.commandTimeout }); // Initialize replica connection for reads (if available) @@ -155,7 +164,7 @@ class RedisManager { maxRetriesPerRequest: 3, keepAlive: 30000, connectTimeout: this.connectionTimeout, - commandTimeout: this.commandTimeout, + commandTimeout: this.commandTimeout }); } else { // Fallback to master for reads if no replicas @@ -172,7 +181,7 @@ class RedisManager { maxRetriesPerRequest: 3, keepAlive: 30000, connectTimeout: this.connectionTimeout, - commandTimeout: this.commandTimeout, + commandTimeout: this.commandTimeout }); // Subscriber uses replica if available (reads) @@ -182,7 +191,7 @@ class RedisManager { maxRetriesPerRequest: 3, keepAlive: 30000, connectTimeout: this.connectionTimeout, - commandTimeout: this.commandTimeout, + commandTimeout: this.commandTimeout }); // Add reconnection handlers for write client @@ -202,11 +211,14 @@ class RedisManager { logger.info("Redis write client ready"); this.isWriteHealthy = true; this.updateOverallHealth(); - + // Trigger reconnection callbacks when Redis comes back online if (this.isHealthy) { - this.triggerReconnectionCallbacks().catch(error => { - logger.error("Error triggering reconnection callbacks:", error); + this.triggerReconnectionCallbacks().catch((error) => { + logger.error( + "Error triggering reconnection callbacks:", + error + ); }); } }); @@ -233,11 +245,14 @@ class RedisManager { logger.info("Redis read client ready"); this.isReadHealthy = true; this.updateOverallHealth(); - + // Trigger reconnection callbacks when Redis comes back online if (this.isHealthy) { - this.triggerReconnectionCallbacks().catch(error => { - logger.error("Error triggering reconnection callbacks:", error); + this.triggerReconnectionCallbacks().catch((error) => { + logger.error( + "Error triggering reconnection callbacks:", + error + ); }); } }); @@ -298,8 +313,8 @@ class RedisManager { } ); - const setupMessage = this.hasReplicas - ? "Redis clients initialized successfully with replica support" + const setupMessage = this.hasReplicas + ? "Redis clients initialized successfully with replica support" : "Redis clients initialized successfully (single instance)"; logger.info(setupMessage); @@ -313,7 +328,8 @@ class RedisManager { private updateOverallHealth(): void { // Overall health is true if write is healthy and (read is healthy OR we don't have replicas) - this.isHealthy = this.isWriteHealthy && (this.isReadHealthy || !this.hasReplicas); + this.isHealthy = + this.isWriteHealthy && (this.isReadHealthy || !this.hasReplicas); } private async executeWithRetry( @@ -322,49 +338,61 @@ class RedisManager { fallbackOperation?: () => Promise ): Promise { let lastError: Error | null = null; - + for (let attempt = 0; attempt <= this.maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error as Error; - + // If this is the last attempt, try fallback if available if (attempt === this.maxRetries && fallbackOperation) { try { - logger.warn(`${operationName} primary operation failed, trying fallback`); + logger.warn( + `${operationName} primary operation failed, trying fallback` + ); return await fallbackOperation(); } catch (fallbackError) { - logger.error(`${operationName} fallback also failed:`, fallbackError); + logger.error( + `${operationName} fallback also failed:`, + fallbackError + ); throw lastError; } } - + // Don't retry on the last attempt if (attempt === this.maxRetries) { break; } - + // Calculate delay with exponential backoff const delay = Math.min( - this.baseRetryDelay * Math.pow(this.backoffMultiplier, attempt), + this.baseRetryDelay * + Math.pow(this.backoffMultiplier, attempt), this.maxRetryDelay ); - - logger.warn(`${operationName} failed (attempt ${attempt + 1}/${this.maxRetries + 1}), retrying in ${delay}ms:`, error); - + + logger.warn( + `${operationName} failed (attempt ${attempt + 1}/${this.maxRetries + 1}), retrying in ${delay}ms:`, + error + ); + // Wait before retrying - await new Promise(resolve => setTimeout(resolve, delay)); + await new Promise((resolve) => setTimeout(resolve, delay)); } } - - logger.error(`${operationName} failed after ${this.maxRetries + 1} attempts:`, lastError); + + logger.error( + `${operationName} failed after ${this.maxRetries + 1} attempts:`, + lastError + ); throw lastError; } private startHealthMonitoring(): void { if (!this.isEnabled) return; - + // Check health every 30 seconds setInterval(async () => { try { @@ -381,7 +409,7 @@ class RedisManager { private async checkRedisHealth(): Promise { const now = Date.now(); - + // Only check health every 30 seconds if (now - this.lastHealthCheck < this.healthCheckInterval) { return this.isHealthy; @@ -400,24 +428,45 @@ class RedisManager { // Check write client (master) health await Promise.race([ this.writeClient.ping(), - new Promise((_, reject) => - setTimeout(() => reject(new Error('Write client health check timeout')), 2000) + new Promise((_, reject) => + setTimeout( + () => + reject( + new Error("Write client health check timeout") + ), + 2000 + ) ) ]); this.isWriteHealthy = true; // Check read client health if it's different from write client - if (this.hasReplicas && this.readClient && this.readClient !== this.writeClient) { + if ( + this.hasReplicas && + this.readClient && + this.readClient !== this.writeClient + ) { try { await Promise.race([ this.readClient.ping(), - new Promise((_, reject) => - setTimeout(() => reject(new Error('Read client health check timeout')), 2000) + new Promise((_, reject) => + setTimeout( + () => + reject( + new Error( + "Read client health check timeout" + ) + ), + 2000 + ) ) ]); this.isReadHealthy = true; } catch (error) { - logger.error("Redis read client health check failed:", error); + logger.error( + "Redis read client health check failed:", + error + ); this.isReadHealthy = false; } } else { @@ -475,16 +524,13 @@ class RedisManager { if (!this.isRedisEnabled() || !this.writeClient) return false; try { - await this.executeWithRetry( - async () => { - if (ttl) { - await this.writeClient!.setex(key, ttl, value); - } else { - await this.writeClient!.set(key, value); - } - }, - "Redis SET" - ); + await this.executeWithRetry(async () => { + if (ttl) { + await this.writeClient!.setex(key, ttl, value); + } else { + await this.writeClient!.set(key, value); + } + }, "Redis SET"); return true; } catch (error) { logger.error("Redis SET error:", error); @@ -496,9 +542,10 @@ class RedisManager { if (!this.isRedisEnabled() || !this.readClient) return null; try { - const fallbackOperation = (this.hasReplicas && this.writeClient && this.isWriteHealthy) - ? () => this.writeClient!.get(key) - : undefined; + const fallbackOperation = + this.hasReplicas && this.writeClient && this.isWriteHealthy + ? () => this.writeClient!.get(key) + : undefined; return await this.executeWithRetry( () => this.readClient!.get(key), @@ -560,9 +607,10 @@ class RedisManager { if (!this.isRedisEnabled() || !this.readClient) return []; try { - const fallbackOperation = (this.hasReplicas && this.writeClient && this.isWriteHealthy) - ? () => this.writeClient!.smembers(key) - : undefined; + const fallbackOperation = + this.hasReplicas && this.writeClient && this.isWriteHealthy + ? () => this.writeClient!.smembers(key) + : undefined; return await this.executeWithRetry( () => this.readClient!.smembers(key), @@ -598,9 +646,10 @@ class RedisManager { if (!this.isRedisEnabled() || !this.readClient) return null; try { - const fallbackOperation = (this.hasReplicas && this.writeClient && this.isWriteHealthy) - ? () => this.writeClient!.hget(key, field) - : undefined; + const fallbackOperation = + this.hasReplicas && this.writeClient && this.isWriteHealthy + ? () => this.writeClient!.hget(key, field) + : undefined; return await this.executeWithRetry( () => this.readClient!.hget(key, field), @@ -632,9 +681,10 @@ class RedisManager { if (!this.isRedisEnabled() || !this.readClient) return {}; try { - const fallbackOperation = (this.hasReplicas && this.writeClient && this.isWriteHealthy) - ? () => this.writeClient!.hgetall(key) - : undefined; + const fallbackOperation = + this.hasReplicas && this.writeClient && this.isWriteHealthy + ? () => this.writeClient!.hgetall(key) + : undefined; return await this.executeWithRetry( () => this.readClient!.hgetall(key), @@ -658,18 +708,18 @@ class RedisManager { } try { - await this.executeWithRetry( - async () => { - // Add timeout to prevent hanging - return Promise.race([ - this.publisher!.publish(channel, message), - new Promise((_, reject) => - setTimeout(() => reject(new Error('Redis publish timeout')), 3000) + await this.executeWithRetry(async () => { + // Add timeout to prevent hanging + return Promise.race([ + this.publisher!.publish(channel, message), + new Promise((_, reject) => + setTimeout( + () => reject(new Error("Redis publish timeout")), + 3000 ) - ]); - }, - "Redis PUBLISH" - ); + ) + ]); + }, "Redis PUBLISH"); return true; } catch (error) { logger.error("Redis PUBLISH error:", error); @@ -689,17 +739,20 @@ class RedisManager { if (!this.subscribers.has(channel)) { this.subscribers.set(channel, new Set()); // Only subscribe to the channel if it's the first subscriber - await this.executeWithRetry( - async () => { - return Promise.race([ - this.subscriber!.subscribe(channel), - new Promise((_, reject) => - setTimeout(() => reject(new Error('Redis subscribe timeout')), 5000) + await this.executeWithRetry(async () => { + return Promise.race([ + this.subscriber!.subscribe(channel), + new Promise((_, reject) => + setTimeout( + () => + reject( + new Error("Redis subscribe timeout") + ), + 5000 ) - ]); - }, - "Redis SUBSCRIBE" - ); + ) + ]); + }, "Redis SUBSCRIBE"); } this.subscribers.get(channel)!.add(callback); diff --git a/server/private/lib/redisStore.ts b/server/private/lib/redisStore.ts index 235f8f8fe..2360e309f 100644 --- a/server/private/lib/redisStore.ts +++ b/server/private/lib/redisStore.ts @@ -11,9 +11,9 @@ * This file is not licensed under the AGPLv3. */ -import { Store, Options, IncrementResponse } from 'express-rate-limit'; -import { rateLimitService } from './rateLimit'; -import logger from '@server/logger'; +import { Store, Options, IncrementResponse } from "express-rate-limit"; +import { rateLimitService } from "./rateLimit"; +import logger from "@server/logger"; /** * A Redis-backed rate limiting store for express-rate-limit that optimizes @@ -57,12 +57,14 @@ export default class RedisStore implements Store { * * @param options - Configuration options for the store. */ - constructor(options: { - prefix?: string; - skipFailedRequests?: boolean; - skipSuccessfulRequests?: boolean; - } = {}) { - this.prefix = options.prefix || 'express-rate-limit'; + constructor( + options: { + prefix?: string; + skipFailedRequests?: boolean; + skipSuccessfulRequests?: boolean; + } = {} + ) { + this.prefix = options.prefix || "express-rate-limit"; this.skipFailedRequests = options.skipFailedRequests || false; this.skipSuccessfulRequests = options.skipSuccessfulRequests || false; } @@ -101,7 +103,8 @@ export default class RedisStore implements Store { return { totalHits: result.totalHits || 1, - resetTime: result.resetTime || new Date(Date.now() + this.windowMs) + resetTime: + result.resetTime || new Date(Date.now() + this.windowMs) }; } catch (error) { logger.error(`RedisStore increment error for key ${key}:`, error); @@ -158,7 +161,9 @@ export default class RedisStore implements Store { */ async resetAll(): Promise { try { - logger.warn('RedisStore resetAll called - this operation can be expensive'); + logger.warn( + "RedisStore resetAll called - this operation can be expensive" + ); // Force sync all pending data first await rateLimitService.forceSyncAllPendingData(); @@ -167,9 +172,9 @@ export default class RedisStore implements Store { // scanning all Redis keys with our prefix, which could be expensive. // In production, it's better to let entries expire naturally. - logger.info('RedisStore resetAll completed (pending data synced)'); + logger.info("RedisStore resetAll completed (pending data synced)"); } catch (error) { - logger.error('RedisStore resetAll error:', error); + logger.error("RedisStore resetAll error:", error); // Don't throw - this is an optional method } } @@ -181,7 +186,9 @@ export default class RedisStore implements Store { * @param key - The identifier for a client. * @returns Current hit count and reset time, or null if no data exists. */ - async getHits(key: string): Promise<{ totalHits: number; resetTime: Date } | null> { + async getHits( + key: string + ): Promise<{ totalHits: number; resetTime: Date } | null> { try { const clientId = `${this.prefix}:${key}`; @@ -200,7 +207,8 @@ export default class RedisStore implements Store { return { totalHits: Math.max(0, (result.totalHits || 0) - 1), // Adjust for the decrement - resetTime: result.resetTime || new Date(Date.now() + this.windowMs) + resetTime: + result.resetTime || new Date(Date.now() + this.windowMs) }; } catch (error) { logger.error(`RedisStore getHits error for key ${key}:`, error); @@ -215,9 +223,9 @@ export default class RedisStore implements Store { async shutdown(): Promise { try { // The rateLimitService handles its own cleanup - logger.info('RedisStore shutdown completed'); + logger.info("RedisStore shutdown completed"); } catch (error) { - logger.error('RedisStore shutdown error:', error); + logger.error("RedisStore shutdown error:", error); } } } diff --git a/server/private/lib/resend.ts b/server/private/lib/resend.ts index 17384ea31..42a11c152 100644 --- a/server/private/lib/resend.ts +++ b/server/private/lib/resend.ts @@ -16,10 +16,10 @@ import privateConfig from "#private/lib/config"; import logger from "@server/logger"; export enum AudienceIds { - SignUps = "6c4e77b2-0851-4bd6-bac8-f51f91360f1a", - Subscribed = "870b43fd-387f-44de-8fc1-707335f30b20", - Churned = "f3ae92bd-2fdb-4d77-8746-2118afd62549", - Newsletter = "5500c431-191c-42f0-a5d4-8b6d445b4ea0" + SignUps = "6c4e77b2-0851-4bd6-bac8-f51f91360f1a", + Subscribed = "870b43fd-387f-44de-8fc1-707335f30b20", + Churned = "f3ae92bd-2fdb-4d77-8746-2118afd62549", + Newsletter = "5500c431-191c-42f0-a5d4-8b6d445b4ea0" } const resend = new Resend( @@ -33,7 +33,9 @@ export async function moveEmailToAudience( audienceId: AudienceIds ) { if (process.env.ENVIRONMENT !== "prod") { - logger.debug(`Skipping moving email ${email} to audience ${audienceId} in non-prod environment`); + logger.debug( + `Skipping moving email ${email} to audience ${audienceId} in non-prod environment` + ); return; } const { error, data } = await retryWithBackoff(async () => { diff --git a/server/private/lib/traefik/index.ts b/server/private/lib/traefik/index.ts index 30d831819..5f2c2635e 100644 --- a/server/private/lib/traefik/index.ts +++ b/server/private/lib/traefik/index.ts @@ -11,4 +11,4 @@ * This file is not licensed under the AGPLv3. */ -export * from "./getTraefikConfig"; \ No newline at end of file +export * from "./getTraefikConfig"; diff --git a/server/private/license/licenseJwt.ts b/server/private/license/licenseJwt.ts index f137db301..eb27b78f9 100644 --- a/server/private/license/licenseJwt.ts +++ b/server/private/license/licenseJwt.ts @@ -19,10 +19,7 @@ import * as crypto from "crypto"; * @param publicKey - The public key used for verification (PEM format) * @returns The decoded payload if validation succeeds, throws an error otherwise */ -function validateJWT( - token: string, - publicKey: string -): Payload { +function validateJWT(token: string, publicKey: string): Payload { // Split the JWT into its three parts const parts = token.split("."); if (parts.length !== 3) { diff --git a/server/private/middlewares/logActionAudit.ts b/server/private/middlewares/logActionAudit.ts index c89a88964..17cc67c08 100644 --- a/server/private/middlewares/logActionAudit.ts +++ b/server/private/middlewares/logActionAudit.ts @@ -41,7 +41,11 @@ async function getActionDays(orgId: string): Promise { } // store the result in cache - cache.set(`org_${orgId}_actionDays`, org.settingsLogRetentionDaysAction, 300); + cache.set( + `org_${orgId}_actionDays`, + org.settingsLogRetentionDaysAction, + 300 + ); return org.settingsLogRetentionDaysAction; } @@ -141,4 +145,3 @@ export function logActionAudit(action: ActionsEnum) { } }; } - diff --git a/server/private/middlewares/verifyCertificateAccess.ts b/server/private/middlewares/verifyCertificateAccess.ts index 1708215ec..dcc57dcae 100644 --- a/server/private/middlewares/verifyCertificateAccess.ts +++ b/server/private/middlewares/verifyCertificateAccess.ts @@ -28,7 +28,8 @@ export async function verifyCertificateAccess( try { // Assume user/org access is already verified const orgId = req.params.orgId; - const certId = req.params.certId || req.body?.certId || req.query?.certId; + const certId = + req.params.certId || req.body?.certId || req.query?.certId; let domainId = req.params.domainId || req.body?.domainId || req.query?.domainId; @@ -39,10 +40,12 @@ export async function verifyCertificateAccess( } if (!domainId) { - if (!certId) { return next( - createHttpError(HttpCode.BAD_REQUEST, "Must provide either certId or domainId") + createHttpError( + HttpCode.BAD_REQUEST, + "Must provide either certId or domainId" + ) ); } @@ -75,7 +78,10 @@ export async function verifyCertificateAccess( if (!domainId) { return next( - createHttpError(HttpCode.BAD_REQUEST, "Must provide either certId or domainId") + createHttpError( + HttpCode.BAD_REQUEST, + "Must provide either certId or domainId" + ) ); } diff --git a/server/private/middlewares/verifyIdpAccess.ts b/server/private/middlewares/verifyIdpAccess.ts index 87397a3d0..410956844 100644 --- a/server/private/middlewares/verifyIdpAccess.ts +++ b/server/private/middlewares/verifyIdpAccess.ts @@ -24,8 +24,7 @@ export async function verifyIdpAccess( ) { try { const userId = req.user!.userId; - const idpId = - req.params.idpId || req.body.idpId || req.query.idpId; + const idpId = req.params.idpId || req.body.idpId || req.query.idpId; const orgId = req.params.orgId; if (!userId) { @@ -50,9 +49,7 @@ export async function verifyIdpAccess( .select() .from(idp) .innerJoin(idpOrg, eq(idp.idpId, idpOrg.idpId)) - .where( - and(eq(idp.idpId, idpId), eq(idpOrg.orgId, orgId)) - ) + .where(and(eq(idp.idpId, idpId), eq(idpOrg.orgId, orgId))) .limit(1); if (!idpRes || !idpRes.idp || !idpRes.idpOrg) { diff --git a/server/private/middlewares/verifyRemoteExitNode.ts b/server/private/middlewares/verifyRemoteExitNode.ts index 2f6d99d21..8abdc47e7 100644 --- a/server/private/middlewares/verifyRemoteExitNode.ts +++ b/server/private/middlewares/verifyRemoteExitNode.ts @@ -26,7 +26,8 @@ export const verifySessionRemoteExitNodeMiddleware = async ( // get the token from the auth header const token = req.headers["authorization"]?.split(" ")[1] || ""; - const { session, remoteExitNode } = await validateRemoteExitNodeSessionToken(token); + const { session, remoteExitNode } = + await validateRemoteExitNodeSessionToken(token); if (!session || !remoteExitNode) { if (config.getRawConfig().app.log_failed_attempts) { diff --git a/server/private/routers/auditLogs/exportAccessAuditLog.ts b/server/private/routers/auditLogs/exportAccessAuditLog.ts index 89aef6cbc..fbeca9320 100644 --- a/server/private/routers/auditLogs/exportAccessAuditLog.ts +++ b/server/private/routers/auditLogs/exportAccessAuditLog.ts @@ -19,7 +19,11 @@ import createHttpError from "http-errors"; import HttpCode from "@server/types/HttpCode"; import { fromError } from "zod-validation-error"; import logger from "@server/logger"; -import { queryAccessAuditLogsParams, queryAccessAuditLogsQuery, queryAccess } from "./queryAccessAuditLog"; +import { + queryAccessAuditLogsParams, + queryAccessAuditLogsQuery, + queryAccess +} from "./queryAccessAuditLog"; import { generateCSV } from "@server/routers/auditLogs/generateCSV"; registry.registerPath({ @@ -67,10 +71,13 @@ export async function exportAccessAuditLogs( const log = await baseQuery.limit(data.limit).offset(data.offset); const csvData = generateCSV(log); - - res.setHeader('Content-Type', 'text/csv'); - res.setHeader('Content-Disposition', `attachment; filename="access-audit-logs-${data.orgId}-${Date.now()}.csv"`); - + + res.setHeader("Content-Type", "text/csv"); + res.setHeader( + "Content-Disposition", + `attachment; filename="access-audit-logs-${data.orgId}-${Date.now()}.csv"` + ); + return res.send(csvData); } catch (error) { logger.error(error); @@ -78,4 +85,4 @@ export async function exportAccessAuditLogs( createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") ); } -} \ No newline at end of file +} diff --git a/server/private/routers/auditLogs/exportActionAuditLog.ts b/server/private/routers/auditLogs/exportActionAuditLog.ts index 12c9ff8b5..1fc4d743a 100644 --- a/server/private/routers/auditLogs/exportActionAuditLog.ts +++ b/server/private/routers/auditLogs/exportActionAuditLog.ts @@ -19,7 +19,11 @@ import createHttpError from "http-errors"; import HttpCode from "@server/types/HttpCode"; import { fromError } from "zod-validation-error"; import logger from "@server/logger"; -import { queryActionAuditLogsParams, queryActionAuditLogsQuery, queryAction } from "./queryActionAuditLog"; +import { + queryActionAuditLogsParams, + queryActionAuditLogsQuery, + queryAction +} from "./queryActionAuditLog"; import { generateCSV } from "@server/routers/auditLogs/generateCSV"; registry.registerPath({ @@ -60,17 +64,20 @@ export async function exportActionAuditLogs( ); } - const data = { ...parsedQuery.data, ...parsedParams.data }; + const data = { ...parsedQuery.data, ...parsedParams.data }; const baseQuery = queryAction(data); const log = await baseQuery.limit(data.limit).offset(data.offset); const csvData = generateCSV(log); - - res.setHeader('Content-Type', 'text/csv'); - res.setHeader('Content-Disposition', `attachment; filename="action-audit-logs-${data.orgId}-${Date.now()}.csv"`); - + + res.setHeader("Content-Type", "text/csv"); + res.setHeader( + "Content-Disposition", + `attachment; filename="action-audit-logs-${data.orgId}-${Date.now()}.csv"` + ); + return res.send(csvData); } catch (error) { logger.error(error); @@ -78,4 +85,4 @@ export async function exportActionAuditLogs( createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") ); } -} \ No newline at end of file +} diff --git a/server/private/routers/auditLogs/index.ts b/server/private/routers/auditLogs/index.ts index ac623c4c0..e1849a617 100644 --- a/server/private/routers/auditLogs/index.ts +++ b/server/private/routers/auditLogs/index.ts @@ -14,4 +14,4 @@ export * from "./queryActionAuditLog"; export * from "./exportActionAuditLog"; export * from "./queryAccessAuditLog"; -export * from "./exportAccessAuditLog"; \ No newline at end of file +export * from "./exportAccessAuditLog"; diff --git a/server/private/routers/auditLogs/queryAccessAuditLog.ts b/server/private/routers/auditLogs/queryAccessAuditLog.ts index 769dcf55d..5d3162aad 100644 --- a/server/private/routers/auditLogs/queryAccessAuditLog.ts +++ b/server/private/routers/auditLogs/queryAccessAuditLog.ts @@ -44,7 +44,8 @@ export const queryAccessAuditLogsQuery = z.object({ .openapi({ type: "string", format: "date-time", - description: "End time as ISO date string (defaults to current time)" + description: + "End time as ISO date string (defaults to current time)" }), action: z .union([z.boolean(), z.string()]) @@ -181,9 +182,15 @@ async function queryUniqueFilterAttributes( .where(baseConditions); return { - actors: uniqueActors.map(row => row.actor).filter((actor): actor is string => actor !== null), - resources: uniqueResources.filter((row): row is { id: number; name: string | null } => row.id !== null), - locations: uniqueLocations.map(row => row.locations).filter((location): location is string => location !== null) + actors: uniqueActors + .map((row) => row.actor) + .filter((actor): actor is string => actor !== null), + resources: uniqueResources.filter( + (row): row is { id: number; name: string | null } => row.id !== null + ), + locations: uniqueLocations + .map((row) => row.locations) + .filter((location): location is string => location !== null) }; } diff --git a/server/private/routers/auditLogs/queryActionAuditLog.ts b/server/private/routers/auditLogs/queryActionAuditLog.ts index d4a43879f..eca583b43 100644 --- a/server/private/routers/auditLogs/queryActionAuditLog.ts +++ b/server/private/routers/auditLogs/queryActionAuditLog.ts @@ -44,7 +44,8 @@ export const queryActionAuditLogsQuery = z.object({ .openapi({ type: "string", format: "date-time", - description: "End time as ISO date string (defaults to current time)" + description: + "End time as ISO date string (defaults to current time)" }), action: z.string().optional(), actorType: z.string().optional(), @@ -68,8 +69,9 @@ export const queryActionAuditLogsParams = z.object({ orgId: z.string() }); -export const queryActionAuditLogsCombined = - queryActionAuditLogsQuery.merge(queryActionAuditLogsParams); +export const queryActionAuditLogsCombined = queryActionAuditLogsQuery.merge( + queryActionAuditLogsParams +); type Q = z.infer; function getWhere(data: Q) { @@ -78,7 +80,9 @@ function getWhere(data: Q) { lt(actionAuditLog.timestamp, data.timeEnd), eq(actionAuditLog.orgId, data.orgId), data.actor ? eq(actionAuditLog.actor, data.actor) : undefined, - data.actorType ? eq(actionAuditLog.actorType, data.actorType) : undefined, + data.actorType + ? eq(actionAuditLog.actorType, data.actorType) + : undefined, data.actorId ? eq(actionAuditLog.actorId, data.actorId) : undefined, data.action ? eq(actionAuditLog.action, data.action) : undefined ); @@ -135,8 +139,12 @@ async function queryUniqueFilterAttributes( .where(baseConditions); return { - actors: uniqueActors.map(row => row.actor).filter((actor): actor is string => actor !== null), - actions: uniqueActions.map(row => row.action).filter((action): action is string => action !== null), + actors: uniqueActors + .map((row) => row.actor) + .filter((actor): actor is string => actor !== null), + actions: uniqueActions + .map((row) => row.action) + .filter((action): action is string => action !== null) }; } diff --git a/server/private/routers/auth/index.ts b/server/private/routers/auth/index.ts index 39a600312..535d58873 100644 --- a/server/private/routers/auth/index.ts +++ b/server/private/routers/auth/index.ts @@ -13,4 +13,4 @@ export * from "./transferSession"; export * from "./getSessionTransferToken"; -export * from "./quickStart"; \ No newline at end of file +export * from "./quickStart"; diff --git a/server/private/routers/auth/quickStart.ts b/server/private/routers/auth/quickStart.ts index 02023a0be..612a3951a 100644 --- a/server/private/routers/auth/quickStart.ts +++ b/server/private/routers/auth/quickStart.ts @@ -395,7 +395,8 @@ export async function quickStart( .values({ targetId: newTarget[0].targetId, hcEnabled: false - }).returning(); + }) + .returning(); // add the new target to the targetIps array targetIps.push(`${ip}/32`); @@ -406,7 +407,12 @@ export async function quickStart( .where(eq(newts.siteId, siteId!)) .limit(1); - await addTargets(newt.newtId, newTarget, newHealthcheck, resource.protocol); + await addTargets( + newt.newtId, + newTarget, + newHealthcheck, + resource.protocol + ); // Set resource pincode if provided if (pincode) { diff --git a/server/private/routers/billing/createCheckoutSession.ts b/server/private/routers/billing/createCheckoutSession.ts index e0e08a20b..a2d8080f7 100644 --- a/server/private/routers/billing/createCheckoutSession.ts +++ b/server/private/routers/billing/createCheckoutSession.ts @@ -26,8 +26,8 @@ import { getLineItems, getStandardFeaturePriceSet } from "@server/lib/billing"; import { getTierPriceSet, TierId } from "@server/lib/billing/tiers"; const createCheckoutSessionSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); export async function createCheckoutSession( req: Request, @@ -72,7 +72,7 @@ export async function createCheckoutSession( billing_address_collection: "required", line_items: [ { - price: standardTierPrice, // Use the standard tier + price: standardTierPrice, // Use the standard tier quantity: 1 }, ...getLineItems(getStandardFeaturePriceSet()) diff --git a/server/private/routers/billing/createPortalSession.ts b/server/private/routers/billing/createPortalSession.ts index a3a2f04f6..9ebe84e09 100644 --- a/server/private/routers/billing/createPortalSession.ts +++ b/server/private/routers/billing/createPortalSession.ts @@ -24,8 +24,8 @@ import { fromError } from "zod-validation-error"; import stripe from "#private/lib/stripe"; const createPortalSessionSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); export async function createPortalSession( req: Request, diff --git a/server/private/routers/billing/getOrgSubscription.ts b/server/private/routers/billing/getOrgSubscription.ts index adc4ee049..e1f8316ef 100644 --- a/server/private/routers/billing/getOrgSubscription.ts +++ b/server/private/routers/billing/getOrgSubscription.ts @@ -34,8 +34,8 @@ import { } from "@server/db"; const getOrgSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); registry.registerPath({ method: "get", diff --git a/server/private/routers/billing/getOrgUsage.ts b/server/private/routers/billing/getOrgUsage.ts index 9e605cca2..1a3437306 100644 --- a/server/private/routers/billing/getOrgUsage.ts +++ b/server/private/routers/billing/getOrgUsage.ts @@ -28,8 +28,8 @@ import { FeatureId } from "@server/lib/billing"; import { GetOrgUsageResponse } from "@server/routers/billing/types"; const getOrgSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); registry.registerPath({ method: "get", @@ -78,11 +78,23 @@ export async function getOrgUsage( // Get usage for org const usageData = []; - const siteUptime = await usageService.getUsage(orgId, FeatureId.SITE_UPTIME); + const siteUptime = await usageService.getUsage( + orgId, + FeatureId.SITE_UPTIME + ); const users = await usageService.getUsageDaily(orgId, FeatureId.USERS); - const domains = await usageService.getUsageDaily(orgId, FeatureId.DOMAINS); - const remoteExitNodes = await usageService.getUsageDaily(orgId, FeatureId.REMOTE_EXIT_NODES); - const egressData = await usageService.getUsage(orgId, FeatureId.EGRESS_DATA_MB); + const domains = await usageService.getUsageDaily( + orgId, + FeatureId.DOMAINS + ); + const remoteExitNodes = await usageService.getUsageDaily( + orgId, + FeatureId.REMOTE_EXIT_NODES + ); + const egressData = await usageService.getUsage( + orgId, + FeatureId.EGRESS_DATA_MB + ); if (siteUptime) { usageData.push(siteUptime); @@ -100,7 +112,8 @@ export async function getOrgUsage( usageData.push(remoteExitNodes); } - const orgLimits = await db.select() + const orgLimits = await db + .select() .from(limits) .where(eq(limits.orgId, orgId)); diff --git a/server/private/routers/billing/hooks/handleCustomerDeleted.ts b/server/private/routers/billing/hooks/handleCustomerDeleted.ts index aa2e69647..e41403539 100644 --- a/server/private/routers/billing/hooks/handleCustomerDeleted.ts +++ b/server/private/routers/billing/hooks/handleCustomerDeleted.ts @@ -31,9 +31,7 @@ export async function handleCustomerDeleted( return; } - await db - .delete(customers) - .where(eq(customers.customerId, customer.id)); + await db.delete(customers).where(eq(customers.customerId, customer.id)); } catch (error) { logger.error( `Error handling customer created event for ID ${customer.id}:`, diff --git a/server/private/routers/billing/hooks/handleSubscriptionDeleted.ts b/server/private/routers/billing/hooks/handleSubscriptionDeleted.ts index 114a4b30a..7a7d91492 100644 --- a/server/private/routers/billing/hooks/handleSubscriptionDeleted.ts +++ b/server/private/routers/billing/hooks/handleSubscriptionDeleted.ts @@ -12,7 +12,14 @@ */ import Stripe from "stripe"; -import { subscriptions, db, subscriptionItems, customers, userOrgs, users } from "@server/db"; +import { + subscriptions, + db, + subscriptionItems, + customers, + userOrgs, + users +} from "@server/db"; import { eq, and } from "drizzle-orm"; import logger from "@server/logger"; import { handleSubscriptionLifesycle } from "../subscriptionLifecycle"; @@ -43,7 +50,6 @@ export async function handleSubscriptionDeleted( .delete(subscriptionItems) .where(eq(subscriptionItems.subscriptionId, subscription.id)); - // Lookup customer to get orgId const [customer] = await db .select() @@ -58,10 +64,7 @@ export async function handleSubscriptionDeleted( return; } - await handleSubscriptionLifesycle( - customer.orgId, - subscription.status - ); + await handleSubscriptionLifesycle(customer.orgId, subscription.status); const [orgUserRes] = await db .select() diff --git a/server/private/routers/billing/index.ts b/server/private/routers/billing/index.ts index 913ae865e..59fce8d62 100644 --- a/server/private/routers/billing/index.ts +++ b/server/private/routers/billing/index.ts @@ -15,4 +15,4 @@ export * from "./createCheckoutSession"; export * from "./createPortalSession"; export * from "./getOrgSubscription"; export * from "./getOrgUsage"; -export * from "./internalGetOrgTier"; \ No newline at end of file +export * from "./internalGetOrgTier"; diff --git a/server/private/routers/billing/internalGetOrgTier.ts b/server/private/routers/billing/internalGetOrgTier.ts index ec114ccaa..92bbc2baa 100644 --- a/server/private/routers/billing/internalGetOrgTier.ts +++ b/server/private/routers/billing/internalGetOrgTier.ts @@ -22,8 +22,8 @@ import { getOrgTierData } from "#private/lib/billing"; import { GetOrgTierResponse } from "@server/routers/billing/types"; const getOrgSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); export async function getOrgTier( req: Request, diff --git a/server/private/routers/billing/subscriptionLifecycle.ts b/server/private/routers/billing/subscriptionLifecycle.ts index 06b2a2a8e..0fc75835e 100644 --- a/server/private/routers/billing/subscriptionLifecycle.ts +++ b/server/private/routers/billing/subscriptionLifecycle.ts @@ -11,11 +11,18 @@ * This file is not licensed under the AGPLv3. */ -import { freeLimitSet, limitsService, subscribedLimitSet } from "@server/lib/billing"; +import { + freeLimitSet, + limitsService, + subscribedLimitSet +} from "@server/lib/billing"; import { usageService } from "@server/lib/billing/usageService"; import logger from "@server/logger"; -export async function handleSubscriptionLifesycle(orgId: string, status: string) { +export async function handleSubscriptionLifesycle( + orgId: string, + status: string +) { switch (status) { case "active": await limitsService.applyLimitSetToOrg(orgId, subscribedLimitSet); @@ -42,4 +49,4 @@ export async function handleSubscriptionLifesycle(orgId: string, status: string) default: break; } -} \ No newline at end of file +} diff --git a/server/private/routers/billing/webhooks.ts b/server/private/routers/billing/webhooks.ts index 24ad10744..9c64350c9 100644 --- a/server/private/routers/billing/webhooks.ts +++ b/server/private/routers/billing/webhooks.ts @@ -32,12 +32,13 @@ export async function billingWebhookHandler( next: NextFunction ): Promise { let event: Stripe.Event = req.body; - const endpointSecret = privateConfig.getRawPrivateConfig().stripe?.webhook_secret; + const endpointSecret = + privateConfig.getRawPrivateConfig().stripe?.webhook_secret; if (!endpointSecret) { - logger.warn("Stripe webhook secret is not configured. Webhook events will not be priocessed."); - return next( - createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "") + logger.warn( + "Stripe webhook secret is not configured. Webhook events will not be priocessed." ); + return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "")); } // Only verify the event if you have an endpoint secret defined. @@ -49,7 +50,10 @@ export async function billingWebhookHandler( if (!signature) { logger.info("No stripe signature found in headers."); return next( - createHttpError(HttpCode.BAD_REQUEST, "No stripe signature found in headers") + createHttpError( + HttpCode.BAD_REQUEST, + "No stripe signature found in headers" + ) ); } @@ -62,7 +66,10 @@ export async function billingWebhookHandler( } catch (err) { logger.error(`Webhook signature verification failed.`, err); return next( - createHttpError(HttpCode.UNAUTHORIZED, "Webhook signature verification failed") + createHttpError( + HttpCode.UNAUTHORIZED, + "Webhook signature verification failed" + ) ); } } diff --git a/server/private/routers/certificates/getCertificate.ts b/server/private/routers/certificates/getCertificate.ts index 4ff8184ea..d06a1badc 100644 --- a/server/private/routers/certificates/getCertificate.ts +++ b/server/private/routers/certificates/getCertificate.ts @@ -24,10 +24,10 @@ import { registry } from "@server/openApi"; import { GetCertificateResponse } from "@server/routers/certificates/types"; const getCertificateSchema = z.strictObject({ - domainId: z.string(), - domain: z.string().min(1).max(255), - orgId: z.string() - }); + domainId: z.string(), + domain: z.string().min(1).max(255), + orgId: z.string() +}); async function query(domainId: string, domain: string) { const [domainRecord] = await db @@ -42,8 +42,8 @@ async function query(domainId: string, domain: string) { let existing: any[] = []; if (domainRecord.type == "ns") { - const domainLevelDown = domain.split('.').slice(1).join('.'); - + const domainLevelDown = domain.split(".").slice(1).join("."); + existing = await db .select({ certId: certificates.certId, @@ -64,7 +64,7 @@ async function query(domainId: string, domain: string) { eq(certificates.wildcard, true), // only NS domains can have wildcard certs or( eq(certificates.domain, domain), - eq(certificates.domain, domainLevelDown), + eq(certificates.domain, domainLevelDown) ) ) ); @@ -102,8 +102,7 @@ registry.registerPath({ tags: ["Certificate"], request: { params: z.object({ - domainId: z - .string(), + domainId: z.string(), domain: z.string().min(1).max(255), orgId: z.string() }) @@ -133,7 +132,9 @@ export async function getCertificate( if (!cert) { logger.warn(`Certificate not found for domain: ${domainId}`); - return next(createHttpError(HttpCode.NOT_FOUND, "Certificate not found")); + return next( + createHttpError(HttpCode.NOT_FOUND, "Certificate not found") + ); } return response(res, { diff --git a/server/private/routers/certificates/index.ts b/server/private/routers/certificates/index.ts index e1b81ae17..b1543e5d5 100644 --- a/server/private/routers/certificates/index.ts +++ b/server/private/routers/certificates/index.ts @@ -12,4 +12,4 @@ */ export * from "./getCertificate"; -export * from "./restartCertificate"; \ No newline at end of file +export * from "./restartCertificate"; diff --git a/server/private/routers/certificates/restartCertificate.ts b/server/private/routers/certificates/restartCertificate.ts index a6ee54603..0e4b19108 100644 --- a/server/private/routers/certificates/restartCertificate.ts +++ b/server/private/routers/certificates/restartCertificate.ts @@ -25,9 +25,9 @@ import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; const restartCertificateParamsSchema = z.strictObject({ - certId: z.string().transform(stoi).pipe(z.int().positive()), - orgId: z.string() - }); + certId: z.string().transform(stoi).pipe(z.int().positive()), + orgId: z.string() +}); registry.registerPath({ method: "post", @@ -36,10 +36,7 @@ registry.registerPath({ tags: ["Certificate"], request: { params: z.object({ - certId: z - .string() - .transform(stoi) - .pipe(z.int().positive()), + certId: z.string().transform(stoi).pipe(z.int().positive()), orgId: z.string() }) }, @@ -94,7 +91,7 @@ export async function restartCertificate( .set({ status: "pending", errorMessage: null, - lastRenewalAttempt: Math.floor(Date.now() / 1000) + lastRenewalAttempt: Math.floor(Date.now() / 1000) }) .where(eq(certificates.certId, certId)); diff --git a/server/private/routers/domain/checkDomainNamespaceAvailability.ts b/server/private/routers/domain/checkDomainNamespaceAvailability.ts index 6c9cb23c6..db9a4b46a 100644 --- a/server/private/routers/domain/checkDomainNamespaceAvailability.ts +++ b/server/private/routers/domain/checkDomainNamespaceAvailability.ts @@ -26,8 +26,8 @@ import { CheckDomainAvailabilityResponse } from "@server/routers/domain/types"; const paramsSchema = z.strictObject({}); const querySchema = z.strictObject({ - subdomain: z.string() - }); + subdomain: z.string() +}); registry.registerPath({ method: "get", diff --git a/server/private/routers/domain/index.ts b/server/private/routers/domain/index.ts index da9cec3fe..3f4bbbf2a 100644 --- a/server/private/routers/domain/index.ts +++ b/server/private/routers/domain/index.ts @@ -12,4 +12,4 @@ */ export * from "./checkDomainNamespaceAvailability"; -export * from "./listDomainNamespaces"; \ No newline at end of file +export * from "./listDomainNamespaces"; diff --git a/server/private/routers/domain/listDomainNamespaces.ts b/server/private/routers/domain/listDomainNamespaces.ts index 29d5d2018..180613a85 100644 --- a/server/private/routers/domain/listDomainNamespaces.ts +++ b/server/private/routers/domain/listDomainNamespaces.ts @@ -26,19 +26,19 @@ import { OpenAPITags, registry } from "@server/openApi"; const paramsSchema = z.strictObject({}); const querySchema = z.strictObject({ - limit: z - .string() - .optional() - .default("1000") - .transform(Number) - .pipe(z.int().nonnegative()), - offset: z - .string() - .optional() - .default("0") - .transform(Number) - .pipe(z.int().nonnegative()) - }); + limit: z + .string() + .optional() + .default("1000") + .transform(Number) + .pipe(z.int().nonnegative()), + offset: z + .string() + .optional() + .default("0") + .transform(Number) + .pipe(z.int().nonnegative()) +}); async function query(limit: number, offset: number) { const res = await db diff --git a/server/private/routers/hybrid.ts b/server/private/routers/hybrid.ts index a61f37b24..3accc5007 100644 --- a/server/private/routers/hybrid.ts +++ b/server/private/routers/hybrid.ts @@ -79,86 +79,72 @@ import semver from "semver"; // Zod schemas for request validation const getResourceByDomainParamsSchema = z.strictObject({ - domain: z.string().min(1, "Domain is required") - }); + domain: z.string().min(1, "Domain is required") +}); const getUserSessionParamsSchema = z.strictObject({ - userSessionId: z.string().min(1, "User session ID is required") - }); + userSessionId: z.string().min(1, "User session ID is required") +}); const getUserOrgRoleParamsSchema = z.strictObject({ - userId: z.string().min(1, "User ID is required"), - orgId: z.string().min(1, "Organization ID is required") - }); + userId: z.string().min(1, "User ID is required"), + orgId: z.string().min(1, "Organization ID is required") +}); const getRoleResourceAccessParamsSchema = z.strictObject({ - roleId: z - .string() - .transform(Number) - .pipe( - z.int().positive("Role ID must be a positive integer") - ), - resourceId: z - .string() - .transform(Number) - .pipe( - z.int() - .positive("Resource ID must be a positive integer") - ) - }); + roleId: z + .string() + .transform(Number) + .pipe(z.int().positive("Role ID must be a positive integer")), + resourceId: z + .string() + .transform(Number) + .pipe(z.int().positive("Resource ID must be a positive integer")) +}); const getUserResourceAccessParamsSchema = z.strictObject({ - userId: z.string().min(1, "User ID is required"), - resourceId: z - .string() - .transform(Number) - .pipe( - z.int() - .positive("Resource ID must be a positive integer") - ) - }); + userId: z.string().min(1, "User ID is required"), + resourceId: z + .string() + .transform(Number) + .pipe(z.int().positive("Resource ID must be a positive integer")) +}); const getResourceRulesParamsSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe( - z.int() - .positive("Resource ID must be a positive integer") - ) - }); + resourceId: z + .string() + .transform(Number) + .pipe(z.int().positive("Resource ID must be a positive integer")) +}); const validateResourceSessionTokenParamsSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe( - z.int() - .positive("Resource ID must be a positive integer") - ) - }); + resourceId: z + .string() + .transform(Number) + .pipe(z.int().positive("Resource ID must be a positive integer")) +}); const validateResourceSessionTokenBodySchema = z.strictObject({ - token: z.string().min(1, "Token is required") - }); + token: z.string().min(1, "Token is required") +}); const validateResourceAccessTokenBodySchema = z.strictObject({ - accessTokenId: z.string().optional(), - resourceId: z.number().optional(), - accessToken: z.string() - }); + accessTokenId: z.string().optional(), + resourceId: z.number().optional(), + accessToken: z.string() +}); // Certificates by domains query validation const getCertificatesByDomainsQuerySchema = z.strictObject({ - // Accept domains as string or array (domains or domains[]) - domains: z - .union([z.array(z.string().min(1)), z.string().min(1)]) - .optional(), - // Handle array format from query parameters (domains[]) - "domains[]": z - .union([z.array(z.string().min(1)), z.string().min(1)]) - .optional() - }); + // Accept domains as string or array (domains or domains[]) + domains: z + .union([z.array(z.string().min(1)), z.string().min(1)]) + .optional(), + // Handle array format from query parameters (domains[]) + "domains[]": z + .union([z.array(z.string().min(1)), z.string().min(1)]) + .optional() +}); // Type exports for request schemas export type GetResourceByDomainParams = z.infer< @@ -566,8 +552,8 @@ hybridRouter.get( ); const getOrgLoginPageParamsSchema = z.strictObject({ - orgId: z.string().min(1) - }); + orgId: z.string().min(1) +}); hybridRouter.get( "/org/:orgId/login-page", @@ -1408,8 +1394,16 @@ hybridRouter.post( ); } - const { olmId, newtId, ip, port, timestamp, token, publicKey, reachableAt } = - parsedParams.data; + const { + olmId, + newtId, + ip, + port, + timestamp, + token, + publicKey, + reachableAt + } = parsedParams.data; const destinations = await updateAndGenerateEndpointDestinations( olmId, diff --git a/server/private/routers/integration.ts b/server/private/routers/integration.ts index 7ce378d1f..9eefff8f8 100644 --- a/server/private/routers/integration.ts +++ b/server/private/routers/integration.ts @@ -18,7 +18,7 @@ import * as logs from "#private/routers/auditLogs"; import { verifyApiKeyHasAction, verifyApiKeyIsRoot, - verifyApiKeyOrgAccess, + verifyApiKeyOrgAccess } from "@server/middlewares"; import { verifyValidSubscription, @@ -26,7 +26,10 @@ import { } from "#private/middlewares"; import { ActionsEnum } from "@server/auth/actions"; -import { unauthenticated as ua, authenticated as a } from "@server/routers/integration"; +import { + unauthenticated as ua, + authenticated as a +} from "@server/routers/integration"; import { logActionAudit } from "#private/middlewares"; export const unauthenticated = ua; @@ -37,7 +40,7 @@ authenticated.post( verifyApiKeyIsRoot, // We are the only ones who can use root key so its fine verifyApiKeyHasAction(ActionsEnum.sendUsageNotification), logActionAudit(ActionsEnum.sendUsageNotification), - org.sendUsageNotification, + org.sendUsageNotification ); authenticated.delete( @@ -45,7 +48,7 @@ authenticated.delete( verifyApiKeyIsRoot, verifyApiKeyHasAction(ActionsEnum.deleteIdp), logActionAudit(ActionsEnum.deleteIdp), - orgIdp.deleteOrgIdp, + orgIdp.deleteOrgIdp ); authenticated.get( diff --git a/server/private/routers/license/activateLicense.ts b/server/private/routers/license/activateLicense.ts index 55b7827ef..f6c8d2663 100644 --- a/server/private/routers/license/activateLicense.ts +++ b/server/private/routers/license/activateLicense.ts @@ -21,8 +21,8 @@ import { z } from "zod"; import { fromError } from "zod-validation-error"; const bodySchema = z.strictObject({ - licenseKey: z.string().min(1).max(255) - }); + licenseKey: z.string().min(1).max(255) +}); export async function activateLicense( req: Request, diff --git a/server/private/routers/license/deleteLicenseKey.ts b/server/private/routers/license/deleteLicenseKey.ts index 6f5469fce..80212e6a4 100644 --- a/server/private/routers/license/deleteLicenseKey.ts +++ b/server/private/routers/license/deleteLicenseKey.ts @@ -24,8 +24,8 @@ import { licenseKey } from "@server/db"; import license from "#private/license/license"; const paramsSchema = z.strictObject({ - licenseKey: z.string().min(1).max(255) - }); + licenseKey: z.string().min(1).max(255) +}); export async function deleteLicenseKey( req: Request, diff --git a/server/private/routers/loginPage/createLoginPage.ts b/server/private/routers/loginPage/createLoginPage.ts index 75744026b..b5e8ccff9 100644 --- a/server/private/routers/loginPage/createLoginPage.ts +++ b/server/private/routers/loginPage/createLoginPage.ts @@ -36,13 +36,13 @@ import { build } from "@server/build"; import { CreateLoginPageResponse } from "@server/routers/loginPage/types"; const paramsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); const bodySchema = z.strictObject({ - subdomain: z.string().nullable().optional(), - domainId: z.string() - }); + subdomain: z.string().nullable().optional(), + domainId: z.string() +}); export type CreateLoginPageBody = z.infer; @@ -149,12 +149,20 @@ export async function createLoginPage( let returned: LoginPage | undefined; await db.transaction(async (trx) => { - const orgSites = await trx .select() .from(sites) - .innerJoin(exitNodes, eq(exitNodes.exitNodeId, sites.exitNodeId)) - .where(and(eq(sites.orgId, orgId), eq(exitNodes.type, "gerbil"), eq(exitNodes.online, true))) + .innerJoin( + exitNodes, + eq(exitNodes.exitNodeId, sites.exitNodeId) + ) + .where( + and( + eq(sites.orgId, orgId), + eq(exitNodes.type, "gerbil"), + eq(exitNodes.online, true) + ) + ) .limit(10); let exitNodesList = orgSites.map((s) => s.exitNodes); @@ -163,7 +171,12 @@ export async function createLoginPage( exitNodesList = await trx .select() .from(exitNodes) - .where(and(eq(exitNodes.type, "gerbil"), eq(exitNodes.online, true))) + .where( + and( + eq(exitNodes.type, "gerbil"), + eq(exitNodes.online, true) + ) + ) .limit(10); } diff --git a/server/private/routers/loginPage/deleteLoginPage.ts b/server/private/routers/loginPage/deleteLoginPage.ts index 5271ebd8f..0d17a7316 100644 --- a/server/private/routers/loginPage/deleteLoginPage.ts +++ b/server/private/routers/loginPage/deleteLoginPage.ts @@ -78,15 +78,11 @@ export async function deleteLoginPage( // if (!leftoverLinks.length) { await db .delete(loginPage) - .where( - eq(loginPage.loginPageId, parsedParams.data.loginPageId) - ); + .where(eq(loginPage.loginPageId, parsedParams.data.loginPageId)); await db .delete(loginPageOrg) - .where( - eq(loginPageOrg.loginPageId, parsedParams.data.loginPageId) - ); + .where(eq(loginPageOrg.loginPageId, parsedParams.data.loginPageId)); // } return response(res, { diff --git a/server/private/routers/loginPage/getLoginPage.ts b/server/private/routers/loginPage/getLoginPage.ts index b3bde203c..73f6a3577 100644 --- a/server/private/routers/loginPage/getLoginPage.ts +++ b/server/private/routers/loginPage/getLoginPage.ts @@ -23,8 +23,8 @@ import { fromError } from "zod-validation-error"; import { GetLoginPageResponse } from "@server/routers/loginPage/types"; const paramsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); async function query(orgId: string) { const [res] = await db diff --git a/server/private/routers/loginPage/updateLoginPage.ts b/server/private/routers/loginPage/updateLoginPage.ts index 0d02b124f..bda614d37 100644 --- a/server/private/routers/loginPage/updateLoginPage.ts +++ b/server/private/routers/loginPage/updateLoginPage.ts @@ -35,7 +35,8 @@ const paramsSchema = z }) .strict(); -const bodySchema = z.strictObject({ +const bodySchema = z + .strictObject({ subdomain: subdomainSchema.nullable().optional(), domainId: z.string().optional() }) @@ -86,7 +87,7 @@ export async function updateLoginPage( const { loginPageId, orgId } = parsedParams.data; - if (build === "saas"){ + if (build === "saas") { const { tier } = await getOrgTierData(orgId); const subscribed = tier === TierId.STANDARD; if (!subscribed) { @@ -182,7 +183,10 @@ export async function updateLoginPage( } // update the full domain if it has changed - if (fullDomain && fullDomain !== existingLoginPage?.fullDomain) { + if ( + fullDomain && + fullDomain !== existingLoginPage?.fullDomain + ) { await db .update(loginPage) .set({ fullDomain }) diff --git a/server/private/routers/misc/sendSupportEmail.ts b/server/private/routers/misc/sendSupportEmail.ts index f1f7a9193..404a25014 100644 --- a/server/private/routers/misc/sendSupportEmail.ts +++ b/server/private/routers/misc/sendSupportEmail.ts @@ -23,9 +23,9 @@ import SupportEmail from "@server/emails/templates/SupportEmail"; import config from "@server/lib/config"; const bodySchema = z.strictObject({ - body: z.string().min(1), - subject: z.string().min(1).max(255) - }); + body: z.string().min(1), + subject: z.string().min(1).max(255) +}); export async function sendSupportEmail( req: Request, diff --git a/server/private/routers/org/index.ts b/server/private/routers/org/index.ts index 189c5323a..8d11c42d9 100644 --- a/server/private/routers/org/index.ts +++ b/server/private/routers/org/index.ts @@ -11,4 +11,4 @@ * This file is not licensed under the AGPLv3. */ -export * from "./sendUsageNotifications"; \ No newline at end of file +export * from "./sendUsageNotifications"; diff --git a/server/private/routers/org/sendUsageNotifications.ts b/server/private/routers/org/sendUsageNotifications.ts index 3ef27f917..4aa421520 100644 --- a/server/private/routers/org/sendUsageNotifications.ts +++ b/server/private/routers/org/sendUsageNotifications.ts @@ -35,10 +35,12 @@ const sendUsageNotificationBodySchema = z.object({ notificationType: z.enum(["approaching_70", "approaching_90", "reached"]), limitName: z.string(), currentUsage: z.number(), - usageLimit: z.number(), + usageLimit: z.number() }); -type SendUsageNotificationRequest = z.infer; +type SendUsageNotificationRequest = z.infer< + typeof sendUsageNotificationBodySchema +>; export type SendUsageNotificationResponse = { success: boolean; @@ -97,17 +99,13 @@ async function getOrgAdmins(orgId: string) { .where( and( eq(userOrgs.orgId, orgId), - or( - eq(userOrgs.isOwner, true), - eq(roles.isAdmin, true) - ) + or(eq(userOrgs.isOwner, true), eq(roles.isAdmin, true)) ) ); // Filter to only include users with verified emails - const orgAdmins = admins.filter(admin => - admin.email && - admin.email.length > 0 + const orgAdmins = admins.filter( + (admin) => admin.email && admin.email.length > 0 ); return orgAdmins; @@ -119,7 +117,9 @@ export async function sendUsageNotification( next: NextFunction ): Promise { try { - const parsedParams = sendUsageNotificationParamsSchema.safeParse(req.params); + const parsedParams = sendUsageNotificationParamsSchema.safeParse( + req.params + ); if (!parsedParams.success) { return next( createHttpError( @@ -140,12 +140,8 @@ export async function sendUsageNotification( } const { orgId } = parsedParams.data; - const { - notificationType, - limitName, - currentUsage, - usageLimit, - } = parsedBody.data; + const { notificationType, limitName, currentUsage, usageLimit } = + parsedBody.data; // Verify organization exists const org = await db @@ -192,7 +188,10 @@ export async function sendUsageNotification( let template; let subject; - if (notificationType === "approaching_70" || notificationType === "approaching_90") { + if ( + notificationType === "approaching_70" || + notificationType === "approaching_90" + ) { template = NotifyUsageLimitApproaching({ email: admin.email, limitName, @@ -220,10 +219,15 @@ export async function sendUsageNotification( emailsSent++; adminEmails.push(admin.email); - - logger.info(`Usage notification sent to admin ${admin.email} for org ${orgId}`); + + logger.info( + `Usage notification sent to admin ${admin.email} for org ${orgId}` + ); } catch (emailError) { - logger.error(`Failed to send usage notification to ${admin.email}:`, emailError); + logger.error( + `Failed to send usage notification to ${admin.email}:`, + emailError + ); // Continue with other admins even if one fails } } @@ -239,11 +243,13 @@ export async function sendUsageNotification( message: `Usage notifications sent to ${emailsSent} administrators`, status: HttpCode.OK }); - } catch (error) { logger.error("Error sending usage notifications:", error); return next( - createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "Failed to send usage notifications") + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Failed to send usage notifications" + ) ); } } diff --git a/server/private/routers/orgIdp/createOrgOidcIdp.ts b/server/private/routers/orgIdp/createOrgOidcIdp.ts index c3ce774e1..709f6167b 100644 --- a/server/private/routers/orgIdp/createOrgOidcIdp.ts +++ b/server/private/routers/orgIdp/createOrgOidcIdp.ts @@ -32,19 +32,19 @@ import { CreateOrgIdpResponse } from "@server/routers/orgIdp/types"; const paramsSchema = z.strictObject({ orgId: z.string().nonempty() }); const bodySchema = z.strictObject({ - name: z.string().nonempty(), - clientId: z.string().nonempty(), - clientSecret: z.string().nonempty(), - authUrl: z.url(), - tokenUrl: z.url(), - identifierPath: z.string().nonempty(), - emailPath: z.string().optional(), - namePath: z.string().optional(), - scopes: z.string().nonempty(), - autoProvision: z.boolean().optional(), - variant: z.enum(["oidc", "google", "azure"]).optional().default("oidc"), - roleMapping: z.string().optional() - }); + name: z.string().nonempty(), + clientId: z.string().nonempty(), + clientSecret: z.string().nonempty(), + authUrl: z.url(), + tokenUrl: z.url(), + identifierPath: z.string().nonempty(), + emailPath: z.string().optional(), + namePath: z.string().optional(), + scopes: z.string().nonempty(), + autoProvision: z.boolean().optional(), + variant: z.enum(["oidc", "google", "azure"]).optional().default("oidc"), + roleMapping: z.string().optional() +}); // registry.registerPath({ // method: "put", @@ -158,7 +158,10 @@ export async function createOrgOidcIdp( }); }); - const redirectUrl = await generateOidcRedirectUrl(idpId as number, orgId); + const redirectUrl = await generateOidcRedirectUrl( + idpId as number, + orgId + ); return response(res, { data: { diff --git a/server/private/routers/orgIdp/deleteOrgIdp.ts b/server/private/routers/orgIdp/deleteOrgIdp.ts index ca0112b2f..721b91cba 100644 --- a/server/private/routers/orgIdp/deleteOrgIdp.ts +++ b/server/private/routers/orgIdp/deleteOrgIdp.ts @@ -66,12 +66,7 @@ export async function deleteOrgIdp( .where(eq(idp.idpId, idpId)); if (!existingIdp) { - return next( - createHttpError( - HttpCode.NOT_FOUND, - "IdP not found" - ) - ); + return next(createHttpError(HttpCode.NOT_FOUND, "IdP not found")); } // Delete the IDP and its related records in a transaction @@ -82,14 +77,10 @@ export async function deleteOrgIdp( .where(eq(idpOidcConfig.idpId, idpId)); // Delete IDP-org mappings - await trx - .delete(idpOrg) - .where(eq(idpOrg.idpId, idpId)); + await trx.delete(idpOrg).where(eq(idpOrg.idpId, idpId)); // Delete the IDP itself - await trx - .delete(idp) - .where(eq(idp.idpId, idpId)); + await trx.delete(idp).where(eq(idp.idpId, idpId)); }); return response(res, { diff --git a/server/private/routers/orgIdp/getOrgIdp.ts b/server/private/routers/orgIdp/getOrgIdp.ts index 3ba85412c..01ddc0f7e 100644 --- a/server/private/routers/orgIdp/getOrgIdp.ts +++ b/server/private/routers/orgIdp/getOrgIdp.ts @@ -93,7 +93,10 @@ export async function getOrgIdp( idpRes.idpOidcConfig!.clientId = decrypt(clientId, key); } - const redirectUrl = await generateOidcRedirectUrl(idpRes.idp.idpId, orgId); + const redirectUrl = await generateOidcRedirectUrl( + idpRes.idp.idpId, + orgId + ); return response(res, { data: { diff --git a/server/private/routers/orgIdp/index.ts b/server/private/routers/orgIdp/index.ts index 562582c67..9cf937a41 100644 --- a/server/private/routers/orgIdp/index.ts +++ b/server/private/routers/orgIdp/index.ts @@ -15,4 +15,4 @@ export * from "./createOrgOidcIdp"; export * from "./getOrgIdp"; export * from "./listOrgIdps"; export * from "./updateOrgOidcIdp"; -export * from "./deleteOrgIdp"; \ No newline at end of file +export * from "./deleteOrgIdp"; diff --git a/server/private/routers/orgIdp/listOrgIdps.ts b/server/private/routers/orgIdp/listOrgIdps.ts index 646d808c4..36cbc6279 100644 --- a/server/private/routers/orgIdp/listOrgIdps.ts +++ b/server/private/routers/orgIdp/listOrgIdps.ts @@ -25,23 +25,23 @@ import { OpenAPITags, registry } from "@server/openApi"; import { ListOrgIdpsResponse } from "@server/routers/orgIdp/types"; const querySchema = z.strictObject({ - limit: z - .string() - .optional() - .default("1000") - .transform(Number) - .pipe(z.int().nonnegative()), - offset: z - .string() - .optional() - .default("0") - .transform(Number) - .pipe(z.int().nonnegative()) - }); + limit: z + .string() + .optional() + .default("1000") + .transform(Number) + .pipe(z.int().nonnegative()), + offset: z + .string() + .optional() + .default("0") + .transform(Number) + .pipe(z.int().nonnegative()) +}); const paramsSchema = z.strictObject({ - orgId: z.string().nonempty() - }); + orgId: z.string().nonempty() +}); async function query(orgId: string, limit: number, offset: number) { const res = await db diff --git a/server/private/routers/orgIdp/updateOrgOidcIdp.ts b/server/private/routers/orgIdp/updateOrgOidcIdp.ts index 3826f6b37..f29e4fc21 100644 --- a/server/private/routers/orgIdp/updateOrgOidcIdp.ts +++ b/server/private/routers/orgIdp/updateOrgOidcIdp.ts @@ -36,18 +36,18 @@ const paramsSchema = z .strict(); const bodySchema = z.strictObject({ - name: z.string().optional(), - clientId: z.string().optional(), - clientSecret: z.string().optional(), - authUrl: z.string().optional(), - tokenUrl: z.string().optional(), - identifierPath: z.string().optional(), - emailPath: z.string().optional(), - namePath: z.string().optional(), - scopes: z.string().optional(), - autoProvision: z.boolean().optional(), - roleMapping: z.string().optional() - }); + name: z.string().optional(), + clientId: z.string().optional(), + clientSecret: z.string().optional(), + authUrl: z.string().optional(), + tokenUrl: z.string().optional(), + identifierPath: z.string().optional(), + emailPath: z.string().optional(), + namePath: z.string().optional(), + scopes: z.string().optional(), + autoProvision: z.boolean().optional(), + roleMapping: z.string().optional() +}); export type UpdateOrgIdpResponse = { idpId: number; diff --git a/server/private/routers/re-key/index.ts b/server/private/routers/re-key/index.ts index 41a1c9679..9c1bccf8a 100644 --- a/server/private/routers/re-key/index.ts +++ b/server/private/routers/re-key/index.ts @@ -13,4 +13,4 @@ export * from "./reGenerateClientSecret"; export * from "./reGenerateSiteSecret"; -export * from "./reGenerateExitNodeSecret"; \ No newline at end of file +export * from "./reGenerateExitNodeSecret"; diff --git a/server/private/routers/re-key/reGenerateClientSecret.ts b/server/private/routers/re-key/reGenerateClientSecret.ts index 310f2602d..5478c690c 100644 --- a/server/private/routers/re-key/reGenerateClientSecret.ts +++ b/server/private/routers/re-key/reGenerateClientSecret.ts @@ -123,7 +123,10 @@ export async function reGenerateClientSecret( }; // Don't await this to prevent blocking the response sendToClient(existingOlms[0].olmId, payload).catch((error) => { - logger.error("Failed to send termination message to olm:", error); + logger.error( + "Failed to send termination message to olm:", + error + ); }); disconnectClient(existingOlms[0].olmId).catch((error) => { @@ -133,7 +136,7 @@ export async function reGenerateClientSecret( return response(res, { data: { - olmId: existingOlms[0].olmId, + olmId: existingOlms[0].olmId }, success: true, error: false, diff --git a/server/private/routers/re-key/reGenerateExitNodeSecret.ts b/server/private/routers/re-key/reGenerateExitNodeSecret.ts index b642f102b..021d2ce95 100644 --- a/server/private/routers/re-key/reGenerateExitNodeSecret.ts +++ b/server/private/routers/re-key/reGenerateExitNodeSecret.ts @@ -12,7 +12,14 @@ */ import { NextFunction, Request, Response } from "express"; -import { db, exitNodes, exitNodeOrgs, ExitNode, ExitNodeOrg, RemoteExitNode } from "@server/db"; +import { + db, + exitNodes, + exitNodeOrgs, + ExitNode, + ExitNodeOrg, + RemoteExitNode +} from "@server/db"; import HttpCode from "@server/types/HttpCode"; import { z } from "zod"; import { remoteExitNodes } from "@server/db"; @@ -91,14 +98,15 @@ export async function reGenerateExitNodeSecret( data: {} }; // Don't await this to prevent blocking the response - sendToClient(existingRemoteExitNode.remoteExitNodeId, payload).catch( - (error) => { - logger.error( - "Failed to send termination message to remote exit node:", - error - ); - } - ); + sendToClient( + existingRemoteExitNode.remoteExitNodeId, + payload + ).catch((error) => { + logger.error( + "Failed to send termination message to remote exit node:", + error + ); + }); disconnectClient(existingRemoteExitNode.remoteExitNodeId).catch( (error) => { diff --git a/server/private/routers/re-key/reGenerateSiteSecret.ts b/server/private/routers/re-key/reGenerateSiteSecret.ts index b427dcc2b..09cf75994 100644 --- a/server/private/routers/re-key/reGenerateSiteSecret.ts +++ b/server/private/routers/re-key/reGenerateSiteSecret.ts @@ -80,7 +80,7 @@ export async function reGenerateSiteSecret( const secretHash = await hashPassword(secret); // get the newt to verify it exists - const existingNewts = await db + const existingNewts = await db .select() .from(newts) .where(eq(newts.siteId, siteId)); @@ -120,15 +120,20 @@ export async function reGenerateSiteSecret( data: {} }; // Don't await this to prevent blocking the response - sendToClient(existingNewts[0].newtId, payload).catch((error) => { - logger.error( - "Failed to send termination message to newt:", - error - ); - }); + sendToClient(existingNewts[0].newtId, payload).catch( + (error) => { + logger.error( + "Failed to send termination message to newt:", + error + ); + } + ); disconnectClient(existingNewts[0].newtId).catch((error) => { - logger.error("Failed to disconnect newt after re-key:", error); + logger.error( + "Failed to disconnect newt after re-key:", + error + ); }); } diff --git a/server/private/routers/remoteExitNode/createRemoteExitNode.ts b/server/private/routers/remoteExitNode/createRemoteExitNode.ts index 5afa82ef6..f734813e9 100644 --- a/server/private/routers/remoteExitNode/createRemoteExitNode.ts +++ b/server/private/routers/remoteExitNode/createRemoteExitNode.ts @@ -36,9 +36,9 @@ export const paramsSchema = z.object({ }); const bodySchema = z.strictObject({ - remoteExitNodeId: z.string().length(15), - secret: z.string().length(48) - }); + remoteExitNodeId: z.string().length(15), + secret: z.string().length(48) +}); export type CreateRemoteExitNodeBody = z.infer; diff --git a/server/private/routers/remoteExitNode/deleteRemoteExitNode.ts b/server/private/routers/remoteExitNode/deleteRemoteExitNode.ts index e293f421b..a23363fc8 100644 --- a/server/private/routers/remoteExitNode/deleteRemoteExitNode.ts +++ b/server/private/routers/remoteExitNode/deleteRemoteExitNode.ts @@ -25,9 +25,9 @@ import { usageService } from "@server/lib/billing/usageService"; import { FeatureId } from "@server/lib/billing"; const paramsSchema = z.strictObject({ - orgId: z.string().min(1), - remoteExitNodeId: z.string().min(1) - }); + orgId: z.string().min(1), + remoteExitNodeId: z.string().min(1) +}); export async function deleteRemoteExitNode( req: Request, diff --git a/server/private/routers/remoteExitNode/getRemoteExitNode.ts b/server/private/routers/remoteExitNode/getRemoteExitNode.ts index c7b982971..01ea080c9 100644 --- a/server/private/routers/remoteExitNode/getRemoteExitNode.ts +++ b/server/private/routers/remoteExitNode/getRemoteExitNode.ts @@ -24,9 +24,9 @@ import { fromError } from "zod-validation-error"; import { GetRemoteExitNodeResponse } from "@server/routers/remoteExitNode/types"; const getRemoteExitNodeSchema = z.strictObject({ - orgId: z.string().min(1), - remoteExitNodeId: z.string().min(1) - }); + orgId: z.string().min(1), + remoteExitNodeId: z.string().min(1) +}); async function query(remoteExitNodeId: string) { const [remoteExitNode] = await db diff --git a/server/private/routers/remoteExitNode/getRemoteExitNodeToken.ts b/server/private/routers/remoteExitNode/getRemoteExitNodeToken.ts index 16ec4d5d6..24f0de159 100644 --- a/server/private/routers/remoteExitNode/getRemoteExitNodeToken.ts +++ b/server/private/routers/remoteExitNode/getRemoteExitNodeToken.ts @@ -55,7 +55,8 @@ export async function getRemoteExitNodeToken( try { if (token) { - const { session, remoteExitNode } = await validateRemoteExitNodeSessionToken(token); + const { session, remoteExitNode } = + await validateRemoteExitNodeSessionToken(token); if (session) { if (config.getRawConfig().app.log_failed_attempts) { logger.info( @@ -103,7 +104,10 @@ export async function getRemoteExitNodeToken( } const resToken = generateSessionToken(); - await createRemoteExitNodeSession(resToken, existingRemoteExitNode.remoteExitNodeId); + await createRemoteExitNodeSession( + resToken, + existingRemoteExitNode.remoteExitNodeId + ); // logger.debug(`Created RemoteExitNode token response: ${JSON.stringify(resToken)}`); diff --git a/server/private/routers/remoteExitNode/handleRemoteExitNodePingMessage.ts b/server/private/routers/remoteExitNode/handleRemoteExitNodePingMessage.ts index 784927145..dafc14121 100644 --- a/server/private/routers/remoteExitNode/handleRemoteExitNodePingMessage.ts +++ b/server/private/routers/remoteExitNode/handleRemoteExitNodePingMessage.ts @@ -33,7 +33,9 @@ export const startRemoteExitNodeOfflineChecker = (): void => { offlineCheckerInterval = setInterval(async () => { try { - const twoMinutesAgo = Math.floor((Date.now() - OFFLINE_THRESHOLD_MS) / 1000); + const twoMinutesAgo = Math.floor( + (Date.now() - OFFLINE_THRESHOLD_MS) / 1000 + ); // Find clients that haven't pinged in the last 2 minutes and mark them as offline const newlyOfflineNodes = await db @@ -48,11 +50,13 @@ export const startRemoteExitNodeOfflineChecker = (): void => { isNull(exitNodes.lastPing) ) ) - ).returning(); - + ) + .returning(); // Update the sites to offline if they have not pinged either - const exitNodeIds = newlyOfflineNodes.map(node => node.exitNodeId); + const exitNodeIds = newlyOfflineNodes.map( + (node) => node.exitNodeId + ); const sitesOnNode = await db .select() @@ -77,7 +81,6 @@ export const startRemoteExitNodeOfflineChecker = (): void => { .where(eq(sites.siteId, site.siteId)); } } - } catch (error) { logger.error("Error in offline checker interval", { error }); } @@ -100,7 +103,9 @@ export const stopRemoteExitNodeOfflineChecker = (): void => { /** * Handles ping messages from clients and responds with pong */ -export const handleRemoteExitNodePingMessage: MessageHandler = async (context) => { +export const handleRemoteExitNodePingMessage: MessageHandler = async ( + context +) => { const { message, client: c, sendToClient } = context; const remoteExitNode = c as RemoteExitNode; @@ -120,7 +125,7 @@ export const handleRemoteExitNodePingMessage: MessageHandler = async (context) = .update(exitNodes) .set({ lastPing: Math.floor(Date.now() / 1000), - online: true, + online: true }) .where(eq(exitNodes.exitNodeId, remoteExitNode.exitNodeId)); } catch (error) { @@ -131,7 +136,7 @@ export const handleRemoteExitNodePingMessage: MessageHandler = async (context) = message: { type: "pong", data: { - timestamp: new Date().toISOString(), + timestamp: new Date().toISOString() } }, broadcast: false, diff --git a/server/private/routers/remoteExitNode/handleRemoteExitNodeRegisterMessage.ts b/server/private/routers/remoteExitNode/handleRemoteExitNodeRegisterMessage.ts index a733db7d8..5ad37edcd 100644 --- a/server/private/routers/remoteExitNode/handleRemoteExitNodeRegisterMessage.ts +++ b/server/private/routers/remoteExitNode/handleRemoteExitNodeRegisterMessage.ts @@ -29,7 +29,8 @@ export const handleRemoteExitNodeRegisterMessage: MessageHandler = async ( return; } - const { remoteExitNodeVersion, remoteExitNodeSecondaryVersion } = message.data; + const { remoteExitNodeVersion, remoteExitNodeSecondaryVersion } = + message.data; if (!remoteExitNodeVersion) { logger.warn("Remote exit node version not found"); @@ -39,7 +40,10 @@ export const handleRemoteExitNodeRegisterMessage: MessageHandler = async ( // update the version await db .update(remoteExitNodes) - .set({ version: remoteExitNodeVersion, secondaryVersion: remoteExitNodeSecondaryVersion }) + .set({ + version: remoteExitNodeVersion, + secondaryVersion: remoteExitNodeSecondaryVersion + }) .where( eq( remoteExitNodes.remoteExitNodeId, diff --git a/server/private/routers/remoteExitNode/listRemoteExitNodes.ts b/server/private/routers/remoteExitNode/listRemoteExitNodes.ts index a13a05cd6..e65486005 100644 --- a/server/private/routers/remoteExitNode/listRemoteExitNodes.ts +++ b/server/private/routers/remoteExitNode/listRemoteExitNodes.ts @@ -24,8 +24,8 @@ import { fromError } from "zod-validation-error"; import { ListRemoteExitNodesResponse } from "@server/routers/remoteExitNode/types"; const listRemoteExitNodesParamsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); const listRemoteExitNodesSchema = z.object({ limit: z diff --git a/server/private/routers/remoteExitNode/pickRemoteExitNodeDefaults.ts b/server/private/routers/remoteExitNode/pickRemoteExitNodeDefaults.ts index bb7c89d57..5dcd545e5 100644 --- a/server/private/routers/remoteExitNode/pickRemoteExitNodeDefaults.ts +++ b/server/private/routers/remoteExitNode/pickRemoteExitNodeDefaults.ts @@ -22,8 +22,8 @@ import { z } from "zod"; import { PickRemoteExitNodeDefaultsResponse } from "@server/routers/remoteExitNode/types"; const paramsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); export async function pickRemoteExitNodeDefaults( req: Request, diff --git a/server/private/routers/remoteExitNode/quickStartRemoteExitNode.ts b/server/private/routers/remoteExitNode/quickStartRemoteExitNode.ts index 4d3681525..ebe365d1b 100644 --- a/server/private/routers/remoteExitNode/quickStartRemoteExitNode.ts +++ b/server/private/routers/remoteExitNode/quickStartRemoteExitNode.ts @@ -38,7 +38,9 @@ export async function quickStartRemoteExitNode( next: NextFunction ): Promise { try { - const parsedBody = quickStartRemoteExitNodeBodySchema.safeParse(req.body); + const parsedBody = quickStartRemoteExitNodeBodySchema.safeParse( + req.body + ); if (!parsedBody.success) { return next( createHttpError( diff --git a/server/private/routers/ws/index.ts b/server/private/routers/ws/index.ts index 4d803a3a7..3a8db5378 100644 --- a/server/private/routers/ws/index.ts +++ b/server/private/routers/ws/index.ts @@ -11,4 +11,4 @@ * This file is not licensed under the AGPLv3. */ -export * from "./ws"; \ No newline at end of file +export * from "./ws"; diff --git a/server/private/routers/ws/messageHandlers.ts b/server/private/routers/ws/messageHandlers.ts index 71c2b2531..5a6c85cff 100644 --- a/server/private/routers/ws/messageHandlers.ts +++ b/server/private/routers/ws/messageHandlers.ts @@ -23,4 +23,4 @@ export const messageHandlers: Record = { "remoteExitNode/ping": handleRemoteExitNodePingMessage }; -startRemoteExitNodeOfflineChecker(); // this is to handle the offline check for remote exit nodes \ No newline at end of file +startRemoteExitNodeOfflineChecker(); // this is to handle the offline check for remote exit nodes diff --git a/server/private/routers/ws/ws.ts b/server/private/routers/ws/ws.ts index f3f4c8fdf..784c3d515 100644 --- a/server/private/routers/ws/ws.ts +++ b/server/private/routers/ws/ws.ts @@ -37,7 +37,14 @@ import { validateRemoteExitNodeSessionToken } from "#private/auth/sessions/remot import { rateLimitService } from "#private/lib/rateLimit"; import { messageHandlers } from "@server/routers/ws/messageHandlers"; import { messageHandlers as privateMessageHandlers } from "#private/routers/ws/messageHandlers"; -import { AuthenticatedWebSocket, ClientType, WSMessage, TokenPayload, WebSocketRequest, RedisMessage } from "@server/routers/ws"; +import { + AuthenticatedWebSocket, + ClientType, + WSMessage, + TokenPayload, + WebSocketRequest, + RedisMessage +} from "@server/routers/ws"; import { validateSessionToken } from "@server/auth/sessions/app"; // Merge public and private message handlers @@ -216,7 +223,7 @@ const initializeRedisSubscription = async (): Promise => { // Each node is responsible for restoring its own connection state to Redis // This approach is more efficient than cross-node coordination because: // 1. Each node knows its own connections (source of truth) -// 2. No network overhead from broadcasting state between nodes +// 2. No network overhead from broadcasting state between nodes // 3. No race conditions from simultaneous updates // 4. Redis becomes eventually consistent as each node restores independently // 5. Simpler logic with better fault tolerance @@ -233,8 +240,10 @@ const recoverConnectionState = async (): Promise => { // Each node simply restores its own local connections to Redis // This is the source of truth - no need for cross-node coordination await restoreLocalConnectionsToRedis(); - - logger.info("Redis connection state recovery completed - restored local state"); + + logger.info( + "Redis connection state recovery completed - restored local state" + ); } catch (error) { logger.error("Error during Redis recovery:", error); } finally { @@ -251,8 +260,10 @@ const restoreLocalConnectionsToRedis = async (): Promise => { try { // Restore all current local connections to Redis for (const [clientId, clients] of connectedClients.entries()) { - const validClients = clients.filter(client => client.readyState === WebSocket.OPEN); - + const validClients = clients.filter( + (client) => client.readyState === WebSocket.OPEN + ); + if (validClients.length > 0) { // Add this node to the client's connection list await redisManager.sadd(getConnectionsKey(clientId), NODE_ID); @@ -303,7 +314,10 @@ const addClient = async ( Date.now().toString() ); } catch (error) { - logger.error("Failed to add client to Redis tracking (connection still functional locally):", error); + logger.error( + "Failed to add client to Redis tracking (connection still functional locally):", + error + ); } } @@ -326,9 +340,14 @@ const removeClient = async ( if (redisManager.isRedisEnabled()) { try { await redisManager.srem(getConnectionsKey(clientId), NODE_ID); - await redisManager.del(getNodeConnectionsKey(NODE_ID, clientId)); + await redisManager.del( + getNodeConnectionsKey(NODE_ID, clientId) + ); } catch (error) { - logger.error("Failed to remove client from Redis tracking (cleanup will occur on recovery):", error); + logger.error( + "Failed to remove client from Redis tracking (cleanup will occur on recovery):", + error + ); } } @@ -345,7 +364,10 @@ const removeClient = async ( ws.connectionId ); } catch (error) { - logger.error("Failed to remove specific connection from Redis tracking:", error); + logger.error( + "Failed to remove specific connection from Redis tracking:", + error + ); } } @@ -372,7 +394,9 @@ const sendToClientLocal = async ( } }); - logger.debug(`sendToClient: Message type ${message.type} sent to clientId ${clientId}`); + logger.debug( + `sendToClient: Message type ${message.type} sent to clientId ${clientId}` + ); return true; }; @@ -411,14 +435,22 @@ const sendToClient = async ( fromNodeId: NODE_ID }; - await redisManager.publish(REDIS_CHANNEL, JSON.stringify(redisMessage)); + await redisManager.publish( + REDIS_CHANNEL, + JSON.stringify(redisMessage) + ); } catch (error) { - logger.error("Failed to send message via Redis, message may be lost:", error); + logger.error( + "Failed to send message via Redis, message may be lost:", + error + ); // Continue execution - local delivery already attempted } } else if (!localSent && !redisManager.isRedisEnabled()) { // Redis is disabled or unavailable - log that we couldn't deliver to remote nodes - logger.debug(`Could not deliver message to ${clientId} - not connected locally and Redis unavailable`); + logger.debug( + `Could not deliver message to ${clientId} - not connected locally and Redis unavailable` + ); } return localSent; @@ -441,13 +473,21 @@ const broadcastToAllExcept = async ( fromNodeId: NODE_ID }; - await redisManager.publish(REDIS_CHANNEL, JSON.stringify(redisMessage)); + await redisManager.publish( + REDIS_CHANNEL, + JSON.stringify(redisMessage) + ); } catch (error) { - logger.error("Failed to broadcast message via Redis, remote nodes may not receive it:", error); + logger.error( + "Failed to broadcast message via Redis, remote nodes may not receive it:", + error + ); // Continue execution - local broadcast already completed } } else { - logger.debug("Redis unavailable - broadcast limited to local node only"); + logger.debug( + "Redis unavailable - broadcast limited to local node only" + ); } }; @@ -512,8 +552,10 @@ const verifyToken = async ( return null; } - if (olm.userId) { // this is a user device and we need to check the user token - const { session: userSession, user } = await validateSessionToken(userToken); + if (olm.userId) { + // this is a user device and we need to check the user token + const { session: userSession, user } = + await validateSessionToken(userToken); if (!userSession || !user) { return null; } @@ -668,7 +710,7 @@ const handleWSUpgrade = (server: HttpServer): void => { url.searchParams.get("token") || request.headers["sec-websocket-protocol"] || ""; - const userToken = url.searchParams.get('userToken') || ''; + const userToken = url.searchParams.get("userToken") || ""; let clientType = url.searchParams.get( "clientType" ) as ClientType; @@ -690,7 +732,11 @@ const handleWSUpgrade = (server: HttpServer): void => { return; } - const tokenPayload = await verifyToken(token, clientType, userToken); + const tokenPayload = await verifyToken( + token, + clientType, + userToken + ); if (!tokenPayload) { logger.debug( "Unauthorized connection attempt: invalid token..." @@ -724,50 +770,68 @@ const handleWSUpgrade = (server: HttpServer): void => { // Add periodic connection state sync to handle Redis disconnections/reconnections const startPeriodicStateSync = (): void => { // Lightweight sync every 5 minutes - just restore our own state - setInterval(async () => { - if (redisManager.isRedisEnabled() && !isRedisRecoveryInProgress) { - try { - await restoreLocalConnectionsToRedis(); - logger.debug("Periodic connection state sync completed"); - } catch (error) { - logger.error("Error during periodic connection state sync:", error); + setInterval( + async () => { + if (redisManager.isRedisEnabled() && !isRedisRecoveryInProgress) { + try { + await restoreLocalConnectionsToRedis(); + logger.debug("Periodic connection state sync completed"); + } catch (error) { + logger.error( + "Error during periodic connection state sync:", + error + ); + } } - } - }, 5 * 60 * 1000); // 5 minutes + }, + 5 * 60 * 1000 + ); // 5 minutes // Cleanup stale connections every 15 minutes - setInterval(async () => { - if (redisManager.isRedisEnabled()) { - try { - await cleanupStaleConnections(); - logger.debug("Periodic connection cleanup completed"); - } catch (error) { - logger.error("Error during periodic connection cleanup:", error); + setInterval( + async () => { + if (redisManager.isRedisEnabled()) { + try { + await cleanupStaleConnections(); + logger.debug("Periodic connection cleanup completed"); + } catch (error) { + logger.error( + "Error during periodic connection cleanup:", + error + ); + } } - } - }, 15 * 60 * 1000); // 15 minutes + }, + 15 * 60 * 1000 + ); // 15 minutes }; const cleanupStaleConnections = async (): Promise => { if (!redisManager.isRedisEnabled()) return; try { - const nodeKeys = await redisManager.getClient()?.keys(`ws:node:${NODE_ID}:*`) || []; - + const nodeKeys = + (await redisManager.getClient()?.keys(`ws:node:${NODE_ID}:*`)) || + []; + for (const nodeKey of nodeKeys) { const connections = await redisManager.hgetall(nodeKey); - const clientId = nodeKey.replace(`ws:node:${NODE_ID}:`, ''); + const clientId = nodeKey.replace(`ws:node:${NODE_ID}:`, ""); const localClients = connectedClients.get(clientId) || []; const localConnectionIds = localClients - .filter(client => client.readyState === WebSocket.OPEN) - .map(client => client.connectionId) + .filter((client) => client.readyState === WebSocket.OPEN) + .map((client) => client.connectionId) .filter(Boolean); // Remove Redis entries for connections that no longer exist locally - for (const [connectionId, timestamp] of Object.entries(connections)) { + for (const [connectionId, timestamp] of Object.entries( + connections + )) { if (!localConnectionIds.includes(connectionId)) { await redisManager.hdel(nodeKey, connectionId); - logger.debug(`Cleaned up stale connection: ${connectionId} for client: ${clientId}`); + logger.debug( + `Cleaned up stale connection: ${connectionId} for client: ${clientId}` + ); } } @@ -776,7 +840,9 @@ const cleanupStaleConnections = async (): Promise => { if (Object.keys(remainingConnections).length === 0) { await redisManager.srem(getConnectionsKey(clientId), NODE_ID); await redisManager.del(nodeKey); - logger.debug(`Cleaned up empty connection tracking for client: ${clientId}`); + logger.debug( + `Cleaned up empty connection tracking for client: ${clientId}` + ); } } } catch (error) { @@ -789,38 +855,38 @@ if (redisManager.isRedisEnabled()) { initializeRedisSubscription().catch((error) => { logger.error("Failed to initialize Redis subscription:", error); }); - + // Register recovery callback with Redis manager // When Redis reconnects, each node simply restores its own local state redisManager.onReconnection(async () => { logger.info("Redis reconnected, starting WebSocket state recovery..."); await recoverConnectionState(); }); - + // Start periodic state synchronization startPeriodicStateSync(); - + logger.info( `WebSocket handler initialized with Redis support - Node ID: ${NODE_ID}` ); } else { - logger.debug( - "WebSocket handler initialized in local mode" - ); + logger.debug("WebSocket handler initialized in local mode"); } // Disconnect a specific client and force them to reconnect const disconnectClient = async (clientId: string): Promise => { const mapKey = getClientMapKey(clientId); const clients = connectedClients.get(mapKey); - + if (!clients || clients.length === 0) { logger.debug(`No connections found for client ID: ${clientId}`); return false; } - logger.info(`Disconnecting client ID: ${clientId} (${clients.length} connection(s))`); - + logger.info( + `Disconnecting client ID: ${clientId} (${clients.length} connection(s))` + ); + // Close all connections for this client clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { diff --git a/server/routers/accessToken/deleteAccessToken.ts b/server/routers/accessToken/deleteAccessToken.ts index 5de4df9bf..4e18ddeb8 100644 --- a/server/routers/accessToken/deleteAccessToken.ts +++ b/server/routers/accessToken/deleteAccessToken.ts @@ -11,8 +11,8 @@ import { db } from "@server/db"; import { OpenAPITags, registry } from "@server/openApi"; const deleteAccessTokenParamsSchema = z.strictObject({ - accessTokenId: z.string() - }); + accessTokenId: z.string() +}); registry.registerPath({ method: "delete", diff --git a/server/routers/accessToken/generateAccessToken.ts b/server/routers/accessToken/generateAccessToken.ts index 36a202686..35da6add3 100644 --- a/server/routers/accessToken/generateAccessToken.ts +++ b/server/routers/accessToken/generateAccessToken.ts @@ -25,17 +25,14 @@ import { sha256 } from "@oslojs/crypto/sha2"; import { OpenAPITags, registry } from "@server/openApi"; export const generateAccessTokenBodySchema = z.strictObject({ - validForSeconds: z.int().positive().optional(), // seconds - title: z.string().optional(), - description: z.string().optional() - }); + validForSeconds: z.int().positive().optional(), // seconds + title: z.string().optional(), + description: z.string().optional() +}); export const generateAccssTokenParamsSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); export type GenerateAccessTokenResponse = Omit< ResourceAccessToken, diff --git a/server/routers/accessToken/listAccessTokens.ts b/server/routers/accessToken/listAccessTokens.ts index 476c858b5..2f929fc62 100644 --- a/server/routers/accessToken/listAccessTokens.ts +++ b/server/routers/accessToken/listAccessTokens.ts @@ -17,7 +17,8 @@ import stoi from "@server/lib/stoi"; import { fromZodError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; -const listAccessTokensParamsSchema = z.strictObject({ +const listAccessTokensParamsSchema = z + .strictObject({ resourceId: z .string() .optional() diff --git a/server/routers/apiKeys/createRootApiKey.ts b/server/routers/apiKeys/createRootApiKey.ts index 8e9e571df..fc0766234 100644 --- a/server/routers/apiKeys/createRootApiKey.ts +++ b/server/routers/apiKeys/createRootApiKey.ts @@ -15,8 +15,8 @@ import logger from "@server/logger"; import { hashPassword } from "@server/auth/password"; const bodySchema = z.strictObject({ - name: z.string().min(1).max(255) - }); + name: z.string().min(1).max(255) +}); export type CreateRootApiKeyBody = z.infer; diff --git a/server/routers/apiKeys/listApiKeyActions.ts b/server/routers/apiKeys/listApiKeyActions.ts index 7432d1750..073a75831 100644 --- a/server/routers/apiKeys/listApiKeyActions.ts +++ b/server/routers/apiKeys/listApiKeyActions.ts @@ -47,8 +47,7 @@ export type ListApiKeyActionsResponse = { registry.registerPath({ method: "get", path: "/org/{orgId}/api-key/{apiKeyId}/actions", - description: - "List all actions set for an API key.", + description: "List all actions set for an API key.", tags: [OpenAPITags.Org, OpenAPITags.ApiKey], request: { params: paramsSchema, diff --git a/server/routers/apiKeys/setApiKeyActions.ts b/server/routers/apiKeys/setApiKeyActions.ts index fe8cc4f19..629673886 100644 --- a/server/routers/apiKeys/setApiKeyActions.ts +++ b/server/routers/apiKeys/setApiKeyActions.ts @@ -11,9 +11,10 @@ import { eq, and, inArray } from "drizzle-orm"; import { OpenAPITags, registry } from "@server/openApi"; const bodySchema = z.strictObject({ - actionIds: z.tuple([z.string()], z.string()) - .transform((v) => Array.from(new Set(v))) - }); + actionIds: z + .tuple([z.string()], z.string()) + .transform((v) => Array.from(new Set(v))) +}); const paramsSchema = z.object({ apiKeyId: z.string().nonempty() diff --git a/server/routers/apiKeys/setApiKeyOrgs.ts b/server/routers/apiKeys/setApiKeyOrgs.ts index d60aad73b..51d0f043e 100644 --- a/server/routers/apiKeys/setApiKeyOrgs.ts +++ b/server/routers/apiKeys/setApiKeyOrgs.ts @@ -10,9 +10,10 @@ import { fromError } from "zod-validation-error"; import { eq, and, inArray } from "drizzle-orm"; const bodySchema = z.strictObject({ - orgIds: z.tuple([z.string()], z.string()) - .transform((v) => Array.from(new Set(v))) - }); + orgIds: z + .tuple([z.string()], z.string()) + .transform((v) => Array.from(new Set(v))) +}); const paramsSchema = z.object({ apiKeyId: z.string().nonempty() diff --git a/server/routers/auditLogs/generateCSV.ts b/server/routers/auditLogs/generateCSV.ts index 8a0670699..ea0da29f9 100644 --- a/server/routers/auditLogs/generateCSV.ts +++ b/server/routers/auditLogs/generateCSV.ts @@ -2,15 +2,17 @@ export function generateCSV(data: any[]): string { if (data.length === 0) { return "orgId,action,actorType,timestamp,actor\n"; } - + const headers = Object.keys(data[0]).join(","); - const rows = data.map(row => - Object.values(row).map(value => - typeof value === 'string' && value.includes(',') - ? `"${value.replace(/"/g, '""')}"` - : value - ).join(",") + const rows = data.map((row) => + Object.values(row) + .map((value) => + typeof value === "string" && value.includes(",") + ? `"${value.replace(/"/g, '""')}"` + : value + ) + .join(",") ); - + return [headers, ...rows].join("\n"); -} \ No newline at end of file +} diff --git a/server/routers/auditLogs/types.ts b/server/routers/auditLogs/types.ts index 81cef7330..474aa9261 100644 --- a/server/routers/auditLogs/types.ts +++ b/server/routers/auditLogs/types.ts @@ -90,4 +90,4 @@ export type QueryAccessAuditLogResponse = { }[]; locations: string[]; }; -}; \ No newline at end of file +}; diff --git a/server/routers/auth/changePassword.ts b/server/routers/auth/changePassword.ts index fa007d378..1a26b9117 100644 --- a/server/routers/auth/changePassword.ts +++ b/server/routers/auth/changePassword.ts @@ -6,10 +6,7 @@ import { z } from "zod"; import { db } from "@server/db"; import { User, users } from "@server/db"; import { response } from "@server/lib/response"; -import { - hashPassword, - verifyPassword -} from "@server/auth/password"; +import { hashPassword, verifyPassword } from "@server/auth/password"; import { verifyTotpCode } from "@server/auth/totp"; import logger from "@server/logger"; import { unauthorized } from "@server/auth/unauthorizedResponse"; @@ -23,10 +20,10 @@ import ConfirmPasswordReset from "@server/emails/templates/NotifyResetPassword"; import config from "@server/lib/config"; export const changePasswordBody = z.strictObject({ - oldPassword: z.string(), - newPassword: passwordSchema, - code: z.string().optional() - }); + oldPassword: z.string(), + newPassword: passwordSchema, + code: z.string().optional() +}); export type ChangePasswordBody = z.infer; @@ -62,12 +59,14 @@ async function invalidateAllSessionsExceptCurrent( } // Delete the user sessions (except current) - await trx.delete(sessions).where( - and( - eq(sessions.userId, userId), - ne(sessions.sessionId, currentSessionId) - ) - ); + await trx + .delete(sessions) + .where( + and( + eq(sessions.userId, userId), + ne(sessions.sessionId, currentSessionId) + ) + ); }); } catch (e) { logger.error("Failed to invalidate user sessions except current", e); @@ -157,7 +156,10 @@ export async function changePassword( .where(eq(users.userId, user.userId)); // Invalidate all sessions except the current one - await invalidateAllSessionsExceptCurrent(user.userId, req.session.sessionId); + await invalidateAllSessionsExceptCurrent( + user.userId, + req.session.sessionId + ); try { const email = user.email!; diff --git a/server/routers/auth/checkResourceSession.ts b/server/routers/auth/checkResourceSession.ts index 39466400b..74a94a843 100644 --- a/server/routers/auth/checkResourceSession.ts +++ b/server/routers/auth/checkResourceSession.ts @@ -9,7 +9,7 @@ import logger from "@server/logger"; export const params = z.strictObject({ token: z.string(), - resourceId: z.string().transform(Number).pipe(z.int().positive()), + resourceId: z.string().transform(Number).pipe(z.int().positive()) }); export type CheckResourceSessionParams = z.infer; @@ -21,7 +21,7 @@ export type CheckResourceSessionResponse = { export async function checkResourceSession( req: Request, res: Response, - next: NextFunction, + next: NextFunction ): Promise { const parsedParams = params.safeParse(req.params); @@ -29,8 +29,8 @@ export async function checkResourceSession( return next( createHttpError( HttpCode.BAD_REQUEST, - fromError(parsedParams.error).toString(), - ), + fromError(parsedParams.error).toString() + ) ); } @@ -39,7 +39,7 @@ export async function checkResourceSession( try { const { resourceSession } = await validateResourceSessionToken( token, - resourceId, + resourceId ); let valid = false; @@ -52,15 +52,15 @@ export async function checkResourceSession( success: true, error: false, message: "Checked validity", - status: HttpCode.OK, + status: HttpCode.OK }); } catch (e) { logger.error(e); return next( createHttpError( HttpCode.INTERNAL_SERVER_ERROR, - "Failed to reset password", - ), + "Failed to reset password" + ) ); } } diff --git a/server/routers/auth/disable2fa.ts b/server/routers/auth/disable2fa.ts index ebf6ab528..254d6ccd2 100644 --- a/server/routers/auth/disable2fa.ts +++ b/server/routers/auth/disable2fa.ts @@ -17,9 +17,9 @@ import { unauthorized } from "@server/auth/unauthorizedResponse"; import { UserType } from "@server/types/UserTypes"; export const disable2faBody = z.strictObject({ - password: z.string(), - code: z.string().optional() - }); + password: z.string(), + code: z.string().optional() +}); export type Disable2faBody = z.infer; @@ -56,7 +56,10 @@ export async function disable2fa( } try { - const validPassword = await verifyPassword(password, user.passwordHash!); + const validPassword = await verifyPassword( + password, + user.passwordHash! + ); if (!validPassword) { return next(unauthorized()); } diff --git a/server/routers/auth/index.ts b/server/routers/auth/index.ts index 4600a4cc2..22040614d 100644 --- a/server/routers/auth/index.ts +++ b/server/routers/auth/index.ts @@ -16,4 +16,4 @@ export * from "./checkResourceSession"; export * from "./securityKey"; export * from "./startDeviceWebAuth"; export * from "./verifyDeviceWebAuth"; -export * from "./pollDeviceWebAuth"; \ No newline at end of file +export * from "./pollDeviceWebAuth"; diff --git a/server/routers/auth/pollDeviceWebAuth.ts b/server/routers/auth/pollDeviceWebAuth.ts index 9949ab428..a5c713625 100644 --- a/server/routers/auth/pollDeviceWebAuth.ts +++ b/server/routers/auth/pollDeviceWebAuth.ts @@ -7,10 +7,7 @@ import logger from "@server/logger"; import { response } from "@server/lib/response"; import { db, deviceWebAuthCodes } from "@server/db"; import { eq, and, gt } from "drizzle-orm"; -import { - createSession, - generateSessionToken -} from "@server/auth/sessions/app"; +import { createSession, generateSessionToken } from "@server/auth/sessions/app"; import { encodeHexLowerCase } from "@oslojs/encoding"; import { sha256 } from "@oslojs/crypto/sha2"; @@ -22,9 +19,7 @@ export type PollDeviceWebAuthParams = z.infer; // Helper function to hash device code before querying database function hashDeviceCode(code: string): string { - return encodeHexLowerCase( - sha256(new TextEncoder().encode(code)) - ); + return encodeHexLowerCase(sha256(new TextEncoder().encode(code))); } export type PollDeviceWebAuthResponse = { @@ -127,7 +122,9 @@ export async function pollDeviceWebAuth( // Check if userId is set (should be set when verified) if (!deviceCode.userId) { - logger.error("Device code is verified but userId is missing", { codeId: deviceCode.codeId }); + logger.error("Device code is verified but userId is missing", { + codeId: deviceCode.codeId + }); return next( createHttpError( HttpCode.INTERNAL_SERVER_ERROR, @@ -165,4 +162,3 @@ export async function pollDeviceWebAuth( ); } } - diff --git a/server/routers/auth/requestPasswordReset.ts b/server/routers/auth/requestPasswordReset.ts index 0f9953e8f..42b53d24d 100644 --- a/server/routers/auth/requestPasswordReset.ts +++ b/server/routers/auth/requestPasswordReset.ts @@ -18,8 +18,8 @@ import { hashPassword } from "@server/auth/password"; import { UserType } from "@server/types/UserTypes"; export const requestPasswordResetBody = z.strictObject({ - email: z.email().toLowerCase() - }); + email: z.email().toLowerCase() +}); export type RequestPasswordResetBody = z.infer; diff --git a/server/routers/auth/requestTotpSecret.ts b/server/routers/auth/requestTotpSecret.ts index 53d801475..bc032ecd2 100644 --- a/server/routers/auth/requestTotpSecret.ts +++ b/server/routers/auth/requestTotpSecret.ts @@ -17,9 +17,9 @@ import { verifySession } from "@server/auth/sessions/verifySession"; import config from "@server/lib/config"; export const requestTotpSecretBody = z.strictObject({ - password: z.string(), - email: z.email().optional() - }); + password: z.string(), + email: z.email().optional() +}); export type RequestTotpSecretBody = z.infer; @@ -46,7 +46,8 @@ export async function requestTotpSecret( const { password, email } = parsedBody.data; - const { user: sessionUser, session: existingSession } = await verifySession(req); + const { user: sessionUser, session: existingSession } = + await verifySession(req); let user: User | null = sessionUser; if (!existingSession) { @@ -112,11 +113,7 @@ export async function requestTotpSecret( const hex = crypto.getRandomValues(new Uint8Array(20)); const secret = encodeHex(hex); - const uri = createTOTPKeyURI( - appName, - user.email!, - hex - ); + const uri = createTOTPKeyURI(appName, user.email!, hex); await db .update(users) diff --git a/server/routers/auth/resetPassword.ts b/server/routers/auth/resetPassword.ts index aeb855586..6e616346a 100644 --- a/server/routers/auth/resetPassword.ts +++ b/server/routers/auth/resetPassword.ts @@ -18,11 +18,11 @@ import { sendEmail } from "@server/emails"; import { passwordSchema } from "@server/auth/passwordSchema"; export const resetPasswordBody = z.strictObject({ - email: z.email().toLowerCase(), - token: z.string(), // reset secret code - newPassword: passwordSchema, - code: z.string().optional() // 2fa code - }); + email: z.email().toLowerCase(), + token: z.string(), // reset secret code + newPassword: passwordSchema, + code: z.string().optional() // 2fa code +}); export type ResetPasswordBody = z.infer; diff --git a/server/routers/auth/securityKey.ts b/server/routers/auth/securityKey.ts index eed2328d4..9a1ee2cdc 100644 --- a/server/routers/auth/securityKey.ts +++ b/server/routers/auth/securityKey.ts @@ -19,9 +19,7 @@ import type { GenerateAuthenticationOptionsOpts, AuthenticatorTransportFuture } from "@simplewebauthn/server"; -import { - isoBase64URL -} from '@simplewebauthn/server/helpers'; +import { isoBase64URL } from "@simplewebauthn/server/helpers"; import config from "@server/lib/config"; import { UserType } from "@server/types/UserTypes"; import { verifyPassword } from "@server/auth/password"; @@ -30,10 +28,12 @@ import { verifyTotpCode } from "@server/auth/totp"; // The RP ID is the domain name of your application const rpID = (() => { - const url = config.getRawConfig().app.dashboard_url ? new URL(config.getRawConfig().app.dashboard_url!) : undefined; + const url = config.getRawConfig().app.dashboard_url + ? new URL(config.getRawConfig().app.dashboard_url!) + : undefined; // For localhost, we must use 'localhost' without port - if (url?.hostname === 'localhost' || !url) { - return 'localhost'; + if (url?.hostname === "localhost" || !url) { + return "localhost"; } return url.hostname; })(); @@ -46,25 +46,38 @@ const origin = config.getRawConfig().app.dashboard_url || "localhost"; // This supports clustered deployments and persists across server restarts // Clean up expired challenges every 5 minutes -setInterval(async () => { - try { - const now = Date.now(); - await db - .delete(webauthnChallenge) - .where(lt(webauthnChallenge.expiresAt, now)); - // logger.debug("Cleaned up expired security key challenges"); - } catch (error) { - logger.error("Failed to clean up expired security key challenges", error); - } -}, 5 * 60 * 1000); +setInterval( + async () => { + try { + const now = Date.now(); + await db + .delete(webauthnChallenge) + .where(lt(webauthnChallenge.expiresAt, now)); + // logger.debug("Cleaned up expired security key challenges"); + } catch (error) { + logger.error( + "Failed to clean up expired security key challenges", + error + ); + } + }, + 5 * 60 * 1000 +); // Helper functions for challenge management -async function storeChallenge(sessionId: string, challenge: string, securityKeyName?: string, userId?: string) { - const expiresAt = Date.now() + (5 * 60 * 1000); // 5 minutes - +async function storeChallenge( + sessionId: string, + challenge: string, + securityKeyName?: string, + userId?: string +) { + const expiresAt = Date.now() + 5 * 60 * 1000; // 5 minutes + // Delete any existing challenge for this session - await db.delete(webauthnChallenge).where(eq(webauthnChallenge.sessionId, sessionId)); - + await db + .delete(webauthnChallenge) + .where(eq(webauthnChallenge.sessionId, sessionId)); + // Insert new challenge await db.insert(webauthnChallenge).values({ sessionId, @@ -88,7 +101,9 @@ async function getChallenge(sessionId: string) { // Check if expired if (challengeData.expiresAt < Date.now()) { - await db.delete(webauthnChallenge).where(eq(webauthnChallenge.sessionId, sessionId)); + await db + .delete(webauthnChallenge) + .where(eq(webauthnChallenge.sessionId, sessionId)); return null; } @@ -96,7 +111,9 @@ async function getChallenge(sessionId: string) { } async function clearChallenge(sessionId: string) { - await db.delete(webauthnChallenge).where(eq(webauthnChallenge.sessionId, sessionId)); + await db + .delete(webauthnChallenge) + .where(eq(webauthnChallenge.sessionId, sessionId)); } export const registerSecurityKeyBody = z.strictObject({ @@ -153,7 +170,10 @@ export async function startRegistration( try { // Verify password - const validPassword = await verifyPassword(password, user.passwordHash!); + const validPassword = await verifyPassword( + password, + user.passwordHash! + ); if (!validPassword) { return next(unauthorized()); } @@ -197,9 +217,11 @@ export async function startRegistration( .from(securityKeys) .where(eq(securityKeys.userId, user.userId)); - const excludeCredentials = existingSecurityKeys.map(key => ({ + const excludeCredentials = existingSecurityKeys.map((key) => ({ id: key.credentialId, - transports: key.transports ? JSON.parse(key.transports) as AuthenticatorTransportFuture[] : undefined + transports: key.transports + ? (JSON.parse(key.transports) as AuthenticatorTransportFuture[]) + : undefined })); const options: GenerateRegistrationOptionsOpts = { @@ -207,18 +229,23 @@ export async function startRegistration( rpID, userID: isoBase64URL.toBuffer(user.userId), userName: user.email || user.username, - attestationType: 'none', + attestationType: "none", excludeCredentials, authenticatorSelection: { - residentKey: 'preferred', - userVerification: 'preferred', + residentKey: "preferred", + userVerification: "preferred" } }; const registrationOptions = await generateRegistrationOptions(options); // Store challenge in database - await storeChallenge(req.session.sessionId, registrationOptions.challenge, name, user.userId); + await storeChallenge( + req.session.sessionId, + registrationOptions.challenge, + name, + user.userId + ); return response(res, { data: registrationOptions, @@ -270,7 +297,7 @@ export async function verifyRegistration( try { // Get challenge from database const challengeData = await getChallenge(req.session.sessionId); - + if (!challengeData) { return next( createHttpError( @@ -292,10 +319,7 @@ export async function verifyRegistration( if (!verified || !registrationInfo) { return next( - createHttpError( - HttpCode.BAD_REQUEST, - "Verification failed" - ) + createHttpError(HttpCode.BAD_REQUEST, "Verification failed") ); } @@ -303,9 +327,13 @@ export async function verifyRegistration( await db.insert(securityKeys).values({ credentialId: registrationInfo.credential.id, userId: user.userId, - publicKey: isoBase64URL.fromBuffer(registrationInfo.credential.publicKey), + publicKey: isoBase64URL.fromBuffer( + registrationInfo.credential.publicKey + ), signCount: registrationInfo.credential.counter || 0, - transports: registrationInfo.credential.transports ? JSON.stringify(registrationInfo.credential.transports) : null, + transports: registrationInfo.credential.transports + ? JSON.stringify(registrationInfo.credential.transports) + : null, name: challengeData.securityKeyName, lastUsed: new Date().toISOString(), dateCreated: new Date().toISOString() @@ -407,7 +435,10 @@ export async function deleteSecurityKey( try { // Verify password - const validPassword = await verifyPassword(password, user.passwordHash!); + const validPassword = await verifyPassword( + password, + user.passwordHash! + ); if (!validPassword) { return next(unauthorized()); } @@ -447,10 +478,12 @@ export async function deleteSecurityKey( await db .delete(securityKeys) - .where(and( - eq(securityKeys.credentialId, credentialId), - eq(securityKeys.userId, user.userId) - )); + .where( + and( + eq(securityKeys.credentialId, credentialId), + eq(securityKeys.userId, user.userId) + ) + ); return response(res, { data: null, @@ -502,10 +535,7 @@ export async function startAuthentication( if (!user || user.type !== UserType.Internal) { return next( - createHttpError( - HttpCode.BAD_REQUEST, - "Invalid credentials" - ) + createHttpError(HttpCode.BAD_REQUEST, "Invalid credentials") ); } @@ -525,25 +555,37 @@ export async function startAuthentication( ); } - allowCredentials = userSecurityKeys.map(key => ({ + allowCredentials = userSecurityKeys.map((key) => ({ id: key.credentialId, - transports: key.transports ? JSON.parse(key.transports) as AuthenticatorTransportFuture[] : undefined + transports: key.transports + ? (JSON.parse( + key.transports + ) as AuthenticatorTransportFuture[]) + : undefined })); } const options: GenerateAuthenticationOptionsOpts = { rpID, allowCredentials, - userVerification: 'preferred', + userVerification: "preferred" }; - const authenticationOptions = await generateAuthenticationOptions(options); + const authenticationOptions = + await generateAuthenticationOptions(options); // Generate a temporary session ID for unauthenticated users - const tempSessionId = email ? `temp_${email}_${Date.now()}` : `temp_${Date.now()}`; + const tempSessionId = email + ? `temp_${email}_${Date.now()}` + : `temp_${Date.now()}`; // Store challenge in database - await storeChallenge(tempSessionId, authenticationOptions.challenge, undefined, userId); + await storeChallenge( + tempSessionId, + authenticationOptions.challenge, + undefined, + userId + ); return response(res, { data: { ...authenticationOptions, tempSessionId }, @@ -580,7 +622,7 @@ export async function verifyAuthentication( } const { credential } = parsedBody.data; - const tempSessionId = req.headers['x-temp-session-id'] as string; + const tempSessionId = req.headers["x-temp-session-id"] as string; if (!tempSessionId) { return next( @@ -594,7 +636,7 @@ export async function verifyAuthentication( try { // Get challenge from database const challengeData = await getChallenge(tempSessionId); - + if (!challengeData) { return next( createHttpError( @@ -646,7 +688,11 @@ export async function verifyAuthentication( id: securityKey.credentialId, publicKey: isoBase64URL.toBuffer(securityKey.publicKey), counter: securityKey.signCount, - transports: securityKey.transports ? JSON.parse(securityKey.transports) as AuthenticatorTransportFuture[] : undefined + transports: securityKey.transports + ? (JSON.parse( + securityKey.transports + ) as AuthenticatorTransportFuture[]) + : undefined }, requireUserVerification: false }); @@ -672,7 +718,8 @@ export async function verifyAuthentication( .where(eq(securityKeys.credentialId, credentialId)); // Create session for the user - const { createSession, generateSessionToken, serializeSessionCookie } = await import("@server/auth/sessions/app"); + const { createSession, generateSessionToken, serializeSessionCookie } = + await import("@server/auth/sessions/app"); const token = generateSessionToken(); const session = await createSession(token, user.userId); const isSecure = req.protocol === "https"; @@ -703,4 +750,4 @@ export async function verifyAuthentication( ) ); } -} \ No newline at end of file +} diff --git a/server/routers/auth/signup.ts b/server/routers/auth/signup.ts index 842214cfc..2605a0267 100644 --- a/server/routers/auth/signup.ts +++ b/server/routers/auth/signup.ts @@ -56,8 +56,14 @@ export async function signup( ); } - const { email, password, inviteToken, inviteId, termsAcceptedTimestamp, marketingEmailConsent } = - parsedBody.data; + const { + email, + password, + inviteToken, + inviteId, + termsAcceptedTimestamp, + marketingEmailConsent + } = parsedBody.data; const passwordHash = await hashPassword(password); const userId = generateId(15); @@ -222,7 +228,9 @@ export async function signup( ); res.appendHeader("Set-Cookie", cookie); if (build == "saas" && marketingEmailConsent) { - logger.debug(`User ${email} opted in to marketing emails during signup.`); + logger.debug( + `User ${email} opted in to marketing emails during signup.` + ); moveEmailToAudience(email, AudienceIds.SignUps); } diff --git a/server/routers/auth/types.ts b/server/routers/auth/types.ts index bb5a1b4eb..023b2d8e8 100644 --- a/server/routers/auth/types.ts +++ b/server/routers/auth/types.ts @@ -5,4 +5,4 @@ export type TransferSessionResponse = { export type GetSessionTransferTokenRenponse = { token: string; -}; \ No newline at end of file +}; diff --git a/server/routers/auth/validateSetupToken.ts b/server/routers/auth/validateSetupToken.ts index 1a4725b67..26043f2d4 100644 --- a/server/routers/auth/validateSetupToken.ts +++ b/server/routers/auth/validateSetupToken.ts @@ -9,8 +9,8 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; const validateSetupTokenSchema = z.strictObject({ - token: z.string().min(1, "Token is required") - }); + token: z.string().min(1, "Token is required") +}); export type ValidateSetupTokenResponse = { valid: boolean; @@ -41,10 +41,7 @@ export async function validateSetupToken( .select() .from(setupTokens) .where( - and( - eq(setupTokens.token, token), - eq(setupTokens.used, false) - ) + and(eq(setupTokens.token, token), eq(setupTokens.used, false)) ); if (!setupToken) { @@ -79,4 +76,4 @@ export async function validateSetupToken( ) ); } -} \ No newline at end of file +} diff --git a/server/routers/auth/verifyEmail.ts b/server/routers/auth/verifyEmail.ts index 8d31eb45a..31c5166da 100644 --- a/server/routers/auth/verifyEmail.ts +++ b/server/routers/auth/verifyEmail.ts @@ -14,8 +14,8 @@ import { freeLimitSet, limitsService } from "@server/lib/billing"; import { build } from "@server/build"; export const verifyEmailBody = z.strictObject({ - code: z.string() - }); + code: z.string() +}); export type VerifyEmailBody = z.infer; diff --git a/server/routers/auth/verifyTotp.ts b/server/routers/auth/verifyTotp.ts index 9243c9f92..207287ea0 100644 --- a/server/routers/auth/verifyTotp.ts +++ b/server/routers/auth/verifyTotp.ts @@ -19,10 +19,10 @@ import { verifySession } from "@server/auth/sessions/verifySession"; import { unauthorized } from "@server/auth/unauthorizedResponse"; export const verifyTotpBody = z.strictObject({ - email: z.email().optional(), - password: z.string().optional(), - code: z.string() - }); + email: z.email().optional(), + password: z.string().optional(), + code: z.string() +}); export type VerifyTotpBody = z.infer; diff --git a/server/routers/badger/exchangeSession.ts b/server/routers/badger/exchangeSession.ts index b4b2deeaa..b8d01c119 100644 --- a/server/routers/badger/exchangeSession.ts +++ b/server/routers/badger/exchangeSession.ts @@ -12,7 +12,10 @@ import { serializeResourceSessionCookie, validateResourceSessionToken } from "@server/auth/sessions/resource"; -import { generateSessionToken, SESSION_COOKIE_EXPIRES } from "@server/auth/sessions/app"; +import { + generateSessionToken, + SESSION_COOKIE_EXPIRES +} from "@server/auth/sessions/app"; import { SESSION_COOKIE_EXPIRES as RESOURCE_SESSION_COOKIE_EXPIRES } from "@server/auth/sessions/resource"; import config from "@server/lib/config"; import { response } from "@server/lib/response"; @@ -55,8 +58,8 @@ export async function exchangeSession( let cleanHost = host; // if the host ends with :port if (cleanHost.match(/:[0-9]{1,5}$/)) { - const matched = ''+cleanHost.match(/:[0-9]{1,5}$/); - cleanHost = cleanHost.slice(0, -1*matched.length); + const matched = "" + cleanHost.match(/:[0-9]{1,5}$/); + cleanHost = cleanHost.slice(0, -1 * matched.length); } const clientIp = requestIp?.split(":")[0]; @@ -153,8 +156,8 @@ export async function exchangeSession( } } else { const expires = new Date( - Date.now() + SESSION_COOKIE_EXPIRES - ).getTime(); + Date.now() + SESSION_COOKIE_EXPIRES + ).getTime(); await createResourceSession({ token, resourceId: resource.resourceId, diff --git a/server/routers/badger/verifySession.test.ts b/server/routers/badger/verifySession.test.ts index b0ad9873b..7c967acef 100644 --- a/server/routers/badger/verifySession.test.ts +++ b/server/routers/badger/verifySession.test.ts @@ -1,13 +1,11 @@ -import { assertEquals } from '@test/assert'; +import { assertEquals } from "@test/assert"; function isPathAllowed(pattern: string, path: string): boolean { - // Normalize and split paths into segments const normalize = (p: string) => p.split("/").filter(Boolean); const patternParts = normalize(pattern); const pathParts = normalize(path); - // Recursive function to try different wildcard matches function matchSegments(patternIndex: number, pathIndex: number): boolean { const indent = " ".repeat(pathIndex); // Indent based on recursion depth @@ -30,7 +28,6 @@ function isPathAllowed(pattern: string, path: string): boolean { // For full segment wildcards, try consuming different numbers of path segments if (currentPatternPart === "*") { - // Try consuming 0 segments (skip the wildcard) if (matchSegments(patternIndex + 1, pathIndex)) { return true; @@ -74,69 +71,213 @@ function isPathAllowed(pattern: string, path: string): boolean { } function runTests() { - console.log('Running path matching tests...'); + console.log("Running path matching tests..."); // Test exact matching - assertEquals(isPathAllowed('foo', 'foo'), true, 'Exact match should be allowed'); - assertEquals(isPathAllowed('foo', 'bar'), false, 'Different segments should not match'); - assertEquals(isPathAllowed('foo/bar', 'foo/bar'), true, 'Exact multi-segment match should be allowed'); - assertEquals(isPathAllowed('foo/bar', 'foo/baz'), false, 'Partial multi-segment match should not be allowed'); + assertEquals( + isPathAllowed("foo", "foo"), + true, + "Exact match should be allowed" + ); + assertEquals( + isPathAllowed("foo", "bar"), + false, + "Different segments should not match" + ); + assertEquals( + isPathAllowed("foo/bar", "foo/bar"), + true, + "Exact multi-segment match should be allowed" + ); + assertEquals( + isPathAllowed("foo/bar", "foo/baz"), + false, + "Partial multi-segment match should not be allowed" + ); // Test with leading and trailing slashes - assertEquals(isPathAllowed('/foo', 'foo'), true, 'Pattern with leading slash should match'); - assertEquals(isPathAllowed('foo/', 'foo'), true, 'Pattern with trailing slash should match'); - assertEquals(isPathAllowed('/foo/', 'foo'), true, 'Pattern with both leading and trailing slashes should match'); - assertEquals(isPathAllowed('foo', '/foo/'), true, 'Path with leading and trailing slashes should match'); + assertEquals( + isPathAllowed("/foo", "foo"), + true, + "Pattern with leading slash should match" + ); + assertEquals( + isPathAllowed("foo/", "foo"), + true, + "Pattern with trailing slash should match" + ); + assertEquals( + isPathAllowed("/foo/", "foo"), + true, + "Pattern with both leading and trailing slashes should match" + ); + assertEquals( + isPathAllowed("foo", "/foo/"), + true, + "Path with leading and trailing slashes should match" + ); // Test simple wildcard matching - assertEquals(isPathAllowed('*', 'foo'), true, 'Single wildcard should match any single segment'); - assertEquals(isPathAllowed('*', 'foo/bar'), true, 'Single wildcard should match multiple segments'); - assertEquals(isPathAllowed('*/bar', 'foo/bar'), true, 'Wildcard prefix should match'); - assertEquals(isPathAllowed('foo/*', 'foo/bar'), true, 'Wildcard suffix should match'); - assertEquals(isPathAllowed('foo/*/baz', 'foo/bar/baz'), true, 'Wildcard in middle should match'); + assertEquals( + isPathAllowed("*", "foo"), + true, + "Single wildcard should match any single segment" + ); + assertEquals( + isPathAllowed("*", "foo/bar"), + true, + "Single wildcard should match multiple segments" + ); + assertEquals( + isPathAllowed("*/bar", "foo/bar"), + true, + "Wildcard prefix should match" + ); + assertEquals( + isPathAllowed("foo/*", "foo/bar"), + true, + "Wildcard suffix should match" + ); + assertEquals( + isPathAllowed("foo/*/baz", "foo/bar/baz"), + true, + "Wildcard in middle should match" + ); // Test multiple wildcards - assertEquals(isPathAllowed('*/*', 'foo/bar'), true, 'Multiple wildcards should match corresponding segments'); - assertEquals(isPathAllowed('*/*/*', 'foo/bar/baz'), true, 'Three wildcards should match three segments'); - assertEquals(isPathAllowed('foo/*/*', 'foo/bar/baz'), true, 'Specific prefix with wildcards should match'); - assertEquals(isPathAllowed('*/*/baz', 'foo/bar/baz'), true, 'Wildcards with specific suffix should match'); + assertEquals( + isPathAllowed("*/*", "foo/bar"), + true, + "Multiple wildcards should match corresponding segments" + ); + assertEquals( + isPathAllowed("*/*/*", "foo/bar/baz"), + true, + "Three wildcards should match three segments" + ); + assertEquals( + isPathAllowed("foo/*/*", "foo/bar/baz"), + true, + "Specific prefix with wildcards should match" + ); + assertEquals( + isPathAllowed("*/*/baz", "foo/bar/baz"), + true, + "Wildcards with specific suffix should match" + ); // Test wildcard consumption behavior - assertEquals(isPathAllowed('*', ''), true, 'Wildcard should optionally consume segments'); - assertEquals(isPathAllowed('foo/*', 'foo'), true, 'Trailing wildcard should be optional'); - assertEquals(isPathAllowed('*/*', 'foo'), true, 'Multiple wildcards can match fewer segments'); - assertEquals(isPathAllowed('*/*/*', 'foo/bar'), true, 'Extra wildcards can be skipped'); + assertEquals( + isPathAllowed("*", ""), + true, + "Wildcard should optionally consume segments" + ); + assertEquals( + isPathAllowed("foo/*", "foo"), + true, + "Trailing wildcard should be optional" + ); + assertEquals( + isPathAllowed("*/*", "foo"), + true, + "Multiple wildcards can match fewer segments" + ); + assertEquals( + isPathAllowed("*/*/*", "foo/bar"), + true, + "Extra wildcards can be skipped" + ); // Test complex nested paths - assertEquals(isPathAllowed('api/*/users', 'api/v1/users'), true, 'API versioning pattern should match'); - assertEquals(isPathAllowed('api/*/users/*', 'api/v1/users/123'), true, 'API resource pattern should match'); - assertEquals(isPathAllowed('api/*/users/*/profile', 'api/v1/users/123/profile'), true, 'Nested API pattern should match'); + assertEquals( + isPathAllowed("api/*/users", "api/v1/users"), + true, + "API versioning pattern should match" + ); + assertEquals( + isPathAllowed("api/*/users/*", "api/v1/users/123"), + true, + "API resource pattern should match" + ); + assertEquals( + isPathAllowed("api/*/users/*/profile", "api/v1/users/123/profile"), + true, + "Nested API pattern should match" + ); // Test for the requested padbootstrap* pattern - assertEquals(isPathAllowed('padbootstrap*', 'padbootstrap'), true, 'padbootstrap* should match padbootstrap'); - assertEquals(isPathAllowed('padbootstrap*', 'padbootstrapv1'), true, 'padbootstrap* should match padbootstrapv1'); - assertEquals(isPathAllowed('padbootstrap*', 'padbootstrap/files'), false, 'padbootstrap* should not match padbootstrap/files'); - assertEquals(isPathAllowed('padbootstrap*/*', 'padbootstrap/files'), true, 'padbootstrap*/* should match padbootstrap/files'); - assertEquals(isPathAllowed('padbootstrap*/files', 'padbootstrapv1/files'), true, 'padbootstrap*/files should not match padbootstrapv1/files (wildcard is segment-based, not partial)'); + assertEquals( + isPathAllowed("padbootstrap*", "padbootstrap"), + true, + "padbootstrap* should match padbootstrap" + ); + assertEquals( + isPathAllowed("padbootstrap*", "padbootstrapv1"), + true, + "padbootstrap* should match padbootstrapv1" + ); + assertEquals( + isPathAllowed("padbootstrap*", "padbootstrap/files"), + false, + "padbootstrap* should not match padbootstrap/files" + ); + assertEquals( + isPathAllowed("padbootstrap*/*", "padbootstrap/files"), + true, + "padbootstrap*/* should match padbootstrap/files" + ); + assertEquals( + isPathAllowed("padbootstrap*/files", "padbootstrapv1/files"), + true, + "padbootstrap*/files should not match padbootstrapv1/files (wildcard is segment-based, not partial)" + ); // Test wildcard edge cases - assertEquals(isPathAllowed('*/*/*/*/*/*', 'a/b'), true, 'Many wildcards can match few segments'); - assertEquals(isPathAllowed('a/*/b/*/c', 'a/anything/b/something/c'), true, 'Multiple wildcards in pattern should match corresponding segments'); + assertEquals( + isPathAllowed("*/*/*/*/*/*", "a/b"), + true, + "Many wildcards can match few segments" + ); + assertEquals( + isPathAllowed("a/*/b/*/c", "a/anything/b/something/c"), + true, + "Multiple wildcards in pattern should match corresponding segments" + ); // Test patterns with partial segment matches - assertEquals(isPathAllowed('padbootstrap*', 'padbootstrap-123'), true, 'Wildcards in isPathAllowed should be segment-based, not character-based'); - assertEquals(isPathAllowed('test*', 'testuser'), true, 'Asterisk as part of segment name is treated as a literal, not a wildcard'); - assertEquals(isPathAllowed('my*app', 'myapp'), true, 'Asterisk in middle of segment name is treated as a literal, not a wildcard'); + assertEquals( + isPathAllowed("padbootstrap*", "padbootstrap-123"), + true, + "Wildcards in isPathAllowed should be segment-based, not character-based" + ); + assertEquals( + isPathAllowed("test*", "testuser"), + true, + "Asterisk as part of segment name is treated as a literal, not a wildcard" + ); + assertEquals( + isPathAllowed("my*app", "myapp"), + true, + "Asterisk in middle of segment name is treated as a literal, not a wildcard" + ); - assertEquals(isPathAllowed('/', '/'), true, 'Root path should match root path'); - assertEquals(isPathAllowed('/', '/test'), false, 'Root path should not match non-root path'); + assertEquals( + isPathAllowed("/", "/"), + true, + "Root path should match root path" + ); + assertEquals( + isPathAllowed("/", "/test"), + false, + "Root path should not match non-root path" + ); - console.log('All tests passed!'); + console.log("All tests passed!"); } // Run all tests try { runTests(); } catch (error) { - console.error('Test failed:', error); + console.error("Test failed:", error); } diff --git a/server/routers/billing/types.ts b/server/routers/billing/types.ts index 2ec5a1b16..4e0aab52c 100644 --- a/server/routers/billing/types.ts +++ b/server/routers/billing/types.ts @@ -14,4 +14,3 @@ export type GetOrgTierResponse = { tier: string | null; active: boolean; }; - diff --git a/server/routers/billing/webhooks.ts b/server/routers/billing/webhooks.ts index 0ca38a8a0..53eda78cf 100644 --- a/server/routers/billing/webhooks.ts +++ b/server/routers/billing/webhooks.ts @@ -11,4 +11,4 @@ export async function billingWebhookHandler( return next( createHttpError(HttpCode.NOT_FOUND, "This endpoint is not in use") ); -} \ No newline at end of file +} diff --git a/server/routers/blueprints/applyJSONBlueprint.ts b/server/routers/blueprints/applyJSONBlueprint.ts index f8c9caec3..7eee15bf1 100644 --- a/server/routers/blueprints/applyJSONBlueprint.ts +++ b/server/routers/blueprints/applyJSONBlueprint.ts @@ -9,12 +9,12 @@ import { OpenAPITags, registry } from "@server/openApi"; import { applyBlueprint } from "@server/lib/blueprints/applyBlueprint"; const applyBlueprintSchema = z.strictObject({ - blueprint: z.string() - }); + blueprint: z.string() +}); const applyBlueprintParamsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); registry.registerPath({ method: "put", diff --git a/server/routers/blueprints/getBlueprint.ts b/server/routers/blueprints/getBlueprint.ts index 45c36af75..915e04814 100644 --- a/server/routers/blueprints/getBlueprint.ts +++ b/server/routers/blueprints/getBlueprint.ts @@ -13,12 +13,9 @@ import { OpenAPITags, registry } from "@server/openApi"; import { BlueprintData } from "./types"; const getBlueprintSchema = z.strictObject({ - blueprintId: z - .string() - .transform(stoi) - .pipe(z.int().positive()), - orgId: z.string() - }); + blueprintId: z.string().transform(stoi).pipe(z.int().positive()), + orgId: z.string() +}); async function query(blueprintId: number, orgId: string) { // Get the client diff --git a/server/routers/blueprints/listBlueprints.ts b/server/routers/blueprints/listBlueprints.ts index 315abfeda..2ece9e53d 100644 --- a/server/routers/blueprints/listBlueprints.ts +++ b/server/routers/blueprints/listBlueprints.ts @@ -11,23 +11,23 @@ import { OpenAPITags, registry } from "@server/openApi"; import { BlueprintData } from "./types"; const listBluePrintsParamsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); const listBluePrintsSchema = z.strictObject({ - limit: z - .string() - .optional() - .default("1000") - .transform(Number) - .pipe(z.int().nonnegative()), - offset: z - .string() - .optional() - .default("0") - .transform(Number) - .pipe(z.int().nonnegative()) - }); + limit: z + .string() + .optional() + .default("1000") + .transform(Number) + .pipe(z.int().nonnegative()), + offset: z + .string() + .optional() + .default("0") + .transform(Number) + .pipe(z.int().nonnegative()) +}); async function queryBlueprints(orgId: string, limit: number, offset: number) { const res = await db diff --git a/server/routers/certificates/createCertificate.ts b/server/routers/certificates/createCertificate.ts index e160e644e..e858e5cda 100644 --- a/server/routers/certificates/createCertificate.ts +++ b/server/routers/certificates/createCertificate.ts @@ -1,5 +1,9 @@ import { db, Transaction } from "@server/db"; -export async function createCertificate(domainId: string, domain: string, trx: Transaction | typeof db) { +export async function createCertificate( + domainId: string, + domain: string, + trx: Transaction | typeof db +) { return; -} \ No newline at end of file +} diff --git a/server/routers/certificates/types.ts b/server/routers/certificates/types.ts index 80136de8c..3ec908578 100644 --- a/server/routers/certificates/types.ts +++ b/server/routers/certificates/types.ts @@ -10,4 +10,4 @@ export type GetCertificateResponse = { updatedAt: string; errorMessage?: string | null; renewalCount: number; -} \ No newline at end of file +}; diff --git a/server/routers/client/listClients.ts b/server/routers/client/listClients.ts index 68cd9aa0e..42e47efea 100644 --- a/server/routers/client/listClients.ts +++ b/server/routers/client/listClients.ts @@ -10,7 +10,16 @@ import { import logger from "@server/logger"; import HttpCode from "@server/types/HttpCode"; import response from "@server/lib/response"; -import { and, count, eq, inArray, isNotNull, isNull, or, sql } from "drizzle-orm"; +import { + and, + count, + eq, + inArray, + isNotNull, + isNull, + or, + sql +} from "drizzle-orm"; import { NextFunction, Request, Response } from "express"; import createHttpError from "http-errors"; import { z } from "zod"; @@ -60,13 +69,9 @@ async function getLatestOlmVersion(): Promise { return latestVersion; } catch (error: any) { if (error.name === "AbortError") { - logger.warn( - "Request to fetch latest Olm version timed out (1.5s)" - ); + logger.warn("Request to fetch latest Olm version timed out (1.5s)"); } else if (error.cause?.code === "UND_ERR_CONNECT_TIMEOUT") { - logger.warn( - "Connection timeout while fetching latest Olm version" - ); + logger.warn("Connection timeout while fetching latest Olm version"); } else { logger.warn( "Error fetching latest Olm version:", @@ -77,10 +82,9 @@ async function getLatestOlmVersion(): Promise { } } - const listClientsParamsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); const listClientsSchema = z.object({ limit: z @@ -95,12 +99,14 @@ const listClientsSchema = z.object({ .default("0") .transform(Number) .pipe(z.int().nonnegative()), - filter: z - .enum(["user", "machine"]) - .optional() + filter: z.enum(["user", "machine"]).optional() }); -function queryClients(orgId: string, accessibleClientIds: number[], filter?: "user" | "machine") { +function queryClients( + orgId: string, + accessibleClientIds: number[], + filter?: "user" | "machine" +) { const conditions = [ inArray(clients.clientId, accessibleClientIds), eq(clients.orgId, orgId) @@ -158,16 +164,17 @@ type OlmWithUpdateAvailable = Awaited>[0] & { olmUpdateAvailable?: boolean; }; - export type ListClientsResponse = { - clients: Array>[0] & { - sites: Array<{ - siteId: number; - siteName: string | null; - siteNiceId: string | null; - }> - olmUpdateAvailable?: boolean; - }>; + clients: Array< + Awaited>[0] & { + sites: Array<{ + siteId: number; + siteName: string | null; + siteNiceId: string | null; + }>; + olmUpdateAvailable?: boolean; + } + >; pagination: { total: number; limit: number; offset: number }; }; @@ -271,28 +278,34 @@ export async function listClients( const totalCount = totalCountResult[0].count; // Get associated sites for all clients - const clientIds = clientsList.map(client => client.clientId); + const clientIds = clientsList.map((client) => client.clientId); const siteAssociations = await getSiteAssociations(clientIds); // Group site associations by client ID - const sitesByClient = siteAssociations.reduce((acc, association) => { - if (!acc[association.clientId]) { - acc[association.clientId] = []; - } - acc[association.clientId].push({ - siteId: association.siteId, - siteName: association.siteName, - siteNiceId: association.siteNiceId - }); - return acc; - }, {} as Record>); + const sitesByClient = siteAssociations.reduce( + (acc, association) => { + if (!acc[association.clientId]) { + acc[association.clientId] = []; + } + acc[association.clientId].push({ + siteId: association.siteId, + siteName: association.siteName, + siteNiceId: association.siteNiceId + }); + return acc; + }, + {} as Record< + number, + Array<{ + siteId: number; + siteName: string | null; + siteNiceId: string | null; + }> + > + ); // Merge clients with their site associations - const clientsWithSites = clientsList.map(client => ({ + const clientsWithSites = clientsList.map((client) => ({ ...client, sites: sitesByClient[client.clientId] || [] })); @@ -322,7 +335,6 @@ export async function listClients( } catch (error) { client.olmUpdateAvailable = false; } - }); } } catch (error) { @@ -333,7 +345,6 @@ export async function listClients( ); } - return response(res, { data: { clients: clientsWithSites, diff --git a/server/routers/client/pickClientDefaults.ts b/server/routers/client/pickClientDefaults.ts index 3d447ecdb..fd31da127 100644 --- a/server/routers/client/pickClientDefaults.ts +++ b/server/routers/client/pickClientDefaults.ts @@ -16,8 +16,8 @@ export type PickClientDefaultsResponse = { }; const pickClientDefaultsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); registry.registerPath({ method: "get", diff --git a/server/routers/client/targets.ts b/server/routers/client/targets.ts index 9887a4542..b7b91925c 100644 --- a/server/routers/client/targets.ts +++ b/server/routers/client/targets.ts @@ -101,14 +101,18 @@ export async function removePeerData( export async function updatePeerData( clientId: number, siteId: number, - remoteSubnets: { - oldRemoteSubnets: string[]; - newRemoteSubnets: string[]; - } | undefined, - aliases: { - oldAliases: Alias[]; - newAliases: Alias[]; - } | undefined, + remoteSubnets: + | { + oldRemoteSubnets: string[]; + newRemoteSubnets: string[]; + } + | undefined, + aliases: + | { + oldAliases: Alias[]; + newAliases: Alias[]; + } + | undefined, olmId?: string ) { if (!olmId) { diff --git a/server/routers/client/terminate.ts b/server/routers/client/terminate.ts index dc49ef054..1cfdc7098 100644 --- a/server/routers/client/terminate.ts +++ b/server/routers/client/terminate.ts @@ -2,7 +2,10 @@ import { sendToClient } from "#dynamic/routers/ws"; import { db, olms } from "@server/db"; import { eq } from "drizzle-orm"; -export async function sendTerminateClient(clientId: number, olmId?: string | null) { +export async function sendTerminateClient( + clientId: number, + olmId?: string | null +) { if (!olmId) { const [olm] = await db .select() diff --git a/server/routers/domain/createOrgDomain.ts b/server/routers/domain/createOrgDomain.ts index 3f223bce4..6558d748c 100644 --- a/server/routers/domain/createOrgDomain.ts +++ b/server/routers/domain/createOrgDomain.ts @@ -1,6 +1,13 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db, Domain, domains, OrgDomains, orgDomains, dnsRecords } from "@server/db"; +import { + db, + Domain, + domains, + OrgDomains, + orgDomains, + dnsRecords +} from "@server/db"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -16,16 +23,15 @@ import { build } from "@server/build"; import config from "@server/lib/config"; const paramsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); const bodySchema = z.strictObject({ - type: z.enum(["ns", "cname", "wildcard"]), - baseDomain: subdomainSchema, - certResolver: z.string().optional().nullable(), - preferWildcardCert: z.boolean().optional().nullable() // optional, only for wildcard - }); - + type: z.enum(["ns", "cname", "wildcard"]), + baseDomain: subdomainSchema, + certResolver: z.string().optional().nullable(), + preferWildcardCert: z.boolean().optional().nullable() // optional, only for wildcard +}); export type CreateDomainResponse = { domainId: string; @@ -72,7 +78,8 @@ export async function createOrgDomain( } const { orgId } = parsedParams.data; - const { type, baseDomain, certResolver, preferWildcardCert } = parsedBody.data; + const { type, baseDomain, certResolver, preferWildcardCert } = + parsedBody.data; if (build == "oss") { if (type !== "wildcard") { @@ -278,7 +285,7 @@ export async function createOrgDomain( // TODO: This needs to be cross region and not hardcoded if (type === "ns") { nsRecords = config.getRawConfig().dns.nameservers as string[]; - + // Save NS records to database for (const nsValue of nsRecords) { recordsToInsert.push({ @@ -300,7 +307,7 @@ export async function createOrgDomain( baseDomain: `_acme-challenge.${baseDomain}` } ]; - + // Save CNAME records to database for (const cnameRecord of cnameRecords) { recordsToInsert.push({ @@ -322,7 +329,7 @@ export async function createOrgDomain( baseDomain: `${baseDomain}` } ]; - + // Save A records to database for (const aRecord of aRecords) { recordsToInsert.push({ diff --git a/server/routers/domain/deleteOrgDomain.ts b/server/routers/domain/deleteOrgDomain.ts index fe4a4805c..fa916beb2 100644 --- a/server/routers/domain/deleteOrgDomain.ts +++ b/server/routers/domain/deleteOrgDomain.ts @@ -11,9 +11,9 @@ import { usageService } from "@server/lib/billing/usageService"; import { FeatureId } from "@server/lib/billing"; const paramsSchema = z.strictObject({ - domainId: z.string(), - orgId: z.string() - }); + domainId: z.string(), + orgId: z.string() +}); export type DeleteAccountDomainResponse = { success: boolean; @@ -48,10 +48,7 @@ export async function deleteAccountDomain( eq(orgDomains.domainId, domainId) ) ) - .innerJoin( - domains, - eq(orgDomains.domainId, domains.domainId) - ); + .innerJoin(domains, eq(orgDomains.domainId, domains.domainId)); if (!existing) { return next( diff --git a/server/routers/domain/getDNSRecords.ts b/server/routers/domain/getDNSRecords.ts index 239cc455c..5a373a11a 100644 --- a/server/routers/domain/getDNSRecords.ts +++ b/server/routers/domain/getDNSRecords.ts @@ -11,16 +11,16 @@ import { OpenAPITags, registry } from "@server/openApi"; import { getServerIp } from "@server/lib/serverIpService"; // your in-memory IP module const getDNSRecordsSchema = z.strictObject({ - domainId: z.string(), - orgId: z.string() - }); + domainId: z.string(), + orgId: z.string() +}); async function query(domainId: string) { const records = await db .select() .from(dnsRecords) .where(eq(dnsRecords.domainId, domainId)); - + return records; } @@ -72,8 +72,11 @@ export async function getDNSRecords( const serverIp = getServerIp(); // Override value for type A or wildcard records - const updatedRecords = records.map(record => { - if ((record.recordType === "A" || record.baseDomain === "*") && serverIp) { + const updatedRecords = records.map((record) => { + if ( + (record.recordType === "A" || record.baseDomain === "*") && + serverIp + ) { return { ...record, value: serverIp }; } return record; @@ -92,4 +95,4 @@ export async function getDNSRecords( createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") ); } -} \ No newline at end of file +} diff --git a/server/routers/domain/getDomain.ts b/server/routers/domain/getDomain.ts index 408cf37d2..3e5565f92 100644 --- a/server/routers/domain/getDomain.ts +++ b/server/routers/domain/getDomain.ts @@ -11,11 +11,9 @@ import { OpenAPITags, registry } from "@server/openApi"; import { domain } from "zod/v4/core/regexes"; const getDomainSchema = z.strictObject({ - domainId: z - .string() - .optional(), - orgId: z.string().optional() - }); + domainId: z.string().optional(), + orgId: z.string().optional() +}); async function query(domainId?: string, orgId?: string) { if (domainId) { @@ -65,7 +63,9 @@ export async function getDomain( const domain = await query(domainId, orgId); if (!domain) { - return next(createHttpError(HttpCode.NOT_FOUND, "Domain not found")); + return next( + createHttpError(HttpCode.NOT_FOUND, "Domain not found") + ); } return response(res, { diff --git a/server/routers/domain/index.ts b/server/routers/domain/index.ts index e7e0b555d..73b28fea8 100644 --- a/server/routers/domain/index.ts +++ b/server/routers/domain/index.ts @@ -4,4 +4,4 @@ export * from "./deleteOrgDomain"; export * from "./restartOrgDomain"; export * from "./getDomain"; export * from "./getDNSRecords"; -export * from "./updateDomain"; \ No newline at end of file +export * from "./updateDomain"; diff --git a/server/routers/domain/listDomains.ts b/server/routers/domain/listDomains.ts index 48f22c6c5..20b236346 100644 --- a/server/routers/domain/listDomains.ts +++ b/server/routers/domain/listDomains.ts @@ -11,23 +11,23 @@ import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; const listDomainsParamsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); const listDomainsSchema = z.strictObject({ - limit: z - .string() - .optional() - .default("1000") - .transform(Number) - .pipe(z.int().nonnegative()), - offset: z - .string() - .optional() - .default("0") - .transform(Number) - .pipe(z.int().nonnegative()) - }); + limit: z + .string() + .optional() + .default("1000") + .transform(Number) + .pipe(z.int().nonnegative()), + offset: z + .string() + .optional() + .default("0") + .transform(Number) + .pipe(z.int().nonnegative()) +}); async function queryDomains(orgId: string, limit: number, offset: number) { const res = await db diff --git a/server/routers/domain/restartOrgDomain.ts b/server/routers/domain/restartOrgDomain.ts index f2bf7c39e..1039d2fbe 100644 --- a/server/routers/domain/restartOrgDomain.ts +++ b/server/routers/domain/restartOrgDomain.ts @@ -9,9 +9,9 @@ import { fromError } from "zod-validation-error"; import { and, eq } from "drizzle-orm"; const paramsSchema = z.strictObject({ - domainId: z.string(), - orgId: z.string() - }); + domainId: z.string(), + orgId: z.string() +}); export type RestartOrgDomainResponse = { success: boolean; diff --git a/server/routers/domain/types.ts b/server/routers/domain/types.ts index 4ae48fb10..ececc2db0 100644 --- a/server/routers/domain/types.ts +++ b/server/routers/domain/types.ts @@ -5,4 +5,4 @@ export type CheckDomainAvailabilityResponse = { domainId: string; fullDomain: string; }[]; -}; \ No newline at end of file +}; diff --git a/server/routers/domain/updateDomain.ts b/server/routers/domain/updateDomain.ts index 083011891..64e78641d 100644 --- a/server/routers/domain/updateDomain.ts +++ b/server/routers/domain/updateDomain.ts @@ -10,14 +10,14 @@ import { eq, and } from "drizzle-orm"; import { OpenAPITags, registry } from "@server/openApi"; const paramsSchema = z.strictObject({ - orgId: z.string(), - domainId: z.string() - }); + orgId: z.string(), + domainId: z.string() +}); const bodySchema = z.strictObject({ - certResolver: z.string().optional().nullable(), - preferWildcardCert: z.boolean().optional().nullable() - }); + certResolver: z.string().optional().nullable(), + preferWildcardCert: z.boolean().optional().nullable() +}); export type UpdateDomainResponse = { domainId: string; @@ -25,7 +25,6 @@ export type UpdateDomainResponse = { preferWildcardCert: boolean | null; }; - registry.registerPath({ method: "patch", path: "/org/{orgId}/domain/{domainId}", @@ -88,7 +87,6 @@ export async function updateOrgDomain( ); } - const [existingDomain] = await db .select() .from(domains) @@ -154,4 +152,4 @@ export async function updateOrgDomain( createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") ); } -} \ No newline at end of file +} diff --git a/server/routers/external.ts b/server/routers/external.ts index 54e84e2e2..54b48c6ef 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -318,7 +318,7 @@ authenticated.post( verifyRoleAccess, verifyUserHasAction(ActionsEnum.setResourceRoles), logActionAudit(ActionsEnum.setResourceRoles), - siteResource.setSiteResourceRoles, + siteResource.setSiteResourceRoles ); authenticated.post( @@ -327,7 +327,7 @@ authenticated.post( verifySetResourceUsers, verifyUserHasAction(ActionsEnum.setResourceUsers), logActionAudit(ActionsEnum.setResourceUsers), - siteResource.setSiteResourceUsers, + siteResource.setSiteResourceUsers ); authenticated.post( @@ -336,7 +336,7 @@ authenticated.post( verifySetResourceClients, verifyUserHasAction(ActionsEnum.setResourceUsers), logActionAudit(ActionsEnum.setResourceUsers), - siteResource.setSiteResourceClients, + siteResource.setSiteResourceClients ); authenticated.post( @@ -345,7 +345,7 @@ authenticated.post( verifySetResourceClients, verifyUserHasAction(ActionsEnum.setResourceUsers), logActionAudit(ActionsEnum.setResourceUsers), - siteResource.addClientToSiteResource, + siteResource.addClientToSiteResource ); authenticated.post( @@ -354,7 +354,7 @@ authenticated.post( verifySetResourceClients, verifyUserHasAction(ActionsEnum.setResourceUsers), logActionAudit(ActionsEnum.setResourceUsers), - siteResource.removeClientFromSiteResource, + siteResource.removeClientFromSiteResource ); authenticated.put( @@ -812,17 +812,9 @@ authenticated.delete( // createNewt // ); -authenticated.put( - "/user/:userId/olm", - verifyIsLoggedInUser, - olm.createUserOlm -); +authenticated.put("/user/:userId/olm", verifyIsLoggedInUser, olm.createUserOlm); -authenticated.get( - "/user/:userId/olms", - verifyIsLoggedInUser, - olm.listUserOlms -); +authenticated.get("/user/:userId/olms", verifyIsLoggedInUser, olm.listUserOlms); authenticated.delete( "/user/:userId/olm/:olmId", diff --git a/server/routers/generatedLicense/types.ts b/server/routers/generatedLicense/types.ts index 4c5efed75..76e86265a 100644 --- a/server/routers/generatedLicense/types.ts +++ b/server/routers/generatedLicense/types.ts @@ -27,4 +27,4 @@ export type NewLicenseKey = { }; }; -export type GenerateNewLicenseResponse = NewLicenseKey; \ No newline at end of file +export type GenerateNewLicenseResponse = NewLicenseKey; diff --git a/server/routers/gerbil/createExitNode.ts b/server/routers/gerbil/createExitNode.ts index 8148ed750..bc9650367 100644 --- a/server/routers/gerbil/createExitNode.ts +++ b/server/routers/gerbil/createExitNode.ts @@ -5,7 +5,10 @@ import { getNextAvailableSubnet } from "@server/lib/exitNodes"; import logger from "@server/logger"; import { eq } from "drizzle-orm"; -export async function createExitNode(publicKey: string, reachableAt: string | undefined) { +export async function createExitNode( + publicKey: string, + reachableAt: string | undefined +) { // Fetch exit node const [exitNodeQuery] = await db.select().from(exitNodes).limit(1); let exitNode: ExitNode; diff --git a/server/routers/gerbil/getConfig.ts b/server/routers/gerbil/getConfig.ts index 56ebd7445..ba3ab7ad8 100644 --- a/server/routers/gerbil/getConfig.ts +++ b/server/routers/gerbil/getConfig.ts @@ -117,4 +117,4 @@ export async function generateGerbilConfig(exitNode: ExitNode) { }; return configResponse; -} \ No newline at end of file +} diff --git a/server/routers/gerbil/index.ts b/server/routers/gerbil/index.ts index bff57d051..aa957d3a6 100644 --- a/server/routers/gerbil/index.ts +++ b/server/routers/gerbil/index.ts @@ -2,4 +2,4 @@ export * from "./getConfig"; export * from "./receiveBandwidth"; export * from "./updateHolePunch"; export * from "./getAllRelays"; -export * from "./getResolvedHostname"; \ No newline at end of file +export * from "./getResolvedHostname"; diff --git a/server/routers/hybrid.ts b/server/routers/hybrid.ts index 235961f11..398abdb80 100644 --- a/server/routers/hybrid.ts +++ b/server/routers/hybrid.ts @@ -1,4 +1,4 @@ import { Router } from "express"; // Root routes -export const hybridRouter = Router(); \ No newline at end of file +export const hybridRouter = Router(); diff --git a/server/routers/idp/createIdpOrgPolicy.ts b/server/routers/idp/createIdpOrgPolicy.ts index b8c947b04..b9a0098b5 100644 --- a/server/routers/idp/createIdpOrgPolicy.ts +++ b/server/routers/idp/createIdpOrgPolicy.ts @@ -12,14 +12,14 @@ import { eq, and } from "drizzle-orm"; import { idp, idpOrg } from "@server/db"; const paramsSchema = z.strictObject({ - idpId: z.coerce.number(), - orgId: z.string() - }); + idpId: z.coerce.number(), + orgId: z.string() +}); const bodySchema = z.strictObject({ - roleMapping: z.string().optional(), - orgMapping: z.string().optional() - }); + roleMapping: z.string().optional(), + orgMapping: z.string().optional() +}); export type CreateIdpOrgPolicyResponse = {}; diff --git a/server/routers/idp/createOidcIdp.ts b/server/routers/idp/createOidcIdp.ts index 2548cb047..c7eeaf305 100644 --- a/server/routers/idp/createOidcIdp.ts +++ b/server/routers/idp/createOidcIdp.ts @@ -15,17 +15,17 @@ import config from "@server/lib/config"; const paramsSchema = z.strictObject({}); const bodySchema = z.strictObject({ - name: z.string().nonempty(), - clientId: z.string().nonempty(), - clientSecret: z.string().nonempty(), - authUrl: z.url(), - tokenUrl: z.url(), - identifierPath: z.string().nonempty(), - emailPath: z.string().optional(), - namePath: z.string().optional(), - scopes: z.string().nonempty(), - autoProvision: z.boolean().optional() - }); + name: z.string().nonempty(), + clientId: z.string().nonempty(), + clientSecret: z.string().nonempty(), + authUrl: z.url(), + tokenUrl: z.url(), + identifierPath: z.string().nonempty(), + emailPath: z.string().optional(), + namePath: z.string().optional(), + scopes: z.string().nonempty(), + autoProvision: z.boolean().optional() +}); export type CreateIdpResponse = { idpId: number; diff --git a/server/routers/idp/deleteIdp.ts b/server/routers/idp/deleteIdp.ts index 56c0ca984..f2b550993 100644 --- a/server/routers/idp/deleteIdp.ts +++ b/server/routers/idp/deleteIdp.ts @@ -53,12 +53,7 @@ export async function deleteIdp( .where(eq(idp.idpId, idpId)); if (!existingIdp) { - return next( - createHttpError( - HttpCode.NOT_FOUND, - "IdP not found" - ) - ); + return next(createHttpError(HttpCode.NOT_FOUND, "IdP not found")); } // Delete the IDP and its related records in a transaction @@ -69,14 +64,10 @@ export async function deleteIdp( .where(eq(idpOidcConfig.idpId, idpId)); // Delete IDP-org mappings - await trx - .delete(idpOrg) - .where(eq(idpOrg.idpId, idpId)); + await trx.delete(idpOrg).where(eq(idpOrg.idpId, idpId)); // Delete the IDP itself - await trx - .delete(idp) - .where(eq(idp.idpId, idpId)); + await trx.delete(idp).where(eq(idp.idpId, idpId)); }); return response(res, { diff --git a/server/routers/idp/deleteIdpOrgPolicy.ts b/server/routers/idp/deleteIdpOrgPolicy.ts index c5f182821..b52a37df2 100644 --- a/server/routers/idp/deleteIdpOrgPolicy.ts +++ b/server/routers/idp/deleteIdpOrgPolicy.ts @@ -11,9 +11,9 @@ import { eq, and } from "drizzle-orm"; import { OpenAPITags, registry } from "@server/openApi"; const paramsSchema = z.strictObject({ - idpId: z.coerce.number(), - orgId: z.string() - }); + idpId: z.coerce.number(), + orgId: z.string() +}); registry.registerPath({ method: "delete", diff --git a/server/routers/idp/generateOidcUrl.ts b/server/routers/idp/generateOidcUrl.ts index 2db8783f4..50b63ee52 100644 --- a/server/routers/idp/generateOidcUrl.ts +++ b/server/routers/idp/generateOidcUrl.ts @@ -24,8 +24,8 @@ const paramsSchema = z .strict(); const bodySchema = z.strictObject({ - redirectUrl: z.string() - }); + redirectUrl: z.string() +}); const querySchema = z.object({ orgId: z.string().optional() // check what actuall calls it diff --git a/server/routers/idp/getIdp.ts b/server/routers/idp/getIdp.ts index e8651c841..072537513 100644 --- a/server/routers/idp/getIdp.ts +++ b/server/routers/idp/getIdp.ts @@ -71,14 +71,8 @@ export async function getIdp( const clientSecret = idpRes.idpOidcConfig!.clientSecret; const clientId = idpRes.idpOidcConfig!.clientId; - idpRes.idpOidcConfig!.clientSecret = decrypt( - clientSecret, - key - ); - idpRes.idpOidcConfig!.clientId = decrypt( - clientId, - key - ); + idpRes.idpOidcConfig!.clientSecret = decrypt(clientSecret, key); + idpRes.idpOidcConfig!.clientId = decrypt(clientId, key); } return response(res, { diff --git a/server/routers/idp/index.ts b/server/routers/idp/index.ts index 81cec8d15..f0dcf02e7 100644 --- a/server/routers/idp/index.ts +++ b/server/routers/idp/index.ts @@ -8,4 +8,4 @@ export * from "./getIdp"; export * from "./createIdpOrgPolicy"; export * from "./deleteIdpOrgPolicy"; export * from "./listIdpOrgPolicies"; -export * from "./updateIdpOrgPolicy"; \ No newline at end of file +export * from "./updateIdpOrgPolicy"; diff --git a/server/routers/idp/listIdpOrgPolicies.ts b/server/routers/idp/listIdpOrgPolicies.ts index 087b52f80..9f7cdb42b 100644 --- a/server/routers/idp/listIdpOrgPolicies.ts +++ b/server/routers/idp/listIdpOrgPolicies.ts @@ -15,19 +15,19 @@ const paramsSchema = z.object({ }); const querySchema = z.strictObject({ - limit: z - .string() - .optional() - .default("1000") - .transform(Number) - .pipe(z.int().nonnegative()), - offset: z - .string() - .optional() - .default("0") - .transform(Number) - .pipe(z.int().nonnegative()) - }); + limit: z + .string() + .optional() + .default("1000") + .transform(Number) + .pipe(z.int().nonnegative()), + offset: z + .string() + .optional() + .default("0") + .transform(Number) + .pipe(z.int().nonnegative()) +}); async function query(idpId: number, limit: number, offset: number) { const res = await db diff --git a/server/routers/idp/listIdps.ts b/server/routers/idp/listIdps.ts index 8ce2ab785..20d1899ea 100644 --- a/server/routers/idp/listIdps.ts +++ b/server/routers/idp/listIdps.ts @@ -11,19 +11,19 @@ import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; const querySchema = z.strictObject({ - limit: z - .string() - .optional() - .default("1000") - .transform(Number) - .pipe(z.int().nonnegative()), - offset: z - .string() - .optional() - .default("0") - .transform(Number) - .pipe(z.int().nonnegative()) - }); + limit: z + .string() + .optional() + .default("1000") + .transform(Number) + .pipe(z.int().nonnegative()), + offset: z + .string() + .optional() + .default("0") + .transform(Number) + .pipe(z.int().nonnegative()) +}); async function query(limit: number, offset: number) { const res = await db diff --git a/server/routers/idp/updateIdpOrgPolicy.ts b/server/routers/idp/updateIdpOrgPolicy.ts index 82d3b5f27..6432faf69 100644 --- a/server/routers/idp/updateIdpOrgPolicy.ts +++ b/server/routers/idp/updateIdpOrgPolicy.ts @@ -11,14 +11,14 @@ import { eq, and } from "drizzle-orm"; import { idp, idpOrg } from "@server/db"; const paramsSchema = z.strictObject({ - idpId: z.coerce.number(), - orgId: z.string() - }); + idpId: z.coerce.number(), + orgId: z.string() +}); const bodySchema = z.strictObject({ - roleMapping: z.string().optional(), - orgMapping: z.string().optional() - }); + roleMapping: z.string().optional(), + orgMapping: z.string().optional() +}); export type UpdateIdpOrgPolicyResponse = {}; diff --git a/server/routers/idp/updateOidcIdp.ts b/server/routers/idp/updateOidcIdp.ts index 1dbdd00a9..a4d55187f 100644 --- a/server/routers/idp/updateOidcIdp.ts +++ b/server/routers/idp/updateOidcIdp.ts @@ -19,19 +19,19 @@ const paramsSchema = z .strict(); const bodySchema = z.strictObject({ - name: z.string().optional(), - clientId: z.string().optional(), - clientSecret: z.string().optional(), - authUrl: z.string().optional(), - tokenUrl: z.string().optional(), - identifierPath: z.string().optional(), - emailPath: z.string().optional(), - namePath: z.string().optional(), - scopes: z.string().optional(), - autoProvision: z.boolean().optional(), - defaultRoleMapping: z.string().optional(), - defaultOrgMapping: z.string().optional() - }); + name: z.string().optional(), + clientId: z.string().optional(), + clientSecret: z.string().optional(), + authUrl: z.string().optional(), + tokenUrl: z.string().optional(), + identifierPath: z.string().optional(), + emailPath: z.string().optional(), + namePath: z.string().optional(), + scopes: z.string().optional(), + autoProvision: z.boolean().optional(), + defaultRoleMapping: z.string().optional(), + defaultOrgMapping: z.string().optional() +}); export type UpdateIdpResponse = { idpId: number; diff --git a/server/routers/license/types.ts b/server/routers/license/types.ts index 945bd368a..a78a287fd 100644 --- a/server/routers/license/types.ts +++ b/server/routers/license/types.ts @@ -8,4 +8,4 @@ export type GetLicenseStatusResponse = LicenseStatus; export type ListLicenseKeysResponse = LicenseKeyCache[]; -export type RecheckStatusResponse = LicenseStatus; \ No newline at end of file +export type RecheckStatusResponse = LicenseStatus; diff --git a/server/routers/loginPage/types.ts b/server/routers/loginPage/types.ts index 26f59cab1..a68dd7d4a 100644 --- a/server/routers/loginPage/types.ts +++ b/server/routers/loginPage/types.ts @@ -8,4 +8,4 @@ export type GetLoginPageResponse = LoginPage; export type UpdateLoginPageResponse = LoginPage; -export type LoadLoginPageResponse = LoginPage & { orgId: string }; \ No newline at end of file +export type LoadLoginPageResponse = LoginPage & { orgId: string }; diff --git a/server/routers/newt/createNewt.ts b/server/routers/newt/createNewt.ts index 930c04be1..b5da405e6 100644 --- a/server/routers/newt/createNewt.ts +++ b/server/routers/newt/createNewt.ts @@ -24,9 +24,9 @@ export type CreateNewtResponse = { }; const createNewtSchema = z.strictObject({ - newtId: z.string(), - secret: z.string() - }); + newtId: z.string(), + secret: z.string() +}); export async function createNewt( req: Request, @@ -34,7 +34,6 @@ export async function createNewt( next: NextFunction ): Promise { try { - const parsedBody = createNewtSchema.safeParse(req.body); if (!parsedBody.success) { return next( @@ -58,7 +57,7 @@ export async function createNewt( await db.insert(newts).values({ newtId: newtId, secretHash, - dateCreated: moment().toISOString(), + dateCreated: moment().toISOString() }); // give the newt their default permissions: @@ -75,12 +74,12 @@ export async function createNewt( data: { newtId, secret, - token, + token }, success: true, error: false, message: "Newt created successfully", - status: HttpCode.OK, + status: HttpCode.OK }); } catch (e) { if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { diff --git a/server/routers/newt/handleNewtPingRequestMessage.ts b/server/routers/newt/handleNewtPingRequestMessage.ts index fea157fdb..b75ddd5e4 100644 --- a/server/routers/newt/handleNewtPingRequestMessage.ts +++ b/server/routers/newt/handleNewtPingRequestMessage.ts @@ -35,7 +35,11 @@ export const handleNewtPingRequestMessage: MessageHandler = async (context) => { const { noCloud } = message.data; - const exitNodesList = await listExitNodes(site.orgId, true, noCloud || false); // filter for only the online ones + const exitNodesList = await listExitNodes( + site.orgId, + true, + noCloud || false + ); // filter for only the online ones let lastExitNodeId = null; if (newt.siteId) { diff --git a/server/routers/newt/handleNewtRegisterMessage.ts b/server/routers/newt/handleNewtRegisterMessage.ts index f4d963a13..77e49a202 100644 --- a/server/routers/newt/handleNewtRegisterMessage.ts +++ b/server/routers/newt/handleNewtRegisterMessage.ts @@ -255,7 +255,7 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { hcTimeout: targetHealthCheck.hcTimeout, hcHeaders: targetHealthCheck.hcHeaders, hcMethod: targetHealthCheck.hcMethod, - hcTlsServerName: targetHealthCheck.hcTlsServerName, + hcTlsServerName: targetHealthCheck.hcTlsServerName }) .from(targets) .innerJoin(resources, eq(targets.resourceId, resources.resourceId)) @@ -328,7 +328,7 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { hcTimeout: target.hcTimeout, // in seconds hcHeaders: hcHeadersSend, hcMethod: target.hcMethod, - hcTlsServerName: target.hcTlsServerName, + hcTlsServerName: target.hcTlsServerName }; }); @@ -366,7 +366,7 @@ async function getUniqueSubnetForSite( trx: Transaction | typeof db = db ): Promise { const lockKey = `subnet-allocation:${exitNode.exitNodeId}`; - + return await lockManager.withLock( lockKey, async () => { @@ -382,7 +382,8 @@ async function getUniqueSubnetForSite( .map((site) => site.subnet) .filter( (subnet) => - subnet && /^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/.test(subnet) + subnet && + /^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/.test(subnet) ) .filter((subnet) => subnet !== null); subnets.push(exitNode.address.replace(/\/\d+$/, `/${blockSize}`)); diff --git a/server/routers/newt/handleReceiveBandwidthMessage.ts b/server/routers/newt/handleReceiveBandwidthMessage.ts index f5170febb..3d060a0c1 100644 --- a/server/routers/newt/handleReceiveBandwidthMessage.ts +++ b/server/routers/newt/handleReceiveBandwidthMessage.ts @@ -10,7 +10,9 @@ interface PeerBandwidth { bytesOut: number; } -export const handleReceiveBandwidthMessage: MessageHandler = async (context) => { +export const handleReceiveBandwidthMessage: MessageHandler = async ( + context +) => { const { message, client, sendToClient } = context; if (!message.data.bandwidthData) { @@ -44,7 +46,7 @@ export const handleReceiveBandwidthMessage: MessageHandler = async (context) => .set({ megabytesOut: (client.megabytesIn || 0) + bytesIn, megabytesIn: (client.megabytesOut || 0) + bytesOut, - lastBandwidthUpdate: new Date().toISOString(), + lastBandwidthUpdate: new Date().toISOString() }) .where(eq(clients.clientId, client.clientId)); } diff --git a/server/routers/newt/handleSocketMessages.ts b/server/routers/newt/handleSocketMessages.ts index 09a473b99..f26f69c97 100644 --- a/server/routers/newt/handleSocketMessages.ts +++ b/server/routers/newt/handleSocketMessages.ts @@ -64,9 +64,5 @@ export const handleDockerContainersMessage: MessageHandler = async ( return; } - await applyNewtDockerBlueprint( - newt.siteId, - newt.newtId, - containers - ); + await applyNewtDockerBlueprint(newt.siteId, newt.newtId, containers); }; diff --git a/server/routers/newt/index.ts b/server/routers/newt/index.ts index 9642a6376..6b17f3249 100644 --- a/server/routers/newt/index.ts +++ b/server/routers/newt/index.ts @@ -5,4 +5,4 @@ export * from "./handleReceiveBandwidthMessage"; export * from "./handleGetConfigMessage"; export * from "./handleSocketMessages"; export * from "./handleNewtPingRequestMessage"; -export * from "./handleApplyBlueprintMessage"; \ No newline at end of file +export * from "./handleApplyBlueprintMessage"; diff --git a/server/routers/newt/peers.ts b/server/routers/newt/peers.ts index 694f0c0fe..c7546ff0d 100644 --- a/server/routers/newt/peers.ts +++ b/server/routers/newt/peers.ts @@ -48,7 +48,11 @@ export async function addPeer( return site; } -export async function deletePeer(siteId: number, publicKey: string, newtId?: string) { +export async function deletePeer( + siteId: number, + publicKey: string, + newtId?: string +) { let site: Site | null = null; if (!newtId) { [site] = await db diff --git a/server/routers/newt/targets.ts b/server/routers/newt/targets.ts index a5883f30c..e97aed35d 100644 --- a/server/routers/newt/targets.ts +++ b/server/routers/newt/targets.ts @@ -26,22 +26,32 @@ export async function addTargets( // Create a map for quick lookup const healthCheckMap = new Map(); - healthCheckData.forEach(hc => { + healthCheckData.forEach((hc) => { healthCheckMap.set(hc.targetId, hc); }); const healthCheckTargets = targets.map((target) => { const hc = healthCheckMap.get(target.targetId); - + // If no health check data found, skip this target if (!hc) { - logger.warn(`No health check configuration found for target ${target.targetId}`); + logger.warn( + `No health check configuration found for target ${target.targetId}` + ); return null; } // Ensure all necessary fields are present - if (!hc.hcPath || !hc.hcHostname || !hc.hcPort || !hc.hcInterval || !hc.hcMethod) { - logger.debug(`Skipping target ${target.targetId} due to missing health check fields`); + if ( + !hc.hcPath || + !hc.hcHostname || + !hc.hcPort || + !hc.hcInterval || + !hc.hcMethod + ) { + logger.debug( + `Skipping target ${target.targetId} due to missing health check fields` + ); return null; // Skip targets with missing health check fields } @@ -49,9 +59,11 @@ export async function addTargets( const hcHeadersSend: { [key: string]: string } = {}; if (hcHeadersParse) { // transform - hcHeadersParse.forEach((header: { name: string; value: string }) => { - hcHeadersSend[header.name] = header.value; - }); + hcHeadersParse.forEach( + (header: { name: string; value: string }) => { + hcHeadersSend[header.name] = header.value; + } + ); } // try to parse the hcStatus into a int and if not possible set to undefined @@ -77,12 +89,14 @@ export async function addTargets( hcHeaders: hcHeadersSend, hcMethod: hc.hcMethod, hcStatus: hcStatus, - hcTlsServerName: hc.hcTlsServerName, + hcTlsServerName: hc.hcTlsServerName }; }); // Filter out any null values from health check targets - const validHealthCheckTargets = healthCheckTargets.filter((target) => target !== null); + const validHealthCheckTargets = healthCheckTargets.filter( + (target) => target !== null + ); await sendToClient(newtId, { type: `newt/healthcheck/add`, diff --git a/server/routers/olm/createOlm.ts b/server/routers/olm/createOlm.ts index 930c04be1..b5da405e6 100644 --- a/server/routers/olm/createOlm.ts +++ b/server/routers/olm/createOlm.ts @@ -24,9 +24,9 @@ export type CreateNewtResponse = { }; const createNewtSchema = z.strictObject({ - newtId: z.string(), - secret: z.string() - }); + newtId: z.string(), + secret: z.string() +}); export async function createNewt( req: Request, @@ -34,7 +34,6 @@ export async function createNewt( next: NextFunction ): Promise { try { - const parsedBody = createNewtSchema.safeParse(req.body); if (!parsedBody.success) { return next( @@ -58,7 +57,7 @@ export async function createNewt( await db.insert(newts).values({ newtId: newtId, secretHash, - dateCreated: moment().toISOString(), + dateCreated: moment().toISOString() }); // give the newt their default permissions: @@ -75,12 +74,12 @@ export async function createNewt( data: { newtId, secret, - token, + token }, success: true, error: false, message: "Newt created successfully", - status: HttpCode.OK, + status: HttpCode.OK }); } catch (e) { if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { diff --git a/server/routers/olm/handleOlmPingMessage.ts b/server/routers/olm/handleOlmPingMessage.ts index 35d704c7b..0fa490c82 100644 --- a/server/routers/olm/handleOlmPingMessage.ts +++ b/server/routers/olm/handleOlmPingMessage.ts @@ -61,9 +61,12 @@ export const startOlmOfflineChecker = (): void => { // Send a disconnect message to the client if connected try { - await sendTerminateClient(offlineClient.clientId, offlineClient.olmId); // terminate first + await sendTerminateClient( + offlineClient.clientId, + offlineClient.olmId + ); // terminate first // wait a moment to ensure the message is sent - await new Promise(resolve => setTimeout(resolve, 1000)); + await new Promise((resolve) => setTimeout(resolve, 1000)); await disconnectClient(offlineClient.olmId); } catch (error) { logger.error( diff --git a/server/routers/olm/handleOlmServerPeerAddMessage.ts b/server/routers/olm/handleOlmServerPeerAddMessage.ts index c0556b0ee..53f3474ce 100644 --- a/server/routers/olm/handleOlmServerPeerAddMessage.ts +++ b/server/routers/olm/handleOlmServerPeerAddMessage.ts @@ -113,14 +113,14 @@ export const handleOlmServerPeerAddMessage: MessageHandler = async ( .select() .from(clientSitesAssociationsCache) .where( - and( + and( eq(clientSitesAssociationsCache.clientId, client.clientId), isNotNull(clientSitesAssociationsCache.endpoint), eq(clientSitesAssociationsCache.publicKey, client.pubKey) // limit it to the current session its connected with otherwise the endpoint could be stale ) ); - // pick an endpoint + // pick an endpoint for (const assoc of currentSessionSiteAssociationCaches) { if (assoc.endpoint) { endpoint = assoc.endpoint; diff --git a/server/routers/olm/index.ts b/server/routers/olm/index.ts index e671dd425..594ef9cbd 100644 --- a/server/routers/olm/index.ts +++ b/server/routers/olm/index.ts @@ -8,4 +8,4 @@ export * from "./listUserOlms"; export * from "./deleteUserOlm"; export * from "./getUserOlm"; export * from "./handleOlmServerPeerAddMessage"; -export * from "./handleOlmUnRelayMessage"; \ No newline at end of file +export * from "./handleOlmUnRelayMessage"; diff --git a/server/routers/org/checkId.ts b/server/routers/org/checkId.ts index 2a898c30b..f11809d20 100644 --- a/server/routers/org/checkId.ts +++ b/server/routers/org/checkId.ts @@ -10,8 +10,8 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; const getOrgSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); export async function checkId( req: Request, diff --git a/server/routers/org/getOrg.ts b/server/routers/org/getOrg.ts index 38a1c6ba6..a30dcc1ce 100644 --- a/server/routers/org/getOrg.ts +++ b/server/routers/org/getOrg.ts @@ -11,8 +11,8 @@ import { fromZodError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; const getOrgSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); export type GetOrgResponse = { org: Org; diff --git a/server/routers/org/getOrgOverview.ts b/server/routers/org/getOrgOverview.ts index dc704d6a8..d368d1b3c 100644 --- a/server/routers/org/getOrgOverview.ts +++ b/server/routers/org/getOrgOverview.ts @@ -19,8 +19,8 @@ import logger from "@server/logger"; import { fromZodError } from "zod-validation-error"; const getOrgParamsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); export type GetOrgOverviewResponse = { orgName: string; diff --git a/server/routers/org/updateOrg.ts b/server/routers/org/updateOrg.ts index 6e7a9b35c..aa9e2151a 100644 --- a/server/routers/org/updateOrg.ts +++ b/server/routers/org/updateOrg.ts @@ -16,10 +16,11 @@ import { TierId } from "@server/lib/billing/tiers"; import { cache } from "@server/lib/cache"; const updateOrgParamsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); -const updateOrgBodySchema = z.strictObject({ +const updateOrgBodySchema = z + .strictObject({ name: z.string().min(1).max(255).optional(), requireTwoFactor: z.boolean().optional(), maxSessionLengthHours: z.number().nullable().optional(), diff --git a/server/routers/orgIdp/types.ts b/server/routers/orgIdp/types.ts index a8e205cc6..f6f581eed 100644 --- a/server/routers/orgIdp/types.ts +++ b/server/routers/orgIdp/types.ts @@ -6,10 +6,10 @@ export type CreateOrgIdpResponse = { }; export type GetOrgIdpResponse = { - idp: Idp, - idpOidcConfig: IdpOidcConfig | null, - redirectUrl: string -} + idp: Idp; + idpOidcConfig: IdpOidcConfig | null; + redirectUrl: string; +}; export type ListOrgIdpsResponse = { idps: { @@ -18,7 +18,7 @@ export type ListOrgIdpsResponse = { name: string; type: string; variant: string; - }[], + }[]; pagination: { total: number; limit: number; diff --git a/server/routers/remoteExitNode/types.ts b/server/routers/remoteExitNode/types.ts index 55d0a2862..25a7d6c53 100644 --- a/server/routers/remoteExitNode/types.ts +++ b/server/routers/remoteExitNode/types.ts @@ -31,4 +31,14 @@ export type ListRemoteExitNodesResponse = { pagination: { total: number; limit: number; offset: number }; }; -export type GetRemoteExitNodeResponse = { remoteExitNodeId: string; dateCreated: string; version: string | null; exitNodeId: number | null; name: string; address: string; endpoint: string; online: boolean; type: string | null; } \ No newline at end of file +export type GetRemoteExitNodeResponse = { + remoteExitNodeId: string; + dateCreated: string; + version: string | null; + exitNodeId: number | null; + name: string; + address: string; + endpoint: string; + online: boolean; + type: string | null; +}; diff --git a/server/routers/resource/addEmailToResourceWhitelist.ts b/server/routers/resource/addEmailToResourceWhitelist.ts index f9cee8382..53828b44c 100644 --- a/server/routers/resource/addEmailToResourceWhitelist.ts +++ b/server/routers/resource/addEmailToResourceWhitelist.ts @@ -11,21 +11,19 @@ import { and, eq } from "drizzle-orm"; import { OpenAPITags, registry } from "@server/openApi"; const addEmailToResourceWhitelistBodySchema = z.strictObject({ - email: z.email() - .or( - z.string().regex(/^\*@[\w.-]+\.[a-zA-Z]{2,}$/, { - error: "Invalid email address. Wildcard (*) must be the entire local part." - }) - ) - .transform((v) => v.toLowerCase()) - }); + email: z + .email() + .or( + z.string().regex(/^\*@[\w.-]+\.[a-zA-Z]{2,}$/, { + error: "Invalid email address. Wildcard (*) must be the entire local part." + }) + ) + .transform((v) => v.toLowerCase()) +}); const addEmailToResourceWhitelistParamsSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); registry.registerPath({ method: "post", diff --git a/server/routers/resource/addRoleToResource.ts b/server/routers/resource/addRoleToResource.ts index c29f27573..ba344c6c0 100644 --- a/server/routers/resource/addRoleToResource.ts +++ b/server/routers/resource/addRoleToResource.ts @@ -93,10 +93,7 @@ export async function addRoleToResource( .select() .from(roles) .where( - and( - eq(roles.roleId, roleId), - eq(roles.orgId, resource.orgId) - ) + and(eq(roles.roleId, roleId), eq(roles.orgId, resource.orgId)) ) .limit(1); @@ -158,4 +155,3 @@ export async function addRoleToResource( ); } } - diff --git a/server/routers/resource/addUserToResource.ts b/server/routers/resource/addUserToResource.ts index 6dbfe086b..ee6081ff8 100644 --- a/server/routers/resource/addUserToResource.ts +++ b/server/routers/resource/addUserToResource.ts @@ -127,4 +127,3 @@ export async function addUserToResource( ); } } - diff --git a/server/routers/resource/authWithAccessToken.ts b/server/routers/resource/authWithAccessToken.ts index 81ca7fbc3..53f72cb21 100644 --- a/server/routers/resource/authWithAccessToken.ts +++ b/server/routers/resource/authWithAccessToken.ts @@ -16,17 +16,17 @@ import stoi from "@server/lib/stoi"; import { logAccessAudit } from "#dynamic/lib/logAccessAudit"; const authWithAccessTokenBodySchema = z.strictObject({ - accessToken: z.string(), - accessTokenId: z.string().optional() - }); + accessToken: z.string(), + accessTokenId: z.string().optional() +}); const authWithAccessTokenParamsSchema = z.strictObject({ - resourceId: z - .string() - .optional() - .transform(stoi) - .pipe(z.int().positive().optional()) - }); + resourceId: z + .string() + .optional() + .transform(stoi) + .pipe(z.int().positive().optional()) +}); export type AuthWithAccessTokenResponse = { session?: string; diff --git a/server/routers/resource/authWithPassword.ts b/server/routers/resource/authWithPassword.ts index 4c1f20582..ecf61896b 100644 --- a/server/routers/resource/authWithPassword.ts +++ b/server/routers/resource/authWithPassword.ts @@ -16,15 +16,12 @@ import config from "@server/lib/config"; import { logAccessAudit } from "#dynamic/lib/logAccessAudit"; export const authWithPasswordBodySchema = z.strictObject({ - password: z.string() - }); + password: z.string() +}); export const authWithPasswordParamsSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); export type AuthWithPasswordResponse = { session?: string; diff --git a/server/routers/resource/authWithPincode.ts b/server/routers/resource/authWithPincode.ts index 59f80ee01..78e132d20 100644 --- a/server/routers/resource/authWithPincode.ts +++ b/server/routers/resource/authWithPincode.ts @@ -15,15 +15,12 @@ import config from "@server/lib/config"; import { logAccessAudit } from "#dynamic/lib/logAccessAudit"; export const authWithPincodeBodySchema = z.strictObject({ - pincode: z.string() - }); + pincode: z.string() +}); export const authWithPincodeParamsSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); export type AuthWithPincodeResponse = { session?: string; diff --git a/server/routers/resource/authWithWhitelist.ts b/server/routers/resource/authWithWhitelist.ts index 11f840430..6a2b7ee76 100644 --- a/server/routers/resource/authWithWhitelist.ts +++ b/server/routers/resource/authWithWhitelist.ts @@ -15,16 +15,13 @@ import config from "@server/lib/config"; import { logAccessAudit } from "#dynamic/lib/logAccessAudit"; const authWithWhitelistBodySchema = z.strictObject({ - email: z.email().toLowerCase(), - otp: z.string().optional() - }); + email: z.email().toLowerCase(), + otp: z.string().optional() +}); const authWithWhitelistParamsSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); export type AuthWithWhitelistResponse = { otpSent?: boolean; diff --git a/server/routers/resource/createResource.ts b/server/routers/resource/createResource.ts index b9ab3ce56..ba1fdba23 100644 --- a/server/routers/resource/createResource.ts +++ b/server/routers/resource/createResource.ts @@ -26,16 +26,17 @@ import { getUniqueResourceName } from "@server/db/names"; import { validateAndConstructDomain } from "@server/lib/domainUtils"; const createResourceParamsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); -const createHttpResourceSchema = z.strictObject({ +const createHttpResourceSchema = z + .strictObject({ name: z.string().min(1).max(255), subdomain: z.string().nullable().optional(), http: z.boolean(), protocol: z.enum(["tcp", "udp"]), domainId: z.string(), - stickySession: z.boolean().optional(), + stickySession: z.boolean().optional() }) .refine( (data) => { @@ -49,7 +50,8 @@ const createHttpResourceSchema = z.strictObject({ } ); -const createRawResourceSchema = z.strictObject({ +const createRawResourceSchema = z + .strictObject({ name: z.string().min(1).max(255), http: z.boolean(), protocol: z.enum(["tcp", "udp"]), @@ -188,7 +190,7 @@ async function createHttpResource( const { name, domainId } = parsedBody.data; const subdomain = parsedBody.data.subdomain; - const stickySession=parsedBody.data.stickySession; + const stickySession = parsedBody.data.stickySession; // Validate domain and construct full domain const domainResult = await validateAndConstructDomain( diff --git a/server/routers/resource/createResourceRule.ts b/server/routers/resource/createResourceRule.ts index c3e086b0e..3f86665b6 100644 --- a/server/routers/resource/createResourceRule.ts +++ b/server/routers/resource/createResourceRule.ts @@ -16,19 +16,16 @@ import { import { OpenAPITags, registry } from "@server/openApi"; const createResourceRuleSchema = z.strictObject({ - action: z.enum(["ACCEPT", "DROP", "PASS"]), - match: z.enum(["CIDR", "IP", "PATH", "COUNTRY"]), - value: z.string().min(1), - priority: z.int(), - enabled: z.boolean().optional() - }); + action: z.enum(["ACCEPT", "DROP", "PASS"]), + match: z.enum(["CIDR", "IP", "PATH", "COUNTRY"]), + value: z.string().min(1), + priority: z.int(), + enabled: z.boolean().optional() +}); const createResourceRuleParamsSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); registry.registerPath({ method: "put", diff --git a/server/routers/resource/deleteResource.ts b/server/routers/resource/deleteResource.ts index a81208a56..d8891d75d 100644 --- a/server/routers/resource/deleteResource.ts +++ b/server/routers/resource/deleteResource.ts @@ -15,11 +15,8 @@ import { OpenAPITags, registry } from "@server/openApi"; // Define Zod schema for request parameters validation const deleteResourceSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); registry.registerPath({ method: "delete", diff --git a/server/routers/resource/deleteResourceRule.ts b/server/routers/resource/deleteResourceRule.ts index 58cb7b486..638f2e1de 100644 --- a/server/routers/resource/deleteResourceRule.ts +++ b/server/routers/resource/deleteResourceRule.ts @@ -11,12 +11,9 @@ import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; const deleteResourceRuleSchema = z.strictObject({ - ruleId: z.string().transform(Number).pipe(z.int().positive()), - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + ruleId: z.string().transform(Number).pipe(z.int().positive()), + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); registry.registerPath({ method: "delete", diff --git a/server/routers/resource/getExchangeToken.ts b/server/routers/resource/getExchangeToken.ts index 8a0276a03..b0af4b7ff 100644 --- a/server/routers/resource/getExchangeToken.ts +++ b/server/routers/resource/getExchangeToken.ts @@ -17,11 +17,8 @@ import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy"; import { logAccessAudit } from "#dynamic/lib/logAccessAudit"; const getExchangeTokenParams = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); export type GetExchangeTokenResponse = { requestToken: string; diff --git a/server/routers/resource/getResource.ts b/server/routers/resource/getResource.ts index f2ce559e9..7f3e8a0ea 100644 --- a/server/routers/resource/getResource.ts +++ b/server/routers/resource/getResource.ts @@ -12,15 +12,15 @@ import stoi from "@server/lib/stoi"; import { OpenAPITags, registry } from "@server/openApi"; const getResourceSchema = z.strictObject({ - resourceId: z - .string() - .optional() - .transform(stoi) - .pipe(z.int().positive().optional()) - .optional(), - niceId: z.string().optional(), - orgId: z.string().optional() - }); + resourceId: z + .string() + .optional() + .transform(stoi) + .pipe(z.int().positive().optional()) + .optional(), + niceId: z.string().optional(), + orgId: z.string().optional() +}); async function query(resourceId?: number, niceId?: string, orgId?: string) { if (resourceId) { @@ -34,13 +34,18 @@ async function query(resourceId?: number, niceId?: string, orgId?: string) { const [res] = await db .select() .from(resources) - .where(and(eq(resources.niceId, niceId), eq(resources.orgId, orgId))) + .where( + and(eq(resources.niceId, niceId), eq(resources.orgId, orgId)) + ) .limit(1); return res; } } -export type GetResourceResponse = Omit>>, 'headers'> & { +export type GetResourceResponse = Omit< + NonNullable>>, + "headers" +> & { headers: { name: string; value: string }[] | null; }; @@ -101,7 +106,9 @@ export async function getResource( return response(res, { data: { ...resource, - headers: resource.headers ? JSON.parse(resource.headers) : resource.headers + headers: resource.headers + ? JSON.parse(resource.headers) + : resource.headers }, success: true, error: false, diff --git a/server/routers/resource/getResourceAuthInfo.ts b/server/routers/resource/getResourceAuthInfo.ts index 60f8e5862..fe0a38c81 100644 --- a/server/routers/resource/getResourceAuthInfo.ts +++ b/server/routers/resource/getResourceAuthInfo.ts @@ -16,8 +16,8 @@ import logger from "@server/logger"; import { build } from "@server/build"; const getResourceAuthInfoSchema = z.strictObject({ - resourceGuid: z.string() - }); + resourceGuid: z.string() +}); export type GetResourceAuthInfoResponse = { resourceId: number; diff --git a/server/routers/resource/getResourceWhitelist.ts b/server/routers/resource/getResourceWhitelist.ts index 3171352a7..52cff0c72 100644 --- a/server/routers/resource/getResourceWhitelist.ts +++ b/server/routers/resource/getResourceWhitelist.ts @@ -11,11 +11,8 @@ import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; const getResourceWhitelistSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); async function queryWhitelist(resourceId: number) { return await db diff --git a/server/routers/resource/listResourceRoles.ts b/server/routers/resource/listResourceRoles.ts index 3dbb8c0d6..68dc58a21 100644 --- a/server/routers/resource/listResourceRoles.ts +++ b/server/routers/resource/listResourceRoles.ts @@ -11,11 +11,8 @@ import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; const listResourceRolesSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); async function query(resourceId: number) { return await db diff --git a/server/routers/resource/listResourceRules.ts b/server/routers/resource/listResourceRules.ts index bc2516a06..dae7922d9 100644 --- a/server/routers/resource/listResourceRules.ts +++ b/server/routers/resource/listResourceRules.ts @@ -11,11 +11,8 @@ import logger from "@server/logger"; import { OpenAPITags, registry } from "@server/openApi"; const listResourceRulesParamsSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); const listResourceRulesSchema = z.object({ limit: z diff --git a/server/routers/resource/listResourceUsers.ts b/server/routers/resource/listResourceUsers.ts index b07bcf0aa..e7f73287e 100644 --- a/server/routers/resource/listResourceUsers.ts +++ b/server/routers/resource/listResourceUsers.ts @@ -11,11 +11,8 @@ import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; const listResourceUsersSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); async function queryUsers(resourceId: number) { return await db diff --git a/server/routers/resource/removeEmailFromResourceWhitelist.ts b/server/routers/resource/removeEmailFromResourceWhitelist.ts index c2cac2dea..d60133b85 100644 --- a/server/routers/resource/removeEmailFromResourceWhitelist.ts +++ b/server/routers/resource/removeEmailFromResourceWhitelist.ts @@ -11,21 +11,19 @@ import { and, eq } from "drizzle-orm"; import { OpenAPITags, registry } from "@server/openApi"; const removeEmailFromResourceWhitelistBodySchema = z.strictObject({ - email: z.email() - .or( - z.string().regex(/^\*@[\w.-]+\.[a-zA-Z]{2,}$/, { - error: "Invalid email address. Wildcard (*) must be the entire local part." - }) - ) - .transform((v) => v.toLowerCase()) - }); + email: z + .email() + .or( + z.string().regex(/^\*@[\w.-]+\.[a-zA-Z]{2,}$/, { + error: "Invalid email address. Wildcard (*) must be the entire local part." + }) + ) + .transform((v) => v.toLowerCase()) +}); const removeEmailFromResourceWhitelistParamsSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); registry.registerPath({ method: "post", diff --git a/server/routers/resource/removeRoleFromResource.ts b/server/routers/resource/removeRoleFromResource.ts index cb44ac4a8..eab7660c3 100644 --- a/server/routers/resource/removeRoleFromResource.ts +++ b/server/routers/resource/removeRoleFromResource.ts @@ -49,9 +49,7 @@ export async function removeRoleFromResource( next: NextFunction ): Promise { try { - const parsedBody = removeRoleFromResourceBodySchema.safeParse( - req.body - ); + const parsedBody = removeRoleFromResourceBodySchema.safeParse(req.body); if (!parsedBody.success) { return next( createHttpError( @@ -95,10 +93,7 @@ export async function removeRoleFromResource( .select() .from(roles) .where( - and( - eq(roles.roleId, roleId), - eq(roles.orgId, resource.orgId) - ) + and(eq(roles.roleId, roleId), eq(roles.orgId, resource.orgId)) ) .limit(1); @@ -163,4 +158,3 @@ export async function removeRoleFromResource( ); } } - diff --git a/server/routers/resource/removeUserFromResource.ts b/server/routers/resource/removeUserFromResource.ts index 8dce7e48d..9da96d3c8 100644 --- a/server/routers/resource/removeUserFromResource.ts +++ b/server/routers/resource/removeUserFromResource.ts @@ -49,9 +49,7 @@ export async function removeUserFromResource( next: NextFunction ): Promise { try { - const parsedBody = removeUserFromResourceBodySchema.safeParse( - req.body - ); + const parsedBody = removeUserFromResourceBodySchema.safeParse(req.body); if (!parsedBody.success) { return next( createHttpError( @@ -133,4 +131,3 @@ export async function removeUserFromResource( ); } } - diff --git a/server/routers/resource/setResourceHeaderAuth.ts b/server/routers/resource/setResourceHeaderAuth.ts index 87ffbacd5..b89179ae4 100644 --- a/server/routers/resource/setResourceHeaderAuth.ts +++ b/server/routers/resource/setResourceHeaderAuth.ts @@ -15,9 +15,9 @@ const setResourceAuthMethodsParamsSchema = z.object({ }); const setResourceAuthMethodsBodySchema = z.strictObject({ - user: z.string().min(4).max(100).nullable(), - password: z.string().min(4).max(100).nullable() - }); + user: z.string().min(4).max(100).nullable(), + password: z.string().min(4).max(100).nullable() +}); registry.registerPath({ method: "post", @@ -75,7 +75,9 @@ export async function setResourceHeaderAuth( .where(eq(resourceHeaderAuth.resourceId, resourceId)); if (user && password) { - const headerAuthHash = await hashPassword(Buffer.from(`${user}:${password}`).toString("base64")); + const headerAuthHash = await hashPassword( + Buffer.from(`${user}:${password}`).toString("base64") + ); await trx .insert(resourceHeaderAuth) diff --git a/server/routers/resource/setResourcePassword.ts b/server/routers/resource/setResourcePassword.ts index 3f9ce9f17..9bd845a4e 100644 --- a/server/routers/resource/setResourcePassword.ts +++ b/server/routers/resource/setResourcePassword.ts @@ -17,8 +17,8 @@ const setResourceAuthMethodsParamsSchema = z.object({ }); const setResourceAuthMethodsBodySchema = z.strictObject({ - password: z.string().min(4).max(100).nullable() - }); + password: z.string().min(4).max(100).nullable() +}); registry.registerPath({ method: "post", diff --git a/server/routers/resource/setResourcePincode.ts b/server/routers/resource/setResourcePincode.ts index 6a88a2798..0d5272731 100644 --- a/server/routers/resource/setResourcePincode.ts +++ b/server/routers/resource/setResourcePincode.ts @@ -18,11 +18,11 @@ const setResourceAuthMethodsParamsSchema = z.object({ }); const setResourceAuthMethodsBodySchema = z.strictObject({ - pincode: z - .string() - .regex(/^\d{6}$/) - .or(z.null()) - }); + pincode: z + .string() + .regex(/^\d{6}$/) + .or(z.null()) +}); registry.registerPath({ method: "post", diff --git a/server/routers/resource/setResourceRoles.ts b/server/routers/resource/setResourceRoles.ts index 5064c7e0a..751fe4f91 100644 --- a/server/routers/resource/setResourceRoles.ts +++ b/server/routers/resource/setResourceRoles.ts @@ -11,15 +11,12 @@ import { eq, and, ne, inArray } from "drizzle-orm"; import { OpenAPITags, registry } from "@server/openApi"; const setResourceRolesBodySchema = z.strictObject({ - roleIds: z.array(z.int().positive()) - }); + roleIds: z.array(z.int().positive()) +}); const setResourceRolesParamsSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); registry.registerPath({ method: "post", @@ -113,10 +110,7 @@ export async function setResourceRoles( .select() .from(roles) .where( - and( - eq(roles.isAdmin, true), - eq(roles.orgId, resource.orgId) - ) + and(eq(roles.isAdmin, true), eq(roles.orgId, resource.orgId)) ); const adminRoleIds = adminRoles.map((role) => role.roleId); @@ -129,9 +123,9 @@ export async function setResourceRoles( ) ); } else { - await trx.delete(roleResources).where( - eq(roleResources.resourceId, resourceId) - ); + await trx + .delete(roleResources) + .where(eq(roleResources.resourceId, resourceId)); } const newRoleResources = await Promise.all( @@ -158,4 +152,3 @@ export async function setResourceRoles( ); } } - diff --git a/server/routers/resource/setResourceUsers.ts b/server/routers/resource/setResourceUsers.ts index b5eca17c7..5ddceb8f0 100644 --- a/server/routers/resource/setResourceUsers.ts +++ b/server/routers/resource/setResourceUsers.ts @@ -11,15 +11,12 @@ import { eq } from "drizzle-orm"; import { OpenAPITags, registry } from "@server/openApi"; const setUserResourcesBodySchema = z.strictObject({ - userIds: z.array(z.string()) - }); + userIds: z.array(z.string()) +}); const setUserResourcesParamsSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); registry.registerPath({ method: "post", diff --git a/server/routers/resource/setResourceWhitelist.ts b/server/routers/resource/setResourceWhitelist.ts index 417ef6d93..18f612f24 100644 --- a/server/routers/resource/setResourceWhitelist.ts +++ b/server/routers/resource/setResourceWhitelist.ts @@ -11,25 +11,21 @@ import { and, eq } from "drizzle-orm"; import { OpenAPITags, registry } from "@server/openApi"; const setResourceWhitelistBodySchema = z.strictObject({ - emails: z - .array( - z.email() - .or( - z.string().regex(/^\*@[\w.-]+\.[a-zA-Z]{2,}$/, { - error: "Invalid email address. Wildcard (*) must be the entire local part." - }) - ) + emails: z + .array( + z.email().or( + z.string().regex(/^\*@[\w.-]+\.[a-zA-Z]{2,}$/, { + error: "Invalid email address. Wildcard (*) must be the entire local part." + }) ) - .max(50) - .transform((v) => v.map((e) => e.toLowerCase())) - }); + ) + .max(50) + .transform((v) => v.map((e) => e.toLowerCase())) +}); const setResourceWhitelistParamsSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); registry.registerPath({ method: "post", diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index f3792e284..1dff9757f 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -26,13 +26,11 @@ import { validateHeaders } from "@server/lib/validators"; import { build } from "@server/build"; const updateResourceParamsSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); -const updateHttpResourceBodySchema = z.strictObject({ +const updateHttpResourceBodySchema = z + .strictObject({ name: z.string().min(1).max(255).optional(), niceId: z.string().min(1).max(255).optional(), subdomain: subdomainSchema.nullable().optional(), @@ -91,7 +89,8 @@ const updateHttpResourceBodySchema = z.strictObject({ export type UpdateResourceResponse = Resource; -const updateRawResourceBodySchema = z.strictObject({ +const updateRawResourceBodySchema = z + .strictObject({ name: z.string().min(1).max(255).optional(), niceId: z.string().min(1).max(255).optional(), proxyPort: z.int().min(1).max(65535).optional(), @@ -239,11 +238,11 @@ async function updateHttpResource( .select() .from(resources) .where( - and( - eq(resources.niceId, updateData.niceId), - eq(resources.orgId, resource.orgId) - ) - ); + and( + eq(resources.niceId, updateData.niceId), + eq(resources.orgId, resource.orgId) + ) + ); if ( existingResource && @@ -391,11 +390,11 @@ async function updateRawResource( .select() .from(resources) .where( - and( - eq(resources.niceId, updateData.niceId), - eq(resources.orgId, resource.orgId) - ) - ); + and( + eq(resources.niceId, updateData.niceId), + eq(resources.orgId, resource.orgId) + ) + ); if ( existingResource && diff --git a/server/routers/resource/updateResourceRule.ts b/server/routers/resource/updateResourceRule.ts index b92c3d075..cae3f16e9 100644 --- a/server/routers/resource/updateResourceRule.ts +++ b/server/routers/resource/updateResourceRule.ts @@ -17,15 +17,13 @@ import { OpenAPITags, registry } from "@server/openApi"; // Define Zod schema for request parameters validation const updateResourceRuleParamsSchema = z.strictObject({ - ruleId: z.string().transform(Number).pipe(z.int().positive()), - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + ruleId: z.string().transform(Number).pipe(z.int().positive()), + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); // Define Zod schema for request body validation -const updateResourceRuleSchema = z.strictObject({ +const updateResourceRuleSchema = z + .strictObject({ action: z.enum(["ACCEPT", "DROP", "PASS"]).optional(), match: z.enum(["CIDR", "IP", "PATH", "COUNTRY"]).optional(), value: z.string().min(1).optional(), diff --git a/server/routers/role/addRoleAction.ts b/server/routers/role/addRoleAction.ts index 74540b78e..5c258de7e 100644 --- a/server/routers/role/addRoleAction.ts +++ b/server/routers/role/addRoleAction.ts @@ -10,12 +10,12 @@ import { eq } from "drizzle-orm"; import { fromError } from "zod-validation-error"; const addRoleActionParamSchema = z.strictObject({ - roleId: z.string().transform(Number).pipe(z.int().positive()) - }); + roleId: z.string().transform(Number).pipe(z.int().positive()) +}); const addRoleActionSchema = z.strictObject({ - actionId: z.string() - }); + actionId: z.string() +}); export async function addRoleAction( req: Request, diff --git a/server/routers/role/addRoleSite.ts b/server/routers/role/addRoleSite.ts index d33c733db..ddd1f07e7 100644 --- a/server/routers/role/addRoleSite.ts +++ b/server/routers/role/addRoleSite.ts @@ -10,12 +10,12 @@ import { eq } from "drizzle-orm"; import { fromError } from "zod-validation-error"; const addRoleSiteParamsSchema = z.strictObject({ - roleId: z.string().transform(Number).pipe(z.int().positive()) - }); + roleId: z.string().transform(Number).pipe(z.int().positive()) +}); const addRoleSiteSchema = z.strictObject({ - siteId: z.string().transform(Number).pipe(z.int().positive()) - }); + siteId: z.string().transform(Number).pipe(z.int().positive()) +}); export async function addRoleSite( req: Request, diff --git a/server/routers/role/createRole.ts b/server/routers/role/createRole.ts index 26573c6ce..16696af49 100644 --- a/server/routers/role/createRole.ts +++ b/server/routers/role/createRole.ts @@ -12,13 +12,13 @@ import { eq, and } from "drizzle-orm"; import { OpenAPITags, registry } from "@server/openApi"; const createRoleParamsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); const createRoleSchema = z.strictObject({ - name: z.string().min(1).max(255), - description: z.string().optional() - }); + name: z.string().min(1).max(255), + description: z.string().optional() +}); export const defaultRoleAllowedActions: ActionsEnum[] = [ ActionsEnum.getOrg, diff --git a/server/routers/role/deleteRole.ts b/server/routers/role/deleteRole.ts index e4d89b2fa..490fe91cc 100644 --- a/server/routers/role/deleteRole.ts +++ b/server/routers/role/deleteRole.ts @@ -11,12 +11,12 @@ import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; const deleteRoleSchema = z.strictObject({ - roleId: z.string().transform(Number).pipe(z.int().positive()) - }); + roleId: z.string().transform(Number).pipe(z.int().positive()) +}); const deelteRoleBodySchema = z.strictObject({ - roleId: z.string().transform(Number).pipe(z.int().positive()) - }); + roleId: z.string().transform(Number).pipe(z.int().positive()) +}); registry.registerPath({ method: "delete", diff --git a/server/routers/role/getRole.ts b/server/routers/role/getRole.ts index afd6e83a5..a5c459968 100644 --- a/server/routers/role/getRole.ts +++ b/server/routers/role/getRole.ts @@ -11,8 +11,8 @@ import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; const getRoleSchema = z.strictObject({ - roleId: z.string().transform(Number).pipe(z.int().positive()) - }); + roleId: z.string().transform(Number).pipe(z.int().positive()) +}); registry.registerPath({ method: "get", diff --git a/server/routers/role/listRoleActions.ts b/server/routers/role/listRoleActions.ts index 8392c296d..31ef66044 100644 --- a/server/routers/role/listRoleActions.ts +++ b/server/routers/role/listRoleActions.ts @@ -10,8 +10,8 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; const listRoleActionsSchema = z.strictObject({ - roleId: z.string().transform(Number).pipe(z.int().positive()) - }); + roleId: z.string().transform(Number).pipe(z.int().positive()) +}); export async function listRoleActions( req: Request, diff --git a/server/routers/role/listRoleResources.ts b/server/routers/role/listRoleResources.ts index 57a84c5c7..7ba1fdab3 100644 --- a/server/routers/role/listRoleResources.ts +++ b/server/routers/role/listRoleResources.ts @@ -10,8 +10,8 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; const listRoleResourcesSchema = z.strictObject({ - roleId: z.string().transform(Number).pipe(z.int().positive()) - }); + roleId: z.string().transform(Number).pipe(z.int().positive()) +}); export async function listRoleResources( req: Request, diff --git a/server/routers/role/listRoleSites.ts b/server/routers/role/listRoleSites.ts index f35e367c3..1c9dcdbe1 100644 --- a/server/routers/role/listRoleSites.ts +++ b/server/routers/role/listRoleSites.ts @@ -10,8 +10,8 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; const listRoleSitesSchema = z.strictObject({ - roleId: z.string().transform(Number).pipe(z.int().positive()) - }); + roleId: z.string().transform(Number).pipe(z.int().positive()) +}); export async function listRoleSites( req: Request, diff --git a/server/routers/role/listRoles.ts b/server/routers/role/listRoles.ts index 14a5c2d13..288a540d1 100644 --- a/server/routers/role/listRoles.ts +++ b/server/routers/role/listRoles.ts @@ -12,8 +12,8 @@ import stoi from "@server/lib/stoi"; import { OpenAPITags, registry } from "@server/openApi"; const listRolesParamsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); const listRolesSchema = z.object({ limit: z diff --git a/server/routers/role/removeRoleAction.ts b/server/routers/role/removeRoleAction.ts index 25fbaa29c..3c2ee7884 100644 --- a/server/routers/role/removeRoleAction.ts +++ b/server/routers/role/removeRoleAction.ts @@ -10,12 +10,12 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; const removeRoleActionParamsSchema = z.strictObject({ - roleId: z.string().transform(Number).pipe(z.int().positive()) - }); + roleId: z.string().transform(Number).pipe(z.int().positive()) +}); const removeRoleActionSchema = z.strictObject({ - actionId: z.string() - }); + actionId: z.string() +}); export async function removeRoleAction( req: Request, diff --git a/server/routers/role/removeRoleResource.ts b/server/routers/role/removeRoleResource.ts index d2c7cae9b..fac1c941a 100644 --- a/server/routers/role/removeRoleResource.ts +++ b/server/routers/role/removeRoleResource.ts @@ -10,15 +10,12 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; const removeRoleResourceParamsSchema = z.strictObject({ - roleId: z.string().transform(Number).pipe(z.int().positive()) - }); + roleId: z.string().transform(Number).pipe(z.int().positive()) +}); const removeRoleResourceSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); export async function removeRoleResource( req: Request, diff --git a/server/routers/role/removeRoleSite.ts b/server/routers/role/removeRoleSite.ts index 8092eed1a..6c64820ea 100644 --- a/server/routers/role/removeRoleSite.ts +++ b/server/routers/role/removeRoleSite.ts @@ -10,12 +10,12 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; const removeRoleSiteParamsSchema = z.strictObject({ - roleId: z.string().transform(Number).pipe(z.int().positive()) - }); + roleId: z.string().transform(Number).pipe(z.int().positive()) +}); const removeRoleSiteSchema = z.strictObject({ - siteId: z.string().transform(Number).pipe(z.int().positive()) - }); + siteId: z.string().transform(Number).pipe(z.int().positive()) +}); export async function removeRoleSite( req: Request, diff --git a/server/routers/role/updateRole.ts b/server/routers/role/updateRole.ts index 136ca389d..c9f63a7b8 100644 --- a/server/routers/role/updateRole.ts +++ b/server/routers/role/updateRole.ts @@ -10,10 +10,11 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; const updateRoleParamsSchema = z.strictObject({ - roleId: z.string().transform(Number).pipe(z.int().positive()) - }); + roleId: z.string().transform(Number).pipe(z.int().positive()) +}); -const updateRoleBodySchema = z.strictObject({ +const updateRoleBodySchema = z + .strictObject({ name: z.string().min(1).max(255).optional(), description: z.string().optional() }) diff --git a/server/routers/site/createSite.ts b/server/routers/site/createSite.ts index 2ec8d3dcb..c798ea30c 100644 --- a/server/routers/site/createSite.ts +++ b/server/routers/site/createSite.ts @@ -20,25 +20,25 @@ import { verifyExitNodeOrgAccess } from "#dynamic/lib/exitNodes"; import { build } from "@server/build"; const createSiteParamsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); const createSiteSchema = z.strictObject({ - name: z.string().min(1).max(255), - exitNodeId: z.int().positive().optional(), - // subdomain: z - // .string() - // .min(1) - // .max(255) - // .transform((val) => val.toLowerCase()) - // .optional(), - pubKey: z.string().optional(), - subnet: z.string().optional(), - newtId: z.string().optional(), - secret: z.string().optional(), - address: z.string().optional(), - type: z.enum(["newt", "wireguard", "local"]) - }); + name: z.string().min(1).max(255), + exitNodeId: z.int().positive().optional(), + // subdomain: z + // .string() + // .min(1) + // .max(255) + // .transform((val) => val.toLowerCase()) + // .optional(), + pubKey: z.string().optional(), + subnet: z.string().optional(), + newtId: z.string().optional(), + secret: z.string().optional(), + address: z.string().optional(), + type: z.enum(["newt", "wireguard", "local"]) +}); // .refine((data) => { // if (data.type === "local") { // return !config.getRawConfig().flags?.disable_local_sites; diff --git a/server/routers/site/deleteSite.ts b/server/routers/site/deleteSite.ts index a086e1430..09750c31b 100644 --- a/server/routers/site/deleteSite.ts +++ b/server/routers/site/deleteSite.ts @@ -13,8 +13,8 @@ import { sendToClient } from "#dynamic/routers/ws"; import { OpenAPITags, registry } from "@server/openApi"; const deleteSiteSchema = z.strictObject({ - siteId: z.string().transform(Number).pipe(z.int().positive()) - }); + siteId: z.string().transform(Number).pipe(z.int().positive()) +}); registry.registerPath({ method: "delete", @@ -93,8 +93,11 @@ export async function deleteSite( data: {} }; // Don't await this to prevent blocking the response - sendToClient(deletedNewtId, payload).catch(error => { - logger.error("Failed to send termination message to newt:", error); + sendToClient(deletedNewtId, payload).catch((error) => { + logger.error( + "Failed to send termination message to newt:", + error + ); }); } diff --git a/server/routers/site/index.ts b/server/routers/site/index.ts index b97557a87..3edf67c14 100644 --- a/server/routers/site/index.ts +++ b/server/routers/site/index.ts @@ -5,4 +5,4 @@ export * from "./updateSite"; export * from "./listSites"; export * from "./listSiteRoles"; export * from "./pickSiteDefaults"; -export * from "./socketIntegration"; \ No newline at end of file +export * from "./socketIntegration"; diff --git a/server/routers/site/listSiteRoles.ts b/server/routers/site/listSiteRoles.ts index ec66d3c57..a2cacf1d1 100644 --- a/server/routers/site/listSiteRoles.ts +++ b/server/routers/site/listSiteRoles.ts @@ -10,8 +10,8 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; const listSiteRolesSchema = z.strictObject({ - siteId: z.string().transform(Number).pipe(z.int().positive()) - }); + siteId: z.string().transform(Number).pipe(z.int().positive()) +}); export async function listSiteRoles( req: Request, diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts index f08547644..37ca8fe48 100644 --- a/server/routers/site/listSites.ts +++ b/server/routers/site/listSites.ts @@ -69,8 +69,8 @@ async function getLatestNewtVersion(): Promise { } const listSitesParamsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); const listSitesSchema = z.object({ limit: z diff --git a/server/routers/site/pickSiteDefaults.ts b/server/routers/site/pickSiteDefaults.ts index 029ae322a..69ed76886 100644 --- a/server/routers/site/pickSiteDefaults.ts +++ b/server/routers/site/pickSiteDefaults.ts @@ -45,8 +45,8 @@ registry.registerPath({ }); const pickSiteDefaultsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); export async function pickSiteDefaults( req: Request, @@ -74,7 +74,10 @@ export async function pickSiteDefaults( if (!randomExitNode) { return next( - createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "No available exit node") + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "No available exit node" + ) ); } @@ -90,7 +93,10 @@ export async function pickSiteDefaults( // TODO: we need to lock this subnet for some time so someone else does not take it const subnets = sitesQuery .map((site) => site.subnet) - .filter((subnet) => subnet && /^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/.test(subnet)) + .filter( + (subnet) => + subnet && /^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/.test(subnet) + ) .filter((subnet) => subnet !== null); // exclude the exit node address by replacing after the / with a site block size subnets.push( diff --git a/server/routers/site/socketIntegration.ts b/server/routers/site/socketIntegration.ts index 338930005..e0ad09d1e 100644 --- a/server/routers/site/socketIntegration.ts +++ b/server/routers/site/socketIntegration.ts @@ -10,10 +10,7 @@ import { z } from "zod"; import { fromError } from "zod-validation-error"; import stoi from "@server/lib/stoi"; import { sendToClient } from "#dynamic/routers/ws"; -import { - fetchContainers, - dockerSocket -} from "../newt/dockerSocket"; +import { fetchContainers, dockerSocket } from "../newt/dockerSocket"; import cache from "@server/lib/cache"; export interface ContainerNetwork { @@ -47,13 +44,13 @@ export interface Container { } const siteIdParamsSchema = z.strictObject({ - siteId: z.string().transform(stoi).pipe(z.int().positive()) - }); + siteId: z.string().transform(stoi).pipe(z.int().positive()) +}); const DockerStatusSchema = z.strictObject({ - isAvailable: z.boolean(), - socketPath: z.string().optional() - }); + isAvailable: z.boolean(), + socketPath: z.string().optional() +}); function validateSiteIdParams(params: any) { const parsedParams = siteIdParamsSchema.safeParse(params); @@ -161,9 +158,7 @@ async function triggerFetch(siteId: number) { async function queryContainers(siteId: number) { const { newt } = await getSiteAndNewt(siteId); - const result = cache.get( - `${newt.newtId}:dockerContainers` - ) as Container[]; + const result = cache.get(`${newt.newtId}:dockerContainers`) as Container[]; if (!result) { throw createHttpError( HttpCode.TOO_EARLY, diff --git a/server/routers/site/updateSite.ts b/server/routers/site/updateSite.ts index 4c25d4c5a..447643628 100644 --- a/server/routers/site/updateSite.ts +++ b/server/routers/site/updateSite.ts @@ -12,16 +12,15 @@ import { OpenAPITags, registry } from "@server/openApi"; import { isValidCIDR } from "@server/lib/validators"; const updateSiteParamsSchema = z.strictObject({ - siteId: z.string().transform(Number).pipe(z.int().positive()) - }); + siteId: z.string().transform(Number).pipe(z.int().positive()) +}); -const updateSiteBodySchema = z.strictObject({ +const updateSiteBodySchema = z + .strictObject({ name: z.string().min(1).max(255).optional(), niceId: z.string().min(1).max(255).optional(), dockerSocketEnabled: z.boolean().optional(), - remoteSubnets: z - .string() - .optional() + remoteSubnets: z.string().optional() // subdomain: z // .string() // .min(1) @@ -41,8 +40,7 @@ const updateSiteBodySchema = z.strictObject({ registry.registerPath({ method: "post", path: "/site/{siteId}", - description: - "Update a site.", + description: "Update a site.", tags: [OpenAPITags.Site], request: { params: updateSiteParamsSchema, @@ -111,7 +109,9 @@ export async function updateSite( // if remoteSubnets is provided, ensure it's a valid comma-separated list of cidrs if (updateData.remoteSubnets) { - const subnets = updateData.remoteSubnets.split(",").map((s) => s.trim()); + const subnets = updateData.remoteSubnets + .split(",") + .map((s) => s.trim()); for (const subnet of subnets) { if (!isValidCIDR(subnet)) { return next( diff --git a/server/routers/siteResource/addClientToSiteResource.ts b/server/routers/siteResource/addClientToSiteResource.ts index 587294e5d..27d7f0573 100644 --- a/server/routers/siteResource/addClientToSiteResource.ts +++ b/server/routers/siteResource/addClientToSiteResource.ts @@ -28,7 +28,8 @@ const addClientToSiteResourceParamsSchema = z registry.registerPath({ method: "post", path: "/site-resource/{siteResourceId}/clients/add", - description: "Add a single client to a site resource. Clients with a userId cannot be added.", + description: + "Add a single client to a site resource. Clients with a userId cannot be added.", tags: [OpenAPITags.Resource, OpenAPITags.Client], request: { params: addClientToSiteResourceParamsSchema, @@ -49,7 +50,9 @@ export async function addClientToSiteResource( next: NextFunction ): Promise { try { - const parsedBody = addClientToSiteResourceBodySchema.safeParse(req.body); + const parsedBody = addClientToSiteResourceBodySchema.safeParse( + req.body + ); if (!parsedBody.success) { return next( createHttpError( @@ -153,4 +156,3 @@ export async function addClientToSiteResource( ); } } - diff --git a/server/routers/siteResource/addRoleToSiteResource.ts b/server/routers/siteResource/addRoleToSiteResource.ts index 542ca5356..abc2d221e 100644 --- a/server/routers/siteResource/addRoleToSiteResource.ts +++ b/server/routers/siteResource/addRoleToSiteResource.ts @@ -163,4 +163,3 @@ export async function addRoleToSiteResource( ); } } - diff --git a/server/routers/siteResource/addUserToSiteResource.ts b/server/routers/siteResource/addUserToSiteResource.ts index c9d1f30a3..4edf741cd 100644 --- a/server/routers/siteResource/addUserToSiteResource.ts +++ b/server/routers/siteResource/addUserToSiteResource.ts @@ -132,4 +132,3 @@ export async function addUserToSiteResource( ); } } - diff --git a/server/routers/siteResource/deleteSiteResource.ts b/server/routers/siteResource/deleteSiteResource.ts index a71756082..3d1e70cc7 100644 --- a/server/routers/siteResource/deleteSiteResource.ts +++ b/server/routers/siteResource/deleteSiteResource.ts @@ -106,7 +106,10 @@ export async function deleteSiteResource( ); } - await rebuildClientAssociationsFromSiteResource(removedSiteResource, trx); + await rebuildClientAssociationsFromSiteResource( + removedSiteResource, + trx + ); }); logger.info( diff --git a/server/routers/siteResource/getSiteResource.ts b/server/routers/siteResource/getSiteResource.ts index 48f10b8b2..7cb9e620f 100644 --- a/server/routers/siteResource/getSiteResource.ts +++ b/server/routers/siteResource/getSiteResource.ts @@ -11,44 +11,55 @@ import logger from "@server/logger"; import { OpenAPITags, registry } from "@server/openApi"; const getSiteResourceParamsSchema = z.strictObject({ - siteResourceId: z - .string() - .optional() - .transform((val) => val ? Number(val) : undefined) - .pipe(z.int().positive().optional()) - .optional(), - siteId: z.string().transform(Number).pipe(z.int().positive()), - niceId: z.string().optional(), - orgId: z.string() - }); + siteResourceId: z + .string() + .optional() + .transform((val) => (val ? Number(val) : undefined)) + .pipe(z.int().positive().optional()) + .optional(), + siteId: z.string().transform(Number).pipe(z.int().positive()), + niceId: z.string().optional(), + orgId: z.string() +}); -async function query(siteResourceId?: number, siteId?: number, niceId?: string, orgId?: string) { +async function query( + siteResourceId?: number, + siteId?: number, + niceId?: string, + orgId?: string +) { if (siteResourceId && siteId && orgId) { const [siteResource] = await db .select() .from(siteResources) - .where(and( - eq(siteResources.siteResourceId, siteResourceId), - eq(siteResources.siteId, siteId), - eq(siteResources.orgId, orgId) - )) + .where( + and( + eq(siteResources.siteResourceId, siteResourceId), + eq(siteResources.siteId, siteId), + eq(siteResources.orgId, orgId) + ) + ) .limit(1); return siteResource; } else if (niceId && siteId && orgId) { const [siteResource] = await db .select() .from(siteResources) - .where(and( - eq(siteResources.niceId, niceId), - eq(siteResources.siteId, siteId), - eq(siteResources.orgId, orgId) - )) + .where( + and( + eq(siteResources.niceId, niceId), + eq(siteResources.siteId, siteId), + eq(siteResources.orgId, orgId) + ) + ) .limit(1); return siteResource; } } -export type GetSiteResourceResponse = NonNullable>>; +export type GetSiteResourceResponse = NonNullable< + Awaited> +>; registry.registerPath({ method: "get", @@ -103,10 +114,7 @@ export async function getSiteResource( if (!siteResource) { return next( - createHttpError( - HttpCode.NOT_FOUND, - "Site resource not found" - ) + createHttpError(HttpCode.NOT_FOUND, "Site resource not found") ); } @@ -119,6 +127,11 @@ export async function getSiteResource( }); } catch (error) { logger.error("Error getting site resource:", error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "Failed to get site resource")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Failed to get site resource" + ) + ); } } diff --git a/server/routers/siteResource/listAllSiteResourcesByOrg.ts b/server/routers/siteResource/listAllSiteResourcesByOrg.ts index 5de66505b..f6975cd24 100644 --- a/server/routers/siteResource/listAllSiteResourcesByOrg.ts +++ b/server/routers/siteResource/listAllSiteResourcesByOrg.ts @@ -11,8 +11,8 @@ import logger from "@server/logger"; import { OpenAPITags, registry } from "@server/openApi"; const listAllSiteResourcesByOrgParamsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); const listAllSiteResourcesByOrgQuerySchema = z.object({ limit: z @@ -30,7 +30,11 @@ const listAllSiteResourcesByOrgQuerySchema = z.object({ }); export type ListAllSiteResourcesByOrgResponse = { - siteResources: (SiteResource & { siteName: string, siteNiceId: string, siteAddress: string | null })[]; + siteResources: (SiteResource & { + siteName: string; + siteNiceId: string; + siteAddress: string | null; + })[]; }; registry.registerPath({ @@ -51,7 +55,9 @@ export async function listAllSiteResourcesByOrg( next: NextFunction ): Promise { try { - const parsedParams = listAllSiteResourcesByOrgParamsSchema.safeParse(req.params); + const parsedParams = listAllSiteResourcesByOrgParamsSchema.safeParse( + req.params + ); if (!parsedParams.success) { return next( createHttpError( @@ -61,7 +67,9 @@ export async function listAllSiteResourcesByOrg( ); } - const parsedQuery = listAllSiteResourcesByOrgQuerySchema.safeParse(req.query); + const parsedQuery = listAllSiteResourcesByOrgQuerySchema.safeParse( + req.query + ); if (!parsedQuery.success) { return next( createHttpError( @@ -108,6 +116,11 @@ export async function listAllSiteResourcesByOrg( }); } catch (error) { logger.error("Error listing all site resources by org:", error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "Failed to list site resources")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Failed to list site resources" + ) + ); } } diff --git a/server/routers/siteResource/listSiteResourceClients.ts b/server/routers/siteResource/listSiteResourceClients.ts index 9b04ac326..772750d16 100644 --- a/server/routers/siteResource/listSiteResourceClients.ts +++ b/server/routers/siteResource/listSiteResourceClients.ts @@ -52,7 +52,9 @@ export async function listSiteResourceClients( next: NextFunction ): Promise { try { - const parsedParams = listSiteResourceClientsSchema.safeParse(req.params); + const parsedParams = listSiteResourceClientsSchema.safeParse( + req.params + ); if (!parsedParams.success) { return next( createHttpError( @@ -82,4 +84,3 @@ export async function listSiteResourceClients( ); } } - diff --git a/server/routers/siteResource/listSiteResourceRoles.ts b/server/routers/siteResource/listSiteResourceRoles.ts index 5504c003a..0dc5913b6 100644 --- a/server/routers/siteResource/listSiteResourceRoles.ts +++ b/server/routers/siteResource/listSiteResourceRoles.ts @@ -83,4 +83,3 @@ export async function listSiteResourceRoles( ); } } - diff --git a/server/routers/siteResource/listSiteResourceUsers.ts b/server/routers/siteResource/listSiteResourceUsers.ts index 6cc19557e..daf754801 100644 --- a/server/routers/siteResource/listSiteResourceUsers.ts +++ b/server/routers/siteResource/listSiteResourceUsers.ts @@ -86,4 +86,3 @@ export async function listSiteResourceUsers( ); } } - diff --git a/server/routers/siteResource/listSiteResources.ts b/server/routers/siteResource/listSiteResources.ts index e530952d8..6ecda7c4c 100644 --- a/server/routers/siteResource/listSiteResources.ts +++ b/server/routers/siteResource/listSiteResources.ts @@ -11,9 +11,9 @@ import logger from "@server/logger"; import { OpenAPITags, registry } from "@server/openApi"; const listSiteResourcesParamsSchema = z.strictObject({ - siteId: z.string().transform(Number).pipe(z.int().positive()), - orgId: z.string() - }); + siteId: z.string().transform(Number).pipe(z.int().positive()), + orgId: z.string() +}); const listSiteResourcesQuerySchema = z.object({ limit: z @@ -52,7 +52,9 @@ export async function listSiteResources( next: NextFunction ): Promise { try { - const parsedParams = listSiteResourcesParamsSchema.safeParse(req.params); + const parsedParams = listSiteResourcesParamsSchema.safeParse( + req.params + ); if (!parsedParams.success) { return next( createHttpError( @@ -83,22 +85,19 @@ export async function listSiteResources( .limit(1); if (site.length === 0) { - return next( - createHttpError( - HttpCode.NOT_FOUND, - "Site not found" - ) - ); + return next(createHttpError(HttpCode.NOT_FOUND, "Site not found")); } // Get site resources const siteResourcesList = await db .select() .from(siteResources) - .where(and( - eq(siteResources.siteId, siteId), - eq(siteResources.orgId, orgId) - )) + .where( + and( + eq(siteResources.siteId, siteId), + eq(siteResources.orgId, orgId) + ) + ) .limit(limit) .offset(offset); @@ -111,6 +110,11 @@ export async function listSiteResources( }); } catch (error) { logger.error("Error listing site resources:", error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "Failed to list site resources")); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Failed to list site resources" + ) + ); } } diff --git a/server/routers/siteResource/removeClientFromSiteResource.ts b/server/routers/siteResource/removeClientFromSiteResource.ts index c6a5dfe86..351128d18 100644 --- a/server/routers/siteResource/removeClientFromSiteResource.ts +++ b/server/routers/siteResource/removeClientFromSiteResource.ts @@ -28,7 +28,8 @@ const removeClientFromSiteResourceParamsSchema = z registry.registerPath({ method: "post", path: "/site-resource/{siteResourceId}/clients/remove", - description: "Remove a single client from a site resource. Clients with a userId cannot be removed.", + description: + "Remove a single client from a site resource. Clients with a userId cannot be removed.", tags: [OpenAPITags.Resource, OpenAPITags.Client], request: { params: removeClientFromSiteResourceParamsSchema, @@ -159,4 +160,3 @@ export async function removeClientFromSiteResource( ); } } - diff --git a/server/routers/siteResource/removeRoleFromSiteResource.ts b/server/routers/siteResource/removeRoleFromSiteResource.ts index 0041ed83e..c9857e841 100644 --- a/server/routers/siteResource/removeRoleFromSiteResource.ts +++ b/server/routers/siteResource/removeRoleFromSiteResource.ts @@ -151,7 +151,7 @@ export async function removeRoleFromSiteResource( ) ); - await rebuildClientAssociationsFromSiteResource(siteResource, trx); + await rebuildClientAssociationsFromSiteResource(siteResource, trx); }); return response(res, { @@ -168,4 +168,3 @@ export async function removeRoleFromSiteResource( ); } } - diff --git a/server/routers/siteResource/removeUserFromSiteResource.ts b/server/routers/siteResource/removeUserFromSiteResource.ts index 280a01f2b..84347b2f6 100644 --- a/server/routers/siteResource/removeUserFromSiteResource.ts +++ b/server/routers/siteResource/removeUserFromSiteResource.ts @@ -138,4 +138,3 @@ export async function removeUserFromSiteResource( ); } } - diff --git a/server/routers/siteResource/setSiteResourceClients.ts b/server/routers/siteResource/setSiteResourceClients.ts index 0a25b7e9e..5a8acbcf5 100644 --- a/server/routers/siteResource/setSiteResourceClients.ts +++ b/server/routers/siteResource/setSiteResourceClients.ts @@ -62,7 +62,9 @@ export async function setSiteResourceClients( const { clientIds } = parsedBody.data; - const parsedParams = setSiteResourceClientsParamsSchema.safeParse(req.params); + const parsedParams = setSiteResourceClientsParamsSchema.safeParse( + req.params + ); if (!parsedParams.success) { return next( createHttpError( @@ -95,9 +97,7 @@ export async function setSiteResourceClients( const clientsWithUsers = await db .select() .from(clients) - .where( - inArray(clients.clientId, clientIds) - ); + .where(inArray(clients.clientId, clientIds)); const clientsWithUserId = clientsWithUsers.filter( (client) => client.userId !== null @@ -119,9 +119,12 @@ export async function setSiteResourceClients( .where(eq(clientSiteResources.siteResourceId, siteResourceId)); if (clientIds.length > 0) { - await trx - .insert(clientSiteResources) - .values(clientIds.map((clientId) => ({ clientId, siteResourceId }))); + await trx.insert(clientSiteResources).values( + clientIds.map((clientId) => ({ + clientId, + siteResourceId + })) + ); } await rebuildClientAssociationsFromSiteResource(siteResource, trx); @@ -141,4 +144,3 @@ export async function setSiteResourceClients( ); } } - diff --git a/server/routers/siteResource/setSiteResourceRoles.ts b/server/routers/siteResource/setSiteResourceRoles.ts index 7aa07de1a..bb71a16b6 100644 --- a/server/routers/siteResource/setSiteResourceRoles.ts +++ b/server/routers/siteResource/setSiteResourceRoles.ts @@ -136,15 +136,19 @@ export async function setSiteResourceRoles( ) ); } else { - await trx.delete(roleSiteResources).where( - eq(roleSiteResources.siteResourceId, siteResourceId) - ); + await trx + .delete(roleSiteResources) + .where( + eq(roleSiteResources.siteResourceId, siteResourceId) + ); } if (roleIds.length > 0) { await trx .insert(roleSiteResources) - .values(roleIds.map((roleId) => ({ roleId, siteResourceId }))); + .values( + roleIds.map((roleId) => ({ roleId, siteResourceId })) + ); } await rebuildClientAssociationsFromSiteResource(siteResource, trx); diff --git a/server/routers/siteResource/setSiteResourceUsers.ts b/server/routers/siteResource/setSiteResourceUsers.ts index 4dae0adaa..eacd826cc 100644 --- a/server/routers/siteResource/setSiteResourceUsers.ts +++ b/server/routers/siteResource/setSiteResourceUsers.ts @@ -63,7 +63,9 @@ export async function setSiteResourceUsers( const { userIds } = parsedBody.data; - const parsedParams = setSiteResourceUsersParamsSchema.safeParse(req.params); + const parsedParams = setSiteResourceUsersParamsSchema.safeParse( + req.params + ); if (!parsedParams.success) { return next( createHttpError( @@ -99,7 +101,9 @@ export async function setSiteResourceUsers( if (userIds.length > 0) { await trx .insert(userSiteResources) - .values(userIds.map((userId) => ({ userId, siteResourceId }))); + .values( + userIds.map((userId) => ({ userId, siteResourceId })) + ); } await rebuildClientAssociationsFromSiteResource(siteResource, trx); @@ -119,4 +123,3 @@ export async function setSiteResourceUsers( ); } } - diff --git a/server/routers/supporterKey/validateSupporterKey.ts b/server/routers/supporterKey/validateSupporterKey.ts index d8b164212..9ac3c4732 100644 --- a/server/routers/supporterKey/validateSupporterKey.ts +++ b/server/routers/supporterKey/validateSupporterKey.ts @@ -10,9 +10,9 @@ import { db } from "@server/db"; import config from "@server/lib/config"; const validateSupporterKeySchema = z.strictObject({ - githubUsername: z.string().nonempty(), - key: z.string().nonempty() - }); + githubUsername: z.string().nonempty(), + key: z.string().nonempty() +}); export type ValidateSupporterKeyResponse = { valid: boolean; diff --git a/server/routers/target/createTarget.ts b/server/routers/target/createTarget.ts index 2c09b5a6c..5d37f6173 100644 --- a/server/routers/target/createTarget.ts +++ b/server/routers/target/createTarget.ts @@ -16,51 +16,41 @@ import { isTargetValid } from "@server/lib/validators"; import { OpenAPITags, registry } from "@server/openApi"; const createTargetParamsSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); const createTargetSchema = z.strictObject({ - siteId: z.int().positive(), - ip: z.string().refine(isTargetValid), - method: z.string().optional().nullable(), - port: z.int().min(1).max(65535), - enabled: z.boolean().default(true), - hcEnabled: z.boolean().optional(), - hcPath: z.string().min(1).optional().nullable(), - hcScheme: z.string().optional().nullable(), - hcMode: z.string().optional().nullable(), - hcHostname: z.string().optional().nullable(), - hcPort: z.int().positive().optional().nullable(), - hcInterval: z.int().positive().min(5).optional().nullable(), - hcUnhealthyInterval: z.int() - .positive() - .min(5) - .optional() - .nullable(), - hcTimeout: z.int().positive().min(1).optional().nullable(), - hcHeaders: z - .array(z.strictObject({ name: z.string(), value: z.string() })) - .nullable() - .optional(), - hcFollowRedirects: z.boolean().optional().nullable(), - hcMethod: z.string().min(1).optional().nullable(), - hcStatus: z.int().optional().nullable(), - hcTlsServerName: z.string().optional().nullable(), - path: z.string().optional().nullable(), - pathMatchType: z - .enum(["exact", "prefix", "regex"]) - .optional() - .nullable(), - rewritePath: z.string().optional().nullable(), - rewritePathType: z - .enum(["exact", "prefix", "regex", "stripPrefix"]) - .optional() - .nullable(), - priority: z.int().min(1).max(1000).optional().nullable() - }); + siteId: z.int().positive(), + ip: z.string().refine(isTargetValid), + method: z.string().optional().nullable(), + port: z.int().min(1).max(65535), + enabled: z.boolean().default(true), + hcEnabled: z.boolean().optional(), + hcPath: z.string().min(1).optional().nullable(), + hcScheme: z.string().optional().nullable(), + hcMode: z.string().optional().nullable(), + hcHostname: z.string().optional().nullable(), + hcPort: z.int().positive().optional().nullable(), + hcInterval: z.int().positive().min(5).optional().nullable(), + hcUnhealthyInterval: z.int().positive().min(5).optional().nullable(), + hcTimeout: z.int().positive().min(1).optional().nullable(), + hcHeaders: z + .array(z.strictObject({ name: z.string(), value: z.string() })) + .nullable() + .optional(), + hcFollowRedirects: z.boolean().optional().nullable(), + hcMethod: z.string().min(1).optional().nullable(), + hcStatus: z.int().optional().nullable(), + hcTlsServerName: z.string().optional().nullable(), + path: z.string().optional().nullable(), + pathMatchType: z.enum(["exact", "prefix", "regex"]).optional().nullable(), + rewritePath: z.string().optional().nullable(), + rewritePathType: z + .enum(["exact", "prefix", "regex", "stripPrefix"]) + .optional() + .nullable(), + priority: z.int().min(1).max(1000).optional().nullable() +}); export type CreateTargetResponse = Target & TargetHealthCheck; @@ -159,7 +149,9 @@ export async function createTarget( if (existingTarget) { // log a warning - logger.warn(`Target with IP ${targetData.ip}, port ${targetData.port}, method ${targetData.method} already exists for resource ID ${resourceId}`); + logger.warn( + `Target with IP ${targetData.ip}, port ${targetData.port}, method ${targetData.method} already exists for resource ID ${resourceId}` + ); } let newTarget: Target[] = []; diff --git a/server/routers/target/deleteTarget.ts b/server/routers/target/deleteTarget.ts index a70b2a1ec..606d86351 100644 --- a/server/routers/target/deleteTarget.ts +++ b/server/routers/target/deleteTarget.ts @@ -14,8 +14,8 @@ import { getAllowedIps } from "./helpers"; import { OpenAPITags, registry } from "@server/openApi"; const deleteTargetSchema = z.strictObject({ - targetId: z.string().transform(Number).pipe(z.int().positive()) - }); + targetId: z.string().transform(Number).pipe(z.int().positive()) +}); registry.registerPath({ method: "delete", diff --git a/server/routers/target/getTarget.ts b/server/routers/target/getTarget.ts index 7fe2e062d..749e1399b 100644 --- a/server/routers/target/getTarget.ts +++ b/server/routers/target/getTarget.ts @@ -11,12 +11,13 @@ import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; const getTargetSchema = z.strictObject({ - targetId: z.string().transform(Number).pipe(z.int().positive()) - }); + targetId: z.string().transform(Number).pipe(z.int().positive()) +}); -type GetTargetResponse = Target & Omit & { - hcHeaders: { name: string; value: string; }[] | null; -}; +type GetTargetResponse = Target & + Omit & { + hcHeaders: { name: string; value: string }[] | null; + }; registry.registerPath({ method: "get", diff --git a/server/routers/target/handleHealthcheckStatusMessage.ts b/server/routers/target/handleHealthcheckStatusMessage.ts index ee4e79508..2bfcff190 100644 --- a/server/routers/target/handleHealthcheckStatusMessage.ts +++ b/server/routers/target/handleHealthcheckStatusMessage.ts @@ -30,7 +30,9 @@ interface HealthcheckStatusMessage { targets: Record; } -export const handleHealthcheckStatusMessage: MessageHandler = async (context) => { +export const handleHealthcheckStatusMessage: MessageHandler = async ( + context +) => { const { message, client: c } = context; const newt = c as Newt; @@ -59,7 +61,9 @@ export const handleHealthcheckStatusMessage: MessageHandler = async (context) => // Process each target status update for (const [targetId, healthStatus] of Object.entries(data.targets)) { - logger.debug(`Processing health status for target ${targetId}: ${healthStatus.status}${healthStatus.lastError ? ` (${healthStatus.lastError})` : ''}`); + logger.debug( + `Processing health status for target ${targetId}: ${healthStatus.status}${healthStatus.lastError ? ` (${healthStatus.lastError})` : ""}` + ); // Verify the target belongs to this newt's site before updating // This prevents unauthorized updates to targets from other sites @@ -76,7 +80,10 @@ export const handleHealthcheckStatusMessage: MessageHandler = async (context) => siteId: targets.siteId }) .from(targets) - .innerJoin(resources, eq(targets.resourceId, resources.resourceId)) + .innerJoin( + resources, + eq(targets.resourceId, resources.resourceId) + ) .innerJoin(sites, eq(targets.siteId, sites.siteId)) .where( and( @@ -87,7 +94,9 @@ export const handleHealthcheckStatusMessage: MessageHandler = async (context) => .limit(1); if (!targetCheck) { - logger.warn(`Target ${targetId} not found or does not belong to site ${newt.siteId}`); + logger.warn( + `Target ${targetId} not found or does not belong to site ${newt.siteId}` + ); errorCount++; continue; } @@ -101,11 +110,15 @@ export const handleHealthcheckStatusMessage: MessageHandler = async (context) => .where(eq(targetHealthCheck.targetId, targetIdNum)) .execute(); - logger.debug(`Updated health status for target ${targetId} to ${healthStatus.status}`); + logger.debug( + `Updated health status for target ${targetId} to ${healthStatus.status}` + ); successCount++; } - logger.debug(`Health status update complete: ${successCount} successful, ${errorCount} errors out of ${Object.keys(data.targets).length} targets`); + logger.debug( + `Health status update complete: ${successCount} successful, ${errorCount} errors out of ${Object.keys(data.targets).length} targets` + ); } catch (error) { logger.error("Error processing healthcheck status message:", error); } diff --git a/server/routers/target/helpers.ts b/server/routers/target/helpers.ts index 13b2ee46b..fe76bd133 100644 --- a/server/routers/target/helpers.ts +++ b/server/routers/target/helpers.ts @@ -4,7 +4,10 @@ import { eq } from "drizzle-orm"; const currentBannedPorts: number[] = []; -export async function pickPort(siteId: number, trx: Transaction | typeof db): Promise<{ +export async function pickPort( + siteId: number, + trx: Transaction | typeof db +): Promise<{ internalPort: number; targetIps: string[]; }> { diff --git a/server/routers/target/listTargets.ts b/server/routers/target/listTargets.ts index 356276cb4..11a23f025 100644 --- a/server/routers/target/listTargets.ts +++ b/server/routers/target/listTargets.ts @@ -11,11 +11,8 @@ import logger from "@server/logger"; import { OpenAPITags, registry } from "@server/openApi"; const listTargetsParamsSchema = z.strictObject({ - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); const listTargetsSchema = z.object({ limit: z @@ -62,7 +59,7 @@ function queryTargets(resourceId: number) { pathMatchType: targets.pathMatchType, rewritePath: targets.rewritePath, rewritePathType: targets.rewritePathType, - priority: targets.priority, + priority: targets.priority }) .from(targets) .leftJoin(sites, eq(sites.siteId, targets.siteId)) @@ -75,8 +72,11 @@ function queryTargets(resourceId: number) { return baseQuery; } -type TargetWithParsedHeaders = Omit>[0], 'hcHeaders'> & { - hcHeaders: { name: string; value: string; }[] | null; +type TargetWithParsedHeaders = Omit< + Awaited>[0], + "hcHeaders" +> & { + hcHeaders: { name: string; value: string }[] | null; }; export type ListTargetsResponse = { @@ -136,7 +136,7 @@ export async function listTargets( const totalCount = totalCountResult[0].count; // Parse hcHeaders from JSON string back to array for each target - const parsedTargetsList = targetsList.map(target => { + const parsedTargetsList = targetsList.map((target) => { let parsedHcHeaders = null; if (target.hcHeaders) { try { diff --git a/server/routers/target/updateTarget.ts b/server/routers/target/updateTarget.ts index f4a598585..b00340eef 100644 --- a/server/routers/target/updateTarget.ts +++ b/server/routers/target/updateTarget.ts @@ -16,10 +16,11 @@ import { OpenAPITags, registry } from "@server/openApi"; import { vs } from "@react-email/components"; const updateTargetParamsSchema = z.strictObject({ - targetId: z.string().transform(Number).pipe(z.int().positive()) - }); + targetId: z.string().transform(Number).pipe(z.int().positive()) +}); -const updateTargetBodySchema = z.strictObject({ +const updateTargetBodySchema = z + .strictObject({ siteId: z.int().positive(), ip: z.string().refine(isTargetValid), method: z.string().min(1).max(10).optional().nullable(), @@ -32,22 +33,27 @@ const updateTargetBodySchema = z.strictObject({ hcHostname: z.string().optional().nullable(), hcPort: z.int().positive().optional().nullable(), hcInterval: z.int().positive().min(5).optional().nullable(), - hcUnhealthyInterval: z.int() - .positive() - .min(5) - .optional() - .nullable(), + hcUnhealthyInterval: z.int().positive().min(5).optional().nullable(), hcTimeout: z.int().positive().min(1).optional().nullable(), - hcHeaders: z.array(z.strictObject({ name: z.string(), value: z.string() })).nullable().optional(), + hcHeaders: z + .array(z.strictObject({ name: z.string(), value: z.string() })) + .nullable() + .optional(), hcFollowRedirects: z.boolean().optional().nullable(), hcMethod: z.string().min(1).optional().nullable(), hcStatus: z.int().optional().nullable(), hcTlsServerName: z.string().optional().nullable(), path: z.string().optional().nullable(), - pathMatchType: z.enum(["exact", "prefix", "regex"]).optional().nullable(), + pathMatchType: z + .enum(["exact", "prefix", "regex"]) + .optional() + .nullable(), rewritePath: z.string().optional().nullable(), - rewritePathType: z.enum(["exact", "prefix", "regex", "stripPrefix"]).optional().nullable(), - priority: z.int().min(1).max(1000).optional(), + rewritePathType: z + .enum(["exact", "prefix", "regex", "stripPrefix"]) + .optional() + .nullable(), + priority: z.int().min(1).max(1000).optional() }) .refine((data) => Object.keys(data).length > 0, { error: "At least one field must be provided for update" @@ -166,7 +172,9 @@ export async function updateTarget( if (foundTarget) { // log a warning - logger.warn(`Target with IP ${targetData.ip}, port ${targetData.port}, method ${targetData.method} already exists for resource ID ${target.resourceId}`); + logger.warn( + `Target with IP ${targetData.ip}, port ${targetData.port}, method ${targetData.method} already exists for resource ID ${target.resourceId}` + ); } const { internalPort, targetIps } = await pickPort(site.siteId!, db); @@ -205,9 +213,11 @@ export async function updateTarget( // When health check is disabled, reset hcHealth to "unknown" // to prevent previously unhealthy targets from being excluded - const hcHealthValue = (parsedBody.data.hcEnabled === false || parsedBody.data.hcEnabled === null) - ? "unknown" - : undefined; + const hcHealthValue = + parsedBody.data.hcEnabled === false || + parsedBody.data.hcEnabled === null + ? "unknown" + : undefined; const [updatedHc] = await db .update(targetHealthCheck) diff --git a/server/routers/traefik/index.ts b/server/routers/traefik/index.ts index 6f5bd4f04..195f00878 100644 --- a/server/routers/traefik/index.ts +++ b/server/routers/traefik/index.ts @@ -1 +1 @@ -export * from "./traefikConfigProvider"; \ No newline at end of file +export * from "./traefikConfigProvider"; diff --git a/server/routers/traefik/traefikConfigProvider.ts b/server/routers/traefik/traefikConfigProvider.ts index 9b12ed8ad..e8ac1621e 100644 --- a/server/routers/traefik/traefikConfigProvider.ts +++ b/server/routers/traefik/traefikConfigProvider.ts @@ -59,4 +59,4 @@ export async function traefikConfigProvider( error: "Failed to build Traefik config" }); } -} \ No newline at end of file +} diff --git a/server/routers/user/acceptInvite.ts b/server/routers/user/acceptInvite.ts index 3e94d96cc..d64ccfb5b 100644 --- a/server/routers/user/acceptInvite.ts +++ b/server/routers/user/acceptInvite.ts @@ -15,9 +15,9 @@ import { FeatureId } from "@server/lib/billing"; import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs"; const acceptInviteBodySchema = z.strictObject({ - token: z.string(), - inviteId: z.string() - }); + token: z.string(), + inviteId: z.string() +}); export type AcceptInviteResponse = { accepted: boolean; diff --git a/server/routers/user/addUserAction.ts b/server/routers/user/addUserAction.ts index f75d50050..ddbae6b01 100644 --- a/server/routers/user/addUserAction.ts +++ b/server/routers/user/addUserAction.ts @@ -10,10 +10,10 @@ import { eq } from "drizzle-orm"; import { fromError } from "zod-validation-error"; const addUserActionSchema = z.strictObject({ - userId: z.string(), - actionId: z.string(), - orgId: z.string() - }); + userId: z.string(), + actionId: z.string(), + orgId: z.string() +}); export async function addUserAction( req: Request, diff --git a/server/routers/user/addUserSite.ts b/server/routers/user/addUserSite.ts index 38ef264cd..ffb9f1ba9 100644 --- a/server/routers/user/addUserSite.ts +++ b/server/routers/user/addUserSite.ts @@ -10,9 +10,9 @@ import { eq } from "drizzle-orm"; import { fromError } from "zod-validation-error"; const addUserSiteSchema = z.strictObject({ - userId: z.string(), - siteId: z.string().transform(Number).pipe(z.int().positive()) - }); + userId: z.string(), + siteId: z.string().transform(Number).pipe(z.int().positive()) +}); export async function addUserSite( req: Request, @@ -61,7 +61,6 @@ export async function addUserSite( status: HttpCode.CREATED }); }); - } catch (error) { logger.error(error); return next( diff --git a/server/routers/user/adminGeneratePasswordResetCode.ts b/server/routers/user/adminGeneratePasswordResetCode.ts index 5d283c5cd..562a459e1 100644 --- a/server/routers/user/adminGeneratePasswordResetCode.ts +++ b/server/routers/user/adminGeneratePasswordResetCode.ts @@ -19,7 +19,9 @@ const adminGeneratePasswordResetCodeSchema = z.strictObject({ userId: z.string().min(1) }); -export type AdminGeneratePasswordResetCodeBody = z.infer; +export type AdminGeneratePasswordResetCodeBody = z.infer< + typeof adminGeneratePasswordResetCodeSchema +>; export type AdminGeneratePasswordResetCodeResponse = { token: string; @@ -32,7 +34,9 @@ export async function adminGeneratePasswordResetCode( res: Response, next: NextFunction ): Promise { - const parsedParams = adminGeneratePasswordResetCodeSchema.safeParse(req.params); + const parsedParams = adminGeneratePasswordResetCodeSchema.safeParse( + req.params + ); if (!parsedParams.success) { return next( @@ -52,12 +56,7 @@ export async function adminGeneratePasswordResetCode( .where(eq(users.userId, userId)); if (!existingUser || !existingUser.length) { - return next( - createHttpError( - HttpCode.NOT_FOUND, - "User not found" - ) - ); + return next(createHttpError(HttpCode.NOT_FOUND, "User not found")); } if (existingUser[0].type !== UserType.Internal) { @@ -122,4 +121,3 @@ export async function adminGeneratePasswordResetCode( ); } } - diff --git a/server/routers/user/adminGetUser.ts b/server/routers/user/adminGetUser.ts index bda144762..06045c770 100644 --- a/server/routers/user/adminGetUser.ts +++ b/server/routers/user/adminGetUser.ts @@ -10,8 +10,8 @@ import logger from "@server/logger"; import { OpenAPITags, registry } from "@server/openApi"; const adminGetUserSchema = z.strictObject({ - userId: z.string().min(1) - }); + userId: z.string().min(1) +}); registry.registerPath({ method: "get", diff --git a/server/routers/user/adminListUsers.ts b/server/routers/user/adminListUsers.ts index a3ad9cdd0..3a965259c 100644 --- a/server/routers/user/adminListUsers.ts +++ b/server/routers/user/adminListUsers.ts @@ -10,19 +10,19 @@ import { idp, users } from "@server/db"; import { fromZodError } from "zod-validation-error"; const listUsersSchema = z.strictObject({ - limit: z - .string() - .optional() - .default("1000") - .transform(Number) - .pipe(z.int().nonnegative()), - offset: z - .string() - .optional() - .default("0") - .transform(Number) - .pipe(z.int().nonnegative()) - }); + limit: z + .string() + .optional() + .default("1000") + .transform(Number) + .pipe(z.int().nonnegative()), + offset: z + .string() + .optional() + .default("0") + .transform(Number) + .pipe(z.int().nonnegative()) +}); async function queryUsers(limit: number, offset: number) { return await db diff --git a/server/routers/user/adminUpdateUser2FA.ts b/server/routers/user/adminUpdateUser2FA.ts index 4bb2486a1..7fb37d010 100644 --- a/server/routers/user/adminUpdateUser2FA.ts +++ b/server/routers/user/adminUpdateUser2FA.ts @@ -11,12 +11,12 @@ import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; const updateUser2FAParamsSchema = z.strictObject({ - userId: z.string() - }); + userId: z.string() +}); const updateUser2FABodySchema = z.strictObject({ - twoFactorSetupRequested: z.boolean() - }); + twoFactorSetupRequested: z.boolean() +}); export type UpdateUser2FAResponse = { userId: string; @@ -90,13 +90,15 @@ export async function updateUser2FA( ); } - logger.debug(`Updating 2FA for user ${userId} to ${twoFactorSetupRequested}`); + logger.debug( + `Updating 2FA for user ${userId} to ${twoFactorSetupRequested}` + ); if (twoFactorSetupRequested) { await db .update(users) .set({ - twoFactorSetupRequested: true, + twoFactorSetupRequested: true }) .where(eq(users.userId, userId)); } else { diff --git a/server/routers/user/createOrgUser.ts b/server/routers/user/createOrgUser.ts index 99a2258c7..e19024770 100644 --- a/server/routers/user/createOrgUser.ts +++ b/server/routers/user/createOrgUser.ts @@ -18,25 +18,26 @@ import { TierId } from "@server/lib/billing/tiers"; import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs"; const paramsSchema = z.strictObject({ - orgId: z.string().nonempty() - }); + orgId: z.string().nonempty() +}); const bodySchema = z.strictObject({ - email: z.email() - .toLowerCase() - .optional() - .refine((data) => { - if (data) { - return z.email().safeParse(data).success; - } - return true; - }), - username: z.string().nonempty().toLowerCase(), - name: z.string().optional(), - type: z.enum(["internal", "oidc"]).optional(), - idpId: z.number().optional(), - roleId: z.number() - }); + email: z + .email() + .toLowerCase() + .optional() + .refine((data) => { + if (data) { + return z.email().safeParse(data).success; + } + return true; + }), + username: z.string().nonempty().toLowerCase(), + name: z.string().optional(), + type: z.enum(["internal", "oidc"]).optional(), + idpId: z.number().optional(), + roleId: z.number() +}); export type CreateOrgUserResponse = {}; diff --git a/server/routers/user/getOrgUser.ts b/server/routers/user/getOrgUser.ts index 4e09afd69..f22a29d37 100644 --- a/server/routers/user/getOrgUser.ts +++ b/server/routers/user/getOrgUser.ts @@ -47,9 +47,9 @@ export type GetOrgUserResponse = NonNullable< >; const getOrgUserParamsSchema = z.strictObject({ - userId: z.string(), - orgId: z.string() - }); + userId: z.string(), + orgId: z.string() +}); registry.registerPath({ method: "get", diff --git a/server/routers/user/inviteUser.ts b/server/routers/user/inviteUser.ts index f43ebeb8d..6a778868a 100644 --- a/server/routers/user/inviteUser.ts +++ b/server/routers/user/inviteUser.ts @@ -22,16 +22,16 @@ import { build } from "@server/build"; import cache from "@server/lib/cache"; const inviteUserParamsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); const inviteUserBodySchema = z.strictObject({ - email: z.email().toLowerCase(), - roleId: z.number(), - validHours: z.number().gt(0).lte(168), - sendEmail: z.boolean().optional(), - regenerate: z.boolean().optional() - }); + email: z.email().toLowerCase(), + roleId: z.number(), + validHours: z.number().gt(0).lte(168), + sendEmail: z.boolean().optional(), + regenerate: z.boolean().optional() +}); export type InviteUserBody = z.infer; @@ -109,12 +109,7 @@ export async function inviteUser( const [role] = await db .select() .from(roles) - .where( - and( - eq(roles.roleId, roleId), - eq(roles.orgId, orgId) - ) - ) + .where(and(eq(roles.roleId, roleId), eq(roles.orgId, orgId))) .limit(1); if (!role) { diff --git a/server/routers/user/listInvitations.ts b/server/routers/user/listInvitations.ts index a61e2372e..4289b877f 100644 --- a/server/routers/user/listInvitations.ts +++ b/server/routers/user/listInvitations.ts @@ -11,23 +11,23 @@ import { fromZodError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; const listInvitationsParamsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); const listInvitationsQuerySchema = z.strictObject({ - limit: z - .string() - .optional() - .default("1000") - .transform(Number) - .pipe(z.int().nonnegative()), - offset: z - .string() - .optional() - .default("0") - .transform(Number) - .pipe(z.int().nonnegative()) - }); + limit: z + .string() + .optional() + .default("1000") + .transform(Number) + .pipe(z.int().nonnegative()), + offset: z + .string() + .optional() + .default("0") + .transform(Number) + .pipe(z.int().nonnegative()) +}); async function queryInvitations(orgId: string, limit: number, offset: number) { return await db diff --git a/server/routers/user/listUsers.ts b/server/routers/user/listUsers.ts index aa70874ea..401dcf58b 100644 --- a/server/routers/user/listUsers.ts +++ b/server/routers/user/listUsers.ts @@ -12,23 +12,23 @@ import { OpenAPITags, registry } from "@server/openApi"; import { eq } from "drizzle-orm"; const listUsersParamsSchema = z.strictObject({ - orgId: z.string() - }); + orgId: z.string() +}); const listUsersSchema = z.strictObject({ - limit: z - .string() - .optional() - .default("1000") - .transform(Number) - .pipe(z.int().nonnegative()), - offset: z - .string() - .optional() - .default("0") - .transform(Number) - .pipe(z.int().nonnegative()) - }); + limit: z + .string() + .optional() + .default("1000") + .transform(Number) + .pipe(z.int().nonnegative()), + offset: z + .string() + .optional() + .default("0") + .transform(Number) + .pipe(z.int().nonnegative()) +}); async function queryUsers(orgId: string, limit: number, offset: number) { return await db @@ -48,7 +48,7 @@ async function queryUsers(orgId: string, limit: number, offset: number) { idpId: users.idpId, idpType: idp.type, idpVariant: idpOidcConfig.variant, - twoFactorEnabled: users.twoFactorEnabled, + twoFactorEnabled: users.twoFactorEnabled }) .from(users) .leftJoin(userOrgs, eq(users.userId, userOrgs.userId)) diff --git a/server/routers/user/removeInvitation.ts b/server/routers/user/removeInvitation.ts index 44ec8c236..6a000afcf 100644 --- a/server/routers/user/removeInvitation.ts +++ b/server/routers/user/removeInvitation.ts @@ -10,9 +10,9 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; const removeInvitationParamsSchema = z.strictObject({ - orgId: z.string(), - inviteId: z.string() - }); + orgId: z.string(), + inviteId: z.string() +}); export async function removeInvitation( req: Request, diff --git a/server/routers/user/removeUserAction.ts b/server/routers/user/removeUserAction.ts index 6e4c1a66c..b9dc8cc0a 100644 --- a/server/routers/user/removeUserAction.ts +++ b/server/routers/user/removeUserAction.ts @@ -10,13 +10,13 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; const removeUserActionParamsSchema = z.strictObject({ - userId: z.string() - }); + userId: z.string() +}); const removeUserActionSchema = z.strictObject({ - actionId: z.string(), - orgId: z.string() - }); + actionId: z.string(), + orgId: z.string() +}); export async function removeUserAction( req: Request, diff --git a/server/routers/user/removeUserOrg.ts b/server/routers/user/removeUserOrg.ts index cbbb4495d..97045e924 100644 --- a/server/routers/user/removeUserOrg.ts +++ b/server/routers/user/removeUserOrg.ts @@ -16,9 +16,9 @@ import { UserType } from "@server/types/UserTypes"; import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs"; const removeUserSchema = z.strictObject({ - userId: z.string(), - orgId: z.string() - }); + userId: z.string(), + orgId: z.string() +}); registry.registerPath({ method: "delete", diff --git a/server/routers/user/removeUserResource.ts b/server/routers/user/removeUserResource.ts index 14dbb540d..bdb0cda32 100644 --- a/server/routers/user/removeUserResource.ts +++ b/server/routers/user/removeUserResource.ts @@ -10,12 +10,9 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; const removeUserResourceSchema = z.strictObject({ - userId: z.string(), - resourceId: z - .string() - .transform(Number) - .pipe(z.int().positive()) - }); + userId: z.string(), + resourceId: z.string().transform(Number).pipe(z.int().positive()) +}); export async function removeUserResource( req: Request, diff --git a/server/routers/user/removeUserSite.ts b/server/routers/user/removeUserSite.ts index 6ed2288a1..a531f02c8 100644 --- a/server/routers/user/removeUserSite.ts +++ b/server/routers/user/removeUserSite.ts @@ -10,12 +10,12 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; const removeUserSiteParamsSchema = z.strictObject({ - userId: z.string() - }); + userId: z.string() +}); const removeUserSiteSchema = z.strictObject({ - siteId: z.int().positive() - }); + siteId: z.int().positive() +}); export async function removeUserSite( req: Request, diff --git a/server/routers/user/updateOrgUser.ts b/server/routers/user/updateOrgUser.ts index e1000063c..97bedb5f9 100644 --- a/server/routers/user/updateOrgUser.ts +++ b/server/routers/user/updateOrgUser.ts @@ -10,11 +10,12 @@ import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; const paramsSchema = z.strictObject({ - userId: z.string(), - orgId: z.string() - }); + userId: z.string(), + orgId: z.string() +}); -const bodySchema = z.strictObject({ +const bodySchema = z + .strictObject({ autoProvisioned: z.boolean().optional() }) .refine((data) => Object.keys(data).length > 0, { diff --git a/server/routers/ws/index.ts b/server/routers/ws/index.ts index 16440ec9e..b580b369d 100644 --- a/server/routers/ws/index.ts +++ b/server/routers/ws/index.ts @@ -1,2 +1,2 @@ export * from "./ws"; -export * from "./types"; \ No newline at end of file +export * from "./types"; diff --git a/server/routers/ws/types.ts b/server/routers/ws/types.ts index 7063bc87d..b4ec690b2 100644 --- a/server/routers/ws/types.ts +++ b/server/routers/ws/types.ts @@ -58,7 +58,9 @@ export interface HandlerContext { connectedClients: Map; } -export type MessageHandler = (context: HandlerContext) => Promise; +export type MessageHandler = ( + context: HandlerContext +) => Promise; // Redis message type for cross-node communication export interface RedisMessage { @@ -67,4 +69,4 @@ export interface RedisMessage { excludeClientId?: string; message: WSMessage; fromNodeId: string; -} \ No newline at end of file +} diff --git a/server/routers/ws/ws.ts b/server/routers/ws/ws.ts index abbec8809..0544af9d6 100644 --- a/server/routers/ws/ws.ts +++ b/server/routers/ws/ws.ts @@ -10,7 +10,13 @@ import { validateOlmSessionToken } from "@server/auth/sessions/olm"; import { messageHandlers } from "./messageHandlers"; import logger from "@server/logger"; import { v4 as uuidv4 } from "uuid"; -import { ClientType, TokenPayload, WebSocketRequest, WSMessage, AuthenticatedWebSocket } from "./types"; +import { + ClientType, + TokenPayload, + WebSocketRequest, + WSMessage, + AuthenticatedWebSocket +} from "./types"; import { validateSessionToken } from "@server/auth/sessions/app"; // Subset of TokenPayload for public ws.ts (newt and olm only) @@ -32,7 +38,11 @@ const connectedClients: Map = new Map(); const getClientMapKey = (clientId: string) => clientId; // Helper functions for client management -const addClient = async (clientType: ClientType, clientId: string, ws: AuthenticatedWebSocket): Promise => { +const addClient = async ( + clientType: ClientType, + clientId: string, + ws: AuthenticatedWebSocket +): Promise => { // Generate unique connection ID const connectionId = uuidv4(); ws.connectionId = connectionId; @@ -43,33 +53,46 @@ const addClient = async (clientType: ClientType, clientId: string, ws: Authentic existingClients.push(ws); connectedClients.set(mapKey, existingClients); - logger.info(`Client added to tracking - ${clientType.toUpperCase()} ID: ${clientId}, Connection ID: ${connectionId}, Total connections: ${existingClients.length}`); + logger.info( + `Client added to tracking - ${clientType.toUpperCase()} ID: ${clientId}, Connection ID: ${connectionId}, Total connections: ${existingClients.length}` + ); }; -const removeClient = async (clientType: ClientType, clientId: string, ws: AuthenticatedWebSocket): Promise => { +const removeClient = async ( + clientType: ClientType, + clientId: string, + ws: AuthenticatedWebSocket +): Promise => { const mapKey = getClientMapKey(clientId); const existingClients = connectedClients.get(mapKey) || []; - const updatedClients = existingClients.filter(client => client !== ws); + const updatedClients = existingClients.filter((client) => client !== ws); if (updatedClients.length === 0) { connectedClients.delete(mapKey); - logger.info(`All connections removed for ${clientType.toUpperCase()} ID: ${clientId}`); + logger.info( + `All connections removed for ${clientType.toUpperCase()} ID: ${clientId}` + ); } else { connectedClients.set(mapKey, updatedClients); - logger.info(`Connection removed - ${clientType.toUpperCase()} ID: ${clientId}, Remaining connections: ${updatedClients.length}`); + logger.info( + `Connection removed - ${clientType.toUpperCase()} ID: ${clientId}, Remaining connections: ${updatedClients.length}` + ); } }; // Local message sending (within this node) -const sendToClientLocal = async (clientId: string, message: WSMessage): Promise => { +const sendToClientLocal = async ( + clientId: string, + message: WSMessage +): Promise => { const mapKey = getClientMapKey(clientId); const clients = connectedClients.get(mapKey); if (!clients || clients.length === 0) { return false; } const messageString = JSON.stringify(message); - clients.forEach(client => { + clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { client.send(messageString); } @@ -77,11 +100,14 @@ const sendToClientLocal = async (clientId: string, message: WSMessage): Promise< return true; }; -const broadcastToAllExceptLocal = async (message: WSMessage, excludeClientId?: string): Promise => { +const broadcastToAllExceptLocal = async ( + message: WSMessage, + excludeClientId?: string +): Promise => { connectedClients.forEach((clients, mapKey) => { const [type, id] = mapKey.split(":"); if (!(excludeClientId && id === excludeClientId)) { - clients.forEach(client => { + clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { client.send(JSON.stringify(message)); } @@ -91,39 +117,53 @@ const broadcastToAllExceptLocal = async (message: WSMessage, excludeClientId?: s }; // Cross-node message sending -const sendToClient = async (clientId: string, message: WSMessage): Promise => { +const sendToClient = async ( + clientId: string, + message: WSMessage +): Promise => { // Try to send locally first const localSent = await sendToClientLocal(clientId, message); - logger.debug(`sendToClient: Message type ${message.type} sent to clientId ${clientId}`); + logger.debug( + `sendToClient: Message type ${message.type} sent to clientId ${clientId}` + ); return localSent; }; -const broadcastToAllExcept = async (message: WSMessage, excludeClientId?: string): Promise => { +const broadcastToAllExcept = async ( + message: WSMessage, + excludeClientId?: string +): Promise => { // Broadcast locally await broadcastToAllExceptLocal(message, excludeClientId); }; // Check if a client has active connections across all nodes const hasActiveConnections = async (clientId: string): Promise => { - const mapKey = getClientMapKey(clientId); - const clients = connectedClients.get(mapKey); - return !!(clients && clients.length > 0); + const mapKey = getClientMapKey(clientId); + const clients = connectedClients.get(mapKey); + return !!(clients && clients.length > 0); }; // Get all active nodes for a client -const getActiveNodes = async (clientType: ClientType, clientId: string): Promise => { - const mapKey = getClientMapKey(clientId); - const clients = connectedClients.get(mapKey); - return (clients && clients.length > 0) ? [NODE_ID] : []; +const getActiveNodes = async ( + clientType: ClientType, + clientId: string +): Promise => { + const mapKey = getClientMapKey(clientId); + const clients = connectedClients.get(mapKey); + return clients && clients.length > 0 ? [NODE_ID] : []; }; // Token verification middleware -const verifyToken = async (token: string, clientType: ClientType, userToken: string): Promise => { - -try { - if (clientType === 'newt') { +const verifyToken = async ( + token: string, + clientType: ClientType, + userToken: string +): Promise => { + try { + if (clientType === "newt") { const { session, newt } = await validateNewtSessionToken(token); if (!session || !newt) { return null; @@ -136,7 +176,7 @@ try { return null; } return { client: existingNewt[0], session, clientType }; - } else if (clientType === 'olm') { + } else if (clientType === "olm") { const { session, olm } = await validateOlmSessionToken(token); if (!session || !olm) { return null; @@ -149,8 +189,10 @@ try { return null; } - if (olm.userId) { // this is a user device and we need to check the user token - const { session: userSession, user } = await validateSessionToken(userToken); + if (olm.userId) { + // this is a user device and we need to check the user token + const { session: userSession, user } = + await validateSessionToken(userToken); if (!userSession || !user) { return null; } @@ -161,7 +203,7 @@ try { return { client: existingOlm[0], session, clientType }; } - + return null; } catch (error) { logger.error("Token verification failed:", error); @@ -169,7 +211,11 @@ try { } }; -const setupConnection = async (ws: AuthenticatedWebSocket, client: Newt | Olm, clientType: "newt" | "olm"): Promise => { +const setupConnection = async ( + ws: AuthenticatedWebSocket, + client: Newt | Olm, + clientType: "newt" | "olm" +): Promise => { logger.info("Establishing websocket connection"); if (!client) { logger.error("Connection attempt without client"); @@ -180,7 +226,8 @@ const setupConnection = async (ws: AuthenticatedWebSocket, client: Newt | Olm, c ws.clientType = clientType; // Add client to tracking - const clientId = clientType === 'newt' ? (client as Newt).newtId : (client as Olm).olmId; + const clientId = + clientType === "newt" ? (client as Newt).newtId : (client as Olm).olmId; await addClient(clientType, clientId, ws); ws.on("message", async (data) => { @@ -188,7 +235,9 @@ const setupConnection = async (ws: AuthenticatedWebSocket, client: Newt | Olm, c const message: WSMessage = JSON.parse(data.toString()); if (!message.type || typeof message.type !== "string") { - throw new Error("Invalid message format: missing or invalid type"); + throw new Error( + "Invalid message format: missing or invalid type" + ); } const handler = messageHandlers[message.type]; @@ -213,33 +262,48 @@ const setupConnection = async (ws: AuthenticatedWebSocket, client: Newt | Olm, c response.excludeSender ? clientId : undefined ); } else if (response.targetClientId) { - await sendToClient(response.targetClientId, response.message); + await sendToClient( + response.targetClientId, + response.message + ); } else { ws.send(JSON.stringify(response.message)); } } } catch (error) { logger.error("Message handling error:", error); - ws.send(JSON.stringify({ - type: "error", - data: { - message: error instanceof Error ? error.message : "Unknown error occurred", - originalMessage: data.toString() - } - })); + ws.send( + JSON.stringify({ + type: "error", + data: { + message: + error instanceof Error + ? error.message + : "Unknown error occurred", + originalMessage: data.toString() + } + }) + ); } }); ws.on("close", () => { removeClient(clientType, clientId, ws); - logger.info(`Client disconnected - ${clientType.toUpperCase()} ID: ${clientId}`); + logger.info( + `Client disconnected - ${clientType.toUpperCase()} ID: ${clientId}` + ); }); ws.on("error", (error: Error) => { - logger.error(`WebSocket error for ${clientType.toUpperCase()} ID ${clientId}:`, error); + logger.error( + `WebSocket error for ${clientType.toUpperCase()} ID ${clientId}:`, + error + ); }); - logger.info(`WebSocket connection established - ${clientType.toUpperCase()} ID: ${clientId}`); + logger.info( + `WebSocket connection established - ${clientType.toUpperCase()} ID: ${clientId}` + ); }; // Router endpoint @@ -249,55 +313,89 @@ router.get("/ws", (req: Request, res: Response) => { // WebSocket upgrade handler const handleWSUpgrade = (server: HttpServer): void => { - server.on("upgrade", async (request: WebSocketRequest, socket: Socket, head: Buffer) => { - try { - const url = new URL(request.url || '', `http://${request.headers.host}`); - const token = url.searchParams.get('token') || request.headers["sec-websocket-protocol"] || ''; - const userToken = url.searchParams.get('userToken') || ''; - let clientType = url.searchParams.get('clientType') as ClientType; + server.on( + "upgrade", + async (request: WebSocketRequest, socket: Socket, head: Buffer) => { + try { + const url = new URL( + request.url || "", + `http://${request.headers.host}` + ); + const token = + url.searchParams.get("token") || + request.headers["sec-websocket-protocol"] || + ""; + const userToken = url.searchParams.get("userToken") || ""; + let clientType = url.searchParams.get( + "clientType" + ) as ClientType; - if (!clientType) { - clientType = "newt"; - } + if (!clientType) { + clientType = "newt"; + } - if (!token || !clientType || !['newt', 'olm'].includes(clientType)) { - logger.warn("Unauthorized connection attempt: invalid token or client type..."); - socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n"); + if ( + !token || + !clientType || + !["newt", "olm"].includes(clientType) + ) { + logger.warn( + "Unauthorized connection attempt: invalid token or client type..." + ); + socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n"); + socket.destroy(); + return; + } + + const tokenPayload = await verifyToken( + token, + clientType, + userToken + ); + if (!tokenPayload) { + logger.warn( + "Unauthorized connection attempt: invalid token..." + ); + socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n"); + socket.destroy(); + return; + } + + wss.handleUpgrade( + request, + socket, + head, + (ws: AuthenticatedWebSocket) => { + setupConnection( + ws, + tokenPayload.client, + tokenPayload.clientType + ); + } + ); + } catch (error) { + logger.error("WebSocket upgrade error:", error); + socket.write("HTTP/1.1 500 Internal Server Error\r\n\r\n"); socket.destroy(); - return; } - - const tokenPayload = await verifyToken(token, clientType, userToken); - if (!tokenPayload) { - logger.warn("Unauthorized connection attempt: invalid token..."); - socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n"); - socket.destroy(); - return; - } - - wss.handleUpgrade(request, socket, head, (ws: AuthenticatedWebSocket) => { - setupConnection(ws, tokenPayload.client, tokenPayload.clientType); - }); - } catch (error) { - logger.error("WebSocket upgrade error:", error); - socket.write("HTTP/1.1 500 Internal Server Error\r\n\r\n"); - socket.destroy(); } - }); + ); }; // Disconnect a specific client and force them to reconnect const disconnectClient = async (clientId: string): Promise => { const mapKey = getClientMapKey(clientId); const clients = connectedClients.get(mapKey); - + if (!clients || clients.length === 0) { logger.debug(`No connections found for client ID: ${clientId}`); return false; } - logger.info(`Disconnecting client ID: ${clientId} (${clients.length} connection(s))`); - + logger.info( + `Disconnecting client ID: ${clientId} (${clients.length} connection(s))` + ); + // Close all connections for this client clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { @@ -313,16 +411,16 @@ const cleanup = async (): Promise => { try { // Close all WebSocket connections connectedClients.forEach((clients) => { - clients.forEach(client => { + clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { client.terminate(); } }); }); - logger.info('WebSocket cleanup completed'); + logger.info("WebSocket cleanup completed"); } catch (error) { - logger.error('Error during WebSocket cleanup:', error); + logger.error("Error during WebSocket cleanup:", error); } }; diff --git a/server/setup/ensureSetupToken.ts b/server/setup/ensureSetupToken.ts index 46a62ca53..64298029c 100644 --- a/server/setup/ensureSetupToken.ts +++ b/server/setup/ensureSetupToken.ts @@ -31,7 +31,9 @@ export async function ensureSetupToken() { // If admin exists, no need for setup token if (existingAdmin) { - logger.debug("Server admin exists. Setup token generation skipped."); + logger.debug( + "Server admin exists. Setup token generation skipped." + ); return; } diff --git a/server/setup/migrationsPg.ts b/server/setup/migrationsPg.ts index c778cca3b..0fc42f9d5 100644 --- a/server/setup/migrationsPg.ts +++ b/server/setup/migrationsPg.ts @@ -30,7 +30,7 @@ const migrations = [ { version: "1.11.0", run: m7 }, { version: "1.11.1", run: m8 }, { version: "1.12.0", run: m9 }, - { version: "1.13.0", run: m10 }, + { version: "1.13.0", run: m10 } // Add new migrations here as they are created ] as { version: string; diff --git a/server/setup/scriptsPg/1.12.0.ts b/server/setup/scriptsPg/1.12.0.ts index 38cdaf436..d3c257e32 100644 --- a/server/setup/scriptsPg/1.12.0.ts +++ b/server/setup/scriptsPg/1.12.0.ts @@ -9,7 +9,9 @@ export default async function migration() { try { await db.execute(sql`BEGIN`); - await db.execute(sql`UPDATE "resourceRules" SET "match" = 'COUNTRY' WHERE "match" = 'GEOIP'`); + await db.execute( + sql`UPDATE "resourceRules" SET "match" = 'COUNTRY' WHERE "match" = 'GEOIP'` + ); await db.execute(sql` CREATE TABLE "accessAuditLog" ( @@ -92,40 +94,97 @@ export default async function migration() { ); `); - await db.execute(sql`ALTER TABLE "blueprints" ADD CONSTRAINT "blueprints_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;`); + await db.execute( + sql`ALTER TABLE "blueprints" ADD CONSTRAINT "blueprints_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;` + ); - await db.execute(sql`ALTER TABLE "remoteExitNode" ADD COLUMN "secondaryVersion" varchar;`); - await db.execute(sql`ALTER TABLE "resources" DROP CONSTRAINT "resources_skipToIdpId_idp_idpId_fk";`); - await db.execute(sql`ALTER TABLE "domains" ADD COLUMN "certResolver" varchar;`); - await db.execute(sql`ALTER TABLE "domains" ADD COLUMN "customCertResolver" varchar;`); - await db.execute(sql`ALTER TABLE "domains" ADD COLUMN "preferWildcardCert" boolean;`); - await db.execute(sql`ALTER TABLE "orgs" ADD COLUMN "requireTwoFactor" boolean;`); - await db.execute(sql`ALTER TABLE "orgs" ADD COLUMN "maxSessionLengthHours" integer;`); - await db.execute(sql`ALTER TABLE "orgs" ADD COLUMN "passwordExpiryDays" integer;`); - await db.execute(sql`ALTER TABLE "orgs" ADD COLUMN "settingsLogRetentionDaysRequest" integer DEFAULT 7 NOT NULL;`); - await db.execute(sql`ALTER TABLE "orgs" ADD COLUMN "settingsLogRetentionDaysAccess" integer DEFAULT 0 NOT NULL;`); - await db.execute(sql`ALTER TABLE "orgs" ADD COLUMN "settingsLogRetentionDaysAction" integer DEFAULT 0 NOT NULL;`); - await db.execute(sql`ALTER TABLE "resourceSessions" ADD COLUMN "issuedAt" bigint;`); - await db.execute(sql`ALTER TABLE "resources" ADD COLUMN "proxyProtocol" boolean DEFAULT false NOT NULL;`); - await db.execute(sql`ALTER TABLE "resources" ADD COLUMN "proxyProtocolVersion" integer DEFAULT 1;`); - await db.execute(sql`ALTER TABLE "session" ADD COLUMN "issuedAt" bigint;`); - await db.execute(sql`ALTER TABLE "user" ADD COLUMN "lastPasswordChange" bigint;`); - await db.execute(sql`ALTER TABLE "accessAuditLog" ADD CONSTRAINT "accessAuditLog_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;`); - await db.execute(sql`ALTER TABLE "actionAuditLog" ADD CONSTRAINT "actionAuditLog_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;`); - await db.execute(sql`ALTER TABLE "dnsRecords" ADD CONSTRAINT "dnsRecords_domainId_domains_domainId_fk" FOREIGN KEY ("domainId") REFERENCES "public"."domains"("domainId") ON DELETE cascade ON UPDATE no action;`); - await db.execute(sql`ALTER TABLE "requestAuditLog" ADD CONSTRAINT "requestAuditLog_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;`); - await db.execute(sql`CREATE INDEX "idx_identityAuditLog_timestamp" ON "accessAuditLog" USING btree ("timestamp");`); - await db.execute(sql`CREATE INDEX "idx_identityAuditLog_org_timestamp" ON "accessAuditLog" USING btree ("orgId","timestamp");`); - await db.execute(sql`CREATE INDEX "idx_actionAuditLog_timestamp" ON "actionAuditLog" USING btree ("timestamp");`); - await db.execute(sql`CREATE INDEX "idx_actionAuditLog_org_timestamp" ON "actionAuditLog" USING btree ("orgId","timestamp");`); - await db.execute(sql`CREATE INDEX "idx_requestAuditLog_timestamp" ON "requestAuditLog" USING btree ("timestamp");`); - await db.execute(sql`CREATE INDEX "idx_requestAuditLog_org_timestamp" ON "requestAuditLog" USING btree ("orgId","timestamp");`); - await db.execute(sql`ALTER TABLE "resources" ADD CONSTRAINT "resources_skipToIdpId_idp_idpId_fk" FOREIGN KEY ("skipToIdpId") REFERENCES "public"."idp"("idpId") ON DELETE set null ON UPDATE no action;`); + await db.execute( + sql`ALTER TABLE "remoteExitNode" ADD COLUMN "secondaryVersion" varchar;` + ); + await db.execute( + sql`ALTER TABLE "resources" DROP CONSTRAINT "resources_skipToIdpId_idp_idpId_fk";` + ); + await db.execute( + sql`ALTER TABLE "domains" ADD COLUMN "certResolver" varchar;` + ); + await db.execute( + sql`ALTER TABLE "domains" ADD COLUMN "customCertResolver" varchar;` + ); + await db.execute( + sql`ALTER TABLE "domains" ADD COLUMN "preferWildcardCert" boolean;` + ); + await db.execute( + sql`ALTER TABLE "orgs" ADD COLUMN "requireTwoFactor" boolean;` + ); + await db.execute( + sql`ALTER TABLE "orgs" ADD COLUMN "maxSessionLengthHours" integer;` + ); + await db.execute( + sql`ALTER TABLE "orgs" ADD COLUMN "passwordExpiryDays" integer;` + ); + await db.execute( + sql`ALTER TABLE "orgs" ADD COLUMN "settingsLogRetentionDaysRequest" integer DEFAULT 7 NOT NULL;` + ); + await db.execute( + sql`ALTER TABLE "orgs" ADD COLUMN "settingsLogRetentionDaysAccess" integer DEFAULT 0 NOT NULL;` + ); + await db.execute( + sql`ALTER TABLE "orgs" ADD COLUMN "settingsLogRetentionDaysAction" integer DEFAULT 0 NOT NULL;` + ); + await db.execute( + sql`ALTER TABLE "resourceSessions" ADD COLUMN "issuedAt" bigint;` + ); + await db.execute( + sql`ALTER TABLE "resources" ADD COLUMN "proxyProtocol" boolean DEFAULT false NOT NULL;` + ); + await db.execute( + sql`ALTER TABLE "resources" ADD COLUMN "proxyProtocolVersion" integer DEFAULT 1;` + ); + await db.execute( + sql`ALTER TABLE "session" ADD COLUMN "issuedAt" bigint;` + ); + await db.execute( + sql`ALTER TABLE "user" ADD COLUMN "lastPasswordChange" bigint;` + ); + await db.execute( + sql`ALTER TABLE "accessAuditLog" ADD CONSTRAINT "accessAuditLog_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "actionAuditLog" ADD CONSTRAINT "actionAuditLog_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "dnsRecords" ADD CONSTRAINT "dnsRecords_domainId_domains_domainId_fk" FOREIGN KEY ("domainId") REFERENCES "public"."domains"("domainId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "requestAuditLog" ADD CONSTRAINT "requestAuditLog_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`CREATE INDEX "idx_identityAuditLog_timestamp" ON "accessAuditLog" USING btree ("timestamp");` + ); + await db.execute( + sql`CREATE INDEX "idx_identityAuditLog_org_timestamp" ON "accessAuditLog" USING btree ("orgId","timestamp");` + ); + await db.execute( + sql`CREATE INDEX "idx_actionAuditLog_timestamp" ON "actionAuditLog" USING btree ("timestamp");` + ); + await db.execute( + sql`CREATE INDEX "idx_actionAuditLog_org_timestamp" ON "actionAuditLog" USING btree ("orgId","timestamp");` + ); + await db.execute( + sql`CREATE INDEX "idx_requestAuditLog_timestamp" ON "requestAuditLog" USING btree ("timestamp");` + ); + await db.execute( + sql`CREATE INDEX "idx_requestAuditLog_org_timestamp" ON "requestAuditLog" USING btree ("orgId","timestamp");` + ); + await db.execute( + sql`ALTER TABLE "resources" ADD CONSTRAINT "resources_skipToIdpId_idp_idpId_fk" FOREIGN KEY ("skipToIdpId") REFERENCES "public"."idp"("idpId") ON DELETE set null ON UPDATE no action;` + ); await db.execute(sql`ALTER TABLE "orgs" DROP COLUMN "settings";`); - // get all of the domains - const domainsQuery = await db.execute(sql`SELECT "domainId", "baseDomain" FROM "domains"`); + const domainsQuery = await db.execute( + sql`SELECT "domainId", "baseDomain" FROM "domains"` + ); const domains = domainsQuery.rows as { domainId: string; baseDomain: string; @@ -135,11 +194,11 @@ export default async function migration() { // insert two records into the dnsRecords table for each domain await db.execute(sql` INSERT INTO "dnsRecords" ("domainId", "recordType", "baseDomain", "value", "verified") - VALUES (${domain.domainId}, 'A', ${`*.${domain.baseDomain}`}, ${'Server IP Address'}, true) + VALUES (${domain.domainId}, 'A', ${`*.${domain.baseDomain}`}, ${"Server IP Address"}, true) `); await db.execute(sql` INSERT INTO "dnsRecords" ("domainId", "recordType", "baseDomain", "value", "verified") - VALUES (${domain.domainId}, 'A', ${domain.baseDomain}, ${'Server IP Address'}, true) + VALUES (${domain.domainId}, 'A', ${domain.baseDomain}, ${"Server IP Address"}, true) `); } diff --git a/server/setup/scriptsPg/1.13.0.ts b/server/setup/scriptsPg/1.13.0.ts index e13276dff..9a56706c0 100644 --- a/server/setup/scriptsPg/1.13.0.ts +++ b/server/setup/scriptsPg/1.13.0.ts @@ -255,7 +255,9 @@ export default async function migration() { const siteDataQuery = await db.execute(sql` SELECT "orgId" FROM "sites" WHERE "siteId" = ${site.siteId} `); - const siteData = siteDataQuery.rows[0] as { orgId: string } | undefined; + const siteData = siteDataQuery.rows[0] as + | { orgId: string } + | undefined; if (!siteData) continue; const subnets = site.remoteSubnets.split(","); diff --git a/server/setup/scriptsPg/1.7.0.ts b/server/setup/scriptsPg/1.7.0.ts index 3cb799e04..aa740ecb3 100644 --- a/server/setup/scriptsPg/1.7.0.ts +++ b/server/setup/scriptsPg/1.7.0.ts @@ -121,7 +121,7 @@ export default async function migration() { try { await db.execute(sql`BEGIN`); - + // Update all existing orgs to have the default subnet await db.execute(sql`UPDATE "orgs" SET "subnet" = '100.90.128.0/24'`); diff --git a/server/setup/scriptsPg/1.9.0.ts b/server/setup/scriptsPg/1.9.0.ts index fdbf3ae99..eac7ade92 100644 --- a/server/setup/scriptsPg/1.9.0.ts +++ b/server/setup/scriptsPg/1.9.0.ts @@ -11,7 +11,9 @@ export default async function migration() { try { // Get the first siteId to use as default - const firstSite = await db.execute(sql`SELECT "siteId" FROM "sites" LIMIT 1`); + const firstSite = await db.execute( + sql`SELECT "siteId" FROM "sites" LIMIT 1` + ); if (firstSite.rows.length > 0) { firstSiteId = firstSite.rows[0].siteId as number; } @@ -52,33 +54,59 @@ export default async function migration() { "enabled" boolean DEFAULT true NOT NULL );`); - await db.execute(sql`ALTER TABLE "resources" DROP CONSTRAINT "resources_siteId_sites_siteId_fk";`); + await db.execute( + sql`ALTER TABLE "resources" DROP CONSTRAINT "resources_siteId_sites_siteId_fk";` + ); - await db.execute(sql`ALTER TABLE "clients" ALTER COLUMN "lastPing" TYPE integer USING NULL;`); + await db.execute( + sql`ALTER TABLE "clients" ALTER COLUMN "lastPing" TYPE integer USING NULL;` + ); - await db.execute(sql`ALTER TABLE "clientSites" ADD COLUMN "endpoint" varchar;`); + await db.execute( + sql`ALTER TABLE "clientSites" ADD COLUMN "endpoint" varchar;` + ); - await db.execute(sql`ALTER TABLE "exitNodes" ADD COLUMN "online" boolean DEFAULT false NOT NULL;`); + await db.execute( + sql`ALTER TABLE "exitNodes" ADD COLUMN "online" boolean DEFAULT false NOT NULL;` + ); - await db.execute(sql`ALTER TABLE "exitNodes" ADD COLUMN "lastPing" integer;`); + await db.execute( + sql`ALTER TABLE "exitNodes" ADD COLUMN "lastPing" integer;` + ); - await db.execute(sql`ALTER TABLE "exitNodes" ADD COLUMN "type" text DEFAULT 'gerbil';`); + await db.execute( + sql`ALTER TABLE "exitNodes" ADD COLUMN "type" text DEFAULT 'gerbil';` + ); await db.execute(sql`ALTER TABLE "olms" ADD COLUMN "version" text;`); await db.execute(sql`ALTER TABLE "orgs" ADD COLUMN "createdAt" text;`); - await db.execute(sql`ALTER TABLE "resources" ADD COLUMN "skipToIdpId" integer;`); + await db.execute( + sql`ALTER TABLE "resources" ADD COLUMN "skipToIdpId" integer;` + ); - await db.execute(sql.raw(`ALTER TABLE "targets" ADD COLUMN "siteId" integer NOT NULL DEFAULT ${firstSiteId || 1};`)); + await db.execute( + sql.raw( + `ALTER TABLE "targets" ADD COLUMN "siteId" integer NOT NULL DEFAULT ${firstSiteId || 1};` + ) + ); - await db.execute(sql`ALTER TABLE "siteResources" ADD CONSTRAINT "siteResources_siteId_sites_siteId_fk" FOREIGN KEY ("siteId") REFERENCES "public"."sites"("siteId") ON DELETE cascade ON UPDATE no action;`); + await db.execute( + sql`ALTER TABLE "siteResources" ADD CONSTRAINT "siteResources_siteId_sites_siteId_fk" FOREIGN KEY ("siteId") REFERENCES "public"."sites"("siteId") ON DELETE cascade ON UPDATE no action;` + ); - await db.execute(sql`ALTER TABLE "siteResources" ADD CONSTRAINT "siteResources_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;`); + await db.execute( + sql`ALTER TABLE "siteResources" ADD CONSTRAINT "siteResources_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;` + ); - await db.execute(sql`ALTER TABLE "resources" ADD CONSTRAINT "resources_skipToIdpId_idp_idpId_fk" FOREIGN KEY ("skipToIdpId") REFERENCES "public"."idp"("idpId") ON DELETE cascade ON UPDATE no action;`); + await db.execute( + sql`ALTER TABLE "resources" ADD CONSTRAINT "resources_skipToIdpId_idp_idpId_fk" FOREIGN KEY ("skipToIdpId") REFERENCES "public"."idp"("idpId") ON DELETE cascade ON UPDATE no action;` + ); - await db.execute(sql`ALTER TABLE "targets" ADD CONSTRAINT "targets_siteId_sites_siteId_fk" FOREIGN KEY ("siteId") REFERENCES "public"."sites"("siteId") ON DELETE cascade ON UPDATE no action;`); + await db.execute( + sql`ALTER TABLE "targets" ADD CONSTRAINT "targets_siteId_sites_siteId_fk" FOREIGN KEY ("siteId") REFERENCES "public"."sites"("siteId") ON DELETE cascade ON UPDATE no action;` + ); await db.execute(sql`ALTER TABLE "clients" DROP COLUMN "endpoint";`); diff --git a/server/setup/scriptsSqlite/1.0.0-beta13.ts b/server/setup/scriptsSqlite/1.0.0-beta13.ts index 9ced727f7..9986b06fd 100644 --- a/server/setup/scriptsSqlite/1.0.0-beta13.ts +++ b/server/setup/scriptsSqlite/1.0.0-beta13.ts @@ -25,7 +25,9 @@ export default async function migration() { console.log(`Added new table and column: resourceRules, applyRules`); } catch (e) { - console.log("Unable to add new table and column: resourceRules, applyRules"); + console.log( + "Unable to add new table and column: resourceRules, applyRules" + ); throw e; } diff --git a/server/setup/scriptsSqlite/1.0.0-beta3.ts b/server/setup/scriptsSqlite/1.0.0-beta3.ts index fccfeb887..5d69af6b3 100644 --- a/server/setup/scriptsSqlite/1.0.0-beta3.ts +++ b/server/setup/scriptsSqlite/1.0.0-beta3.ts @@ -38,4 +38,4 @@ export default async function migration() { fs.writeFileSync(filePath, updatedYaml, "utf8"); console.log("Done."); -} \ No newline at end of file +} diff --git a/server/setup/scriptsSqlite/1.0.0-beta6.ts b/server/setup/scriptsSqlite/1.0.0-beta6.ts index 891296781..a13a7e31f 100644 --- a/server/setup/scriptsSqlite/1.0.0-beta6.ts +++ b/server/setup/scriptsSqlite/1.0.0-beta6.ts @@ -43,7 +43,9 @@ export default async function migration() { const updatedYaml = yaml.dump(rawConfig); fs.writeFileSync(filePath, updatedYaml, "utf8"); } catch (error) { - console.log("We were unable to add CORS to your config file. Please add it manually."); + console.log( + "We were unable to add CORS to your config file. Please add it manually." + ); console.error(error); } diff --git a/server/setup/scriptsSqlite/1.0.0-beta9.ts b/server/setup/scriptsSqlite/1.0.0-beta9.ts index 7cce1c2dd..6d48ed394 100644 --- a/server/setup/scriptsSqlite/1.0.0-beta9.ts +++ b/server/setup/scriptsSqlite/1.0.0-beta9.ts @@ -182,12 +182,15 @@ export default async function migration() { if (parsedConfig.success) { // delete permanent from redirect-to-https middleware - delete traefikConfig.http.middlewares["redirect-to-https"].redirectScheme.permanent; + delete traefikConfig.http.middlewares["redirect-to-https"] + .redirectScheme.permanent; const updatedTraefikYaml = yaml.dump(traefikConfig); fs.writeFileSync(traefikPath, updatedTraefikYaml, "utf8"); - console.log("Deleted permanent from redirect-to-https middleware."); + console.log( + "Deleted permanent from redirect-to-https middleware." + ); } else { console.log(fromZodError(parsedConfig.error)); console.log( diff --git a/server/setup/scriptsSqlite/1.10.0.ts b/server/setup/scriptsSqlite/1.10.0.ts index 3065a664d..03cf24dc8 100644 --- a/server/setup/scriptsSqlite/1.10.0.ts +++ b/server/setup/scriptsSqlite/1.10.0.ts @@ -13,15 +13,11 @@ export default async function migration() { try { const resources = db - .prepare( - "SELECT resourceId FROM resources" - ) + .prepare("SELECT resourceId FROM resources") .all() as Array<{ resourceId: number }>; const siteResources = db - .prepare( - "SELECT siteResourceId FROM siteResources" - ) + .prepare("SELECT siteResourceId FROM siteResources") .all() as Array<{ siteResourceId: number }>; db.transaction(() => { @@ -82,17 +78,13 @@ export default async function migration() { // Handle auto-provisioned users for identity providers const autoProvisionIdps = db - .prepare( - "SELECT idpId FROM idp WHERE autoProvision = 1" - ) + .prepare("SELECT idpId FROM idp WHERE autoProvision = 1") .all() as Array<{ idpId: number }>; for (const idp of autoProvisionIdps) { // Get all users with this identity provider const usersWithIdp = db - .prepare( - "SELECT id FROM user WHERE idpId = ?" - ) + .prepare("SELECT id FROM user WHERE idpId = ?") .all(idp.idpId) as Array<{ id: string }>; // Update userOrgs to set autoProvisioned to true for these users diff --git a/server/setup/scriptsSqlite/1.10.1.ts b/server/setup/scriptsSqlite/1.10.1.ts index f6f9894ed..241815585 100644 --- a/server/setup/scriptsSqlite/1.10.1.ts +++ b/server/setup/scriptsSqlite/1.10.1.ts @@ -5,16 +5,16 @@ import path from "path"; const version = "1.10.1"; export default async function migration() { - console.log(`Running setup script ${version}...`); + console.log(`Running setup script ${version}...`); - const location = path.join(APP_PATH, "db", "db.sqlite"); - const db = new Database(location); + const location = path.join(APP_PATH, "db", "db.sqlite"); + const db = new Database(location); - try { - db.pragma("foreign_keys = OFF"); + try { + db.pragma("foreign_keys = OFF"); - db.transaction(() => { - db.exec(`ALTER TABLE "targets" RENAME TO "targets_old"; + db.transaction(() => { + db.exec(`ALTER TABLE "targets" RENAME TO "targets_old"; --> statement-breakpoint CREATE TABLE "targets" ( "targetId" INTEGER PRIMARY KEY AUTOINCREMENT, @@ -57,13 +57,13 @@ SELECT FROM "targets_old"; --> statement-breakpoint DROP TABLE "targets_old";`); - })(); + })(); - db.pragma("foreign_keys = ON"); + db.pragma("foreign_keys = ON"); - console.log(`Migrated database`); - } catch (e) { - console.log("Failed to migrate db:", e); - throw e; - } -} \ No newline at end of file + console.log(`Migrated database`); + } catch (e) { + console.log("Failed to migrate db:", e); + throw e; + } +} diff --git a/server/setup/scriptsSqlite/1.11.0.ts b/server/setup/scriptsSqlite/1.11.0.ts index c79cfdb46..41d68563f 100644 --- a/server/setup/scriptsSqlite/1.11.0.ts +++ b/server/setup/scriptsSqlite/1.11.0.ts @@ -13,25 +13,29 @@ export default async function migration() { const db = new Database(location); db.transaction(() => { - - db.prepare(` + db.prepare( + ` CREATE TABLE 'account' ( 'accountId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, 'userId' text NOT NULL, FOREIGN KEY ('userId') REFERENCES 'user'('id') ON UPDATE no action ON DELETE cascade ); - `).run(); + ` + ).run(); - db.prepare(` + db.prepare( + ` CREATE TABLE 'accountDomains' ( 'accountId' integer NOT NULL, 'domainId' text NOT NULL, FOREIGN KEY ('accountId') REFERENCES 'account'('accountId') ON UPDATE no action ON DELETE cascade, FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE cascade ); - `).run(); + ` + ).run(); - db.prepare(` + db.prepare( + ` CREATE TABLE 'certificates' ( 'certId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, 'domain' text NOT NULL, @@ -49,11 +53,15 @@ export default async function migration() { 'keyFile' text, FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE cascade ); - `).run(); + ` + ).run(); - db.prepare(`CREATE UNIQUE INDEX 'certificates_domain_unique' ON 'certificates' ('domain');`).run(); + db.prepare( + `CREATE UNIQUE INDEX 'certificates_domain_unique' ON 'certificates' ('domain');` + ).run(); - db.prepare(` + db.prepare( + ` CREATE TABLE 'customers' ( 'customerId' text PRIMARY KEY NOT NULL, 'orgId' text NOT NULL, @@ -65,9 +73,11 @@ export default async function migration() { 'updatedAt' integer NOT NULL, FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade ); - `).run(); + ` + ).run(); - db.prepare(` + db.prepare( + ` CREATE TABLE 'dnsChallenges' ( 'dnsChallengeId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, 'domain' text NOT NULL, @@ -77,26 +87,32 @@ export default async function migration() { 'expiresAt' integer NOT NULL, 'completed' integer DEFAULT false ); - `).run(); + ` + ).run(); - db.prepare(` + db.prepare( + ` CREATE TABLE 'domainNamespaces' ( 'domainNamespaceId' text PRIMARY KEY NOT NULL, 'domainId' text NOT NULL, FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE set null ); - `).run(); + ` + ).run(); - db.prepare(` + db.prepare( + ` CREATE TABLE 'exitNodeOrgs' ( 'exitNodeId' integer NOT NULL, 'orgId' text NOT NULL, FOREIGN KEY ('exitNodeId') REFERENCES 'exitNodes'('exitNodeId') ON UPDATE no action ON DELETE cascade, FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade ); - `).run(); + ` + ).run(); - db.prepare(` + db.prepare( + ` CREATE TABLE 'loginPage' ( 'loginPageId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, 'subdomain' text, @@ -106,27 +122,33 @@ export default async function migration() { FOREIGN KEY ('exitNodeId') REFERENCES 'exitNodes'('exitNodeId') ON UPDATE no action ON DELETE set null, FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE set null ); - `).run(); + ` + ).run(); - db.prepare(` + db.prepare( + ` CREATE TABLE 'loginPageOrg' ( 'loginPageId' integer NOT NULL, 'orgId' text NOT NULL, FOREIGN KEY ('loginPageId') REFERENCES 'loginPage'('loginPageId') ON UPDATE no action ON DELETE cascade, FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade ); - `).run(); + ` + ).run(); - db.prepare(` + db.prepare( + ` CREATE TABLE 'remoteExitNodeSession' ( 'id' text PRIMARY KEY NOT NULL, 'remoteExitNodeId' text NOT NULL, 'expiresAt' integer NOT NULL, FOREIGN KEY ('remoteExitNodeId') REFERENCES 'remoteExitNode'('id') ON UPDATE no action ON DELETE cascade ); - `).run(); + ` + ).run(); - db.prepare(` + db.prepare( + ` CREATE TABLE 'remoteExitNode' ( 'id' text PRIMARY KEY NOT NULL, 'secretHash' text NOT NULL, @@ -135,9 +157,11 @@ export default async function migration() { 'exitNodeId' integer, FOREIGN KEY ('exitNodeId') REFERENCES 'exitNodes'('exitNodeId') ON UPDATE no action ON DELETE cascade ); - `).run(); + ` + ).run(); - db.prepare(` + db.prepare( + ` CREATE TABLE 'sessionTransferToken' ( 'token' text PRIMARY KEY NOT NULL, 'sessionId' text NOT NULL, @@ -145,9 +169,11 @@ export default async function migration() { 'expiresAt' integer NOT NULL, FOREIGN KEY ('sessionId') REFERENCES 'session'('id') ON UPDATE no action ON DELETE cascade ); - `).run(); + ` + ).run(); - db.prepare(` + db.prepare( + ` CREATE TABLE 'subscriptionItems' ( 'subscriptionItemId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, 'subscriptionId' text NOT NULL, @@ -162,9 +188,11 @@ export default async function migration() { 'name' text, FOREIGN KEY ('subscriptionId') REFERENCES 'subscriptions'('subscriptionId') ON UPDATE no action ON DELETE cascade ); - `).run(); + ` + ).run(); - db.prepare(` + db.prepare( + ` CREATE TABLE 'subscriptions' ( 'subscriptionId' text PRIMARY KEY NOT NULL, 'customerId' text NOT NULL, @@ -175,9 +203,11 @@ export default async function migration() { 'billingCycleAnchor' integer, FOREIGN KEY ('customerId') REFERENCES 'customers'('customerId') ON UPDATE no action ON DELETE cascade ); - `).run(); + ` + ).run(); - db.prepare(` + db.prepare( + ` CREATE TABLE 'usage' ( 'usageId' text PRIMARY KEY NOT NULL, 'featureId' text NOT NULL, @@ -191,9 +221,11 @@ export default async function migration() { 'nextRolloverAt' integer, FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade ); - `).run(); + ` + ).run(); - db.prepare(` + db.prepare( + ` CREATE TABLE 'usageNotifications' ( 'notificationId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, 'orgId' text NOT NULL, @@ -203,18 +235,22 @@ export default async function migration() { 'sentAt' integer NOT NULL, FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade ); - `).run(); + ` + ).run(); - db.prepare(` + db.prepare( + ` CREATE TABLE 'resourceHeaderAuth' ( 'headerAuthId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, 'resourceId' integer NOT NULL, 'headerAuthHash' text NOT NULL, FOREIGN KEY ('resourceId') REFERENCES 'resources'('resourceId') ON UPDATE no action ON DELETE cascade ); - `).run(); + ` + ).run(); - db.prepare(` + db.prepare( + ` CREATE TABLE 'targetHealthCheck' ( 'targetHealthCheckId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, 'targetId' integer NOT NULL, @@ -234,11 +270,13 @@ export default async function migration() { 'hcHealth' text DEFAULT 'unknown', FOREIGN KEY ('targetId') REFERENCES 'targets'('targetId') ON UPDATE no action ON DELETE cascade ); - `).run(); + ` + ).run(); db.prepare(`DROP TABLE 'limits';`).run(); - db.prepare(` + db.prepare( + ` CREATE TABLE 'limits' ( 'limitId' text PRIMARY KEY NOT NULL, 'featureId' text NOT NULL, @@ -247,12 +285,15 @@ export default async function migration() { 'description' text, FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade ); - `).run(); + ` + ).run(); db.prepare(`ALTER TABLE 'orgs' ADD 'settings' text;`).run(); db.prepare(`ALTER TABLE 'targets' ADD 'rewritePath' text;`).run(); db.prepare(`ALTER TABLE 'targets' ADD 'rewritePathType' text;`).run(); - db.prepare(`ALTER TABLE 'targets' ADD 'priority' integer DEFAULT 100 NOT NULL;`).run(); + db.prepare( + `ALTER TABLE 'targets' ADD 'priority' integer DEFAULT 100 NOT NULL;` + ).run(); const webauthnCredentials = db .prepare( @@ -269,7 +310,7 @@ export default async function migration() { dateCreated: string; }[]; - db.prepare(`DELETE FROM 'webauthnCredentials';`).run(); + db.prepare(`DELETE FROM 'webauthnCredentials';`).run(); for (const webauthnCredential of webauthnCredentials) { const newCredentialId = isoBase64URL.fromBuffer( @@ -304,7 +345,9 @@ export default async function migration() { ).run(); // 2. Select all rows - const resources = db.prepare(`SELECT resourceId FROM resources`).all() as { + const resources = db + .prepare(`SELECT resourceId FROM resources`) + .all() as { resourceId: number; }[]; diff --git a/server/setup/scriptsSqlite/1.12.0.ts b/server/setup/scriptsSqlite/1.12.0.ts index bb357c81f..292f1f05f 100644 --- a/server/setup/scriptsSqlite/1.12.0.ts +++ b/server/setup/scriptsSqlite/1.12.0.ts @@ -112,7 +112,6 @@ export default async function migration() { ` ).run(); - db.prepare( ` CREATE TABLE 'blueprints' ( @@ -212,10 +211,14 @@ export default async function migration() { db.prepare( `ALTER TABLE 'user' ADD 'lastPasswordChange' integer;` ).run(); - db.prepare(`ALTER TABLE 'remoteExitNode' ADD 'secondaryVersion' text;`).run(); + db.prepare( + `ALTER TABLE 'remoteExitNode' ADD 'secondaryVersion' text;` + ).run(); // get all of the domains - const domains = db.prepare(`SELECT domainId, baseDomain from domains`).all() as { + const domains = db + .prepare(`SELECT domainId, baseDomain from domains`) + .all() as { domainId: number; baseDomain: string; }[]; diff --git a/server/setup/scriptsSqlite/1.13.0.ts b/server/setup/scriptsSqlite/1.13.0.ts index 5b2bcf014..df8d73443 100644 --- a/server/setup/scriptsSqlite/1.13.0.ts +++ b/server/setup/scriptsSqlite/1.13.0.ts @@ -287,7 +287,10 @@ export default async function migration() { let aliasIpOctet = 8; for (const siteResource of siteResourcesForAlias) { const aliasAddress = `100.96.128.${aliasIpOctet}`; - updateAliasAddress.run(aliasAddress, siteResource.siteResourceId); + updateAliasAddress.run( + aliasAddress, + siteResource.siteResourceId + ); aliasIpOctet++; } @@ -303,7 +306,12 @@ export default async function migration() { for (const subnet of subnets) { // Generate a unique niceId for each new site resource let niceId = generateName(); - insertCidrResource.run(site.siteId, subnet.trim(), niceId, site.siteId); + insertCidrResource.run( + site.siteId, + subnet.trim(), + niceId, + site.siteId + ); } } } diff --git a/server/setup/scriptsSqlite/1.5.0.ts b/server/setup/scriptsSqlite/1.5.0.ts index 46e9cccaa..10c122942 100644 --- a/server/setup/scriptsSqlite/1.5.0.ts +++ b/server/setup/scriptsSqlite/1.5.0.ts @@ -48,9 +48,7 @@ export default async function migration() { const rawConfig = yaml.load(fileContents) as any; if (rawConfig.cors?.headers) { - const headers = JSON.parse( - JSON.stringify(rawConfig.cors.headers) - ); + const headers = JSON.parse(JSON.stringify(rawConfig.cors.headers)); rawConfig.cors.allowed_headers = headers; delete rawConfig.cors.headers; } @@ -61,9 +59,7 @@ export default async function migration() { console.log(`Migrated CORS headers to allowed_headers`); } catch (e) { - console.log( - `Unable to migrate config file. Error: ${e}` - ); + console.log(`Unable to migrate config file. Error: ${e}`); } console.log(`${version} migration complete`); diff --git a/server/setup/scriptsSqlite/1.6.0.ts b/server/setup/scriptsSqlite/1.6.0.ts index adab26977..45abe693b 100644 --- a/server/setup/scriptsSqlite/1.6.0.ts +++ b/server/setup/scriptsSqlite/1.6.0.ts @@ -58,7 +58,9 @@ export default async function migration() { console.log(`Set trust_proxy to 1 in config file`); } catch (e) { - console.log(`Unable to migrate config file. Please do it manually. Error: ${e}`); + console.log( + `Unable to migrate config file. Please do it manually. Error: ${e}` + ); } console.log(`${version} migration complete`); diff --git a/server/setup/scriptsSqlite/1.9.0.ts b/server/setup/scriptsSqlite/1.9.0.ts index 5f247ea50..89d7b595f 100644 --- a/server/setup/scriptsSqlite/1.9.0.ts +++ b/server/setup/scriptsSqlite/1.9.0.ts @@ -11,26 +11,28 @@ export default async function migration() { const db = new Database(location); const resourceSiteMap = new Map(); - let firstSiteId: number = 1; + let firstSiteId: number = 1; - try { - // Get the first siteId to use as default - const firstSite = db.prepare("SELECT siteId FROM sites LIMIT 1").get() as { siteId: number } | undefined; - if (firstSite) { - firstSiteId = firstSite.siteId; - } + try { + // Get the first siteId to use as default + const firstSite = db + .prepare("SELECT siteId FROM sites LIMIT 1") + .get() as { siteId: number } | undefined; + if (firstSite) { + firstSiteId = firstSite.siteId; + } - const resources = db - .prepare( - "SELECT resourceId, siteId FROM resources WHERE siteId IS NOT NULL" - ) - .all() as Array<{ resourceId: number; siteId: number }>; - for (const resource of resources) { - resourceSiteMap.set(resource.resourceId, resource.siteId); - } - } catch (e) { - console.log("Error getting resources:", e); - } + const resources = db + .prepare( + "SELECT resourceId, siteId FROM resources WHERE siteId IS NOT NULL" + ) + .all() as Array<{ resourceId: number; siteId: number }>; + for (const resource of resources) { + resourceSiteMap.set(resource.resourceId, resource.siteId); + } + } catch (e) { + console.log("Error getting resources:", e); + } try { db.pragma("foreign_keys = OFF"); diff --git a/server/types/HttpCode.ts b/server/types/HttpCode.ts index 70f210538..a20c8577d 100644 --- a/server/types/HttpCode.ts +++ b/server/types/HttpCode.ts @@ -59,7 +59,7 @@ export enum HttpCode { INSUFFICIENT_STORAGE = 507, LOOP_DETECTED = 508, NOT_EXTENDED = 510, - NETWORK_AUTHENTICATION_REQUIRED = 511, + NETWORK_AUTHENTICATION_REQUIRED = 511 } export default HttpCode; diff --git a/src/app/[orgId]/settings/(private)/billing/layout.tsx b/src/app/[orgId]/settings/(private)/billing/layout.tsx index 538c7fde6..e52f19edf 100644 --- a/src/app/[orgId]/settings/(private)/billing/layout.tsx +++ b/src/app/[orgId]/settings/(private)/billing/layout.tsx @@ -10,7 +10,7 @@ import { GetOrgUserResponse } from "@server/routers/user"; import { AxiosResponse } from "axios"; import { redirect } from "next/navigation"; import { cache } from "react"; -import { getTranslations } from 'next-intl/server'; +import { getTranslations } from "next-intl/server"; type BillingSettingsProps = { children: React.ReactNode; @@ -19,7 +19,7 @@ type BillingSettingsProps = { export default async function BillingSettingsPage({ children, - params, + params }: BillingSettingsProps) { const { orgId } = await params; @@ -35,8 +35,8 @@ export default async function BillingSettingsPage({ const getOrgUser = cache(async () => internal.get>( `/org/${orgId}/user/${user.userId}`, - await authCookieHeader(), - ), + await authCookieHeader() + ) ); const res = await getOrgUser(); orgUser = res.data.data; @@ -49,8 +49,8 @@ export default async function BillingSettingsPage({ const getOrg = cache(async () => internal.get>( `/org/${orgId}`, - await authCookieHeader(), - ), + await authCookieHeader() + ) ); const res = await getOrg(); org = res.data.data; @@ -65,11 +65,11 @@ export default async function BillingSettingsPage({ - {children} + {children} diff --git a/src/app/[orgId]/settings/(private)/idp/create/page.tsx b/src/app/[orgId]/settings/(private)/idp/create/page.tsx index 8667abda8..a899a2aa3 100644 --- a/src/app/[orgId]/settings/(private)/idp/create/page.tsx +++ b/src/app/[orgId]/settings/(private)/idp/create/page.tsx @@ -64,10 +64,8 @@ export default function Page() { clientSecret: z .string() .min(1, { message: t("idpClientSecretRequired") }), - authUrl: z.url({ message: t("idpErrorAuthUrlInvalid") }) - .optional(), - tokenUrl: z.url({ message: t("idpErrorTokenUrlInvalid") }) - .optional(), + authUrl: z.url({ message: t("idpErrorAuthUrlInvalid") }).optional(), + tokenUrl: z.url({ message: t("idpErrorTokenUrlInvalid") }).optional(), identifierPath: z .string() .min(1, { message: t("idpPathRequired") }) @@ -379,9 +377,11 @@ export default function Page() { > { form.setValue( "autoProvision", diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesDataTable.tsx b/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesDataTable.tsx index a1bb69c06..c12aa9ba1 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesDataTable.tsx +++ b/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesDataTable.tsx @@ -19,18 +19,17 @@ export function ExitNodesDataTable({ onRefresh, isRefreshing }: DataTableProps) { - const t = useTranslations(); return ( { const remoteExitNodeId = searchParams.get("remoteExitNodeId"); const remoteExitNodeSecret = searchParams.get("remoteExitNodeSecret"); - + if (remoteExitNodeId && remoteExitNodeSecret) { setStrategy("adopt"); form.setValue("remoteExitNodeId", remoteExitNodeId); diff --git a/src/app/[orgId]/settings/access/invitations/page.tsx b/src/app/[orgId]/settings/access/invitations/page.tsx index d7fee3228..b6ee14484 100644 --- a/src/app/[orgId]/settings/access/invitations/page.tsx +++ b/src/app/[orgId]/settings/access/invitations/page.tsx @@ -1,7 +1,9 @@ import { internal } from "@app/lib/api"; import { authCookieHeader } from "@app/lib/api/cookies"; import { AxiosResponse } from "axios"; -import InvitationsTable, { InvitationRow } from "../../../../../components/InvitationsTable"; +import InvitationsTable, { + InvitationRow +} from "../../../../../components/InvitationsTable"; import { GetOrgResponse } from "@server/routers/org"; import { cache } from "react"; import OrgProvider from "@app/providers/OrgProvider"; @@ -9,7 +11,7 @@ import UserProvider from "@app/providers/UserProvider"; import { verifySession } from "@app/lib/auth/verifySession"; import AccessPageHeaderAndNav from "../../../../../components/AccessPageHeaderAndNav"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; -import { getTranslations } from 'next-intl/server'; +import { getTranslations } from "next-intl/server"; type InvitationsPageProps = { params: Promise<{ orgId: string }>; @@ -68,7 +70,7 @@ export default async function InvitationsPage(props: InvitationsPageProps) { id: invite.inviteId, email: invite.email, expiresAt: new Date(Number(invite.expiresAt)).toISOString(), - role: invite.roleName || t('accessRoleUnknown'), + role: invite.roleName || t("accessRoleUnknown"), roleId: invite.roleId }; }); @@ -76,8 +78,8 @@ export default async function InvitationsPage(props: InvitationsPageProps) { return ( <> diff --git a/src/app/[orgId]/settings/access/roles/page.tsx b/src/app/[orgId]/settings/access/roles/page.tsx index cffe4ed9e..c4818abe7 100644 --- a/src/app/[orgId]/settings/access/roles/page.tsx +++ b/src/app/[orgId]/settings/access/roles/page.tsx @@ -7,7 +7,7 @@ import OrgProvider from "@app/providers/OrgProvider"; import { ListRolesResponse } from "@server/routers/role"; import RolesTable, { RoleRow } from "../../../../../components/RolesTable"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; -import { getTranslations } from 'next-intl/server'; +import { getTranslations } from "next-intl/server"; type RolesPageProps = { params: Promise<{ orgId: string }>; @@ -66,8 +66,8 @@ export default async function RolesPage(props: RolesPageProps) { return ( <> diff --git a/src/app/[orgId]/settings/access/users/create/page.tsx b/src/app/[orgId]/settings/access/users/create/page.tsx index 9417282d5..3199d817f 100644 --- a/src/app/[orgId]/settings/access/users/create/page.tsx +++ b/src/app/[orgId]/settings/access/users/create/page.tsx @@ -106,7 +106,8 @@ export default function Page() { const genericOidcFormSchema = z.object({ username: z.string().min(1, { message: t("usernameRequired") }), - email: z.email({ message: t("emailInvalid") }) + email: z + .email({ message: t("emailInvalid") }) .optional() .or(z.literal("")), name: z.string().optional(), diff --git a/src/app/[orgId]/settings/access/users/page.tsx b/src/app/[orgId]/settings/access/users/page.tsx index 453aca2f6..662ada608 100644 --- a/src/app/[orgId]/settings/access/users/page.tsx +++ b/src/app/[orgId]/settings/access/users/page.tsx @@ -10,7 +10,7 @@ import UserProvider from "@app/providers/UserProvider"; import { verifySession } from "@app/lib/auth/verifySession"; import AccessPageHeaderAndNav from "../../../../../components/AccessPageHeaderAndNav"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; -import { getTranslations } from 'next-intl/server'; +import { getTranslations } from "next-intl/server"; type UsersPageProps = { params: Promise<{ orgId: string }>; @@ -79,9 +79,11 @@ export default async function UsersPage(props: UsersPageProps) { type: user.type, idpVariant: user.idpVariant, idpId: user.idpId, - idpName: user.idpName || t('idpNameInternal'), - status: t('userConfirmed'), - role: user.isOwner ? t('accessRoleOwner') : user.roleName || t('accessRoleMember'), + idpName: user.idpName || t("idpNameInternal"), + status: t("userConfirmed"), + role: user.isOwner + ? t("accessRoleOwner") + : user.roleName || t("accessRoleMember"), isOwner: user.isOwner || false }; }); @@ -89,8 +91,8 @@ export default async function UsersPage(props: UsersPageProps) { return ( <> diff --git a/src/app/[orgId]/settings/api-keys/[apiKeyId]/layout.tsx b/src/app/[orgId]/settings/api-keys/[apiKeyId]/layout.tsx index e012f3323..19b695ca2 100644 --- a/src/app/[orgId]/settings/api-keys/[apiKeyId]/layout.tsx +++ b/src/app/[orgId]/settings/api-keys/[apiKeyId]/layout.tsx @@ -6,7 +6,7 @@ import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import { GetApiKeyResponse } from "@server/routers/apiKeys"; import ApiKeyProvider from "@app/providers/ApiKeyProvider"; import { HorizontalTabs } from "@app/components/HorizontalTabs"; -import { getTranslations } from 'next-intl/server'; +import { getTranslations } from "next-intl/server"; interface SettingsLayoutProps { children: React.ReactNode; @@ -33,14 +33,16 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { const navItems = [ { - title: t('apiKeysPermissionsTitle'), + title: t("apiKeysPermissionsTitle"), href: "/{orgId}/settings/api-keys/{apiKeyId}/permissions" } ]; return ( <> - + {children} diff --git a/src/app/[orgId]/settings/api-keys/[apiKeyId]/page.tsx b/src/app/[orgId]/settings/api-keys/[apiKeyId]/page.tsx index e54f442dd..518db250b 100644 --- a/src/app/[orgId]/settings/api-keys/[apiKeyId]/page.tsx +++ b/src/app/[orgId]/settings/api-keys/[apiKeyId]/page.tsx @@ -4,5 +4,7 @@ export default async function ApiKeysPage(props: { params: Promise<{ orgId: string; apiKeyId: string }>; }) { const params = await props.params; - redirect(`/${params.orgId}/settings/api-keys/${params.apiKeyId}/permissions`); + redirect( + `/${params.orgId}/settings/api-keys/${params.apiKeyId}/permissions` + ); } diff --git a/src/app/[orgId]/settings/api-keys/[apiKeyId]/permissions/page.tsx b/src/app/[orgId]/settings/api-keys/[apiKeyId]/permissions/page.tsx index 121d9523a..da63c9c59 100644 --- a/src/app/[orgId]/settings/api-keys/[apiKeyId]/permissions/page.tsx +++ b/src/app/[orgId]/settings/api-keys/[apiKeyId]/permissions/page.tsx @@ -45,10 +45,10 @@ export default function Page() { .catch((e) => { toast({ variant: "destructive", - title: t('apiKeysPermissionsErrorLoadingActions'), + title: t("apiKeysPermissionsErrorLoadingActions"), description: formatAxiosError( e, - t('apiKeysPermissionsErrorLoadingActions') + t("apiKeysPermissionsErrorLoadingActions") ) }); }); @@ -79,18 +79,18 @@ export default function Page() { ) }) .catch((e) => { - console.error(t('apiKeysErrorSetPermission'), e); + console.error(t("apiKeysErrorSetPermission"), e); toast({ variant: "destructive", - title: t('apiKeysErrorSetPermission'), + title: t("apiKeysErrorSetPermission"), description: formatAxiosError(e) }); }); if (actionsRes && actionsRes.status === 200) { toast({ - title: t('apiKeysPermissionsUpdated'), - description: t('apiKeysPermissionsUpdatedDescription') + title: t("apiKeysPermissionsUpdated"), + description: t("apiKeysPermissionsUpdatedDescription") }); } @@ -104,10 +104,12 @@ export default function Page() { - {t('apiKeysPermissionsGeneralSettings')} + {t("apiKeysPermissionsGeneralSettings")} - {t('apiKeysPermissionsGeneralSettingsDescription')} + {t( + "apiKeysPermissionsGeneralSettingsDescription" + )} @@ -124,7 +126,7 @@ export default function Page() { loading={loadingSavePermissions} disabled={loadingSavePermissions} > - {t('apiKeysPermissionsSave')} + {t("apiKeysPermissionsSave")} diff --git a/src/app/[orgId]/settings/api-keys/create/page.tsx b/src/app/[orgId]/settings/api-keys/create/page.tsx index b62c26284..fa062ba19 100644 --- a/src/app/[orgId]/settings/api-keys/create/page.tsx +++ b/src/app/[orgId]/settings/api-keys/create/page.tsx @@ -66,10 +66,10 @@ export default function Page() { name: z .string() .min(2, { - message: t('nameMin', {len: 2}) + message: t("nameMin", { len: 2 }) }) .max(255, { - message: t('nameMax', {len: 255}) + message: t("nameMax", { len: 255 }) }) }); @@ -84,7 +84,7 @@ export default function Page() { return data.copied; }, { - message: t('apiKeysConfirmCopy2'), + message: t("apiKeysConfirmCopy2"), path: ["copied"] } ); @@ -119,7 +119,7 @@ export default function Page() { .catch((e) => { toast({ variant: "destructive", - title: t('apiKeysErrorCreate'), + title: t("apiKeysErrorCreate"), description: formatAxiosError(e) }); }); @@ -140,10 +140,10 @@ export default function Page() { ) }) .catch((e) => { - console.error(t('apiKeysErrorSetPermission'), e); + console.error(t("apiKeysErrorSetPermission"), e); toast({ variant: "destructive", - title: t('apiKeysErrorSetPermission'), + title: t("apiKeysErrorSetPermission"), description: formatAxiosError(e) }); }); @@ -182,8 +182,8 @@ export default function Page() { <>
@@ -203,7 +203,7 @@ export default function Page() { - {t('apiKeysTitle')} + {t("apiKeysTitle")} @@ -224,7 +224,7 @@ export default function Page() { render={({ field }) => ( - {t('name')} + {t("name")} - {t('apiKeysGeneralSettings')} + {t("apiKeysGeneralSettings")} - {t('apiKeysGeneralSettingsDescription')} + {t( + "apiKeysGeneralSettingsDescription" + )} @@ -267,14 +269,14 @@ export default function Page() { - {t('apiKeysList')} + {t("apiKeysList")} - {t('name')} + {t("name")} - {t('created')} + {t("created")} {moment( @@ -297,10 +299,10 @@ export default function Page() { - {t('apiKeysSave')} + {t("apiKeysSave")} - {t('apiKeysSaveDescription')} + {t("apiKeysSaveDescription")} @@ -367,7 +369,7 @@ export default function Page() { router.push(`/${orgId}/settings/api-keys`); }} > - {t('cancel')} + {t("cancel")} )} {!apiKey && ( @@ -379,7 +381,7 @@ export default function Page() { form.handleSubmit(onSubmit)(); }} > - {t('generate')} + {t("generate")} )} @@ -390,7 +392,7 @@ export default function Page() { copiedForm.handleSubmit(onCopiedSubmit)(); }} > - {t('done')} + {t("done")} )} diff --git a/src/app/[orgId]/settings/api-keys/page.tsx b/src/app/[orgId]/settings/api-keys/page.tsx index ca526a7d4..2973bb542 100644 --- a/src/app/[orgId]/settings/api-keys/page.tsx +++ b/src/app/[orgId]/settings/api-keys/page.tsx @@ -2,9 +2,11 @@ import { internal } from "@app/lib/api"; import { authCookieHeader } from "@app/lib/api/cookies"; import { AxiosResponse } from "axios"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; -import OrgApiKeysTable, { OrgApiKeyRow } from "../../../../components/OrgApiKeysTable"; +import OrgApiKeysTable, { + OrgApiKeyRow +} from "../../../../components/OrgApiKeysTable"; import { ListOrgApiKeysResponse } from "@server/routers/apiKeys"; -import { getTranslations } from 'next-intl/server'; +import { getTranslations } from "next-intl/server"; type ApiKeyPageProps = { params: Promise<{ orgId: string }>; @@ -37,8 +39,8 @@ export default async function ApiKeysPage(props: ApiKeyPageProps) { return ( <> diff --git a/src/app/[orgId]/settings/clients/machine/[niceId]/layout.tsx b/src/app/[orgId]/settings/clients/machine/[niceId]/layout.tsx index f8a9610e3..145fb1728 100644 --- a/src/app/[orgId]/settings/clients/machine/[niceId]/layout.tsx +++ b/src/app/[orgId]/settings/clients/machine/[niceId]/layout.tsx @@ -21,7 +21,10 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { let client = null; try { - console.log("making request to ", `/org/${params.orgId}/client/${params.niceId}`); + console.log( + "making request to ", + `/org/${params.orgId}/client/${params.niceId}` + ); const res = await internal.get>( `/org/${params.orgId}/client/${params.niceId}`, await authCookieHeader() diff --git a/src/app/[orgId]/settings/general/layout.tsx b/src/app/[orgId]/settings/general/layout.tsx index 82b2c9991..8c8efa59d 100644 --- a/src/app/[orgId]/settings/general/layout.tsx +++ b/src/app/[orgId]/settings/general/layout.tsx @@ -10,7 +10,7 @@ import { GetOrgUserResponse } from "@server/routers/user"; import { AxiosResponse } from "axios"; import { redirect } from "next/navigation"; import { cache } from "react"; -import { getTranslations } from 'next-intl/server'; +import { getTranslations } from "next-intl/server"; type GeneralSettingsProps = { children: React.ReactNode; @@ -19,7 +19,7 @@ type GeneralSettingsProps = { export default async function GeneralSettingsPage({ children, - params, + params }: GeneralSettingsProps) { const { orgId } = await params; @@ -35,8 +35,8 @@ export default async function GeneralSettingsPage({ const getOrgUser = cache(async () => internal.get>( `/org/${orgId}/user/${user.userId}`, - await authCookieHeader(), - ), + await authCookieHeader() + ) ); const res = await getOrgUser(); orgUser = res.data.data; @@ -49,8 +49,8 @@ export default async function GeneralSettingsPage({ const getOrg = cache(async () => internal.get>( `/org/${orgId}`, - await authCookieHeader(), - ), + await authCookieHeader() + ) ); const res = await getOrg(); org = res.data.data; @@ -62,9 +62,9 @@ export default async function GeneralSettingsPage({ const navItems = [ { - title: t('general'), - href: `/{orgId}/settings/general`, - }, + title: t("general"), + href: `/{orgId}/settings/general` + } ]; return ( @@ -72,13 +72,11 @@ export default async function GeneralSettingsPage({ - - {children} - + {children} diff --git a/src/app/[orgId]/settings/logs/request/page.tsx b/src/app/[orgId]/settings/logs/request/page.tsx index b2f9bab40..42cfec57b 100644 --- a/src/app/[orgId]/settings/logs/request/page.tsx +++ b/src/app/[orgId]/settings/logs/request/page.tsx @@ -6,7 +6,11 @@ import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { useParams, useRouter, useSearchParams } from "next/navigation"; import { useTranslations } from "next-intl"; -import { getStoredPageSize, LogDataTable, setStoredPageSize } from "@app/components/LogDataTable"; +import { + getStoredPageSize, + LogDataTable, + setStoredPageSize +} from "@app/components/LogDataTable"; import { ColumnDef } from "@tanstack/react-table"; import { DateTimeValue } from "@app/components/DateTimePicker"; import { Key, RouteOff, User, Lock, Unlock, ArrowUpRight } from "lucide-react"; @@ -757,8 +761,8 @@ export default function GeneralPage() { return ( <> {authInfo.headerAuth - ? t("resourceHeaderAuthProtectionEnabled") + ? t( + "resourceHeaderAuthProtectionEnabled" + ) : t( "resourceHeaderAuthProtectionDisabled" )} @@ -921,7 +923,8 @@ export default function ResourceAuthenticationPage() { validateTag={( tag ) => { - return z.email() + return z + .email() .or( z .string() diff --git a/src/app/[orgId]/settings/resources/proxy/[niceId]/general/page.tsx b/src/app/[orgId]/settings/resources/proxy/[niceId]/general/page.tsx index 3d9aa9777..fa9a6976c 100644 --- a/src/app/[orgId]/settings/resources/proxy/[niceId]/general/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/[niceId]/general/page.tsx @@ -104,7 +104,7 @@ export default function GeneralForm() { name: z.string().min(1).max(255), niceId: z.string().min(1).max(255).optional(), domainId: z.string().optional(), - proxyPort: z.int().min(1).max(65535).optional(), + proxyPort: z.int().min(1).max(65535).optional() // enableProxy: z.boolean().optional() }) .refine( @@ -134,7 +134,7 @@ export default function GeneralForm() { niceId: resource.niceId, subdomain: resource.subdomain ? resource.subdomain : undefined, domainId: resource.domainId || undefined, - proxyPort: resource.proxyPort || undefined, + proxyPort: resource.proxyPort || undefined // enableProxy: resource.enableProxy || false }, mode: "onChange" @@ -168,7 +168,7 @@ export default function GeneralForm() { const rawDomains = res.data.data.domains as DomainRow[]; const domains = rawDomains.map((domain) => ({ ...domain, - baseDomain: toUnicode(domain.baseDomain), + baseDomain: toUnicode(domain.baseDomain) })); setBaseDomains(domains); setFormKey((key) => key + 1); @@ -195,9 +195,11 @@ export default function GeneralForm() { enabled: data.enabled, name: data.name, niceId: data.niceId, - subdomain: data.subdomain ? toASCII(data.subdomain) : undefined, + subdomain: data.subdomain + ? toASCII(data.subdomain) + : undefined, domainId: data.domainId, - proxyPort: data.proxyPort, + proxyPort: data.proxyPort // ...(!resource.http && { // enableProxy: data.enableProxy // }) @@ -223,7 +225,7 @@ export default function GeneralForm() { niceId: data.niceId, subdomain: data.subdomain, fullDomain: resource.fullDomain, - proxyPort: data.proxyPort, + proxyPort: data.proxyPort // ...(!resource.http && { // enableProxy: data.enableProxy // }) @@ -235,7 +237,9 @@ export default function GeneralForm() { }); if (data.niceId && data.niceId !== resource?.niceId) { - router.replace(`/${updated.orgId}/settings/resources/proxy/${data.niceId}/general`); + router.replace( + `/${updated.orgId}/settings/resources/proxy/${data.niceId}/general` + ); } else { router.refresh(); } @@ -320,11 +324,15 @@ export default function GeneralForm() { name="niceId" render={({ field }) => ( - {t("identifier")} + + {t("identifier")} + @@ -360,10 +368,10 @@ export default function GeneralForm() { .target .value ? parseInt( - e - .target - .value - ) + e + .target + .value + ) : undefined ) } @@ -498,17 +506,29 @@ export default function GeneralForm() { ); @@ -440,15 +449,18 @@ export default function ResourceRules(props: { type="number" onClick={(e) => e.currentTarget.focus()} onBlur={(e) => { - const parsed = z.int() + const parsed = z + .int() .optional() .safeParse(e.target.value); if (!parsed.data) { toast({ variant: "destructive", - title: t('rulesErrorInvalidIpAddress'), // correct priority or IP? - description: t('rulesErrorInvalidPriorityDescription') + title: t("rulesErrorInvalidIpAddress"), // correct priority or IP? + description: t( + "rulesErrorInvalidPriorityDescription" + ) }); setLoading(false); return; @@ -463,7 +475,7 @@ export default function ResourceRules(props: { }, { accessorKey: "action", - header: () => ({t('rulesAction')}), + header: () => {t("rulesAction")}, cell: ({ row }) => ( - updateRule(row.original.ruleId, { match: value, value: value === "COUNTRY" ? "US" : row.original.value }) + onValueChange={( + value: "CIDR" | "IP" | "PATH" | "COUNTRY" + ) => + updateRule(row.original.ruleId, { + match: value, + value: + value === "COUNTRY" ? "US" : row.original.value + }) } > @@ -502,7 +520,9 @@ export default function ResourceRules(props: { {RuleMatch.IP} {RuleMatch.CIDR} {isMaxmindAvailable && ( - {RuleMatch.COUNTRY} + + {RuleMatch.COUNTRY} + )} @@ -510,8 +530,8 @@ export default function ResourceRules(props: { }, { accessorKey: "value", - header: () => ({t('value')}), - cell: ({ row }) => ( + header: () => {t("value")}, + cell: ({ row }) => row.original.match === "COUNTRY" ? ( @@ -521,29 +541,43 @@ export default function ResourceRules(props: { className="min-w-[200px] justify-between" > {row.original.value - ? COUNTRIES.find((country) => country.code === row.original.value)?.name + - " (" + row.original.value + ")" - : t('selectCountry')} + ? COUNTRIES.find( + (country) => + country.code === + row.original.value + )?.name + + " (" + + row.original.value + + ")" + : t("selectCountry")} - + - {t('noCountryFound')} + + {t("noCountryFound")} + {COUNTRIES.map((country) => ( { - updateRule(row.original.ruleId, { value: country.code }); + updateRule( + row.original.ruleId, + { value: country.code } + ); }} > ) - ) }, { accessorKey: "enabled", - header: () => ({t('enabled')}), + header: () => {t("enabled")}, cell: ({ row }) => ( ({t('actions')}), + header: () => {t("actions")}, cell: ({ row }) => (
) @@ -664,10 +697,10 @@ export default function ResourceRules(props: { - {t('rulesResource')} + {t("rulesResource")} - {t('rulesResourceDescription')} + {t("rulesResourceDescription")} @@ -675,7 +708,7 @@ export default function ResourceRules(props: {
setRulesEnabled(val)} /> @@ -692,7 +725,9 @@ export default function ResourceRules(props: { name="action" render={({ field }) => ( - {t('rulesAction')} + + {t("rulesAction")} + @@ -725,11 +766,15 @@ export default function ResourceRules(props: { name="match" render={({ field }) => ( - {t('rulesMatchType')} + + {t("rulesMatchType")} + diff --git a/src/app/[orgId]/settings/sites/[niceId]/wireguardConfig.ts b/src/app/[orgId]/settings/sites/[niceId]/wireguardConfig.ts index 01157a68a..5e3d1281d 100644 --- a/src/app/[orgId]/settings/sites/[niceId]/wireguardConfig.ts +++ b/src/app/[orgId]/settings/sites/[niceId]/wireguardConfig.ts @@ -6,16 +6,16 @@ function gf(init: number[] | undefined = undefined) { var r = new Float64Array(16); if (init) { - for (var i = 0; i < init.length; ++i) - r[i] = init[i]; + for (var i = 0; i < init.length; ++i) r[i] = init[i]; } return r; } function pack(o: Uint8Array, n: Float64Array) { - var b, m = gf(), t = gf(); - for (var i = 0; i < 16; ++i) - t[i] = n[i]; + var b, + m = gf(), + t = gf(); + for (var i = 0; i < 16; ++i) t[i] = n[i]; carry(t); carry(t); carry(t); @@ -45,7 +45,8 @@ function carry(o: Float64Array) { } function cswap(p: Float64Array, q: Float64Array, b: number) { - var t, c = ~(b - 1); + var t, + c = ~(b - 1); for (var i = 0; i < 16; ++i) { t = c & (p[i] ^ q[i]); p[i] ^= t; @@ -54,40 +55,32 @@ function cswap(p: Float64Array, q: Float64Array, b: number) { } function add(o: Float64Array, a: Float64Array, b: Float64Array) { - for (var i = 0; i < 16; ++i) - o[i] = (a[i] + b[i]) | 0; + for (var i = 0; i < 16; ++i) o[i] = (a[i] + b[i]) | 0; } function subtract(o: Float64Array, a: Float64Array, b: Float64Array) { - for (var i = 0; i < 16; ++i) - o[i] = (a[i] - b[i]) | 0; + for (var i = 0; i < 16; ++i) o[i] = (a[i] - b[i]) | 0; } function multmod(o: Float64Array, a: Float64Array, b: Float64Array) { var t = new Float64Array(31); for (var i = 0; i < 16; ++i) { - for (var j = 0; j < 16; ++j) - t[i + j] += a[i] * b[j]; + for (var j = 0; j < 16; ++j) t[i + j] += a[i] * b[j]; } - for (var i = 0; i < 15; ++i) - t[i] += 38 * t[i + 16]; - for (var i = 0; i < 16; ++i) - o[i] = t[i]; + for (var i = 0; i < 15; ++i) t[i] += 38 * t[i + 16]; + for (var i = 0; i < 16; ++i) o[i] = t[i]; carry(o); carry(o); } function invert(o: Float64Array, i: Float64Array) { var c = gf(); - for (var a = 0; a < 16; ++a) - c[a] = i[a]; + for (var a = 0; a < 16; ++a) c[a] = i[a]; for (var a = 253; a >= 0; --a) { multmod(c, c, c); - if (a !== 2 && a !== 4) - multmod(c, c, i); + if (a !== 2 && a !== 4) multmod(c, c, i); } - for (var a = 0; a < 16; ++a) - o[a] = c[a]; + for (var a = 0; a < 16; ++a) o[a] = c[a]; } function clamp(z: Uint8Array) { @@ -96,7 +89,8 @@ function clamp(z: Uint8Array) { } function generatePublicKey(privateKey: Uint8Array) { - var r, z = new Uint8Array(32); + var r, + z = new Uint8Array(32); var a = gf([1]), b = gf([9]), c = gf(), @@ -105,8 +99,7 @@ function generatePublicKey(privateKey: Uint8Array) { f = gf(), _121665 = gf([0xdb41, 1]), _9 = gf([9]); - for (var i = 0; i < 32; ++i) - z[i] = privateKey[i]; + for (var i = 0; i < 32; ++i) z[i] = privateKey[i]; clamp(z); for (var i = 254; i >= 0; --i) { r = (z[i >>> 3] >>> (i & 7)) & 1; @@ -152,9 +145,16 @@ function generatePrivateKey() { } function encodeBase64(dest: Uint8Array, src: Uint8Array) { - var input = Uint8Array.from([(src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63]); + var input = Uint8Array.from([ + (src[0] >> 2) & 63, + ((src[0] << 4) | (src[1] >> 4)) & 63, + ((src[1] << 2) | (src[2] >> 6)) & 63, + src[2] & 63 + ]); for (var i = 0; i < 4; ++i) - dest[i] = input[i] + 65 + + dest[i] = + input[i] + + 65 + (((25 - input[i]) >> 8) & 6) - (((51 - input[i]) >> 8) & 75) - (((61 - input[i]) >> 8) & 15) + @@ -162,10 +162,14 @@ function encodeBase64(dest: Uint8Array, src: Uint8Array) { } function keyToBase64(key: Uint8Array) { - var i, base64 = new Uint8Array(44); + var i, + base64 = new Uint8Array(44); for (i = 0; i < 32 / 3; ++i) encodeBase64(base64.subarray(i * 4), key.subarray(i * 3)); - encodeBase64(base64.subarray(i * 4), Uint8Array.from([key[i * 3 + 0], key[i * 3 + 1], 0])); + encodeBase64( + base64.subarray(i * 4), + Uint8Array.from([key[i * 3 + 0], key[i * 3 + 1], 0]) + ); base64[43] = 61; return String.fromCharCode.apply(null, base64 as any); } @@ -177,4 +181,4 @@ export function generateKeypair() { publicKey: keyToBase64(publicKey), privateKey: keyToBase64(privateKey) }; -} \ No newline at end of file +} diff --git a/src/app/[orgId]/settings/sites/create/page.tsx b/src/app/[orgId]/settings/sites/create/page.tsx index f4ea4f05b..6395c265c 100644 --- a/src/app/[orgId]/settings/sites/create/page.tsx +++ b/src/app/[orgId]/settings/sites/create/page.tsx @@ -215,7 +215,6 @@ export default function Page() { string | undefined >(); - const hydrateCommands = ( id: string, secret: string, @@ -753,7 +752,9 @@ WantedBy=default.target` {tunnelTypes.length > 1 && ( <>
- {t("type")} + + {t("type")} +
= 1024 * 1024) { - return t('terabytes', {count: (mb / (1024 * 1024)).toFixed(2)}); + return t("terabytes", { count: (mb / (1024 * 1024)).toFixed(2) }); } else if (mb >= 1024) { - return t('gigabytes', {count: (mb / 1024).toFixed(2)}); + return t("gigabytes", { count: (mb / 1024).toFixed(2) }); } else { - return t('megabytes', {count: mb.toFixed(2)}); + return t("megabytes", { count: mb.toFixed(2) }); } } @@ -53,7 +53,7 @@ export default async function SitesPage(props: SitesPageProps) { newtVersion: site.newtVersion || undefined, newtUpdateAvailable: site.newtUpdateAvailable || false, exitNodeName: site.exitNodeName || undefined, - exitNodeEndpoint: site.exitNodeEndpoint || undefined, + exitNodeEndpoint: site.exitNodeEndpoint || undefined }; }); @@ -62,8 +62,8 @@ export default async function SitesPage(props: SitesPageProps) { {/* */} diff --git a/src/app/admin/api-keys/[apiKeyId]/layout.tsx b/src/app/admin/api-keys/[apiKeyId]/layout.tsx index 7e9e579f4..d547a0b0c 100644 --- a/src/app/admin/api-keys/[apiKeyId]/layout.tsx +++ b/src/app/admin/api-keys/[apiKeyId]/layout.tsx @@ -34,14 +34,16 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { const navItems = [ { - title: t('apiKeysPermissionsTitle'), + title: t("apiKeysPermissionsTitle"), href: "/admin/api-keys/{apiKeyId}/permissions" } ]; return ( <> - + {children} diff --git a/src/app/admin/api-keys/[apiKeyId]/permissions/page.tsx b/src/app/admin/api-keys/[apiKeyId]/permissions/page.tsx index e00ae4256..fad36194a 100644 --- a/src/app/admin/api-keys/[apiKeyId]/permissions/page.tsx +++ b/src/app/admin/api-keys/[apiKeyId]/permissions/page.tsx @@ -45,10 +45,10 @@ export default function Page() { .catch((e) => { toast({ variant: "destructive", - title: t('apiKeysPermissionsErrorLoadingActions'), + title: t("apiKeysPermissionsErrorLoadingActions"), description: formatAxiosError( e, - t('apiKeysPermissionsErrorLoadingActions') + t("apiKeysPermissionsErrorLoadingActions") ) }); }); @@ -79,18 +79,18 @@ export default function Page() { ) }) .catch((e) => { - console.error(t('apiKeysErrorSetPermission'), e); + console.error(t("apiKeysErrorSetPermission"), e); toast({ variant: "destructive", - title: t('apiKeysErrorSetPermission'), + title: t("apiKeysErrorSetPermission"), description: formatAxiosError(e) }); }); if (actionsRes && actionsRes.status === 200) { toast({ - title: t('apiKeysPermissionsUpdated'), - description: t('apiKeysPermissionsUpdatedDescription') + title: t("apiKeysPermissionsUpdated"), + description: t("apiKeysPermissionsUpdatedDescription") }); } @@ -104,10 +104,12 @@ export default function Page() { - {t('apiKeysPermissionsTitle')} + {t("apiKeysPermissionsTitle")} - {t('apiKeysPermissionsGeneralSettingsDescription')} + {t( + "apiKeysPermissionsGeneralSettingsDescription" + )} @@ -125,7 +127,7 @@ export default function Page() { loading={loadingSavePermissions} disabled={loadingSavePermissions} > - {t('apiKeysPermissionsSave')} + {t("apiKeysPermissionsSave")} diff --git a/src/app/admin/api-keys/create/page.tsx b/src/app/admin/api-keys/create/page.tsx index 65f8e46a0..083ec89d6 100644 --- a/src/app/admin/api-keys/create/page.tsx +++ b/src/app/admin/api-keys/create/page.tsx @@ -30,7 +30,7 @@ import { createApiClient, formatAxiosError } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { toast } from "@app/hooks/useToast"; import { AxiosResponse } from "axios"; -import { useRouter } from "next/navigation"; +import { useRouter } from "next/navigation"; import { CreateOrgApiKeyBody, CreateOrgApiKeyResponse @@ -64,10 +64,10 @@ export default function Page() { name: z .string() .min(2, { - message: t('nameMin', {len: 2}) + message: t("nameMin", { len: 2 }) }) .max(255, { - message: t('nameMax', {len: 255}) + message: t("nameMax", { len: 255 }) }) }); @@ -82,7 +82,7 @@ export default function Page() { return data.copied; }, { - message: t('apiKeysConfirmCopy2'), + message: t("apiKeysConfirmCopy2"), path: ["copied"] } ); @@ -115,7 +115,7 @@ export default function Page() { .catch((e) => { toast({ variant: "destructive", - title: t('apiKeysErrorCreate'), + title: t("apiKeysErrorCreate"), description: formatAxiosError(e) }); }); @@ -136,10 +136,10 @@ export default function Page() { ) }) .catch((e) => { - console.error(t('apiKeysErrorSetPermission'), e); + console.error(t("apiKeysErrorSetPermission"), e); toast({ variant: "destructive", - title: t('apiKeysErrorSetPermission'), + title: t("apiKeysErrorSetPermission"), description: formatAxiosError(e) }); }); @@ -172,8 +172,8 @@ export default function Page() { <>
@@ -193,7 +193,7 @@ export default function Page() { - {t('apiKeysTitle')} + {t("apiKeysTitle")} @@ -214,7 +214,7 @@ export default function Page() { render={({ field }) => ( - {t('name')} + {t("name")} - {t('apiKeysGeneralSettings')} + {t("apiKeysGeneralSettings")} - {t('apiKeysGeneralSettingsDescription')} + {t( + "apiKeysGeneralSettingsDescription" + )} @@ -258,14 +260,14 @@ export default function Page() { - {t('apiKeysList')} + {t("apiKeysList")} - {t('name')} + {t("name")} - {t('created')} + {t("created")} {moment( @@ -288,10 +290,10 @@ export default function Page() { - {t('apiKeysSave')} + {t("apiKeysSave")} - {t('apiKeysSaveDescription')} + {t("apiKeysSaveDescription")} @@ -358,7 +360,7 @@ export default function Page() { router.push(`/admin/api-keys`); }} > - {t('cancel')} + {t("cancel")} )} {!apiKey && ( @@ -370,7 +372,7 @@ export default function Page() { form.handleSubmit(onSubmit)(); }} > - {t('generate')} + {t("generate")} )} @@ -381,7 +383,7 @@ export default function Page() { copiedForm.handleSubmit(onCopiedSubmit)(); }} > - {t('done')} + {t("done")} )}
diff --git a/src/app/admin/api-keys/page.tsx b/src/app/admin/api-keys/page.tsx index e518911ff..60195c4aa 100644 --- a/src/app/admin/api-keys/page.tsx +++ b/src/app/admin/api-keys/page.tsx @@ -34,8 +34,8 @@ export default async function ApiKeysPage(props: ApiKeyPageProps) { return ( <> diff --git a/src/app/admin/idp/[idpId]/general/page.tsx b/src/app/admin/idp/[idpId]/general/page.tsx index 7eae6950b..d431efa2d 100644 --- a/src/app/admin/idp/[idpId]/general/page.tsx +++ b/src/app/admin/idp/[idpId]/general/page.tsx @@ -58,17 +58,17 @@ export default function GeneralPage() { const t = useTranslations(); const GeneralFormSchema = z.object({ - name: z.string().min(2, { message: t('nameMin', {len: 2}) }), - clientId: z.string().min(1, { message: t('idpClientIdRequired') }), - clientSecret: z.string().min(1, { message: t('idpClientSecretRequired') }), - authUrl: z.url({ message: t('idpErrorAuthUrlInvalid') }), - tokenUrl: z.url({ message: t('idpErrorTokenUrlInvalid') }), - identifierPath: z + name: z.string().min(2, { message: t("nameMin", { len: 2 }) }), + clientId: z.string().min(1, { message: t("idpClientIdRequired") }), + clientSecret: z .string() - .min(1, { message: t('idpPathRequired') }), + .min(1, { message: t("idpClientSecretRequired") }), + authUrl: z.url({ message: t("idpErrorAuthUrlInvalid") }), + tokenUrl: z.url({ message: t("idpErrorTokenUrlInvalid") }), + identifierPath: z.string().min(1, { message: t("idpPathRequired") }), emailPath: z.string().optional(), namePath: z.string().optional(), - scopes: z.string().min(1, { message: t('idpScopeRequired') }), + scopes: z.string().min(1, { message: t("idpScopeRequired") }), autoProvision: z.boolean().default(false) }); @@ -111,7 +111,7 @@ export default function GeneralPage() { } } catch (e) { toast({ - title: t('error'), + title: t("error"), description: formatAxiosError(e), variant: "destructive" }); @@ -145,14 +145,14 @@ export default function GeneralPage() { if (res.status === 200) { toast({ - title: t('success'), - description: t('idpUpdatedDescription') + title: t("success"), + description: t("idpUpdatedDescription") }); router.refresh(); } } catch (e) { toast({ - title: t('error'), + title: t("error"), description: formatAxiosError(e), variant: "destructive" }); @@ -171,17 +171,17 @@ export default function GeneralPage() { - {t('idpTitle')} + {t("idpTitle")} - {t('idpSettingsDescription')} + {t("idpSettingsDescription")} - {t('redirectUrl')} + {t("redirectUrl")} @@ -192,10 +192,10 @@ export default function GeneralPage() { - {t('redirectUrlAbout')} + {t("redirectUrlAbout")} - {t('redirectUrlAboutDescription')} + {t("redirectUrlAboutDescription")} @@ -210,12 +210,14 @@ export default function GeneralPage() { name="name" render={({ field }) => ( - {t('name')} + + {t("name")} + - {t('idpDisplayName')} + {t("idpDisplayName")} @@ -225,7 +227,7 @@ export default function GeneralPage() {
- {t('idpAutoProvisionUsersDescription')} + {t("idpAutoProvisionUsersDescription")} @@ -250,10 +252,10 @@ export default function GeneralPage() { - {t('idpOidcConfigure')} + {t("idpOidcConfigure")} - {t('idpOidcConfigureDescription')} + {t("idpOidcConfigureDescription")} @@ -270,13 +272,15 @@ export default function GeneralPage() { render={({ field }) => ( - {t('idpClientId')} + {t("idpClientId")} - {t('idpClientIdDescription')} + {t( + "idpClientIdDescription" + )} @@ -289,7 +293,7 @@ export default function GeneralPage() { render={({ field }) => ( - {t('idpClientSecret')} + {t("idpClientSecret")} - {t('idpClientSecretDescription')} + {t( + "idpClientSecretDescription" + )} @@ -311,13 +317,15 @@ export default function GeneralPage() { render={({ field }) => ( - {t('idpAuthUrl')} + {t("idpAuthUrl")} - {t('idpAuthUrlDescription')} + {t( + "idpAuthUrlDescription" + )} @@ -330,13 +338,15 @@ export default function GeneralPage() { render={({ field }) => ( - {t('idpTokenUrl')} + {t("idpTokenUrl")} - {t('idpTokenUrlDescription')} + {t( + "idpTokenUrlDescription" + )} @@ -351,10 +361,10 @@ export default function GeneralPage() { - {t('idpToken')} + {t("idpToken")} - {t('idpTokenDescription')} + {t("idpTokenDescription")} @@ -368,17 +378,21 @@ export default function GeneralPage() { - {t('idpJmespathAbout')} + {t("idpJmespathAbout")} - {t('idpJmespathAboutDescription')} + {t( + "idpJmespathAboutDescription" + )} - {t('idpJmespathAboutDescriptionLink')}{" "} + {t( + "idpJmespathAboutDescriptionLink" + )}{" "} @@ -390,13 +404,15 @@ export default function GeneralPage() { render={({ field }) => ( - {t('idpJmespathLabel')} + {t("idpJmespathLabel")} - {t('idpJmespathLabelDescription')} + {t( + "idpJmespathLabelDescription" + )} @@ -409,13 +425,17 @@ export default function GeneralPage() { render={({ field }) => ( - {t('idpJmespathEmailPathOptional')} + {t( + "idpJmespathEmailPathOptional" + )} - {t('idpJmespathEmailPathOptionalDescription')} + {t( + "idpJmespathEmailPathOptionalDescription" + )} @@ -428,13 +448,17 @@ export default function GeneralPage() { render={({ field }) => ( - {t('idpJmespathNamePathOptional')} + {t( + "idpJmespathNamePathOptional" + )} - {t('idpJmespathNamePathOptionalDescription')} + {t( + "idpJmespathNamePathOptionalDescription" + )} @@ -447,13 +471,17 @@ export default function GeneralPage() { render={({ field }) => ( - {t('idpOidcConfigureScopes')} + {t( + "idpOidcConfigureScopes" + )} - {t('idpOidcConfigureScopesDescription')} + {t( + "idpOidcConfigureScopesDescription" + )} @@ -474,7 +502,7 @@ export default function GeneralPage() { loading={loading} disabled={loading} > - {t('saveGeneralSettings')} + {t("saveGeneralSettings")} diff --git a/src/app/admin/idp/[idpId]/layout.tsx b/src/app/admin/idp/[idpId]/layout.tsx index af64e440b..d3d9cb2e3 100644 --- a/src/app/admin/idp/[idpId]/layout.tsx +++ b/src/app/admin/idp/[idpId]/layout.tsx @@ -30,11 +30,11 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { const navItems: HorizontalTabs = [ { - title: t('general'), + title: t("general"), href: `/admin/idp/${params.idpId}/general` }, { - title: t('orgPolicies'), + title: t("orgPolicies"), href: `/admin/idp/${params.idpId}/policies` } ]; @@ -42,8 +42,8 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { return ( <>
diff --git a/src/app/admin/idp/[idpId]/policies/page.tsx b/src/app/admin/idp/[idpId]/policies/page.tsx index e3b085b4f..bf17abe98 100644 --- a/src/app/admin/idp/[idpId]/policies/page.tsx +++ b/src/app/admin/idp/[idpId]/policies/page.tsx @@ -89,7 +89,7 @@ export default function PoliciesPage() { const [editingPolicy, setEditingPolicy] = useState(null); const policyFormSchema = z.object({ - orgId: z.string().min(1, { message: t('orgRequired') }), + orgId: z.string().min(1, { message: t("orgRequired") }), roleMapping: z.string().optional(), orgMapping: z.string().optional() }); @@ -133,7 +133,7 @@ export default function PoliciesPage() { } } catch (e) { toast({ - title: t('error'), + title: t("error"), description: formatAxiosError(e), variant: "destructive" }); @@ -148,7 +148,7 @@ export default function PoliciesPage() { } } catch (e) { toast({ - title: t('error'), + title: t("error"), description: formatAxiosError(e), variant: "destructive" }); @@ -167,7 +167,7 @@ export default function PoliciesPage() { } } catch (e) { toast({ - title: t('error'), + title: t("error"), description: formatAxiosError(e), variant: "destructive" }); @@ -202,15 +202,15 @@ export default function PoliciesPage() { }; setPolicies([...policies, newPolicy]); toast({ - title: t('success'), - description: t('orgPolicyAddedDescription') + title: t("success"), + description: t("orgPolicyAddedDescription") }); setShowAddDialog(false); form.reset(); } } catch (e) { toast({ - title: t('error'), + title: t("error"), description: formatAxiosError(e), variant: "destructive" }); @@ -244,8 +244,8 @@ export default function PoliciesPage() { ) ); toast({ - title: t('success'), - description: t('orgPolicyUpdatedDescription') + title: t("success"), + description: t("orgPolicyUpdatedDescription") }); setShowAddDialog(false); setEditingPolicy(null); @@ -253,7 +253,7 @@ export default function PoliciesPage() { } } catch (e) { toast({ - title: t('error'), + title: t("error"), description: formatAxiosError(e), variant: "destructive" }); @@ -271,13 +271,13 @@ export default function PoliciesPage() { policies.filter((policy) => policy.orgId !== orgId) ); toast({ - title: t('success'), - description: t('orgPolicyDeletedDescription') + title: t("success"), + description: t("orgPolicyDeletedDescription") }); } } catch (e) { toast({ - title: t('error'), + title: t("error"), description: formatAxiosError(e), variant: "destructive" }); @@ -295,13 +295,13 @@ export default function PoliciesPage() { }); if (res.status === 200) { toast({ - title: t('success'), - description: t('defaultMappingsUpdatedDescription') + title: t("success"), + description: t("defaultMappingsUpdatedDescription") }); } } catch (e) { toast({ - title: t('error'), + title: t("error"), description: formatAxiosError(e), variant: "destructive" }); @@ -320,18 +320,18 @@ export default function PoliciesPage() { - {t('orgPoliciesAbout')} + {t("orgPoliciesAbout")} {/*TODO(vlalx): Validate replacing */} - {t('orgPoliciesAboutDescription')}{" "} + {t("orgPoliciesAboutDescription")}{" "} - {t('orgPoliciesAboutDescriptionLink')} + {t("orgPoliciesAboutDescriptionLink")} @@ -340,10 +340,10 @@ export default function PoliciesPage() { - {t('defaultMappingsOptional')} + {t("defaultMappingsOptional")} - {t('defaultMappingsOptionalDescription')} + {t("defaultMappingsOptionalDescription")} @@ -362,13 +362,15 @@ export default function PoliciesPage() { render={({ field }) => ( - {t('defaultMappingsRole')} + {t("defaultMappingsRole")} - {t('defaultMappingsRoleDescription')} + {t( + "defaultMappingsRoleDescription" + )} @@ -381,13 +383,15 @@ export default function PoliciesPage() { render={({ field }) => ( - {t('defaultMappingsOrg')} + {t("defaultMappingsOrg")} - {t('defaultMappingsOrgDescription')} + {t( + "defaultMappingsOrgDescription" + )} @@ -402,7 +406,7 @@ export default function PoliciesPage() { form="policy-default-mappings-form" loading={updateDefaultMappingsLoading} > - {t('defaultMappingsSubmit')} + {t("defaultMappingsSubmit")} @@ -445,11 +449,11 @@ export default function PoliciesPage() { {editingPolicy - ? t('orgPoliciesEdit') - : t('orgPoliciesAdd')} + ? t("orgPoliciesEdit") + : t("orgPoliciesAdd")} - {t('orgPolicyConfig')} + {t("orgPolicyConfig")} @@ -466,7 +470,7 @@ export default function PoliciesPage() { name="orgId" render={({ field }) => ( - {t('org')} + {t("org")} {editingPolicy ? ( ) : ( @@ -490,17 +494,25 @@ export default function PoliciesPage() { org.orgId === field.value )?.name - : t('orgSelect')} + : t( + "orgSelect" + )} - + - {t('orgNotFound')} + {t( + "orgNotFound" + )} {organizations.map( @@ -551,13 +563,15 @@ export default function PoliciesPage() { render={({ field }) => ( - {t('roleMappingPathOptional')} + {t("roleMappingPathOptional")} - {t('defaultMappingsRoleDescription')} + {t( + "defaultMappingsRoleDescription" + )} @@ -570,13 +584,15 @@ export default function PoliciesPage() { render={({ field }) => ( - {t('orgMappingPathOptional')} + {t("orgMappingPathOptional")} - {t('defaultMappingsOrgDescription')} + {t( + "defaultMappingsOrgDescription" + )} @@ -603,7 +619,9 @@ export default function PoliciesPage() { : addPolicyLoading } > - {editingPolicy ? t('orgPolicyUpdate') : t('orgPolicyAdd')} + {editingPolicy + ? t("orgPolicyUpdate") + : t("orgPolicyAdd")} diff --git a/src/app/admin/idp/create/page.tsx b/src/app/admin/idp/create/page.tsx index 73d605a15..dbcd5f002 100644 --- a/src/app/admin/idp/create/page.tsx +++ b/src/app/admin/idp/create/page.tsx @@ -48,18 +48,18 @@ export default function Page() { const t = useTranslations(); const createIdpFormSchema = z.object({ - name: z.string().min(2, { message: t('nameMin', {len: 2}) }), + name: z.string().min(2, { message: t("nameMin", { len: 2 }) }), type: z.enum(["oidc"]), - clientId: z.string().min(1, { message: t('idpClientIdRequired') }), - clientSecret: z.string().min(1, { message: t('idpClientSecretRequired') }), - authUrl: z.url({ message: t('idpErrorAuthUrlInvalid') }), - tokenUrl: z.url({ message: t('idpErrorTokenUrlInvalid') }), - identifierPath: z + clientId: z.string().min(1, { message: t("idpClientIdRequired") }), + clientSecret: z .string() - .min(1, { message: t('idpPathRequired') }), + .min(1, { message: t("idpClientSecretRequired") }), + authUrl: z.url({ message: t("idpErrorAuthUrlInvalid") }), + tokenUrl: z.url({ message: t("idpErrorTokenUrlInvalid") }), + identifierPath: z.string().min(1, { message: t("idpPathRequired") }), emailPath: z.string().optional(), namePath: z.string().optional(), - scopes: z.string().min(1, { message: t('idpScopeRequired') }), + scopes: z.string().min(1, { message: t("idpScopeRequired") }), autoProvision: z.boolean().default(false) }); @@ -75,7 +75,7 @@ export default function Page() { { id: "oidc", title: "OAuth2/OIDC", - description: t('idpOidcDescription') + description: t("idpOidcDescription") } ]; @@ -117,14 +117,14 @@ export default function Page() { if (res.status === 201) { toast({ - title: t('success'), - description: t('idpCreatedDescription') + title: t("success"), + description: t("idpCreatedDescription") }); router.push(`/admin/idp/${res.data.data.idpId}`); } } catch (e) { toast({ - title: t('error'), + title: t("error"), description: formatAxiosError(e), variant: "destructive" }); @@ -137,8 +137,8 @@ export default function Page() { <>
@@ -154,10 +154,10 @@ export default function Page() { - {t('idpTitle')} + {t("idpTitle")} - {t('idpCreateSettingsDescription')} + {t("idpCreateSettingsDescription")} @@ -173,12 +173,14 @@ export default function Page() { name="name" render={({ field }) => ( - {t('name')} + + {t("name")} + - {t('idpDisplayName')} + {t("idpDisplayName")} @@ -188,7 +190,7 @@ export default function Page() {
- {t('idpAutoProvisionUsersDescription')} + {t("idpAutoProvisionUsersDescription")} @@ -212,10 +214,10 @@ export default function Page() { - {t('idpType')} + {t("idpType")} - {t('idpTypeDescription')} + {t("idpTypeDescription")} @@ -235,10 +237,10 @@ export default function Page() { - {t('idpOidcConfigure')} + {t("idpOidcConfigure")} - {t('idpOidcConfigureDescription')} + {t("idpOidcConfigureDescription")} @@ -254,13 +256,15 @@ export default function Page() { render={({ field }) => ( - {t('idpClientId')} + {t("idpClientId")} - {t('idpClientIdDescription')} + {t( + "idpClientIdDescription" + )} @@ -273,7 +277,7 @@ export default function Page() { render={({ field }) => ( - {t('idpClientSecret')} + {t("idpClientSecret")} - {t('idpClientSecretDescription')} + {t( + "idpClientSecretDescription" + )} @@ -295,7 +301,7 @@ export default function Page() { render={({ field }) => ( - {t('idpAuthUrl')} + {t("idpAuthUrl")} - {t('idpAuthUrlDescription')} + {t( + "idpAuthUrlDescription" + )} @@ -317,7 +325,7 @@ export default function Page() { render={({ field }) => ( - {t('idpTokenUrl')} + {t("idpTokenUrl")} - {t('idpTokenUrlDescription')} + {t( + "idpTokenUrlDescription" + )} @@ -338,10 +348,10 @@ export default function Page() { - {t('idpOidcConfigureAlert')} + {t("idpOidcConfigureAlert")} - {t('idpOidcConfigureAlertDescription')} + {t("idpOidcConfigureAlertDescription")} @@ -350,10 +360,10 @@ export default function Page() { - {t('idpToken')} + {t("idpToken")} - {t('idpTokenDescription')} + {t("idpTokenDescription")} @@ -366,17 +376,21 @@ export default function Page() { - {t('idpJmespathAbout')} + {t("idpJmespathAbout")} - {t('idpJmespathAboutDescription')}{" "} + {t( + "idpJmespathAboutDescription" + )}{" "} - {t('idpJmespathAboutDescriptionLink')}{" "} + {t( + "idpJmespathAboutDescriptionLink" + )}{" "} @@ -388,13 +402,15 @@ export default function Page() { render={({ field }) => ( - {t('idpJmespathLabel')} + {t("idpJmespathLabel")} - {t('idpJmespathLabelDescription')} + {t( + "idpJmespathLabelDescription" + )} @@ -407,13 +423,17 @@ export default function Page() { render={({ field }) => ( - {t('idpJmespathEmailPathOptional')} + {t( + "idpJmespathEmailPathOptional" + )} - {t('idpJmespathEmailPathOptionalDescription')} + {t( + "idpJmespathEmailPathOptionalDescription" + )} @@ -426,13 +446,17 @@ export default function Page() { render={({ field }) => ( - {t('idpJmespathNamePathOptional')} + {t( + "idpJmespathNamePathOptional" + )} - {t('idpJmespathNamePathOptionalDescription')} + {t( + "idpJmespathNamePathOptionalDescription" + )} @@ -445,13 +469,17 @@ export default function Page() { render={({ field }) => ( - {t('idpOidcConfigureScopes')} + {t( + "idpOidcConfigureScopes" + )} - {t('idpOidcConfigureScopesDescription')} + {t( + "idpOidcConfigureScopesDescription" + )} @@ -473,7 +501,7 @@ export default function Page() { router.push("/admin/idp"); }} > - {t('cancel')} + {t("cancel")}
diff --git a/src/app/admin/idp/page.tsx b/src/app/admin/idp/page.tsx index fef0990c2..a341c0469 100644 --- a/src/app/admin/idp/page.tsx +++ b/src/app/admin/idp/page.tsx @@ -22,8 +22,8 @@ export default async function IdpPage() { return ( <> diff --git a/src/app/admin/license/layout.tsx b/src/app/admin/license/layout.tsx index 6c6e8baf5..56813034b 100644 --- a/src/app/admin/license/layout.tsx +++ b/src/app/admin/license/layout.tsx @@ -14,4 +14,3 @@ export default async function AdminLicenseLayout(props: LayoutProps) { return props.children; } - diff --git a/src/app/admin/license/page.tsx b/src/app/admin/license/page.tsx index d01b215ae..ac6d3e67f 100644 --- a/src/app/admin/license/page.tsx +++ b/src/app/admin/license/page.tsx @@ -316,9 +316,7 @@ export default function LicensePage() { }} dialog={
-

- {t("licenseQuestionRemove")} -

+

{t("licenseQuestionRemove")}

{t("licenseMessageRemove")}

diff --git a/src/app/admin/users/AdminUsersTable.tsx b/src/app/admin/users/AdminUsersTable.tsx index 94f3f0b96..efcf94845 100644 --- a/src/app/admin/users/AdminUsersTable.tsx +++ b/src/app/admin/users/AdminUsersTable.tsx @@ -188,7 +188,7 @@ export default function UsersTable({ users }: Props) { }, { id: "actions", - header: () => ({t("actions")}), + header: () => {t("actions")}, cell: ({ row }) => { const r = row.original; return ( diff --git a/src/app/admin/users/[userId]/layout.tsx b/src/app/admin/users/[userId]/layout.tsx index 062b40d8b..0c8c50e60 100644 --- a/src/app/admin/users/[userId]/layout.tsx +++ b/src/app/admin/users/[userId]/layout.tsx @@ -6,7 +6,7 @@ import { AdminGetUserResponse } from "@server/routers/user/adminGetUser"; import { HorizontalTabs } from "@app/components/HorizontalTabs"; import { cache } from "react"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; -import { getTranslations } from 'next-intl/server'; +import { getTranslations } from "next-intl/server"; interface UserLayoutProps { children: React.ReactNode; @@ -36,7 +36,7 @@ export default async function UserLayoutProps(props: UserLayoutProps) { const navItems = [ { - title: t('general'), + title: t("general"), href: "/admin/users/{userId}/general" } ]; @@ -45,11 +45,9 @@ export default async function UserLayoutProps(props: UserLayoutProps) { <> - - {children} - + {children} ); -} \ No newline at end of file +} diff --git a/src/app/admin/users/[userId]/page.tsx b/src/app/admin/users/[userId]/page.tsx index edf5aaed5..07b39686b 100644 --- a/src/app/admin/users/[userId]/page.tsx +++ b/src/app/admin/users/[userId]/page.tsx @@ -5,4 +5,4 @@ export default async function UserPage(props: { }) { const { userId } = await props.params; redirect(`/admin/users/${userId}/general`); -} \ No newline at end of file +} diff --git a/src/app/admin/users/page.tsx b/src/app/admin/users/page.tsx index bf6547a39..c84a077fe 100644 --- a/src/app/admin/users/page.tsx +++ b/src/app/admin/users/page.tsx @@ -36,7 +36,7 @@ export default async function UsersPage(props: PageProps) { username: row.username, type: row.type, idpId: row.idpId, - idpName: row.idpName || t('idpNameInternal'), + idpName: row.idpName || t("idpNameInternal"), dateCreated: row.dateCreated, serverAdmin: row.serverAdmin, twoFactorEnabled: row.twoFactorEnabled, @@ -47,14 +47,16 @@ export default async function UsersPage(props: PageProps) { return ( <> - {t('userAbount')} + + {t("userAbount")} + - {t('userAbountDescription')} + {t("userAbountDescription")} diff --git a/src/app/auth/(private)/org/page.tsx b/src/app/auth/(private)/org/page.tsx index 36dfd175f..5b68708a8 100644 --- a/src/app/auth/(private)/org/page.tsx +++ b/src/app/auth/(private)/org/page.tsx @@ -9,9 +9,7 @@ import { LoginFormIDP } from "@app/components/LoginForm"; import { ListOrgIdpsResponse } from "@server/routers/orgIdp/types"; import { build } from "@server/build"; import { headers } from "next/headers"; -import { - LoadLoginPageResponse -} from "@server/routers/loginPage/types"; +import { LoadLoginPageResponse } from "@server/routers/loginPage/types"; import IdpLoginButtons from "@app/components/private/IdpLoginButtons"; import { Card, diff --git a/src/app/auth/2fa/setup/page.tsx b/src/app/auth/2fa/setup/page.tsx index 64a6cf576..944731b9a 100644 --- a/src/app/auth/2fa/setup/page.tsx +++ b/src/app/auth/2fa/setup/page.tsx @@ -45,7 +45,9 @@ export default function Setup2FAPage() { {t("otpSetup")} - {t("adminEnabled2FaOnYourAccount", { email: email || "your account" })} + {t("adminEnabled2FaOnYourAccount", { + email: email || "your account" + })} diff --git a/src/app/auth/idp/[idpId]/oidc/callback/page.tsx b/src/app/auth/idp/[idpId]/oidc/callback/page.tsx index a2432e3e1..3d6460841 100644 --- a/src/app/auth/idp/[idpId]/oidc/callback/page.tsx +++ b/src/app/auth/idp/[idpId]/oidc/callback/page.tsx @@ -25,15 +25,17 @@ export default async function Page(props: { const allCookies = await cookies(); const stateCookie = allCookies.get("p_oidc_state")?.value; - const idpRes = await cache( - async () => await priv.get>(`/idp/${params.idpId}`) + async () => + await priv.get>( + `/idp/${params.idpId}` + ) )(); const foundIdp = idpRes.data?.data?.idp; if (!foundIdp) { - return
{t('idpErrorNotFound')}
; + return
{t("idpErrorNotFound")}
; } const allHeaders = await headers(); diff --git a/src/app/auth/login/device/page.tsx b/src/app/auth/login/device/page.tsx index 07c804fb1..2fa5fac59 100644 --- a/src/app/auth/login/device/page.tsx +++ b/src/app/auth/login/device/page.tsx @@ -19,7 +19,9 @@ export default async function DeviceLoginPage({ searchParams }: Props) { const redirectDestination = code ? `/auth/login/device?code=${encodeURIComponent(code)}` : "/auth/login/device"; - redirect(`/auth/login?forceLogin=true&redirect=${encodeURIComponent(redirectDestination)}`); + redirect( + `/auth/login?forceLogin=true&redirect=${encodeURIComponent(redirectDestination)}` + ); } const userName = user?.name || user?.username || ""; diff --git a/src/app/auth/login/device/success/page.tsx b/src/app/auth/login/device/success/page.tsx index 1b79c6002..81e62fd66 100644 --- a/src/app/auth/login/device/success/page.tsx +++ b/src/app/auth/login/device/success/page.tsx @@ -26,7 +26,9 @@ export default function DeviceAuthSuccessPage() {
-

{t("deviceActivation")}

+

+ {t("deviceActivation")} +

diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index 615f33cf0..f632a88ce 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -98,7 +98,11 @@ export default async function Page(props: { )} - + {(!signUpDisabled || isInvite) && (

diff --git a/src/app/auth/reset-password/ResetPasswordForm.tsx b/src/app/auth/reset-password/ResetPasswordForm.tsx index 986c52e40..b6afd6d8c 100644 --- a/src/app/auth/reset-password/ResetPasswordForm.tsx +++ b/src/app/auth/reset-password/ResetPasswordForm.tsx @@ -88,18 +88,18 @@ export default function ResetPasswordForm({ const formSchema = z .object({ - email: z.email({ message: t('emailInvalid') }), - token: z.string().min(8, { message: t('tokenInvalid') }), + email: z.email({ message: t("emailInvalid") }), + token: z.string().min(8, { message: t("tokenInvalid") }), password: passwordSchema, confirmPassword: passwordSchema }) .refine((data) => data.password === data.confirmPassword, { path: ["confirmPassword"], - message: t('passwordNotMatch') + message: t("passwordNotMatch") }); const mfaSchema = z.object({ - code: z.string().length(6, { message: t('pincodeInvalid') }) + code: z.string().length(6, { message: t("pincodeInvalid") }) }); const form = useForm({ @@ -139,8 +139,8 @@ export default function ResetPasswordForm({ } as RequestPasswordResetBody ) .catch((e) => { - setError(formatAxiosError(e, t('errorOccurred'))); - console.error(t('passwordErrorRequestReset'), e); + setError(formatAxiosError(e, t("errorOccurred"))); + console.error(t("passwordErrorRequestReset"), e); setIsSubmitting(false); }); @@ -169,8 +169,8 @@ export default function ResetPasswordForm({ } as ResetPasswordBody ) .catch((e) => { - setError(formatAxiosError(e, t('errorOccurred'))); - console.error(t('passwordErrorReset'), e); + setError(formatAxiosError(e, t("errorOccurred"))); + console.error(t("passwordErrorReset"), e); setIsSubmitting(false); }); @@ -186,7 +186,11 @@ export default function ResetPasswordForm({ return; } - setSuccessMessage(quickstart ? t('accountSetupSuccess') : t('passwordResetSuccess')); + setSuccessMessage( + quickstart + ? t("accountSetupSuccess") + : t("passwordResetSuccess") + ); // Auto-login after successful password reset try { @@ -208,7 +212,10 @@ export default function ResetPasswordForm({ try { await api.post("/auth/verify-email/request"); } catch (verificationError) { - console.error("Failed to send verification code:", verificationError); + console.error( + "Failed to send verification code:", + verificationError + ); } if (redirect) { @@ -229,7 +236,6 @@ export default function ResetPasswordForm({ } setIsSubmitting(false); }, 1500); - } catch (loginError) { // Auto-login failed, but password reset was successful console.error("Auto-login failed:", loginError); @@ -251,13 +257,14 @@ export default function ResetPasswordForm({ - {quickstart ? t('completeAccountSetup') : t('passwordReset')} + {quickstart + ? t("completeAccountSetup") + : t("passwordReset")} {quickstart - ? t('completeAccountSetupDescription') - : t('passwordResetDescription') - } + ? t("completeAccountSetupDescription") + : t("passwordResetDescription")} @@ -276,16 +283,19 @@ export default function ResetPasswordForm({ name="email" render={({ field }) => ( - {t('email')} + + {t("email")} + {quickstart - ? t('accountSetupSent') - : t('passwordResetSent') - } + ? t("accountSetupSent") + : t( + "passwordResetSent" + )} )} @@ -306,7 +316,9 @@ export default function ResetPasswordForm({ name="email" render={({ field }) => ( - {t('email')} + + {t("email")} + {quickstart - ? t('accountSetupCode') - : t('passwordResetCode') - } + ? t( + "accountSetupCode" + ) + : t( + "passwordResetCode" + )} {quickstart - ? t('accountSetupCodeDescription') - : t('passwordResetCodeDescription') - } + ? t( + "accountSetupCodeDescription" + ) + : t( + "passwordResetCodeDescription" + )} )} @@ -355,9 +373,8 @@ export default function ResetPasswordForm({ {quickstart - ? t('passwordCreate') - : t('passwordNew') - } + ? t("passwordCreate") + : t("passwordNew")} {quickstart - ? t('passwordCreateConfirm') - : t('passwordNewConfirm') - } + ? t( + "passwordCreateConfirm" + ) + : t( + "passwordNewConfirm" + )} ( - {t('pincodeAuth')} + {t("pincodeAuth")}

@@ -475,8 +495,10 @@ export default function ResetPasswordForm({ )} {state === "reset" - ? (quickstart ? t('completeSetup') : t('passwordReset')) - : t('pincodeSubmit2')} + ? quickstart + ? t("completeSetup") + : t("passwordReset") + : t("pincodeSubmit2")} )} @@ -491,9 +513,8 @@ export default function ResetPasswordForm({ )} {quickstart - ? t('accountSetupSubmit') - : t('passwordResetSubmit') - } + ? t("accountSetupSubmit") + : t("passwordResetSubmit")} )} @@ -507,7 +528,7 @@ export default function ResetPasswordForm({ mfaForm.reset(); }} > - {t('passwordBack')} + {t("passwordBack")} )} @@ -521,7 +542,7 @@ export default function ResetPasswordForm({ form.reset(); }} > - {t('backToEmail')} + {t("backToEmail")} )}
diff --git a/src/app/auth/verify-email/page.tsx b/src/app/auth/verify-email/page.tsx index c549abf0c..e4428370b 100644 --- a/src/app/auth/verify-email/page.tsx +++ b/src/app/auth/verify-email/page.tsx @@ -35,10 +35,7 @@ export default async function Page(props: { return ( <> - + ); } diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index 60c02bee0..d3ca37ccf 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -1,17 +1,16 @@ import { getTranslations } from "next-intl/server"; export default async function NotFound() { - const t = await getTranslations(); return (

404

- {t('pageNotFound')} + {t("pageNotFound")}

- {t('pageNotFoundDescription')} + {t("pageNotFoundDescription")}

); diff --git a/src/app/setup/page.tsx b/src/app/setup/page.tsx index 530725542..36853e5c6 100644 --- a/src/app/setup/page.tsx +++ b/src/app/setup/page.tsx @@ -312,7 +312,9 @@ export default function StepperForm() {
- {t("setupSubnetDescription")} + {t( + "setupSubnetDescription" + )}
)} diff --git a/src/components/AccessPageHeaderAndNav.tsx b/src/components/AccessPageHeaderAndNav.tsx index 47690dc66..12a34f06c 100644 --- a/src/components/AccessPageHeaderAndNav.tsx +++ b/src/components/AccessPageHeaderAndNav.tsx @@ -14,21 +14,21 @@ export default function AccessPageHeaderAndNav({ hasInvitations }: AccessPageHeaderAndNavProps) { const t = useTranslations(); - + const navItems = [ { - title: t('users'), + title: t("users"), href: `/{orgId}/settings/access/users` }, { - title: t('roles'), + title: t("roles"), href: `/{orgId}/settings/access/roles` } ]; if (hasInvitations) { navItems.push({ - title: t('invite'), + title: t("invite"), href: `/{orgId}/settings/access/invitations` }); } @@ -36,13 +36,11 @@ export default function AccessPageHeaderAndNav({ return ( <> - - {children} - + {children} ); } diff --git a/src/components/AccessToken.tsx b/src/components/AccessToken.tsx index 969a2d4e0..54f926433 100644 --- a/src/components/AccessToken.tsx +++ b/src/components/AccessToken.tsx @@ -20,10 +20,7 @@ type AccessTokenProps = { resourceId?: number; }; -export default function AccessToken({ - token, - resourceId -}: AccessTokenProps) { +export default function AccessToken({ token, resourceId }: AccessTokenProps) { const [loading, setLoading] = useState(true); const [isValid, setIsValid] = useState(false); @@ -79,7 +76,7 @@ export default function AccessToken({ ); } } catch (e) { - console.error(t('accessTokenError'), e); + console.error(t("accessTokenError"), e); } finally { setLoading(false); } @@ -102,7 +99,7 @@ export default function AccessToken({ ); } } catch (e) { - console.error(t('accessTokenError'), e); + console.error(t("accessTokenError"), e); } finally { setLoading(false); } @@ -118,26 +115,22 @@ export default function AccessToken({ function renderTitle() { if (isValid) { - return t('accessGranted'); + return t("accessGranted"); } else { - return t('accessUrlInvalid'); + return t("accessUrlInvalid"); } } function renderContent() { if (isValid) { - return ( -
- {t('accessGrantedDescription')} -
- ); + return
{t("accessGrantedDescription")}
; } else { return (
- {t('accessUrlInvalidDescription')} + {t("accessUrlInvalidDescription")}
diff --git a/src/components/AccessTokenUsage.tsx b/src/components/AccessTokenUsage.tsx index c44f43b7d..4b1703717 100644 --- a/src/components/AccessTokenUsage.tsx +++ b/src/components/AccessTokenUsage.tsx @@ -44,31 +44,35 @@ export default function AccessTokenSection({ <>

- {t('shareTokenDescription')} + {t("shareTokenDescription")}

- {t('accessToken')} - {t('usageExamples')} + {t("accessToken")} + + {t("usageExamples")} +
-
{t('tokenId')}
+
{t("tokenId")}
-
{t('token')}
+
{t("token")}
-

{t('requestHeades')}

+

+ {t("requestHeades")} +

-

{t('queryParameter')}

+

+ {t("queryParameter")} +

@@ -85,17 +91,17 @@ ${env.server.resourceAccessTokenHeadersToken}: ${token}`} - {t('importantNote')} + {t("importantNote")} - {t('shareImportantDescription')} + {t("shareImportantDescription")}
- {t('shareTokenSecurety')} + {t("shareTokenSecurety")}
); diff --git a/src/components/AdminIdpDataTable.tsx b/src/components/AdminIdpDataTable.tsx index 26ec6b318..e11ed8fc8 100644 --- a/src/components/AdminIdpDataTable.tsx +++ b/src/components/AdminIdpDataTable.tsx @@ -26,10 +26,10 @@ export function IdpDataTable({ columns={columns} data={data} persistPageSize="idp-table" - title={t('idp')} - searchPlaceholder={t('idpSearch')} + title={t("idp")} + searchPlaceholder={t("idpSearch")} searchColumn="name" - addButtonText={t('idpAdd')} + addButtonText={t("idpAdd")} onAdd={() => { router.push("/admin/idp/create"); }} diff --git a/src/components/AdminIdpTable.tsx b/src/components/AdminIdpTable.tsx index b0782fcb8..76a0fdd73 100644 --- a/src/components/AdminIdpTable.tsx +++ b/src/components/AdminIdpTable.tsx @@ -175,9 +175,7 @@ export default function IdpTable({ idps }: Props) { - diff --git a/src/components/AdminUsersDataTable.tsx b/src/components/AdminUsersDataTable.tsx index 2eb3c2e21..afa473e86 100644 --- a/src/components/AdminUsersDataTable.tsx +++ b/src/components/AdminUsersDataTable.tsx @@ -1,8 +1,6 @@ "use client"; -import { - ColumnDef, -} from "@tanstack/react-table"; +import { ColumnDef } from "@tanstack/react-table"; import { DataTable } from "@app/components/ui/data-table"; import { useTranslations } from "next-intl"; @@ -19,7 +17,6 @@ export function UsersDataTable({ onRefresh, isRefreshing }: DataTableProps) { - const t = useTranslations(); return ( @@ -27,8 +24,8 @@ export function UsersDataTable({ columns={columns} data={data} persistPageSize="userServer-table" - title={t('userServer')} - searchPlaceholder={t('userSearch')} + title={t("userServer")} + searchPlaceholder={t("userSearch")} searchColumn="email" onRefresh={onRefresh} isRefreshing={isRefreshing} diff --git a/src/components/ApiKeysDataTable.tsx b/src/components/ApiKeysDataTable.tsx index c4f931363..8069b101a 100644 --- a/src/components/ApiKeysDataTable.tsx +++ b/src/components/ApiKeysDataTable.tsx @@ -44,7 +44,6 @@ export function ApiKeysDataTable({ onRefresh, isRefreshing }: DataTableProps) { - const t = useTranslations(); return ( @@ -52,11 +51,11 @@ export function ApiKeysDataTable({ columns={columns} data={data} persistPageSize="apiKeys-table" - title={t('apiKeys')} - searchPlaceholder={t('searchApiKeys')} + title={t("apiKeys")} + searchPlaceholder={t("searchApiKeys")} searchColumn="name" onAdd={addApiKey} - addButtonText={t('apiKeysAdd')} + addButtonText={t("apiKeysAdd")} onRefresh={onRefresh} isRefreshing={isRefreshing} enableColumnVisibility={true} diff --git a/src/components/ApiKeysTable.tsx b/src/components/ApiKeysTable.tsx index 863d2bc66..c3202277c 100644 --- a/src/components/ApiKeysTable.tsx +++ b/src/components/ApiKeysTable.tsx @@ -108,7 +108,7 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) { { accessorKey: "key", friendlyName: t("key"), - header: () => ({t("key")}), + header: () => {t("key")}, cell: ({ row }) => { const r = row.original; return {r.key}; @@ -117,7 +117,7 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) { { accessorKey: "createdAt", friendlyName: t("createdAt"), - header: () => ({t("createdAt")}), + header: () => {t("createdAt")}, cell: ({ row }) => { const r = row.original; return {moment(r.createdAt).format("lll")} ; @@ -161,9 +161,7 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) { - diff --git a/src/components/AutoLoginHandler.tsx b/src/components/AutoLoginHandler.tsx index 2391ece6e..ee8f41264 100644 --- a/src/components/AutoLoginHandler.tsx +++ b/src/components/AutoLoginHandler.tsx @@ -67,7 +67,8 @@ export default function AutoLoginHandler({ console.error("Failed to generate OIDC URL:", e); setError( t("autoLoginErrorGeneratingUrl", { - defaultValue: "An unexpected error occurred. Please try again." + defaultValue: + "An unexpected error occurred. Please try again." }) ); } finally { diff --git a/src/components/BrandingLogo.tsx b/src/components/BrandingLogo.tsx index 540b8e0e2..b4472365f 100644 --- a/src/components/BrandingLogo.tsx +++ b/src/components/BrandingLogo.tsx @@ -38,7 +38,7 @@ export default function BrandingLogo(props: BrandingLogoProps) { if (isUnlocked() && env.branding.logo?.darkPath) { return env.branding.logo.darkPath; } - return "/logo/word_mark_white.png"; + return "/logo/word_mark_white.png"; } const path = getPath(); diff --git a/src/components/ChangePasswordDialog.tsx b/src/components/ChangePasswordDialog.tsx index 85a55ab2f..3430f26a9 100644 --- a/src/components/ChangePasswordDialog.tsx +++ b/src/components/ChangePasswordDialog.tsx @@ -20,7 +20,10 @@ type ChangePasswordDialogProps = { setOpen: (val: boolean) => void; }; -export default function ChangePasswordDialog({ open, setOpen }: ChangePasswordDialogProps) { +export default function ChangePasswordDialog({ + open, + setOpen +}: ChangePasswordDialogProps) { const t = useTranslations(); const [currentStep, setCurrentStep] = useState(1); const [loading, setLoading] = useState(false); @@ -47,18 +50,16 @@ export default function ChangePasswordDialog({ open, setOpen }: ChangePasswordDi > - - {t('changePassword')} - + {t("changePassword")} - {t('changePasswordDescription')} + {t("changePasswordDescription")} setOpen(false)} @@ -77,7 +78,7 @@ export default function ChangePasswordDialog({ open, setOpen }: ChangePasswordDi disabled={loading} onClick={handleSubmit} > - {t('submit')} + {t("submit")} )} diff --git a/src/components/ChangePasswordForm.tsx b/src/components/ChangePasswordForm.tsx index 5d1395bca..57015712e 100644 --- a/src/components/ChangePasswordForm.tsx +++ b/src/components/ChangePasswordForm.tsx @@ -22,11 +22,7 @@ import { import { toast } from "@app/hooks/useToast"; import { formatAxiosError } from "@app/lib/api"; import { useTranslations } from "next-intl"; -import { - InputOTP, - InputOTPGroup, - InputOTPSlot -} from "./ui/input-otp"; +import { InputOTP, InputOTPGroup, InputOTPSlot } from "./ui/input-otp"; import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"; import { ChangePasswordResponse } from "@server/routers/auth"; import { cn } from "@app/lib/cn"; @@ -114,14 +110,22 @@ const ChangePasswordForm = forwardRef< onLoadingChange?.(loading); }, [loading, onLoadingChange]); - const passwordSchema = z.object({ - oldPassword: z.string().min(1, { message: t("passwordRequired") }), - newPassword: z.string().min(8, { message: t("passwordRequirementsChars") }), - confirmPassword: z.string().min(1, { message: t("passwordRequired") }) - }).refine((data) => data.newPassword === data.confirmPassword, { - message: t("passwordsDoNotMatch"), - path: ["confirmPassword"], - }); + const passwordSchema = z + .object({ + oldPassword: z + .string() + .min(1, { message: t("passwordRequired") }), + newPassword: z + .string() + .min(8, { message: t("passwordRequirementsChars") }), + confirmPassword: z + .string() + .min(1, { message: t("passwordRequired") }) + }) + .refine((data) => data.newPassword === data.confirmPassword, { + message: t("passwordsDoNotMatch"), + path: ["confirmPassword"] + }); const mfaSchema = z.object({ code: z.string().length(6, { message: t("pincodeInvalid") }) @@ -143,11 +147,13 @@ const ChangePasswordForm = forwardRef< } }); - const changePassword = async (values: z.infer) => { + const changePassword = async ( + values: z.infer + ) => { setLoading(true); const endpoint = `/auth/change-password`; - const payload = { + const payload = { oldPassword: values.oldPassword, newPassword: values.newPassword }; @@ -181,7 +187,7 @@ const ChangePasswordForm = forwardRef< const endpoint = `/auth/change-password`; const passwordValues = passwordForm.getValues(); - const payload = { + const payload = { oldPassword: passwordValues.oldPassword, newPassword: passwordValues.newPassword, code: values.code @@ -303,7 +309,9 @@ const ChangePasswordForm = forwardRef<
- {t("passwordStrength")} + {t( + "passwordStrength" + )}
- {t("passwordRequirements")} + {t( + "passwordRequirements" + )}
@@ -505,13 +515,14 @@ const ChangePasswordForm = forwardRef< {confirmPasswordValue.length > 0 && !doPasswordsMatch && (

- {t("passwordsDoNotMatch")} + {t( + "passwordsDoNotMatch" + )}

)} {/* Only show FormMessage when field is empty */} - {confirmPasswordValue.length === 0 && ( - - )} + {confirmPasswordValue.length === + 0 && } )} /> @@ -523,7 +534,9 @@ const ChangePasswordForm = forwardRef< {step === 2 && (
-

{t("otpAuth")}

+

+ {t("otpAuth")} +

{t("otpAuthDescription")}

@@ -551,9 +564,12 @@ const ChangePasswordForm = forwardRef< onChange={( value: string ) => { - field.onChange(value); + field.onChange( + value + ); if ( - value.length === 6 + value.length === + 6 ) { mfaForm.handleSubmit( confirmMfa @@ -630,10 +646,7 @@ const ChangePasswordForm = forwardRef< )} {step === 3 && ( - )} @@ -644,4 +657,4 @@ const ChangePasswordForm = forwardRef< } ); -export default ChangePasswordForm; \ No newline at end of file +export default ChangePasswordForm; diff --git a/src/components/ClientsDataTable.tsx b/src/components/ClientsDataTable.tsx index cf42e7bc3..8d38e5979 100644 --- a/src/components/ClientsDataTable.tsx +++ b/src/components/ClientsDataTable.tsx @@ -1,8 +1,6 @@ "use client"; -import { - ColumnDef, -} from "@tanstack/react-table"; +import { ColumnDef } from "@tanstack/react-table"; import { DataTable } from "@app/components/ui/data-table"; type TabFilter = { diff --git a/src/components/ColumnFilter.tsx b/src/components/ColumnFilter.tsx index eee91ecc8..a856984eb 100644 --- a/src/components/ColumnFilter.tsx +++ b/src/components/ColumnFilter.tsx @@ -1,7 +1,18 @@ import { useState } from "react"; import { Button } from "@app/components/ui/button"; -import { Popover, PopoverContent, PopoverTrigger } from "@app/components/ui/popover"; -import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@app/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger +} from "@app/components/ui/popover"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList +} from "@app/components/ui/command"; import { CheckIcon, ChevronDownIcon, Filter } from "lucide-react"; import { cn } from "@app/lib/cn"; @@ -31,7 +42,9 @@ export function ColumnFilter({ }: ColumnFilterProps) { const [open, setOpen] = useState(false); - const selectedOption = options.find(option => option.value === selectedValue); + const selectedOption = options.find( + (option) => option.value === selectedValue + ); return ( @@ -49,7 +62,9 @@ export function ColumnFilter({
- {selectedOption ? selectedOption.label : placeholder} + {selectedOption + ? selectedOption.label + : placeholder}
@@ -79,7 +94,9 @@ export function ColumnFilter({ value={option.label} onSelect={() => { onValueChange( - selectedValue === option.value ? undefined : option.value + selectedValue === option.value + ? undefined + : option.value ); setOpen(false); }} @@ -101,4 +118,4 @@ export function ColumnFilter({
); -} \ No newline at end of file +} diff --git a/src/components/ContainersSelector.tsx b/src/components/ContainersSelector.tsx index 7a5cc74b2..5a0f49f99 100644 --- a/src/components/ContainersSelector.tsx +++ b/src/components/ContainersSelector.tsx @@ -186,7 +186,7 @@ const DockerContainersTable: FC<{ { accessorKey: "name", friendlyName: t("containerName"), - header: () => ({t("containerName")}), + header: () => {t("containerName")}, cell: ({ row }) => (
{row.original.name}
) @@ -194,7 +194,7 @@ const DockerContainersTable: FC<{ { accessorKey: "image", friendlyName: t("containerImage"), - header: () => ({t("containerImage")}), + header: () => {t("containerImage")}, cell: ({ row }) => (
{row.original.image} @@ -204,7 +204,7 @@ const DockerContainersTable: FC<{ { accessorKey: "state", friendlyName: t("containerState"), - header: () => ({t("containerState")}), + header: () => {t("containerState")}, cell: ({ row }) => ( ({t("containerNetworks")}), + header: () => {t("containerNetworks")}, cell: ({ row }) => { const networks = Object.keys(row.original.networks); return ( @@ -239,7 +239,9 @@ const DockerContainersTable: FC<{ { accessorKey: "hostname", friendlyName: t("containerHostnameIp"), - header: () => ({t("containerHostnameIp")}), + header: () => ( + {t("containerHostnameIp")} + ), enableHiding: false, cell: ({ row }) => (
@@ -250,7 +252,7 @@ const DockerContainersTable: FC<{ { accessorKey: "labels", friendlyName: t("containerLabels"), - header: () => ({t("containerLabels")}), + header: () => {t("containerLabels")}, cell: ({ row }) => { const labels = row.original.labels || {}; const labelEntries = Object.entries(labels); @@ -302,7 +304,7 @@ const DockerContainersTable: FC<{ }, { accessorKey: "ports", - header: () => ({t("containerPorts")}), + header: () => {t("containerPorts")}, enableHiding: false, cell: ({ row }) => { const ports = getExposedPorts(row.original); @@ -360,7 +362,7 @@ const DockerContainersTable: FC<{ }, { id: "actions", - header: () => ({t("containerActions")}), + header: () => {t("containerActions")}, cell: ({ row }) => { const ports = getExposedPorts(row.original); return ( diff --git a/src/components/CopyTextBox.tsx b/src/components/CopyTextBox.tsx index 72a99c3fb..e516bf0d6 100644 --- a/src/components/CopyTextBox.tsx +++ b/src/components/CopyTextBox.tsx @@ -29,7 +29,7 @@ export default function CopyTextBox({ setIsCopied(true); setTimeout(() => setIsCopied(false), 2000); } catch (err) { - console.error(t('copyTextFailed'), err); + console.error(t("copyTextFailed"), err); } } }; @@ -54,7 +54,7 @@ export default function CopyTextBox({ type="button" className="absolute top-0.5 right-0 z-10 bg-card" onClick={copyToClipboard} - aria-label={t('copyTextClipboard')} + aria-label={t("copyTextClipboard")} > {isCopied ? ( diff --git a/src/components/CopyToClipboard.tsx b/src/components/CopyToClipboard.tsx index 5cdeed55d..b755f9a59 100644 --- a/src/components/CopyToClipboard.tsx +++ b/src/components/CopyToClipboard.tsx @@ -9,7 +9,11 @@ type CopyToClipboardProps = { isLink?: boolean; }; -const CopyToClipboard = ({ text, displayText, isLink }: CopyToClipboardProps) => { +const CopyToClipboard = ({ + text, + displayText, + isLink +}: CopyToClipboardProps) => { const [copied, setCopied] = useState(false); const handleCopy = () => { @@ -60,7 +64,7 @@ const CopyToClipboard = ({ text, displayText, isLink }: CopyToClipboardProps) => ) : ( )} - {t('copyText')} + {t("copyText")}
); diff --git a/src/components/CreateDomainForm.tsx b/src/components/CreateDomainForm.tsx index 7d614376f..9a187f1ed 100644 --- a/src/components/CreateDomainForm.tsx +++ b/src/components/CreateDomainForm.tsx @@ -45,11 +45,16 @@ import { } from "@app/components/InfoSection"; import { useOrgContext } from "@app/hooks/useOrgContext"; import { build } from "@server/build"; -import { toASCII, toUnicode } from 'punycode'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"; +import { toASCII, toUnicode } from "punycode"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from "./ui/select"; import { useRouter } from "next/navigation"; - // Helper functions for Unicode domain handling function toPunycode(domain: string): string { try { @@ -76,9 +81,9 @@ function isValidDomainFormat(domain: string): boolean { return false; } - const parts = domain.split('.'); + const parts = domain.split("."); for (const part of parts) { - if (part.length === 0 || part.startsWith('-') || part.endsWith('-')) { + if (part.length === 0 || part.startsWith("-") || part.endsWith("-")) { return false; } if (part.length > 63) { @@ -137,7 +142,8 @@ export default function CreateDomainForm({ resolver: zodResolver(formSchema), defaultValues: { baseDomain: "", - type: build == "oss" || !env.flags.usePangolinDns ? "wildcard" : "ns", + type: + build == "oss" || !env.flags.usePangolinDns ? "wildcard" : "ns", certResolver: null, preferWildcardCert: false } @@ -172,7 +178,9 @@ export default function CreateDomainForm({ description: t("domainCreatedDescription") }); onCreated?.(domainData); - router.push(`/${org.org.orgId}/settings/domains/${domainData.domainId}`); + router.push( + `/${org.org.orgId}/settings/domains/${domainData.domainId}` + ); } catch (e) { toast({ title: t("error"), @@ -182,7 +190,7 @@ export default function CreateDomainForm({ } finally { setLoading(false); } - }; + } // Domain type options let domainOptions: any = []; @@ -225,145 +233,213 @@ export default function CreateDomainForm({ -
- - ( - - - - - )} - /> - ( - - {t("domain")} - - - - {punycodePreview && ( - - - - {t("internationaldomaindetected")} - -
-

{t("willbestoredas")} {punycodePreview}

-
-
-
-
- )} - -
- )} - /> - {domainType === "wildcard" && ( - <> - ( - - {t("certResolver")} - - - - - - )} + + + ( + + - {form.watch("certResolver") !== null && - form.watch("certResolver") !== "default" && ( - ( - - - field.onChange(e.target.value)} - /> - - - - )} - /> - )} + + + )} + /> + ( + + {t("domain")} + + + + {punycodePreview && ( + + + + + {t( + "internationaldomaindetected" + )} + + +
+

+ {t( + "willbestoredas" + )}{" "} + + { + punycodePreview + } + +

+
+
+
+
+ )} + +
+ )} + /> + {domainType === "wildcard" && ( + <> + ( + + + {t("certResolver")} + + + + + + + )} + /> + {form.watch("certResolver") !== null && + form.watch("certResolver") !== + "default" && ( + ( + + + + field.onChange( + e.target + .value + ) + } + /> + + + + )} + /> + )} - {form.watch("certResolver") !== null && - form.watch("certResolver") !== "default" && ( - ( - - - - - {/*
+ {form.watch("certResolver") !== null && + form.watch("certResolver") !== + "default" && ( + ( + + + + + {/*
{t("preferWildcardCert")}
*/} -
- )} - /> - )} - - )} - - - + + )} + /> + )} + + )} + + diff --git a/src/components/CreateInternalResourceDialog.tsx b/src/components/CreateInternalResourceDialog.tsx index 94238f9de..91ef26daf 100644 --- a/src/components/CreateInternalResourceDialog.tsx +++ b/src/components/CreateInternalResourceDialog.tsx @@ -272,7 +272,7 @@ export default function CreateInternalResourceDialog({ // an alias is required if (data.mode === "host" && isHostname(data.destination)) { const currentAlias = data.alias?.trim() || ""; - + if (!currentAlias) { // Prefill alias based on destination let aliasValue = data.destination; @@ -281,7 +281,7 @@ export default function CreateInternalResourceDialog({ const cleanedName = cleanForFQDN(data.name); aliasValue = `${cleanedName}.internal`; } - + // Update the form with the prefilled alias form.setValue("alias", aliasValue); data.alias = aliasValue; diff --git a/src/components/CreateRoleForm.tsx b/src/components/CreateRoleForm.tsx index dca75f5a2..b8df1f783 100644 --- a/src/components/CreateRoleForm.tsx +++ b/src/components/CreateRoleForm.tsx @@ -48,7 +48,7 @@ export default function CreateRoleForm({ const t = useTranslations(); const formSchema = z.object({ - name: z.string({ message: t('nameRequired') }).max(32), + name: z.string({ message: t("nameRequired") }).max(32), description: z.string().max(255).optional() }); @@ -78,10 +78,10 @@ export default function CreateRoleForm({ .catch((e) => { toast({ variant: "destructive", - title: t('accessRoleErrorCreate'), + title: t("accessRoleErrorCreate"), description: formatAxiosError( e, - t('accessRoleErrorCreateDescription') + t("accessRoleErrorCreateDescription") ) }); }); @@ -89,8 +89,8 @@ export default function CreateRoleForm({ if (res && res.status === 201) { toast({ variant: "default", - title: t('accessRoleCreated'), - description: t('accessRoleCreatedDescription') + title: t("accessRoleCreated"), + description: t("accessRoleCreatedDescription") }); if (open) { @@ -117,9 +117,9 @@ export default function CreateRoleForm({ > - {t('accessRoleCreate')} + {t("accessRoleCreate")} - {t('accessRoleCreateDescription')} + {t("accessRoleCreateDescription")} @@ -134,7 +134,9 @@ export default function CreateRoleForm({ name="name" render={({ field }) => ( - {t('accessRoleName')} + + {t("accessRoleName")} + @@ -147,7 +149,9 @@ export default function CreateRoleForm({ name="description" render={({ field }) => ( - {t('description')} + + {t("description")} + @@ -160,7 +164,7 @@ export default function CreateRoleForm({ - + diff --git a/src/components/CreateShareLinkForm.tsx b/src/components/CreateShareLinkForm.tsx index 3cc203f3b..361bfe7d0 100644 --- a/src/components/CreateShareLinkForm.tsx +++ b/src/components/CreateShareLinkForm.tsx @@ -67,7 +67,7 @@ import { } from "@app/components/ui/collapsible"; import AccessTokenSection from "@app/components/AccessTokenUsage"; import { useTranslations } from "next-intl"; -import { toUnicode } from 'punycode'; +import { toUnicode } from "punycode"; type FormProps = { open: boolean; @@ -104,7 +104,7 @@ export default function CreateShareLinkForm({ >([]); const formSchema = z.object({ - resourceId: z.number({ message: t('shareErrorSelectResource') }), + resourceId: z.number({ message: t("shareErrorSelectResource") }), resourceName: z.string(), resourceUrl: z.string(), timeUnit: z.string(), @@ -113,12 +113,12 @@ export default function CreateShareLinkForm({ }); const timeUnits = [ - { unit: "minutes", name: t('minutes') }, - { unit: "hours", name: t('hours') }, - { unit: "days", name: t('days') }, - { unit: "weeks", name: t('weeks') }, - { unit: "months", name: t('months') }, - { unit: "years", name: t('years') } + { unit: "minutes", name: t("minutes") }, + { unit: "hours", name: t("hours") }, + { unit: "days", name: t("days") }, + { unit: "weeks", name: t("weeks") }, + { unit: "months", name: t("months") }, + { unit: "years", name: t("years") } ]; const form = useForm>({ @@ -144,10 +144,10 @@ export default function CreateShareLinkForm({ console.error(e); toast({ variant: "destructive", - title: t('shareErrorFetchResource'), + title: t("shareErrorFetchResource"), description: formatAxiosError( e, - t('shareErrorFetchResourceDescription') + t("shareErrorFetchResourceDescription") ) }); }); @@ -204,17 +204,21 @@ export default function CreateShareLinkForm({ validForSeconds: neverExpire ? undefined : timeInSeconds, title: values.title || - t('shareLink', {resource: (values.resourceName || "Resource" + values.resourceId)}) + t("shareLink", { + resource: + values.resourceName || + "Resource" + values.resourceId + }) } ) .catch((e) => { console.error(e); toast({ variant: "destructive", - title: t('shareErrorCreate'), + title: t("shareErrorCreate"), description: formatAxiosError( e, - t('shareErrorCreateDescription') + t("shareErrorCreateDescription") ) }); }); @@ -263,9 +267,9 @@ export default function CreateShareLinkForm({ > - {t('shareCreate')} + {t("shareCreate")} - {t('shareCreateDescription')} + {t("shareCreateDescription")} @@ -283,7 +287,7 @@ export default function CreateShareLinkForm({ render={({ field }) => ( - {t('resource')} + {t("resource")} @@ -301,17 +305,25 @@ export default function CreateShareLinkForm({ ? getSelectedResourceName( field.value ) - : t('resourceSelect')} + : t( + "resourceSelect" + )} - + - {t('resourcesNotFound')} + {t( + "resourcesNotFound" + )} {resources.map( @@ -367,7 +379,9 @@ export default function CreateShareLinkForm({ render={({ field }) => ( - {t('shareTitleOptional')} + {t( + "shareTitleOptional" + )} @@ -379,7 +393,9 @@ export default function CreateShareLinkForm({
- {t('expireIn')} + + {t("expireIn")} +
- + @@ -458,12 +480,12 @@ export default function CreateShareLinkForm({ htmlFor="terms" className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" > - {t('neverExpire')} + {t("neverExpire")}

- {t('shareExpireDescription')} + {t("shareExpireDescription")}

@@ -471,16 +493,15 @@ export default function CreateShareLinkForm({ )} {link && (
-

- {t('shareSeeOnce')} -

-

- {t('shareAccessHint')} -

+

{t("shareSeeOnce")}

+

{t("shareAccessHint")}

- +
@@ -503,12 +524,12 @@ export default function CreateShareLinkForm({ className="p-0 flex items-center justify-between w-full" >

- {t('shareTokenUsage')} + {t("shareTokenUsage")}

- {t('toggle')} + {t("toggle")}
@@ -538,7 +559,7 @@ export default function CreateShareLinkForm({ - + diff --git a/src/components/Credenza.tsx b/src/components/Credenza.tsx index 6b80b3d30..32c09b496 100644 --- a/src/components/Credenza.tsx +++ b/src/components/Credenza.tsx @@ -78,7 +78,10 @@ const CredenzaClose = ({ className, children, ...props }: CredenzaProps) => { const CredenzaClose = isDesktop ? DialogClose : DrawerClose; return ( - + {children} ); @@ -155,7 +158,13 @@ const CredenzaBody = ({ className, children, ...props }: CredenzaProps) => { // ); return ( -
+
{children}
); diff --git a/src/components/DNSRecordTable.tsx b/src/components/DNSRecordTable.tsx index bc764eb93..6995301dd 100644 --- a/src/components/DNSRecordTable.tsx +++ b/src/components/DNSRecordTable.tsx @@ -21,10 +21,7 @@ type Props = { type: string | null; }; -export default function DNSRecordsTable({ - records, - type -}: Props) { +export default function DNSRecordsTable({ records, type }: Props) { const t = useTranslations(); const env = useEnvContext(); @@ -114,11 +111,5 @@ export default function DNSRecordsTable({ ...(env.env.flags.usePangolinDns ? [statusColumn] : []) ]; - return ( - - ); + return ; } diff --git a/src/components/DNSRecordsDataTable.tsx b/src/components/DNSRecordsDataTable.tsx index 2a6b75fd1..786b2d716 100644 --- a/src/components/DNSRecordsDataTable.tsx +++ b/src/components/DNSRecordsDataTable.tsx @@ -110,7 +110,11 @@ export function DNSRecordsDataTable({

{t("dnsRecord")}

{t("required")}
- +