fix: Improve validation of Bot Tokens (#1206)

* improve bot token validation by trying to decode user id from token

Try to decode the user id from the supplied bot token as a way of validating the token. If this should fail, indicate that the token is invalid.

* Update the tokenutils tests to pass the new validation checks

* Add test case for CheckBotTokenValidity method

* lint: clean up whitespace

* Add check for null or whitespace string, lint whitespace

* fix userid conversion

* Add hint to user to check that token is not an oauth client secret

* Catch exception that can be thrown by GetString

* Refactor token conversion logic into it's own testable method
This commit is contained in:
Chris Johnston
2018-12-02 10:03:12 -08:00
committed by Christopher F
parent 91e0f03bfd
commit f4b1a5f25b
2 changed files with 108 additions and 4 deletions

View File

@@ -1,4 +1,5 @@
using System;
using System.Text;
namespace Discord
{
@@ -16,6 +17,65 @@ namespace Discord
/// </remarks>
internal const int MinBotTokenLength = 58;
/// <summary>
/// Decodes a base 64 encoded string into a ulong value.
/// </summary>
/// <param name="encoded"> A base 64 encoded string containing a User Id.</param>
/// <returns> A ulong containing the decoded value of the string, or null if the value was invalid. </returns>
internal static ulong? DecodeBase64UserId(string encoded)
{
if (string.IsNullOrWhiteSpace(encoded))
return null;
try
{
// decode the base64 string
var bytes = Convert.FromBase64String(encoded);
var idStr = Encoding.UTF8.GetString(bytes);
// try to parse a ulong from the resulting string
if (ulong.TryParse(idStr, out var id))
return id;
}
catch (DecoderFallbackException)
{
// ignore exception, can be thrown by GetString
}
catch (FormatException)
{
// ignore exception, can be thrown if base64 string is invalid
}
catch (ArgumentException)
{
// ignore exception, can be thrown by BitConverter
}
return null;
}
/// <summary>
/// Checks the validity of a bot token by attempting to decode a ulong userid
/// from the bot token.
/// </summary>
/// <param name="message">
/// The bot token to validate.
/// </param>
/// <returns>
/// True if the bot token was valid, false if it was not.
/// </returns>
internal static bool CheckBotTokenValidity(string message)
{
if (string.IsNullOrWhiteSpace(message))
return false;
// split each component of the JWT
var segments = message.Split('.');
// ensure that there are three parts
if (segments.Length != 3)
return false;
// return true if the user id could be determined
return DecodeBase64UserId(segments[0]).HasValue;
}
/// <summary>
/// Checks the validity of the supplied token of a specific type.
/// </summary>
@@ -42,13 +102,17 @@ namespace Discord
// this value was determined by referencing examples in the discord documentation, and by comparing with
// pre-existing tokens
if (token.Length < MinBotTokenLength)
throw new ArgumentException(message: $"A Bot token must be at least {MinBotTokenLength} characters in length.", paramName: nameof(token));
throw new ArgumentException(message: $"A Bot token must be at least {MinBotTokenLength} characters in length. " +
"Ensure that the Bot Token provided is not an OAuth client secret.", paramName: nameof(token));
// check the validity of the bot token by decoding the ulong userid from the jwt
if (!CheckBotTokenValidity(token))
throw new ArgumentException(message: "The Bot token was invalid. " +
"Ensure that the Bot Token provided is not an OAuth client secret.", paramName: nameof(token));
break;
default:
// All unrecognized TokenTypes (including User tokens) are considered to be invalid.
throw new ArgumentException(message: "Unrecognized TokenType.", paramName: nameof(token));
}
}
}
}