feat: link fediverse account to existing user

This commit is contained in:
sam 2024-12-04 01:48:52 +01:00
parent 03209e4028
commit 57e1ec09c0
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
17 changed files with 335 additions and 95 deletions

View file

@ -104,21 +104,9 @@ public class DiscordAuthController(
{
CheckRequirements();
var existingAccounts = await db
.AuthMethods.Where(m => m.UserId == CurrentUser!.Id && m.AuthType == AuthType.Discord)
.CountAsync();
if (existingAccounts > AuthUtils.MaxAuthMethodsPerType)
{
throw new ApiError.BadRequest(
"Too many linked Discord accounts, maximum of 3 per account."
);
}
var state = HttpUtility.UrlEncode(
await keyCacheService.GenerateAddExtraAccountStateAsync(
AuthType.Discord,
CurrentUser!.Id
)
var state = await remoteAuthService.ValidateAddAccountRequestAsync(
CurrentUser!.Id,
AuthType.Discord
);
var url =
@ -138,12 +126,11 @@ public class DiscordAuthController(
{
CheckRequirements();
var accountState = await keyCacheService.GetAddExtraAccountStateAsync(req.State);
if (
accountState is not { AuthType: AuthType.Discord }
|| accountState.UserId != CurrentUser!.Id
)
throw new ApiError.BadRequest("Invalid state", "state", req.State);
await remoteAuthService.ValidateAddAccountStateAsync(
req.State,
CurrentUser!.Id,
AuthType.Discord
);
var remoteUser = await remoteAuthService.RequestDiscordTokenAsync(req.Code);
try

View file

@ -1,5 +1,8 @@
using System.Net;
using EntityFramework.Exceptions.Common;
using Foxnouns.Backend.Database;
using Foxnouns.Backend.Database.Models;
using Foxnouns.Backend.Middleware;
using Foxnouns.Backend.Services;
using Foxnouns.Backend.Services.Auth;
using Foxnouns.Backend.Utils;
@ -15,13 +18,14 @@ public class FediverseAuthController(
DatabaseContext db,
FediverseAuthService fediverseAuthService,
AuthService authService,
RemoteAuthService remoteAuthService,
KeyCacheService keyCacheService
) : ApiControllerBase
{
private readonly ILogger _logger = logger.ForContext<FediverseAuthController>();
[HttpGet]
[ProducesResponseType<FediverseUrlResponse>(statusCode: StatusCodes.Status200OK)]
[ProducesResponseType<AuthController.SingleUrlResponse>(statusCode: StatusCodes.Status200OK)]
public async Task<IActionResult> GetFediverseUrlAsync(
[FromQuery] string instance,
[FromQuery] bool forceRefresh = false
@ -31,7 +35,7 @@ public class FediverseAuthController(
throw new ApiError.BadRequest("Not a valid domain.", "instance", instance);
var url = await fediverseAuthService.GenerateAuthUrlAsync(instance, forceRefresh);
return Ok(new FediverseUrlResponse(url));
return Ok(new AuthController.SingleUrlResponse(url));
}
[HttpPost("callback")]
@ -118,9 +122,74 @@ public class FediverseAuthController(
return Ok(await authService.GenerateUserTokenAsync(user));
}
public record CallbackRequest(string Instance, string Code, string State);
[HttpGet("add-account")]
[Authorize("*")]
public async Task<IActionResult> AddFediverseAccountAsync(
[FromQuery] string instance,
[FromQuery] bool forceRefresh = false
)
{
if (instance.Any(c => c is '@' or ':' or '/') || !instance.Contains('.'))
throw new ApiError.BadRequest("Not a valid domain.", "instance", instance);
private record FediverseUrlResponse(string Url);
var state = await remoteAuthService.ValidateAddAccountRequestAsync(
CurrentUser!.Id,
AuthType.Fediverse,
instance
);
var url = await fediverseAuthService.GenerateAuthUrlAsync(instance, forceRefresh, state);
return Ok(new AuthController.SingleUrlResponse(url));
}
[HttpPost("add-account/callback")]
[Authorize("*")]
public async Task<IActionResult> AddAccountCallbackAsync([FromBody] CallbackRequest req)
{
await remoteAuthService.ValidateAddAccountStateAsync(
req.State,
CurrentUser!.Id,
AuthType.Fediverse,
req.Instance
);
var app = await fediverseAuthService.GetApplicationAsync(req.Instance);
var remoteUser = await fediverseAuthService.GetRemoteFediverseUserAsync(app, req.Code);
try
{
var authMethod = await authService.AddAuthMethodAsync(
CurrentUser.Id,
AuthType.Fediverse,
remoteUser.Id,
remoteUser.Username,
app
);
_logger.Debug(
"Added new Fediverse auth method {AuthMethodId} to user {UserId}",
authMethod.Id,
CurrentUser.Id
);
return Ok(
new AuthController.AddOauthAccountResponse(
authMethod.Id,
AuthType.Fediverse,
authMethod.RemoteId,
$"{authMethod.RemoteUsername}@{app.Domain}"
)
);
}
catch (UniqueConstraintException)
{
throw new ApiError(
"That account is already linked.",
HttpStatusCode.BadRequest,
ErrorCode.AccountAlreadyLinked
);
}
}
public record CallbackRequest(string Instance, string Code, string State);
private record FediverseTicketData(
Snowflake ApplicationId,