feat: server admin password reset via tenant portal
All checks were successful
CI / build (push) Successful in 2m23s
CI / docker (push) Successful in 1m8s

- POST /api/tenant/server/admin-password — resets server's built-in
  admin password via M2M API call to the tenant's server
- Settings page: "Server Admin Password" card
- ServerApiClient.resetServerAdminPassword() calls server's password
  reset endpoint with M2M token

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-11 09:46:30 +02:00
parent 4121bd64b2
commit d5eead888d
5 changed files with 86 additions and 1 deletions

View File

@@ -94,6 +94,12 @@ export function useRemoveTeamMember() {
});
}
export function useResetServerAdminPassword() {
return useMutation<void, Error, string>({
mutationFn: (password) => api.post('/tenant/server/admin-password', { password }),
});
}
export function useChangeOwnPassword() {
return useMutation<void, Error, string>({
mutationFn: (password) => api.post('/tenant/password', { password }),

View File

@@ -9,7 +9,7 @@ import {
Spinner,
useToast,
} from '@cameleer/design-system';
import { useTenantSettings, useChangeOwnPassword } from '../../api/tenant-hooks';
import { useTenantSettings, useChangeOwnPassword, useResetServerAdminPassword } from '../../api/tenant-hooks';
import { tierColor } from '../../utils/tier';
import styles from '../../styles/platform.module.css';
@@ -26,10 +26,12 @@ function statusColor(status: string): 'success' | 'error' | 'warning' | 'auto' {
export function SettingsPage() {
const { data, isLoading, isError } = useTenantSettings();
const changePassword = useChangeOwnPassword();
const resetServerAdmin = useResetServerAdminPassword();
const { toast } = useToast();
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [serverAdminPw, setServerAdminPw] = useState('');
async function handleChangePassword(e: React.FormEvent) {
e.preventDefault();
@@ -140,6 +142,46 @@ export function SettingsPage() {
</div>
</form>
</Card>
<Card title="Server Admin Password">
<p className={styles.description} style={{ marginTop: 0 }}>
Reset the built-in admin password for your server dashboard (local login at <code>/login?local</code>).
</p>
<form
onSubmit={async (e) => {
e.preventDefault();
if (serverAdminPw.length < 8) {
toast({ title: 'Password must be at least 8 characters', variant: 'error' });
return;
}
try {
await resetServerAdmin.mutateAsync(serverAdminPw);
toast({ title: 'Server admin password reset successfully', variant: 'success' });
setServerAdminPw('');
} catch (err) {
toast({ title: 'Failed to reset server admin password', description: String(err), variant: 'error' });
}
}}
style={{ display: 'flex', flexDirection: 'column', gap: 16, marginTop: 12 }}
>
<FormField label="New admin password" htmlFor="server-admin-pw">
<Input
id="server-admin-pw"
type="password"
value={serverAdminPw}
onChange={(e) => setServerAdminPw(e.target.value)}
placeholder="Enter new admin password"
required
minLength={8}
/>
</FormField>
<div>
<Button type="submit" variant="primary" loading={resetServerAdmin.isPending}>
Reset Admin Password
</Button>
</div>
</form>
</Card>
</div>
);
}