u
This commit is contained in:
@@ -20,6 +20,56 @@ function getBaseUrl(req) {
|
||||
return `${proto}://${host}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a candidate redirect_uri matches any registered redirect URI.
|
||||
* Per RFC 8252 Section 7.3 (OAuth 2.0 for Native Apps):
|
||||
* "The authorization server MUST allow any port for loopback IP redirect URIs."
|
||||
* @param {string} candidateUri
|
||||
* @param {Array<string>|string} registeredUris
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isRedirectUriAllowed(candidateUri, registeredUris) {
|
||||
if (!candidateUri || !registeredUris) return false;
|
||||
const uriList = Array.isArray(registeredUris) ? registeredUris : [registeredUris];
|
||||
if (uriList.includes(candidateUri)) return true;
|
||||
|
||||
try {
|
||||
const candidate = new URL(candidateUri);
|
||||
const isCandidateLoopback = candidate.hostname === '127.0.0.1' || candidate.hostname === 'localhost';
|
||||
|
||||
for (const reg of uriList) {
|
||||
try {
|
||||
const registered = new URL(reg);
|
||||
const normCandidatePath = candidate.pathname.replace(/\/$/, '');
|
||||
const normRegisteredPath = registered.pathname.replace(/\/$/, '');
|
||||
|
||||
if (
|
||||
candidate.origin === registered.origin &&
|
||||
normCandidatePath === normRegisteredPath
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const isRegisteredLoopback = registered.hostname === '127.0.0.1' || registered.hostname === 'localhost';
|
||||
if (
|
||||
isCandidateLoopback &&
|
||||
isRegisteredLoopback &&
|
||||
candidate.protocol === registered.protocol &&
|
||||
normCandidatePath === normRegisteredPath
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
// Ignore malformed registered URI
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const tokenVerifier = {
|
||||
verifyAccessToken: async (token) => {
|
||||
let decoded;
|
||||
@@ -325,7 +375,7 @@ export function register(app, _basePath = '') {
|
||||
registeredUris = [client.redirectUris];
|
||||
}
|
||||
|
||||
if (!redirect_uri || !registeredUris.includes(redirect_uri)) {
|
||||
if (!redirect_uri || !isRedirectUriAllowed(redirect_uri, registeredUris)) {
|
||||
return res.status(400).send('Invalid or unregistered redirect_uri');
|
||||
}
|
||||
|
||||
@@ -543,7 +593,7 @@ export function register(app, _basePath = '') {
|
||||
});
|
||||
}
|
||||
|
||||
if (authCode.redirectUri !== redirect_uri) {
|
||||
if (!isRedirectUriAllowed(redirect_uri, [authCode.redirectUri])) {
|
||||
return res.status(400).json({
|
||||
error: 'invalid_grant',
|
||||
error_description: 'Redirect URI does not match authorization code',
|
||||
|
||||
Reference in New Issue
Block a user