From 0b8bb5a974624a15947dd75f4535ed9d7116a8b2 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Wed, 18 Jun 2025 16:11:37 -0400 Subject: [PATCH 1/7] don't use word mark in nav bar --- src/components/Layout.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 11b992e3..28ef307b 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -78,15 +78,17 @@ export function Layout({ } if (lightOrDark === "light") { - return "/logo/word_mark_black.png"; + // return "/logo/word_mark_black.png"; + return "/logo/pangolin_orange.svg"; } - return "/logo/word_mark_white.png"; + // return "/logo/word_mark_white.png"; + return "/logo/pangolin_orange.svg"; } setPath(getPath()); }, [theme, env]); - + const t = useTranslations(); return ( @@ -170,8 +172,8 @@ export function Layout({ Pangolin Logo From b56ba3ee2344034bd838a4f942e7e233bc951c9b Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Wed, 18 Jun 2025 16:22:01 -0400 Subject: [PATCH 2/7] prevent org id taken error for flashing after create org --- .gitignore | 3 ++- package-lock.json | 17 ++--------------- server/db/index.ts | 2 -- src/app/setup/page.tsx | 7 ++++--- 4 files changed, 8 insertions(+), 21 deletions(-) delete mode 100644 server/db/index.ts diff --git a/.gitignore b/.gitignore index cd73cef1..04c4b7ef 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,5 @@ installer bin .secrets test_event.json -.idea/ \ No newline at end of file +.idea/ +server/db/index.ts diff --git a/package-lock.json b/package-lock.json index f97c5ad4..24196000 100644 --- a/package-lock.json +++ b/package-lock.json @@ -88,6 +88,7 @@ "winston": "3.17.0", "winston-daily-rotate-file": "5.0.0", "ws": "8.18.2", + "yargs": "18.0.0", "zod": "3.25.56", "zod-validation-error": "3.4.1" }, @@ -120,8 +121,7 @@ "tsc-alias": "1.8.16", "tsx": "4.19.4", "typescript": "^5", - "typescript-eslint": "^8.34.0", - "yargs": "18.0.0" + "typescript-eslint": "^8.34.0" } }, "node_modules/@alloc/quick-lru": { @@ -6169,7 +6169,6 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^7.2.0", @@ -6184,7 +6183,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -6197,14 +6195,12 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true, "license": "MIT" }, "node_modules/cliui/node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", @@ -6222,7 +6218,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", @@ -7392,7 +7387,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -8337,7 +8331,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -8347,7 +8340,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -16474,7 +16466,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -16506,7 +16497,6 @@ "version": "18.0.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", - "dev": true, "license": "MIT", "dependencies": { "cliui": "^9.0.1", @@ -16524,7 +16514,6 @@ "version": "22.0.0", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", - "dev": true, "license": "ISC", "engines": { "node": "^20.19.0 || ^22.12.0 || >=23" @@ -16534,14 +16523,12 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true, "license": "MIT" }, "node_modules/yargs/node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", diff --git a/server/db/index.ts b/server/db/index.ts deleted file mode 100644 index 826c950f..00000000 --- a/server/db/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./sqlite"; -// export * from "./pg"; diff --git a/src/app/setup/page.tsx b/src/app/setup/page.tsx index d725d0b1..3a1f3725 100644 --- a/src/app/setup/page.tsx +++ b/src/app/setup/page.tsx @@ -45,6 +45,7 @@ export default function StepperForm() { const [loading, setLoading] = useState(false); const [isChecked, setIsChecked] = useState(false); const [error, setError] = useState(null); + const [orgCreated, setOrgCreated] = useState(false); const orgSchema = z.object({ orgName: z.string().min(1, { message: t('orgNameRequired') }), @@ -63,7 +64,7 @@ export default function StepperForm() { const router = useRouter(); const checkOrgIdAvailability = useCallback(async (value: string) => { - if (loading) { + if (loading || orgCreated) { return; } try { @@ -76,7 +77,7 @@ export default function StepperForm() { } catch (error) { setOrgIdTaken(false); } - }, []); + }, [loading, orgCreated, api]); const debouncedCheckOrgIdAvailability = useCallback( debounce(checkOrgIdAvailability, 300), @@ -108,7 +109,7 @@ export default function StepperForm() { }); if (res && res.status === 201) { - // setCurrentStep("site"); + setOrgCreated(true); router.push(`/${values.orgId}/settings/sites/create`); } } catch (e) { From c043912f9474211869ad3e17f31c3b2ac2776c4b Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Wed, 18 Jun 2025 16:42:07 -0400 Subject: [PATCH 3/7] fix bug preventing creating raw resources with api key closes #920 --- server/routers/resource/createResource.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/routers/resource/createResource.ts b/server/routers/resource/createResource.ts index ba115f71..1cbfa38e 100644 --- a/server/routers/resource/createResource.ts +++ b/server/routers/resource/createResource.ts @@ -39,7 +39,7 @@ const createHttpResourceSchema = z isBaseDomain: z.boolean().optional(), siteId: z.number(), http: z.boolean(), - protocol: z.string(), + protocol: z.enum(["tcp", "udp"]), domainId: z.string() }) .strict() @@ -71,7 +71,7 @@ const createRawResourceSchema = z name: z.string().min(1).max(255), siteId: z.number(), http: z.boolean(), - protocol: z.string(), + protocol: z.enum(["tcp", "udp"]), proxyPort: z.number().int().min(1).max(65535) }) .strict() @@ -85,7 +85,7 @@ const createRawResourceSchema = z return true; }, { - message: "Proxy port cannot be set" + message: "Raw resources are not allowed" } ); @@ -392,7 +392,7 @@ async function createRawResource( resourceId: newResource[0].resourceId }); - if (req.userOrgRoleId != adminRole[0].roleId) { + if (req.user && req.userOrgRoleId != adminRole[0].roleId) { // make sure the user can access the resource await trx.insert(userResources).values({ userId: req.user?.userId!, From 97ae76e4e7386a9200b43872f8fe16bab20a682c Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 19 Jun 2025 11:22:29 -0400 Subject: [PATCH 4/7] forward headers from server component and make trust_proxy config a number --- server/apiServer.ts | 5 +++-- server/lib/readConfigFile.ts | 2 +- src/lib/api/cookies.ts | 9 ++++++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/server/apiServer.ts b/server/apiServer.ts index 824a860d..ace27e9b 100644 --- a/server/apiServer.ts +++ b/server/apiServer.ts @@ -20,8 +20,9 @@ const externalPort = config.getRawConfig().server.external_port; export function createApiServer() { const apiServer = express(); - if (config.getRawConfig().server.trust_proxy) { - apiServer.set("trust proxy", 1); + const trustProxy = config.getRawConfig().server.trust_proxy; + if (trustProxy) { + apiServer.set("trust proxy", trustProxy); } const corsConfig = config.getRawConfig().server.cors; diff --git a/server/lib/readConfigFile.ts b/server/lib/readConfigFile.ts index 13efce5d..7a142739 100644 --- a/server/lib/readConfigFile.ts +++ b/server/lib/readConfigFile.ts @@ -112,7 +112,7 @@ export const configSchema = z.object({ credentials: z.boolean().optional() }) .optional(), - trust_proxy: z.boolean().optional().default(true), + trust_proxy: z.number().int().gte(0).optional().default(1), secret: z .string() .optional() diff --git a/src/lib/api/cookies.ts b/src/lib/api/cookies.ts index 6694c178..fac1810b 100644 --- a/src/lib/api/cookies.ts +++ b/src/lib/api/cookies.ts @@ -1,4 +1,4 @@ -import { cookies } from "next/headers"; +import { cookies, headers } from "next/headers"; import { pullEnv } from "../pullEnv"; export async function authCookieHeader() { @@ -7,9 +7,16 @@ export async function authCookieHeader() { const allCookies = await cookies(); const cookieName = env.server.sessionCookieName; const sessionId = allCookies.get(cookieName)?.value ?? null; + + // all other headers + // this is needed to pass through x-forwarded-for, x-forwarded-proto, etc. + const otherHeaders = await headers(); + const otherHeadersObject = Object.fromEntries(otherHeaders.entries()); + return { headers: { Cookie: `${cookieName}=${sessionId}`, + ...otherHeadersObject }, }; } From 58ba0d07b010ffb42af2140c2095a74169546340 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 19 Jun 2025 12:08:06 -0400 Subject: [PATCH 5/7] add migration to set trust_proxy to 1 if it exists in config --- server/setup/migrationsPg.ts | 4 ++- server/setup/migrationsSqlite.ts | 2 ++ server/setup/scriptsPg/1.6.0.ts | 46 +++++++++++++++++++++++++++++ server/setup/scriptsSqlite/1.6.0.ts | 46 +++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 server/setup/scriptsPg/1.6.0.ts create mode 100644 server/setup/scriptsSqlite/1.6.0.ts diff --git a/server/setup/migrationsPg.ts b/server/setup/migrationsPg.ts index a3dc6499..1f88baa8 100644 --- a/server/setup/migrationsPg.ts +++ b/server/setup/migrationsPg.ts @@ -2,14 +2,16 @@ import { migrate } from "drizzle-orm/node-postgres/migrator"; import { db } from "../db/pg"; import semver from "semver"; import { versionMigrations } from "../db/pg"; -import { __DIRNAME, APP_PATH, APP_VERSION } from "@server/lib/consts"; +import { __DIRNAME, APP_VERSION } from "@server/lib/consts"; import path from "path"; +import m1 from "./scriptsSqlite/1.6.0"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA // Define the migration list with versions and their corresponding functions const migrations = [ + { version: "1.6.0", run: m1 } // Add new migrations here as they are created ] as { version: string; diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index 36b87de2..1e279bae 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -21,6 +21,7 @@ import m17 from "./scriptsSqlite/1.1.0"; import m18 from "./scriptsSqlite/1.2.0"; import m19 from "./scriptsSqlite/1.3.0"; import m20 from "./scriptsSqlite/1.5.0"; +import m21 from "./scriptsSqlite/1.6.0"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -42,6 +43,7 @@ const migrations = [ { version: "1.2.0", run: m18 }, { version: "1.3.0", run: m19 }, { version: "1.5.0", run: m20 }, + { version: "1.6.0", run: m21 } // Add new migrations here as they are created ] as const; diff --git a/server/setup/scriptsPg/1.6.0.ts b/server/setup/scriptsPg/1.6.0.ts new file mode 100644 index 00000000..375f8388 --- /dev/null +++ b/server/setup/scriptsPg/1.6.0.ts @@ -0,0 +1,46 @@ +import { configFilePath1, configFilePath2 } from "@server/lib/consts"; +import fs from "fs"; +import yaml from "js-yaml"; + +const version = "1.6.0"; + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + try { + // Determine which config file exists + const filePaths = [configFilePath1, configFilePath2]; + let filePath = ""; + for (const path of filePaths) { + if (fs.existsSync(path)) { + filePath = path; + break; + } + } + + if (!filePath) { + throw new Error( + `No config file found (expected config.yml or config.yaml).` + ); + } + + // Read and parse the YAML file + let rawConfig: any; + const fileContents = fs.readFileSync(filePath, "utf8"); + rawConfig = yaml.load(fileContents); + + if (rawConfig.server?.trust_proxy) { + rawConfig.server.trust_proxy = 1; + } + + // Write the updated YAML back to the file + const updatedYaml = yaml.dump(rawConfig); + fs.writeFileSync(filePath, updatedYaml, "utf8"); + + console.log(`Set trust_proxy to 1 in config file`); + } catch (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 new file mode 100644 index 00000000..375f8388 --- /dev/null +++ b/server/setup/scriptsSqlite/1.6.0.ts @@ -0,0 +1,46 @@ +import { configFilePath1, configFilePath2 } from "@server/lib/consts"; +import fs from "fs"; +import yaml from "js-yaml"; + +const version = "1.6.0"; + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + try { + // Determine which config file exists + const filePaths = [configFilePath1, configFilePath2]; + let filePath = ""; + for (const path of filePaths) { + if (fs.existsSync(path)) { + filePath = path; + break; + } + } + + if (!filePath) { + throw new Error( + `No config file found (expected config.yml or config.yaml).` + ); + } + + // Read and parse the YAML file + let rawConfig: any; + const fileContents = fs.readFileSync(filePath, "utf8"); + rawConfig = yaml.load(fileContents); + + if (rawConfig.server?.trust_proxy) { + rawConfig.server.trust_proxy = 1; + } + + // Write the updated YAML back to the file + const updatedYaml = yaml.dump(rawConfig); + fs.writeFileSync(filePath, updatedYaml, "utf8"); + + console.log(`Set trust_proxy to 1 in config file`); + } catch (e) { + console.log(`Unable to migrate config file. Error: ${e}`); + } + + console.log(`${version} migration complete`); +} From 1bf2e23f5d53f91ed634028aa5b8200384188a5b Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 19 Jun 2025 15:41:49 -0400 Subject: [PATCH 6/7] make username lowercase --- server/routers/auth/login.ts | 4 ++-- server/routers/auth/requestPasswordReset.ts | 4 ++-- server/routers/auth/resetPassword.ts | 4 ++-- server/routers/auth/signup.ts | 4 ++-- server/routers/idp/validateOidcCallback.ts | 6 ++++-- server/routers/resource/authWithWhitelist.ts | 4 ++-- server/routers/user/createOrgUser.ts | 3 ++- server/routers/user/inviteUser.ts | 4 ++-- 8 files changed, 18 insertions(+), 15 deletions(-) diff --git a/server/routers/auth/login.ts b/server/routers/auth/login.ts index 5558a9c7..f5f7ff77 100644 --- a/server/routers/auth/login.ts +++ b/server/routers/auth/login.ts @@ -23,8 +23,8 @@ export const loginBodySchema = z .object({ email: z .string() - .email() - .transform((v) => v.toLowerCase()), + .toLowerCase() + .email(), password: z.string(), code: z.string().optional() }) diff --git a/server/routers/auth/requestPasswordReset.ts b/server/routers/auth/requestPasswordReset.ts index 4127533f..62951ab1 100644 --- a/server/routers/auth/requestPasswordReset.ts +++ b/server/routers/auth/requestPasswordReset.ts @@ -20,8 +20,8 @@ export const requestPasswordResetBody = z .object({ email: z .string() - .email() - .transform((v) => v.toLowerCase()) + .toLowerCase() + .email(), }) .strict(); diff --git a/server/routers/auth/resetPassword.ts b/server/routers/auth/resetPassword.ts index d99b8718..8ae62eb0 100644 --- a/server/routers/auth/resetPassword.ts +++ b/server/routers/auth/resetPassword.ts @@ -21,8 +21,8 @@ export const resetPasswordBody = z .object({ email: z .string() - .email() - .transform((v) => v.toLowerCase()), + .toLowerCase() + .email(), token: z.string(), // reset secret code newPassword: passwordSchema, code: z.string().optional() // 2fa code diff --git a/server/routers/auth/signup.ts b/server/routers/auth/signup.ts index d2a1e730..0c7e926e 100644 --- a/server/routers/auth/signup.ts +++ b/server/routers/auth/signup.ts @@ -26,8 +26,8 @@ import { UserType } from "@server/types/UserTypes"; export const signupBodySchema = z.object({ email: z .string() - .email() - .transform((v) => v.toLowerCase()), + .toLowerCase() + .email(), password: passwordSchema, inviteToken: z.string().optional(), inviteId: z.string().optional() diff --git a/server/routers/idp/validateOidcCallback.ts b/server/routers/idp/validateOidcCallback.ts index 0066693f..eaf9a2e6 100644 --- a/server/routers/idp/validateOidcCallback.ts +++ b/server/routers/idp/validateOidcCallback.ts @@ -172,10 +172,10 @@ export async function validateOidcCallback( const claims = arctic.decodeIdToken(idToken); logger.debug("ID token claims", { claims }); - const userIdentifier = jmespath.search( + let userIdentifier = jmespath.search( claims, existingIdp.idpOidcConfig.identifierPath - ); + ) as string | null; if (!userIdentifier) { return next( @@ -186,6 +186,8 @@ export async function validateOidcCallback( ); } + userIdentifier = userIdentifier.toLowerCase(); + logger.debug("User identifier", { userIdentifier }); let email = null; diff --git a/server/routers/resource/authWithWhitelist.ts b/server/routers/resource/authWithWhitelist.ts index ba0d36d3..07662f7f 100644 --- a/server/routers/resource/authWithWhitelist.ts +++ b/server/routers/resource/authWithWhitelist.ts @@ -22,8 +22,8 @@ const authWithWhitelistBodySchema = z .object({ email: z .string() - .email() - .transform((v) => v.toLowerCase()), + .toLowerCase() + .email(), otp: z.string().optional() }) .strict(); diff --git a/server/routers/user/createOrgUser.ts b/server/routers/user/createOrgUser.ts index f6fcb619..264ea3d9 100644 --- a/server/routers/user/createOrgUser.ts +++ b/server/routers/user/createOrgUser.ts @@ -21,6 +21,7 @@ const bodySchema = z .object({ email: z .string() + .toLowerCase() .optional() .refine((data) => { if (data) { @@ -28,7 +29,7 @@ const bodySchema = z } return true; }), - username: z.string().nonempty(), + username: z.string().nonempty().toLowerCase(), name: z.string().optional(), type: z.enum(["internal", "oidc"]).optional(), idpId: z.number().optional(), diff --git a/server/routers/user/inviteUser.ts b/server/routers/user/inviteUser.ts index 6b47338a..5b2e8d1e 100644 --- a/server/routers/user/inviteUser.ts +++ b/server/routers/user/inviteUser.ts @@ -30,8 +30,8 @@ const inviteUserBodySchema = z .object({ email: z .string() - .email() - .transform((v) => v.toLowerCase()), + .toLowerCase() + .email(), roleId: z.number(), validHours: z.number().gt(0).lte(168), sendEmail: z.boolean().optional(), From f300838f8e37a2ce518b86bf2be2aec6c2102eb2 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 19 Jun 2025 15:58:05 -0400 Subject: [PATCH 7/7] add migration for 1.6.0 --- server/routers/idp/validateOidcCallback.ts | 4 ++++ server/setup/scriptsPg/1.6.0.ts | 11 ++++++++++ server/setup/scriptsSqlite/1.6.0.ts | 24 ++++++++++++++++++++-- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/server/routers/idp/validateOidcCallback.ts b/server/routers/idp/validateOidcCallback.ts index eaf9a2e6..3d28db24 100644 --- a/server/routers/idp/validateOidcCallback.ts +++ b/server/routers/idp/validateOidcCallback.ts @@ -211,6 +211,10 @@ export async function validateOidcCallback( logger.debug("User email", { email }); logger.debug("User name", { name }); + if (email) { + email = email.toLowerCase(); + } + const [existingUser] = await db .select() .from(users) diff --git a/server/setup/scriptsPg/1.6.0.ts b/server/setup/scriptsPg/1.6.0.ts index 375f8388..62beb0e0 100644 --- a/server/setup/scriptsPg/1.6.0.ts +++ b/server/setup/scriptsPg/1.6.0.ts @@ -1,4 +1,6 @@ +import { db } from "@server/db/pg/driver"; import { configFilePath1, configFilePath2 } from "@server/lib/consts"; +import { sql } from "drizzle-orm"; import fs from "fs"; import yaml from "js-yaml"; @@ -7,6 +9,15 @@ const version = "1.6.0"; export default async function migration() { console.log(`Running setup script ${version}...`); + try { + db.execute(sql`UPDATE 'user' SET email = LOWER(email);`); + db.execute(sql`UPDATE 'user' SET username = LOWER(username);`); + console.log(`Migrated database schema`); + } catch (e) { + console.log("Unable to make all usernames and emails lowercase"); + console.log(e); + } + try { // Determine which config file exists const filePaths = [configFilePath1, configFilePath2]; diff --git a/server/setup/scriptsSqlite/1.6.0.ts b/server/setup/scriptsSqlite/1.6.0.ts index 375f8388..b27fedb5 100644 --- a/server/setup/scriptsSqlite/1.6.0.ts +++ b/server/setup/scriptsSqlite/1.6.0.ts @@ -1,12 +1,32 @@ -import { configFilePath1, configFilePath2 } from "@server/lib/consts"; +import { APP_PATH, configFilePath1, configFilePath2 } from "@server/lib/consts"; +import Database from "better-sqlite3"; import fs from "fs"; import yaml from "js-yaml"; +import path from "path"; const version = "1.6.0"; export default async function migration() { console.log(`Running setup script ${version}...`); + const location = path.join(APP_PATH, "db", "db.sqlite"); + const db = new Database(location); + + try { + db.pragma("foreign_keys = OFF"); + db.transaction(() => { + db.exec(` + UPDATE 'user' SET email = LOWER(email); + UPDATE 'user' SET username = LOWER(username); + `); + })(); // <-- executes the transaction immediately + db.pragma("foreign_keys = ON"); + console.log(`Migrated database schema`); + } catch (e) { + console.log("Unable to make all usernames and emails lowercase"); + console.log(e); + } + try { // Determine which config file exists const filePaths = [configFilePath1, configFilePath2]; @@ -39,7 +59,7 @@ export default async function migration() { console.log(`Set trust_proxy to 1 in config file`); } catch (e) { - console.log(`Unable to migrate config file. Error: ${e}`); + console.log(`Unable to migrate config file. Please do it manually. Error: ${e}`); } console.log(`${version} migration complete`);