mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-08-05 14:50:11 +00:00
Compare commits
3
Commits
4cd9ad00d2
...
a5ad16ac27
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5ad16ac27 | ||
|
|
6a1a8357bf | ||
|
|
31cfd19b6b |
@@ -1959,6 +1959,7 @@ export default function App() {
|
|||||||
lockTimeoutMinutes,
|
lockTimeoutMinutes,
|
||||||
sessionTimeoutAction,
|
sessionTimeoutAction,
|
||||||
authorizedDevices: authorizedDevicesQuery.data || [],
|
authorizedDevices: authorizedDevicesQuery.data || [],
|
||||||
|
currentDeviceIdentifier: getCurrentDeviceIdentifier(),
|
||||||
authorizedDevicesLoading: authorizedDevicesQuery.isFetching,
|
authorizedDevicesLoading: authorizedDevicesQuery.isFetching,
|
||||||
authorizedDevicesError: authorizedDevicesQuery.isError && !authorizedDevicesQuery.data ? t('txt_load_devices_failed') : '',
|
authorizedDevicesError: authorizedDevicesQuery.isError && !authorizedDevicesQuery.data ? t('txt_load_devices_failed') : '',
|
||||||
domainRules: IS_DEMO_MODE ? demoDomainRules : domainRulesQuery.data || null,
|
domainRules: IS_DEMO_MODE ? demoDomainRules : domainRulesQuery.data || null,
|
||||||
@@ -2031,6 +2032,7 @@ export default function App() {
|
|||||||
onRevokeDeviceTrust: accountSecurityActions.openRevokeDeviceTrust,
|
onRevokeDeviceTrust: accountSecurityActions.openRevokeDeviceTrust,
|
||||||
onTrustDevicePermanently: accountSecurityActions.openTrustDevicePermanently,
|
onTrustDevicePermanently: accountSecurityActions.openTrustDevicePermanently,
|
||||||
onRemoveDevice: accountSecurityActions.openRemoveDevice,
|
onRemoveDevice: accountSecurityActions.openRemoveDevice,
|
||||||
|
onRemoveSelectedDevices: accountSecurityActions.openRemoveSelectedDevices,
|
||||||
onRevokeAllDeviceTrust: accountSecurityActions.openRevokeAllDeviceTrust,
|
onRevokeAllDeviceTrust: accountSecurityActions.openRevokeAllDeviceTrust,
|
||||||
onRemoveAllDevices: accountSecurityActions.openRemoveAllDevices,
|
onRemoveAllDevices: accountSecurityActions.openRemoveAllDevices,
|
||||||
onRefreshAdmin: adminActions.refreshAdmin,
|
onRefreshAdmin: adminActions.refreshAdmin,
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export interface AppMainRoutesProps {
|
|||||||
lockTimeoutMinutes: 0 | 1 | 5 | 15 | 30;
|
lockTimeoutMinutes: 0 | 1 | 5 | 15 | 30;
|
||||||
sessionTimeoutAction: 'lock' | 'logout';
|
sessionTimeoutAction: 'lock' | 'logout';
|
||||||
authorizedDevices: AuthorizedDevice[];
|
authorizedDevices: AuthorizedDevice[];
|
||||||
|
currentDeviceIdentifier: string;
|
||||||
authorizedDevicesLoading: boolean;
|
authorizedDevicesLoading: boolean;
|
||||||
authorizedDevicesError: string;
|
authorizedDevicesError: string;
|
||||||
domainRules: DomainRules | null;
|
domainRules: DomainRules | null;
|
||||||
@@ -130,6 +131,7 @@ export interface AppMainRoutesProps {
|
|||||||
onRevokeDeviceTrust: (device: AuthorizedDevice) => void;
|
onRevokeDeviceTrust: (device: AuthorizedDevice) => void;
|
||||||
onTrustDevicePermanently: (device: AuthorizedDevice) => void;
|
onTrustDevicePermanently: (device: AuthorizedDevice) => void;
|
||||||
onRemoveDevice: (device: AuthorizedDevice) => void;
|
onRemoveDevice: (device: AuthorizedDevice) => void;
|
||||||
|
onRemoveSelectedDevices: (devices: AuthorizedDevice[]) => void;
|
||||||
onRevokeAllDeviceTrust: () => void;
|
onRevokeAllDeviceTrust: () => void;
|
||||||
onRemoveAllDevices: () => void;
|
onRemoveAllDevices: () => void;
|
||||||
onCreateInvite: (hours: number) => Promise<void>;
|
onCreateInvite: (hours: number) => Promise<void>;
|
||||||
@@ -347,6 +349,7 @@ export default function AppMainRoutes(props: AppMainRoutesProps) {
|
|||||||
<Suspense fallback={<RouteContentFallback />}>
|
<Suspense fallback={<RouteContentFallback />}>
|
||||||
<SecurityDevicesPage
|
<SecurityDevicesPage
|
||||||
devices={props.authorizedDevices}
|
devices={props.authorizedDevices}
|
||||||
|
currentDeviceIdentifier={props.currentDeviceIdentifier}
|
||||||
loading={props.authorizedDevicesLoading}
|
loading={props.authorizedDevicesLoading}
|
||||||
error={props.authorizedDevicesError}
|
error={props.authorizedDevicesError}
|
||||||
pendingAuthRequests={props.pendingAuthRequests}
|
pendingAuthRequests={props.pendingAuthRequests}
|
||||||
@@ -359,6 +362,7 @@ export default function AppMainRoutes(props: AppMainRoutesProps) {
|
|||||||
onRevokeTrust={props.onRevokeDeviceTrust}
|
onRevokeTrust={props.onRevokeDeviceTrust}
|
||||||
onTrustPermanently={props.onTrustDevicePermanently}
|
onTrustPermanently={props.onTrustDevicePermanently}
|
||||||
onRemoveDevice={props.onRemoveDevice}
|
onRemoveDevice={props.onRemoveDevice}
|
||||||
|
onRemoveSelectedDevices={props.onRemoveSelectedDevices}
|
||||||
onRevokeAll={props.onRevokeAllDeviceTrust}
|
onRevokeAll={props.onRevokeAllDeviceTrust}
|
||||||
onRemoveAll={props.onRemoveAllDevices}
|
onRemoveAll={props.onRemoveAllDevices}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState } from 'preact/hooks';
|
import { useState } from 'preact/hooks';
|
||||||
import { Clock3, Pencil, RefreshCw, ShieldCheck, ShieldOff, Trash2 } from 'lucide-preact';
|
import { CheckSquare, Clock3, Pencil, RefreshCw, ShieldCheck, ShieldOff, Trash2 } from 'lucide-preact';
|
||||||
import ConfirmDialog from '@/components/ConfirmDialog';
|
import ConfirmDialog from '@/components/ConfirmDialog';
|
||||||
import LoadingState from '@/components/LoadingState';
|
import LoadingState from '@/components/LoadingState';
|
||||||
import PendingAuthRequestsPanel from '@/components/PendingAuthRequestsPanel';
|
import PendingAuthRequestsPanel from '@/components/PendingAuthRequestsPanel';
|
||||||
@@ -8,6 +8,7 @@ import { t } from '@/lib/i18n';
|
|||||||
|
|
||||||
interface SecurityDevicesPageProps {
|
interface SecurityDevicesPageProps {
|
||||||
devices: AuthorizedDevice[];
|
devices: AuthorizedDevice[];
|
||||||
|
currentDeviceIdentifier: string;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: string;
|
error: string;
|
||||||
pendingAuthRequests: AuthRequest[];
|
pendingAuthRequests: AuthRequest[];
|
||||||
@@ -20,6 +21,7 @@ interface SecurityDevicesPageProps {
|
|||||||
onRevokeTrust: (device: AuthorizedDevice) => void;
|
onRevokeTrust: (device: AuthorizedDevice) => void;
|
||||||
onTrustPermanently: (device: AuthorizedDevice) => void;
|
onTrustPermanently: (device: AuthorizedDevice) => void;
|
||||||
onRemoveDevice: (device: AuthorizedDevice) => void;
|
onRemoveDevice: (device: AuthorizedDevice) => void;
|
||||||
|
onRemoveSelectedDevices: (devices: AuthorizedDevice[]) => void;
|
||||||
onRevokeAll: () => void;
|
onRevokeAll: () => void;
|
||||||
onRemoveAll: () => void;
|
onRemoveAll: () => void;
|
||||||
}
|
}
|
||||||
@@ -62,6 +64,14 @@ export default function SecurityDevicesPage(props: SecurityDevicesPageProps) {
|
|||||||
const [editingDevice, setEditingDevice] = useState<AuthorizedDevice | null>(null);
|
const [editingDevice, setEditingDevice] = useState<AuthorizedDevice | null>(null);
|
||||||
const [deviceNote, setDeviceNote] = useState('');
|
const [deviceNote, setDeviceNote] = useState('');
|
||||||
const [savingNote, setSavingNote] = useState(false);
|
const [savingNote, setSavingNote] = useState(false);
|
||||||
|
const [selectedDeviceIds, setSelectedDeviceIds] = useState<string[]>([]);
|
||||||
|
const currentDeviceIdentifier = props.currentDeviceIdentifier;
|
||||||
|
const selectableDevices = props.devices.filter((device) => (
|
||||||
|
device.identifier !== currentDeviceIdentifier
|
||||||
|
));
|
||||||
|
const selectedDeviceIdSet = new Set(selectedDeviceIds);
|
||||||
|
const selectedDevices = selectableDevices.filter((device) => selectedDeviceIdSet.has(device.identifier));
|
||||||
|
const allSelectableSelected = selectableDevices.length > 0 && selectedDevices.length === selectableDevices.length;
|
||||||
|
|
||||||
async function handleSaveDeviceNote(): Promise<void> {
|
async function handleSaveDeviceNote(): Promise<void> {
|
||||||
if (!editingDevice || savingNote) return;
|
if (!editingDevice || savingNote) return;
|
||||||
@@ -75,6 +85,19 @@ export default function SecurityDevicesPage(props: SecurityDevicesPageProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleSelectAllDevices(): void {
|
||||||
|
setSelectedDeviceIds(allSelectableSelected ? [] : selectableDevices.map((device) => device.identifier));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSelectedDevice(device: AuthorizedDevice): void {
|
||||||
|
if (device.identifier === currentDeviceIdentifier) return;
|
||||||
|
setSelectedDeviceIds((current) => (
|
||||||
|
current.includes(device.identifier)
|
||||||
|
? current.filter((id) => id !== device.identifier)
|
||||||
|
: [...current, device.identifier]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="stack">
|
<div className="stack">
|
||||||
@@ -101,6 +124,27 @@ export default function SecurityDevicesPage(props: SecurityDevicesPageProps) {
|
|||||||
<RefreshCw size={14} className="btn-icon" />
|
<RefreshCw size={14} className="btn-icon" />
|
||||||
{t('txt_refresh')}
|
{t('txt_refresh')}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-secondary small"
|
||||||
|
disabled={props.loading || selectableDevices.length === 0}
|
||||||
|
onClick={toggleSelectAllDevices}
|
||||||
|
>
|
||||||
|
<CheckSquare size={14} className="btn-icon" />
|
||||||
|
{allSelectableSelected ? t('txt_clear_selection') : t('txt_select_all')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-danger small"
|
||||||
|
disabled={selectedDevices.length === 0}
|
||||||
|
onClick={() => {
|
||||||
|
props.onRemoveSelectedDevices(selectedDevices);
|
||||||
|
setSelectedDeviceIds([]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash2 size={14} className="btn-icon" />
|
||||||
|
{t('txt_remove_selected_devices', { count: selectedDevices.length })}
|
||||||
|
</button>
|
||||||
<button type="button" className="btn btn-danger small" onClick={props.onRevokeAll}>
|
<button type="button" className="btn btn-danger small" onClick={props.onRevokeAll}>
|
||||||
<ShieldOff size={14} className="btn-icon" />
|
<ShieldOff size={14} className="btn-icon" />
|
||||||
{t('txt_revoke_all_trusted')}
|
{t('txt_revoke_all_trusted')}
|
||||||
@@ -122,6 +166,7 @@ export default function SecurityDevicesPage(props: SecurityDevicesPageProps) {
|
|||||||
)}
|
)}
|
||||||
<table className="table authorized-devices-table">
|
<table className="table authorized-devices-table">
|
||||||
<colgroup>
|
<colgroup>
|
||||||
|
<col className="authorized-devices-col-select" />
|
||||||
<col className="authorized-devices-col-device" />
|
<col className="authorized-devices-col-device" />
|
||||||
<col className="authorized-devices-col-type" />
|
<col className="authorized-devices-col-type" />
|
||||||
<col className="authorized-devices-col-status" />
|
<col className="authorized-devices-col-status" />
|
||||||
@@ -132,6 +177,7 @@ export default function SecurityDevicesPage(props: SecurityDevicesPageProps) {
|
|||||||
</colgroup>
|
</colgroup>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>{t('txt_select')}</th>
|
||||||
<th>{t('txt_device')}</th>
|
<th>{t('txt_device')}</th>
|
||||||
<th>{t('txt_type')}</th>
|
<th>{t('txt_type')}</th>
|
||||||
<th>{t('txt_status')}</th>
|
<th>{t('txt_status')}</th>
|
||||||
@@ -144,6 +190,16 @@ export default function SecurityDevicesPage(props: SecurityDevicesPageProps) {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{props.devices.map((device) => (
|
{props.devices.map((device) => (
|
||||||
<tr key={device.identifier}>
|
<tr key={device.identifier}>
|
||||||
|
<td data-label={t('txt_select')}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="authorized-device-checkbox"
|
||||||
|
checked={selectedDeviceIdSet.has(device.identifier)}
|
||||||
|
disabled={device.identifier === currentDeviceIdentifier}
|
||||||
|
aria-label={t('txt_select_device_name', { name: device.name || t('txt_unknown_device') })}
|
||||||
|
onChange={() => toggleSelectedDevice(device)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
<td data-label={t('txt_device')}>
|
<td data-label={t('txt_device')}>
|
||||||
<div>{device.name || t('txt_unknown_device')}</div>
|
<div>{device.name || t('txt_unknown_device')}</div>
|
||||||
{!!device.deviceNote && !!device.systemName && device.systemName !== device.name && (
|
{!!device.deviceNote && !!device.systemName && device.systemName !== device.name && (
|
||||||
@@ -216,14 +272,14 @@ export default function SecurityDevicesPage(props: SecurityDevicesPageProps) {
|
|||||||
))}
|
))}
|
||||||
{props.loading && props.devices.length === 0 && (
|
{props.loading && props.devices.length === 0 && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={7}>
|
<td colSpan={8}>
|
||||||
<LoadingState lines={5} compact />
|
<LoadingState lines={5} compact />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
{!props.loading && props.devices.length === 0 && (
|
{!props.loading && props.devices.length === 0 && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={7}>
|
<td colSpan={8}>
|
||||||
<div className="empty empty-comfortable">{t('txt_no_devices_found')}</div>
|
<div className="empty empty-comfortable">{t('txt_no_devices_found')}</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
changeMasterPassword,
|
changeMasterPassword,
|
||||||
deleteAllAuthorizedDevices,
|
deleteAllAuthorizedDevices,
|
||||||
deleteAuthorizedDevice,
|
deleteAuthorizedDevice,
|
||||||
|
deleteAuthorizedDevices,
|
||||||
deriveLoginHash,
|
deriveLoginHash,
|
||||||
deleteAccountPasskey as deleteAccountPasskeyApi,
|
deleteAccountPasskey as deleteAccountPasskeyApi,
|
||||||
enableAccountPasskeyDirectUnlock as enableAccountPasskeyDirectUnlockApi,
|
enableAccountPasskeyDirectUnlock as enableAccountPasskeyDirectUnlockApi,
|
||||||
@@ -389,6 +390,38 @@ export default function useAccountSecurityActions(options: UseAccountSecurityAct
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
openRemoveSelectedDevices(devices: AuthorizedDevice[]) {
|
||||||
|
const selectedDevices = devices.filter((device) => String(device.identifier || '').trim());
|
||||||
|
if (selectedDevices.length === 0) {
|
||||||
|
onNotify('warning', t('txt_no_devices_selected'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const includesCurrentDevice = selectedDevices.some((device) => device.identifier === getCurrentDeviceIdentifier());
|
||||||
|
onSetConfirm({
|
||||||
|
title: t('txt_remove_selected_devices', { count: selectedDevices.length }),
|
||||||
|
message: includesCurrentDevice
|
||||||
|
? t('txt_remove_selected_devices_and_sign_out_current', { count: selectedDevices.length })
|
||||||
|
: t('txt_remove_selected_devices_confirm', { count: selectedDevices.length }),
|
||||||
|
danger: true,
|
||||||
|
onConfirm: () => {
|
||||||
|
onSetConfirm(null);
|
||||||
|
void (async () => {
|
||||||
|
try {
|
||||||
|
await deleteAuthorizedDevices(authedFetch, selectedDevices);
|
||||||
|
onNotify('success', t('txt_selected_devices_removed', { count: selectedDevices.length }));
|
||||||
|
if (includesCurrentDevice) {
|
||||||
|
onLogoutNow();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await refetchAuthorizedDevices();
|
||||||
|
} catch (error) {
|
||||||
|
onNotify('error', error instanceof Error ? error.message : t('txt_remove_selected_devices_failed'));
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
openRevokeAllDeviceTrust() {
|
openRevokeAllDeviceTrust() {
|
||||||
onSetConfirm({
|
onSetConfirm({
|
||||||
title: t('txt_revoke_all_trusted_devices'),
|
title: t('txt_revoke_all_trusted_devices'),
|
||||||
|
|||||||
@@ -136,6 +136,19 @@ function withPrfExtension(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function withoutCreatePrfExtension(options: PublicKeyCredentialCreationOptions): PublicKeyCredentialCreationOptions {
|
||||||
|
const extensions = { ...(((options as any).extensions || {}) as Record<string, unknown>) };
|
||||||
|
delete extensions.prf;
|
||||||
|
if (!Object.keys(extensions).length) {
|
||||||
|
const { extensions: _extensions, ...rest } = options as any;
|
||||||
|
return rest as PublicKeyCredentialCreationOptions;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...options,
|
||||||
|
extensions: extensions as any,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function readPrfFirstResult(credential: PublicKeyCredential): ArrayBuffer | undefined {
|
function readPrfFirstResult(credential: PublicKeyCredential): ArrayBuffer | undefined {
|
||||||
const result = (credential.getClientExtensionResults() as any).prf?.results?.first;
|
const result = (credential.getClientExtensionResults() as any).prf?.results?.first;
|
||||||
return result instanceof ArrayBuffer ? result : undefined;
|
return result instanceof ArrayBuffer ? result : undefined;
|
||||||
@@ -288,6 +301,7 @@ export async function createAccountPasskeyCredential(
|
|||||||
throw new Error(t('txt_passkey_browser_not_supported'));
|
throw new Error(t('txt_passkey_browser_not_supported'));
|
||||||
}
|
}
|
||||||
const nativeOptions = cloneCreationOptions(response.options);
|
const nativeOptions = cloneCreationOptions(response.options);
|
||||||
|
const noPrfOptions = withoutCreatePrfExtension(nativeOptions);
|
||||||
const createWithOptions = async (options: PublicKeyCredentialCreationOptions): Promise<PublicKeyCredential> => {
|
const createWithOptions = async (options: PublicKeyCredentialCreationOptions): Promise<PublicKeyCredential> => {
|
||||||
const credential = await navigator.credentials.create({ publicKey: options });
|
const credential = await navigator.credentials.create({ publicKey: options });
|
||||||
if (!(credential instanceof PublicKeyCredential)) {
|
if (!(credential instanceof PublicKeyCredential)) {
|
||||||
@@ -298,9 +312,9 @@ export async function createAccountPasskeyCredential(
|
|||||||
let credential: PublicKeyCredential;
|
let credential: PublicKeyCredential;
|
||||||
if (requestPrf && await canRequestPrfExtension()) {
|
if (requestPrf && await canRequestPrfExtension()) {
|
||||||
const prfOptions: PublicKeyCredentialCreationOptions = {
|
const prfOptions: PublicKeyCredentialCreationOptions = {
|
||||||
...nativeOptions,
|
...noPrfOptions,
|
||||||
extensions: {
|
extensions: {
|
||||||
...((nativeOptions as any).extensions || {}),
|
...((noPrfOptions as any).extensions || {}),
|
||||||
prf: {},
|
prf: {},
|
||||||
} as any,
|
} as any,
|
||||||
};
|
};
|
||||||
@@ -308,10 +322,10 @@ export async function createAccountPasskeyCredential(
|
|||||||
credential = await createWithOptions(prfOptions);
|
credential = await createWithOptions(prfOptions);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!shouldRetryCreateWithoutPrf(error)) throw error;
|
if (!shouldRetryCreateWithoutPrf(error)) throw error;
|
||||||
credential = await createWithOptions(nativeOptions);
|
credential = await createWithOptions(noPrfOptions);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
credential = await createWithOptions(nativeOptions);
|
credential = await createWithOptions(noPrfOptions);
|
||||||
}
|
}
|
||||||
if (!(credential instanceof PublicKeyCredential)) {
|
if (!(credential instanceof PublicKeyCredential)) {
|
||||||
throw new Error(t('txt_no_passkey_created'));
|
throw new Error(t('txt_no_passkey_created'));
|
||||||
|
|||||||
@@ -885,6 +885,20 @@ export async function deleteAuthorizedDevice(
|
|||||||
if (!resp.ok) throw new Error(t('txt_remove_device_failed'));
|
if (!resp.ok) throw new Error(t('txt_remove_device_failed'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteAuthorizedDevices(
|
||||||
|
authedFetch: AuthedFetch,
|
||||||
|
devices: Array<Pick<AuthorizedDevice, 'identifier' | 'hasStoredDevice'>>
|
||||||
|
): Promise<void> {
|
||||||
|
const uniqueDevices = Array.from(
|
||||||
|
new Map(devices.map((device) => [String(device.identifier || '').trim(), device])).values()
|
||||||
|
).filter((device) => String(device.identifier || '').trim());
|
||||||
|
await Promise.all(uniqueDevices.map((device) => (
|
||||||
|
device.hasStoredDevice === false
|
||||||
|
? revokeAuthorizedDeviceTrust(authedFetch, device.identifier)
|
||||||
|
: deleteAuthorizedDevice(authedFetch, device.identifier)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateAuthorizedDeviceName(
|
export async function updateAuthorizedDeviceName(
|
||||||
authedFetch: AuthedFetch,
|
authedFetch: AuthedFetch,
|
||||||
deviceIdentifier: string,
|
deviceIdentifier: string,
|
||||||
|
|||||||
@@ -838,6 +838,11 @@ const en: Record<string, string> = {
|
|||||||
"txt_remove_all_devices": "Remove all devices",
|
"txt_remove_all_devices": "Remove all devices",
|
||||||
"txt_remove_all_devices_and_clear_all_2fa_trust": "Remove all devices and clear all 2FA trust?",
|
"txt_remove_all_devices_and_clear_all_2fa_trust": "Remove all devices and clear all 2FA trust?",
|
||||||
"txt_remove_all_devices_and_sign_out_all_sessions": "Remove all devices, clear all trust, and sign out every device?",
|
"txt_remove_all_devices_and_sign_out_all_sessions": "Remove all devices, clear all trust, and sign out every device?",
|
||||||
|
"txt_remove_selected_devices": "Remove selected ({count})",
|
||||||
|
"txt_remove_selected_devices_confirm": "Remove {count} selected devices, clear their trust, and sign them out?",
|
||||||
|
"txt_remove_selected_devices_and_sign_out_current": "Remove {count} selected devices, clear their trust, and sign out this device too?",
|
||||||
|
"txt_selected_devices_removed": "Selected devices removed",
|
||||||
|
"txt_remove_selected_devices_failed": "Failed to remove selected devices",
|
||||||
"txt_remove_device_name_and_clear_its_2fa_trust": "Remove device \"{name}\" and clear its 2FA trust?",
|
"txt_remove_device_name_and_clear_its_2fa_trust": "Remove device \"{name}\" and clear its 2FA trust?",
|
||||||
"txt_remove_device_and_sign_out_name": "Remove device \"{name}\", clear its trust, and sign it out?",
|
"txt_remove_device_and_sign_out_name": "Remove device \"{name}\", clear its trust, and sign it out?",
|
||||||
"txt_reveal": "Reveal",
|
"txt_reveal": "Reveal",
|
||||||
@@ -879,6 +884,9 @@ const en: Record<string, string> = {
|
|||||||
"txt_security_code": "Security Code",
|
"txt_security_code": "Security Code",
|
||||||
"txt_security_code_cvv": "Security Code (CVV)",
|
"txt_security_code_cvv": "Security Code (CVV)",
|
||||||
"txt_select_all": "Select All",
|
"txt_select_all": "Select All",
|
||||||
|
"txt_clear_selection": "Clear selection",
|
||||||
|
"txt_select_device_name": "Select {name}",
|
||||||
|
"txt_no_devices_selected": "No devices selected",
|
||||||
"txt_select": "Select",
|
"txt_select": "Select",
|
||||||
"txt_select_duplicate_items": "Select Duplicates",
|
"txt_select_duplicate_items": "Select Duplicates",
|
||||||
"txt_select_an_item": "Select an item",
|
"txt_select_an_item": "Select an item",
|
||||||
|
|||||||
@@ -838,6 +838,11 @@ const es: Record<string, string> = {
|
|||||||
"txt_remove_all_devices": "Quitar todos los dispositivos",
|
"txt_remove_all_devices": "Quitar todos los dispositivos",
|
||||||
"txt_remove_all_devices_and_clear_all_2fa_trust": "¿Quitar todos los dispositivos y limpiar toda la confianza 2FA?",
|
"txt_remove_all_devices_and_clear_all_2fa_trust": "¿Quitar todos los dispositivos y limpiar toda la confianza 2FA?",
|
||||||
"txt_remove_all_devices_and_sign_out_all_sessions": "¿Quitar todos los dispositivos, limpiar toda la confianza y cerrar sesión en todos los dispositivos?",
|
"txt_remove_all_devices_and_sign_out_all_sessions": "¿Quitar todos los dispositivos, limpiar toda la confianza y cerrar sesión en todos los dispositivos?",
|
||||||
|
"txt_remove_selected_devices": "Quitar seleccionados ({count})",
|
||||||
|
"txt_remove_selected_devices_confirm": "¿Quitar {count} dispositivos seleccionados, limpiar su confianza y cerrar sesión?",
|
||||||
|
"txt_remove_selected_devices_and_sign_out_current": "¿Quitar {count} dispositivos seleccionados, limpiar su confianza y cerrar también esta sesión?",
|
||||||
|
"txt_selected_devices_removed": "Dispositivos seleccionados quitados",
|
||||||
|
"txt_remove_selected_devices_failed": "Error al quitar los dispositivos seleccionados",
|
||||||
"txt_remove_device_name_and_clear_its_2fa_trust": "¿Quitar dispositivo \"{name}\" y limpiar su confianza 2FA?",
|
"txt_remove_device_name_and_clear_its_2fa_trust": "¿Quitar dispositivo \"{name}\" y limpiar su confianza 2FA?",
|
||||||
"txt_remove_device_and_sign_out_name": "¿Quitar dispositivo \"{name}\", limpiar su confianza y cerrar sesión?",
|
"txt_remove_device_and_sign_out_name": "¿Quitar dispositivo \"{name}\", limpiar su confianza y cerrar sesión?",
|
||||||
"txt_reveal": "Mostrar",
|
"txt_reveal": "Mostrar",
|
||||||
@@ -879,6 +884,9 @@ const es: Record<string, string> = {
|
|||||||
"txt_security_code": "Código de seguridad",
|
"txt_security_code": "Código de seguridad",
|
||||||
"txt_security_code_cvv": "Código de seguridad (CVV)",
|
"txt_security_code_cvv": "Código de seguridad (CVV)",
|
||||||
"txt_select_all": "Seleccionar todo",
|
"txt_select_all": "Seleccionar todo",
|
||||||
|
"txt_clear_selection": "Borrar selección",
|
||||||
|
"txt_select_device_name": "Seleccionar {name}",
|
||||||
|
"txt_no_devices_selected": "No hay dispositivos seleccionados",
|
||||||
"txt_select": "Seleccionar",
|
"txt_select": "Seleccionar",
|
||||||
"txt_select_duplicate_items": "Seleccionar duplicados",
|
"txt_select_duplicate_items": "Seleccionar duplicados",
|
||||||
"txt_select_an_item": "Seleccione un elemento",
|
"txt_select_an_item": "Seleccione un elemento",
|
||||||
|
|||||||
@@ -838,6 +838,11 @@ const ru: Record<string, string> = {
|
|||||||
"txt_remove_all_devices": "Удалить все устройства",
|
"txt_remove_all_devices": "Удалить все устройства",
|
||||||
"txt_remove_all_devices_and_clear_all_2fa_trust": "Удалить все устройства и очистить все доверие 2FA?",
|
"txt_remove_all_devices_and_clear_all_2fa_trust": "Удалить все устройства и очистить все доверие 2FA?",
|
||||||
"txt_remove_all_devices_and_sign_out_all_sessions": "Удалить все устройства, отменить все доверительные отношения и выйти из системы на каждом устройстве?",
|
"txt_remove_all_devices_and_sign_out_all_sessions": "Удалить все устройства, отменить все доверительные отношения и выйти из системы на каждом устройстве?",
|
||||||
|
"txt_remove_selected_devices": "Удалить выбранные ({count})",
|
||||||
|
"txt_remove_selected_devices_confirm": "Удалить {count} выбранных устройств, очистить их доверие и выйти из системы на них?",
|
||||||
|
"txt_remove_selected_devices_and_sign_out_current": "Удалить {count} выбранных устройств, очистить их доверие и также выйти из системы на этом устройстве?",
|
||||||
|
"txt_selected_devices_removed": "Выбранные устройства удалены",
|
||||||
|
"txt_remove_selected_devices_failed": "Не удалось удалить выбранные устройства",
|
||||||
"txt_remove_device_name_and_clear_its_2fa_trust": "Удалить устройство «{name}» и очистить его доверие 2FA?",
|
"txt_remove_device_name_and_clear_its_2fa_trust": "Удалить устройство «{name}» и очистить его доверие 2FA?",
|
||||||
"txt_remove_device_and_sign_out_name": "Удалить устройство «{name}», очистить его доверие и выйти из системы?",
|
"txt_remove_device_and_sign_out_name": "Удалить устройство «{name}», очистить его доверие и выйти из системы?",
|
||||||
"txt_reveal": "Раскрыть",
|
"txt_reveal": "Раскрыть",
|
||||||
@@ -879,6 +884,9 @@ const ru: Record<string, string> = {
|
|||||||
"txt_security_code": "Код безопасности",
|
"txt_security_code": "Код безопасности",
|
||||||
"txt_security_code_cvv": "Код безопасности (CVV)",
|
"txt_security_code_cvv": "Код безопасности (CVV)",
|
||||||
"txt_select_all": "Выбрать все",
|
"txt_select_all": "Выбрать все",
|
||||||
|
"txt_clear_selection": "Очистить выбор",
|
||||||
|
"txt_select_device_name": "Выбрать {name}",
|
||||||
|
"txt_no_devices_selected": "Устройства не выбраны",
|
||||||
"txt_select": "Выбрать",
|
"txt_select": "Выбрать",
|
||||||
"txt_select_duplicate_items": "Выберите дубликаты",
|
"txt_select_duplicate_items": "Выберите дубликаты",
|
||||||
"txt_select_an_item": "Выберите элемент",
|
"txt_select_an_item": "Выберите элемент",
|
||||||
|
|||||||
@@ -838,6 +838,11 @@ const zhCN: Record<string, string> = {
|
|||||||
"txt_remove_all_devices": "移除所有设备",
|
"txt_remove_all_devices": "移除所有设备",
|
||||||
"txt_remove_all_devices_and_clear_all_2fa_trust": "确认移除所有设备并清除全部 2FA 信任吗?",
|
"txt_remove_all_devices_and_clear_all_2fa_trust": "确认移除所有设备并清除全部 2FA 信任吗?",
|
||||||
"txt_remove_all_devices_and_sign_out_all_sessions": "确认移除所有设备、清除全部信任,并让所有设备重新登录吗?",
|
"txt_remove_all_devices_and_sign_out_all_sessions": "确认移除所有设备、清除全部信任,并让所有设备重新登录吗?",
|
||||||
|
"txt_remove_selected_devices": "移除已选({count})",
|
||||||
|
"txt_remove_selected_devices_confirm": "确认移除选中的 {count} 台设备、清除其信任,并让它们重新登录吗?",
|
||||||
|
"txt_remove_selected_devices_and_sign_out_current": "确认移除选中的 {count} 台设备、清除其信任,并同时退出本设备吗?",
|
||||||
|
"txt_selected_devices_removed": "已移除选中设备",
|
||||||
|
"txt_remove_selected_devices_failed": "移除选中设备失败",
|
||||||
"txt_remove_device_name_and_clear_its_2fa_trust": "确认移除设备“{name}”并清除其 2FA 信任吗?",
|
"txt_remove_device_name_and_clear_its_2fa_trust": "确认移除设备“{name}”并清除其 2FA 信任吗?",
|
||||||
"txt_remove_device_and_sign_out_name": "确认移除设备“{name}”,清除其信任,并让它重新登录吗?",
|
"txt_remove_device_and_sign_out_name": "确认移除设备“{name}”,清除其信任,并让它重新登录吗?",
|
||||||
"txt_reveal": "显示",
|
"txt_reveal": "显示",
|
||||||
@@ -879,6 +884,9 @@ const zhCN: Record<string, string> = {
|
|||||||
"txt_security_code": "安全码",
|
"txt_security_code": "安全码",
|
||||||
"txt_security_code_cvv": "安全码 (CVV)",
|
"txt_security_code_cvv": "安全码 (CVV)",
|
||||||
"txt_select_all": "全选",
|
"txt_select_all": "全选",
|
||||||
|
"txt_clear_selection": "取消选择",
|
||||||
|
"txt_select_device_name": "选择 {name}",
|
||||||
|
"txt_no_devices_selected": "未选择设备",
|
||||||
"txt_select": "请选择",
|
"txt_select": "请选择",
|
||||||
"txt_select_duplicate_items": "选择重复项",
|
"txt_select_duplicate_items": "选择重复项",
|
||||||
"txt_select_an_item": "请选择一个项目",
|
"txt_select_an_item": "请选择一个项目",
|
||||||
|
|||||||
@@ -838,6 +838,11 @@ const zhTW: Record<string, string> = {
|
|||||||
"txt_remove_all_devices": "移除所有設備",
|
"txt_remove_all_devices": "移除所有設備",
|
||||||
"txt_remove_all_devices_and_clear_all_2fa_trust": "確認移除所有設備並清除全部 2FA 信任嗎?",
|
"txt_remove_all_devices_and_clear_all_2fa_trust": "確認移除所有設備並清除全部 2FA 信任嗎?",
|
||||||
"txt_remove_all_devices_and_sign_out_all_sessions": "確認移除所有設備、清除全部信任,並讓所有設備重新登錄嗎?",
|
"txt_remove_all_devices_and_sign_out_all_sessions": "確認移除所有設備、清除全部信任,並讓所有設備重新登錄嗎?",
|
||||||
|
"txt_remove_selected_devices": "移除已選({count})",
|
||||||
|
"txt_remove_selected_devices_confirm": "確認移除選中的 {count} 臺設備、清除其信任,並讓它們重新登錄嗎?",
|
||||||
|
"txt_remove_selected_devices_and_sign_out_current": "確認移除選中的 {count} 臺設備、清除其信任,並同時退出本設備嗎?",
|
||||||
|
"txt_selected_devices_removed": "已移除選中設備",
|
||||||
|
"txt_remove_selected_devices_failed": "移除選中設備失敗",
|
||||||
"txt_remove_device_name_and_clear_its_2fa_trust": "確認移除設備“{name}”並清除其 2FA 信任嗎?",
|
"txt_remove_device_name_and_clear_its_2fa_trust": "確認移除設備“{name}”並清除其 2FA 信任嗎?",
|
||||||
"txt_remove_device_and_sign_out_name": "確認移除設備“{name}”,清除其信任,並讓它重新登錄嗎?",
|
"txt_remove_device_and_sign_out_name": "確認移除設備“{name}”,清除其信任,並讓它重新登錄嗎?",
|
||||||
"txt_reveal": "顯示",
|
"txt_reveal": "顯示",
|
||||||
@@ -879,6 +884,9 @@ const zhTW: Record<string, string> = {
|
|||||||
"txt_security_code": "安全碼",
|
"txt_security_code": "安全碼",
|
||||||
"txt_security_code_cvv": "安全碼 (CVV)",
|
"txt_security_code_cvv": "安全碼 (CVV)",
|
||||||
"txt_select_all": "全選",
|
"txt_select_all": "全選",
|
||||||
|
"txt_clear_selection": "取消選擇",
|
||||||
|
"txt_select_device_name": "選擇 {name}",
|
||||||
|
"txt_no_devices_selected": "未選擇設備",
|
||||||
"txt_select": "請選擇",
|
"txt_select": "請選擇",
|
||||||
"txt_select_duplicate_items": "選擇重複項",
|
"txt_select_duplicate_items": "選擇重複項",
|
||||||
"txt_select_an_item": "請選擇一個項目",
|
"txt_select_an_item": "請選擇一個項目",
|
||||||
|
|||||||
@@ -1520,8 +1520,12 @@
|
|||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.authorized-devices-col-select {
|
||||||
|
width: 4%;
|
||||||
|
}
|
||||||
|
|
||||||
.authorized-devices-col-device {
|
.authorized-devices-col-device {
|
||||||
width: 28%;
|
width: 26%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.authorized-devices-col-type {
|
.authorized-devices-col-type {
|
||||||
@@ -1544,6 +1548,12 @@
|
|||||||
width: 26%;
|
width: 26%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.authorized-device-checkbox {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
accent-color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
.authorized-devices-table td:first-child {
|
.authorized-devices-table td:first-child {
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user