+
diff --git a/src/routes/(app)/uploads/+page.server.ts b/src/routes/(app)/uploads/+page.server.ts
index b7c9556..3883b88 100644
--- a/src/routes/(app)/uploads/+page.server.ts
+++ b/src/routes/(app)/uploads/+page.server.ts
@@ -3,13 +3,11 @@ import { error } from '@sveltejs/kit';
export async function load({ locals, url }) {
if (!locals.user) return error(403, { status: 403, message: 'Forbidden' });
-
if (+(url.searchParams.get('i') || 0) < 0) error(400, { status: 403, message: 'Invalid Index' });
- const totalUploads = await prisma.upload.count();
+ const totalUploads = await prisma.upload.count({ where: { uploaderId: locals.user.id } });
if (!totalUploads) return { uploads: [], totalUploads: 0 };
-
if (+(url.searchParams.get('i') || 0) >= Math.ceil(totalUploads / 15))
error(400, { status: 403, message: 'Invalid Index' });
diff --git a/src/routes/api/+server.js b/src/routes/api/+server.ts
similarity index 52%
rename from src/routes/api/+server.js
rename to src/routes/api/+server.ts
index f750c3b..9633400 100644
--- a/src/routes/api/+server.js
+++ b/src/routes/api/+server.ts
@@ -1,4 +1,3 @@
-/** @type {import('./$types').RequestHandler} */
export function GET() {
return new Response('OK');
}
diff --git a/src/routes/api/v1/auth/login/+server.js b/src/routes/api/v1/auth/login/+server.js
deleted file mode 100644
index aaf2a15..0000000
--- a/src/routes/api/v1/auth/login/+server.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import { json } from '@sveltejs/kit';
-import { COOKIE } from '$lib/config';
-import { findUser, createSession } from '$lib/server/database';
-import { email } from '$lib/server/validator';
-import { verifyHash } from '$lib/server/crypto';
-
-/** @type {import('./$types').RequestHandler} */
-export async function POST(event) {
- const { request, cookies } = event;
- const body = await request.json();
-
- if (!body?.email || !email(body?.email))
- return json({ error: 'Invalid email.' }, { status: 400 });
-
- if (!body?.password || body?.password.length > 128 || body?.password.length < 6)
- return json({ error: 'Invalid password.' }, { status: 400 });
-
- const user = await findUser({ email: body?.email });
- if (!user) return json({ error: 'User record not found.' }, { status: 401 });
-
- if (!(await verifyHash(user.password, body?.password)))
- return json({ error: 'User record not found.' }, { status: 401 });
-
- const session = await createSession(user.id);
-
- cookies.set(COOKIE, session.id, { path: '/' });
-
- return json(
- {
- success: true,
- data: {
- id: user.id,
- username: user.username,
- email: user.email
- }
- },
- { status: 200 }
- );
-}
diff --git a/src/routes/api/v1/auth/login/+server.ts b/src/routes/api/v1/auth/login/+server.ts
new file mode 100644
index 0000000..1c4219a
--- /dev/null
+++ b/src/routes/api/v1/auth/login/+server.ts
@@ -0,0 +1,47 @@
+import { json } from '@sveltejs/kit';
+import { COOKIE } from '$lib/config';
+import { findUser, createSession } from '$lib/server/database';
+import { email } from '$lib/server/validator';
+import { verifyHash } from '$lib/server/crypto';
+import * as validator from '$lib/server/validator';
+import { ZodError } from 'zod';
+
+export async function POST({ request, cookies }) {
+ const body = (await request.json()) as {
+ email?: string;
+ password?: string;
+ };
+
+ try {
+ const email = validator.email(body.email);
+ const password = validator.password(body.password);
+
+ const user = await findUser({ email: email });
+ if (!user) return json({ error: 'User record not found.' }, { status: 401 });
+
+ if (!(await verifyHash(user.password, password)))
+ return json({ error: 'User record not found.' }, { status: 401 });
+
+ const session = await createSession(user.id);
+
+ cookies.set(COOKIE, session.id, { path: '/', sameSite: 'strict' });
+
+ return json(
+ {
+ success: true,
+ data: {
+ id: user.id,
+ username: user.username,
+ email: user.email
+ }
+ },
+ { status: 200 }
+ );
+ } catch (e) {
+ if (e instanceof ZodError) {
+ return json({ error: e.errors[0].message }, { status: 400 });
+ } else {
+ return json({ error: 'Internal Server Error' }, { status: 500 });
+ }
+ }
+}
diff --git a/src/routes/api/v1/auth/logout/+server.js b/src/routes/api/v1/auth/logout/+server.ts
similarity index 75%
rename from src/routes/api/v1/auth/logout/+server.js
rename to src/routes/api/v1/auth/logout/+server.ts
index 01dbad2..f8e53da 100644
--- a/src/routes/api/v1/auth/logout/+server.js
+++ b/src/routes/api/v1/auth/logout/+server.ts
@@ -2,9 +2,11 @@ import { redirect } from '@sveltejs/kit';
import { getSession, deleteSession } from '$lib/server/database';
import { COOKIE } from '$lib/config';
-/** @type {import('./$types').RequestHandler} */
export async function GET({ cookies }) {
- const session = await getSession(cookies.get(COOKIE));
+ const cookie = cookies.get(COOKIE);
+ if (!cookie) return redirect(302, '/');
+
+ const session = await getSession(cookie);
if (!session) {
cookies.delete(COOKIE, { path: '/' });
return redirect(302, '/');
diff --git a/src/routes/api/v1/auth/register/+server.js b/src/routes/api/v1/auth/register/+server.js
deleted file mode 100644
index d27b763..0000000
--- a/src/routes/api/v1/auth/register/+server.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import { json } from '@sveltejs/kit';
-import { COOKIE } from '$lib/config';
-import { createUser, createSession, findUser } from '$lib/server/database';
-import { email } from '$lib/server/validator';
-
-export async function POST(event) {
- const { request, cookies } = event;
- const body = await request.json();
-
- if (!body?.username || body?.username.length > 16 || body?.username.length < 3)
- return json({ error: 'Invalid username.' }, { status: 400 });
-
- if (!body?.email || !email(body?.email))
- return json({ error: 'Invalid email.' }, { status: 400 });
-
- if (!body?.password || body?.password.length > 128 || body?.password.length < 6)
- return json({ error: 'Invalid password.' }, { status: 400 });
-
- const usernameTaken = !!(await findUser({ username: body?.username }));
- if (usernameTaken) return json({ error: 'That username is taken.' }, { status: 400 });
-
- const emailUsed = !!(await findUser({ email: body?.email }));
- if (emailUsed)
- return json({ error: 'That email has been used too many times.' }, { status: 400 });
-
- const user = await createUser(body?.username, body?.email, body?.password).catch((e) => {});
- if (!user) return json({ error: 'Internal Server Error' }, { status: 500 });
-
- const session = await createSession(user.id);
- if (!session) return json({ error: 'Internal Server Error' }, { status: 500 });
-
- cookies.set(COOKIE, session.id, { path: '/' });
-
- return json(
- {
- success: true,
- data: {
- id: user.id,
- username: user.username,
- email: user.email
- }
- },
- { status: 200 }
- );
-}
diff --git a/src/routes/api/v1/auth/register/+server.ts b/src/routes/api/v1/auth/register/+server.ts
new file mode 100644
index 0000000..baedbcc
--- /dev/null
+++ b/src/routes/api/v1/auth/register/+server.ts
@@ -0,0 +1,42 @@
+import { json } from '@sveltejs/kit';
+import { COOKIE } from '$lib/config';
+import { createUser, createSession } from '$lib/server/database';
+import * as validator from '$lib/server/validator';
+import { ZodError } from 'zod';
+
+export async function POST({ request, cookies }) {
+ const body = (await request.json()) as {
+ username?: string;
+ password?: string;
+ email?: string;
+ };
+
+ try {
+ const username = await validator.usernameAndNotUsed(body.username);
+ const email = await validator.emailAndNotUsed(body.email);
+ const password = validator.password(body.password);
+
+ const user = await createUser(username, email, password);
+ const session = await createSession(user.id);
+
+ cookies.set(COOKIE, session.id, { path: '/', sameSite: 'strict' });
+
+ return json(
+ {
+ success: true,
+ data: {
+ id: user.id,
+ username: user.username,
+ email: user.email
+ }
+ },
+ { status: 200 }
+ );
+ } catch (e) {
+ if (e instanceof ZodError) {
+ return json({ error: e.errors[0].message }, { status: 400 });
+ } else {
+ return json({ error: 'Internal Server Error' }, { status: 500 });
+ }
+ }
+}
diff --git a/src/routes/api/v1/keys/+server.ts b/src/routes/api/v1/keys/+server.ts
new file mode 100644
index 0000000..5856dc9
--- /dev/null
+++ b/src/routes/api/v1/keys/+server.ts
@@ -0,0 +1,29 @@
+import { API_KEY_PERMISSIONS } from '$lib/config.js';
+import { createUserApiKey, deleteUserApiKey, getUserApiKeys } from '$lib/server/database';
+import { error, json } from '@sveltejs/kit';
+
+export async function GET({ locals }) {
+ return json(await getUserApiKeys(locals.user.id));
+}
+
+export async function POST({ locals }) {
+ return json(
+ await createUserApiKey(
+ locals.user.id,
+ API_KEY_PERMISSIONS.CREATE_UPLOADS | API_KEY_PERMISSIONS.READ_ACCOUNT,
+ new Date('2099')
+ )
+ );
+}
+
+export async function DELETE({ locals, request }) {
+ const body = (await request.json().catch(() => {})) as { key?: string };
+ if (!body?.key) error(400, { status: 400, message: 'Missing "key" value.' });
+ if (!locals.user) error(401, { status: 401, message: 'Unauthorized' });
+
+ return json(
+ await deleteUserApiKey(locals.user.id, body.key).catch((_) =>
+ error(400, { status: 400, message: 'API key does not exist.' })
+ )
+ );
+}
diff --git a/src/routes/api/v1/statistics/+server.js b/src/routes/api/v1/statistics/+server.ts
similarity index 76%
rename from src/routes/api/v1/statistics/+server.js
rename to src/routes/api/v1/statistics/+server.ts
index aa140ba..061d8d0 100644
--- a/src/routes/api/v1/statistics/+server.js
+++ b/src/routes/api/v1/statistics/+server.ts
@@ -1,10 +1,9 @@
import { bytesToHumanReadable } from '$lib';
import prisma from '$lib/server/database';
-import minio, { USAGE } from '$lib/server/minio';
+import { USAGE } from '$lib/server/minio';
import { json } from '@sveltejs/kit';
import { get } from 'svelte/store';
-/** @type {import("@sveltejs/kit").RequestHandler} */
export async function GET() {
return json({
users: await prisma.user.count({}),
diff --git a/src/routes/api/v1/user/+server.js b/src/routes/api/v1/user/+server.js
deleted file mode 100644
index 7806e5c..0000000
--- a/src/routes/api/v1/user/+server.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { json } from '@sveltejs/kit';
-
-/** @type {import('./$types').RequestHandler} */
-export async function GET(event) {
- const { request, cookies, locals } = event;
-
- return json(locals?.user);
-}
diff --git a/src/routes/api/v1/user/+server.ts b/src/routes/api/v1/user/+server.ts
new file mode 100644
index 0000000..111a696
--- /dev/null
+++ b/src/routes/api/v1/user/+server.ts
@@ -0,0 +1,5 @@
+import { json } from '@sveltejs/kit';
+
+export async function GET({ locals }) {
+ return json(locals.user);
+}
diff --git a/static/favicon.ico b/static/favicon.ico
new file mode 100644
index 0000000..8b11af6
Binary files /dev/null and b/static/favicon.ico differ
diff --git a/svelte.config.js b/svelte.config.js
index 8b24099..802cdd3 100644
--- a/svelte.config.js
+++ b/svelte.config.js
@@ -4,7 +4,8 @@ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
- adapter: adapter()
+ adapter: adapter(),
+ csrf: { checkOrigin: false }
},
preprocess: vitePreprocess()
};
diff --git a/yarn.lock b/yarn.lock
index 0b30cc3..212d152 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1787,11 +1787,6 @@ util@^0.12.3:
is-typed-array "^1.1.3"
which-typed-array "^1.1.2"
-validator@^13.12.0:
- version "13.12.0"
- resolved "https://registry.yarnpkg.com/validator/-/validator-13.12.0.tgz#7d78e76ba85504da3fee4fd1922b385914d4b35f"
- integrity sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==
-
vite@^5.3.5:
version "5.3.5"
resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.5.tgz#b847f846fb2b6cb6f6f4ed50a830186138cb83d8"
@@ -1870,3 +1865,8 @@ yaml@^2.3.4:
version "2.5.0"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.0.tgz#c6165a721cf8000e91c36490a41d7be25176cf5d"
integrity sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==
+
+zod@^3.23.8:
+ version "3.23.8"
+ resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"
+ integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==