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
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
10 changed files with 110 additions and 54 deletions

View file

@ -45,6 +45,8 @@ type discordCallbackResponse struct {
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)
@ -96,6 +98,8 @@ func (s *Server) discordCallback(w http.ResponseWriter, r *http.Request) error {
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

@ -33,6 +33,8 @@ type fediCallbackResponse struct {
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)
@ -117,6 +119,8 @@ func (s *Server) mastodonCallback(w http.ResponseWriter, r *http.Request) error
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,18 +159,45 @@
<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 {#if deleteCancelled}
header="Force delete account" <Alert color="secondary" fade={false}>
isOpen={forceDeleteModalOpen} Account deletion cancelled! You can now <a href="/auth/login">log in</a> again.
toggle={toggleForceDeleteModal} </Alert>
> {/if}
{#if deleteError}
<ErrorAlert error={deleteError} />
{/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}
Loading...
{/if}
<Modal header="Force delete account" isOpen={forceDeleteModalOpen} toggle={toggleForceDeleteModal}>
<ModalBody> <ModalBody>
<p> <p>
If you want to delete your account, type your username below: If you want to delete your account, type your username (<code>{user.name}</code>) below:
<br /> <br />
<b> <b>
This is irreversible! Your account <i>cannot</i> be recovered after you press "Force delete This is irreversible! Your account <i>cannot</i> be recovered after you press "Force delete account".
account".
</b> </b>
</p> </p>
<p> <p>
@ -179,15 +213,4 @@
> >
<Button color="secondary" on:click={toggleForceDeleteModal}>Cancel delete</Button> <Button color="secondary" on:click={toggleForceDeleteModal}>Cancel delete</Button>
</ModalFooter> </ModalFooter>
</Modal> </Modal>
{#if deleteCancelled}
<Alert color="secondary" fade={false}>
Account deletion cancelled! You can now <a href="/auth/login">log in</a> again.
</Alert>
{/if}
{#if deleteError}
<ErrorAlert error={deleteError} />
{/if}
{:else}
Loading...
{/if}

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