diff --git a/package-lock.json b/package-lock.json index baec0b2b..7eb58d2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,7 +61,6 @@ "http-errors": "2.0.0", "i": "^0.3.7", "input-otp": "1.4.2", - "ioredis": "^5.6.1", "jmespath": "^0.16.0", "js-yaml": "4.1.0", "jsonwebtoken": "^9.0.2", @@ -77,7 +76,6 @@ "oslo": "1.2.1", "pg": "^8.16.2", "qrcode.react": "4.2.0", - "rate-limit-redis": "^4.2.1", "react": "19.1.0", "react-dom": "19.1.0", "react-easy-sort": "^1.6.0", @@ -2010,12 +2008,6 @@ "url": "https://opencollective.com/libvips" } }, - "node_modules/@ioredis/commands": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", - "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==", - "license": "MIT" - }, "node_modules/@isaacs/balanced-match": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", @@ -2058,6 +2050,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.4" @@ -6309,6 +6302,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=18" @@ -6455,15 +6449,6 @@ "node": ">=6" } }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/cmdk": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", @@ -6947,15 +6932,6 @@ "node": ">=0.4.0" } }, - "node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -8835,6 +8811,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, "license": "ISC" }, "node_modules/graphemer": { @@ -9122,30 +9099,6 @@ "tslib": "^2.8.0" } }, - "node_modules/ioredis": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz", - "integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==", - "license": "MIT", - "dependencies": { - "@ioredis/commands": "^1.1.1", - "cluster-key-slot": "^1.1.0", - "debug": "^4.3.4", - "denque": "^2.1.0", - "lodash.defaults": "^4.2.0", - "lodash.isarguments": "^3.1.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0", - "standard-as-callback": "^2.1.0" - }, - "engines": { - "node": ">=12.22.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ioredis" - } - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -9606,6 +9559,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, "license": "ISC", "engines": { "node": ">=16" @@ -10112,24 +10066,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", - "license": "MIT" - }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", "license": "MIT" }, - "node_modules/lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", - "license": "MIT" - }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", @@ -10481,6 +10423,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, "license": "MIT", "dependencies": { "minipass": "^7.1.2" @@ -10493,6 +10436,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, "license": "MIT", "bin": { "mkdirp": "dist/cjs/src/bin.js" @@ -14470,18 +14414,6 @@ "node": ">= 0.6" } }, - "node_modules/rate-limit-redis": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/rate-limit-redis/-/rate-limit-redis-4.2.1.tgz", - "integrity": "sha512-JsUsVmRVI6G/XrlYtfGV1NMCbGS/CVYayHkxD5Ism5FaL8qpFHCXbFkUeIi5WJ/onJOKWCgtB/xtCLa6qSXb4g==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "peerDependencies": { - "express-rate-limit": ">= 6" - } - }, "node_modules/raw-body": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", @@ -14828,27 +14760,6 @@ "node": ">=0.8.8" } }, - "node_modules/redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", - "license": "MIT", - "dependencies": { - "redis-errors": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -15628,12 +15539,6 @@ "node": "*" } }, - "node_modules/standard-as-callback": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", - "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", - "license": "MIT" - }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -16021,6 +15926,7 @@ "version": "7.4.3", "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, "license": "ISC", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -16667,6 +16573,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^3.1.1" @@ -16972,6 +16879,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=18" diff --git a/server/db/pg/schema.ts b/server/db/pg/schema.ts index c8a768a8..093f7c31 100644 --- a/server/db/pg/schema.ts +++ b/server/db/pg/schema.ts @@ -22,7 +22,7 @@ export const domains = pgTable("domains", { export const orgs = pgTable("orgs", { orgId: varchar("orgId").primaryKey(), name: varchar("name").notNull(), - passwordResetTokenExpiryHours: integer("passwordResetTokenExpiryHours").notNull().default(1) + passwordResetTokenExpiryHours: integer("passwordResetTokenExpiryHours").notNull().default(1), subnet: varchar("subnet") }); diff --git a/server/db/sqlite/schema.ts b/server/db/sqlite/schema.ts index c62fb53f..e27e6b42 100644 --- a/server/db/sqlite/schema.ts +++ b/server/db/sqlite/schema.ts @@ -16,7 +16,7 @@ export const domains = sqliteTable("domains", { export const orgs = sqliteTable("orgs", { orgId: text("orgId").primaryKey(), name: text("name").notNull(), - passwordResetTokenExpiryHours: integer("passwordResetTokenExpiryHours").notNull().default(1) + passwordResetTokenExpiryHours: integer("passwordResetTokenExpiryHours").notNull().default(1), subnet: text("subnet") }); diff --git a/server/setup/scriptsPg/1.7.0.ts b/server/setup/scriptsPg/1.7.0.ts index c0245871..02dcd8c8 100644 --- a/server/setup/scriptsPg/1.7.0.ts +++ b/server/setup/scriptsPg/1.7.0.ts @@ -1,4 +1,3 @@ -import { db } from "@server/db/pg"; import { db } from "@server/db/pg/driver"; import { sql } from "drizzle-orm"; const version = "1.7.0"; diff --git a/src/app/[orgId]/page.tsx b/src/app/[orgId]/page.tsx index da24eca2..9a1dda94 100644 --- a/src/app/[orgId]/page.tsx +++ b/src/app/[orgId]/page.tsx @@ -12,6 +12,7 @@ import { Layout } from "@app/components/Layout"; import { ListUserOrgsResponse } from "@server/routers/org"; import { pullEnv } from "@app/lib/pullEnv"; import EnvProvider from "@app/providers/EnvProvider"; +import { orgLangingNavItems } from "@app/app/navigation"; type OrgPageProps = { params: Promise<{ orgId: string }>; @@ -38,48 +39,9 @@ export default async function OrgPage(props: OrgPageProps) { overview = res.data.data; } catch (e) {} - // If user is admin or owner, show the admin landing card and potentially redirect + // If user is admin or owner, redirect to settings if (overview?.isAdmin || overview?.isOwner) { - let orgs: ListUserOrgsResponse["orgs"] = []; - try { - const getOrgs = cache(async () => - internal.get>( - `/user/${user.userId}/orgs`, - await authCookieHeader() - ) - ); - const res = await getOrgs(); - if (res && res.data.data.orgs) { - orgs = res.data.data.orgs; - } - } catch (e) {} - - return ( - - - - {overview && ( -
- -
- )} -
-
-
- ); + redirect(`/${orgId}/settings`); } // For non-admin users, show the member resources portal @@ -101,21 +63,8 @@ export default async function OrgPage(props: OrgPageProps) { {overview && ( -
- +
+
)} diff --git a/src/app/[orgId]/settings/access/users/create/page.tsx b/src/app/[orgId]/settings/access/users/create/page.tsx index 4d723e6f..dd84db14 100644 --- a/src/app/[orgId]/settings/access/users/create/page.tsx +++ b/src/app/[orgId]/settings/access/users/create/page.tsx @@ -676,9 +676,7 @@ export default function Page() { render={({ field }) => ( - {t( - "emailOptional" - )} + {t("nameOptional")} ( - "nameOptional" - )} + {t("nameOptional")} >( - "/auth/reset-password", - { - email, - token, - newPassword: password, - code - } as ResetPasswordBody - ) - .catch((e) => { - setError(formatAxiosError(e, t('errorOccurred'))); - console.error(t('passwordErrorReset'), e); - setIsSubmitting(false); - }); + try { + const res = await api + .post>( + "/auth/reset-password", + { + email, + token, + newPassword: password, + code + } as ResetPasswordBody + ); - console.log(res); + if (res) { + setError(null); - if (res) { - setError(null); - - if (res.data.data?.codeRequested) { - setState("mfa"); - setIsSubmitting(false); - mfaForm.reset(); - return; - } - - setSuccessMessage(quickstart ? t('accountSetupSuccess') : t('passwordResetSuccess')); - - // Auto-login after successful password reset - try { - const loginRes = await api.post("/auth/login", { - email: form.getValues("email"), - password: form.getValues("password") - }); - - if (loginRes.data.data?.codeRequested) { - if (redirect) { - router.push(`/auth/login?redirect=${redirect}`); - } else { - router.push("/auth/login"); - } + if (res.data.data?.codeRequested) { + setState("mfa"); + setIsSubmitting(false); + mfaForm.reset(); return; } -if (loginRes.data.data?.emailVerificationRequired) { - try { - await api.post("/auth/verify-email/request"); - } catch (verificationError) { - console.error("Failed to send verification code:", verificationError); + setSuccessMessage(quickstart ? t('accountSetupSuccess') : t('passwordResetSuccess')); + + // Auto-login after successful password reset + try { + const loginRes = await api.post("/auth/login", { + email: form.getValues("email"), + password: form.getValues("password") + }); + + if (loginRes.data.data?.codeRequested) { + if (redirect) { + router.push(`/auth/login?redirect=${redirect}`); + } else { + router.push("/auth/login"); + } + return; } - if (redirect) { - router.push(`/auth/verify-email?redirect=${redirect}`); - } else { - router.push("/auth/verify-email"); + if (loginRes.data.data?.emailVerificationRequired) { + try { + await api.post("/auth/verify-email/request"); + } catch (verificationError) { + console.error("Failed to send verification code:", verificationError); + } + + if (redirect) { + router.push(`/auth/verify-email?redirect=${redirect}`); + } else { + router.push("/auth/verify-email"); + } + return; } - return; - } else { + + // Login successful, redirect + setTimeout(() => { + if (redirect) { + const safe = cleanRedirect(redirect); + router.push(safe); + } else { + router.push("/"); + } + setIsSubmitting(false); + }, 1500); + } catch (loginError) { + // Auto-login failed, but password reset was successful + console.error("Auto-login failed:", loginError); setTimeout(() => { if (redirect) { const safe = cleanRedirect(redirect); @@ -225,34 +233,14 @@ if (loginRes.data.data?.emailVerificationRequired) { } else { router.push("/auth/login"); } - }, 0); + setIsSubmitting(false); + }, 1500); } - } - - // Login successful, redirect - setTimeout(() => { - if (redirect) { - const safe = cleanRedirect(redirect); - router.push(safe); - } else { - router.push("/"); - } - setIsSubmitting(false); - }, 1500); - - } catch (loginError) { - // Auto-login failed, but password reset was successful - console.error("Auto-login failed:", loginError); - setTimeout(() => { - if (redirect) { - const safe = cleanRedirect(redirect); - router.push(safe); - } else { - router.push("/login"); - } - setIsSubmitting(false); - }, 1500); } + } catch (e) { + setError(formatAxiosError(e, t('errorOccurred'))); + console.error(t('passwordErrorReset'), e); + setIsSubmitting(false); } } diff --git a/src/components/SidebarNav.tsx b/src/components/SidebarNav.tsx index 7e8ad336..6775223f 100644 --- a/src/components/SidebarNav.tsx +++ b/src/components/SidebarNav.tsx @@ -20,6 +20,8 @@ export type SidebarNavItem = { title: string; icon?: React.ReactNode; showProfessional?: boolean; + autoExpand?: boolean; + children?: SidebarNavItem[]; }; export type SidebarNavSection = { @@ -76,44 +78,60 @@ export function SidebarNav({ : t(item.title); const itemContent = ( - + { + if (isDisabled) { + e.preventDefault(); + } else if (onItemClick) { + onItemClick(); + } + }} + tabIndex={isDisabled ? -1 : undefined} + aria-disabled={isDisabled} + > + {item.icon && ( + + {item.icon} + + )} + {!isCollapsed && ( + <> + {t(item.title)} + {item.showProfessional && !isUnlocked() && ( + + {t("licenseBadge")} + + )} + + )} + + {!isCollapsed && item.children && (item.autoExpand || isActive) && ( +
+ {item.children.map((child) => { + const childHref = hydrateHref(child.href); + const isChildActive = pathname.startsWith(childHref); + return renderNavItem( + child, + childHref, + isChildActive, + isDisabled + ); + })} +
)} - onClick={(e) => { - if (isDisabled) { - e.preventDefault(); - } else if (onItemClick) { - onItemClick(); - } - }} - tabIndex={isDisabled ? -1 : undefined} - aria-disabled={isDisabled} - > - {item.icon && ( - - {item.icon} - - )} - {!isCollapsed && ( - <> - {t(item.title)} - {item.showProfessional && !isUnlocked() && ( - - {t("licenseBadge")} - - )} - - )} - + ); if (isCollapsed) {