mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-27 07:04:57 +01:00
Custom DTO for ITokenFactory
This commit is contained in:
@@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||
|
||||
using Tgstation.Server.Host.Authority.Core;
|
||||
using Tgstation.Server.Host.GraphQL.Mutations.Payloads;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
namespace Tgstation.Server.Host.Authority
|
||||
{
|
||||
@@ -15,8 +16,8 @@ namespace Tgstation.Server.Host.Authority
|
||||
/// Attempt to login to the server with the current Basic or OAuth credentials.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in an authenticated token <see cref="AuthorityResponse{TResult}"/>.</returns>
|
||||
RequirementsGated<AuthorityResponse<string>> AttemptLogin(CancellationToken cancellationToken);
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in a <see cref="GeneratedToken"/> <see cref="AuthorityResponse{TResult}"/>.</returns>
|
||||
RequirementsGated<AuthorityResponse<GeneratedToken>> AttemptLogin(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to login to an OAuth service with the current OAuth credentials.
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace Tgstation.Server.Host.Authority
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public RequirementsGated<AuthorityResponse<string>> AttemptLogin(CancellationToken cancellationToken)
|
||||
public RequirementsGated<AuthorityResponse<GeneratedToken>> AttemptLogin(CancellationToken cancellationToken)
|
||||
=> new(
|
||||
() => null,
|
||||
() => AttemptLoginImpl(cancellationToken),
|
||||
@@ -176,19 +176,19 @@ namespace Tgstation.Server.Host.Authority
|
||||
/// Login process.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="AuthorityResponse{TResult}"/> containing the authenticated bearer token.</returns>
|
||||
private async ValueTask<AuthorityResponse<string>> AttemptLoginImpl(CancellationToken cancellationToken)
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="AuthorityResponse{TResult}"/> containing the <see cref="GeneratedToken"/>.</returns>
|
||||
private async ValueTask<AuthorityResponse<GeneratedToken>> AttemptLoginImpl(CancellationToken cancellationToken)
|
||||
{
|
||||
// password and oauth logins disabled
|
||||
if (securityConfigurationOptions.Value.OidcStrictMode)
|
||||
return Unauthorized<string>();
|
||||
return Unauthorized<GeneratedToken>();
|
||||
|
||||
var headers = apiHeadersProvider.ApiHeaders;
|
||||
if (headers == null)
|
||||
return GenerateHeadersExceptionResponse<string>(apiHeadersProvider.HeadersException!);
|
||||
return GenerateHeadersExceptionResponse<GeneratedToken>(apiHeadersProvider.HeadersException!);
|
||||
|
||||
if (headers.IsTokenAuthentication)
|
||||
return BadRequest<string>(ErrorCode.TokenWithToken);
|
||||
return BadRequest<GeneratedToken>(ErrorCode.TokenWithToken);
|
||||
|
||||
var oAuthLogin = headers.OAuthProvider.HasValue;
|
||||
|
||||
@@ -211,7 +211,7 @@ namespace Tgstation.Server.Host.Authority
|
||||
if (oAuthLogin)
|
||||
{
|
||||
var oAuthProvider = headers.OAuthProvider!.Value;
|
||||
var (errorResponse, oauthResult) = await TryOAuthenticate<string>(headers, oAuthProvider, true, cancellationToken);
|
||||
var (errorResponse, oauthResult) = await TryOAuthenticate<GeneratedToken>(headers, oAuthProvider, true, cancellationToken);
|
||||
if (errorResponse != null)
|
||||
return errorResponse;
|
||||
|
||||
@@ -224,7 +224,7 @@ namespace Tgstation.Server.Host.Authority
|
||||
{
|
||||
var canonicalUserName = User.CanonicalizeName(headers.Username!);
|
||||
if (canonicalUserName == User.CanonicalizeName(User.TgsSystemUserName))
|
||||
return Unauthorized<string>();
|
||||
return Unauthorized<GeneratedToken>();
|
||||
|
||||
if (systemIdentity == null)
|
||||
query = query.Where(x => x.CanonicalName == canonicalUserName);
|
||||
@@ -236,7 +236,7 @@ namespace Tgstation.Server.Host.Authority
|
||||
|
||||
// No user? You're not allowed
|
||||
if (user == null)
|
||||
return Unauthorized<string>();
|
||||
return Unauthorized<GeneratedToken>();
|
||||
|
||||
// A system user may have had their name AND password changed to one in our DB...
|
||||
// Or a DB user was created that had the same user/pass as a system user
|
||||
@@ -251,7 +251,7 @@ namespace Tgstation.Server.Host.Authority
|
||||
{
|
||||
// DB User password check and update
|
||||
if (!isLikelyDbUser || !cryptographySuite.CheckUserPassword(user, headers.Password!))
|
||||
return Unauthorized<string>();
|
||||
return Unauthorized<GeneratedToken>();
|
||||
if (user.PasswordHash != originalHash)
|
||||
{
|
||||
Logger.LogDebug("User ID {userId}'s password hash needs a refresh, updating database.", user.Id);
|
||||
@@ -294,17 +294,17 @@ namespace Tgstation.Server.Host.Authority
|
||||
if (!user.Enabled!.Value)
|
||||
{
|
||||
Logger.LogTrace("Not logging in disabled user {userId}.", user.Id);
|
||||
return Forbid<string>();
|
||||
return Forbid<GeneratedToken>();
|
||||
}
|
||||
|
||||
var (token, expiresAt) = tokenFactory.CreateToken(user, oAuthLogin);
|
||||
var token = tokenFactory.CreateToken(user, oAuthLogin);
|
||||
|
||||
if (usingSystemIdentity)
|
||||
await CacheSystemIdentity(systemIdentity!, user, expiresAt);
|
||||
await CacheSystemIdentity(systemIdentity!, user, token.Expiry);
|
||||
|
||||
Logger.LogDebug("Successfully logged in user {userId}!", user.Id);
|
||||
|
||||
return new AuthorityResponse<string>(token);
|
||||
return new AuthorityResponse<GeneratedToken>(token);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
return ValueTask.FromResult(HeadersIssue(ApiHeadersProvider.HeadersException!));
|
||||
}
|
||||
|
||||
return loginAuthority.InvokeTransformable<string, TokenResponse, TokenResponseTransformer>(this, authority => authority.AttemptLogin(cancellationToken));
|
||||
return loginAuthority.InvokeTransformable<GeneratedToken, TokenResponse, TokenResponseTransformer>(this, authority => authority.AttemptLogin(cancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using Tgstation.Server.Api.Models.Response;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
namespace Tgstation.Server.Host.Controllers.Transformers
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ITransformer{TInput, TOutput}"/> for <see cref="TokenResponse"/>s.
|
||||
/// </summary>
|
||||
sealed class TokenResponseTransformer : TransformerBase<string, TokenResponse>
|
||||
sealed class TokenResponseTransformer : TransformerBase<GeneratedToken, TokenResponse>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TokenResponseTransformer"/> class.
|
||||
@@ -15,7 +16,7 @@ namespace Tgstation.Server.Host.Controllers.Transformers
|
||||
: base(
|
||||
token => new TokenResponse
|
||||
{
|
||||
Bearer = token,
|
||||
Bearer = token.Token,
|
||||
})
|
||||
{
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using HotChocolate.Types;
|
||||
using Tgstation.Server.Host.Authority;
|
||||
using Tgstation.Server.Host.GraphQL.Mutations.Payloads;
|
||||
using Tgstation.Server.Host.GraphQL.Transformers;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
namespace Tgstation.Server.Host.GraphQL
|
||||
{
|
||||
@@ -39,7 +40,7 @@ namespace Tgstation.Server.Host.GraphQL
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(loginAuthority);
|
||||
|
||||
return loginAuthority.InvokeTransformable<string, LoginResult, LoginResultTransformer>(
|
||||
return loginAuthority.InvokeTransformable<GeneratedToken, LoginResult, LoginResultTransformer>(
|
||||
authority => authority.AttemptLogin(cancellationToken));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using Tgstation.Server.Host.GraphQL.Mutations.Payloads;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
namespace Tgstation.Server.Host.GraphQL.Transformers
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ITransformer{TInput, TOutput}"/> for <see cref="LoginResult"/>s.
|
||||
/// </summary>
|
||||
sealed class LoginResultTransformer : TransformerBase<string, LoginResult>
|
||||
sealed class LoginResultTransformer : TransformerBase<GeneratedToken, LoginResult>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoginResultTransformer"/> class.
|
||||
@@ -15,7 +16,7 @@ namespace Tgstation.Server.Host.GraphQL.Transformers
|
||||
: base(
|
||||
token => new LoginResult
|
||||
{
|
||||
Bearer = token,
|
||||
Bearer = token.Token,
|
||||
})
|
||||
{
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Host.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// A securely generated token.
|
||||
/// </summary>
|
||||
public record struct GeneratedToken
|
||||
{
|
||||
/// <summary>
|
||||
/// The token string.
|
||||
/// </summary>
|
||||
public required string Token { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// When the token expires.
|
||||
/// </summary>
|
||||
public required DateTimeOffset Expiry { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace Tgstation.Server.Host.Security
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="Models.User"/> to create the token for. Must have the <see cref="Api.Models.EntityId.Id"/> field available.</param>
|
||||
/// <param name="serviceLogin">Whether or not this is an external service login.</param>
|
||||
/// <returns>A new token <see cref="string"/> and the <see cref="DateTimeOffset"/> that it expires.</returns>
|
||||
(string Token, DateTimeOffset Expiry) CreateToken(Models.User user, bool serviceLogin);
|
||||
/// <returns>A new <see cref="GeneratedToken"/>.</returns>
|
||||
GeneratedToken CreateToken(Models.User user, bool serviceLogin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace Tgstation.Server.Host.Security
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public (string Token, DateTimeOffset Expiry) CreateToken(User user, bool serviceLogin)
|
||||
public GeneratedToken CreateToken(User user, bool serviceLogin)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(user);
|
||||
|
||||
@@ -141,7 +141,11 @@ namespace Tgstation.Server.Host.Security
|
||||
|
||||
var tokenResponse = tokenHandler.WriteToken(securityToken);
|
||||
|
||||
return (Token: tokenResponse, Expiry: expiry);
|
||||
return new GeneratedToken
|
||||
{
|
||||
Token = tokenResponse,
|
||||
Expiry = expiry,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace Tgstation.Server.Host.Swarm.Tests
|
||||
|
||||
public TokenValidationParameters ValidationParameters => throw new NotSupportedException();
|
||||
|
||||
public (string, DateTimeOffset) CreateToken(User user, bool serviceLogin)
|
||||
public GeneratedToken CreateToken(User user, bool serviceLogin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user