-
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! |
@garrett-sutton could you sign your commits please? |
0bdb854
to
fb423fb
Compare
Done. As a note, this should probably be called out in |
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
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.
You're right we haven't been consistent across the board:
// None
public Func<SslClientAuthenticationOptions, ValueTask>? ConfigureClientAuthentication { get; init; }
// Uses CTS with Connection timeout - but not passing in dispose token from connection object
public Func<Uri, ClientWebSocketOptions, CancellationToken, ValueTask>? ConfigureClientWebSocketOptions { get; init; }
I think we should start using the CT from connection dispose to have a clean shutdown. Should we pass _disposedCancellationTokenSource
into Authenticate here:
await _userCredentials.AuthenticateAsync(_clientOpts, WritableServerInfo, _currentConnectUri, Opts.ConnectTimeout, _disposedCancellationTokenSource.Token).ConfigureAwait(false);
then we can link them before passing to the callback here e.g.:
using var cts = new CancellationTokenSource(timeout);
#if NETSTANDARD
using var ctr = cancellationToken.Register(static state => ((CancellationTokenSource)state!).Cancel(), cts);
#else
await using var ctr = cancellationToken.UnsafeRegister(static state => ((CancellationTokenSource)state!).Cancel(), cts);
#endif
var authCred = await AuthCredCallback(uri.Uri, cts.Token).ConfigureAwait(false);
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.
Ok, that makes sense! Thank you for the help on this. Those changes are reflecting in the most recent update of the commit with functional changes: 9088040
6080576
to
8851ab5
Compare
8851ab5
to
a093653
Compare
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 like how the API turned out!
public string? Username { get; set; } = null; | ||
|
||
/// <summary>Connection password (if auth_required is set)</summary> | ||
[JsonPropertyName("pass")] | ||
public string? Password { get; init; } = null; | ||
public string? Password { get; set; } = null; |
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.
Can these two be switched back to init?
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 believe they need to have setters because without them the callback can't allow for a client to specify a new username and password (i.e. we are passing in an existing instance of ClientOpts
to the AuthenticateAsync method that invokes the new callback).
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.
yes as @garrett-sutton said they are being set here
We should probably make ClientOpts
a readonly record all together and return a new ClientOpts
object from the auth method. that may be too much change for this PR we can follow up.
@mtmk at this point, do you have a feeling on how close we are to approval/merging? And as a follow-up, how long can I expect until a new release is cut with these changes? |
@garrett-sutton I'd like to allow a few days for others to have a chance to review and comment. All being well, I'd say we can merge and release early next week. |
@mtmk sounds good. I'm looking to utilize the new feature in a new project at my org that we're trying to release soon. I just wanted to check in on where we are at. Thanks! |
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.
sorry @garrett-sutton a few last minute changes from me nothing too big hopefully.
src/NATS.Client.Core/NatsAuthOpts.cs
Outdated
|
||
internal string? Secret { get; } | ||
|
||
public static NatsAuthCred NoAuth() => new(NatsAuthType.None, string.Empty, string.Empty); |
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 remove this. to me it's not well defined what it's for and I don't believe there is a known use case for it now. I suggest we take it out to avoid confusion unless I'm missing something (sorry I may have suggested it, not sure).
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 think the use case was you had auth before, but you don't now. Or you have a pool of servers, some with auth and some without. So when you see the Url that is being connected to, you can specify no auth if you would like.
I believe this could be a public static readonly NatsAuthCred None
though if you like that better
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.
agreed. I removed this in the latest iteration of the functional commit: 333c03b
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.
@caleblloyd Do you have a sense for how likely that workflow could be? I'm torn here that yes, this is technically possible but it seems much less likely than the other workflows. We're also not taking away someone's ability to connect w/o auth.
Curious to hear your thoughts.
Also, impeccable timing on your comment and mine.
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 don't think switching auth methods is all that common, but I do like how this Struct represents the Union of all available Auth types. I could see us using it in the future to construct a NatsAuthOpts
, so it'd be nice to have the NoAuth
option. Also since it's a Struct that does represent an accurate default for an empty struct that is declared. (well it did when the NatsAuthCred
enum had None
as the default 😄
src/NATS.Client.Core/NatsAuthOpts.cs
Outdated
@@ -1,5 +1,47 @@ | |||
namespace NATS.Client.Core; | |||
|
|||
public enum NatsAuthType |
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.
could this be made internal?
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.
changed in the latest iteration of the functional commit: 333c03b
src/NATS.Client.Core/NatsAuthOpts.cs
Outdated
@@ -1,5 +1,47 @@ | |||
namespace NATS.Client.Core; | |||
|
|||
public enum NatsAuthType | |||
{ | |||
None, |
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 remove this too as per NoAuth comment elsewhere.
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.
removed in latest iteration of functional commit: 333c03b
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.
Not sure if removing this is a good idea, since NatsAuthCred
is a struct if an empty struct is declared, it's going to have the default value of this enum. The None
value makes sense there.
src/NATS.Client.Core/NatsAuthOpts.cs
Outdated
|
||
public static NatsAuthCred NoAuth() => new(NatsAuthType.None, string.Empty, string.Empty); | ||
|
||
public static NatsAuthCred UserInfo(string username, string password) |
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.
Can we call these methods FromUserInfo(), FromToken(), ...
? I think that would be more inline with other APIs like TimeSpan
.
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 did this in the latest iteration of the commit with functional changes: 333c03b
Add factory methods for NatsAuthCred
a093653
to
333c03b
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