From e97b603d9473d06c3d63fc0bdbb929900d9bef12 Mon Sep 17 00:00:00 2001 From: cirroskais Date: Fri, 2 Aug 2024 03:37:23 -0400 Subject: [PATCH 1/7] actually count the number of the users upload --- src/lib/config.ts | 11 +++++++++++ src/routes/(app)/uploads/+page.server.ts | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/lib/config.ts b/src/lib/config.ts index 5f1add6..ae168ec 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -12,3 +12,14 @@ export const MAIL_WHITELIST = [ 'pm.me', 'proton.me' ]; + +export const DOMAINS = [ + 'cdn.cirroskais.xyz', + 'cdn.madhouselabs.net', + 'snep.lol', + 'i.chadthundercock.com', + 'doing-ya.mom', + '*.is-gay.zip' +]; + +export const DEV_DOMAINS = ['cdn.dev.madhouselabs.net']; diff --git a/src/routes/(app)/uploads/+page.server.ts b/src/routes/(app)/uploads/+page.server.ts index b7c9556..29d0e27 100644 --- a/src/routes/(app)/uploads/+page.server.ts +++ b/src/routes/(app)/uploads/+page.server.ts @@ -6,7 +6,7 @@ export async function load({ locals, url }) { 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 }; From 303f1a232fe2092b88a8b4b54401f67eac747d77 Mon Sep 17 00:00:00 2001 From: cirroskais Date: Fri, 2 Aug 2024 05:05:10 -0400 Subject: [PATCH 2/7] labels for inputs, add api keys --- .../migration.sql | 16 ++++++++++ prisma/schema.prisma | 11 +++++++ src/lib/components/Header.svelte | 2 +- src/lib/components/ListedAPIKey.svelte | 16 ++++++++++ src/lib/config.ts | 11 +++++++ src/lib/server/database.ts | 23 ++++++++++++++ .../{links => documentation}/+page.svelte | 0 src/routes/(app)/settings/+page.server.ts | 7 +++++ src/routes/(app)/settings/+page.svelte | 30 ++++++++----------- src/routes/(app)/uploads/+page.server.ts | 2 -- src/routes/api/v1/keys/+server.ts | 17 +++++++++++ 11 files changed, 115 insertions(+), 20 deletions(-) create mode 100644 prisma/migrations/20240802082051_add_api_keys_to_schema/migration.sql create mode 100644 src/lib/components/ListedAPIKey.svelte rename src/routes/(app)/{links => documentation}/+page.svelte (100%) create mode 100644 src/routes/(app)/settings/+page.server.ts create mode 100644 src/routes/api/v1/keys/+server.ts diff --git a/prisma/migrations/20240802082051_add_api_keys_to_schema/migration.sql b/prisma/migrations/20240802082051_add_api_keys_to_schema/migration.sql new file mode 100644 index 0000000..d46c83e --- /dev/null +++ b/prisma/migrations/20240802082051_add_api_keys_to_schema/migration.sql @@ -0,0 +1,16 @@ +-- AlterTable +ALTER TABLE `User` ADD COLUMN `totp` VARCHAR(191) NULL; + +-- CreateTable +CREATE TABLE `APIKey` ( + `id` VARCHAR(191) NOT NULL, + `userId` INTEGER NOT NULL, + `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `expiresAt` DATETIME(3) NOT NULL, + `permissions` INTEGER NOT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- AddForeignKey +ALTER TABLE `APIKey` ADD CONSTRAINT `APIKey_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 19ef386..7775c27 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -12,6 +12,7 @@ model User { username String @unique email String @unique password String + totp String? role Role createdAt DateTime @default(now()) lastSeen DateTime @default(now()) @@ -20,6 +21,7 @@ model User { maxUploadMB Int @default(100) uploads Upload[] sessions Session[] + apiKeys APIKey[] } model Session { @@ -58,6 +60,15 @@ model Upload { uploaded DateTime @default(now()) } +model APIKey { + id String @id + user User @relation(fields: [userId], references: [id]) + userId Int + createdAt DateTime @default(now()) + expiresAt DateTime + permissions Int +} + enum Role { ADMINISTRATOR USER diff --git a/src/lib/components/Header.svelte b/src/lib/components/Header.svelte index 6e5bb15..6404869 100644 --- a/src/lib/components/Header.svelte +++ b/src/lib/components/Header.svelte @@ -20,7 +20,7 @@
Dashboard Uploads - Links + Documentation {#if $user?.role === 'ADMINISTRATOR'} Admin {/if} diff --git a/src/lib/components/ListedAPIKey.svelte b/src/lib/components/ListedAPIKey.svelte new file mode 100644 index 0000000..9d2b9fa --- /dev/null +++ b/src/lib/components/ListedAPIKey.svelte @@ -0,0 +1,16 @@ + + + + + {key.id} + + + + + diff --git a/src/lib/config.ts b/src/lib/config.ts index ae168ec..27c20c2 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -23,3 +23,14 @@ export const DOMAINS = [ ]; export const DEV_DOMAINS = ['cdn.dev.madhouselabs.net']; + +export const API_KEY_PERMISSIONS = { + CREATE_UPLOADS: 1 << 0, + READ_UPLOADS: 1 << 1, + UPDATE_UPLOADS: 1 << 2, + DELETE_UPLOADS: 1 << 3, + + READ_ACCOUNT: 1 << 4, + UPDATE_ACCOUNT_ACCOUNT_SETTINGS: 1 << 5, // allows for updating username & email. bad idea? probably. + UPDATE_ACCOUNT_EMBED_SETTINGS: 1 << 6 +}; diff --git a/src/lib/server/database.ts b/src/lib/server/database.ts index fdbdde6..063fa1e 100644 --- a/src/lib/server/database.ts +++ b/src/lib/server/database.ts @@ -138,3 +138,26 @@ export async function getSettings(id: number) { } }); } + +export async function getUserApiKeys(userId: number) { + if (!userId) return false; + + return await prisma.aPIKey.findMany({ + where: { + userId: userId + } + }); +} + +export async function createUserApiKey(userId: number, permissions: number, expiresAt: Date) { + if (!userId) return false; + + return await prisma.aPIKey.create({ + data: { + id: randomBytes(42).toString('base64url'), + userId, + permissions, + expiresAt + } + }); +} diff --git a/src/routes/(app)/links/+page.svelte b/src/routes/(app)/documentation/+page.svelte similarity index 100% rename from src/routes/(app)/links/+page.svelte rename to src/routes/(app)/documentation/+page.svelte diff --git a/src/routes/(app)/settings/+page.server.ts b/src/routes/(app)/settings/+page.server.ts new file mode 100644 index 0000000..f669c16 --- /dev/null +++ b/src/routes/(app)/settings/+page.server.ts @@ -0,0 +1,7 @@ +import { getUserApiKeys } from '$lib/server/database.js'; + +export async function load({ locals }) { + return { + keys: (await getUserApiKeys(locals.user.id)) || [] + }; +} diff --git a/src/routes/(app)/settings/+page.svelte b/src/routes/(app)/settings/+page.svelte index ba40b1e..7593b4a 100644 --- a/src/routes/(app)/settings/+page.svelte +++ b/src/routes/(app)/settings/+page.svelte @@ -1,6 +1,9 @@
@@ -26,7 +29,7 @@
- + - - - d8895a0c-daa5-4b6e-b66f-2494c039fe9e - - - - - + {#each data.keys as key} + + {/each}
@@ -165,8 +161,8 @@
-
- +
+

2FA

A One-Time Password will be required each time you login.

@@ -184,7 +180,7 @@
-
+
@@ -195,10 +191,10 @@
-
+
- - + +

Enable encryption for new uploads. (Client-side)

diff --git a/src/routes/(app)/uploads/+page.server.ts b/src/routes/(app)/uploads/+page.server.ts index 29d0e27..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({ 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/v1/keys/+server.ts b/src/routes/api/v1/keys/+server.ts new file mode 100644 index 0000000..94437c0 --- /dev/null +++ b/src/routes/api/v1/keys/+server.ts @@ -0,0 +1,17 @@ +import { API_KEY_PERMISSIONS } from '$lib/config.js'; +import { createUserApiKey, getUserApiKeys } from '$lib/server/database'; +import { 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') + ) + ); +} From 7745b0741845378e550cb034743e1a121fbe6b6a Mon Sep 17 00:00:00 2001 From: cirroskais Date: Fri, 2 Aug 2024 09:33:34 -0400 Subject: [PATCH 3/7] better validator --- package.json | 2 +- src/lib/components/Dropdown.svelte | 2 +- src/lib/components/Forms/LoginForm.svelte | 2 + src/lib/components/Forms/RegisterForm.svelte | 1 + src/lib/components/Header.svelte | 2 +- src/lib/components/Inputs/FormInput.svelte | 36 ++++---------- src/lib/components/ListedAPIKey.svelte | 14 ++++-- src/lib/server/database.ts | 11 ++++ src/lib/server/validator.js | 8 --- src/lib/server/validator.ts | 42 ++++++++++++++++ src/routes/(app)/settings/+page.svelte | 31 +++++++++--- src/routes/api/{+server.js => +server.ts} | 1 - src/routes/api/v1/auth/login/+server.js | 39 --------------- src/routes/api/v1/auth/login/+server.ts | 47 ++++++++++++++++++ .../v1/auth/logout/{+server.js => +server.ts} | 6 ++- src/routes/api/v1/auth/register/+server.js | 45 ----------------- src/routes/api/v1/auth/register/+server.ts | 42 ++++++++++++++++ src/routes/api/v1/keys/+server.ts | 16 +++++- .../v1/statistics/{+server.js => +server.ts} | 3 +- src/routes/api/v1/user/+server.js | 8 --- src/routes/api/v1/user/+server.ts | 5 ++ static/favicon.ico | Bin 0 -> 16958 bytes yarn.lock | 10 ++-- 23 files changed, 223 insertions(+), 150 deletions(-) delete mode 100644 src/lib/server/validator.js create mode 100644 src/lib/server/validator.ts rename src/routes/api/{+server.js => +server.ts} (52%) delete mode 100644 src/routes/api/v1/auth/login/+server.js create mode 100644 src/routes/api/v1/auth/login/+server.ts rename src/routes/api/v1/auth/logout/{+server.js => +server.ts} (75%) delete mode 100644 src/routes/api/v1/auth/register/+server.js create mode 100644 src/routes/api/v1/auth/register/+server.ts rename src/routes/api/v1/statistics/{+server.js => +server.ts} (76%) delete mode 100644 src/routes/api/v1/user/+server.js create mode 100644 src/routes/api/v1/user/+server.ts create mode 100644 static/favicon.ico diff --git a/package.json b/package.json index 9872883..9d8fe1d 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,6 @@ "mime": "^4.0.4", "minio": "^7.1.4", "svelte-sonner": "^0.3.27", - "validator": "^13.12.0" + "zod": "^3.23.8" } } diff --git a/src/lib/components/Dropdown.svelte b/src/lib/components/Dropdown.svelte index cb32b6d..676e3df 100644 --- a/src/lib/components/Dropdown.svelte +++ b/src/lib/components/Dropdown.svelte @@ -25,7 +25,7 @@ {#if visible}
diff --git a/src/lib/components/Forms/LoginForm.svelte b/src/lib/components/Forms/LoginForm.svelte index 3c9454e..7c7c268 100644 --- a/src/lib/components/Forms/LoginForm.svelte +++ b/src/lib/components/Forms/LoginForm.svelte @@ -1,4 +1,5 @@ - - + + {#if type === 'username'}
- -
- -
{:else if type === 'email'}
@@ -33,7 +31,7 @@
- -
- -
{:else if type === 'password'}
@@ -54,7 +46,7 @@
- -
- -
{/if} diff --git a/src/lib/components/ListedAPIKey.svelte b/src/lib/components/ListedAPIKey.svelte index 9d2b9fa..1711adc 100644 --- a/src/lib/components/ListedAPIKey.svelte +++ b/src/lib/components/ListedAPIKey.svelte @@ -1,7 +1,15 @@ - @@ -9,7 +17,7 @@ {key.id} - diff --git a/src/lib/server/database.ts b/src/lib/server/database.ts index 063fa1e..c0abcc3 100644 --- a/src/lib/server/database.ts +++ b/src/lib/server/database.ts @@ -161,3 +161,14 @@ export async function createUserApiKey(userId: number, permissions: number, expi } }); } + +export async function deleteUserApiKey(userId: number, id: string) { + if (!userId || !id) return false; + + return await prisma.aPIKey.delete({ + where: { + userId, + id + } + }); +} diff --git a/src/lib/server/validator.js b/src/lib/server/validator.js deleted file mode 100644 index 62784b6..0000000 --- a/src/lib/server/validator.js +++ /dev/null @@ -1,8 +0,0 @@ -import validator from 'validator'; -import { MAIL_WHITELIST } from '$lib/config'; - -// https://github.com/validatorjs/validator.js?tab=readme-ov-file#validators - -export function email(input) { - return validator.isEmail(input, { host_whitelist: MAIL_WHITELIST }); -} diff --git a/src/lib/server/validator.ts b/src/lib/server/validator.ts new file mode 100644 index 0000000..cd0aea7 --- /dev/null +++ b/src/lib/server/validator.ts @@ -0,0 +1,42 @@ +import { z } from 'zod'; +import { findUser } from './database'; + +const INTERNAL_email = z.string().email('Invalid email address.'); + +export const email = INTERNAL_email.parse; + +export const emailAndNotUsed = INTERNAL_email.refine(async (_) => { + return !Boolean(await findUser({ email: _ })); +}, 'Email is already being used.').parseAsync; + +const INTERNAL_username = z + .string() + .min(3, 'Username must be at least 3 characters.') + .max(16, 'Usernames must be no more than 16 characters.') + .regex( + new RegExp(/^[A-z0-9\_\-\.]+$/g), + 'Usernames must be alphanumeric with dashes, underscores, and periods.' + ); + +export const username = INTERNAL_username.parse; + +export const usernameAndNotUsed = INTERNAL_username.refine(async (_) => { + return !Boolean(await findUser({ username: _ })); +}, 'Username is already being used.').parseAsync; + +export const password = z + .string() + .min(6, 'Passwords must be longer than 6 characters.') + .max(128, 'You do not need a password longer than 128 fucking characters.').parse; + +export const embedTitle = z + .string() + .max(256, 'Title must not be longer than 256 characters.').parse; + +export const embedDescription = z + .string() + .max(2000, 'Description must not be longer than 2000 characters.').parse; + +export const embedColor = z + .number() + .max(parseInt('ffffff', 16), 'Color must be less than 16777215.').parse; diff --git a/src/routes/(app)/settings/+page.svelte b/src/routes/(app)/settings/+page.svelte index 7593b4a..e377417 100644 --- a/src/routes/(app)/settings/+page.svelte +++ b/src/routes/(app)/settings/+page.svelte @@ -1,9 +1,21 @@ -
@@ -21,6 +33,8 @@ name="username" id="username" placeholder="cirro" + maxlength="16" + bind:value={username} />

Your username is used to identify you around the site. You can change it at any time. @@ -36,6 +50,7 @@ name="email" id="email" placeholder="c*******@madhouselabs.net" + bind:value={email} />

Changing your email may require you to verify ownership. @@ -50,7 +65,7 @@

API Keys -

@@ -89,23 +104,25 @@ name="title" id="title" placeholder={`{{ file }}`} + maxlength="256" + bind:value={title} /> -

The title shown on the embed.

+

The title shown on the embed. Max 256 characters.

-

- The description of the embed. Can have up to 2000 characters. -

+

The description of the embed. Max 2000 characters.

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..1cc1f08 --- /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: '/' }); + + 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..be8b459 --- /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: '/' }); + + 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 index 94437c0..5856dc9 100644 --- a/src/routes/api/v1/keys/+server.ts +++ b/src/routes/api/v1/keys/+server.ts @@ -1,6 +1,6 @@ import { API_KEY_PERMISSIONS } from '$lib/config.js'; -import { createUserApiKey, getUserApiKeys } from '$lib/server/database'; -import { json } from '@sveltejs/kit'; +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)); @@ -15,3 +15,15 @@ export async function POST({ locals }) { ) ); } + +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 0000000000000000000000000000000000000000..8b11af6b4b32f9a56d1f94aa2ce00bd92e84342b GIT binary patch literal 16958 zcmeI4cZ?KO9LMKCz=9m2vBMpTNU&kAcy}TYOAIz*qGE|9ilW9wROAo@H7ZyLs6?<~ zj2L4>{X+wyV(%KG0(XK9+oDldU3UF^XXcTYad-A@xg8_-BcFZm{a%^h_sz_kHxt0~ z>iE;#?D5;^4LZ#8w)8x25a9A!L4Mz;i&`$1^9m7d`+qDK(k}n{V4eCqum3{VK`liv;xRy8kSUQ|tPeAd;&5YCSm>bl*5x0{Ru7IrqB%>zo4Zphh*; zFRcqf`n9$(}1J?8TlZ zUmp>u^Q~sCXW4K zd(eF^{aP3Q6IP-AA&B@1r0M)Tf-b`MB@70AmN#vmBmbYU3iS6x!0RCGv+{_Uitkg{ zGpyT`bOorr*1_(OxK*Hk3c*XuTcEYynD+R3mwyKujFVLUMW8;q4(vLL$yk~C4eYWfJ8w^y|CRD#e`+@hjQ)9YByDBr zzkryf;Unm6#jkHTZLo*si)OVaS_$gE8n%N(twj9|1p6)Md3UrG_)g^8;TX$TC~MyX zlL|GemA^#&bBIglZa!!|lj+;z5^&YB3vIP_m}_7M@`+gq`VS^ZeQ(#=J-`ZhI(dE5 z(KlV&22vOYTiebsKbutg#iNkeUf5B8cVb)wigyAS(;BY*{$`;=_rZDaENC5k2(<5s zT3;My9nhGUMjO&4oG91jiyrTh{jFBsZeus z(64WFZ-U|+V+A;kyuM*Bg_&?7#I5JL4x;zSjrcUK_#O28tm(brp#Ow0zV}JB)*0KI z%s#MzaagJL#)DuOoCWv8cW@=x){VkkFms^vao7=RVny^z+e@Ihq@$4OyN|SvhXwFC z`~|iyUE>;~C4|H`?WgVpyX{KKdY{?^I@F5jAIZV9@DXS(WIAVB=X71%44R)G!H!>f zX?O+ZgXTs1qK)Be(B7vH*mg37RbY?vE@iE!`S#MdE4L#07jgu}qdkk(tGhul{RVcN zzfyh+?t(L6FVH$^_6THs(;TXu`tIj@f}bv7egLViAA4-&b?-a_*TLCv3N%8+Rz$zi zsddhdN%6b@Q$c&aMljb0*;vyYX)HY>jEyC8VOY?d9S6F0>|;jruTvkLUtFbG5&dfz zO~=)qSI_jTpb55wa_tN{YRq}o05i#7Yn7Gl21mdHpnEB*e+9nbpnIxpE24j220j!j z(Ho6zHypa2w5I6UDw_@y{9OK@&2b+Ly02j_6*PzvNJjPIpU-!9eG(7F+f*uY;n3=>naYyZ2+Bbt@oFM{Ty)XU7(e##?E$y|NUp@+;q;(dTd*mjooNmgkRGON&E>$tt=KtohjX|2}AEfCpO`Q>x+uDP4 z@!}wz9H!Hj1m$}FV7Y9+Ak7@o;`=j0f_CXk+Wm5BWtc7vj@v#x<@?(khznb?{rD!O zQyYVHN?oQw<(~EL^zhU1y>x(VLu*(42MO8UUfrT5Uv&GtI;9P3W>Assnp-o+o0!Ws z`xGv3%Xz=}+!<&NitS}uvF(?o5 clU$}TO#25ZA^4W{<0hmq%>}~;m4$x)0p`F`l>h($ literal 0 HcmV?d00001 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== From 4c3f4d719f5e1b03134a088e6b9d80cdbb28a16f Mon Sep 17 00:00:00 2001 From: cirroskais Date: Fri, 2 Aug 2024 10:34:38 -0400 Subject: [PATCH 4/7] basic sharex stuff, api key auth --- src/hooks.server.js | 62 +++++++++++-------- src/lib/components/Forms/LoginForm.svelte | 2 +- src/lib/components/Forms/RegisterForm.svelte | 2 +- src/lib/components/Inputs/FormInput.svelte | 12 ++-- src/lib/server/database.ts | 25 ++++++++ src/lib/server/{ratelimit.js => ratelimit.ts} | 2 +- src/routes/(app)/documentation/+page.svelte | 38 ++++++++++++ 7 files changed, 107 insertions(+), 36 deletions(-) rename src/lib/server/{ratelimit.js => ratelimit.ts} (94%) diff --git a/src/hooks.server.js b/src/hooks.server.js index fbb3424..c879a79 100644 --- a/src/hooks.server.js +++ b/src/hooks.server.js @@ -1,40 +1,48 @@ -import { error, redirect } from '@sveltejs/kit'; -import { getSession } from '$lib/server/database'; +import { redirect } from '@sveltejs/kit'; +import { getSession, getUserApiKey } from '$lib/server/database'; import { COOKIE } from '$lib/config'; -const PUBLIC_RESOURCES = [ - '/', - '/api', - '/api/auth/register', - '/api/auth/login', - '/terms', - '/privacy' -]; - -/** @type {import('@sveltejs/kit').Handle} */ export async function handle({ event, resolve }) { - const { cookies, locals } = event; - const session = await getSession(cookies.get(COOKIE) || ''); + const { cookies, locals, request } = event; - if (session && session.user) { - locals.user = { - id: session.user.id, - username: session.user.username, - email: session.user.email, - maxUploadMB: session.user.maxUploadMB, - role: session.user.role - }; - } else { - if (event.route.id) { - if (event.route.id.includes('(app)')) return redirect(303, '/'); + let cookie = cookies.get(COOKIE); + let bearer = request.headers.get('Authorization'); + if (bearer) bearer = bearer.replace('Bearer ', ''); + + if (cookie) { + const session = await getSession(cookie); + if (session && session.user) { + locals.user = { + id: session.user.id, + username: session.user.username, + email: session.user.email, + maxUploadMB: session.user.maxUploadMB, + role: session.user.role + }; } } + if (bearer && !locals.user) { + const apiKey = await getUserApiKey(bearer); + if (apiKey && apiKey.user) { + locals.user = { + id: apiKey.user.id, + username: apiKey.user.username, + email: apiKey.user.email, + maxUploadMB: apiKey.user.maxUploadMB, + role: apiKey.user.role + }; + } + } + + if (!locals.user && event.route.id) { + if (event.route.id.includes('(app)')) return redirect(303, '/'); + } + return await resolve(event); } -/** @type {import('@sveltejs/kit').HandleServerError} */ -export async function handleError({ error, event, status, message }) { +export async function handleError({ error, status, message }) { console.log(error); return { diff --git a/src/lib/components/Forms/LoginForm.svelte b/src/lib/components/Forms/LoginForm.svelte index 7c7c268..e5126f4 100644 --- a/src/lib/components/Forms/LoginForm.svelte +++ b/src/lib/components/Forms/LoginForm.svelte @@ -60,7 +60,7 @@ type={'email'} name={'email'} id={'email'} - placeholder={'user@example.com'} + placeholder={'john@doefamily.com'} bind:value={email} required={true} > diff --git a/src/lib/components/Forms/RegisterForm.svelte b/src/lib/components/Forms/RegisterForm.svelte index 2359c4f..3807c3c 100644 --- a/src/lib/components/Forms/RegisterForm.svelte +++ b/src/lib/components/Forms/RegisterForm.svelte @@ -80,7 +80,7 @@ type={'email'} name={'email'} id={'email'} - placeholder={'user@example.com'} + placeholder={'jane@doefamily.com'} bind:value={email} required={true} > diff --git a/src/lib/components/Inputs/FormInput.svelte b/src/lib/components/Inputs/FormInput.svelte index e99a294..f5bbed5 100644 --- a/src/lib/components/Inputs/FormInput.svelte +++ b/src/lib/components/Inputs/FormInput.svelte @@ -11,8 +11,8 @@ --> {#if type === 'username'} -
-
+
+
{:else if type === 'email'} -
-
+
+
{:else if type === 'password'} -
-
+
+
+ import { page } from '$app/stores'; + import Button from '$lib/components/Inputs/Button.svelte'; + import { API_KEY_PERMISSIONS } from '$lib/config'; + import { get } from 'svelte/store'; + + let awesome = ''; + + async function click() { + const response = await fetch('/api/v1/keys'); + const body = (await response.json()) as { id: string; permissions: number }[]; + + const key = body.find((key) => key.permissions & API_KEY_PERMISSIONS.CREATE_UPLOADS); + if (!key) return (awesome = 'What the fuck did i tell you'); + + awesome = `{ + "Version": "14.0.0", + "Name": "cirros file uploader", + "DestinationType": "ImageUploader, FileUploader", + "RequestMethod": "POST", + "RequestURL": "${get(page).url.origin}/api/v1/upload", + "Headers": { + "Authorization": "Bearer ${key.id}", + }, + "Body": "MultipartFormData", + "FileFormName": "file", + "URL": "${get(page).url.origin}{response}", +}`; + } + + +

I'll make real documentation later but for now have this ShareX button

+

+ MAKE SURE TO HAVE A VALID API KEY WITH THE CREATE_UPLOADS PERMISSION ( 1 << 0 ) +

+ + +{awesome} From fa8d7b42231b455a2878df7cc5686ef95bb64882 Mon Sep 17 00:00:00 2001 From: cirroskais Date: Fri, 2 Aug 2024 10:56:30 -0400 Subject: [PATCH 5/7] set samesite to strict --- src/{hooks.server.js => hooks.server.ts} | 0 src/routes/api/v1/auth/login/+server.ts | 2 +- src/routes/api/v1/auth/register/+server.ts | 2 +- svelte.config.js | 3 ++- 4 files changed, 4 insertions(+), 3 deletions(-) rename src/{hooks.server.js => hooks.server.ts} (100%) diff --git a/src/hooks.server.js b/src/hooks.server.ts similarity index 100% rename from src/hooks.server.js rename to src/hooks.server.ts diff --git a/src/routes/api/v1/auth/login/+server.ts b/src/routes/api/v1/auth/login/+server.ts index 1cc1f08..1c4219a 100644 --- a/src/routes/api/v1/auth/login/+server.ts +++ b/src/routes/api/v1/auth/login/+server.ts @@ -24,7 +24,7 @@ export async function POST({ request, cookies }) { const session = await createSession(user.id); - cookies.set(COOKIE, session.id, { path: '/' }); + cookies.set(COOKIE, session.id, { path: '/', sameSite: 'strict' }); return json( { diff --git a/src/routes/api/v1/auth/register/+server.ts b/src/routes/api/v1/auth/register/+server.ts index be8b459..baedbcc 100644 --- a/src/routes/api/v1/auth/register/+server.ts +++ b/src/routes/api/v1/auth/register/+server.ts @@ -19,7 +19,7 @@ export async function POST({ request, cookies }) { const user = await createUser(username, email, password); const session = await createSession(user.id); - cookies.set(COOKIE, session.id, { path: '/' }); + cookies.set(COOKIE, session.id, { path: '/', sameSite: 'strict' }); return json( { 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() }; From e130fcd4c3e1c7c7f346477f6ed065687fb7d1d6 Mon Sep 17 00:00:00 2001 From: cirroskais Date: Fri, 2 Aug 2024 11:14:07 -0400 Subject: [PATCH 6/7] allow for api key auth in upload endpoint --- src/lib/server/auth.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index 7788f04..181e1c7 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -1,7 +1,18 @@ import { COOKIE } from '$lib/config'; import type { Cookies } from '@sveltejs/kit'; -import { getSession } from './database'; -import type { User, UserSettings } from '@prisma/client'; +import { getSession, getUserApiKey } from './database'; +import type { Role, UserSettings } from '@prisma/client'; + +interface User { + id: number; + username: string; + email: string; + password: string; + role: Role; + createdAt: Date; + lastSeen: Date; + maxUploadMB: number; +} interface UserAndMaybeSettings extends User { settings: UserSettings | null; @@ -14,7 +25,10 @@ export async function authenticate(request: Request, cookies: Cookies) { let user: UserAndMaybeSettings | false = false; if (bearer && !cookie) { - return false; + const key = await getUserApiKey(bearer); + if (key) { + user = key.user; + } } if (cookie && !bearer) { From a927530fe55ffd0d5e5d50db8d665d92a7b7358e Mon Sep 17 00:00:00 2001 From: cirroskais Date: Fri, 2 Aug 2024 11:36:47 -0400 Subject: [PATCH 7/7] download button for sharex button result --- src/lib/components/Inputs/Link.svelte | 4 +++- src/routes/(app)/documentation/+page.svelte | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/lib/components/Inputs/Link.svelte b/src/lib/components/Inputs/Link.svelte index ed9391f..8c535f9 100644 --- a/src/lib/components/Inputs/Link.svelte +++ b/src/lib/components/Inputs/Link.svelte @@ -1,15 +1,17 @@ {#if style === 'link'} - + {:else if style === 'button'} diff --git a/src/routes/(app)/documentation/+page.svelte b/src/routes/(app)/documentation/+page.svelte index 67d98d2..fb7ce89 100644 --- a/src/routes/(app)/documentation/+page.svelte +++ b/src/routes/(app)/documentation/+page.svelte @@ -1,6 +1,7 @@