-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Backend support for API Authentication * Stub bcrypt passwords, tell legacy user to set password * Password reset support * Add /user/me, fix auth * Bump Bunkum to v2.1.5 * Bump Bunkum to 2.2.0 * Implement CORS * Add Content-Type to CORS allowed headers I'm gonna be dealing with this forever now, aren't I? * Actual implementation for API logins * Add JoinDate to users * Cache level categories * Use int for TokenType * Remove outdated comments * Regex for verifying password reset * Use Encoding.UTF8 for digest calculation * Combine reset tokens with normal tokens, token expiry
- Loading branch information
Showing
17 changed files
with
420 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace Refresh.GameServer.Authentication; | ||
|
||
public enum TokenType | ||
{ | ||
Game = 0, | ||
Api = 1, | ||
PasswordReset = 2, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
135 changes: 135 additions & 0 deletions
135
Refresh.GameServer/Endpoints/Api/AuthenticationApiEndpoints.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
using System.Net; | ||
using System.Text.RegularExpressions; | ||
using Bunkum.CustomHttpListener.Parsing; | ||
using Bunkum.HttpServer; | ||
using Bunkum.HttpServer.Endpoints; | ||
using Bunkum.HttpServer.Responses; | ||
using Refresh.GameServer.Authentication; | ||
using Refresh.GameServer.Database; | ||
using Refresh.GameServer.Types.UserData; | ||
|
||
namespace Refresh.GameServer.Endpoints.Api; | ||
|
||
public partial class AuthenticationApiEndpoints : EndpointGroup | ||
{ | ||
// How many rounds to do for password hashing (BCrypt) | ||
// 14 is ~1 second for logins and reset, which is fair because logins are a one-time thing | ||
// 200 OK on POST '/api/v2/resetPassword' (1058ms) | ||
// 200 OK on POST '/api/v2/auth' (1087ms) | ||
// | ||
// If increased, passwords will automatically be rehashed at login time to use the new WorkFactor | ||
// If decreased, passwords will stay at higher WorkFactor until reset | ||
private const int WorkFactor = 14; | ||
|
||
[GeneratedRegex("^[a-f0-9]{128}$")] | ||
private static partial Regex Sha512Regex(); | ||
|
||
[ApiEndpoint("auth", Method.Post)] | ||
[Authentication(false)] | ||
public Response Authenticate(RequestContext context, RealmDatabaseContext database, ApiAuthenticationRequest body) | ||
{ | ||
GameUser? user = database.GetUserByUsername(body.Username); | ||
if (user == null) | ||
{ | ||
return new Response(new ApiErrorResponse("The username or password was incorrect."), ContentType.Json, HttpStatusCode.Forbidden); | ||
} | ||
|
||
// if this is a legacy user, have them create a password on login | ||
if (user.PasswordBcrypt == null) | ||
{ | ||
Token resetToken = database.GenerateTokenForUser(user, TokenType.PasswordReset); | ||
|
||
ApiResetPasswordResponse resetResp = new() | ||
{ | ||
Reason = "The account you are trying to sign into is a legacy account. Please set a password.", | ||
ResetToken = resetToken.TokenData, | ||
}; | ||
|
||
return new Response(resetResp, ContentType.Json, HttpStatusCode.Unauthorized); | ||
} | ||
|
||
if (BC.PasswordNeedsRehash(user.PasswordBcrypt, WorkFactor)) | ||
{ | ||
database.SetUserPassword(user, BC.HashPassword(body.PasswordSha512, WorkFactor)); | ||
} | ||
|
||
if (!BC.Verify(body.PasswordSha512, user.PasswordBcrypt)) | ||
{ | ||
return new Response(new ApiErrorResponse("The username or password was incorrect."), ContentType.Json, HttpStatusCode.Forbidden); | ||
} | ||
|
||
Token token = database.GenerateTokenForUser(user, TokenType.Api); | ||
|
||
ApiAuthenticationResponse resp = new() | ||
{ | ||
TokenData = token.TokenData, | ||
UserId = user.UserId.ToString(), | ||
ExpiresAt = token.ExpiresAt, | ||
}; | ||
|
||
return new Response(resp, ContentType.Json); | ||
} | ||
|
||
[ApiEndpoint("resetPassword", Method.Post)] | ||
[Authentication(false)] | ||
public Response ResetPassword(RequestContext context, RealmDatabaseContext database, ApiResetPasswordRequest body) | ||
{ | ||
GameUser? user = database.GetUserFromTokenData(body.ResetToken, TokenType.PasswordReset); | ||
if (user == null) return new Response(HttpStatusCode.Unauthorized); | ||
|
||
if (body.PasswordSha512.Length != 128 || !Sha512Regex().IsMatch(body.PasswordSha512)) | ||
return new Response("Password is definitely not SHA512. Please hash the password - it'll work out better for both of us.", | ||
ContentType.Plaintext, HttpStatusCode.BadRequest); | ||
|
||
string? passwordBcrypt = BC.HashPassword(body.PasswordSha512, WorkFactor); | ||
if (passwordBcrypt == null) return new Response(HttpStatusCode.InternalServerError); | ||
|
||
database.SetUserPassword(user, passwordBcrypt); | ||
|
||
return new Response(HttpStatusCode.OK); | ||
} | ||
} | ||
|
||
#nullable disable | ||
|
||
[Serializable] | ||
public class ApiAuthenticationRequest | ||
{ | ||
public string Username { get; set; } | ||
public string PasswordSha512 { get; set; } | ||
} | ||
|
||
[Serializable] | ||
public class ApiAuthenticationResponse | ||
{ | ||
public string TokenData { get; set; } | ||
public string UserId { get; set; } | ||
public DateTimeOffset ExpiresAt { get; set; } | ||
} | ||
|
||
[Serializable] | ||
public class ApiResetPasswordRequest | ||
{ | ||
public string PasswordSha512 { get; set; } | ||
public string ResetToken { get; set; } | ||
} | ||
|
||
[Serializable] | ||
public class ApiResetPasswordResponse | ||
{ | ||
public string Reason { get; set; } | ||
public string ResetToken { get; set; } | ||
} | ||
|
||
[Serializable] | ||
public class ApiErrorResponse | ||
{ | ||
public ApiErrorResponse() {} | ||
|
||
public ApiErrorResponse(string reason) | ||
{ | ||
this.Reason = reason; | ||
} | ||
|
||
public string Reason { get; set; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
using System.Text; | ||
|
||
namespace Refresh.GameServer.Extensions; | ||
|
||
internal static class MemoryStreamExtensions | ||
{ | ||
internal static void WriteString(this MemoryStream ms, string str) => ms.Write(Encoding.UTF8.GetBytes(str)); | ||
} |
Oops, something went wrong.