From bfafd2a0447d0dc04a8184585052127d9831d0e9 Mon Sep 17 00:00:00 2001 From: DecDuck Date: Mon, 7 Oct 2024 22:35:54 +1100 Subject: [PATCH] ca groundwork --- .env.example | 5 + .yarnrc | 1 + app.vue | 4 + assets/core.scss | 12 ++ components/HeaderUserWidget.vue | 68 +++++++ components/InlineUserWidget.vue | 18 -- components/LoadingButton.vue | 31 ++++ components/PanelWidget.vue | 5 + components/UserFooter.vue | 53 +++--- components/UserHeader.vue | 7 +- {components => composables}/types.d.ts | 0 composables/user.ts | 16 ++ layouts/admin.vue | 2 + layouts/default.vue | 26 +-- middleware/require-user.global.ts | 15 ++ package.json | 3 + pages/admin/index.vue | 8 + pages/index.vue | 10 +- pages/register.vue | 2 +- pages/signin.vue | 175 ++++++++++++++++-- .../migration.sql | 2 + .../20241007065541_add_client/migration.sql | 15 ++ prisma/schema.prisma | 20 +- public/fonts/inter/InterVariable-Italic.ttf | Bin 0 -> 894460 bytes public/fonts/inter/InterVariable.ttf | Bin 0 -> 862936 bytes public/wallpapers/signin.jpg | Bin 0 -> 962320 bytes .../api/v1/{ => auth}/signin/simple.post.ts | 3 +- .../api/v1/{ => auth}/signup/simple.post.ts | 0 server/api/v1/client/handshake.post.ts | 3 + server/api/v1/client/initiate.post.ts | 3 + server/api/v1/client/session.post.ts | 3 + server/api/v1/whoami.get.ts | 3 +- server/internal/clients/README.md | 26 +++ server/internal/clients/ca.ts | 34 ++++ server/internal/clients/store.ts | 23 +++ server/internal/downloads/README.md | 5 + server/internal/downloads/coordinator.ts | 10 + server/internal/session/index.ts | 8 +- server/internal/session/memory.ts | 66 +++---- server/internal/session/types.d.ts | 14 +- server/plugins/ca.ts | 18 ++ server/routes/signout.get.ts | 5 + tailwind.config.js | 6 +- yarn.lock | 30 +++ 44 files changed, 628 insertions(+), 130 deletions(-) create mode 100644 .env.example create mode 100644 .yarnrc create mode 100644 components/HeaderUserWidget.vue delete mode 100644 components/InlineUserWidget.vue create mode 100644 components/LoadingButton.vue create mode 100644 components/PanelWidget.vue rename {components => composables}/types.d.ts (100%) create mode 100644 composables/user.ts create mode 100644 layouts/admin.vue create mode 100644 middleware/require-user.global.ts create mode 100644 pages/admin/index.vue create mode 100644 prisma/migrations/20241007043002_add_user_admin/migration.sql create mode 100644 prisma/migrations/20241007065541_add_client/migration.sql create mode 100644 public/fonts/inter/InterVariable-Italic.ttf create mode 100644 public/fonts/inter/InterVariable.ttf create mode 100644 public/wallpapers/signin.jpg rename server/api/v1/{ => auth}/signin/simple.post.ts (91%) rename server/api/v1/{ => auth}/signup/simple.post.ts (100%) create mode 100644 server/api/v1/client/handshake.post.ts create mode 100644 server/api/v1/client/initiate.post.ts create mode 100644 server/api/v1/client/session.post.ts create mode 100644 server/internal/clients/README.md create mode 100644 server/internal/clients/ca.ts create mode 100644 server/internal/clients/store.ts create mode 100644 server/internal/downloads/README.md create mode 100644 server/internal/downloads/coordinator.ts create mode 100644 server/plugins/ca.ts create mode 100644 server/routes/signout.get.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6c9aa06 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +DATABASE_URL="postgres://drop:drop@127.0.0.1:5432/drop" + +CLIENT_CERTIFICATES="./.data/ca" + +GIANT_BOMB_API_KEY="" \ No newline at end of file diff --git a/.yarnrc b/.yarnrc new file mode 100644 index 0000000..6370fa5 --- /dev/null +++ b/.yarnrc @@ -0,0 +1 @@ +"@drop:registry" "https://lab.deepcore.dev/api/v4/projects/57/packages/npm/" \ No newline at end of file diff --git a/app.vue b/app.vue index f8eacfa..b34046f 100644 --- a/app.vue +++ b/app.vue @@ -3,3 +3,7 @@ + + diff --git a/assets/core.scss b/assets/core.scss index 36bfbbe..61d4df6 100644 --- a/assets/core.scss +++ b/assets/core.scss @@ -36,4 +36,16 @@ $helvetica: ( font-weight: $weight; font-style: $style; } +} + +@font-face { + font-family: "Inter"; + src: url("/fonts/inter/InterVariable.ttf"); + font-style: normal; +} + +@font-face { + font-family: "Inter"; + src: url("/fonts/inter/InterVariable-Italic.ttf"); + font-style: italic; } \ No newline at end of file diff --git a/components/HeaderUserWidget.vue b/components/HeaderUserWidget.vue new file mode 100644 index 0000000..72c8828 --- /dev/null +++ b/components/HeaderUserWidget.vue @@ -0,0 +1,68 @@ + + + \ No newline at end of file diff --git a/components/InlineUserWidget.vue b/components/InlineUserWidget.vue deleted file mode 100644 index c73cda6..0000000 --- a/components/InlineUserWidget.vue +++ /dev/null @@ -1,18 +0,0 @@ - - - \ No newline at end of file diff --git a/components/LoadingButton.vue b/components/LoadingButton.vue new file mode 100644 index 0000000..b61303f --- /dev/null +++ b/components/LoadingButton.vue @@ -0,0 +1,31 @@ + + + diff --git a/components/PanelWidget.vue b/components/PanelWidget.vue new file mode 100644 index 0000000..be7ba0f --- /dev/null +++ b/components/PanelWidget.vue @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/components/UserFooter.vue b/components/UserFooter.vue index cb9705b..e5c22ef 100644 --- a/components/UserFooter.vue +++ b/components/UserFooter.vue @@ -18,18 +18,18 @@
-

Solutions

+

Games

-

Support

+

Community

-

Company

+

Documentation

-

Legal

+

About

    -
  • +
  • {{ item.name }}
  • @@ -67,29 +67,26 @@ import GithubLogo from './GithubLogo.vue'; import DiscordLogo from './DiscordLogo.vue'; const navigation = { - solutions: [ - { name: 'Marketing', href: '#' }, - { name: 'Analytics', href: '#' }, - { name: 'Commerce', href: '#' }, - { name: 'Insights', href: '#' }, + games: [ + { name: 'Newly Added', href: '#' }, + { name: 'New Releases', href: '#' }, + { name: 'Top Sellers', href: '#' }, + { name: 'Find a Game', href: '#' }, ], - support: [ - { name: 'Pricing', href: '#' }, - { name: 'Documentation', href: '#' }, - { name: 'Guides', href: '#' }, - { name: 'API Status', href: '#' }, + community: [ + { name: 'Friends', href: '#' }, + { name: 'Groups', href: '#' }, + { name: 'Servers', href: '#' }, ], - company: [ - { name: 'About', href: '#' }, - { name: 'Blog', href: '#' }, - { name: 'Jobs', href: '#' }, - { name: 'Press', href: '#' }, - { name: 'Partners', href: '#' }, + documentation: [ + { name: 'API', href: '#' }, + { name: 'Server Docs', href: '#' }, + { name: 'Client Docs', href: '#' }, ], - legal: [ - { name: 'Claim', href: '#' }, - { name: 'Privacy', href: '#' }, - { name: 'Terms', href: '#' }, + about: [ + { name: 'About Drop', href: '#' }, + { name: 'Features', href: '#' }, + { name: 'FAQ', href: '#' }, ], social: [ { diff --git a/components/UserHeader.vue b/components/UserHeader.vue index a58743e..9bbaeef 100644 --- a/components/UserHeader.vue +++ b/components/UserHeader.vue @@ -3,7 +3,7 @@
@@ -26,8 +26,7 @@ \ No newline at end of file + titleTemplate(title) { + if (title) return `${title} | Drop`; + return `Drop`; + }, +}); + diff --git a/middleware/require-user.global.ts b/middleware/require-user.global.ts new file mode 100644 index 0000000..8273279 --- /dev/null +++ b/middleware/require-user.global.ts @@ -0,0 +1,15 @@ +const whitelistedPrefixes = ["/signin", "/register"]; + +export default defineNuxtRouteMiddleware(async (to, from) => { + if (import.meta.server) return; + if (whitelistedPrefixes.findIndex((e) => to.fullPath.startsWith(e)) != -1) + return; + + const user = useUser(); + if (user === undefined) { + await updateUser(); + } + if (!user.value) { + return navigateTo({ path: "/signin", query: { redirect: to.fullPath } }); + } +}); diff --git a/package.json b/package.json index b733015..906ec72 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "postinstall": "nuxt prepare" }, "dependencies": { + "@drop/droplet": "^0.2.0", + "@drop/droplet-linux-x64-gnu": "^0.2.0", "@headlessui/vue": "^1.7.23", "@heroicons/vue": "^2.1.5", "@prisma/client": "5.20.0", @@ -25,6 +27,7 @@ }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e", "devDependencies": { + "@tailwindcss/forms": "^0.5.9", "@types/bcrypt": "^5.0.2", "@types/turndown": "^5.0.5", "@types/uuid": "^10.0.0", diff --git a/pages/admin/index.vue b/pages/admin/index.vue new file mode 100644 index 0000000..ad33254 --- /dev/null +++ b/pages/admin/index.vue @@ -0,0 +1,8 @@ + + + \ No newline at end of file diff --git a/pages/index.vue b/pages/index.vue index 99393bc..48b5df7 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -1,9 +1,11 @@ \ No newline at end of file + title: "Home", +}); + +const user = useUser(); + diff --git a/pages/register.vue b/pages/register.vue index 983f2de..210fbc3 100644 --- a/pages/register.vue +++ b/pages/register.vue @@ -12,7 +12,7 @@ const username = ref(""); const password = ref(""); async function register() { - await $fetch('/api/v1/signup/simple', { + await $fetch('/api/v1/auth/signup/simple', { method: "POST", body: { username: username.value, diff --git a/pages/signin.vue b/pages/signin.vue index 89d67a1..0057975 100644 --- a/pages/signin.vue +++ b/pages/signin.vue @@ -1,23 +1,172 @@ \ No newline at end of file + +async function signin() { + await $fetch("/api/v1/auth/signin/simple", { + method: "POST", + body: { + username: username.value, + password: password.value, + rememberMe: rememberMe.value, + }, + }); + const user = useUser(); + user.value = await $fetch("/api/v1/whoami"); +} + +definePageMeta({ + layout: false, +}); + +useHead({ + title: "Sign in to Drop", +}); + diff --git a/prisma/migrations/20241007043002_add_user_admin/migration.sql b/prisma/migrations/20241007043002_add_user_admin/migration.sql new file mode 100644 index 0000000..554507b --- /dev/null +++ b/prisma/migrations/20241007043002_add_user_admin/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "admin" BOOLEAN NOT NULL DEFAULT false; diff --git a/prisma/migrations/20241007065541_add_client/migration.sql b/prisma/migrations/20241007065541_add_client/migration.sql new file mode 100644 index 0000000..efca156 --- /dev/null +++ b/prisma/migrations/20241007065541_add_client/migration.sql @@ -0,0 +1,15 @@ +-- CreateEnum +CREATE TYPE "ClientCapabilities" AS ENUM ('DownloadAggregation'); + +-- CreateTable +CREATE TABLE "Client" ( + "sharedToken" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "endpoint" TEXT NOT NULL, + "capabilities" "ClientCapabilities"[], + + CONSTRAINT "Client_pkey" PRIMARY KEY ("sharedToken") +); + +-- AddForeignKey +ALTER TABLE "Client" ADD CONSTRAINT "Client_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 31f6a07..4dddcb5 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -14,10 +14,12 @@ datasource db { } model User { - id String @id @default(uuid()) - username String @unique + id String @id @default(uuid()) + username String @unique + admin Boolean @default(false) authMecs LinkedAuthMec[] + clients Client[] } enum AuthMec { @@ -35,6 +37,20 @@ model LinkedAuthMec { @@id([userId, mec]) } +enum ClientCapabilities { + DownloadAggregation +} + +// References a device +model Client { + sharedToken String @id @default(uuid()) + userId String + user User @relation(fields: [userId], references: [id]) + + endpoint String + capabilities ClientCapabilities[] +} + enum MetadataSource { Custom GiantBomb diff --git a/public/fonts/inter/InterVariable-Italic.ttf b/public/fonts/inter/InterVariable-Italic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ed674e77cb61b71f4fda616cd70b4bf9849c743a GIT binary patch literal 894460 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