+23
-15
src/pds.js
+23
-15
src/pds.js
···
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({
···
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 {
···
3765
};
3766
}
3767
if (!codeChallenge || codeChallengeMethod !== 'S256') {
3768
-
return { error: errorResponse('invalid_request', 'PKCE with S256 required', 400) };
3769
}
3770
3771
let clientMetadata;
···
3920
return new Response('Missing client_id parameter', { status: 400 });
3921
}
3922
3923
-
const match = requestUri.match(/^urn:ietf:params:oauth:request_uri:(.+)$/);
3924
if (!match) return new Response('Invalid request_uri', { status: 400 });
3925
3926
const rows = this.sql
···
3932
.toArray();
3933
const authRequest = rows[0];
3934
3935
-
if (!authRequest) return new Response('Request not found', { status: 400 });
3936
if (new Date(/** @type {string} */ (authRequest.expires_at)) < new Date())
3937
return new Response('Request expired', { status: 400 });
3938
if (authRequest.code)
···
3952
scope: parameters.scope || 'atproto',
3953
requestUri: requestUri || '',
3954
}),
3955
-
{ status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' } },
3956
);
3957
}
3958
···
3731
* Validate OAuth authorization request parameters.
3732
* Shared between PAR and direct authorization flows.
3733
* @param {Object} params - The authorization parameters
3734
+
* @param {string | undefined | null} params.clientId - The client_id
3735
+
* @param {string | undefined | null} params.redirectUri - The redirect_uri
3736
+
* @param {string | undefined | null} params.responseType - The response_type
3737
+
* @param {string | undefined | null} params.codeChallenge - The code_challenge
3738
+
* @param {string | undefined | null} params.codeChallengeMethod - The code_challenge_method
3739
* @returns {Promise<{error: Response} | {clientMetadata: ClientMetadata}>}
3740
*/
3741
async validateAuthorizationParameters({
···
3746
codeChallengeMethod,
3747
}) {
3748
if (!clientId) {
3749
+
return {
3750
+
error: errorResponse('invalid_request', 'client_id required', 400),
3751
+
};
3752
}
3753
if (!redirectUri) {
3754
+
return {
3755
+
error: errorResponse('invalid_request', 'redirect_uri required', 400),
3756
+
};
3757
}
3758
if (responseType !== 'code') {
3759
return {
···
3765
};
3766
}
3767
if (!codeChallenge || codeChallengeMethod !== 'S256') {
3768
+
return {
3769
+
error: errorResponse('invalid_request', 'PKCE with S256 required', 400),
3770
+
};
3771
}
3772
3773
let clientMetadata;
···
3922
return new Response('Missing client_id parameter', { status: 400 });
3923
}
3924
3925
+
const match = requestUri.match(
3926
+
/^urn:ietf:params:oauth:request_uri:(.+)$/,
3927
+
);
3928
if (!match) return new Response('Invalid request_uri', { status: 400 });
3929
3930
const rows = this.sql
···
3936
.toArray();
3937
const authRequest = rows[0];
3938
3939
+
if (!authRequest)
3940
+
return new Response('Request not found', { status: 400 });
3941
if (new Date(/** @type {string} */ (authRequest.expires_at)) < new Date())
3942
return new Response('Request expired', { status: 400 });
3943
if (authRequest.code)
···
3957
scope: parameters.scope || 'atproto',
3958
requestUri: requestUri || '',
3959
}),
3960
+
{
3961
+
status: 200,
3962
+
headers: { 'Content-Type': 'text/html; charset=utf-8' },
3963
+
},
3964
);
3965
}
3966
+9
-2
test/e2e.test.js
+9
-2
test/e2e.test.js
···
1474
authorizeUrl.searchParams.set('login_hint', DID);
1475
1476
const getRes = await fetch(authorizeUrl.toString());
1477
-
assert.strictEqual(getRes.status, 200, 'Direct authorize GET should succeed');
1478
1479
const html = await getRes.text();
1480
assert.ok(html.includes('Authorize'), 'Should show consent page');
1481
-
assert.ok(html.includes('request_uri'), 'Should include request_uri in form');
1482
});
1483
1484
it('completes full direct authorization flow', async () => {
···
1474
authorizeUrl.searchParams.set('login_hint', DID);
1475
1476
const getRes = await fetch(authorizeUrl.toString());
1477
+
assert.strictEqual(
1478
+
getRes.status,
1479
+
200,
1480
+
'Direct authorize GET should succeed',
1481
+
);
1482
1483
const html = await getRes.text();
1484
assert.ok(html.includes('Authorize'), 'Should show consent page');
1485
+
assert.ok(
1486
+
html.includes('request_uri'),
1487
+
'Should include request_uri in form',
1488
+
);
1489
});
1490
1491
it('completes full direct authorization flow', async () => {