labels for inputs, add api keys

This commit is contained in:
cirroskais 2024-08-02 05:05:10 -04:00
parent e97b603d94
commit 303f1a232f
No known key found for this signature in database
GPG key ID: 5FC73EBF2678E33D
11 changed files with 115 additions and 20 deletions

View file

@ -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;

View file

@ -12,6 +12,7 @@ model User {
username String @unique username String @unique
email String @unique email String @unique
password String password String
totp String?
role Role role Role
createdAt DateTime @default(now()) createdAt DateTime @default(now())
lastSeen DateTime @default(now()) lastSeen DateTime @default(now())
@ -20,6 +21,7 @@ model User {
maxUploadMB Int @default(100) maxUploadMB Int @default(100)
uploads Upload[] uploads Upload[]
sessions Session[] sessions Session[]
apiKeys APIKey[]
} }
model Session { model Session {
@ -58,6 +60,15 @@ model Upload {
uploaded DateTime @default(now()) 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 { enum Role {
ADMINISTRATOR ADMINISTRATOR
USER USER

View file

@ -20,7 +20,7 @@
<div class="flex my-auto space-x-3"> <div class="flex my-auto space-x-3">
<HeaderLink href="/dashboard">Dashboard</HeaderLink> <HeaderLink href="/dashboard">Dashboard</HeaderLink>
<HeaderLink href="/uploads">Uploads</HeaderLink> <HeaderLink href="/uploads">Uploads</HeaderLink>
<HeaderLink href="/links">Links</HeaderLink> <HeaderLink href="/documentation">Documentation</HeaderLink>
{#if $user?.role === 'ADMINISTRATOR'} {#if $user?.role === 'ADMINISTRATOR'}
<HeaderLink href="/admin">Admin</HeaderLink> <HeaderLink href="/admin">Admin</HeaderLink>
{/if} {/if}

View file

@ -0,0 +1,16 @@
<script>
import { X } from 'lucide-svelte';
export let key;
</script>
<tr>
<th>
<tt class="text-md">{key.id}</tt>
</th>
<th class="flex">
<button class="m-auto hover:text-red">
<X></X>
</button>
</th>
</tr>

View file

@ -23,3 +23,14 @@ export const DOMAINS = [
]; ];
export const DEV_DOMAINS = ['cdn.dev.madhouselabs.net']; 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
};

View file

@ -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
}
});
}

View file

@ -0,0 +1,7 @@
import { getUserApiKeys } from '$lib/server/database.js';
export async function load({ locals }) {
return {
keys: (await getUserApiKeys(locals.user.id)) || []
};
}

View file

@ -1,6 +1,9 @@
<script> <script>
import { Info, X, Plus } from 'lucide-svelte'; import { Info, X, Plus } from 'lucide-svelte';
import Button from '$lib/components/Inputs/Button.svelte'; import Button from '$lib/components/Inputs/Button.svelte';
import ListedApiKey from '$lib/components/ListedAPIKey.svelte';
export let data;
</script> </script>
<div class="grid grid-cols-1 gap-2 mx-auto xl:grid-cols-3 w-fit"> <div class="grid grid-cols-1 gap-2 mx-auto xl:grid-cols-3 w-fit">
@ -26,7 +29,7 @@
</div> </div>
<div class="mb-1"> <div class="mb-1">
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<label class="text-lg font-bold" for="username">Email</label> <label class="text-lg font-bold" for="email">Email</label>
<input <input
class="px-2 h-8 rounded-lg ring-1 transition-all bg-mantle ring-surface2 focus-visible:outline-none focus-visible:outline-overlay0" class="px-2 h-8 rounded-lg ring-1 transition-all bg-mantle ring-surface2 focus-visible:outline-none focus-visible:outline-overlay0"
type="email" type="email"
@ -60,16 +63,9 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> {#each data.keys as key}
<th> <ListedApiKey {key} />
<tt class="text-md">d8895a0c-daa5-4b6e-b66f-2494c039fe9e </tt> {/each}
</th>
<th class="flex">
<button class="m-auto hover:text-red">
<X></X>
</button>
</th>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -165,8 +161,8 @@
</div> </div>
<div> <div>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-0.5">
<label class="text-lg font-bold" for="description">2FA</label> <p class="text-lg font-bold">2FA</p>
<p class="text-sm text-surface2"> <p class="text-sm text-surface2">
A One-Time Password will be required each time you login. A One-Time Password will be required each time you login.
</p> </p>
@ -184,7 +180,7 @@
<span class="w-full border-b-2 border-surface0" /> <span class="w-full border-b-2 border-surface0" />
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<div class=""> <div class="">
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-0.5">
<div> <div>
<input type="checkbox" name="newPostsPublic" id="newPostsPublic" /> <input type="checkbox" name="newPostsPublic" id="newPostsPublic" />
<label class="" for="newPostsPublic">New Posts Public</label> <label class="" for="newPostsPublic">New Posts Public</label>
@ -195,10 +191,10 @@
</div> </div>
</div> </div>
<div class=""> <div class="">
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-0.5">
<div> <div>
<input type="checkbox" name="newPostsPublic" id="newPostsPublic" /> <input type="checkbox" name="encryptUploads" id="encryptUploads" />
<label class="" for="newPostsPublic">Encrypt Uploads</label> <label class="" for="encryptUploads">Encrypt Uploads</label>
</div> </div>
<p class="text-sm text-surface2">Enable encryption for new uploads. (Client-side)</p> <p class="text-sm text-surface2">Enable encryption for new uploads. (Client-side)</p>
</div> </div>

View file

@ -3,13 +3,11 @@ import { error } from '@sveltejs/kit';
export async function load({ locals, url }) { export async function load({ locals, url }) {
if (!locals.user) return error(403, { status: 403, message: 'Forbidden' }); if (!locals.user) return error(403, { status: 403, message: 'Forbidden' });
if (+(url.searchParams.get('i') || 0) < 0) error(400, { status: 403, message: 'Invalid Index' }); 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 } }); const totalUploads = await prisma.upload.count({ where: { uploaderId: locals.user.id } });
if (!totalUploads) return { uploads: [], totalUploads: 0 }; if (!totalUploads) return { uploads: [], totalUploads: 0 };
if (+(url.searchParams.get('i') || 0) >= Math.ceil(totalUploads / 15)) if (+(url.searchParams.get('i') || 0) >= Math.ceil(totalUploads / 15))
error(400, { status: 403, message: 'Invalid Index' }); error(400, { status: 403, message: 'Invalid Index' });

View file

@ -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')
)
);
}