-
Notifications
You must be signed in to change notification settings - Fork 257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Expose custom agent parameter? #93
Comments
Thanks for the feedback, @bitinn! I'll think about it. I can see how this can be useful. |
👋 @bitinn I was thinking about this issue, and while I may never expose the underlying HTTP library, I think it will be possible to ... well, monkey-patch it: var https = require('https')
var agent = new https.Agent({})
var request = require('grant/lib/client.js')
require.cache[require.resolve('grant/lib/client.js')].exports = (options) => {
options.agent = agent
return request(options)
} Just make sure you execute this code after Grant has been required. The Let me know what do you think! |
Thx I will give it a go when available, a bit busy lately |
👋 Just released v5.2.0 with support for request options, take a look at the examples as well. |
Hi @simov , I am using @feathersjs/authentication-oauth (v 4.5.6) which uses this library. Unfortunately, it still uses version 4.7.0 and hasn't been updated to version 5. I've tried the Monkey Patch code and I can't get it to work no matter what I try. I feel that since 2018 when this was written, the library has changed enough that the patch shown isn't compatible. Is there a tweak I can do to this example to get it to work with version 4.7 (since it's embedded in FeathersJS)? I've spent several hours trying many different options. Thanks |
I'm pretty sure nothing changed in Grant regarding the way the internal HTTP client is being exposed. Can you provide a short code example about initializing FeathersJS? |
@simov, Here are the steps to create a full Feathersjs server with Microsoft Azure OAuth (on Ubuntu 20.04). This shows the code I tried to add to monkey patch the options variable that didn't work.
"local": {
"usernameField": "email",
"passwordField": "password"
},
"oauth": {
"redirect": "/",
"microsoft": {
"activeDirectoryGroups": [],
"subdomain": "MICROSOFT_DIR_TENANT_ID",
"key": "MICROSOFT_APP_CLIENT_ID",
"secret": "MICROSOFT_CLIENT_SECRET",
"authorize_url": "https://login.microsoftonline.com/[subdomain]/oauth2/v2.0/authorize",
"access_url": "https://login.microsoftonline.com/[subdomain]/oauth2/v2.0/token",
"profile_url": "https://graph.microsoft.com/v1.0/me",
"memberof_url": "https://graph.microsoft.com/v1.0/me/memberOf?$select=displayName",
"custom_params": {
"response_type": "code",
"response_mode": "query",
"grant_type": "authorization_code"
},
"scope": [
"https://graph.microsoft.com/offline_access",
"https://graph.microsoft.com/openid",
"https://graph.microsoft.com/profile",
"https://graph.microsoft.com/email"
],
"state": true,
"nonce": true
}
}
const { AuthenticationService, JWTStrategy } = require('@feathersjs/authentication');
const { LocalStrategy } = require('@feathersjs/authentication-local');
const { expressOauth } = require('@feathersjs/authentication-oauth');
const { AzureADStrategy } = require('./authentication-azure.js');
module.exports = app => {
const authentication = new AuthenticationService(app);
authentication.register('jwt', new JWTStrategy());
authentication.register('local', new LocalStrategy());
authentication.register('microsoft', new AzureADStrategy());
app.use('/authentication', authentication);
app.configure(expressOauth());
};
const { OAuthStrategy } = require('@feathersjs/authentication-oauth');
const axios = require('axios');
const feathersErrors = require('@feathersjs/errors');
// https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/12345678-1234-1234-12345678901234567/isMSAApp/
// https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols
// HTTP/HTTPS proxy to connect to
var proxyAddr = process.env.http_proxy || 'http://localhost:8009';
console.log('using proxy server ', proxyAddr);
// https://github.com/simov/grant/issues/93
var https = require('https');
const moduleName = 'grant/lib/client.js';
var request = require(moduleName);
var agent = new https.Agent({});
const fooa = require.cache[require.resolve(moduleName)]; //eslint-disable-line
console.log(fooa);
require.cache[require.resolve(moduleName)].exports = (options) => {
options.agent = agent;
return request(options);
};
const foob = require.cache[require.resolve(moduleName)]; //eslint-disable-line
console.dir(foob);
class AzureADStrategy extends OAuthStrategy {
async getEntityData(profile) {
const baseData = await super.getEntityData(profile);
console.dir(baseData);
console.dir(profile);
return {
...baseData,
email: profile.mail,
firstName: profile.givenName,
lastName: profile.surname,
displayName: profile.displayName,
};
}
// https://docs.microsoft.com/en-us/graph/api/user-list-memberof?view=graph-rest-1.0&tabs=http
async getMemberOfGroups(data, params) {
const url = this.configuration.memberof_url || '';
if (!url) {
throw new feathersErrors.GeneralError(
'Missing memberof_url in configuration'
);
}
if (params.authenticated === true && data.access_token) {
try {
// fetch data from a url endpoint
const memberInfo = await axios.get(url, {
headers: {
'user-agent': 'feathers-azure-authentication',
Accept: 'application/json',
Authorization: `Bearer ${data.access_token}`,
},
});
return memberInfo.data || {};
} catch (error) {
throw new feathersErrors.GeneralError(error);
}
} else {
throw new feathersErrors.NotAuthenticated(
'Not authorized - missing access_token'
);
}
}
async getProfile(data, params) {
const profile = await super.getProfile(data, params);
const memberOf = await this.getMemberOfGroups(data, params);
const ADGroups = memberOf.value || [];
// If there was a list of AD groups to check for
let groupFound = true;
if (
// Get allowed list from default.json
this.configuration.activeDirectoryGroups &&
this.configuration.activeDirectoryGroups.length > 0
) {
groupFound = this.configuration.activeDirectoryGroups.some(
(groupToCheckFor) => {
return ADGroups.some((adGroup) => {
return adGroup.displayName === groupToCheckFor; // Look for an exact match (Case Sensitive).
});
}
);
}
if (!groupFound) {
throw new feathersErrors.NotAuthenticated(
'User must be a member of one of the valid Azure Active Directory groups for this API set'
);
}
const retval = {
...profile,
memberOf: {
...memberOf,
},
};
return retval;
}
}
exports.AzureADStrategy = AzureADStrategy; And, if you want to test with Microsoft env vars, a quick hack is to modify the run script in "scripts": {
"test": "npm run lint && npm run mocha",
"lint": "eslint src/. test/. --config .eslintrc.json --fix",
"dev": "MICROSOFT_DIR_TENANT_ID=11111111-2222-3333-4444-555555555555 MICROSOFT_APP_CLIENT_ID=11111111-2222-3333-4444-555555555555 MICROSOFT_CLIENT_SECRET=12345678901234567890123456789012 nodemon src/",
"start": "node src/",
"mocha": "mocha test/ --recursive --exit"
}, Now run the server with I tried all sorts of different ways to get ahold of the options variable so I could change the proxy but nothing I tried worked. |
Thanks for the detailed instructions @nathanbrizzee, I really appreciate the effort you put into this. The monkey patch have to be applied after initializing Grant, because it have to be loaded in the NPM cache in order to patch it, but the patch itself have to be loaded before any other code that relies on Grant, and therefore may potentially initialize and cache some state on their own. Just to be safe I decided to put the patch at the top of // src/index.js
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0
var tunnel = require('tunnel')
var agent = tunnel.httpsOverHttp({
proxy: {host: 'localhost', port: 8009}
})
require('grant')
var request = require('grant/lib/client.js')
require.cache[require.resolve('grant/lib/client.js')].exports = (options) => {
options.agent = agent
return request(options)
}
/* eslint-disable no-console */
const logger = require('./logger')
const app = require('./app')
const port = app.get('port')
const server = app.listen(port)
process.on('unhandledRejection', (reason, p) =>
logger.error('Unhandled Rejection at: Promise ', p, reason)
)
server.on('listening', () =>
logger.info('Feathers application started on http://%s:%d', app.get('host'), port)
) I tested with a local mitm-proxy that I have and it works. You can also have a look at the examples on alternative agents that you can use. |
Hi @simov , Thanks for the write-up! I copied your code into index.js as you showed. It worked for the first two login send.js calls (login.microsoftonline.com) but the third call (graph.microsoft.com) didn't receive the patch (in send.js) so it timed out on the call. I tried if a few times debugging along the way. Not sure why the first two calls get patched and the third call doesn't get patched. I then added a new file called proxy.js in the same folder as index.js and moved the code there so I could import it elsewhere (for an axios call). Here's a copy of that code. const url = require('url');
const tunnel = require('tunnel');
const proxy = {
agent: null,
proxyAddr: process.env.https_proxy || process.env.http_proxy || ''
};
if (proxy.proxyAddr) {
// process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
const q = url.parse(proxy.proxyAddr, true);
const proxyInfo = {
proxy: {
host: q.hostname,
port: q.port,
headers: {
'User-Agent': 'FeathersJS'
}
}
};
if (q.auth) {
proxyInfo.proxy.proxyAuth = q.auth;
}
proxy.agent = tunnel.httpsOverHttp(proxyInfo);
require('grant');
const request = require('grant/lib/client.js');
require.cache[require.resolve('grant/lib/client.js')].exports = (options) => {
options.agent = proxy.agent;
return request(options);
};
}
module.exports = proxy; Then in index.js I added the require as the first line which should accomplish the same thing as your example code. // src/index.js
/* eslint-disable no-console */
// Proxy must be the first line to monkey patch the Grant library
const proxy = require('./proxy'); // eslint-disable-line
const logger = require('./logger');
const app = require('./app');
const port = app.get('port') || app.get('serverport');
const server = app.listen(port);
process.on('unhandledRejection', (reason, p) => {
... In authentication-azure.js, I added some code for adding the proxy to axios, but I never reach that code because of the timeout on the graph api call earlier. const { OAuthStrategy } = require('@feathersjs/authentication-oauth');
const ax = require('axios').default;
const feathersErrors = require('@feathersjs/errors');
const proxy = require('./proxy'); // eslint-disable-line
const config = { };
if (proxy.agent !== null) {
config.httpsAgent = proxy.agent;
config.httpAgent = proxy.agent;
}
const axios = ax.create(config);
// https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/c0cf535a-bb4d-4731-94fb-8a4165b1a124/isMSAApp/
class AzureADStrategy extends OAuthStrategy {
async getEntityData(profile) {
... My other solution that is working is to clone the request-compose package into a local sub folder of my Feathersjs project and then modify send.js to add the proxy agent to the options variable and do a local npm install into my feathersjs project. That always works but now I have code that has been forked from the main repo. |
I see, I totally forgot about the grant-profile request. Unfortunately that's going to be impossible to monkey patch because grant-profile wraps the call to the request-compose Patching request-compose |
Since
purest
allows for it, I would love to get the same support forgrant
. That would allow me to control the whole oauth + user profile fetching flow.Namely, I want to use this with a proxy agent like: https://github.com/TooTallNate/node-proxy-agent
This is necessary as nodejs core still haven't expose an overridable
http.globalAgent
api yet, sorry for nagging you on this: nodejs/node#23281The text was updated successfully, but these errors were encountered: