mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-07-09 07:25:43 +08:00
fix: validate X-Refreshed-Token before storing it as the auth token (#971)
* fix: validate X-Refreshed-Token before storing it as the auth token authenticatedFetch stored any X-Refreshed-Token response header value straight into localStorage. A malformed or injected header value would silently overwrite the stored auth token, breaking every subsequent authenticated request. Only accept values that match the JWT shape the server actually issues (three base64url segments). * fix: apply refreshed-token validation to file upload path Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYpHw7VYW1d2HwfL9EZKx4 --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
123d244a51
commit
5884573a69
@@ -3,6 +3,7 @@ import type { DragEvent } from 'react';
|
|||||||
|
|
||||||
import { IS_PLATFORM } from '../../../constants/config';
|
import { IS_PLATFORM } from '../../../constants/config';
|
||||||
import type { Project } from '../../../types/app';
|
import type { Project } from '../../../types/app';
|
||||||
|
import { isValidRefreshedToken } from '../../../utils/api';
|
||||||
import {
|
import {
|
||||||
MAX_FILE_UPLOAD_COUNT,
|
MAX_FILE_UPLOAD_COUNT,
|
||||||
MAX_FILE_UPLOAD_SIZE_BYTES,
|
MAX_FILE_UPLOAD_SIZE_BYTES,
|
||||||
@@ -131,7 +132,7 @@ const uploadFormDataWithProgress = (
|
|||||||
|
|
||||||
xhr.onload = () => {
|
xhr.onload = () => {
|
||||||
const refreshedToken = xhr.getResponseHeader('X-Refreshed-Token');
|
const refreshedToken = xhr.getResponseHeader('X-Refreshed-Token');
|
||||||
if (refreshedToken) {
|
if (isValidRefreshedToken(refreshedToken)) {
|
||||||
localStorage.setItem('auth-token', refreshedToken);
|
localStorage.setItem('auth-token', refreshedToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
import { IS_PLATFORM } from "../constants/config";
|
import { IS_PLATFORM } from "../constants/config";
|
||||||
|
|
||||||
|
// Only accept a refreshed token that has this app's issued JWT shape
|
||||||
|
// (three base64url segments). An attacker-injected/malformed header value
|
||||||
|
// must never overwrite the stored auth token.
|
||||||
|
/**
|
||||||
|
* @param {unknown} token
|
||||||
|
* @returns {token is string}
|
||||||
|
*/
|
||||||
|
export const isValidRefreshedToken = (token) =>
|
||||||
|
typeof token === 'string' &&
|
||||||
|
/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(token);
|
||||||
|
|
||||||
// Utility function for authenticated API calls
|
// Utility function for authenticated API calls
|
||||||
export const authenticatedFetch = (url, options = {}) => {
|
export const authenticatedFetch = (url, options = {}) => {
|
||||||
const token = localStorage.getItem('auth-token');
|
const token = localStorage.getItem('auth-token');
|
||||||
@@ -23,7 +34,7 @@ export const authenticatedFetch = (url, options = {}) => {
|
|||||||
},
|
},
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
const refreshedToken = response.headers.get('X-Refreshed-Token');
|
const refreshedToken = response.headers.get('X-Refreshed-Token');
|
||||||
if (refreshedToken) {
|
if (isValidRefreshedToken(refreshedToken)) {
|
||||||
localStorage.setItem('auth-token', refreshedToken);
|
localStorage.setItem('auth-token', refreshedToken);
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
|
|||||||
Reference in New Issue
Block a user