Skip to content
Merged
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
97 changes: 97 additions & 0 deletions client/src/components/__tests__/AuthDebugger.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
14 changes: 9 additions & 5 deletions client/src/lib/oauth-state-machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down