mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-08-07 15:40:12 +00:00
feat: implement device login approval system
Add a complete device authentication approval flow that allows users to approve login requests from new devices on their already-authenticated devices. Core features: - Create authentication requests when logging in from new devices - Display pending requests with device info, IP address, and fingerprint phrases - Approve or deny requests from web interface with real-time notifications - Support multiple auth request types (authenticate & unlock, unlock only) - Automatic expiration and cleanup of stale requests Backend changes: - Add auth_requests table with proper indexes for efficient queries - Implement full CRUD API for authentication requests - Add notification hub integration for real-time updates - Add device fingerprint phrase generation for security verification Frontend changes: - Add AuthRequestApprovalDialog component for approving/denying requests - Add PendingAuthRequestsPanel component to display and manage pending requests - Integrate panels into Security and Settings pages - Add fingerprint wordlist for generating human-readable verification phrases - Update i18n translations for all supported languages Security considerations: - Access code verification to prevent unauthorized access - Device fingerprint validation for additional security layer - IP address and country tracking for audit purposes - Automatic expiration of old requests (15 minutes) - Only most recent request per device can be approved Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
e9aef72df7
commit
c652cc1533
@@ -0,0 +1,74 @@
|
||||
import { ShieldCheck, ShieldX } from 'lucide-preact';
|
||||
import ConfirmDialog from '@/components/ConfirmDialog';
|
||||
import { t } from '@/lib/i18n';
|
||||
import type { AuthRequest } from '@/lib/types';
|
||||
|
||||
interface AuthRequestApprovalDialogProps {
|
||||
open: boolean;
|
||||
authRequest: AuthRequest | null;
|
||||
submitting: boolean;
|
||||
onApprove: () => void;
|
||||
onDeny: () => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function formatDateTime(value: string | null | undefined): string {
|
||||
if (!value) return t('txt_dash');
|
||||
const parsed = new Date(value);
|
||||
if (Number.isNaN(parsed.getTime())) return value;
|
||||
return parsed.toLocaleString();
|
||||
}
|
||||
|
||||
export default function AuthRequestApprovalDialog(props: AuthRequestApprovalDialogProps) {
|
||||
const authRequest = props.authRequest;
|
||||
return (
|
||||
<ConfirmDialog
|
||||
open={props.open && !!authRequest}
|
||||
title={t('txt_approve_device_login')}
|
||||
message={t('txt_auth_request_approve_message')}
|
||||
confirmText={props.submitting ? t('txt_approving') : t('txt_approve')}
|
||||
cancelText={t('txt_later')}
|
||||
confirmDisabled={props.submitting || !authRequest}
|
||||
cancelDisabled={props.submitting}
|
||||
onConfirm={props.onApprove}
|
||||
onCancel={props.onClose}
|
||||
afterActions={(
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-danger dialog-btn"
|
||||
disabled={props.submitting || !authRequest}
|
||||
onClick={props.onDeny}
|
||||
>
|
||||
<ShieldX size={14} className="btn-icon" />
|
||||
{t('txt_deny')}
|
||||
</button>
|
||||
)}
|
||||
>
|
||||
{authRequest && (
|
||||
<div className="auth-request-details">
|
||||
<div className="auth-request-device">
|
||||
<ShieldCheck size={18} />
|
||||
<div>
|
||||
<strong>{authRequest.requestDeviceType || t('txt_unknown_device')}</strong>
|
||||
<small>{authRequest.requestDeviceIdentifier}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div className="auth-request-kv">
|
||||
<span>{t('txt_created')}</span>
|
||||
<strong>{formatDateTime(authRequest.creationDate)}</strong>
|
||||
</div>
|
||||
{authRequest.requestIpAddress && (
|
||||
<div className="auth-request-kv">
|
||||
<span>{t('txt_ip_address')}</span>
|
||||
<strong>{authRequest.requestIpAddress}</strong>
|
||||
</div>
|
||||
)}
|
||||
<div className="auth-request-fingerprint">
|
||||
<span>{t('txt_fingerprint_phrase')}</span>
|
||||
<strong>{authRequest.fingerprintPhrase || t('txt_dash')}</strong>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</ConfirmDialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user