-
Notifications
You must be signed in to change notification settings - Fork 58
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
Add callback to NatsAuthOpts that allows refreshing a Token #712
base: main
Are you sure you want to change the base?
Conversation
7bf2f6f
to
c85fb33
Compare
@mtmk are you the right person to review this? Or is there someone else that might be good to reach out to? Thanks! |
@@ -31,6 +32,8 @@ public UserCredentials(NatsAuthOpts authOpts) | |||
|
|||
public string? Token { get; } | |||
|
|||
public Func<Task<string>>? TokenHandler { get; } |
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.
to be consistent with other callbacks should we make this ValueTask
. Can't remember if we also pass cancellation token. Also what about JWT? would that benefit from a callback like this?
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 modified my initial commit to be a ValueTask
rather than a Task
: 21c6654
From what I can tell in other places, it doesn't appear that a cancellation token is passed.
In 58864dc, I added callbacks for JWT, NKey, and seed. Based on my understanding, if the JWT is being refreshed, the seed also needs to be refreshed. And the same goes with NKeys. I added unit tests to show that the callbacks will take precedence over any raw values that are set on initialization of NatsAuthOpts.
@garrett-sutton could you sign your commits please? |
0bdb854
to
fb423fb
Compare
Done. As a note, this should probably be called out in |
public string? Token { get; } | ||
|
||
public string? Sign(string? nonce) | ||
public Func<ValueTask<string>>? TokenHandler { get; } |
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.
API usability point: should there be a single callback rather than individual ones? would there be a case where application might want to change its auth method? should we also pass in the host, is that helpful in any way?
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.
+1 for single callback on this....
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 used your latest suggestion in the other thread in beedf42 to consolidate things into a single callback.
I ended up calling the new structure a NatsAuthCred
and largely chose that because you mentioned you weren't thrilled about the idea of NatsAuthKey
. I'm open to other ideas on it though.
The most recent options callback I reviewed did supply a URI and a CancellationToken as an argument nats.net/src/NATS.Client.Core/NatsWebSocketOpts.cs Lines 24 to 28 in caa4bef
Sounds like a good idea, a signature could be In Func<Uri, CancellationToken, ValueTask<NatsAuthOpts>? Callback { get; init; } = null; This would have to come with the caveat that a Callback could not return another Or in Func<Uri, CancellationToken, ValueTask<NatsAuthOpts>? AuthOptsCallback { get; init; } = null; Could also be a slipper slope though, what other options could be updated between reconnects? And which ones could be updated after knowing the URI that will be connected to? Is it worth putting just the auth ones in now, or coming up with a broader approach to allow for updating all potential options |
Yes we have started to pass in CT in our callbacks only recently so we should carry on with that. I agree passing back the whole NatsAuthOpts is tricky. I was thinking of a simpler enum with a new type: enum NatsAuthType { Token, Jwt, NKey, Seed }
struct NatsAuthKey(NatsAuthType type, string Value)
Func<Uri, CancellationToken, ValueTask<NatsAuthKey>? AuthKeyCallback { get; init; } = null; I feel name |
I like this approach. One follow-up question though. For using something like this, do we actually need to return an array of NatsAuthKey from the callback? I expect that if the JWT or the NKey change that the Seed should also change. Is that correct? If so, I think we need to provide a way for implementers to specify that multiple things need to be updated. |
you're right. we'd need to pass seed/secret next to value. how about this? enum NatsAuthType { Token, Jwt, NKey, UserInfo }
struct NatsAuthKey(NatsAuthType type, string Value, string Secret) |
I think the combination of things they may want to supply is:
Too bad TypeUnions aren't in C# yet, it'd be nice to have records for
|
fb423fb
to
6080576
Compare
case NatsAuthType.Nkey: | ||
opts.NKey = a.Value; | ||
seed = a.Seed; | ||
break; |
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.
NKeys and Seeds are a pair. It was probably a mistake in the initial implementation to require passing both, as the NKey can be derived from the seed. I guess it acts as a discriminator so we can tell it apart from JWT + Seed
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.
In the latest, I used the "add a seed" and we can derive the nkey from it approach: 21c5e74
opts.JWT = a.Value; | ||
seed = a.Seed; | ||
break; |
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.
JWTs and Seeds are a pair, the Sub in the JWT is the public NKey for the seed
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.
In the static factory methods, I implemented it such that you need to input the jwt and seed for jwt auth but only the seed for nkey auth: 21c5e74
that's good analysis! enum NatsAuthType { None, Token, Jwt, UserInfo, CredFile .... }
struct NatsAuthenticator
{
NatsAuthType _type;
string? _value;
string? _secret;
private NatsAuthenticator(NatsAuthType, string?, string?) { ... }
public static NatsAuthenticator NoAuth() { ... }
public static NatsAuthenticator UsernamePassword(string Username, string Password) { ... }
public static NatsAuthenticator Token(string Token) { ... }
public static NatsAuthenticator Jwt(string Jwt, string Seed) { ... }
public static NatsAuthenticator Jwt(string Jwt) { ... } // maybe?
...
} EDIT: actually just scanned through the changes quick. I feel |
string? seed = null; | ||
if (AuthCredCallback != null) | ||
{ | ||
using var cts = new CancellationTokenSource(timeout); |
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.
We should take in CT from above as param and use it here. if we need the timeout we should link the tokens. The idea is if we're e.g. shutting down callbacks should get the signal as well and quit whatever they might be doing and not hanging.
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.
A question about this. I don't necessarily object to this comment, but I'm not sure of whether 1) we need the timeout and if we do 2) what token to link it to. 3) Otherwise, I'm not sure if it is just OK to pass in CancellationToken.None
to this method?
Do you have thoughts on these points?
I took the current approach because it seems to be what other AuthenticateAsync type methods did (i.e. sslConnection.AuthenticateAsClientAsync
src/NATS.Client.Core/NatsAuthOpts.cs
Outdated
Nkey, | ||
} | ||
|
||
public struct NatsAuthCred(NatsAuthType type, string value, string seed) |
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.
We should use a private .ctor and hide the fields. We can expose static factory methods as public only to minimize the API surface not exposing internals.
edit: also I'm guessing it can be a readonly struct
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.
This is done in 21c5e74
Add factory methods for NatsAuthCred
6080576
to
8851ab5
Compare
Adds the following to
NatsAuthOpts
Func<Uri, CancellationToken, ValueTask<NatsAuthCred>>? AuthCredCallback
The purpose of this PR is to allow for use cases to refresh a Token, JWT, or NKey associated with their NatsConnection during a reconnect scenario.
resolves #356