This commit is contained in:
seb
2026-10-12 22:41:14 +02:00
parent 3d009ba216
commit 2f92199d7f

View File

@@ -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',