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
Refactor basic, bearer, digest auth logic into separate class #1360
Conversation
YESSS! I can't 👍 this enough! |
(I'm away for the long weekend so I can't review but from a quick readthrough it looks good |
} | ||
} | ||
|
||
Auth.prototype._digest = function (authHeader) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
change _digest
to digest
to better match the naming of the other functions
This is excellent stuff @simov. How about getting rid of any underscores in I'm not saying that we should do the following now. But, given that we want to move towards a composable API, what would it take to get there for this PR? I'm thinking that inside require('./lib/auth').extend(Request) What hooks would be necessary in One issue I foresee - I'm sure there are others: say an app has two different use cases for |
Oh and about the |
I removed all occurances of @nylen I'm not sure what's your goal with this line in this case. require('./lib/auth').extend(Request) The idea of the Auth class is to have its own scope and instance to keep the auth related variables outside of Request. The methods in Auth are also meant to be accessed through We can easily export Auth from request.js and then again from index.js so the user would be able to replace the About |
@simov here's the idea: #1094 (comment) Eventually, we would have var request = require('request-base')
, auth = require('request-auth')
auth.extend(request.Request) Some of the benefits of a composable API like this:
My question was to try to get us to think through what those auth hooks would need to look like. We'd need a way to observe Splitting code for features into separate files, like you've done here, is a necessary first step towards this approach, and we need to start pulling apart more features like this into separate layers. So I'll merge this in a couple of days, but please @FredKSchott @seanstrom and anyone else share your thoughts for where we want to go. |
I think we're looking too ahead of us atm, the functions shouldn't be allowed to explode into a couple of hundreds lines of code in the first place. The only way to really move something out of the code and stop thinking about it, is to make it properly encapsulated (of course without sacrificing too much while doing so). The rest is really just a matter of implementation preference in the consumer. Take a look at my last commit. Now the Auth class is fully encapsulated, and it knows nothing about Request or its state, nor the heavily modified Response object state. That means the Auth class potentially can be re-used in different context. Also you can completely ignore it while looking at the Request code, because all that it affects is in the Request code itself (only the implementation is hidden which you certainly don't want to look at all the time). About the plugin approach. There are three completely different things here:
Essentially what the extend approach suggests is this:
But why is that? Request should define it's own public API as a module, then use the hooks internally. Potentially we want to give the user ability to swap the internal implementation on the fly. The extend techiquie implies that all of these custom 'modules' should work with heavily modified request and response instances, how exactly we're going to document that? It's like telling our users (developers/ourselfs) to take a look at a couple of hundreds lines of code in Request + in a few other 'modules' as well Passing the request class in the extend method doesn't solve the problem when that module needs its own state, meaning that it's not just a utility module. So it must be instantiated inside the Request ctor. Also the idea of having separate Auth class is to not pollute the Request state with methods and variables. The We should almost always use a map for arguments, not only that it makes the invocation more readable, but you ca easily add just the needed parameters. request.auth({bearer:'token'}) in place of request.auth(null,null,false,'token') just like it's used in the options object. So we can implement support for a map args there, and leave the current implementation in Request as a backward compatibility. |
Again I am totally satisfied with this PR for now and plan to merge it as-is. Encapsulation is a great first step and you've done a good job here.
Agreed, and I'd like to start keeping a list of desired changes like this for our next major version. I have a couple too.
-1. There is a lot of value in letting people mix and match Here's another example: https://github.com/FrankyBoy/request-ntlm. NTLM auth would be nice to have (for those of us who get to deal with M$ stuff), but I doubt that any of us have time to do it, and that module should really be done as a wrapper around Having an
(ping @FrankyBoy - we're not to this point yet, but we're discussing how to do this at #1094 and here)
true, this is a concern. We will need to beef up our test suite as much as possible going forward.
Agree with both of these points, that's the kind of thing I want to discuss here. Maybe exports.extend = function(Request) {
Request._initHooks.push(function() {
var self = this
self._auth = new Auth()
})
Request.prototype.auth = function(options) {
})
}
function Auth() {
// ...
} |
Monkey patching have never been a problem in js. My whole point was that we should make sure we move as much code as cleanly as possible (capsulated), and then figure out what to do with the rest. Because at the end of the day we're still going to support that code, even though it's in separate So I can live with that extend thing, as long as it doesn't modify the state, at least for those modules that live in the request org. Other than that I'm glad we agree on most of the points here 👍 @nylen Also once this got it, I'll review my previous oauth refactoring PR, as there a few small things that I really don't like, and I'll make a new PR about it too. |
redirectTo = self.uri | ||
break | ||
} else if (response.statusCode === 401) { | ||
var authHeader = self._auth.response(self.method, self.uri.path, response.headers) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of passing the raw headers, you could pass response.caseless
and save yourself a second conversion step from headers -> caseless inside of auth.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I intentionally made it that way, so that the module knows less about the consumer, and it can work with more generic input data parameters.
Also in caseless
that's just an assignment, so I don't think it degrades performance or anything like that.
this.dict = dict || {}
+1 lgtm |
@FredKSchott thanks for your feedback, I added the default values and the comment for the @nylen let me know if I have to squash the commits into one at some point. |
yeah that would be good, if you squash this then I'll merge today. I was hoping some others would weigh in on our discussion but I guess not. |
Done |
@nylen seems like you guys covered most of it :) Monkey patching isn't the answer imo. It might work for one or two plugins, but the conflicting code and assumptions of each plugin will become a nightmare at any sort of practical scale, especially if we see plugins becoming more prevalent once supported. I'd like to see us expose a collection of events or methods that a plugin can hook into. Alternatively, we could actually define a plugin architecture for request to load:
This strategy has the additional benefit of supporting asynchronous plugins as well. |
I'm fine with anything we come up with, but -1 on blessing monkey patching as the official way to augment request. |
Refactor basic, bearer, digest auth logic into separate class
Make debug a prototype method. This has a couple of advantages: - It's no longer necessary for the debug code to be at a specific place in request.js, see request#1354 - It's easier for helper methods in other files to generate debug messages, see request#1360
WIP
I moved the
auth
method logic into separate file. Theauth
method is split into two methodsbasic
andbearer
. Also I moved_digest
and the part of the code inonRequestResponse
related to these 3 methods.The reasons to move exactly these functions is as follows: all private flags
_hasAuth
,_sentAuth
and private variables_bearer
,_user
,_pass
are local for theAuth
class. That wayrequest
now knows only about only 3 distinct methods_auth.basic
,_auth.bearer
and_auth.response
versus 6 variables_hasAuth
,_sentAuth
,_bearer
,_user
,_pass
and_digest
The
Auth
class keeps a pointer to therequest
instance, but only use it to modify the headers, as well as reading some values (as I said private variables related to theAuth
class are not set inrequest
)Other than that the code is mostly copy/pasted without changes, it certainly can be improved but I wanted to make this PR as short as possible. Also the
debug
call is currently commented out.Ping @nylen @seanstrom @FredKSchott