feat: add mod action notice to login page

This commit is contained in:
Sam 2023-03-23 15:40:33 +01:00
parent f02e64fca7
commit ab77fab0ea
No known key found for this signature in database
GPG Key ID: B4EF20DDE721CAA1
10 changed files with 110 additions and 54 deletions

View File

@ -43,8 +43,10 @@ type discordCallbackResponse struct {
Ticket string `json:"ticket,omitempty"` Ticket string `json:"ticket,omitempty"`
RequireInvite bool `json:"require_invite"` // require an invite for signing up RequireInvite bool `json:"require_invite"` // require an invite for signing up
IsDeleted bool `json:"is_deleted"` IsDeleted bool `json:"is_deleted"`
DeletedAt *time.Time `json:"deleted_at,omitempty"` DeletedAt *time.Time `json:"deleted_at,omitempty"`
SelfDelete *bool `json:"self_delete,omitempty"`
DeleteReason *string `json:"delete_reason,omitempty"`
} }
func (s *Server) discordCallback(w http.ResponseWriter, r *http.Request) error { func (s *Server) discordCallback(w http.ResponseWriter, r *http.Request) error {
@ -81,7 +83,7 @@ func (s *Server) discordCallback(w http.ResponseWriter, r *http.Request) error {
u, err := s.DB.DiscordUser(ctx, du.ID) u, err := s.DB.DiscordUser(ctx, du.ID)
if err == nil { if err == nil {
if u.DeletedAt != nil && *u.SelfDelete { if u.DeletedAt != nil {
// store cancel delete token // store cancel delete token
token := undeleteToken() token := undeleteToken()
err = s.saveUndeleteToken(ctx, u.ID, token) err = s.saveUndeleteToken(ctx, u.ID, token)
@ -91,11 +93,13 @@ func (s *Server) discordCallback(w http.ResponseWriter, r *http.Request) error {
} }
render.JSON(w, r, discordCallbackResponse{ render.JSON(w, r, discordCallbackResponse{
HasAccount: true, HasAccount: true,
Token: token, Token: token,
User: dbUserToUserResponse(u, []db.Field{}), User: dbUserToUserResponse(u, []db.Field{}),
IsDeleted: true, IsDeleted: true,
DeletedAt: u.DeletedAt, DeletedAt: u.DeletedAt,
SelfDelete: u.SelfDelete,
DeleteReason: u.DeleteReason,
}) })
return nil return nil
} }

View File

@ -31,8 +31,10 @@ type fediCallbackResponse struct {
Ticket string `json:"ticket,omitempty"` Ticket string `json:"ticket,omitempty"`
RequireInvite bool `json:"require_invite"` // require an invite for signing up RequireInvite bool `json:"require_invite"` // require an invite for signing up
IsDeleted bool `json:"is_deleted"` IsDeleted bool `json:"is_deleted"`
DeletedAt *time.Time `json:"deleted_at,omitempty"` DeletedAt *time.Time `json:"deleted_at,omitempty"`
SelfDelete *bool `json:"self_delete,omitempty"`
DeleteReason *string `json:"delete_reason,omitempty"`
} }
type partialMastodonAccount struct { type partialMastodonAccount struct {
@ -102,7 +104,7 @@ func (s *Server) mastodonCallback(w http.ResponseWriter, r *http.Request) error
u, err := s.DB.FediverseUser(ctx, mu.ID, app.ID) u, err := s.DB.FediverseUser(ctx, mu.ID, app.ID)
if err == nil { if err == nil {
if u.DeletedAt != nil && *u.SelfDelete { if u.DeletedAt != nil {
// store cancel delete token // store cancel delete token
token := undeleteToken() token := undeleteToken()
err = s.saveUndeleteToken(ctx, u.ID, token) err = s.saveUndeleteToken(ctx, u.ID, token)
@ -112,11 +114,13 @@ func (s *Server) mastodonCallback(w http.ResponseWriter, r *http.Request) error
} }
render.JSON(w, r, fediCallbackResponse{ render.JSON(w, r, fediCallbackResponse{
HasAccount: true, HasAccount: true,
Token: token, Token: token,
User: dbUserToUserResponse(u, []db.Field{}), User: dbUserToUserResponse(u, []db.Field{}),
IsDeleted: true, IsDeleted: true,
DeletedAt: u.DeletedAt, DeletedAt: u.DeletedAt,
SelfDelete: u.SelfDelete,
DeleteReason: u.DeleteReason,
}) })
return nil return nil
} }

View File

@ -27,6 +27,16 @@ func (s *Server) cancelDelete(w http.ResponseWriter, r *http.Request) error {
return server.APIError{Code: server.ErrNotFound} // assume invalid token return server.APIError{Code: server.ErrNotFound} // assume invalid token
} }
// only self deleted users can undelete themselves
u, err := s.DB.User(ctx, id)
if err != nil {
log.Errorf("getting user: %v", err)
return errors.Wrap(err, "getting user")
}
if !*u.SelfDelete {
return server.APIError{Code: server.ErrForbidden}
}
err = s.DB.UndoDeleteUser(ctx, id) err = s.DB.UndoDeleteUser(ctx, id)
if err != nil { if err != nil {
log.Errorf("executing undelete query: %v", err) log.Errorf("executing undelete query: %v", err)

View File

@ -77,6 +77,11 @@ func (s *Server) resolveReport(w http.ResponseWriter, r *http.Request) error {
} }
if req.Delete { if req.Delete {
err = s.DB.InvalidateAllTokens(ctx, tx, report.UserID)
if err != nil {
return errors.Wrap(err, "invalidating tokens")
}
err = s.DB.CleanUser(ctx, report.UserID) err = s.DB.CleanUser(ctx, report.UserID)
if err != nil { if err != nil {
log.Errorf("cleaning user data: %v", err) log.Errorf("cleaning user data: %v", err)

View File

@ -5,6 +5,7 @@
import ErrorAlert from "$lib/components/ErrorAlert.svelte"; import ErrorAlert from "$lib/components/ErrorAlert.svelte";
import { userStore } from "$lib/store"; import { userStore } from "$lib/store";
import { addToast } from "$lib/toast"; import { addToast } from "$lib/toast";
import { DateTime } from "luxon";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { import {
Alert, Alert,
@ -26,6 +27,8 @@
export let token: string | undefined; export let token: string | undefined;
export let user: MeUser | undefined; export let user: MeUser | undefined;
export let deletedAt: string | undefined; export let deletedAt: string | undefined;
export let selfDelete: boolean | undefined;
export let deleteReason: string | undefined;
onMount(() => { onMount(() => {
if (!isDeleted && token && user) { if (!isDeleted && token && user) {
@ -136,8 +139,12 @@
</div> </div>
<Button type="submit" color="primary">Sign up</Button> <Button type="submit" color="primary">Sign up</Button>
</form> </form>
{:else if isDeleted && token} {:else if isDeleted && token && selfDelete && deletedAt}
<p>Your account is pending deletion since {deletedAt}.</p> <p>
Your account is pending deletion since {DateTime.fromISO(deletedAt)
.toLocal()
.toLocaleString(DateTime.DATETIME_MED)}.
</p>
<p>If you wish to cancel deletion, press the button below.</p> <p>If you wish to cancel deletion, press the button below.</p>
<p> <p>
<Button color="primary" on:click={cancelDelete} disabled={deleteCancelled} <Button color="primary" on:click={cancelDelete} disabled={deleteCancelled}
@ -152,34 +159,6 @@
<p> <p>
<Button color="link" on:click={toggleForceDeleteModal}>Force delete account</Button> <Button color="link" on:click={toggleForceDeleteModal}>Force delete account</Button>
</p> </p>
<Modal
header="Force delete account"
isOpen={forceDeleteModalOpen}
toggle={toggleForceDeleteModal}
>
<ModalBody>
<p>
If you want to delete your account, type your username below:
<br />
<b>
This is irreversible! Your account <i>cannot</i> be recovered after you press "Force delete
account".
</b>
</p>
<p>
<input type="text" class="form-control" bind:value={forceDeleteName} />
</p>
{#if deleteError}
<ErrorAlert error={deleteError} />
{/if}
</ModalBody>
<ModalFooter>
<Button color="danger" on:click={forceDeleteAccount} disabled={forceDeleteName !== user?.name}
>Force delete account</Button
>
<Button color="secondary" on:click={toggleForceDeleteModal}>Cancel delete</Button>
</ModalFooter>
</Modal>
{#if deleteCancelled} {#if deleteCancelled}
<Alert color="secondary" fade={false}> <Alert color="secondary" fade={false}>
Account deletion cancelled! You can now <a href="/auth/login">log in</a> again. Account deletion cancelled! You can now <a href="/auth/login">log in</a> again.
@ -188,6 +167,50 @@
{#if deleteError} {#if deleteError}
<ErrorAlert error={deleteError} /> <ErrorAlert error={deleteError} />
{/if} {/if}
{:else if isDeleted && token && !selfDelete && deletedAt}
<p>
Your account is pending deletion since {DateTime.fromISO(deletedAt)
.toLocal()
.toLocaleString(DateTime.DATETIME_MED)}.
</p>
<p>
<strong>Your account was deactivated by a moderator.</strong> You cannot cancel deletion. The moderator
gave the following reason:
</p>
<blockquote class="blockquote">
{deleteReason}
</blockquote>
<p>
Your account will be fully deleted 180 days after being deactivated. If you want your data wiped
immediately instead, press the force delete link below.
</p>
<p>
<Button color="link" on:click={toggleForceDeleteModal}>Force delete account</Button>
</p>
{:else} {:else}
Loading... Loading...
{/if} {/if}
<Modal header="Force delete account" isOpen={forceDeleteModalOpen} toggle={toggleForceDeleteModal}>
<ModalBody>
<p>
If you want to delete your account, type your username (<code>{user.name}</code>) below:
<br />
<b>
This is irreversible! Your account <i>cannot</i> be recovered after you press "Force delete account".
</b>
</p>
<p>
<input type="text" class="form-control" bind:value={forceDeleteName} />
</p>
{#if deleteError}
<ErrorAlert error={deleteError} />
{/if}
</ModalBody>
<ModalFooter>
<Button color="danger" on:click={forceDeleteAccount} disabled={forceDeleteName !== user?.name}
>Force delete account</Button
>
<Button color="secondary" on:click={toggleForceDeleteModal}>Cancel delete</Button>
</ModalFooter>
</Modal>

View File

@ -33,4 +33,6 @@ interface CallbackResponse {
is_deleted: boolean; is_deleted: boolean;
deleted_at?: string; deleted_at?: string;
self_delete?: boolean;
delete_reason?: string;
} }

View File

@ -57,6 +57,8 @@
token={data.token} token={data.token}
user={data.user} user={data.user}
deletedAt={data.deleted_at} deletedAt={data.deleted_at}
selfDelete={data.self_delete}
deleteReason={data.delete_reason}
{linkAccount} {linkAccount}
{signupForm} {signupForm}
/> />

View File

@ -33,4 +33,6 @@ interface CallbackResponse {
is_deleted: boolean; is_deleted: boolean;
deleted_at?: string; deleted_at?: string;
self_delete?: boolean;
delete_reason?: string;
} }

View File

@ -59,6 +59,8 @@
token={data.token} token={data.token}
user={data.user} user={data.user}
deletedAt={data.deleted_at} deletedAt={data.deleted_at}
selfDelete={data.self_delete}
deleteReason={data.delete_reason}
{linkAccount} {linkAccount}
{signupForm} {signupForm}
/> />

View File

@ -48,14 +48,16 @@
<div> <div>
{#each data.reports as report, index} {#each data.reports as report, index}
<ReportCard {report}> <div class="my-2">
&bull; <ReportCard {report}>
<Button outline color="warning" size="sm" on:click={() => openWarnModalFor(index)} &bull;
>Warn user</Button <Button outline color="warning" size="sm" on:click={() => openWarnModalFor(index)}
> >Warn user</Button
<Button outline color="danger" size="sm">Deactivate user</Button> >
<Button outline color="secondary" size="sm">Ignore report</Button> <Button outline color="danger" size="sm">Deactivate user</Button>
</ReportCard> <Button outline color="secondary" size="sm">Ignore report</Button>
</ReportCard>
</div>
{/each} {/each}
</div> </div>