mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
feat: add permanent trust functionality for devices with corresponding API and UI updates
This commit is contained in:
@@ -1517,6 +1517,7 @@ export default function App() {
|
||||
onSaveDomainRules: handleSaveDomainRules,
|
||||
onRenameAuthorizedDevice: accountSecurityActions.renameAuthorizedDevice,
|
||||
onRevokeDeviceTrust: accountSecurityActions.openRevokeDeviceTrust,
|
||||
onTrustDevicePermanently: accountSecurityActions.openTrustDevicePermanently,
|
||||
onRemoveDevice: accountSecurityActions.openRemoveDevice,
|
||||
onRevokeAllDeviceTrust: accountSecurityActions.openRevokeAllDeviceTrust,
|
||||
onRemoveAllDevices: accountSecurityActions.openRemoveAllDevices,
|
||||
|
||||
@@ -116,6 +116,7 @@ export interface AppMainRoutesProps {
|
||||
onSaveDomainRules: (customEquivalentDomains: CustomEquivalentDomain[], excludedGlobalEquivalentDomains: number[]) => Promise<void>;
|
||||
onRenameAuthorizedDevice: (device: AuthorizedDevice, name: string) => Promise<void>;
|
||||
onRevokeDeviceTrust: (device: AuthorizedDevice) => void;
|
||||
onTrustDevicePermanently: (device: AuthorizedDevice) => void;
|
||||
onRemoveDevice: (device: AuthorizedDevice) => void;
|
||||
onRevokeAllDeviceTrust: () => void;
|
||||
onRemoveAllDevices: () => void;
|
||||
@@ -322,6 +323,7 @@ export default function AppMainRoutes(props: AppMainRoutesProps) {
|
||||
onRefresh={() => void props.onRefreshAuthorizedDevices()}
|
||||
onRenameDevice={props.onRenameAuthorizedDevice}
|
||||
onRevokeTrust={props.onRevokeDeviceTrust}
|
||||
onTrustPermanently={props.onTrustDevicePermanently}
|
||||
onRemoveDevice={props.onRemoveDevice}
|
||||
onRevokeAll={props.onRevokeAllDeviceTrust}
|
||||
onRemoveAll={props.onRemoveAllDevices}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'preact/hooks';
|
||||
import { Clock3, Pencil, RefreshCw, ShieldOff, Trash2 } from 'lucide-preact';
|
||||
import { Clock3, Pencil, RefreshCw, ShieldCheck, ShieldOff, Trash2 } from 'lucide-preact';
|
||||
import ConfirmDialog from '@/components/ConfirmDialog';
|
||||
import LoadingState from '@/components/LoadingState';
|
||||
import type { AuthorizedDevice } from '@/lib/types';
|
||||
@@ -12,6 +12,7 @@ interface SecurityDevicesPageProps {
|
||||
onRefresh: () => void;
|
||||
onRenameDevice: (device: AuthorizedDevice, name: string) => Promise<void>;
|
||||
onRevokeTrust: (device: AuthorizedDevice) => void;
|
||||
onTrustPermanently: (device: AuthorizedDevice) => void;
|
||||
onRemoveDevice: (device: AuthorizedDevice) => void;
|
||||
onRevokeAll: () => void;
|
||||
onRemoveAll: () => void;
|
||||
@@ -24,6 +25,12 @@ function formatDateTime(value: string | null | undefined): string {
|
||||
return date.toLocaleString();
|
||||
}
|
||||
|
||||
function isPermanentTrust(value: string | null | undefined): boolean {
|
||||
if (!value) return false;
|
||||
const date = new Date(value);
|
||||
return !Number.isNaN(date.getTime()) && date.getUTCFullYear() >= 2099;
|
||||
}
|
||||
|
||||
function mapDeviceTypeName(type: number): string {
|
||||
switch (type) {
|
||||
case 0: return t('txt_android');
|
||||
@@ -135,7 +142,7 @@ export default function SecurityDevicesPage(props: SecurityDevicesPageProps) {
|
||||
{device.trusted ? (
|
||||
<div className="trusted-cell">
|
||||
<Clock3 size={13} />
|
||||
<span>{formatDateTime(device.trustedUntil)}</span>
|
||||
<span>{isPermanentTrust(device.trustedUntil) ? t('txt_permanent_trust') : formatDateTime(device.trustedUntil)}</span>
|
||||
</div>
|
||||
) : (
|
||||
<span className="muted-inline">{t('txt_not_trusted')}</span>
|
||||
@@ -152,6 +159,15 @@ export default function SecurityDevicesPage(props: SecurityDevicesPageProps) {
|
||||
<ShieldOff size={14} className="btn-icon" />
|
||||
{t('txt_untrust')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary small"
|
||||
disabled={!device.trusted || !device.trustedUntil || isPermanentTrust(device.trustedUntil)}
|
||||
onClick={() => props.onTrustPermanently(device)}
|
||||
>
|
||||
<ShieldCheck size={14} className="btn-icon" />
|
||||
{t('txt_trust_permanently')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary small"
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
revokeAuthorizedDeviceTrust,
|
||||
revokeAllAuthorizedDeviceTrust,
|
||||
setTotp,
|
||||
trustAuthorizedDevicePermanently,
|
||||
updateAuthorizedDeviceName,
|
||||
updateProfile,
|
||||
} from '@/lib/api/auth';
|
||||
@@ -208,6 +209,26 @@ export default function useAccountSecurityActions(options: UseAccountSecurityAct
|
||||
});
|
||||
},
|
||||
|
||||
openTrustDevicePermanently(device: AuthorizedDevice) {
|
||||
onSetConfirm({
|
||||
title: t('txt_trust_device_permanently'),
|
||||
message: t('txt_trust_device_permanently_for_name', { name: device.name }),
|
||||
danger: false,
|
||||
onConfirm: () => {
|
||||
onSetConfirm(null);
|
||||
void (async () => {
|
||||
try {
|
||||
await trustAuthorizedDevicePermanently(authedFetch, device.identifier);
|
||||
await refetchAuthorizedDevices();
|
||||
onNotify('success', t('txt_device_trusted_permanently'));
|
||||
} catch (error) {
|
||||
onNotify('error', error instanceof Error ? error.message : t('txt_trust_device_permanently_failed'));
|
||||
}
|
||||
})();
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
openRemoveDevice(device: AuthorizedDevice) {
|
||||
onSetConfirm({
|
||||
title: t('txt_remove_device'),
|
||||
|
||||
@@ -667,6 +667,14 @@ export async function revokeAuthorizedDeviceTrust(
|
||||
if (!resp.ok) throw new Error(t('txt_revoke_device_trust_failed'));
|
||||
}
|
||||
|
||||
export async function trustAuthorizedDevicePermanently(
|
||||
authedFetch: AuthedFetch,
|
||||
deviceIdentifier: string
|
||||
): Promise<void> {
|
||||
const resp = await authedFetch(`/api/devices/authorized/${encodeURIComponent(deviceIdentifier)}/permanent`, { method: 'POST' });
|
||||
if (!resp.ok) throw new Error(t('txt_trust_device_permanently_failed'));
|
||||
}
|
||||
|
||||
export async function revokeAllAuthorizedDeviceTrust(authedFetch: AuthedFetch): Promise<void> {
|
||||
const resp = await authedFetch('/api/devices/authorized', { method: 'DELETE' });
|
||||
if (!resp.ok) throw new Error(t('txt_revoke_all_device_trust_failed'));
|
||||
|
||||
@@ -1083,6 +1083,14 @@ export function createDemoMainRoutesProps(base: AppMainRoutesProps, notify: Noti
|
||||
)));
|
||||
notify('success', t('txt_device_authorization_revoked'));
|
||||
},
|
||||
onTrustDevicePermanently: (device) => {
|
||||
state.setAuthorizedDevices((prev) => prev.map((item) => (
|
||||
item.identifier === device.identifier && item.trusted
|
||||
? { ...item, trustedUntil: '2099-12-31T23:59:59.000Z', revisionDate: new Date().toISOString() }
|
||||
: item
|
||||
)));
|
||||
notify('success', t('txt_device_trusted_permanently'));
|
||||
},
|
||||
onRemoveDevice: (device) => {
|
||||
state.setAuthorizedDevices((prev) => prev.filter((item) => item.identifier !== device.identifier));
|
||||
notify('success', t('txt_device_removed'));
|
||||
|
||||
@@ -690,6 +690,12 @@ const en: Record<string, string> = {
|
||||
"txt_revoke_all_device_trust_failed": "Failed to revoke all device trust",
|
||||
"txt_revoke_trust": "Revoke Trust",
|
||||
"txt_untrust": "Untrust",
|
||||
"txt_trust_permanently": "Trust permanently",
|
||||
"txt_trust_device_permanently": "Trust device permanently",
|
||||
"txt_trust_device_permanently_for_name": "Upgrade \"{name}\" from 30-day trust to permanent trust?",
|
||||
"txt_trust_device_permanently_failed": "Failed to trust device permanently",
|
||||
"txt_device_trusted_permanently": "Device trusted permanently",
|
||||
"txt_permanent_trust": "Permanent trust",
|
||||
"txt_update_device_note_failed": "Update device note failed",
|
||||
"txt_role": "Role",
|
||||
"txt_save": "Save",
|
||||
|
||||
@@ -690,6 +690,12 @@ const es: Record<string, string> = {
|
||||
"txt_revoke_all_device_trust_failed": "Error al revocar la confianza de todos los dispositivos",
|
||||
"txt_revoke_trust": "Revocar confianza",
|
||||
"txt_untrust": "Quitar confianza",
|
||||
"txt_trust_permanently": "Confiar permanentemente",
|
||||
"txt_trust_device_permanently": "Confiar permanentemente en el dispositivo",
|
||||
"txt_trust_device_permanently_for_name": "¿Actualizar \"{name}\" de confianza de 30 días a confianza permanente?",
|
||||
"txt_trust_device_permanently_failed": "Error al confiar permanentemente en el dispositivo",
|
||||
"txt_device_trusted_permanently": "Dispositivo confiado permanentemente",
|
||||
"txt_permanent_trust": "Confianza permanente",
|
||||
"txt_update_device_note_failed": "Error al actualizar la nota del dispositivo",
|
||||
"txt_role": "Rol",
|
||||
"txt_save": "Guardar",
|
||||
|
||||
@@ -690,6 +690,12 @@ const ru: Record<string, string> = {
|
||||
"txt_revoke_all_device_trust_failed": "Не удалось отозвать все доверие устройств.",
|
||||
"txt_revoke_trust": "Отозвать доверие",
|
||||
"txt_untrust": "Не доверять",
|
||||
"txt_trust_permanently": "Доверять постоянно",
|
||||
"txt_trust_device_permanently": "Постоянно доверять устройству",
|
||||
"txt_trust_device_permanently_for_name": "Повысить доверие к «{name}» с 30 дней до постоянного?",
|
||||
"txt_trust_device_permanently_failed": "Не удалось постоянно доверять устройству.",
|
||||
"txt_device_trusted_permanently": "Устройство постоянно доверено",
|
||||
"txt_permanent_trust": "Постоянное доверие",
|
||||
"txt_update_device_note_failed": "Не удалось обновить примечание об устройстве.",
|
||||
"txt_role": "Роль",
|
||||
"txt_save": "Сохранить",
|
||||
|
||||
@@ -690,6 +690,12 @@ const zhCN: Record<string, string> = {
|
||||
"txt_revoke_all_device_trust_failed": "撤销所有设备信任失败",
|
||||
"txt_revoke_trust": "撤销信任",
|
||||
"txt_untrust": "不信任",
|
||||
"txt_trust_permanently": "永久信任",
|
||||
"txt_trust_device_permanently": "永久信任设备",
|
||||
"txt_trust_device_permanently_for_name": "确认把“{name}”从 30 天信任升级为永久信任吗?",
|
||||
"txt_trust_device_permanently_failed": "永久信任设备失败",
|
||||
"txt_device_trusted_permanently": "设备已永久信任",
|
||||
"txt_permanent_trust": "永久信任",
|
||||
"txt_update_device_note_failed": "更新设备备注失败",
|
||||
"txt_role": "角色",
|
||||
"txt_save": "保存",
|
||||
|
||||
@@ -690,6 +690,12 @@ const zhTW: Record<string, string> = {
|
||||
"txt_revoke_all_device_trust_failed": "撤銷所有設備信任失敗",
|
||||
"txt_revoke_trust": "撤銷信任",
|
||||
"txt_untrust": "不信任",
|
||||
"txt_trust_permanently": "永久信任",
|
||||
"txt_trust_device_permanently": "永久信任設備",
|
||||
"txt_trust_device_permanently_for_name": "確認把“{name}”從 30 天信任升級為永久信任嗎?",
|
||||
"txt_trust_device_permanently_failed": "永久信任設備失敗",
|
||||
"txt_device_trusted_permanently": "設備已永久信任",
|
||||
"txt_permanent_trust": "永久信任",
|
||||
"txt_update_device_note_failed": "更新設備備註失敗",
|
||||
"txt_role": "角色",
|
||||
"txt_save": "保存",
|
||||
|
||||
Reference in New Issue
Block a user