This repository has been archived by the owner on Jul 9, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
23 changed files
with
490 additions
and
0 deletions.
There are no files selected for viewing
6 changes: 6 additions & 0 deletions
6
backend/src/Logitar.Master.Application/Accounts/Commands/SignInCommand.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,6 @@ | ||
using Logitar.Master.Contracts.Accounts; | ||
using MediatR; | ||
|
||
namespace Logitar.Master.Application.Accounts.Commands; | ||
|
||
public record SignInCommand(SignInPayload Payload) : IRequest<SignInCommandResult>; |
62 changes: 62 additions & 0 deletions
62
backend/src/Logitar.Master.Application/Accounts/Commands/SignInCommandHandler.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,62 @@ | ||
using Logitar.Master.Contracts.Accounts; | ||
using Logitar.Portal.Contracts.Messages; | ||
using Logitar.Portal.Contracts.Tokens; | ||
using Logitar.Portal.Contracts.Users; | ||
using MediatR; | ||
|
||
namespace Logitar.Master.Application.Accounts.Commands; | ||
|
||
internal class SignInCommandHandler : IRequestHandler<SignInCommand, SignInCommandResult> | ||
{ | ||
private const string AuthenticationTokenType = "auth+jwt"; | ||
private const string PasswordlessTemplate = "AccountAuthentication"; | ||
|
||
private readonly IMessageService _messageService; | ||
private readonly ITokenService _tokenService; | ||
private readonly IUserService _userService; | ||
|
||
public SignInCommandHandler(IMessageService messageService, ITokenService tokenService, IUserService userService) | ||
{ | ||
_messageService = messageService; | ||
_tokenService = tokenService; | ||
_userService = userService; | ||
} | ||
|
||
public async Task<SignInCommandResult> Handle(SignInCommand command, CancellationToken cancellationToken) | ||
{ | ||
SignInPayload payload = command.Payload; | ||
// TODO(fpion): validation payload | ||
|
||
if (payload.Credentials != null) | ||
{ | ||
return await HandleCredentialsAsync(payload.Credentials, payload.Locale, cancellationToken); | ||
} | ||
|
||
throw new InvalidOperationException($"The {nameof(SignInPayload)} is not valid."); | ||
} | ||
|
||
private async Task<SignInCommandResult> HandleCredentialsAsync(Credentials credentials, string locale, CancellationToken cancellationToken) | ||
{ | ||
User? user = await _userService.FindAsync(credentials.EmailAddress, cancellationToken); | ||
if (user == null || !user.HasPassword) | ||
{ | ||
Email email = user?.Email ?? new(credentials.EmailAddress); | ||
CreatedToken token = await _tokenService.CreateAsync(user?.GetSubject(), email, AuthenticationTokenType, cancellationToken); | ||
Dictionary<string, string> variables = new() | ||
{ | ||
["Token"] = token.Token | ||
}; | ||
SentMessages sentMessages = user == null | ||
? await _messageService.SendAsync(PasswordlessTemplate, email, locale, variables, cancellationToken) | ||
: await _messageService.SendAsync(PasswordlessTemplate, user, locale, variables, cancellationToken); | ||
SentMessage sentMessage = sentMessages.ToSentMessage(email); | ||
return SignInCommandResult.AuthenticationLinkSent(sentMessage); | ||
} | ||
else if (credentials.Password == null) | ||
{ | ||
return SignInCommandResult.RequirePassword(); | ||
} | ||
|
||
throw new NotImplementedException(); // TODO(fpion): implement | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
backend/src/Logitar.Master.Application/Accounts/IMessageService.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,10 @@ | ||
using Logitar.Portal.Contracts.Messages; | ||
using Logitar.Portal.Contracts.Users; | ||
|
||
namespace Logitar.Master.Application.Accounts; | ||
|
||
public interface IMessageService | ||
{ | ||
Task<SentMessages> SendAsync(string template, Email email, string locale, Dictionary<string, string> variables, CancellationToken cancellationToken = default); | ||
Task<SentMessages> SendAsync(string template, User user, string locale, Dictionary<string, string> variables, CancellationToken cancellationToken = default); | ||
} |
9 changes: 9 additions & 0 deletions
9
backend/src/Logitar.Master.Application/Accounts/ITokenService.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,9 @@ | ||
using Logitar.Portal.Contracts.Tokens; | ||
using Logitar.Portal.Contracts.Users; | ||
|
||
namespace Logitar.Master.Application.Accounts; | ||
|
||
public interface ITokenService | ||
{ | ||
Task<CreatedToken> CreateAsync(string? subject, Email email, string type, CancellationToken cancellationToken = default); | ||
} |
8 changes: 8 additions & 0 deletions
8
backend/src/Logitar.Master.Application/Accounts/IUserService.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,8 @@ | ||
using Logitar.Portal.Contracts.Users; | ||
|
||
namespace Logitar.Master.Application.Accounts; | ||
|
||
public interface IUserService | ||
{ | ||
Task<User?> FindAsync(string emailAddress, CancellationToken cancellationToken = default); | ||
} |
75 changes: 75 additions & 0 deletions
75
backend/src/Logitar.Master.Application/Accounts/MessageExtensions.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,75 @@ | ||
using Logitar.Master.Contracts.Accounts; | ||
using Logitar.Portal.Contracts.Messages; | ||
using Logitar.Portal.Contracts.Users; | ||
using System.Text; | ||
|
||
namespace Logitar.Master.Application.Accounts; | ||
|
||
internal static class MessageExtensions | ||
{ | ||
private const string Base12Table = "2345679ACDEF"; | ||
private const string Base32Table = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; | ||
|
||
public static string GenerateConfirmationNumber(this SentMessages sentMessages) | ||
{ | ||
if (sentMessages.Ids.Count == 0) | ||
{ | ||
throw new ArgumentException("No message has been sent.", nameof(sentMessages)); | ||
} | ||
else if (sentMessages.Ids.Count > 1) | ||
{ | ||
throw new ArgumentException("More than one message have been sent.", nameof(sentMessages)); | ||
} | ||
|
||
StringBuilder number = new(); | ||
|
||
string id = Convert.ToBase64String(sentMessages.Ids.Single().ToByteArray()); | ||
int total = 0; | ||
for (int i = 0; i <= 2; i++) | ||
{ | ||
total += (int)Math.Pow(64, 2 - i) * GetBase64Value(id[i]); | ||
} | ||
for (int i = 3; i >= 0; i--) | ||
{ | ||
int divider = (int)Math.Pow(32, i); | ||
int value = total / divider; | ||
total %= divider; | ||
number.Append(GetBase32Character(value)); | ||
} | ||
DateTime now = DateTime.UtcNow; | ||
number.Append('-').Append((now.Year % 100).ToString("D2")).Append(now.Month.ToString("D2")).Append(now.Day.ToString("D2")).Append('-'); | ||
|
||
int minuteRange = ((now.Hour * 60) + now.Minute) / 10; | ||
number.Append(GetBase12Character(minuteRange / 12)); | ||
number.Append(GetBase12Character(minuteRange % 12)); | ||
|
||
return number.ToString(); | ||
} | ||
private static char GetBase12Character(int value) => Base12Table[value]; | ||
private static char GetBase32Character(int value) => Base32Table[value]; | ||
private static int GetBase64Value(char c) | ||
{ | ||
if (c >= 'A' && c <= 'Z') | ||
{ | ||
return c - 'A'; | ||
} | ||
else if (c >= 'a' && c <= 'z') | ||
{ | ||
return c - 'a' + 26; | ||
} | ||
else if (c >= '0' && c <= '9') | ||
{ | ||
return c - '0' + 52; | ||
} | ||
else if (c == '+') | ||
{ | ||
return 62; | ||
} | ||
return 63; | ||
} | ||
|
||
public static SentMessage ToSentMessage(this SentMessages sentMessages, Email email) | ||
{ | ||
return new SentMessage(sentMessages.GenerateConfirmationNumber(), ContactType.Email, email.Address); | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
backend/src/Logitar.Master.Application/Accounts/UserExtensions.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,8 @@ | ||
using Logitar.Portal.Contracts.Users; | ||
|
||
namespace Logitar.Master.Application.Accounts; | ||
|
||
internal static class UserExtensions | ||
{ | ||
public static string GetSubject(this User user) => user.Id.ToString(); | ||
} |
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,7 @@ | ||
namespace Logitar.Master.Contracts.Accounts; | ||
|
||
public enum ContactType | ||
{ | ||
Email = 0, | ||
Phone = 1 | ||
} |
17 changes: 17 additions & 0 deletions
17
backend/src/Logitar.Master.Contracts/Accounts/Credentials.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,17 @@ | ||
namespace Logitar.Master.Contracts.Accounts; | ||
|
||
public record Credentials | ||
{ | ||
public string EmailAddress { get; set; } | ||
public string? Password { get; set; } | ||
|
||
public Credentials() : this(string.Empty) | ||
{ | ||
} | ||
|
||
public Credentials(string emailAddress, string? password = null) | ||
{ | ||
EmailAddress = emailAddress; | ||
Password = password; | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
backend/src/Logitar.Master.Contracts/Accounts/SentMessage.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,23 @@ | ||
namespace Logitar.Master.Contracts.Accounts; | ||
|
||
public record SentMessage | ||
{ | ||
public string ConfirmationNumber { get; set; } | ||
public ContactType ContactType { get; set; } | ||
public string MaskedContact { get; set; } | ||
|
||
public SentMessage() : this(string.Empty, string.Empty) | ||
{ | ||
} | ||
|
||
public SentMessage(string confirmationNumber, string maskedContact) : this(confirmationNumber, default, maskedContact) | ||
{ | ||
} | ||
|
||
public SentMessage(string confirmationNumber, ContactType contactType, string maskedContact) | ||
{ | ||
ConfirmationNumber = confirmationNumber; | ||
ContactType = contactType; | ||
MaskedContact = maskedContact; | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
backend/src/Logitar.Master.Contracts/Accounts/SignInPayload.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,17 @@ | ||
namespace Logitar.Master.Contracts.Accounts; | ||
|
||
public record SignInPayload | ||
{ | ||
public string Locale { get; set; } | ||
|
||
public Credentials? Credentials { get; set; } | ||
|
||
public SignInPayload() : this(string.Empty) | ||
{ | ||
} | ||
|
||
public SignInPayload(string locale) | ||
{ | ||
Locale = locale; | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
backend/src/Logitar.Master.Contracts/Accounts/SignInResult.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,29 @@ | ||
using Logitar.Portal.Contracts.Sessions; | ||
|
||
namespace Logitar.Master.Contracts.Accounts; | ||
|
||
public record SignInCommandResult | ||
{ | ||
public SentMessage? AuthenticationLinkSentTo { get; set; } | ||
public bool IsPasswordRequired { get; set; } | ||
public Session? Session { get; set; } | ||
|
||
public SignInCommandResult() | ||
{ | ||
} | ||
|
||
public static SignInCommandResult AuthenticationLinkSent(SentMessage sentMessage) => new() | ||
{ | ||
AuthenticationLinkSentTo = sentMessage | ||
}; | ||
|
||
public static SignInCommandResult RequirePassword() => new() | ||
{ | ||
IsPasswordRequired = true | ||
}; | ||
|
||
public static SignInCommandResult Succeed(Session session) => new() | ||
{ | ||
Session = session | ||
}; | ||
} |
26 changes: 26 additions & 0 deletions
26
backend/src/Logitar.Master/Authentication/BearerTokenService.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,26 @@ | ||
using Logitar.Master.Models.Account; | ||
using Logitar.Master.Settings; | ||
using Logitar.Portal.Contracts.Sessions; | ||
|
||
namespace Logitar.Master.Authentication; | ||
|
||
internal class BearerTokenService : IBearerTokenService | ||
{ | ||
private readonly BearerTokenSettings _settings; | ||
|
||
public BearerTokenService(BearerTokenSettings settings) | ||
{ | ||
_settings = settings; | ||
} | ||
|
||
public TokenResponse GetTokenResponse(Session session) | ||
{ | ||
string accessToken = ""; // TODO(fpion): implement | ||
|
||
return new TokenResponse(accessToken, _settings.TokenType) | ||
{ | ||
ExpiresIn = _settings.LifetimeSeconds, | ||
RefreshToken = session.RefreshToken | ||
}; | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
backend/src/Logitar.Master/Authentication/IBearerTokenService.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,9 @@ | ||
using Logitar.Master.Models.Account; | ||
using Logitar.Portal.Contracts.Sessions; | ||
|
||
namespace Logitar.Master.Authentication; | ||
|
||
public interface IBearerTokenService | ||
{ | ||
TokenResponse GetTokenResponse(Session session); | ||
} |
51 changes: 51 additions & 0 deletions
51
backend/src/Logitar.Master/Controllers/AccountController.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,51 @@ | ||
using Logitar.Master.Application.Accounts.Commands; | ||
using Logitar.Master.Authentication; | ||
using Logitar.Master.Contracts.Accounts; | ||
using Logitar.Master.Models.Account; | ||
using MediatR; | ||
using Microsoft.AspNetCore.Mvc; | ||
|
||
namespace Logitar.Master.Controllers; | ||
|
||
[ApiController] | ||
public class AccountController : ControllerBase | ||
{ | ||
private readonly IBearerTokenService _bearerTokenService; | ||
private readonly ISender _sender; | ||
|
||
public AccountController(IBearerTokenService bearerTokenService, ISender sender) | ||
{ | ||
_bearerTokenService = bearerTokenService; | ||
_sender = sender; | ||
} | ||
|
||
[HttpPost("/auth/sign/in")] | ||
public async Task<ActionResult<SignInResponse>> SignInAsync([FromBody] SignInPayload payload, CancellationToken cancellationToken) | ||
{ | ||
SignInCommandResult result = await _sender.Send(new SignInCommand(payload), cancellationToken); | ||
if (result.Session != null) | ||
{ | ||
// TODO(fpion): SignIn | ||
} | ||
|
||
return Ok(new SignInResponse(result)); | ||
} | ||
|
||
[HttpPost("/auth/token")] | ||
public async Task<ActionResult<GetTokenResponse>> TokenAsync([FromBody] GetTokenPayload payload, CancellationToken cancellationToken) | ||
{ | ||
if (!string.IsNullOrWhiteSpace(payload.RefreshToken)) | ||
{ | ||
throw new NotImplementedException(); // TODO(fpion): renew session | ||
} | ||
|
||
SignInCommandResult result = await _sender.Send(new SignInCommand(payload), cancellationToken); | ||
GetTokenResponse response = new(result); | ||
if (result.Session != null) | ||
{ | ||
response.TokenResponse = _bearerTokenService.GetTokenResponse(result.Session); | ||
} | ||
|
||
return Ok(response); | ||
} | ||
} |
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,25 @@ | ||
using Logitar.Portal.Contracts.Users; | ||
|
||
namespace Logitar.Master.Models.Account; | ||
|
||
public record CurrentUser | ||
{ | ||
public string DisplayName { get; set; } | ||
public string? EmailAddress { get; set; } | ||
public string? PictureUrl { get; set; } | ||
|
||
public CurrentUser() : this(string.Empty) | ||
{ | ||
} | ||
|
||
public CurrentUser(string displayName) | ||
{ | ||
DisplayName = displayName; | ||
} | ||
|
||
public CurrentUser(User user) : this(user.FullName ?? user.UniqueName) | ||
{ | ||
EmailAddress = user.Email?.Address; | ||
PictureUrl = user.Picture; | ||
} | ||
} |
Oops, something went wrong.