From 97da2e27360225e48d23079f1acf6ea15ca6d321 Mon Sep 17 00:00:00 2001 From: Andre Padez Date: Mon, 23 Feb 2026 00:57:34 +0000 Subject: [PATCH] fixed members login and permissions issues --- src/apps/officer-web/App.tsx | 7 +- .../VerifyScreen/VerifyScreen.tsx | 10 +- .../VerifyScreen/useVerifyScreen.ts | 45 ++++++-- .../Dashboard/Layout/Header/UserMenu.tsx | 40 ++++--- .../Settings/ProfileSettings/UserData.tsx | 10 ++ .../UserSettings/InviteUserDialog.tsx | 86 ++++++++++++++ .../Settings/UserSettings/UsersTable.tsx | 95 +++++++++++++++ .../Dashboard/Settings/UserSettings/index.tsx | 46 ++++++++ .../Screens/Dashboard/Settings/index.tsx | 1 + .../Dashboard/Workspaces/WorkspaceScreen.tsx | 2 +- src/databases/officer_db/src/schema/users.ts | 2 +- src/server.tsx | 3 +- src/servers/_middlewares/rate-limiter.ts | 24 ++-- src/servers/_middlewares/user-middleware.ts | 2 +- src/servers/api/auth/change-password.ts | 5 +- src/servers/api/auth/forgot-password.ts | 5 +- src/servers/api/auth/passkey-router.ts | 4 +- src/servers/api/auth/signin.ts | 4 +- src/servers/api/auth/verify-token.ts | 6 +- src/servers/api/auth/verify.ts | 26 ++++- src/servers/api/pi/pi-bridge.ts | 16 ++- src/servers/api/pi/websocket.ts | 30 ++++- .../api/terminal/Dockerfile.terminal-sidecar | 4 +- src/servers/api/terminal/entrypoint.sh | 11 +- src/servers/api/terminal/websocket.ts | 24 ++-- src/servers/api/users/update-user.ts | 4 +- src/servers/api/users/users-router.ts | 109 ++++++++++++++++++ src/servers/hono.ts | 4 +- src/workspaces/emailer/emails/UserInvite.tsx | 11 +- src/workspaces/hooks/src/useAuth/types.ts | 2 +- src/workspaces/hooks/src/useAuth/useAuth.ts | 1 + .../src/apps/Chat/ChatPanelWrapper.tsx | 16 ++- .../Chat/EmbeddableChat/EmbeddableChat.tsx | 1 + .../Chat/EmbeddableChat/useEmbeddableChat.ts | 3 +- .../src/apps/Terminal/TerminalWrapper.tsx | 9 +- .../src/apps/Workspaces/WorkspacePreview.tsx | 6 +- .../components/Workspace/WorkspaceContext.ts | 1 + .../components/Workspace/WorkspaceView.tsx | 4 +- .../src/components/Workspace/index.ts | 2 +- .../src/components/Workspace/types.ts | 3 + .../officerdev/src/hooks/usePiChat.ts | 1 + src/workspaces/officerdev/src/index.ts | 2 +- 42 files changed, 574 insertions(+), 113 deletions(-) create mode 100644 src/apps/officer-web/Screens/Dashboard/Settings/UserSettings/InviteUserDialog.tsx create mode 100644 src/apps/officer-web/Screens/Dashboard/Settings/UserSettings/UsersTable.tsx create mode 100644 src/apps/officer-web/Screens/Dashboard/Settings/UserSettings/index.tsx create mode 100644 src/servers/api/users/users-router.ts diff --git a/src/apps/officer-web/App.tsx b/src/apps/officer-web/App.tsx index 991113ea..4364702c 100644 --- a/src/apps/officer-web/App.tsx +++ b/src/apps/officer-web/App.tsx @@ -6,7 +6,7 @@ import { useServerSettings } from 'state/useServerSettings'; import { useInitialData } from '@/state/useInitialData'; export function App() { - const { isLoading, isAuthenticated } = useAuth(); + const { isLoading, isAuthenticated, user } = useAuth(); const { onboardingComplete, plugins, isLoading: isServerSettingsLoading } = useServerSettings(); useInitialData(); @@ -37,8 +37,9 @@ export function App() { } /> } /> - } /> - } /> + : } /> + : } /> + : } /> } /> } /> } /> diff --git a/src/apps/officer-web/Screens/Authentication/VerifyScreen/VerifyScreen.tsx b/src/apps/officer-web/Screens/Authentication/VerifyScreen/VerifyScreen.tsx index f6ea81ce..b226a60f 100644 --- a/src/apps/officer-web/Screens/Authentication/VerifyScreen/VerifyScreen.tsx +++ b/src/apps/officer-web/Screens/Authentication/VerifyScreen/VerifyScreen.tsx @@ -6,7 +6,7 @@ import { cn } from '@/lib/utils'; import { useVerifyScreen } from './useVerifyScreen'; export const VerifyScreen = () => { - const { formRef, isValid, tokenStatus, isSubmitting, handleSubmit } = useVerifyScreen(); + const { formRef, isValid, tokenStatus, flow, isSubmitting, handleSubmit } = useVerifyScreen(); const loading = tokenStatus === 'loading'; const invalid = tokenStatus === 'invalid'; const hideform = loading || invalid; @@ -30,8 +30,12 @@ export const VerifyScreen = () => {
-
Create Your Account
-
Set up your administrator profile
+
+ {flow === 'bootstrap' ? 'Create Your Account' : 'Accept Invitation'} +
+
+ {flow === 'bootstrap' ? 'Set up your administrator profile' : 'Set up your profile to get started'} +
diff --git a/src/apps/officer-web/Screens/Authentication/VerifyScreen/useVerifyScreen.ts b/src/apps/officer-web/Screens/Authentication/VerifyScreen/useVerifyScreen.ts index 8a9df7aa..2ab312e0 100644 --- a/src/apps/officer-web/Screens/Authentication/VerifyScreen/useVerifyScreen.ts +++ b/src/apps/officer-web/Screens/Authentication/VerifyScreen/useVerifyScreen.ts @@ -9,18 +9,20 @@ export const useVerifyScreen = () => { const isMounted = useMounted(); const navigate = useNavigate(); const client = useClient('/api/auth'); - const { state, formRef, update, isValid } = useForm({ email: '' }, validateForm); + const { state, formRef, update } = useForm({ email: '' }); const [verificationCode] = useState(() => new URL(window.location.href).searchParams.get('verificationCode') ?? ''); const [tokenStatus, setTokenStatus] = useState('loading'); + const [flow, setFlow] = useState<'bootstrap' | 'invite' | 'verify'>('bootstrap'); const [isSubmitting, setIsSubmitting] = useState(false); const verifyToken = async () => { if (!verificationCode) return; try { - const data = await client.post<{ ok: boolean; email: string }>('/verify-token', { verificationCode }); + const data = await client.post<{ ok: boolean; email: string; flow: 'bootstrap' | 'invite' | 'verify' }>('/verify-token', { verificationCode }); if (data.ok) { setTokenStatus('valid'); + setFlow(data.flow); requestAnimationFrame(() => update({ email: data.email })); } else { setTokenStatus('invalid'); @@ -45,15 +47,25 @@ export const useVerifyScreen = () => { setIsSubmitting(true); try { - await client.post('/bootstrap', { - token: verificationCode, - name: state.name, - username: state.username, - password: state.password, - confirmPassword: state.confirmPassword, - }); + if (flow === 'bootstrap') { + await client.post('/bootstrap', { + token: verificationCode, + name: state.name, + username: state.username, + password: state.password, + confirmPassword: state.confirmPassword, + }); + } else { + await client.post('/verify', { + verificationCode, + name: state.name, + username: state.username, + password: state.password, + confirmPassword: state.confirmPassword, + }); + } toast.success('Account created. Please sign in.'); - navigate('/auth/login'); + navigate('/'); } catch (ex) { const error = ex as { message?: string }; toast.error(error.message || 'Failed to create account. Please try again.'); @@ -62,7 +74,9 @@ export const useVerifyScreen = () => { } }; - return { formRef, state, isValid, tokenStatus, isSubmitting, handleSubmit }; + const isValid = flow === 'bootstrap' ? validateBootstrap(state) : validateInvite(state); + + return { formRef, state, isValid, tokenStatus, flow, isSubmitting, handleSubmit }; }; type VerifyFormState = { @@ -75,7 +89,14 @@ type VerifyFormState = { type TokenStatus = 'loading' | 'valid' | 'invalid'; -const validateForm = (state: Partial) => { +const validateBootstrap = (state: Partial) => { + const { name, username, password, confirmPassword } = state; + if (!name || !username || !password || !confirmPassword) return false; + if (password !== confirmPassword) return false; + return true; +}; + +const validateInvite = (state: Partial) => { const { name, username, password, confirmPassword } = state; if (!name || !username || !password || !confirmPassword) return false; if (password !== confirmPassword) return false; diff --git a/src/apps/officer-web/Screens/Dashboard/Layout/Header/UserMenu.tsx b/src/apps/officer-web/Screens/Dashboard/Layout/Header/UserMenu.tsx index c08e011d..9588006a 100644 --- a/src/apps/officer-web/Screens/Dashboard/Layout/Header/UserMenu.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Layout/Header/UserMenu.tsx @@ -1,7 +1,7 @@ import { Link } from 'react-router'; import * as Dropdown from '@/components/ui/dropdown-menu'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { User, LogOut, Settings, Package, Sun, Moon } from 'lucide-react'; +import { User, Users, LogOut, Settings, Package, Sun, Moon } from 'lucide-react'; import { useAuth } from 'hooks/useAuth'; import { useTranslation } from '@/lib/i18n'; import { useColorMode } from '@/components/ui/ThemeProvider'; @@ -16,6 +16,8 @@ export function UserMenu() { const { settings, saveSettings } = useSettings(); if (isLoading) return null; + const isAdmin = user?.role !== 'Member'; + const toggleColorMode = () => { const next = colorMode === 'dark' ? 'light' : 'dark'; setColorMode(next); @@ -41,18 +43,30 @@ export function UserMenu() { {t('header.userMenu.profile')} - - - - {t('header.userMenu.systemSettings')} - - - - - - {t('header.userMenu.resources')} - - + {isAdmin && ( + <> + + + + {t('header.userMenu.systemSettings')} + + + + + + {t('header.userMenu.resources')} + + + + )} + {user?.role === 'Super Admin' && ( + + + + Users + + + )} {colorMode === 'dark' ? ( diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/UserData.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/UserData.tsx index e5b1f3b7..5c101203 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/UserData.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/UserData.tsx @@ -102,6 +102,16 @@ export const UserData = () => { /> + +