add account deletion

This commit is contained in:
not-nullptr 2024-03-10 03:38:25 +00:00
parent c5686166d7
commit 5351c54d8d
9 changed files with 110 additions and 37 deletions

2
package-lock.json generated
View file

@ -11,6 +11,7 @@
"@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/enhanced-img": "^0.1.8",
"better-sqlite3": "^9.4.3",
"cookie": "^0.6.0",
"reflect-metadata": "^0.2.1",
"sequelize": "^6.37.1",
"sqlite3": "^5.1.7",
@ -22,6 +23,7 @@
"@sveltejs/adapter-node": "^5.0.1",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@types/cookie": "^0.6.0",
"autoprefixer": "^10.4.16",
"express": "^4.18.3",
"flowbite": "^2.3.0",

View file

@ -17,6 +17,7 @@
"@sveltejs/adapter-node": "^5.0.1",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@types/cookie": "^0.6.0",
"autoprefixer": "^10.4.16",
"express": "^4.18.3",
"flowbite": "^2.3.0",
@ -43,6 +44,7 @@
"@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/enhanced-img": "^0.1.8",
"better-sqlite3": "^9.4.3",
"cookie": "^0.6.0",
"reflect-metadata": "^0.2.1",
"sequelize": "^6.37.1",
"sqlite3": "^5.1.7",

View file

@ -1,8 +1,16 @@
import type { CreateAccountRequest, CreateAccountResponse } from "$types/api";
import type { CreateAccountRequest, CreateAccountResponse, GetUserResponse } from "$types/api";
const apiUsers = {
async createAccount(body: CreateAccountRequest): Promise<CreateAccountResponse> {
return await SuyuAPI.req("POST", "/api/user/create-account", body);
return await SuyuAPI.req("POST", "/api/user", body);
},
async deleteAccount() {
return await SuyuAPI.req("DELETE", "/api/user");
},
async getUser(): Promise<GetUserResponse> {
return await SuyuAPI.req("GET", "/api/user");
},
} as const;

View file

@ -1,8 +1,12 @@
import { userRepo } from "$lib/server/repo";
import type { SuyuUser } from "$lib/server/schema";
import cookie from "cookie";
export async function useAuth(request: Request | string): Promise<SuyuUser | null> {
const apiKey = typeof request === "string" ? request : request.headers.get("Authorization");
const apiKey =
typeof request === "string"
? request
: cookie.parse(request.headers.get("cookie") || "").token;
if (!apiKey) {
return null;
}

View file

@ -1,10 +1,10 @@
import { useAuth } from "$lib/util/api";
export async function load(opts) {
const apiKey = opts.cookies.get("api_key");
const apiKey = opts.cookies.get("token");
const user = await useAuth(apiKey || "unused");
return {
user: { ...user },
a: "B",
token: apiKey,
};
}

View file

@ -7,28 +7,50 @@
export let data: PageData;
let usernameToCreate: string;
let createBtn: HTMLButtonElement;
async function createAccount() {
createBtn.disabled = true;
const response = await SuyuAPI.users.createAccount({ username: usernameToCreate });
if (response.success) {
data = {
...(data || {}),
user: response.user,
token: response.token,
};
// add api_key cookie
document.cookie = `api_key=${response.token}; path=/`;
// add token cookie
document.cookie = `token=${response.token}; path=/`;
} else {
alert("Failed to create account: " + response.error);
window.location.reload();
}
usernameToCreate = "";
createBtn.disabled = false;
}
async function deleteAccount() {
const response = await SuyuAPI.users.deleteAccount();
if (response.success) {
data = {
...(data || {}),
// @ts-expect-error since we're deleting the account, we can't expect the user to still exist
user: undefined,
token: undefined,
};
// remove token cookie
document.cookie = "token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
} else {
alert("Failed to delete account: " + response.error);
}
}
</script>
<div class="panel-blur main-panel">
<h2>Account Settings</h2>
<p>
{#if data?.user}
{#if data?.token && data?.user && data.user.username}
<p>Username: {data.user.username}</p>
<p>Token: <code>{data.token}</code></p>
{:else}
<p>
It appears you don't have an account; please register one to access suyu's online
@ -36,10 +58,13 @@
</p>
<div class="create-account">
<input bind:value={usernameToCreate} type="text" placeholder="Username" />
<button on:click={createAccount}>Create Account</button>
<button bind:this={createBtn} on:click={createAccount}>Create Account</button>
</div>
{/if}
</p>
<div class="float-bottom-right">
<button class="danger" on:click={deleteAccount}>Delete Account</button>
</div>
</div>
<style>
@ -65,4 +90,19 @@
.create-account {
margin-top: 16px;
}
.main-panel code {
background-color: #222429;
border: var(--border-primary);
padding: 2px 8px;
border-radius: 4px;
user-select: all;
}
.float-bottom-right {
position: absolute;
bottom: 0;
right: 0;
margin: 16px;
}
</style>

View file

@ -3,7 +3,13 @@
import { userRepo } from "$lib/server/repo";
import type { SuyuUser } from "$lib/server/schema";
import { json, serializeRoles } from "$lib/server/util";
import type { CreateAccountRequest, CreateAccountResponse } from "$types/api";
import { useAuth } from "$lib/util/api";
import type {
CreateAccountRequest,
CreateAccountResponse,
DeleteAccountResponse,
GetUserResponse,
} from "$types/api";
import crypto from "crypto";
import { promisify } from "util";
@ -43,3 +49,31 @@ export async function POST({ request }) {
user: createdUser,
});
}
export async function GET({ request }) {
const user = await useAuth(request);
if (!user) {
return json<GetUserResponse>({
success: false,
error: "unauthorized",
});
}
return json<GetUserResponse>({
success: true,
user,
});
}
export async function DELETE({ request }) {
const user = await useAuth(request);
if (!user) {
return json<DeleteAccountResponse>({
success: false,
error: "unauthorized",
});
}
await userRepo.remove(user);
return json<DeleteAccountResponse>({
success: true,
});
}

View file

@ -1,17 +0,0 @@
import { json } from "$lib/server/util";
import { useAuth } from "$lib/util/api";
import type { GetUserResponse } from "$types/api";
export async function GET({ request }) {
const user = await useAuth(request);
if (!user) {
return json<GetUserResponse>({
success: false,
error: "unauthorized",
});
}
return json<GetUserResponse>({
success: true,
user,
});
}

20
src/types/api.d.ts vendored
View file

@ -2,6 +2,11 @@
import type { SuyuUser } from "$lib/server/schema";
export interface GenericFailureResponse {
success: false;
error: string;
}
export interface CreateAccountRequest {
username: string;
}
@ -11,22 +16,17 @@ export interface CreateAccountResponseSuccess {
user: SuyuUser;
token: string;
}
export type CreateAccountResponse = CreateAccountResponseSuccess | GenericFailureResponse;
export interface CreateAccountResponseFailure {
success: false;
error: string;
export interface DeleteAccountResponseSuccess {
success: true;
}
export type CreateAccountResponse = CreateAccountResponseSuccess | CreateAccountResponseFailure;
export type DeleteAccountResponse = DeleteAccountResponseSuccess | GenericFailureResponse;
export interface GetUserResponseSuccess {
success: true;
user: SuyuUser;
}
export interface GetUserResponseFailure {
success: false;
error: string;
}
export type GetUserResponse = GetUserResponseSuccess | GetUserResponseFailure;
export type GetUserResponse = GetUserResponseSuccess | GenericFailureResponse;