Merge pull request #684 from 2underscores/dcr-fallback-fix

Fix: Pre-registered Client ID as fallback to DCR
This commit is contained in:
Ola Hungerford 2025-08-13 05:51:39 -07:00 • committed by GitHub
commit a30e0d1fda
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 106 additions and 5 deletions

View file

@ -442,6 +442,103 @@ describe("AuthDebugger", () => {
});
});
describe("Client Registration behavior", () => {
it("uses preregistered (static) client information without calling DCR", async () => {
const preregClientInfo = {
client_id: "static_client_id",
client_secret: "static_client_secret",
redirect_uris: ["http://localhost:3000/oauth/callback/debug"],
};
// Return preregistered client info for the server-specific key
sessionStorageMock.getItem.mockImplementation((key) => {
if (
key ===
`[${defaultProps.serverUrl}] ${SESSION_KEYS.PREREGISTERED_CLIENT_INFORMATION}`
) {
return JSON.stringify(preregClientInfo);
}
return null;
});
const updateAuthState = jest.fn();
await act(async () => {
renderAuthDebugger({
updateAuthState,
authState: {
...defaultAuthState,
isInitiatingAuth: false,
oauthStep: "client_registration",
oauthMetadata: mockOAuthMetadata as unknown as OAuthMetadata,
},
});
});
// Proceed from client_registration → authorization_redirect
await act(async () => {
fireEvent.click(screen.getByText("Continue"));
});
// Should NOT attempt dynamic client registration
expect(mockRegisterClient).not.toHaveBeenCalled();
// Should advance with the preregistered client info
expect(updateAuthState).toHaveBeenCalledWith(
expect.objectContaining({
oauthClientInfo: expect.objectContaining({
client_id: "static_client_id",
}),
oauthStep: "authorization_redirect",
}),
);
});
it("falls back to DCR when no static client information is available", async () => {
// No preregistered or dynamic client info present in session storage
sessionStorageMock.getItem.mockImplementation(() => null);
// DCR returns a new client
mockRegisterClient.mockResolvedValueOnce(mockOAuthClientInfo);
const updateAuthState = jest.fn();
await act(async () => {
renderAuthDebugger({
updateAuthState,
authState: {
...defaultAuthState,
isInitiatingAuth: false,
oauthStep: "client_registration",
oauthMetadata: mockOAuthMetadata as unknown as OAuthMetadata,
},
});
});
await act(async () => {
fireEvent.click(screen.getByText("Continue"));
});
expect(mockRegisterClient).toHaveBeenCalledTimes(1);
// Should save and advance with the DCR client info
expect(updateAuthState).toHaveBeenCalledWith(
expect.objectContaining({
oauthClientInfo: expect.objectContaining({
client_id: "test_client_id",
}),
oauthStep: "authorization_redirect",
}),
);
// Verify the dynamically registered client info was persisted
expect(sessionStorage.setItem).toHaveBeenCalledWith(
`[${defaultProps.serverUrl}] ${SESSION_KEYS.CLIENT_INFORMATION}`,
expect.any(String),
);
});
});
describe("OAuth State Persistence", () => {
it("should store auth state to sessionStorage before redirect in Quick OAuth Flow", async () => {
const updateAuthState = jest.fn();

View file

@ -89,12 +89,16 @@ export const oauthTransitions: Record<OAuthStep, StateTransition> = {
clientMetadata.scope = scopesSupported.join(" ");
}
const fullInformation = await registerClient(context.serverUrl, {
metadata,
clientMetadata,
});
// Try Static client first, with DCR as fallback
let fullInformation = await context.provider.clientInformation();
if (!fullInformation) {
fullInformation = await registerClient(context.serverUrl, {
metadata,
clientMetadata,
});
context.provider.saveClientInformation(fullInformation);
}
context.provider.saveClientInformation(fullInformation);
context.updateState({
oauthClientInfo: fullInformation,
oauthStep: "authorization_redirect",