A zero-dependency AT Protocol Personal Data Server written in JavaScript
atproto pds

refactor: extract validateAuthorizationParameters helper

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Changed files
+77
src
+77
src/pds.js
··· 3728 3728 } 3729 3729 3730 3730 /** 3731 + * Validate OAuth authorization request parameters. 3732 + * Shared between PAR and direct authorization flows. 3733 + * @param {Object} params - The authorization parameters 3734 + * @param {string} params.clientId - The client_id 3735 + * @param {string} params.redirectUri - The redirect_uri 3736 + * @param {string} params.responseType - The response_type 3737 + * @param {string} [params.responseMode] - The response_mode 3738 + * @param {string} [params.scope] - The scope 3739 + * @param {string} [params.state] - The state 3740 + * @param {string} params.codeChallenge - The code_challenge 3741 + * @param {string} params.codeChallengeMethod - The code_challenge_method 3742 + * @param {string} [params.loginHint] - The login_hint 3743 + * @returns {Promise<{error: Response} | {clientMetadata: ClientMetadata}>} 3744 + */ 3745 + async validateAuthorizationParameters({ 3746 + clientId, 3747 + redirectUri, 3748 + responseType, 3749 + codeChallenge, 3750 + codeChallengeMethod, 3751 + }) { 3752 + if (!clientId) { 3753 + return { error: errorResponse('invalid_request', 'client_id required', 400) }; 3754 + } 3755 + if (!redirectUri) { 3756 + return { error: errorResponse('invalid_request', 'redirect_uri required', 400) }; 3757 + } 3758 + if (responseType !== 'code') { 3759 + return { 3760 + error: errorResponse( 3761 + 'unsupported_response_type', 3762 + 'response_type must be code', 3763 + 400, 3764 + ), 3765 + }; 3766 + } 3767 + if (!codeChallenge || codeChallengeMethod !== 'S256') { 3768 + return { error: errorResponse('invalid_request', 'PKCE with S256 required', 400) }; 3769 + } 3770 + 3771 + let clientMetadata; 3772 + try { 3773 + clientMetadata = await getClientMetadata(clientId); 3774 + } catch (err) { 3775 + return { error: errorResponse('invalid_client', err.message, 400) }; 3776 + } 3777 + 3778 + // Validate redirect_uri against registered URIs 3779 + const isLoopback = 3780 + clientId.startsWith('http://localhost') || 3781 + clientId.startsWith('http://127.0.0.1'); 3782 + const redirectUriValid = clientMetadata.redirect_uris.some((uri) => { 3783 + if (isLoopback) { 3784 + try { 3785 + const registered = new URL(uri); 3786 + const requested = new URL(redirectUri); 3787 + return registered.origin === requested.origin; 3788 + } catch { 3789 + return false; 3790 + } 3791 + } 3792 + return uri === redirectUri; 3793 + }); 3794 + if (!redirectUriValid) { 3795 + return { 3796 + error: errorResponse( 3797 + 'invalid_request', 3798 + 'redirect_uri not registered for this client', 3799 + 400, 3800 + ), 3801 + }; 3802 + } 3803 + 3804 + return { clientMetadata }; 3805 + } 3806 + 3807 + /** 3731 3808 * Handle Pushed Authorization Request (PAR) endpoint. 3732 3809 * Validates DPoP proof, client metadata, PKCE parameters, and stores the authorization request. 3733 3810 * @param {Request} request - The incoming request