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