Skip to content

Commit 62dbbca

Browse files
committed
use client info scope
1 parent 590d484 commit 62dbbca

File tree

2 files changed

+78
-74
lines changed

2 files changed

+78
-74
lines changed

src/client/auth.test.ts

Lines changed: 77 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
startAuthorization,
44
exchangeAuthorization,
55
refreshAuthorization,
6-
registerClient,
76
discoverOAuthProtectedResourceMetadata,
87
extractResourceMetadataUrl,
98
auth,
@@ -17,6 +16,7 @@ global.fetch = mockFetch;
1716
describe("OAuth Authorization", () => {
1817
beforeEach(() => {
1918
mockFetch.mockReset();
19+
jest.clearAllMocks();
2020
});
2121

2222
describe("extractResourceMetadataUrl", () => {
@@ -626,89 +626,93 @@ describe("OAuth Authorization", () => {
626626
});
627627

628628
describe("registerClient", () => {
629-
const validClientMetadata = {
630-
redirect_uris: ["http://localhost:3000/callback"],
631-
client_name: "Test Client",
629+
// ...existing tests...
630+
});
631+
632+
describe("auth", () => {
633+
// Patch pkce-challenge to return deterministic values
634+
jest.mock("pkce-challenge", () => () => ({
635+
code_verifier: "test_verifier",
636+
code_challenge: "test_challenge",
637+
}));
638+
639+
const validMetadata = {
640+
issuer: "https://auth.example.com",
641+
authorization_endpoint: "https://auth.example.com/authorize",
642+
token_endpoint: "https://auth.example.com/token",
643+
registration_endpoint: "https://auth.example.com/register",
644+
response_types_supported: ["code"],
645+
code_challenge_methods_supported: ["S256"],
632646
};
633647

634648
const validClientInfo = {
635649
client_id: "client123",
636650
client_secret: "secret123",
637-
client_id_issued_at: 1612137600,
638-
client_secret_expires_at: 1612224000,
639-
...validClientMetadata,
651+
redirect_uris: ["http://localhost:3000/callback"],
652+
client_name: "Test Client",
640653
};
641654

642-
it("registers client and returns client information", async () => {
643-
mockFetch.mockResolvedValueOnce({
644-
ok: true,
645-
status: 200,
646-
json: async () => validClientInfo,
647-
});
648-
649-
const clientInfo = await registerClient("https://auth.example.com", {
650-
clientMetadata: validClientMetadata,
651-
});
652-
653-
expect(clientInfo).toEqual(validClientInfo);
654-
expect(mockFetch).toHaveBeenCalledWith(
655-
expect.objectContaining({
656-
href: "https://auth.example.com/register",
657-
}),
658-
expect.objectContaining({
659-
method: "POST",
660-
headers: {
661-
"Content-Type": "application/json",
662-
},
663-
body: JSON.stringify(validClientMetadata),
664-
})
665-
);
666-
});
667-
668-
it("validates client information response schema", async () => {
669-
mockFetch.mockResolvedValueOnce({
670-
ok: true,
671-
status: 200,
672-
json: async () => ({
673-
// Missing required fields
674-
client_secret: "secret123",
675-
}),
676-
});
677-
678-
await expect(
679-
registerClient("https://auth.example.com", {
680-
clientMetadata: validClientMetadata,
681-
})
682-
).rejects.toThrow();
683-
});
684-
685-
it("throws when registration endpoint not available in metadata", async () => {
686-
const metadata = {
687-
issuer: "https://auth.example.com",
688-
authorization_endpoint: "https://auth.example.com/authorize",
689-
token_endpoint: "https://auth.example.com/token",
690-
response_types_supported: ["code"],
655+
it("uses scope from registered client information if not present in clientMetadata", async () => {
656+
// Mock fetch for metadata discovery and registration
657+
mockFetch
658+
.mockResolvedValueOnce({
659+
ok: true,
660+
status: 200,
661+
json: async () => ({}),
662+
}) // protected resource metadata
663+
.mockResolvedValueOnce({
664+
ok: true,
665+
status: 200,
666+
json: async () => validMetadata,
667+
}) // discovery
668+
.mockResolvedValueOnce({
669+
ok: true,
670+
status: 200,
671+
json: async () => ({
672+
...validClientInfo,
673+
scope: "dynamic scope from registration",
674+
}),
675+
}); // registration
676+
677+
// Provider: clientInformation returns undefined first, then fullInformation after registration
678+
const fullInformation = {
679+
...validClientInfo,
680+
scope: "dynamic scope from registration",
681+
};
682+
const clientInformationMock = jest
683+
.fn()
684+
.mockResolvedValueOnce(undefined)
685+
.mockResolvedValueOnce(fullInformation);
686+
687+
const provider = {
688+
get redirectUrl() {
689+
return "http://localhost:3000/callback";
690+
},
691+
get clientMetadata() {
692+
// No scope here!
693+
return validClientInfo;
694+
},
695+
clientInformation: clientInformationMock,
696+
tokens: jest.fn().mockResolvedValue(undefined),
697+
saveTokens: jest.fn(),
698+
saveCodeVerifier: jest.fn(),
699+
codeVerifier: jest.fn(),
700+
redirectToAuthorization: jest.fn(),
701+
saveClientInformation: jest.fn(),
691702
};
692703

693-
await expect(
694-
registerClient("https://auth.example.com", {
695-
metadata,
696-
clientMetadata: validClientMetadata,
697-
})
698-
).rejects.toThrow(/does not support dynamic client registration/);
699-
});
700-
701-
it("throws on error response", async () => {
702-
mockFetch.mockResolvedValueOnce({
703-
ok: false,
704-
status: 400,
704+
await auth(provider, {
705+
serverUrl: "https://auth.example.com",
705706
});
706707

707-
await expect(
708-
registerClient("https://auth.example.com", {
709-
clientMetadata: validClientMetadata,
710-
})
711-
).rejects.toThrow("Dynamic client registration failed");
708+
// Check saveClientInformation was called with fullInformation
709+
expect(provider.saveClientInformation).toHaveBeenCalledWith(fullInformation);
710+
711+
// Check that redirectToAuthorization was called with a URL containing the correct scope from registration
712+
expect(provider.redirectToAuthorization).toHaveBeenCalledTimes(1);
713+
const urlArg = provider.redirectToAuthorization.mock.calls[0][0];
714+
expect(urlArg).toBeInstanceOf(URL);
715+
expect(urlArg.searchParams.get("scope")).toBe("dynamic scope from registration");
712716
});
713717
});
714718

src/client/auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ export async function auth(
175175
clientInformation,
176176
state,
177177
redirectUrl: provider.redirectUrl,
178-
scope: scope || provider.clientMetadata.scope,
178+
scope: scope || provider.clientMetadata.scope || (clientInformation as OAuthClientInformationFull).scope,
179179
});
180180

181181
await provider.saveCodeVerifier(codeVerifier);

0 commit comments

Comments
 (0)