diff --git a/src/Discord.Net.Core/Utils/TokenUtils.cs b/src/Discord.Net.Core/Utils/TokenUtils.cs
new file mode 100644
index 000000000..2cc0f1041
--- /dev/null
+++ b/src/Discord.Net.Core/Utils/TokenUtils.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ public static class TokenUtils
+ {
+ ///
+ /// Checks the validity of the supplied token of a specific type.
+ ///
+ /// The type of token to validate.
+ /// The token value to validate.
+ /// Thrown when the supplied token string is null, empty, or contains only whitespace.
+ /// Thrown when the supplied TokenType or token value is invalid.
+ public static void ValidateToken(TokenType tokenType, string token)
+ {
+ // A Null or WhiteSpace token of any type is invalid.
+ if (string.IsNullOrWhiteSpace(token))
+ throw new ArgumentNullException("A token cannot be null, empty, or contain only whitespace.", nameof(token));
+
+ switch (tokenType)
+ {
+ case TokenType.Webhook:
+ // no validation is performed on Webhook tokens
+ break;
+ case TokenType.Bearer:
+ // no validation is performed on Bearer tokens
+ break;
+ case TokenType.Bot:
+ // bot tokens are assumed to be at least 59 characters in length
+ // this value was determined by referencing examples in the discord documentation, and by comparing with
+ // pre-existing tokens
+ if (token.Length < 59)
+ throw new ArgumentException("A Bot token must be at least 59 characters in length.", nameof(token));
+ break;
+ default:
+ // All unrecognized TokenTypes (including User tokens) are considered to be invalid.
+ throw new ArgumentException("Unrecognized TokenType.", nameof(token));
+ }
+ }
+
+ }
+}
diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs
index f8642b96c..8ac4e9f98 100644
--- a/src/Discord.Net.Rest/BaseDiscordClient.cs
+++ b/src/Discord.Net.Rest/BaseDiscordClient.cs
@@ -55,11 +55,11 @@ namespace Discord.Rest
await _stateLock.WaitAsync().ConfigureAwait(false);
try
{
- await LoginInternalAsync(tokenType, token).ConfigureAwait(false);
+ await LoginInternalAsync(tokenType, token, validateToken).ConfigureAwait(false);
}
finally { _stateLock.Release(); }
}
- private async Task LoginInternalAsync(TokenType tokenType, string token)
+ private async Task LoginInternalAsync(TokenType tokenType, string token, bool validateToken)
{
if (_isFirstLogin)
{
@@ -73,6 +73,21 @@ namespace Discord.Rest
try
{
+ // If token validation is enabled, validate the token and let it throw any ArgumentExceptions
+ // that result from invalid parameters
+ if (validateToken)
+ {
+ try
+ {
+ TokenUtils.ValidateToken(tokenType, token);
+ }
+ catch (ArgumentException ex)
+ {
+ // log these ArgumentExceptions and allow for the client to attempt to log in anyways
+ await LogManager.WarningAsync("Discord", "A supplied token was invalid", ex).ConfigureAwait(false);
+ }
+ }
+
await ApiClient.LoginAsync(tokenType, token).ConfigureAwait(false);
await OnLoginAsync(tokenType, token).ConfigureAwait(false);
LoginState = LoginState.LoggedIn;
diff --git a/test/Discord.Net.Tests/Tests.TokenUtils.cs b/test/Discord.Net.Tests/Tests.TokenUtils.cs
new file mode 100644
index 000000000..dc5a93e34
--- /dev/null
+++ b/test/Discord.Net.Tests/Tests.TokenUtils.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xunit;
+
+namespace Discord
+{
+ public class TokenUtilsTests
+ {
+ ///
+ /// Tests the usage of
+ /// to see that when a null, empty or whitespace-only string is passed as the token,
+ /// it will throw an ArgumentNullException.
+ ///
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")] // string.Empty isn't a constant type
+ [InlineData(" ")]
+ [InlineData(" ")]
+ [InlineData("\t")]
+ public void TestNullOrWhitespaceToken(string token)
+ {
+ // an ArgumentNullException should be thrown, regardless of the TokenType
+ Assert.Throws(() => TokenUtils.ValidateToken(TokenType.Bearer, token));
+ Assert.Throws(() => TokenUtils.ValidateToken(TokenType.Bot, token));
+ Assert.Throws(() => TokenUtils.ValidateToken(TokenType.Webhook, token));
+ }
+
+ ///
+ /// Tests the behavior of
+ /// to see that valid Webhook tokens do not throw Exceptions.
+ ///
+ ///
+ [Theory]
+ [InlineData("123123123")]
+ // bot token
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")]
+ // bearer token taken from discord docs
+ [InlineData("6qrZcUqja7812RVdnEKjpzOL4CvHBFG")]
+ // client secret
+ [InlineData("937it3ow87i4ery69876wqire")]
+ public void TestWebhookTokenDoesNotThrowExceptions(string token)
+ {
+ TokenUtils.ValidateToken(TokenType.Webhook, token);
+ }
+
+ // No tests for invalid webhook token behavior, because there is nothing there yet.
+
+ ///
+ /// Tests the behavior of
+ /// to see that valid Webhook tokens do not throw Exceptions.
+ ///
+ ///
+ [Theory]
+ [InlineData("123123123")]
+ // bot token
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")]
+ // bearer token taken from discord docs
+ [InlineData("6qrZcUqja7812RVdnEKjpzOL4CvHBFG")]
+ // client secret
+ [InlineData("937it3ow87i4ery69876wqire")]
+ public void TestBearerTokenDoesNotThrowExceptions(string token)
+ {
+ TokenUtils.ValidateToken(TokenType.Bearer, token);
+ }
+
+ // No tests for invalid bearer token behavior, because there is nothing there yet.
+
+ ///
+ /// Tests the behavior of
+ /// to see that valid Bot tokens do not throw Exceptions.
+ /// Valid Bot tokens can be strings of length 59 or above.
+ ///
+ [Theory]
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")]
+ [InlineData("This appears to be completely invalid, however the current validation rules are not very strict.")]
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWss")]
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWsMTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")]
+ public void TestBotTokenDoesNotThrowExceptions(string token)
+ {
+ // This example token is pulled from the Discord Docs
+ // https://discordapp.com/developers/docs/reference#authentication-example-bot-token-authorization-header
+ // should not throw any exception
+ TokenUtils.ValidateToken(TokenType.Bot, token);
+ }
+
+ ///
+ /// Tests the usage of with
+ /// a Bot token that is invalid.
+ ///
+ [Theory]
+ [InlineData("This is invalid")]
+ // missing a single character from the end
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKW")]
+ // bearer token
+ [InlineData("6qrZcUqja7812RVdnEKjpzOL4CvHBFG")]
+ // client secret
+ [InlineData("937it3ow87i4ery69876wqire")]
+ public void TestBotTokenInvalidThrowsArgumentException(string token)
+ {
+ Assert.Throws(() => TokenUtils.ValidateToken(TokenType.Bot, token));
+ }
+
+ ///
+ /// Tests the behavior of
+ /// to see that an is thrown when an invalid
+ /// is supplied as a parameter.
+ ///
+ ///
+ /// The type is treated as an invalid .
+ ///
+ [Theory]
+ // TokenType.User
+ [InlineData(0)]
+ // out of range TokenType
+ [InlineData(4)]
+ [InlineData(7)]
+ public void TestUnrecognizedTokenType(int type)
+ {
+ Assert.Throws(() =>
+ TokenUtils.ValidateToken((TokenType)type, "MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs"));
+ }
+ }
+}