mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-30 15:15:24 +00:00
feat: add auth extension points and one ssh connect pipeline
This commit is contained in:
1 parent
d3c209af13
commit
d774df8b41
159 files changed
+43243
-7451
No files matched your search
@@ -0,0 +1,108 @@
|
||||
import { useCallback, useEffect, useState, type ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { ShieldOff } from "lucide-react";
|
||||
import { Button } from "@/components/button";
|
||||
import {
|
||||
getUserSecondFactors,
|
||||
resetUserSecondFactors,
|
||||
type UserSecondFactorSummary,
|
||||
} from "@/api/auth-methods-api";
|
||||
|
||||
/**
|
||||
* Every second factor a user enrolled in, including ones whose plugin is off
|
||||
* (which is what locks a user out), with a reset for all of them.
|
||||
*/
|
||||
export function AdminSecondFactorsSection({
|
||||
userId,
|
||||
username,
|
||||
heading,
|
||||
requestConfirm,
|
||||
onReset,
|
||||
}: {
|
||||
userId: string;
|
||||
username: string;
|
||||
heading: ReactNode;
|
||||
requestConfirm: (message: string, onConfirm: () => void) => void;
|
||||
onReset: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [factors, setFactors] = useState<UserSecondFactorSummary[]>([]);
|
||||
const [working, setWorking] = useState(false);
|
||||
|
||||
const load = useCallback(() => {
|
||||
getUserSecondFactors(userId)
|
||||
.then(setFactors)
|
||||
.catch(() => setFactors([]));
|
||||
}, [userId]);
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
}, [load]);
|
||||
|
||||
async function reset() {
|
||||
setWorking(true);
|
||||
try {
|
||||
await resetUserSecondFactors(userId);
|
||||
toast.success(t("admin.secondFactorsResetSuccess"));
|
||||
onReset();
|
||||
load();
|
||||
} catch {
|
||||
toast.error(t("admin.secondFactorsResetFailed"));
|
||||
} finally {
|
||||
setWorking(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
{heading}
|
||||
{factors.length === 0 ? (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t("admin.secondFactorsNone")}
|
||||
</span>
|
||||
) : (
|
||||
<div className="flex flex-col gap-1">
|
||||
{factors.map((factor) => (
|
||||
<div
|
||||
key={`${factor.pluginId}:${factor.factorId}`}
|
||||
className="flex items-center justify-between text-xs"
|
||||
>
|
||||
<span>
|
||||
{factor.labelKey
|
||||
? t(factor.labelKey, { defaultValue: factor.factorId })
|
||||
: factor.factorId}
|
||||
</span>
|
||||
{!factor.available && (
|
||||
<span className="text-[10px] text-destructive">
|
||||
{t("admin.secondFactorUnavailable", {
|
||||
plugin: factor.pluginId,
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{factors.length > 0 && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-7 w-fit text-[10px] border-destructive/40 text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
disabled={working}
|
||||
onClick={() =>
|
||||
requestConfirm(
|
||||
t("admin.resetSecondFactorsConfirm", { username }),
|
||||
() => {
|
||||
void reset();
|
||||
},
|
||||
)
|
||||
}
|
||||
>
|
||||
<ShieldOff className="size-3" />
|
||||
{t("admin.resetSecondFactors")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user