Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ const App = () => {
handleDragStart: handleSidebarDragStart,
} = useDraggableSidebar(320);

const [clientEncryptionKey, setClientEncryptionKey] = useState<string>("");

const {
connectionStatus,
serverCapabilities,
Expand All @@ -262,6 +264,7 @@ const App = () => {
oauthClientId,
oauthScope,
config,
clientEncryptionKey,
connectionType,
onNotification: (notification) => {
setNotifications((prev) => [...prev, notification as ServerNotification]);
Expand Down Expand Up @@ -435,9 +438,13 @@ const App = () => {
};

try {
const stateMachine = new OAuthStateMachine(sseUrl, (updates) => {
currentState = { ...currentState, ...updates };
});
const stateMachine = new OAuthStateMachine(
sseUrl,
clientEncryptionKey,
(updates) => {
currentState = { ...currentState, ...updates };
},
);

while (
currentState.oauthStep !== "complete" &&
Expand Down Expand Up @@ -475,7 +482,7 @@ const App = () => {
});
}
},
[sseUrl],
[sseUrl, clientEncryptionKey],
);

useEffect(() => {
Expand Down Expand Up @@ -528,6 +535,9 @@ const App = () => {
if (data.defaultServerUrl) {
setSseUrl(data.defaultServerUrl);
}
if (data.clientEncryptionKey) {
setClientEncryptionKey(data.clientEncryptionKey);
}
})
.catch((error) =>
console.error("Error fetching default environment:", error),
Expand Down Expand Up @@ -833,6 +843,7 @@ const App = () => {
onBack={() => setIsAuthDebuggerVisible(false)}
authState={authState}
updateAuthState={updateAuthState}
clientEncryptionKey={clientEncryptionKey}
/>
</TabsContent>
);
Expand All @@ -843,7 +854,10 @@ const App = () => {
);
return (
<Suspense fallback={<div>Loading...</div>}>
<OAuthCallback onConnect={onOAuthConnect} />
<OAuthCallback
onConnect={onOAuthConnect}
clientEncryptionKey={clientEncryptionKey}
/>
</Suspense>
);
}
Expand All @@ -854,7 +868,10 @@ const App = () => {
);
return (
<Suspense fallback={<div>Loading...</div>}>
<OAuthDebugCallback onConnect={onOAuthDebugConnect} />
<OAuthDebugCallback
onConnect={onOAuthDebugConnect}
clientEncryptionKey={clientEncryptionKey}
/>
</Suspense>
);
}
Expand Down
57 changes: 45 additions & 12 deletions client/src/components/AuthDebugger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ import { OAuthStateMachine } from "../lib/oauth-state-machine";
import { SESSION_KEYS } from "../lib/constants";
import { validateRedirectUrl } from "@/utils/urlValidation";

import { encodeWithKey } from "../lib/auth";

export interface AuthDebuggerProps {
serverUrl: string;
onBack: () => void;
authState: AuthDebuggerState;
updateAuthState: (updates: Partial<AuthDebuggerState>) => void;
clientEncryptionKey: string;
}

interface StatusMessageProps {
Expand Down Expand Up @@ -60,13 +63,17 @@ const AuthDebugger = ({
onBack,
authState,
updateAuthState,
clientEncryptionKey,
}: AuthDebuggerProps) => {
// Check for existing tokens on mount
useEffect(() => {
if (serverUrl && !authState.oauthTokens) {
const checkTokens = async () => {
try {
const provider = new DebugInspectorOAuthClientProvider(serverUrl);
const provider = new DebugInspectorOAuthClientProvider(
serverUrl,
clientEncryptionKey,
);
const existingTokens = await provider.tokens();
if (existingTokens) {
updateAuthState({
Expand All @@ -80,7 +87,7 @@ const AuthDebugger = ({
};
checkTokens();
}
}, [serverUrl, updateAuthState, authState.oauthTokens]);
}, [serverUrl, updateAuthState, authState.oauthTokens, clientEncryptionKey]);

const startOAuthFlow = useCallback(() => {
if (!serverUrl) {
Expand All @@ -103,8 +110,9 @@ const AuthDebugger = ({
}, [serverUrl, updateAuthState]);

const stateMachine = useMemo(
() => new OAuthStateMachine(serverUrl, updateAuthState),
[serverUrl, updateAuthState],
() =>
new OAuthStateMachine(serverUrl, clientEncryptionKey, updateAuthState),
[serverUrl, updateAuthState, clientEncryptionKey],
);

const proceedToNextStep = useCallback(async () => {
Expand Down Expand Up @@ -150,11 +158,15 @@ const AuthDebugger = ({
latestError: null,
};

const oauthMachine = new OAuthStateMachine(serverUrl, (updates) => {
// Update our temporary state during the process
currentState = { ...currentState, ...updates };
// But don't call updateAuthState yet
});
const oauthMachine = new OAuthStateMachine(
serverUrl,
clientEncryptionKey,
(updates) => {
// Update our temporary state during the process
currentState = { ...currentState, ...updates };
// But don't call updateAuthState yet
},
);

// Manually step through each stage of the OAuth flow
while (currentState.oauthStep !== "complete") {
Expand All @@ -181,11 +193,30 @@ const AuthDebugger = ({
return;
}

// Encrypt the client secret before storing
const client_secret = currentState.oauthClientInfo?.client_secret;
const encrypted_secret =
clientEncryptionKey &&
client_secret &&
typeof encodeWithKey === "function"
? encodeWithKey(clientEncryptionKey, client_secret)
: undefined;
const stateToStore = encrypted_secret
? {
...currentState,
oauthClientInfo: {
...currentState.oauthClientInfo,
client_secret: encrypted_secret,
},
}
: currentState;

// Store the current auth state before redirecting
sessionStorage.setItem(
SESSION_KEYS.AUTH_DEBUGGER_STATE,
JSON.stringify(currentState),
JSON.stringify(stateToStore),
);

// Open the authorization URL automatically
window.location.href = currentState.authorizationUrl.toString();
break;
Expand Down Expand Up @@ -214,12 +245,13 @@ const AuthDebugger = ({
} finally {
updateAuthState({ isInitiatingAuth: false });
}
}, [serverUrl, updateAuthState, authState]);
}, [serverUrl, updateAuthState, authState, clientEncryptionKey]);

const handleClearOAuth = useCallback(() => {
if (serverUrl) {
const serverAuthProvider = new DebugInspectorOAuthClientProvider(
serverUrl,
clientEncryptionKey,
);
serverAuthProvider.clear();
updateAuthState({
Expand All @@ -235,7 +267,7 @@ const AuthDebugger = ({
updateAuthState({ statusMessage: null });
}, 3000);
}
}, [serverUrl, updateAuthState]);
}, [serverUrl, updateAuthState, clientEncryptionKey]);

return (
<div className="w-full p-4">
Expand Down Expand Up @@ -312,6 +344,7 @@ const AuthDebugger = ({
authState={authState}
updateAuthState={updateAuthState}
proceedToNextStep={proceedToNextStep}
clientEncryptionKey={clientEncryptionKey}
/>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/JsonView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const JsonView = memo(
variant: "destructive",
});
}
}, [toast, normalizedData]);
}, [toast, normalizedData, setCopied]);

return (
<div className={clsx("p-4 border rounded relative", className)}>
Expand Down
13 changes: 10 additions & 3 deletions client/src/components/OAuthCallback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ import {

interface OAuthCallbackProps {
onConnect: (serverUrl: string) => void;
clientEncryptionKey: string;
}

const OAuthCallback = ({ onConnect }: OAuthCallbackProps) => {
const OAuthCallback = ({
onConnect,
clientEncryptionKey,
}: OAuthCallbackProps) => {
const { toast } = useToast();
const hasProcessedRef = useRef(false);

Expand Down Expand Up @@ -44,7 +48,10 @@ const OAuthCallback = ({ onConnect }: OAuthCallbackProps) => {
let result;
try {
// Create an auth provider with the current server URL
const serverAuthProvider = new InspectorOAuthClientProvider(serverUrl);
const serverAuthProvider = new InspectorOAuthClientProvider(
serverUrl,
clientEncryptionKey,
);

result = await auth(serverAuthProvider, {
serverUrl,
Expand Down Expand Up @@ -73,7 +80,7 @@ const OAuthCallback = ({ onConnect }: OAuthCallbackProps) => {
handleCallback().finally(() => {
window.history.replaceState({}, document.title, "/");
});
}, [toast, onConnect]);
}, [toast, onConnect, clientEncryptionKey]);

return (
<div className="flex items-center justify-center h-screen">
Expand Down
17 changes: 15 additions & 2 deletions client/src/components/OAuthDebugCallback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
parseOAuthCallbackParams,
} from "@/utils/oauthUtils.ts";
import { AuthDebuggerState } from "@/lib/auth-types";
import { decodeWithKey } from "@/lib/auth.ts";

interface OAuthCallbackProps {
onConnect: ({
Expand All @@ -16,9 +17,13 @@ interface OAuthCallbackProps {
errorMsg?: string;
restoredState?: AuthDebuggerState;
}) => void;
clientEncryptionKey: string;
}

const OAuthDebugCallback = ({ onConnect }: OAuthCallbackProps) => {
const OAuthDebugCallback = ({
onConnect,
clientEncryptionKey,
}: OAuthCallbackProps) => {
useEffect(() => {
let isProcessed = false;

Expand Down Expand Up @@ -57,6 +62,14 @@ const OAuthDebugCallback = ({ onConnect }: OAuthCallbackProps) => {
restoredState.authorizationUrl,
);
}
// Decrypt client secret if present
if (restoredState && restoredState.oauthClientInfo.client_secret) {
const client_secret = restoredState.oauthClientInfo?.client_secret;
restoredState.oauthClientInfo.client_secret =
clientEncryptionKey && client_secret
? decodeWithKey(clientEncryptionKey, client_secret)
: undefined;
}
// Clean up the stored state
sessionStorage.removeItem(SESSION_KEYS.AUTH_DEBUGGER_STATE);
} catch (e) {
Expand Down Expand Up @@ -94,7 +107,7 @@ const OAuthDebugCallback = ({ onConnect }: OAuthCallbackProps) => {
return () => {
isProcessed = true;
};
}, [onConnect]);
}, [onConnect, clientEncryptionKey]);

const callbackParams = parseOAuthCallbackParams(window.location.search);

Expand Down
6 changes: 4 additions & 2 deletions client/src/components/OAuthFlowProgress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ interface OAuthFlowProgressProps {
authState: AuthDebuggerState;
updateAuthState: (updates: Partial<AuthDebuggerState>) => void;
proceedToNextStep: () => Promise<void>;
clientEncryptionKey: string;
}

const steps: Array<OAuthStep> = [
Expand All @@ -72,11 +73,12 @@ export const OAuthFlowProgress = ({
authState,
updateAuthState,
proceedToNextStep,
clientEncryptionKey,
}: OAuthFlowProgressProps) => {
const { toast } = useToast();
const provider = useMemo(
() => new DebugInspectorOAuthClientProvider(serverUrl),
[serverUrl],
() => new DebugInspectorOAuthClientProvider(serverUrl, clientEncryptionKey),
[serverUrl, clientEncryptionKey],
);
const [clientInfo, setClientInfo] = useState<OAuthClientInformation | null>(
null,
Expand Down
3 changes: 3 additions & 0 deletions client/src/components/__tests__/AuthDebugger.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ Object.defineProperty(window, "sessionStorage", {
value: sessionStorageMock,
});

const clientEncryptionKey = "test-encryption-key";

describe("AuthDebugger", () => {
const defaultAuthState = EMPTY_DEBUGGER_STATE;

Expand All @@ -149,6 +151,7 @@ describe("AuthDebugger", () => {
onBack: jest.fn(),
authState: defaultAuthState,
updateAuthState: jest.fn(),
clientEncryptionKey,
};

beforeEach(() => {
Expand Down
Loading