diff --git a/.idea/.idea.Iceshrimp.NET/.idea/inspectionProfiles/Project_Default.xml b/.idea/.idea.Iceshrimp.NET/.idea/inspectionProfiles/Project_Default.xml index 9f91588a..e9d94835 100644 --- a/.idea/.idea.Iceshrimp.NET/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/.idea.Iceshrimp.NET/.idea/inspectionProfiles/Project_Default.xml @@ -2,6 +2,11 @@ \ No newline at end of file diff --git a/Iceshrimp.Backend/Controllers/Mastodon/AuthController.cs b/Iceshrimp.Backend/Controllers/Mastodon/AuthController.cs index 29900b75..9cda530a 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/AuthController.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/AuthController.cs @@ -1,6 +1,7 @@ using System.Net; using System.Net.Mime; using Iceshrimp.Backend.Controllers.Mastodon.Attributes; +using Iceshrimp.Backend.Controllers.Pleroma.Schemas.Entities; using Iceshrimp.Backend.Controllers.Shared.Attributes; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; @@ -8,6 +9,7 @@ using Iceshrimp.Backend.Core.Extensions; using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Services; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.RateLimiting; @@ -157,4 +159,44 @@ public class AuthController(DatabaseContext db, MetaService meta) : ControllerBa return new object(); } + + [Authenticate] + [HttpGet("/api/oauth_tokens.json")] + [ProducesResults(HttpStatusCode.OK)] + public async Task> GetOauthTokens() + { + var user = HttpContext.GetUserOrFail(); + var oauthTokens = await db.OauthTokens + .Where(p => p.User == user) + .Include(oauthToken => oauthToken.App) + .ToListAsync(); + + List result = []; + foreach (var token in oauthTokens) + { + result.Add(new PleromaOauthTokenEntity() + { + Id = token.Id, + AppName = token.App.Name, + ValidUntil = token.CreatedAt + TimeSpan.FromDays(365 * 100) + }); + } + + return result; + } + + [Authenticate] + [HttpDelete("/api/oauth_tokens/{id}")] + [ProducesResults(HttpStatusCode.Created)] + [ProducesErrors(HttpStatusCode.BadRequest, HttpStatusCode.Forbidden)] + public async Task RevokeOauthTokenPleroma(string id) + { + var token = await db.OauthTokens.FirstOrDefaultAsync(p => p.Id == id) ?? + throw GracefulException.Forbidden("You are not authorized to revoke this token"); + + db.Remove(token); + await db.SaveChangesAsync(); + + Response.StatusCode = 201; + } } \ No newline at end of file diff --git a/Iceshrimp.Backend/Controllers/Mastodon/Renderers/UserRenderer.cs b/Iceshrimp.Backend/Controllers/Mastodon/Renderers/UserRenderer.cs index b793465d..ca0b163d 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/Renderers/UserRenderer.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/Renderers/UserRenderer.cs @@ -1,9 +1,11 @@ using Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities; +using Iceshrimp.Backend.Controllers.Pleroma.Schemas.Entities; using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Extensions; using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion; +using Iceshrimp.Backend.Core.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; @@ -13,7 +15,8 @@ public class UserRenderer( IOptions config, IOptionsSnapshot security, MfmConverter mfmConverter, - DatabaseContext db + DatabaseContext db, + FlagService flags ) : IScopedService { private readonly string _transparent = $"https://{config.Value.WebDomain}/assets/transparent.png"; @@ -48,6 +51,25 @@ public class UserRenderer( var avatarAlt = data?.AvatarAlt.GetValueOrDefault(user.Id); var bannerAlt = data?.BannerAlt.GetValueOrDefault(user.Id); + string? favicon; + string? softwareName; + string? softwareVersion; + if (user.IsRemoteUser) + { + var instInfo = await db.Instances + .Where(p => p.Host == user.Host) + .FirstOrDefaultAsync(); + favicon = instInfo?.FaviconUrl; + softwareName = instInfo?.SoftwareName; + softwareVersion = instInfo?.SoftwareVersion; + } + else + { + favicon = config.Value.WebDomain + "/_content/Iceshrimp.Assets.Branding/favicon.png"; + softwareName = "iceshrimp"; + softwareVersion = config.Value.Version; + } + var res = new AccountEntity { Id = user.Id, @@ -74,7 +96,30 @@ public class UserRenderer( IsBot = user.IsBot, IsDiscoverable = user.IsExplorable, Fields = fields?.ToList() ?? [], - Emoji = profileEmoji + Emoji = profileEmoji, + Pleroma = flags?.IsPleroma.Value == true + ? new PleromaUserExtensions + { + IsAdmin = user.IsAdmin, + IsModerator = user.IsModerator, + Favicon = favicon! + } : null, + Akkoma = flags?.IsPleroma.Value == true + ? new AkkomaUserExtensions + { + Instance = new AkkomaInstanceEntity + { + Name = user.Host ?? config.Value.AccountDomain, + NodeInfo = new AkkomaNodeInfoEntity + { + Software = new AkkomaNodeInfoSoftwareEntity + { + Name = softwareName, + Version = softwareVersion + } + } + } + } : null }; if (localUser is null && security.Value.PublicPreview == Enums.PublicPreview.RestrictedNoMedia) //TODO diff --git a/Iceshrimp.Backend/Controllers/Mastodon/Schemas/Entities/AccountEntity.cs b/Iceshrimp.Backend/Controllers/Mastodon/Schemas/Entities/AccountEntity.cs index 576fa109..84daa6f6 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/Schemas/Entities/AccountEntity.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/Schemas/Entities/AccountEntity.cs @@ -1,3 +1,4 @@ +using Iceshrimp.Backend.Controllers.Pleroma.Schemas.Entities; using Iceshrimp.Shared.Helpers; using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; @@ -5,30 +6,32 @@ namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities; public class AccountEntity : IIdentifiable { - [J("username")] public required string Username { get; set; } - [J("acct")] public required string Acct { get; set; } - [J("fqn")] public required string FullyQualifiedName { get; set; } - [J("display_name")] public required string DisplayName { get; set; } - [J("locked")] public required bool IsLocked { get; set; } - [J("created_at")] public required string CreatedAt { get; set; } - [J("followers_count")] public required long FollowersCount { get; set; } - [J("following_count")] public required long FollowingCount { get; set; } - [J("statuses_count")] public required long StatusesCount { get; set; } - [J("note")] public required string Note { get; set; } - [J("url")] public required string Url { get; set; } - [J("uri")] public required string Uri { get; set; } - [J("avatar")] public required string AvatarUrl { get; set; } - [J("avatar_static")] public required string AvatarStaticUrl { get; set; } - [J("header")] public required string HeaderUrl { get; set; } - [J("header_static")] public required string HeaderStaticUrl { get; set; } - [J("moved")] public required AccountEntity? MovedToAccount { get; set; } - [J("bot")] public required bool IsBot { get; set; } - [J("discoverable")] public required bool IsDiscoverable { get; set; } - [J("fields")] public required List Fields { get; set; } - [J("source")] public AccountSource? Source { get; set; } - [J("emojis")] public required List Emoji { get; set; } - [J("id")] public required string Id { get; set; } - [J("last_status_at")] public string? LastStatusAt { get; set; } + [J("username")] public required string Username { get; set; } + [J("acct")] public required string Acct { get; set; } + [J("fqn")] public required string FullyQualifiedName { get; set; } + [J("display_name")] public required string DisplayName { get; set; } + [J("locked")] public required bool IsLocked { get; set; } + [J("created_at")] public required string CreatedAt { get; set; } + [J("followers_count")] public required long FollowersCount { get; set; } + [J("following_count")] public required long FollowingCount { get; set; } + [J("statuses_count")] public required long StatusesCount { get; set; } + [J("note")] public required string Note { get; set; } + [J("url")] public required string Url { get; set; } + [J("uri")] public required string Uri { get; set; } + [J("avatar")] public required string AvatarUrl { get; set; } + [J("avatar_static")] public required string AvatarStaticUrl { get; set; } + [J("header")] public required string HeaderUrl { get; set; } + [J("header_static")] public required string HeaderStaticUrl { get; set; } + [J("moved")] public required AccountEntity? MovedToAccount { get; set; } + [J("bot")] public required bool IsBot { get; set; } + [J("discoverable")] public required bool IsDiscoverable { get; set; } + [J("fields")] public required List Fields { get; set; } + [J("source")] public AccountSource? Source { get; set; } + [J("emojis")] public required List Emoji { get; set; } + [J("id")] public required string Id { get; set; } + [J("last_status_at")] public string? LastStatusAt { get; set; } + [J("pleroma")] public required PleromaUserExtensions? Pleroma { get; set; } + [J("akkoma")] public required AkkomaUserExtensions? Akkoma { get; set; } [J("avatar_description")] public required string AvatarDescription { get; set; } [J("header_description")] public required string HeaderDescription { get; set; } diff --git a/Iceshrimp.Backend/Properties/launchSettings.json b/Iceshrimp.Backend/Properties/launchSettings.json index 8406b1ed..4a336233 100644 --- a/Iceshrimp.Backend/Properties/launchSettings.json +++ b/Iceshrimp.Backend/Properties/launchSettings.json @@ -6,6 +6,7 @@ "dotnetRunMessages": true, "launchBrowser": false, "externalUrlConfiguration": true, + "commandLineArgs": "--migrate-and-start", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/Iceshrimp.Backend/configuration.ini b/Iceshrimp.Backend/configuration.ini index b72a8e8e..b5b2c20a 100644 --- a/Iceshrimp.Backend/configuration.ini +++ b/Iceshrimp.Backend/configuration.ini @@ -7,8 +7,8 @@ ListenHost = localhost ;;ListenSocketPerms = 660 ;; Caution: changing these settings after initial setup *will* break federation -WebDomain = shrimp.example.org -AccountDomain = example.org +WebDomain = localhost:3000 +AccountDomain = localhost:3000 ;; End of problematic settings block ;; Additional domains this instance allows API access from, separated by commas. @@ -184,7 +184,7 @@ ProxyRemoteMedia = true [Storage:Local] ;; Path where media is stored at. Must be writable for the service user. -Path = /path/to/media/location +Path = /home/luke/Documents/shrimp [Storage:ObjectStorage] ;;Endpoint = endpoint.example.org