From 96efc97fc1cae1db1cdf242440b260142520ba9a Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 13 May 2016 02:54:58 -0300 Subject: [PATCH] Added user login, switching users/tokens, and token validation --- src/Discord.Net/API/DiscordRawClient.cs | 57 ++++++++++------ src/Discord.Net/API/Rest/LoginParams.cs | 12 ++++ src/Discord.Net/API/Rest/LoginResponse.cs | 10 +++ src/Discord.Net/Discord.Net.csproj | 2 + src/Discord.Net/IDiscordClient.cs | 3 +- src/Discord.Net/Rest/DiscordClient.cs | 66 +++++++++++++------ .../Rest/Entities/Users/GuildUser.cs | 5 +- 7 files changed, 110 insertions(+), 45 deletions(-) create mode 100644 src/Discord.Net/API/Rest/LoginParams.cs create mode 100644 src/Discord.Net/API/Rest/LoginResponse.cs diff --git a/src/Discord.Net/API/DiscordRawClient.cs b/src/Discord.Net/API/DiscordRawClient.cs index c01696448..31e4701a9 100644 --- a/src/Discord.Net/API/DiscordRawClient.cs +++ b/src/Discord.Net/API/DiscordRawClient.cs @@ -29,29 +29,13 @@ namespace Discord.API public TokenType AuthTokenType { get; private set; } public IRestClient RestClient { get; private set; } public IRequestQueue RequestQueue { get; private set; } - - internal DiscordRawClient(RestClientProvider restClientProvider, CancellationToken cancelToken, TokenType authTokenType, string authToken) + + internal DiscordRawClient(RestClientProvider restClientProvider, CancellationToken cancelToken) { _cancelToken = cancelToken; - AuthTokenType = authTokenType; - - switch (authTokenType) - { - case TokenType.Bot: - authToken = $"Bot {authToken}"; - break; - case TokenType.Bearer: - authToken = $"Bearer {authToken}"; - break; - case TokenType.User: - break; - default: - throw new ArgumentException("Unknown oauth token type", nameof(authTokenType)); - } _restClient = restClientProvider(DiscordConfig.ClientAPIUrl, cancelToken); _restClient.SetHeader("accept", "*/*"); - _restClient.SetHeader("authorization", authToken); _restClient.SetHeader("user-agent", DiscordConfig.UserAgent); _requestQueue = new RequestQueue(_restClient); @@ -69,6 +53,27 @@ namespace Discord.API _serializer.ContractResolver = new OptionalContractResolver(); } + public void SetToken(TokenType tokenType, string token) + { + AuthTokenType = tokenType; + + switch (tokenType) + { + case TokenType.Bot: + token = $"Bot {token}"; + break; + case TokenType.Bearer: + token = $"Bearer {token}"; + break; + case TokenType.User: + break; + default: + throw new ArgumentException("Unknown oauth token type", nameof(tokenType)); + } + + _restClient.SetHeader("authorization", token); + } + //Core public Task Send(string method, string endpoint, GlobalBucket bucket = GlobalBucket.General) => SendInternal(method, endpoint, null, true, bucket); @@ -122,7 +127,7 @@ namespace Discord.API stopwatch.Stop(); double milliseconds = ToMilliseconds(stopwatch); - SentRequest(this, new SentRequestEventArgs(method, endpoint, bytes, milliseconds)); + SentRequest?.Invoke(this, new SentRequestEventArgs(method, endpoint, bytes, milliseconds)); return responseStream; } @@ -134,11 +139,23 @@ namespace Discord.API stopwatch.Stop(); double milliseconds = ToMilliseconds(stopwatch); - SentRequest(this, new SentRequestEventArgs(method, endpoint, bytes, milliseconds)); + SentRequest?.Invoke(this, new SentRequestEventArgs(method, endpoint, bytes, milliseconds)); return responseStream; } + + //Auth + public async Task Login(LoginParams args) + { + var response = await Send("POST", "auth/login", args).ConfigureAwait(false); + SetToken(TokenType.User, response.Token); + } + public async Task ValidateToken() + { + await Send("GET", "auth/login").ConfigureAwait(false); + } + //Gateway public async Task GetGateway() { diff --git a/src/Discord.Net/API/Rest/LoginParams.cs b/src/Discord.Net/API/Rest/LoginParams.cs new file mode 100644 index 000000000..c5d028063 --- /dev/null +++ b/src/Discord.Net/API/Rest/LoginParams.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + public class LoginParams + { + [JsonProperty("email")] + public string Email { get; set; } + [JsonProperty("password")] + public string Password { get; set; } + } +} diff --git a/src/Discord.Net/API/Rest/LoginResponse.cs b/src/Discord.Net/API/Rest/LoginResponse.cs new file mode 100644 index 000000000..2d566612d --- /dev/null +++ b/src/Discord.Net/API/Rest/LoginResponse.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + public class LoginResponse + { + [JsonProperty("token")] + public string Token { get; set; } + } +} diff --git a/src/Discord.Net/Discord.Net.csproj b/src/Discord.Net/Discord.Net.csproj index b178e48e5..48f2a4928 100644 --- a/src/Discord.Net/Discord.Net.csproj +++ b/src/Discord.Net/Discord.Net.csproj @@ -70,6 +70,8 @@ + + diff --git a/src/Discord.Net/IDiscordClient.cs b/src/Discord.Net/IDiscordClient.cs index b78fb02c6..4d12632cd 100644 --- a/src/Discord.Net/IDiscordClient.cs +++ b/src/Discord.Net/IDiscordClient.cs @@ -14,7 +14,8 @@ namespace Discord IRestClient RestClient { get; } IRequestQueue RequestQueue { get; } - Task Login(TokenType tokenType, string token); + Task Login(string email, string password); + Task Login(TokenType tokenType, string token, bool validateToken = true); Task Logout(); Task GetChannel(ulong id); diff --git a/src/Discord.Net/Rest/DiscordClient.cs b/src/Discord.Net/Rest/DiscordClient.cs index 9a6e0cb36..9ad3fe10a 100644 --- a/src/Discord.Net/Rest/DiscordClient.cs +++ b/src/Discord.Net/Rest/DiscordClient.cs @@ -47,43 +47,64 @@ namespace Discord.Rest _log.Message += (s,e) => Log.Raise(this, e); } - public async Task Login(TokenType tokenType, string token) + public async Task Login(string email, string password) { await _connectionLock.WaitAsync().ConfigureAwait(false); try { - await LoginInternal(tokenType, token).ConfigureAwait(false); + await LoginInternal(email, password).ConfigureAwait(false); } finally { _connectionLock.Release(); } } - private async Task LoginInternal(TokenType tokenType, string token) + public async Task Login(TokenType tokenType, string token, bool validateToken = true) + { + await _connectionLock.WaitAsync().ConfigureAwait(false); + try + { + await LoginInternal(tokenType, token, validateToken).ConfigureAwait(false); + } + finally { _connectionLock.Release(); } + } + private async Task LoginInternal(string email, string password) { if (IsLoggedIn) LogoutInternal(); - try { var cancelTokenSource = new CancellationTokenSource(); - - BaseClient = new API.DiscordRawClient(_restClientProvider, cancelTokenSource.Token, tokenType, token); - BaseClient.SentRequest += (s, e) => _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms"); + BaseClient = new API.DiscordRawClient(_restClientProvider, cancelTokenSource.Token); - //MessageQueue = new MessageQueue(RestClient, _restLogger); - //await MessageQueue.Start(_cancelTokenSource.Token).ConfigureAwait(false); + var args = new LoginParams { Email = email, Password = password }; + await BaseClient.Login(args).ConfigureAwait(false); + await CompleteLogin(cancelTokenSource, false).ConfigureAwait(false); + } + catch { LogoutInternal(); throw; } + } + private async Task LoginInternal(TokenType tokenType, string token, bool validateToken) + { + if (IsLoggedIn) + LogoutInternal(); + try + { + var cancelTokenSource = new CancellationTokenSource(); + BaseClient = new API.DiscordRawClient(_restClientProvider, cancelTokenSource.Token); - try - { - var currentUser = await BaseClient.GetCurrentUser().ConfigureAwait(false); - _currentUser = new SelfUser(this, currentUser); - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized && tokenType == TokenType.Bearer) { } //Ignore 401 if Bearer doesnt have identity - - _cancelTokenSource = cancelTokenSource; - IsLoggedIn = true; - LoggedIn.Raise(this); + BaseClient.SetToken(tokenType, token); + await CompleteLogin(cancelTokenSource, validateToken).ConfigureAwait(false); } catch { LogoutInternal(); throw; } } + private async Task CompleteLogin(CancellationTokenSource cancelTokenSource, bool validateToken) + { + BaseClient.SentRequest += (s, e) => _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms"); + + if (validateToken) + await BaseClient.ValidateToken().ConfigureAwait(false); + + _cancelTokenSource = cancelTokenSource; + IsLoggedIn = true; + LoggedIn.Raise(this); + } public async Task Logout() { @@ -99,9 +120,14 @@ namespace Discord.Rest { bool wasLoggedIn = IsLoggedIn; - try { _cancelTokenSource.Cancel(false); } catch { } + if (_cancelTokenSource != null) + { + try { _cancelTokenSource.Cancel(false); } + catch { } + } BaseClient = null; + _currentUser = null; if (wasLoggedIn) { diff --git a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs b/src/Discord.Net/Rest/Entities/Users/GuildUser.cs index 2e2b11c5c..c27c06892 100644 --- a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs +++ b/src/Discord.Net/Rest/Entities/Users/GuildUser.cs @@ -87,10 +87,7 @@ namespace Discord.Rest bool isCurrentUser = (await Discord.GetCurrentUser().ConfigureAwait(false)).Id == Id; if (isCurrentUser && args.Nickname.IsSpecified) { - var nickArgs = new ModifyCurrentUserNickParams - { - Nickname = args.Nickname.Value - }; + var nickArgs = new ModifyCurrentUserNickParams { Nickname = args.Nickname.Value }; await Discord.BaseClient.ModifyCurrentUserNick(Guild.Id, nickArgs).ConfigureAwait(false); args.Nickname = new API.Optional(); //Remove }