u
This commit is contained in:
@@ -20,6 +20,56 @@ function getBaseUrl(req) {
|
|||||||
return `${proto}://${host}`;
|
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 = {
|
const tokenVerifier = {
|
||||||
verifyAccessToken: async (token) => {
|
verifyAccessToken: async (token) => {
|
||||||
let decoded;
|
let decoded;
|
||||||
@@ -325,7 +375,7 @@ export function register(app, _basePath = '') {
|
|||||||
registeredUris = [client.redirectUris];
|
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');
|
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({
|
return res.status(400).json({
|
||||||
error: 'invalid_grant',
|
error: 'invalid_grant',
|
||||||
error_description: 'Redirect URI does not match authorization code',
|
error_description: 'Redirect URI does not match authorization code',
|
||||||
|
|||||||
Reference in New Issue
Block a user