Compare commits

...

3 commits

11 changed files with 52 additions and 23 deletions

View file

@ -44,7 +44,7 @@ public class FediverseAuthController(
[ProducesResponseType<SingleUrlResponse>(statusCode: StatusCodes.Status200OK)] [ProducesResponseType<SingleUrlResponse>(statusCode: StatusCodes.Status200OK)]
public async Task<IActionResult> GetFediverseUrlAsync( public async Task<IActionResult> GetFediverseUrlAsync(
[FromQuery] string instance, [FromQuery] string instance,
[FromQuery] bool forceRefresh = false [FromQuery(Name = "force-refresh")] bool forceRefresh = false
) )
{ {
if (instance.Any(c => c is '@' or ':' or '/') || !instance.Contains('.')) if (instance.Any(c => c is '@' or ':' or '/') || !instance.Contains('.'))
@ -139,7 +139,7 @@ public class FediverseAuthController(
[Authorize("*")] [Authorize("*")]
public async Task<IActionResult> AddFediverseAccountAsync( public async Task<IActionResult> AddFediverseAccountAsync(
[FromQuery] string instance, [FromQuery] string instance,
[FromQuery] bool forceRefresh = false [FromQuery(Name = "force-refresh")] bool forceRefresh = false
) )
{ {
if (instance.Any(c => c is '@' or ':' or '/') || !instance.Contains('.')) if (instance.Any(c => c is '@' or ':' or '/') || !instance.Contains('.'))

View file

@ -173,6 +173,9 @@ public class ReportsController(
public async Task<IActionResult> GetReportsAsync( public async Task<IActionResult> GetReportsAsync(
[FromQuery] int? limit = null, [FromQuery] int? limit = null,
[FromQuery] Snowflake? before = null, [FromQuery] Snowflake? before = null,
[FromQuery] Snowflake? after = null,
[FromQuery(Name = "by-reporter")] Snowflake? byReporter = null,
[FromQuery(Name = "by-target")] Snowflake? byTarget = null,
[FromQuery(Name = "include-closed")] bool includeClosed = false [FromQuery(Name = "include-closed")] bool includeClosed = false
) )
{ {
@ -187,11 +190,21 @@ public class ReportsController(
IQueryable<Report> query = db IQueryable<Report> query = db
.Reports.Include(r => r.Reporter) .Reports.Include(r => r.Reporter)
.Include(r => r.TargetUser) .Include(r => r.TargetUser)
.Include(r => r.TargetMember) .Include(r => r.TargetMember);
.OrderByDescending(r => r.Id);
if (byTarget != null && await db.Users.AnyAsync(u => u.Id == byTarget.Value))
query = query.Where(r => r.TargetUserId == byTarget.Value);
if (byReporter != null && await db.Users.AnyAsync(u => u.Id == byReporter.Value))
query = query.Where(r => r.ReporterId == byReporter.Value);
if (before != null) if (before != null)
query = query.Where(r => r.Id < before.Value); query = query.Where(r => r.Id < before.Value).OrderByDescending(r => r.Id);
else if (after != null)
query = query.Where(r => r.Id > after.Value).OrderBy(r => r.Id);
else
query = query.OrderByDescending(r => r.Id);
if (!includeClosed) if (!includeClosed)
query = query.Where(r => r.Status == ReportStatus.Open); query = query.Where(r => r.Status == ReportStatus.Open);

View file

@ -48,6 +48,7 @@ public record UserResponse(
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] Instant? LastActive, [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] Instant? LastActive,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] Instant? LastSidReroll, [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] Instant? LastSidReroll,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? Timezone, [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? Timezone,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] bool? Suspended,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] bool? Deleted [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] bool? Deleted
); );

View file

@ -23,26 +23,30 @@ public class LimitMiddleware : IMiddleware
Endpoint? endpoint = ctx.GetEndpoint(); Endpoint? endpoint = ctx.GetEndpoint();
LimitAttribute? attribute = endpoint?.Metadata.GetMetadata<LimitAttribute>(); LimitAttribute? attribute = endpoint?.Metadata.GetMetadata<LimitAttribute>();
Token? token = ctx.GetToken();
if (attribute == null) if (attribute == null)
{ {
await next(ctx); // Check for authorize attribute
return; // If it exists, and the user is deleted, throw an error.
}
Token? token = ctx.GetToken();
if ( if (
token?.User.Deleted == true endpoint?.Metadata.GetMetadata<AuthorizeAttribute>() != null
&& (!attribute.UsableBySuspendedUsers || token.User.DeletedBy == null) && token?.User.Deleted == true
) )
{ {
throw new ApiError.Forbidden("Deleted users cannot access this endpoint."); throw new ApiError.Forbidden("Deleted users cannot access this endpoint.");
} }
if (attribute.RequireAdmin && token?.User.Role != UserRole.Admin) await next(ctx);
{ return;
throw new ApiError.Forbidden("This endpoint can only be used by admins.");
} }
if (token?.User.Deleted == true && !attribute.UsableBySuspendedUsers)
throw new ApiError.Forbidden("Deleted users cannot access this endpoint.");
if (attribute.RequireAdmin && token?.User.Role != UserRole.Admin)
throw new ApiError.Forbidden("This endpoint can only be used by admins.");
if ( if (
attribute.RequireModerator attribute.RequireModerator
&& token?.User.Role is not (UserRole.Admin or UserRole.Moderator) && token?.User.Role is not (UserRole.Admin or UserRole.Moderator)

View file

@ -21,7 +21,6 @@ using Newtonsoft.Json.Linq;
namespace Foxnouns.Backend.Services; namespace Foxnouns.Backend.Services;
public class ModerationRendererService( public class ModerationRendererService(
DatabaseContext db,
UserRendererService userRenderer, UserRendererService userRenderer,
MemberRendererService memberRenderer MemberRendererService memberRenderer
) )

View file

@ -115,6 +115,7 @@ public class UserRendererService(
tokenHidden ? user.LastActive : null, tokenHidden ? user.LastActive : null,
tokenHidden ? user.LastSidReroll : null, tokenHidden ? user.LastSidReroll : null,
tokenHidden ? user.Timezone ?? "<none>" : null, tokenHidden ? user.Timezone ?? "<none>" : null,
tokenHidden ? user is { Deleted: true, DeletedBy: not null } : null,
tokenHidden ? user.Deleted : null tokenHidden ? user.Deleted : null
); );
} }

View file

@ -26,6 +26,7 @@ export type MeUser = UserWithMembers & {
last_active: string; last_active: string;
last_sid_reroll: string; last_sid_reroll: string;
timezone: string; timezone: string;
suspended: boolean;
deleted: boolean; deleted: boolean;
}; };

View file

@ -21,10 +21,17 @@
</script> </script>
{#if user && user.deleted} {#if user && user.deleted}
<div class="suspended-alert text-center py-3 mb-2 px-2"> <div class="deleted-alert text-center py-3 mb-2 px-2">
{#if user.suspended}
<strong>{$t("nav.suspended-account-hint")}</strong> <strong>{$t("nav.suspended-account-hint")}</strong>
<br /> <br />
<a href="/contact">{$t("nav.appeal-suspension-link")}</a> <a href="/contact">{$t("nav.appeal-suspension-link")}</a>
{:else}
<strong>{$t("nav.deleted-account-hint")}</strong>
<br />
<a href="/settings/reactivate">{$t("nav.reactivate-account-link")}</a>
<a href="/contact">{$t("nav.delete-permanently-link")}</a>
{/if}
</div> </div>
{/if} {/if}
@ -66,7 +73,7 @@
</Navbar> </Navbar>
<style> <style>
.suspended-alert { .deleted-alert {
color: var(--bs-danger-text-emphasis); color: var(--bs-danger-text-emphasis);
background-color: var(--bs-danger-bg-subtle); background-color: var(--bs-danger-bg-subtle);
} }

View file

@ -4,7 +4,10 @@
"log-in": "Log in or sign up", "log-in": "Log in or sign up",
"settings": "Settings", "settings": "Settings",
"suspended-account-hint": "Your account has been suspended. Your profile has been hidden and you will not be able to change any settings.", "suspended-account-hint": "Your account has been suspended. Your profile has been hidden and you will not be able to change any settings.",
"appeal-suspension-link": "I want to appeal" "appeal-suspension-link": "I want to appeal",
"deleted-account-hint": "You have requested deletion of your account. If you want to reactivate it, click the link below.",
"reactivate-account-link": "Reactivate account",
"delete-permanently-link": "I want my account deleted permanently"
}, },
"avatar-tooltip": "Avatar for {{name}}", "avatar-tooltip": "Avatar for {{name}}",
"profile": { "profile": {

View file

@ -65,7 +65,7 @@ export const actions = {
try { try {
const resp = await apiRequest<{ url: string }>( const resp = await apiRequest<{ url: string }>(
"GET", "GET",
`/auth/fediverse?instance=${encodeURIComponent(instance)}&forceRefresh=true`, `/auth/fediverse?instance=${encodeURIComponent(instance)}&force-refresh=true`,
{ fetch, isInternal: true }, { fetch, isInternal: true },
); );
redirect(303, resp.url); redirect(303, resp.url);

View file

@ -24,7 +24,7 @@ export const actions = {
const { url } = await apiRequest<{ url: string }>( const { url } = await apiRequest<{ url: string }>(
"GET", "GET",
`/auth/fediverse/add-account?instance=${encodeURIComponent(instance)}&forceRefresh=true`, `/auth/fediverse/add-account?instance=${encodeURIComponent(instance)}&force-refresh=true`,
{ {
isInternal: true, isInternal: true,
fetch, fetch,