mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-24 06:20:14 +00:00
fix: require reauthentication for auth request approval
This commit is contained in:
@@ -14,6 +14,19 @@ function normalizeText(value: unknown, maxLength: number): string {
|
|||||||
return String(value ?? '').trim().slice(0, maxLength);
|
return String(value ?? '').trim().slice(0, maxLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSerializedEncString(value: unknown): value is string {
|
||||||
|
const text = String(value || '').trim();
|
||||||
|
if (!text) return false;
|
||||||
|
const parts = text.split('.');
|
||||||
|
if (parts.length !== 2) return false;
|
||||||
|
const type = Number(parts[0]);
|
||||||
|
const bodyParts = parts[1].split('|');
|
||||||
|
if (type === 2) return bodyParts.length === 3 && bodyParts.every(Boolean);
|
||||||
|
if (type === 3 || type === 4) return bodyParts.length === 1 && !!bodyParts[0];
|
||||||
|
if (type === 5 || type === 6) return bodyParts.length === 2 && bodyParts.every(Boolean);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function getClientIp(request: Request): string | null {
|
function getClientIp(request: Request): string | null {
|
||||||
return (
|
return (
|
||||||
request.headers.get('CF-Connecting-IP') ||
|
request.headers.get('CF-Connecting-IP') ||
|
||||||
@@ -251,6 +264,9 @@ export async function handleUpdateAuthRequest(request: Request, env: Env, userId
|
|||||||
if (approved && !key) {
|
if (approved && !key) {
|
||||||
return errorResponse('Encrypted key is required to approve the request.', 400);
|
return errorResponse('Encrypted key is required to approve the request.', 400);
|
||||||
}
|
}
|
||||||
|
if (approved && !isSerializedEncString(key)) {
|
||||||
|
return errorResponse('Encrypted key is not a valid encrypted string.', 400);
|
||||||
|
}
|
||||||
|
|
||||||
const updated = await storage.updateAuthRequestResponse(id, userId, {
|
const updated = await storage.updateAuthRequestResponse(id, userId, {
|
||||||
approved,
|
approved,
|
||||||
|
|||||||
@@ -337,6 +337,7 @@ export async function handleToken(request: Request, env: Env): Promise<Response>
|
|||||||
}
|
}
|
||||||
|
|
||||||
let validatedAuthRequestId: string | null = null;
|
let validatedAuthRequestId: string | null = null;
|
||||||
|
let authRequestLoginKey: string | null = null;
|
||||||
let valid = false;
|
let valid = false;
|
||||||
const normalizedAuthRequestId = String(authRequestId || '').trim();
|
const normalizedAuthRequestId = String(authRequestId || '').trim();
|
||||||
if (normalizedAuthRequestId) {
|
if (normalizedAuthRequestId) {
|
||||||
@@ -349,10 +350,12 @@ export async function handleToken(request: Request, env: Env): Promise<Response>
|
|||||||
authRequest.responseDate &&
|
authRequest.responseDate &&
|
||||||
!authRequest.authenticationDate &&
|
!authRequest.authenticationDate &&
|
||||||
!isAuthRequestExpired(authRequest) &&
|
!isAuthRequestExpired(authRequest) &&
|
||||||
|
!!authRequest.key &&
|
||||||
constantTimeEquals(authRequest.accessCode, passwordHash)
|
constantTimeEquals(authRequest.accessCode, passwordHash)
|
||||||
);
|
);
|
||||||
if (valid) {
|
if (valid) {
|
||||||
validatedAuthRequestId = authRequest!.id;
|
validatedAuthRequestId = authRequest!.id;
|
||||||
|
authRequestLoginKey = authRequest!.key;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
valid = await auth.verifyPassword(passwordHash, user.masterPasswordHash, user.email);
|
valid = await auth.verifyPassword(passwordHash, user.masterPasswordHash, user.email);
|
||||||
@@ -493,7 +496,7 @@ export async function handleToken(request: Request, env: Env): Promise<Response>
|
|||||||
token_type: 'Bearer',
|
token_type: 'Bearer',
|
||||||
...(shouldUseWebSession(request) ? { web_session: true } : { refresh_token: refreshToken }),
|
...(shouldUseWebSession(request) ? { web_session: true } : { refresh_token: refreshToken }),
|
||||||
...(trustedTwoFactorTokenToReturn ? { TwoFactorToken: trustedTwoFactorTokenToReturn } : {}),
|
...(trustedTwoFactorTokenToReturn ? { TwoFactorToken: trustedTwoFactorTokenToReturn } : {}),
|
||||||
Key: user.key,
|
Key: authRequestLoginKey || user.key,
|
||||||
PrivateKey: user.privateKey,
|
PrivateKey: user.privateKey,
|
||||||
AccountKeys: accountKeys,
|
AccountKeys: accountKeys,
|
||||||
accountKeys: accountKeys,
|
accountKeys: accountKeys,
|
||||||
|
|||||||
+27
-8
@@ -238,6 +238,7 @@ export default function App() {
|
|||||||
const [disableTotpPassword, setDisableTotpPassword] = useState('');
|
const [disableTotpPassword, setDisableTotpPassword] = useState('');
|
||||||
const [disableTotpSubmitting, setDisableTotpSubmitting] = useState(false);
|
const [disableTotpSubmitting, setDisableTotpSubmitting] = useState(false);
|
||||||
const [authRequestDialogDismissedId, setAuthRequestDialogDismissedId] = useState<string | null>(null);
|
const [authRequestDialogDismissedId, setAuthRequestDialogDismissedId] = useState<string | null>(null);
|
||||||
|
const [authRequestDialogSelectedId, setAuthRequestDialogSelectedId] = useState<string | null>(null);
|
||||||
const [authRequestSubmittingId, setAuthRequestSubmittingId] = useState<string | null>(null);
|
const [authRequestSubmittingId, setAuthRequestSubmittingId] = useState<string | null>(null);
|
||||||
const [recoverValues, setRecoverValues] = useState({ email: '', password: '', recoveryCode: '' });
|
const [recoverValues, setRecoverValues] = useState({ email: '', password: '', recoveryCode: '' });
|
||||||
const [themePreference, setThemePreference] = useState<ThemePreference>(() => readThemePreference());
|
const [themePreference, setThemePreference] = useState<ThemePreference>(() => readThemePreference());
|
||||||
@@ -1119,7 +1120,20 @@ export default function App() {
|
|||||||
});
|
});
|
||||||
const pendingAuthRequests = (pendingAuthRequestsQuery.data || []).filter(isPendingAuthRequest);
|
const pendingAuthRequests = (pendingAuthRequestsQuery.data || []).filter(isPendingAuthRequest);
|
||||||
const latestPendingAuthRequest = pendingAuthRequests[0] || null;
|
const latestPendingAuthRequest = pendingAuthRequests[0] || null;
|
||||||
const authRequestDialogOpen = !!latestPendingAuthRequest && latestPendingAuthRequest.id !== authRequestDialogDismissedId;
|
const selectedPendingAuthRequest = authRequestDialogSelectedId
|
||||||
|
? pendingAuthRequests.find((request) => request.id === authRequestDialogSelectedId) || null
|
||||||
|
: null;
|
||||||
|
const authRequestDialogRequest = selectedPendingAuthRequest || (
|
||||||
|
latestPendingAuthRequest && latestPendingAuthRequest.id !== authRequestDialogDismissedId
|
||||||
|
? latestPendingAuthRequest
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
const authRequestDialogOpen = !!authRequestDialogRequest;
|
||||||
|
|
||||||
|
async function beginApproveAuthRequest(authRequest: AuthRequest): Promise<void> {
|
||||||
|
setAuthRequestDialogSelectedId(authRequest.id);
|
||||||
|
setAuthRequestDialogDismissedId(null);
|
||||||
|
}
|
||||||
|
|
||||||
async function approveAuthRequest(authRequest: AuthRequest): Promise<void> {
|
async function approveAuthRequest(authRequest: AuthRequest): Promise<void> {
|
||||||
if (!session) throw new Error(t('txt_vault_key_unavailable'));
|
if (!session) throw new Error(t('txt_vault_key_unavailable'));
|
||||||
@@ -1133,6 +1147,7 @@ export default function App() {
|
|||||||
requestApproved: true,
|
requestApproved: true,
|
||||||
});
|
});
|
||||||
setAuthRequestDialogDismissedId(null);
|
setAuthRequestDialogDismissedId(null);
|
||||||
|
setAuthRequestDialogSelectedId(null);
|
||||||
pushToast('success', t('txt_auth_request_approved'));
|
pushToast('success', t('txt_auth_request_approved'));
|
||||||
await pendingAuthRequestsQuery.refetch();
|
await pendingAuthRequestsQuery.refetch();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -1148,6 +1163,7 @@ export default function App() {
|
|||||||
requestApproved: false,
|
requestApproved: false,
|
||||||
});
|
});
|
||||||
setAuthRequestDialogDismissedId(null);
|
setAuthRequestDialogDismissedId(null);
|
||||||
|
setAuthRequestDialogSelectedId(null);
|
||||||
pushToast('success', t('txt_auth_request_denied'));
|
pushToast('success', t('txt_auth_request_denied'));
|
||||||
await pendingAuthRequestsQuery.refetch();
|
await pendingAuthRequestsQuery.refetch();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -2002,7 +2018,7 @@ export default function App() {
|
|||||||
onRefreshPendingAuthRequests: async () => {
|
onRefreshPendingAuthRequests: async () => {
|
||||||
await pendingAuthRequestsQuery.refetch();
|
await pendingAuthRequestsQuery.refetch();
|
||||||
},
|
},
|
||||||
onApproveAuthRequest: approveAuthRequest,
|
onApproveAuthRequest: beginApproveAuthRequest,
|
||||||
onDenyAuthRequest: denyAuthRequest,
|
onDenyAuthRequest: denyAuthRequest,
|
||||||
onLockTimeoutChange: setLockTimeoutMinutes,
|
onLockTimeoutChange: setLockTimeoutMinutes,
|
||||||
onSessionTimeoutActionChange: setSessionTimeoutAction,
|
onSessionTimeoutActionChange: setSessionTimeoutAction,
|
||||||
@@ -2278,21 +2294,24 @@ export default function App() {
|
|||||||
/>
|
/>
|
||||||
<AuthRequestApprovalDialog
|
<AuthRequestApprovalDialog
|
||||||
open={authRequestDialogOpen}
|
open={authRequestDialogOpen}
|
||||||
authRequest={latestPendingAuthRequest}
|
authRequest={authRequestDialogRequest}
|
||||||
submitting={!!authRequestSubmittingId}
|
submitting={!!authRequestSubmittingId}
|
||||||
onApprove={() => {
|
onApprove={() => {
|
||||||
if (!latestPendingAuthRequest) return;
|
if (!authRequestDialogRequest) return;
|
||||||
void approveAuthRequest(latestPendingAuthRequest).catch((error) => {
|
void approveAuthRequest(authRequestDialogRequest).catch((error) => {
|
||||||
pushToast('error', error instanceof Error ? error.message : t('txt_auth_request_update_failed'));
|
pushToast('error', error instanceof Error ? error.message : t('txt_auth_request_update_failed'));
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onDeny={() => {
|
onDeny={() => {
|
||||||
if (!latestPendingAuthRequest) return;
|
if (!authRequestDialogRequest) return;
|
||||||
void denyAuthRequest(latestPendingAuthRequest).catch((error) => {
|
void denyAuthRequest(authRequestDialogRequest).catch((error) => {
|
||||||
pushToast('error', error instanceof Error ? error.message : t('txt_auth_request_update_failed'));
|
pushToast('error', error instanceof Error ? error.message : t('txt_auth_request_update_failed'));
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onClose={() => setAuthRequestDialogDismissedId(latestPendingAuthRequest?.id || null)}
|
onClose={() => {
|
||||||
|
setAuthRequestDialogSelectedId(null);
|
||||||
|
setAuthRequestDialogDismissedId(authRequestDialogRequest?.id || null);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user