Skip to content

Commit

Permalink
Merge pull request from GHSA-wrvh-rcmr-9qfc
Browse files Browse the repository at this point in the history
Add validation for custom U&P OAuth callbacks
  • Loading branch information
alexandrebodin committed May 3, 2024
2 parents ca6c7c8 + 1c8a790 commit 9c79921
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 4 deletions.
30 changes: 30 additions & 0 deletions packages/plugins/users-permissions/server/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,36 @@ module.exports = {
},
},
},
callback: {
validate(callback, provider) {
let uCallback;
let uProviderCallback;

try {
uCallback = new URL(callback);
uProviderCallback = new URL(provider.callback);
} catch {
throw new Error('The callback is not a valid URL')
}


// Make sure the different origin matches
if(uCallback.origin !== uProviderCallback.origin) {
throw new Error(
`Forbidden callback provided: origins don't match. Please verify your config.`
);
}

// Make sure the different pathname matches
if(uCallback.pathname !== uProviderCallback.pathname) {
throw new Error(
`Forbidden callback provided: pathname don't match. Please verify your config.`
);
}

// NOTE: We're not checking the search parameters on purpose to allow passing different states
}
},
}),
validator() {},
};
26 changes: 22 additions & 4 deletions packages/plugins/users-permissions/server/controllers/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,28 @@ module.exports = {
}

// Ability to pass OAuth callback dynamically
grantConfig[provider].callback =
_.get(ctx, 'query.callback') ||
_.get(ctx, 'session.grant.dynamic.callback') ||
grantConfig[provider].callback;
const queryCustomCallback = _.get(ctx, 'query.callback');
const dynamicSessionCallback = _.get(ctx, 'session.grant.dynamic.callback');

const customCallback = queryCustomCallback ?? dynamicSessionCallback;

// The custom callback is validated to make sure it's not redirecting to an unwanted actor.
if (customCallback !== undefined) {
try {
// We're extracting the callback validator from the plugin config since it can be user-customized
const { validate: validateCallback } = strapi
.plugin('users-permissions')
.config('callback');

await validateCallback(customCallback, grantConfig[provider]);

grantConfig[provider].callback = customCallback;
} catch (e) {
throw new ValidationError('Invalid callback URL provided', { callback: customCallback });
}
}

// Build a valid redirect URI for the current provider
grantConfig[provider].redirect_uri = getService('providers').buildRedirectUri(provider);

return grant(grantConfig)(ctx, next);
Expand Down

0 comments on commit 9c79921

Please sign in to comment.