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…
	
	Add table
		Add a link
		
	
		Reference in a new issue