Files
TREK/client/src/components/Admin/useAdminUserActions.ts
T
Maurice b53130ca0d feat(admin): remove the SSO link of an account whose provider changed its subject
A sign-in with another subject of the same provider is refused, so an
account whose provider re-issued the subject (a migrated or rebuilt IdP, a
changed sub format) was locked out: editing the email, as the wiki advised,
never touched the stored link. The edit dialog now offers "Remove SSO link"
for other accounts while SSO is set up (DELETE /api/admin/users/:id/sso,
audit admin.user_sso_unlink). The next SSO sign-in links again under the
rules of a first one, so it still needs a verified, not self-set email.
The log line names the remedy, and the wiki tells the two refusals apart.

Fixes #3065
2026-11-12 15:39:21 +01:00

72 lines
2.4 KiB
TypeScript

import { adminApi } from '../../api/client';
import type { TranslationFn } from '../../types';
interface UserActionsHost {
toast: { success: (message: string) => void; error: (message: string) => void };
setRotatingJwt: (value: boolean) => void;
setShowRotateJwtModal: (value: boolean) => void;
logout: () => void;
navigate: (to: string, options?: { state?: unknown }) => void;
}
/**
* The irreversible user actions behind the admin dialogs and their phone sheets:
* rotating the JWT secret, which signs everyone out including the admin, removing a
* user's passkeys, switching off a user's two-factor login and removing a user's SSO
* link. Each shell keeps its own confirm step and busy state around them.
*/
export function useAdminUserActions(admin: UserActionsHost, t: TranslationFn) {
const { toast, setRotatingJwt, setShowRotateJwtModal, logout, navigate } = admin;
const rotateJwt = async () => {
setRotatingJwt(true);
try {
await adminApi.rotateJwtSecret();
setShowRotateJwtModal(false);
logout();
navigate('/login', { state: { noRedirect: true } });
} catch {
toast.error(t('common.error'));
setRotatingJwt(false);
}
};
/** Removes the user's passkeys and toasts how many went; true when it worked. */
const resetPasskeys = async (user: { id: number }): Promise<boolean> => {
try {
const r = await adminApi.resetUserPasskeys(user.id);
toast.success(t('admin.passkey.resetDone', { count: r.deleted ?? 0 }));
return true;
} catch {
toast.error(t('common.error'));
return false;
}
};
/** Switches off the user's 2FA (their sessions end with it) and toasts; true when it worked. */
const resetMfa = async (user: { id: number }): Promise<boolean> => {
try {
await adminApi.resetUserMfa(user.id);
toast.success(t('admin.mfa.resetDone'));
return true;
} catch {
toast.error(t('common.error'));
return false;
}
};
/** Forgets the user's SSO identity so the next SSO sign-in links again (#3065); true when it worked. */
const unlinkSso = async (user: { id: number }): Promise<boolean> => {
try {
await adminApi.unlinkUserSso(user.id);
toast.success(t('admin.sso.unlinkDone'));
return true;
} catch {
toast.error(t('common.error'));
return false;
}
};
return { rotateJwt, resetPasskeys, resetMfa, unlinkSso };
}