feat(auth): misc fediverse auth improvements
- remove automatic app validation - add force refresh option to GetFediverseUrlAsync - pass state to mastodon authorization URI
This commit is contained in:
parent
142ff36d3a
commit
4e9c4af4a5
9 changed files with 143 additions and 180 deletions
|
@ -22,12 +22,15 @@ public class FediverseAuthController(
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType<FediverseUrlResponse>(statusCode: StatusCodes.Status200OK)]
|
[ProducesResponseType<FediverseUrlResponse>(statusCode: StatusCodes.Status200OK)]
|
||||||
public async Task<IActionResult> GetFediverseUrlAsync([FromQuery] string instance)
|
public async Task<IActionResult> GetFediverseUrlAsync(
|
||||||
|
[FromQuery] string instance,
|
||||||
|
[FromQuery] bool forceRefresh = false
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (instance.Any(c => c is '@' or ':' or '/') || !instance.Contains('.'))
|
if (instance.Any(c => c is '@' or ':' or '/') || !instance.Contains('.'))
|
||||||
throw new ApiError.BadRequest("Not a valid domain.", "instance", instance);
|
throw new ApiError.BadRequest("Not a valid domain.", "instance", instance);
|
||||||
|
|
||||||
var url = await fediverseAuthService.GenerateAuthUrlAsync(instance);
|
var url = await fediverseAuthService.GenerateAuthUrlAsync(instance, forceRefresh);
|
||||||
return Ok(new FediverseUrlResponse(url));
|
return Ok(new FediverseUrlResponse(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +39,11 @@ public class FediverseAuthController(
|
||||||
public async Task<IActionResult> FediverseCallbackAsync([FromBody] CallbackRequest req)
|
public async Task<IActionResult> FediverseCallbackAsync([FromBody] CallbackRequest req)
|
||||||
{
|
{
|
||||||
var app = await fediverseAuthService.GetApplicationAsync(req.Instance);
|
var app = await fediverseAuthService.GetApplicationAsync(req.Instance);
|
||||||
var remoteUser = await fediverseAuthService.GetRemoteFediverseUserAsync(app, req.Code);
|
var remoteUser = await fediverseAuthService.GetRemoteFediverseUserAsync(
|
||||||
|
app,
|
||||||
|
req.Code,
|
||||||
|
req.State
|
||||||
|
);
|
||||||
|
|
||||||
var user = await authService.AuthenticateUserAsync(
|
var user = await authService.AuthenticateUserAsync(
|
||||||
AuthType.Fediverse,
|
AuthType.Fediverse,
|
||||||
|
@ -72,12 +79,16 @@ public class FediverseAuthController(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var ticketData = await keyCacheService.GetKeyAsync<FediverseTicketData>(
|
var ticketData = await keyCacheService.GetKeyAsync<FediverseTicketData>(
|
||||||
$"fediverse:{req.Ticket}"
|
$"fediverse:{req.Ticket}",
|
||||||
|
delete: true
|
||||||
);
|
);
|
||||||
if (ticketData == null)
|
if (ticketData == null)
|
||||||
throw new ApiError.BadRequest("Invalid ticket", "ticket", req.Ticket);
|
throw new ApiError.BadRequest("Invalid ticket", "ticket", req.Ticket);
|
||||||
|
|
||||||
var app = await db.FediverseApplications.FindAsync(ticketData.ApplicationId);
|
var app = await db.FediverseApplications.FindAsync(ticketData.ApplicationId);
|
||||||
|
if (app == null)
|
||||||
|
throw new FoxnounsError("Null application found for ticket");
|
||||||
|
|
||||||
if (
|
if (
|
||||||
await db.AuthMethods.AnyAsync(a =>
|
await db.AuthMethods.AnyAsync(a =>
|
||||||
a.AuthType == AuthType.Fediverse
|
a.AuthType == AuthType.Fediverse
|
||||||
|
@ -107,7 +118,7 @@ public class FediverseAuthController(
|
||||||
return Ok(await authService.GenerateUserTokenAsync(user));
|
return Ok(await authService.GenerateUserTokenAsync(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
public record CallbackRequest(string Instance, string Code);
|
public record CallbackRequest(string Instance, string Code, string State);
|
||||||
|
|
||||||
private record FediverseUrlResponse(string Url);
|
private record FediverseUrlResponse(string Url);
|
||||||
|
|
||||||
|
|
3
Foxnouns.Backend/Controllers/SidController.cs
Normal file
3
Foxnouns.Backend/Controllers/SidController.cs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
namespace Foxnouns.Backend.Controllers;
|
||||||
|
|
||||||
|
public class SidController { }
|
|
@ -0,0 +1,40 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Foxnouns.Backend.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
[DbContext(typeof(DatabaseContext))]
|
||||||
|
[Migration("20241123210306_RemoveFediverseApplicationTokens")]
|
||||||
|
public partial class RemoveFediverseApplicationTokens : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(name: "access_token", table: "fediverse_applications");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(name: "token_valid_until", table: "fediverse_applications");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "access_token",
|
||||||
|
table: "fediverse_applications",
|
||||||
|
type: "text",
|
||||||
|
nullable: true
|
||||||
|
);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<Instant>(
|
||||||
|
name: "token_valid_until",
|
||||||
|
table: "fediverse_applications",
|
||||||
|
type: "timestamp with time zone",
|
||||||
|
nullable: true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -107,10 +107,6 @@ namespace Foxnouns.Backend.Database.Migrations
|
||||||
.HasColumnType("bigint")
|
.HasColumnType("bigint")
|
||||||
.HasColumnName("id");
|
.HasColumnName("id");
|
||||||
|
|
||||||
b.Property<string>("AccessToken")
|
|
||||||
.HasColumnType("text")
|
|
||||||
.HasColumnName("access_token");
|
|
||||||
|
|
||||||
b.Property<string>("ClientId")
|
b.Property<string>("ClientId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text")
|
.HasColumnType("text")
|
||||||
|
@ -130,10 +126,6 @@ namespace Foxnouns.Backend.Database.Migrations
|
||||||
.HasColumnType("integer")
|
.HasColumnType("integer")
|
||||||
.HasColumnName("instance_type");
|
.HasColumnName("instance_type");
|
||||||
|
|
||||||
b.Property<Instant?>("TokenValidUntil")
|
|
||||||
.HasColumnType("timestamp with time zone")
|
|
||||||
.HasColumnName("token_valid_until");
|
|
||||||
|
|
||||||
b.HasKey("Id")
|
b.HasKey("Id")
|
||||||
.HasName("pk_fediverse_applications");
|
.HasName("pk_fediverse_applications");
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,6 @@ public class FediverseApplication : BaseModel
|
||||||
public required string ClientId { get; set; }
|
public required string ClientId { get; set; }
|
||||||
public required string ClientSecret { get; set; }
|
public required string ClientSecret { get; set; }
|
||||||
public required FediverseInstanceType InstanceType { get; set; }
|
public required FediverseInstanceType InstanceType { get; set; }
|
||||||
|
|
||||||
// These are for ensuring the application is still valid.
|
|
||||||
public string? AccessToken { get; set; }
|
|
||||||
public Instant? TokenValidUntil { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum FediverseInstanceType
|
public enum FediverseInstanceType
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.0"/>
|
||||||
<PackageReference Include="Minio" Version="6.0.3"/>
|
<PackageReference Include="Minio" Version="6.0.3"/>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
||||||
<PackageReference Include="NodaTime" Version="3.1.11"/>
|
<PackageReference Include="NodaTime" Version="3.1.11"/>
|
||||||
|
@ -35,6 +36,7 @@
|
||||||
<PackageReference Include="Serilog.Sinks.Seq" Version="8.0.0"/>
|
<PackageReference Include="Serilog.Sinks.Seq" Version="8.0.0"/>
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5"/>
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5"/>
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2"/>
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2"/>
|
||||||
|
<PackageReference Include="System.Text.Json" Version="9.0.0"/>
|
||||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1"/>
|
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ using System.Net;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using Foxnouns.Backend.Database;
|
using Foxnouns.Backend.Database;
|
||||||
using Foxnouns.Backend.Database.Models;
|
using Foxnouns.Backend.Database.Models;
|
||||||
using Duration = NodaTime.Duration;
|
using Foxnouns.Backend.Extensions;
|
||||||
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
|
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
|
||||||
|
|
||||||
namespace Foxnouns.Backend.Services.Auth;
|
namespace Foxnouns.Backend.Services.Auth;
|
||||||
|
@ -35,12 +35,6 @@ public partial class FediverseAuthService
|
||||||
$"Application created on Mastodon-compatible instance {instance} was null"
|
$"Application created on Mastodon-compatible instance {instance} was null"
|
||||||
);
|
);
|
||||||
|
|
||||||
var token = await GetMastodonAppTokenAsync(
|
|
||||||
instance,
|
|
||||||
mastodonApp.ClientId,
|
|
||||||
mastodonApp.ClientSecret
|
|
||||||
);
|
|
||||||
|
|
||||||
FediverseApplication app;
|
FediverseApplication app;
|
||||||
|
|
||||||
if (existingAppId == null)
|
if (existingAppId == null)
|
||||||
|
@ -52,8 +46,6 @@ public partial class FediverseAuthService
|
||||||
ClientSecret = mastodonApp.ClientSecret,
|
ClientSecret = mastodonApp.ClientSecret,
|
||||||
Domain = instance,
|
Domain = instance,
|
||||||
InstanceType = FediverseInstanceType.MastodonApi,
|
InstanceType = FediverseInstanceType.MastodonApi,
|
||||||
AccessToken = token,
|
|
||||||
TokenValidUntil = _clock.GetCurrentInstant() + Duration.FromDays(60),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_db.Add(app);
|
_db.Add(app);
|
||||||
|
@ -67,8 +59,6 @@ public partial class FediverseAuthService
|
||||||
app.ClientId = mastodonApp.ClientId;
|
app.ClientId = mastodonApp.ClientId;
|
||||||
app.ClientSecret = mastodonApp.ClientSecret;
|
app.ClientSecret = mastodonApp.ClientSecret;
|
||||||
app.InstanceType = FediverseInstanceType.MastodonApi;
|
app.InstanceType = FediverseInstanceType.MastodonApi;
|
||||||
app.AccessToken = null;
|
|
||||||
app.TokenValidUntil = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await _db.SaveChangesAsync();
|
await _db.SaveChangesAsync();
|
||||||
|
@ -76,8 +66,14 @@ public partial class FediverseAuthService
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<FediverseUser> GetMastodonUserAsync(FediverseApplication app, string code)
|
private async Task<FediverseUser> GetMastodonUserAsync(
|
||||||
|
FediverseApplication app,
|
||||||
|
string code,
|
||||||
|
string state
|
||||||
|
)
|
||||||
{
|
{
|
||||||
|
await _keyCacheService.ValidateAuthStateAsync(state);
|
||||||
|
|
||||||
var tokenResp = await _client.PostAsync(
|
var tokenResp = await _client.PostAsync(
|
||||||
MastodonTokenUri(app.Domain),
|
MastodonTokenUri(app.Domain),
|
||||||
new FormUrlEncodedContent(
|
new FormUrlEncodedContent(
|
||||||
|
@ -122,109 +118,27 @@ public partial class FediverseAuthService
|
||||||
|
|
||||||
private record MastodonTokenResponse([property: J("access_token")] string AccessToken);
|
private record MastodonTokenResponse([property: J("access_token")] string AccessToken);
|
||||||
|
|
||||||
// TODO: Mastodon's OAuth documentation doesn't specify a "state" parameter. that feels... wrong
|
private async Task<string> GenerateMastodonAuthUrlAsync(
|
||||||
// https://docs.joinmastodon.org/methods/oauth/
|
FediverseApplication app,
|
||||||
private async Task<string> GenerateMastodonAuthUrlAsync(FediverseApplication app)
|
bool forceRefresh
|
||||||
|
)
|
||||||
{
|
{
|
||||||
try
|
if (forceRefresh)
|
||||||
{
|
{
|
||||||
await ValidateMastodonAppAsync(app);
|
_logger.Information(
|
||||||
}
|
"An app credentials refresh was requested for {ApplicationId}, creating a new application",
|
||||||
catch (FoxnounsError.RemoteAuthError e)
|
app.Id
|
||||||
{
|
|
||||||
_logger.Error(
|
|
||||||
e,
|
|
||||||
"Error validating app token for {AppId} on {Instance}",
|
|
||||||
app.Id,
|
|
||||||
app.Domain
|
|
||||||
);
|
);
|
||||||
|
|
||||||
app = await CreateMastodonApplicationAsync(app.Domain, existingAppId: app.Id);
|
app = await CreateMastodonApplicationAsync(app.Domain, existingAppId: app.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var state = HttpUtility.UrlEncode(await _keyCacheService.GenerateAuthStateAsync());
|
||||||
|
|
||||||
return $"https://{app.Domain}/oauth/authorize?response_type=code"
|
return $"https://{app.Domain}/oauth/authorize?response_type=code"
|
||||||
+ $"&client_id={app.ClientId}"
|
+ $"&client_id={app.ClientId}"
|
||||||
+ $"&scope={HttpUtility.UrlEncode("read:accounts")}"
|
+ $"&scope={HttpUtility.UrlEncode("read:accounts")}"
|
||||||
+ $"&redirect_uri={HttpUtility.UrlEncode(MastodonRedirectUri(app.Domain))}";
|
+ $"&redirect_uri={HttpUtility.UrlEncode(MastodonRedirectUri(app.Domain))}"
|
||||||
}
|
+ $"&state={state}";
|
||||||
|
|
||||||
private async Task ValidateMastodonAppAsync(FediverseApplication app)
|
|
||||||
{
|
|
||||||
// If we don't have an access token stored, or it's too old, get one
|
|
||||||
// When doing this we don't need to fetch the application info
|
|
||||||
if (app.AccessToken == null || app.TokenValidUntil < _clock.GetCurrentInstant())
|
|
||||||
{
|
|
||||||
_logger.Debug(
|
|
||||||
"Application {AppId} on instance {Instance} has no valid token, fetching it",
|
|
||||||
app.Id,
|
|
||||||
app.Domain
|
|
||||||
);
|
|
||||||
|
|
||||||
app.AccessToken = await GetMastodonAppTokenAsync(
|
|
||||||
app.Domain,
|
|
||||||
app.ClientId,
|
|
||||||
app.ClientSecret
|
|
||||||
);
|
|
||||||
app.TokenValidUntil = _clock.GetCurrentInstant() + Duration.FromDays(60);
|
|
||||||
|
|
||||||
_db.Update(app);
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.Debug(
|
|
||||||
"Checking whether application {AppId} on instance {Instance} is still valid",
|
|
||||||
app.Id,
|
|
||||||
app.Domain
|
|
||||||
);
|
|
||||||
|
|
||||||
var req = new HttpRequestMessage(HttpMethod.Get, MastodonCurrentAppUri(app.Domain));
|
|
||||||
req.Headers.Add("Authorization", $"Bearer {app.AccessToken}");
|
|
||||||
|
|
||||||
var resp = await _client.SendAsync(req);
|
|
||||||
if (!resp.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
var error = await resp.Content.ReadAsStringAsync();
|
|
||||||
throw new FoxnounsError.RemoteAuthError(
|
|
||||||
"Verifying app credentials returned an error",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<string> GetMastodonAppTokenAsync(
|
|
||||||
string instance,
|
|
||||||
string clientId,
|
|
||||||
string clientSecret
|
|
||||||
)
|
|
||||||
{
|
|
||||||
var resp = await _client.PostAsync(
|
|
||||||
MastodonTokenUri(instance),
|
|
||||||
new FormUrlEncodedContent(
|
|
||||||
new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{ "grant_type", "client_credentials" },
|
|
||||||
{ "client_id", clientId },
|
|
||||||
{ "client_secret", clientSecret },
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if (!resp.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
var error = await resp.Content.ReadAsStringAsync();
|
|
||||||
throw new FoxnounsError.RemoteAuthError(
|
|
||||||
"Requesting app token returned an error",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var token = (await resp.Content.ReadFromJsonAsync<MastodonTokenResponse>())?.AccessToken;
|
|
||||||
if (token == null)
|
|
||||||
{
|
|
||||||
throw new FoxnounsError($"Token response from instance {instance} was invalid");
|
|
||||||
}
|
|
||||||
|
|
||||||
return token;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string MastodonTokenUri(string instance) => $"https://{instance}/oauth/token";
|
private static string MastodonTokenUri(string instance) => $"https://{instance}/oauth/token";
|
||||||
|
@ -232,9 +146,6 @@ public partial class FediverseAuthService
|
||||||
private static string MastodonCurrentUserUri(string instance) =>
|
private static string MastodonCurrentUserUri(string instance) =>
|
||||||
$"https://{instance}/api/v1/accounts/verify_credentials";
|
$"https://{instance}/api/v1/accounts/verify_credentials";
|
||||||
|
|
||||||
private static string MastodonCurrentAppUri(string instance) =>
|
|
||||||
$"https://{instance}/api/v1/apps/verify_credentials";
|
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")]
|
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")]
|
||||||
private record PartialMastodonApplication(
|
private record PartialMastodonApplication(
|
||||||
[property: J("name")] string Name,
|
[property: J("name")] string Name,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
using Foxnouns.Backend.Database;
|
using Foxnouns.Backend.Database;
|
||||||
using Foxnouns.Backend.Database.Models;
|
using Foxnouns.Backend.Database.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NodaTime;
|
|
||||||
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
|
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
|
||||||
|
|
||||||
namespace Foxnouns.Backend.Services.Auth;
|
namespace Foxnouns.Backend.Services.Auth;
|
||||||
|
@ -10,26 +9,27 @@ public partial class FediverseAuthService
|
||||||
{
|
{
|
||||||
private const string NodeInfoRel = "http://nodeinfo.diaspora.software/ns/schema/2.0";
|
private const string NodeInfoRel = "http://nodeinfo.diaspora.software/ns/schema/2.0";
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly HttpClient _client;
|
private readonly HttpClient _client;
|
||||||
private readonly DatabaseContext _db;
|
private readonly ILogger _logger;
|
||||||
private readonly Config _config;
|
private readonly Config _config;
|
||||||
|
private readonly DatabaseContext _db;
|
||||||
|
private readonly KeyCacheService _keyCacheService;
|
||||||
private readonly ISnowflakeGenerator _snowflakeGenerator;
|
private readonly ISnowflakeGenerator _snowflakeGenerator;
|
||||||
private readonly IClock _clock;
|
|
||||||
|
|
||||||
public FediverseAuthService(
|
public FediverseAuthService(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
Config config,
|
Config config,
|
||||||
DatabaseContext db,
|
DatabaseContext db,
|
||||||
ISnowflakeGenerator snowflakeGenerator,
|
KeyCacheService keyCacheService,
|
||||||
IClock clock
|
ISnowflakeGenerator snowflakeGenerator
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
_logger = logger.ForContext<FediverseAuthService>();
|
||||||
_config = config;
|
_config = config;
|
||||||
_db = db;
|
_db = db;
|
||||||
|
_keyCacheService = keyCacheService;
|
||||||
_snowflakeGenerator = snowflakeGenerator;
|
_snowflakeGenerator = snowflakeGenerator;
|
||||||
_clock = clock;
|
|
||||||
_logger = logger.ForContext<FediverseAuthService>();
|
|
||||||
_client = new HttpClient();
|
_client = new HttpClient();
|
||||||
_client.DefaultRequestHeaders.Remove("User-Agent");
|
_client.DefaultRequestHeaders.Remove("User-Agent");
|
||||||
_client.DefaultRequestHeaders.Remove("Accept");
|
_client.DefaultRequestHeaders.Remove("Accept");
|
||||||
|
@ -37,10 +37,10 @@ public partial class FediverseAuthService
|
||||||
_client.DefaultRequestHeaders.Add("Accept", "application/json");
|
_client.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GenerateAuthUrlAsync(string instance)
|
public async Task<string> GenerateAuthUrlAsync(string instance, bool forceRefresh)
|
||||||
{
|
{
|
||||||
var app = await GetApplicationAsync(instance);
|
var app = await GetApplicationAsync(instance);
|
||||||
return await GenerateAuthUrlAsync(app);
|
return await GenerateAuthUrlAsync(app, forceRefresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
// thank you, gargron and syuilo, for agreeing on a name for *once* in your lives,
|
// thank you, gargron and syuilo, for agreeing on a name for *once* in your lives,
|
||||||
|
@ -96,21 +96,25 @@ public partial class FediverseAuthService
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> GenerateAuthUrlAsync(FediverseApplication app) =>
|
private async Task<string> GenerateAuthUrlAsync(FediverseApplication app, bool forceRefresh) =>
|
||||||
app.InstanceType switch
|
app.InstanceType switch
|
||||||
{
|
{
|
||||||
FediverseInstanceType.MastodonApi => await GenerateMastodonAuthUrlAsync(app),
|
FediverseInstanceType.MastodonApi => await GenerateMastodonAuthUrlAsync(
|
||||||
|
app,
|
||||||
|
forceRefresh
|
||||||
|
),
|
||||||
FediverseInstanceType.MisskeyApi => throw new NotImplementedException(),
|
FediverseInstanceType.MisskeyApi => throw new NotImplementedException(),
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(app), app.InstanceType, null),
|
_ => throw new ArgumentOutOfRangeException(nameof(app), app.InstanceType, null),
|
||||||
};
|
};
|
||||||
|
|
||||||
public async Task<FediverseUser> GetRemoteFediverseUserAsync(
|
public async Task<FediverseUser> GetRemoteFediverseUserAsync(
|
||||||
FediverseApplication app,
|
FediverseApplication app,
|
||||||
string code
|
string code,
|
||||||
|
string state
|
||||||
) =>
|
) =>
|
||||||
app.InstanceType switch
|
app.InstanceType switch
|
||||||
{
|
{
|
||||||
FediverseInstanceType.MastodonApi => await GetMastodonUserAsync(app, code),
|
FediverseInstanceType.MastodonApi => await GetMastodonUserAsync(app, code, state),
|
||||||
FediverseInstanceType.MisskeyApi => throw new NotImplementedException(),
|
FediverseInstanceType.MisskeyApi => throw new NotImplementedException(),
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(app), app.InstanceType, null),
|
_ => throw new ArgumentOutOfRangeException(nameof(app), app.InstanceType, null),
|
||||||
};
|
};
|
||||||
|
|
|
@ -96,6 +96,19 @@
|
||||||
"Mono.TextTemplating": "2.2.1"
|
"Mono.TextTemplating": "2.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Microsoft.Extensions.Caching.Memory": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[9.0.0, )",
|
||||||
|
"resolved": "9.0.0",
|
||||||
|
"contentHash": "zbnPX/JQ0pETRSUG9fNPBvpIq42Aufvs15gGYyNIMhCun9yhmWihz0WgsI7bSDPjxWTKBf8oX/zv6v2uZ3W9OQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.Extensions.Caching.Abstractions": "9.0.0",
|
||||||
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0",
|
||||||
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.0",
|
||||||
|
"Microsoft.Extensions.Options": "9.0.0",
|
||||||
|
"Microsoft.Extensions.Primitives": "9.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"Minio": {
|
"Minio": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[6.0.3, )",
|
"requested": "[6.0.3, )",
|
||||||
|
@ -246,6 +259,16 @@
|
||||||
"Swashbuckle.AspNetCore.SwaggerUI": "6.6.2"
|
"Swashbuckle.AspNetCore.SwaggerUI": "6.6.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"System.Text.Json": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[9.0.0, )",
|
||||||
|
"resolved": "9.0.0",
|
||||||
|
"contentHash": "js7+qAu/9mQvnhA4EfGMZNEzXtJCDxgkgj8ohuxq/Qxv+R56G+ljefhiJHOxTNiw54q8vmABCWUwkMulNdlZ4A==",
|
||||||
|
"dependencies": {
|
||||||
|
"System.IO.Pipelines": "9.0.0",
|
||||||
|
"System.Text.Encodings.Web": "9.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"System.Text.RegularExpressions": {
|
"System.Text.RegularExpressions": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[4.3.1, )",
|
"requested": "[4.3.1, )",
|
||||||
|
@ -412,22 +435,10 @@
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Caching.Abstractions": {
|
"Microsoft.Extensions.Caching.Abstractions": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "8.0.0",
|
"resolved": "9.0.0",
|
||||||
"contentHash": "3KuSxeHoNYdxVYfg2IRZCThcrlJ1XJqIXkAWikCsbm5C/bCjv7G0WoKDyuR98Q+T607QT2Zl5GsbGRkENcV2yQ==",
|
"contentHash": "FPWZAa9c0H4dvOj351iR1jkUIs4u9ykL4Bm592yhjDyO5lCoWd+TMAHx2EMbarzUvCvgjWjJIoC6//Q9kH6YhA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
"Microsoft.Extensions.Primitives": "9.0.0"
|
||||||
}
|
|
||||||
},
|
|
||||||
"Microsoft.Extensions.Caching.Memory": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.0.0",
|
|
||||||
"contentHash": "7pqivmrZDzo1ADPkRwjy+8jtRKWRCPag9qPI+p7sgu7Q4QreWhcvbiWXsbhP+yY8XSiDvZpu2/LWdBv7PnmOpQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Extensions.Caching.Abstractions": "8.0.0",
|
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
|
|
||||||
"Microsoft.Extensions.Options": "8.0.0",
|
|
||||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Configuration": {
|
"Microsoft.Extensions.Configuration": {
|
||||||
|
@ -465,8 +476,8 @@
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "8.0.1",
|
"resolved": "9.0.0",
|
||||||
"contentHash": "fGLiCRLMYd00JYpClraLjJTNKLmMJPnqxMaiRzEBIIvevlzxz33mXy39Lkd48hu1G+N21S7QpaO5ZzKsI6FRuA=="
|
"contentHash": "+6f2qv2a3dLwd5w6JanPIPs47CxRbnk+ZocMJUhv9NxP88VlOcJYZs9jY+MYSjxvady08bUZn6qgiNh7DadGgg=="
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.DependencyModel": {
|
"Microsoft.Extensions.DependencyModel": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
|
@ -542,10 +553,11 @@
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Logging.Abstractions": {
|
"Microsoft.Extensions.Logging.Abstractions": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "8.0.0",
|
"resolved": "9.0.0",
|
||||||
"contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==",
|
"contentHash": "g0UfujELzlLbHoVG8kPKVBaW470Ewi+jnptGS9KUi6jcb+k2StujtK3m26DFSGGwQ/+bVgZfsWqNzlP6YOejvw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0",
|
||||||
|
"System.Diagnostics.DiagnosticSource": "9.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Logging.Configuration": {
|
"Microsoft.Extensions.Logging.Configuration": {
|
||||||
|
@ -570,11 +582,11 @@
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Options": {
|
"Microsoft.Extensions.Options": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "8.0.0",
|
"resolved": "9.0.0",
|
||||||
"contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==",
|
"contentHash": "y2146b3jrPI3Q0lokKXdKLpmXqakYbDIPDV6r3M8SqvSf45WwOTzkyfDpxnZXJsJQEpAsAqjUq5Pu8RCJMjubg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0",
|
||||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
"Microsoft.Extensions.Primitives": "9.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Options.ConfigurationExtensions": {
|
"Microsoft.Extensions.Options.ConfigurationExtensions": {
|
||||||
|
@ -591,8 +603,8 @@
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Primitives": {
|
"Microsoft.Extensions.Primitives": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "8.0.0",
|
"resolved": "9.0.0",
|
||||||
"contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g=="
|
"contentHash": "N3qEBzmLMYiASUlKxxFIISP4AiwuPTHF5uCh+2CWSwwzAJiIYx0kBJsS30cp1nvhSySFAVi30jecD307jV+8Kg=="
|
||||||
},
|
},
|
||||||
"Microsoft.NETCore.Platforms": {
|
"Microsoft.NETCore.Platforms": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
|
@ -983,8 +995,8 @@
|
||||||
},
|
},
|
||||||
"System.Diagnostics.DiagnosticSource": {
|
"System.Diagnostics.DiagnosticSource": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "8.0.0",
|
"resolved": "9.0.0",
|
||||||
"contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ=="
|
"contentHash": "ddppcFpnbohLWdYKr/ZeLZHmmI+DXFgZ3Snq+/E7SwcdW4UnvxmaugkwGywvGVWkHPGCSZjCP+MLzu23AL5SDw=="
|
||||||
},
|
},
|
||||||
"System.Diagnostics.Tracing": {
|
"System.Diagnostics.Tracing": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
|
@ -1072,8 +1084,8 @@
|
||||||
},
|
},
|
||||||
"System.IO.Pipelines": {
|
"System.IO.Pipelines": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "6.0.3",
|
"resolved": "9.0.0",
|
||||||
"contentHash": "ryTgF+iFkpGZY1vRQhfCzX0xTdlV3pyaTTqRu2ETbEv+HlV7O6y7hyQURnghNIXvctl5DuZ//Dpks6HdL/Txgw=="
|
"contentHash": "eA3cinogwaNB4jdjQHOP3Z3EuyiDII7MT35jgtnsA4vkn0LUrrSHsU0nzHTzFzmaFYeKV7MYyMxOocFzsBHpTw=="
|
||||||
},
|
},
|
||||||
"System.Linq": {
|
"System.Linq": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
|
@ -1484,16 +1496,8 @@
|
||||||
},
|
},
|
||||||
"System.Text.Encodings.Web": {
|
"System.Text.Encodings.Web": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "8.0.0",
|
"resolved": "9.0.0",
|
||||||
"contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ=="
|
"contentHash": "e2hMgAErLbKyUUwt18qSBf9T5Y+SFAL3ZedM8fLupkVj8Rj2PZ9oxQ37XX2LF8fTO1wNIxvKpihD7Of7D/NxZw=="
|
||||||
},
|
|
||||||
"System.Text.Json": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "8.0.4",
|
|
||||||
"contentHash": "bAkhgDJ88XTsqczoxEMliSrpijKZHhbJQldhAmObj/RbrN3sU5dcokuXmWJWsdQAhiMJ9bTayWsL1C9fbbCRhw==",
|
|
||||||
"dependencies": {
|
|
||||||
"System.Text.Encodings.Web": "8.0.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"System.Threading": {
|
"System.Threading": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
|
|
Loading…
Reference in a new issue