make client side client_id generation configurable in the oauth router (#734)
This commit is contained in:
parent
117781453c
commit
cc9ea7fd21
4 changed files with 36 additions and 6 deletions
|
|
@ -16,5 +16,5 @@ export interface OAuthRegisteredClientsStore {
|
|||
*
|
||||
* If unimplemented, dynamic client registration is unsupported.
|
||||
*/
|
||||
registerClient?(client: OAuthClientInformationFull): OAuthClientInformationFull | Promise<OAuthClientInformationFull>;
|
||||
registerClient?(client: Omit<OAuthClientInformationFull, "client_id">): OAuthClientInformationFull | Promise<OAuthClientInformationFull>;
|
||||
}
|
||||
|
|
@ -218,6 +218,26 @@ describe('Client Registration Handler', () => {
|
|||
expect(response.body.client_secret_expires_at).toBe(0);
|
||||
});
|
||||
|
||||
it('sets no client_id when clientIdGeneration=false', async () => {
|
||||
// Create handler with no expiry
|
||||
const customApp = express();
|
||||
const options: ClientRegistrationHandlerOptions = {
|
||||
clientsStore: mockClientStoreWithRegistration,
|
||||
clientIdGeneration: false
|
||||
};
|
||||
|
||||
customApp.use('/register', clientRegistrationHandler(options));
|
||||
|
||||
const response = await supertest(customApp)
|
||||
.post('/register')
|
||||
.send({
|
||||
redirect_uris: ['https://example.com/callback']
|
||||
});
|
||||
|
||||
expect(response.status).toBe(201);
|
||||
expect(response.body.client_id).toBeUndefined();
|
||||
});
|
||||
|
||||
it('handles client with all metadata fields', async () => {
|
||||
const fullClientMetadata: OAuthClientMetadata = {
|
||||
redirect_uris: ['https://example.com/callback'],
|
||||
|
|
|
|||
|
|
@ -31,6 +31,13 @@ export type ClientRegistrationHandlerOptions = {
|
|||
* Registration endpoints are particularly sensitive to abuse and should be rate limited.
|
||||
*/
|
||||
rateLimit?: Partial<RateLimitOptions> | false;
|
||||
|
||||
/**
|
||||
* Whether to generate a client ID before calling the client registration endpoint.
|
||||
*
|
||||
* If not set, defaults to true.
|
||||
*/
|
||||
clientIdGeneration?: boolean;
|
||||
};
|
||||
|
||||
const DEFAULT_CLIENT_SECRET_EXPIRY_SECONDS = 30 * 24 * 60 * 60; // 30 days
|
||||
|
|
@ -38,7 +45,8 @@ const DEFAULT_CLIENT_SECRET_EXPIRY_SECONDS = 30 * 24 * 60 * 60; // 30 days
|
|||
export function clientRegistrationHandler({
|
||||
clientsStore,
|
||||
clientSecretExpirySeconds = DEFAULT_CLIENT_SECRET_EXPIRY_SECONDS,
|
||||
rateLimit: rateLimitConfig
|
||||
rateLimit: rateLimitConfig,
|
||||
clientIdGeneration = true,
|
||||
}: ClientRegistrationHandlerOptions): RequestHandler {
|
||||
if (!clientsStore.registerClient) {
|
||||
throw new Error("Client registration store does not support registering clients");
|
||||
|
|
@ -78,7 +86,6 @@ export function clientRegistrationHandler({
|
|||
const isPublicClient = clientMetadata.token_endpoint_auth_method === 'none'
|
||||
|
||||
// Generate client credentials
|
||||
const clientId = crypto.randomUUID();
|
||||
const clientSecret = isPublicClient
|
||||
? undefined
|
||||
: crypto.randomBytes(32).toString('hex');
|
||||
|
|
@ -89,14 +96,17 @@ export function clientRegistrationHandler({
|
|||
const secretExpiryTime = clientsDoExpire ? clientIdIssuedAt + clientSecretExpirySeconds : 0
|
||||
const clientSecretExpiresAt = isPublicClient ? undefined : secretExpiryTime
|
||||
|
||||
let clientInfo: OAuthClientInformationFull = {
|
||||
let clientInfo: Omit<OAuthClientInformationFull, "client_id"> & { client_id?: string } = {
|
||||
...clientMetadata,
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
client_id_issued_at: clientIdIssuedAt,
|
||||
client_secret_expires_at: clientSecretExpiresAt,
|
||||
};
|
||||
|
||||
if (clientIdGeneration) {
|
||||
clientInfo.client_id = crypto.randomUUID();
|
||||
}
|
||||
|
||||
clientInfo = await clientsStore.registerClient!(clientInfo);
|
||||
res.status(201).json(clientInfo);
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ export function mcpAuthRouter(options: AuthRouterOptions): RequestHandler {
|
|||
new URL(oauthMetadata.registration_endpoint).pathname,
|
||||
clientRegistrationHandler({
|
||||
clientsStore: options.provider.clientsStore,
|
||||
...options,
|
||||
...options.clientRegistrationOptions,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue