From 0006f451da309868a2f42e1f0d481ed2b77ca501 Mon Sep 17 00:00:00 2001 From: Julia van der Kris Date: Tue, 16 Jul 2024 17:36:29 +0200 Subject: [PATCH 01/10] Broken attempt at integrating Auth.js --- package.json | 2 + pnpm-lock.yaml | 102 +++++++++++++++++++++ src/lib/server/auth.ts | 24 +++++ src/routes/+layout.server.ts | 7 ++ src/routes/app/auth/protected/+page.svelte | 1 + src/routes/hooks.server.ts | 1 + 6 files changed, 137 insertions(+) create mode 100644 src/lib/server/auth.ts create mode 100644 src/routes/+layout.server.ts create mode 100644 src/routes/app/auth/protected/+page.svelte create mode 100644 src/routes/hooks.server.ts diff --git a/package.json b/package.json index 4f24985..b511665 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,8 @@ "type": "module", "packageManager": "pnpm@9.5.0", "dependencies": { + "@auth/prisma-adapter": "^2.4.1", + "@auth/sveltekit": "^1.4.1", "@types/d3": "^7.4.3", "@types/uuid": "^10.0.0", "d3": "^7.9.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f903ee1..30106ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + '@auth/prisma-adapter': + specifier: ^2.4.1 + version: 2.4.1(@prisma/client@5.14.0(prisma@5.14.0)) + '@auth/sveltekit': + specifier: ^1.4.1 + version: 1.4.1(@sveltejs/kit@2.5.10(@sveltejs/vite-plugin-svelte@3.1.0(svelte@4.2.17)(vite@5.2.11(@types/node@20.12.12)(sass@1.77.2)))(svelte@4.2.17)(vite@5.2.11(@types/node@20.12.12)(sass@1.77.2)))(svelte@4.2.17) '@types/d3': specifier: ^7.4.3 version: 7.4.3 @@ -91,6 +97,41 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@auth/core@0.34.1': + resolution: {integrity: sha512-tuYU2VIbI8rFbkSwP710LmybB2FXJsPN7j3sjRVfN9SXVQBK2ej6LdewQaofpBGp4Mk+cC2UeiGNH0or4tgaeA==} + peerDependencies: + '@simplewebauthn/browser': ^9.0.1 + '@simplewebauthn/server': ^9.0.2 + nodemailer: ^6.8.0 + peerDependenciesMeta: + '@simplewebauthn/browser': + optional: true + '@simplewebauthn/server': + optional: true + nodemailer: + optional: true + + '@auth/prisma-adapter@2.4.1': + resolution: {integrity: sha512-VF5IOTHEWHX6WHUxIbsbc12m34cp5T82fzJfi7DmzwBZb89UsoROejV8l0B1dCTZ+pDIS0d4zN9dSa2gIKywNQ==} + peerDependencies: + '@prisma/client': '>=2.26.0 || >=3 || >=4 || >=5' + + '@auth/sveltekit@1.4.1': + resolution: {integrity: sha512-oIt/eFoYJhsH6Tz625NIfctz5CFxTn1PNrl9FxlDB+S1j4Qli/LRF9hS5Ad24056keKSApXx9o61aQANDsrXLQ==} + peerDependencies: + '@simplewebauthn/browser': ^9.0.1 + '@simplewebauthn/server': ^9.0.3 + '@sveltejs/kit': ^1.0.0 || ^2.0.0 + nodemailer: ^6.6.5 + svelte: ^3.54.0 || ^4.0.0 || ^5 + peerDependenciesMeta: + '@simplewebauthn/browser': + optional: true + '@simplewebauthn/server': + optional: true + nodemailer: + optional: true + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -303,6 +344,9 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@panva/hkdf@1.2.1': + resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1303,6 +1347,9 @@ packages: resolution: {integrity: sha512-qH3nOSj8q/8+Eg8LUPOq3C+6HWkpUioIjDsq1+D4zY91oZvpPttw8GwtF1nReRYKXl+1AORyFqtm2f5Q1SB6/Q==} engines: {node: 14 >=14.21 || 16 >=16.20 || >=18} + jose@5.6.3: + resolution: {integrity: sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==} + js-tokens@9.0.0: resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} @@ -1438,6 +1485,9 @@ packages: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + oauth4webapi@2.11.1: + resolution: {integrity: sha512-aNzOnL98bL6izG97zgnZs1PFEyO4WDVRhz2Pd066NPak44w5ESLRCYmJIyey8avSBPOMtBjhF3ZDDm7bIb7UOg==} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -1546,6 +1596,14 @@ packages: resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} engines: {node: ^10 || ^12 || >=14} + preact-render-to-string@5.2.3: + resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==} + peerDependencies: + preact: '>=10' + + preact@10.11.3: + resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -1565,6 +1623,9 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + pretty-format@3.8.0: + resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} + prisma@5.14.0: resolution: {integrity: sha512-gCNZco7y5XtjrnQYeDJTiVZmT/ncqCr5RY1/Cf8X2wgLRmyh9ayPAGBNziI4qEE4S6SxCH5omQLVo9lmURaJ/Q==} engines: {node: '>=16.13'} @@ -1979,6 +2040,32 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 + '@auth/core@0.34.1': + dependencies: + '@panva/hkdf': 1.2.1 + '@types/cookie': 0.6.0 + cookie: 0.6.0 + jose: 5.6.3 + oauth4webapi: 2.11.1 + preact: 10.11.3 + preact-render-to-string: 5.2.3(preact@10.11.3) + + '@auth/prisma-adapter@2.4.1(@prisma/client@5.14.0(prisma@5.14.0))': + dependencies: + '@auth/core': 0.34.1 + '@prisma/client': 5.14.0(prisma@5.14.0) + transitivePeerDependencies: + - '@simplewebauthn/browser' + - '@simplewebauthn/server' + - nodemailer + + '@auth/sveltekit@1.4.1(@sveltejs/kit@2.5.10(@sveltejs/vite-plugin-svelte@3.1.0(svelte@4.2.17)(vite@5.2.11(@types/node@20.12.12)(sass@1.77.2)))(svelte@4.2.17)(vite@5.2.11(@types/node@20.12.12)(sass@1.77.2)))(svelte@4.2.17)': + dependencies: + '@auth/core': 0.34.1 + '@sveltejs/kit': 2.5.10(@sveltejs/vite-plugin-svelte@3.1.0(svelte@4.2.17)(vite@5.2.11(@types/node@20.12.12)(sass@1.77.2)))(svelte@4.2.17)(vite@5.2.11(@types/node@20.12.12)(sass@1.77.2)) + set-cookie-parser: 2.6.0 + svelte: 4.2.17 + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 @@ -2134,6 +2221,8 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 + '@panva/hkdf@1.2.1': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -3231,6 +3320,8 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jose@5.6.3: {} + js-tokens@9.0.0: {} js-yaml@4.1.0: @@ -3341,6 +3432,8 @@ snapshots: dependencies: path-key: 4.0.0 + oauth4webapi@2.11.1: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -3440,6 +3533,13 @@ snapshots: picocolors: 1.0.1 source-map-js: 1.2.0 + preact-render-to-string@5.2.3(preact@10.11.3): + dependencies: + preact: 10.11.3 + pretty-format: 3.8.0 + + preact@10.11.3: {} + prelude-ls@1.2.1: {} prettier-plugin-svelte@3.2.3(prettier@3.2.5)(svelte@4.2.17): @@ -3455,6 +3555,8 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 + pretty-format@3.8.0: {} + prisma@5.14.0: dependencies: '@prisma/engines': 5.14.0 diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts new file mode 100644 index 0000000..5f00c44 --- /dev/null +++ b/src/lib/server/auth.ts @@ -0,0 +1,24 @@ +import { SvelteKitAuth } from "@auth/sveltekit"; +import { type Provider } from "@auth/sveltekit/providers"; +import { PrismaAdapter } from "@auth/prisma-adapter"; + +import prisma from "$lib/server/prisma"; +import { env } from "$env/dynamic/private"; + + +const surfProvider: Provider = { + id: "surfconext", + name: "SURFconext", + type: "oidc", + issuer: env.SURFCONEXT_ISSUER, + clientId: env.SURFCONEXT_CLIENT_ID, + clientSecret: env.SURFCONEXT_CLIENT_SECRET, +}; + + +export const { handle, signIn, signOut } = SvelteKitAuth({ + providers: [surfProvider], + adapter: PrismaAdapter(prisma), + secret: env.AUTH_SECRET, + // trustHost: true +}); diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts new file mode 100644 index 0000000..498025a --- /dev/null +++ b/src/routes/+layout.server.ts @@ -0,0 +1,7 @@ +import type { LayoutServerLoad } from './$types'; + +export const load: LayoutServerLoad = async (event) => { + return { + session: await event.locals.auth() + } +} diff --git a/src/routes/app/auth/protected/+page.svelte b/src/routes/app/auth/protected/+page.svelte new file mode 100644 index 0000000..31487f8 --- /dev/null +++ b/src/routes/app/auth/protected/+page.svelte @@ -0,0 +1 @@ +

hi there

diff --git a/src/routes/hooks.server.ts b/src/routes/hooks.server.ts new file mode 100644 index 0000000..8f98b74 --- /dev/null +++ b/src/routes/hooks.server.ts @@ -0,0 +1 @@ +export { handle } from '$lib/server/auth'; From d0302c0d9e006b96c3fdc1045ea32599b39812b2 Mon Sep 17 00:00:00 2001 From: Julia van der Kris Date: Tue, 16 Jul 2024 20:46:03 +0200 Subject: [PATCH 02/10] Fix Auth.js config after hours of debugging and hopelessly scouring the internet Swear to god I'm gonna punch an Auth.js dev what the fuck is this --- src/{routes => }/hooks.server.ts | 0 src/lib/server/auth.ts | 20 ++++++++------------ 2 files changed, 8 insertions(+), 12 deletions(-) rename src/{routes => }/hooks.server.ts (100%) diff --git a/src/routes/hooks.server.ts b/src/hooks.server.ts similarity index 100% rename from src/routes/hooks.server.ts rename to src/hooks.server.ts diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index 5f00c44..af147cf 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -1,23 +1,19 @@ import { SvelteKitAuth } from "@auth/sveltekit"; -import { type Provider } from "@auth/sveltekit/providers"; import { PrismaAdapter } from "@auth/prisma-adapter"; import prisma from "$lib/server/prisma"; import { env } from "$env/dynamic/private"; -const surfProvider: Provider = { - id: "surfconext", - name: "SURFconext", - type: "oidc", - issuer: env.SURFCONEXT_ISSUER, - clientId: env.SURFCONEXT_CLIENT_ID, - clientSecret: env.SURFCONEXT_CLIENT_SECRET, -}; - - export const { handle, signIn, signOut } = SvelteKitAuth({ - providers: [surfProvider], + providers: [{ + id: "surfconext", + name: "SURFconext", + type: "oidc", + issuer: env.SURFCONEXT_ISSUER, + clientId: env.SURFCONEXT_CLIENT_ID, + clientSecret: env.SURFCONEXT_CLIENT_SECRET, + }], adapter: PrismaAdapter(prisma), secret: env.AUTH_SECRET, // trustHost: true From c8b1cc36d9079c7266342d57c05a2441bec3cbd3 Mon Sep 17 00:00:00 2001 From: Julia van der Kris Date: Wed, 17 Jul 2024 22:22:21 +0200 Subject: [PATCH 03/10] Add auth.js prisma models --- .../migration.sql | 93 +++++++++++++++++++ prisma/schema.prisma | 67 ++++++++++--- src/routes/app/+layout.svelte | 7 +- src/routes/signin/+page.server.ts | 4 + src/routes/signout/+page.server.ts | 4 + 5 files changed, 161 insertions(+), 14 deletions(-) create mode 100644 prisma/migrations/20240717184305_auth_js_models/migration.sql create mode 100644 src/routes/signin/+page.server.ts create mode 100644 src/routes/signout/+page.server.ts diff --git a/prisma/migrations/20240717184305_auth_js_models/migration.sql b/prisma/migrations/20240717184305_auth_js_models/migration.sql new file mode 100644 index 0000000..9de25e0 --- /dev/null +++ b/prisma/migrations/20240717184305_auth_js_models/migration.sql @@ -0,0 +1,93 @@ +/* + Warnings: + + - The primary key for the `User` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to drop the column `first_name` on the `User` table. All the data in the column will be lost. + - You are about to drop the column `last_name` on the `User` table. All the data in the column will be lost. + - You are about to drop the column `netid` on the `User` table. All the data in the column will be lost. + - You are about to drop the column `role` on the `User` table. All the data in the column will be lost. + - A unique constraint covering the columns `[email]` on the table `User` will be added. If there are existing duplicate values, this will fail. + - Added the required column `updatedAt` to the `User` table without a default value. This is not possible if the table is not empty. + - Made the column `email` on table `User` required. This step will fail if there are existing NULL values in that column. + +*/ +-- DropForeignKey +ALTER TABLE "_ProgramToUser" DROP CONSTRAINT "_ProgramToUser_B_fkey"; + +-- DropIndex +DROP INDEX "User_netid_key"; + +-- AlterTable +ALTER TABLE "User" DROP CONSTRAINT "User_pkey", +DROP COLUMN "first_name", +DROP COLUMN "last_name", +DROP COLUMN "netid", +DROP COLUMN "role", +ADD COLUMN "emailVerified" TIMESTAMP(3), +ADD COLUMN "firstName" TEXT, +ADD COLUMN "image" TEXT, +ADD COLUMN "lastName" TEXT, +ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL, +ALTER COLUMN "id" DROP DEFAULT, +ALTER COLUMN "id" SET DATA TYPE TEXT, +ALTER COLUMN "email" SET NOT NULL, +ADD CONSTRAINT "User_pkey" PRIMARY KEY ("id"); +DROP SEQUENCE "User_id_seq"; + +-- AlterTable +ALTER TABLE "_ProgramToUser" ALTER COLUMN "B" SET DATA TYPE TEXT; + +-- DropEnum +DROP TYPE "UserRole"; + +-- CreateTable +CREATE TABLE "Account" ( + "userId" TEXT NOT NULL, + "type" TEXT NOT NULL, + "provider" TEXT NOT NULL, + "providerAccountId" TEXT NOT NULL, + "refresh_token" TEXT, + "access_token" TEXT, + "expires_at" INTEGER, + "token_type" TEXT, + "scope" TEXT, + "id_token" TEXT, + "session_state" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Account_pkey" PRIMARY KEY ("provider","providerAccountId") +); + +-- CreateTable +CREATE TABLE "Session" ( + "sessionToken" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "expires" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL +); + +-- CreateTable +CREATE TABLE "VerificationToken" ( + "identifier" TEXT NOT NULL, + "token" TEXT NOT NULL, + "expires" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "VerificationToken_pkey" PRIMARY KEY ("identifier","token") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- AddForeignKey +ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_ProgramToUser" ADD CONSTRAINT "_ProgramToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ecff9a1..88280d0 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -14,25 +14,66 @@ datasource db { } -enum UserRole { - USER - ADMIN +// START AUTH STUFF +model User { + id String @id @default(cuid()) + firstName String? + lastName String? + email String @unique + emailVerified DateTime? + image String? + accounts Account[] + sessions Session[] + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + programs Program[] } -model User { - id Int @id @default(autoincrement()) - netid String @unique - first_name String - last_name String - role UserRole @default(USER) - createdAt DateTime @default(now()) - email String? - programs Program[] +model Account { + userId String + type String + provider String + providerAccountId String + refresh_token String? + access_token String? + expires_at Int? + token_type String? + scope String? + id_token String? + session_state String? + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@id([provider, providerAccountId]) } +model Session { + sessionToken String @unique + userId String + expires DateTime + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model VerificationToken { + identifier String + token String + expires DateTime + + @@id([identifier, token]) +} +// END AUTH STUFF + model Program { - id Int @id @default(autoincrement()) + id Int @id @default(autoincrement()) name String description String? createdAt DateTime @default(now()) diff --git a/src/routes/app/+layout.svelte b/src/routes/app/+layout.svelte index 22be2e5..28527e7 100644 --- a/src/routes/app/+layout.svelte +++ b/src/routes/app/+layout.svelte @@ -4,6 +4,11 @@ import Button from '$components/Button.svelte'; import LinkButton from '$components/LinkButton.svelte'; + // Auth + import { SignIn, SignOut } from '@auth/sveltekit/components'; + import { signIn, signOut } from '@auth/sveltekit/client'; + import { page } from '$app/stores'; + import PRIME_logo from '$assets/PRIME-logo.svg'; import TUDelft_logo from '$assets/TUD-logo.svg'; @@ -26,7 +31,7 @@ {:else} Register - + {/if} diff --git a/src/routes/signin/+page.server.ts b/src/routes/signin/+page.server.ts new file mode 100644 index 0000000..372118a --- /dev/null +++ b/src/routes/signin/+page.server.ts @@ -0,0 +1,4 @@ +import { signIn } from '$lib/server/auth'; +import type { Actions } from './$types'; + +export const actions: Actions = { default: signIn } diff --git a/src/routes/signout/+page.server.ts b/src/routes/signout/+page.server.ts new file mode 100644 index 0000000..7a1456d --- /dev/null +++ b/src/routes/signout/+page.server.ts @@ -0,0 +1,4 @@ +import { signOut } from '$lib/server/auth'; +import type { Actions } from './$types'; + +export const actions: Actions = { default: signOut } From 1fa75a042bb66a23568365470e562c51c5d9834e Mon Sep 17 00:00:00 2001 From: Julia van der Kris Date: Mon, 26 Aug 2024 21:55:46 +0200 Subject: [PATCH 04/10] AAAAAAAAAAAAAAAaaaa why can I not read OIDC claims?? --- src/lib/server/auth.ts | 24 ++++++++++++++++--- src/routes/app/auth/protected/+page.server.ts | 0 src/routes/app/auth/protected/+page.svelte | 9 +++++++ 3 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 src/routes/app/auth/protected/+page.server.ts diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index af147cf..498bd97 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -3,17 +3,35 @@ import { PrismaAdapter } from "@auth/prisma-adapter"; import prisma from "$lib/server/prisma"; import { env } from "$env/dynamic/private"; +import type { OIDCConfig } from "@auth/sveltekit/providers"; -export const { handle, signIn, signOut } = SvelteKitAuth({ - providers: [{ +interface SurfConextProfile extends Record { + email: string; + email_verified: boolean; + name: string; + nickname: string; + preferred_username: string; + given_name: string; + family_name: string; +} + + +function SurfConextProvider

(): OIDCConfig

{ + return { id: "surfconext", name: "SURFconext", type: "oidc", issuer: env.SURFCONEXT_ISSUER, + wellKnown: "https://connect.test.surfconext.nl/.well-known/openid-configuration", clientId: env.SURFCONEXT_CLIENT_ID, clientSecret: env.SURFCONEXT_CLIENT_SECRET, - }], + }; +} + + +export const { handle, signIn, signOut } = SvelteKitAuth({ + providers: [SurfConextProvider], adapter: PrismaAdapter(prisma), secret: env.AUTH_SECRET, // trustHost: true diff --git a/src/routes/app/auth/protected/+page.server.ts b/src/routes/app/auth/protected/+page.server.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/app/auth/protected/+page.svelte b/src/routes/app/auth/protected/+page.svelte index 31487f8..cec1ad7 100644 --- a/src/routes/app/auth/protected/+page.svelte +++ b/src/routes/app/auth/protected/+page.svelte @@ -1 +1,10 @@ + + + +

hi there

From 5e66ea39e28637832acfb6542f1ad1bb11222b64 Mon Sep 17 00:00:00 2001 From: Julia van der Kris Date: Tue, 17 Sep 2024 22:10:26 +0200 Subject: [PATCH 05/10] Improve user/program/course roles, add graph links --- prisma/schema.prisma | 60 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 88280d0..9f3a8de 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -15,8 +15,25 @@ datasource db { // START AUTH STUFF +enum UserRole { + USER + ADMIN +} + +enum ProgramRole { + COORDINATOR + ADMIN +} + +enum CourseRole { + READ + WRITE + ADMIN +} + model User { id String @id @default(cuid()) + role UserRole @default(USER) firstName String? lastName String? email String @unique @@ -28,7 +45,8 @@ model User { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - programs Program[] + programs ProgramCoordinator[] + courses CourseCoordinator[] } model Account { @@ -79,7 +97,31 @@ model Program { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt courses Course[] - coordinators User[] + coordinators ProgramCoordinator[] +} + + +model ProgramCoordinator { + userId String + programId Int + role ProgramRole @default(COORDINATOR) + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + program Program @relation(fields: [programId], references: [id], onDelete: Cascade) + + @@id([userId, programId]) +} + + +model CourseCoordinator { + userId String + courseId Int + role CourseRole @default(READ) + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + course Course @relation(fields: [courseId], references: [id], onDelete: Cascade) + + @@id([userId, courseId]) } @@ -97,6 +139,8 @@ model Course { program Program @relation(fields: [programId], references: [id]) programId Int graphs Graph[] + links GraphLink[] + coordinators CourseCoordinator[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } @@ -110,6 +154,7 @@ model Graph { domains Domain[] subjects Subject[] lectures Lecture[] + links GraphLink[] // UI names createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -153,3 +198,14 @@ model Lecture { name String? subjects Subject[] } + + +model GraphLink { + id Int @id @default(autoincrement()) + name String + courseId Int + graphId Int? + + course Course @relation(fields: [courseId], references: [id], onDelete: Cascade) + graph Graph? @relation(fields: [graphId], references: [id]) +} From 0ce526a7b234160a470dec1f0bbb01995015b2b5 Mon Sep 17 00:00:00 2001 From: Julia van der Kris Date: Fri, 20 Sep 2024 00:15:52 +0200 Subject: [PATCH 06/10] Add join table for SubjectLecture, with coordinates --- prisma/schema.prisma | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9f3a8de..6d91b8a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -187,7 +187,20 @@ model Subject { domainId Int? parentSubjects Subject[] @relation("SubjectRelation") childSubjects Subject[] @relation("SubjectRelation") - lectures Lecture[] + lectures SubjectLecture[] +} + + +model SubjectLecture { + subjectId Int + lectureId Int + x Int @default(0) + y Int @default(0) + + subject Subject @relation(fields: [subjectId], references: [id], onDelete: Cascade) + lecture Lecture @relation(fields: [lectureId], references: [id], onDelete: Cascade) + + @@id([subjectId, lectureId]) } @@ -196,7 +209,7 @@ model Lecture { graph Graph @relation(fields: [graphId], references: [id]) graphId Int name String? - subjects Subject[] + subjects SubjectLecture[] } From 2181aff6e59891db185a8b5660b704c315fc4ed4 Mon Sep 17 00:00:00 2001 From: Julia van der Kris Date: Sat, 5 Oct 2024 16:28:23 +0200 Subject: [PATCH 07/10] Enable Auth.js debug info when DEBUG environment variable is set --- src/lib/server/auth.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index 498bd97..c5d4430 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -34,5 +34,6 @@ export const { handle, signIn, signOut } = SvelteKitAuth({ providers: [SurfConextProvider], adapter: PrismaAdapter(prisma), secret: env.AUTH_SECRET, + debug: Boolean(env.DEBUG), // trustHost: true }); From cf9cb6be166313fbdfad3d123ca40a0cf807062b Mon Sep 17 00:00:00 2001 From: Julia van der Kris Date: Thu, 10 Oct 2024 16:17:42 +0200 Subject: [PATCH 08/10] Make Programs & Courses many-to-many + formatting --- prisma/schema.prisma | 73 ++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6d91b8a..5eca838 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -9,8 +9,8 @@ generator client { } datasource db { - provider = "postgresql" - url = env("DATABASE_URL") + provider = "postgresql" + url = env("DATABASE_URL") } @@ -32,60 +32,60 @@ enum CourseRole { } model User { - id String @id @default(cuid()) + id String @id @default(cuid()) role UserRole @default(USER) firstName String? lastName String? - email String @unique - emailVerified DateTime? - image String? - accounts Account[] - sessions Session[] + email String @unique + emailVerified DateTime? + image String? + accounts Account[] + sessions Session[] - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt programs ProgramCoordinator[] courses CourseCoordinator[] } model Account { - userId String - type String - provider String - providerAccountId String - refresh_token String? - access_token String? - expires_at Int? - token_type String? - scope String? - id_token String? - session_state String? + userId String + type String + provider String + providerAccountId String + refresh_token String? + access_token String? + expires_at Int? + token_type String? + scope String? + id_token String? + session_state String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt - user User @relation(fields: [userId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) - @@id([provider, providerAccountId]) + @@id([provider, providerAccountId]) } model Session { - sessionToken String @unique - userId String - expires DateTime - user User @relation(fields: [userId], references: [id], onDelete: Cascade) + sessionToken String @unique + userId String + expires DateTime + user User @relation(fields: [userId], references: [id], onDelete: Cascade) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model VerificationToken { - identifier String - token String - expires DateTime + identifier String + token String + expires DateTime - @@id([identifier, token]) + @@id([identifier, token]) } // END AUTH STUFF @@ -136,8 +136,7 @@ model Course { name String description String? type CourseType @default(COURSE) - program Program @relation(fields: [programId], references: [id]) - programId Int + programs Program[] graphs Graph[] links GraphLink[] coordinators CourseCoordinator[] From 8e1ce342de559f31c1d3a04ea45bda64d4987239 Mon Sep 17 00:00:00 2001 From: Julia van der Kris Date: Sun, 20 Oct 2024 14:56:42 +0200 Subject: [PATCH 09/10] Auth.js workaround: manually fetch userinfo from OIDC endpoint --- prisma/schema.prisma | 1 + src/lib/server/auth.ts | 39 ++++++++++++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5eca838..79338d4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -34,6 +34,7 @@ enum CourseRole { model User { id String @id @default(cuid()) role UserRole @default(USER) + nickname String? firstName String? lastName String? email String @unique diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index c5d4430..0d56674 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -7,13 +7,27 @@ import type { OIDCConfig } from "@auth/sveltekit/providers"; interface SurfConextProfile extends Record { - email: string; - email_verified: boolean; - name: string; nickname: string; - preferred_username: string; - given_name: string; - family_name: string; + firstName: string; + lastName: string; + email: string; +} + + +/** + * Workaround for Auth.js not automatically fetching userinfo through endpoint specified in .well-known config + * + * @param accessToken JWT access token + * @returns Object containing user information + */ +async function fetchUserInfo(accessToken: string | undefined) { + if (!accessToken) throw new Error("No access token provided"); + const res = await fetch(`${env.SURFCONEXT_ISSUER}/oidc/userinfo`, { + headers: { + "Authorization": `Bearer ${accessToken}` + } + }); + return await res.json(); } @@ -23,9 +37,20 @@ function SurfConextProvider

(): OIDCConfig

{ name: "SURFconext", type: "oidc", issuer: env.SURFCONEXT_ISSUER, - wellKnown: "https://connect.test.surfconext.nl/.well-known/openid-configuration", + wellKnown: `${env.SURFCONEXT_ISSUER}/.well-known/openid-configuration`, clientId: env.SURFCONEXT_CLIENT_ID, clientSecret: env.SURFCONEXT_CLIENT_SECRET, + + async profile(profile, tokens): Promise { + const userInfo = await fetchUserInfo(tokens.access_token); + const res = { + nickname: userInfo.nickname, + firstName: userInfo.given_name, + lastName: userInfo.family_name, + email: userInfo.email, + }; + return res; + } }; } From ff26d79644af2bda03018c7217eeb8a074df6528 Mon Sep 17 00:00:00 2001 From: Julia van der Kris Date: Sun, 20 Oct 2024 16:19:30 +0200 Subject: [PATCH 10/10] Rename some more role-related stuff in prisma schema --- .../migration.sql | 124 ++++++++++++++++++ prisma/schema.prisma | 16 +-- 2 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 prisma/migrations/20241020141845_fix_auth_and_roles/migration.sql diff --git a/prisma/migrations/20241020141845_fix_auth_and_roles/migration.sql b/prisma/migrations/20241020141845_fix_auth_and_roles/migration.sql new file mode 100644 index 0000000..b266f3c --- /dev/null +++ b/prisma/migrations/20241020141845_fix_auth_and_roles/migration.sql @@ -0,0 +1,124 @@ +/* + Warnings: + + - You are about to drop the column `programId` on the `Course` table. All the data in the column will be lost. + - You are about to drop the `_LectureToSubject` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `_ProgramToUser` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- CreateEnum +CREATE TYPE "UserRole" AS ENUM ('USER', 'ADMIN'); + +-- CreateEnum +CREATE TYPE "ProgramRole" AS ENUM ('WRITE', 'ADMIN'); + +-- CreateEnum +CREATE TYPE "CourseRole" AS ENUM ('READ', 'WRITE', 'ADMIN'); + +-- DropForeignKey +ALTER TABLE "Course" DROP CONSTRAINT "Course_programId_fkey"; + +-- DropForeignKey +ALTER TABLE "_LectureToSubject" DROP CONSTRAINT "_LectureToSubject_A_fkey"; + +-- DropForeignKey +ALTER TABLE "_LectureToSubject" DROP CONSTRAINT "_LectureToSubject_B_fkey"; + +-- DropForeignKey +ALTER TABLE "_ProgramToUser" DROP CONSTRAINT "_ProgramToUser_A_fkey"; + +-- DropForeignKey +ALTER TABLE "_ProgramToUser" DROP CONSTRAINT "_ProgramToUser_B_fkey"; + +-- AlterTable +ALTER TABLE "Course" DROP COLUMN "programId"; + +-- AlterTable +ALTER TABLE "User" ADD COLUMN "nickname" TEXT, +ADD COLUMN "role" "UserRole" NOT NULL DEFAULT 'USER'; + +-- DropTable +DROP TABLE "_LectureToSubject"; + +-- DropTable +DROP TABLE "_ProgramToUser"; + +-- CreateTable +CREATE TABLE "ProgramUser" ( + "userId" TEXT NOT NULL, + "programId" INTEGER NOT NULL, + "role" "ProgramRole" NOT NULL DEFAULT 'WRITE', + + CONSTRAINT "ProgramUser_pkey" PRIMARY KEY ("userId","programId") +); + +-- CreateTable +CREATE TABLE "CourseUser" ( + "userId" TEXT NOT NULL, + "courseId" INTEGER NOT NULL, + "role" "CourseRole" NOT NULL DEFAULT 'READ', + + CONSTRAINT "CourseUser_pkey" PRIMARY KEY ("userId","courseId") +); + +-- CreateTable +CREATE TABLE "SubjectLecture" ( + "subjectId" INTEGER NOT NULL, + "lectureId" INTEGER NOT NULL, + "x" INTEGER NOT NULL DEFAULT 0, + "y" INTEGER NOT NULL DEFAULT 0, + + CONSTRAINT "SubjectLecture_pkey" PRIMARY KEY ("subjectId","lectureId") +); + +-- CreateTable +CREATE TABLE "GraphLink" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "courseId" INTEGER NOT NULL, + "graphId" INTEGER, + + CONSTRAINT "GraphLink_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "_CourseToProgram" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "_CourseToProgram_AB_unique" ON "_CourseToProgram"("A", "B"); + +-- CreateIndex +CREATE INDEX "_CourseToProgram_B_index" ON "_CourseToProgram"("B"); + +-- AddForeignKey +ALTER TABLE "ProgramUser" ADD CONSTRAINT "ProgramUser_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ProgramUser" ADD CONSTRAINT "ProgramUser_programId_fkey" FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CourseUser" ADD CONSTRAINT "CourseUser_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CourseUser" ADD CONSTRAINT "CourseUser_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "SubjectLecture" ADD CONSTRAINT "SubjectLecture_subjectId_fkey" FOREIGN KEY ("subjectId") REFERENCES "Subject"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "SubjectLecture" ADD CONSTRAINT "SubjectLecture_lectureId_fkey" FOREIGN KEY ("lectureId") REFERENCES "Lecture"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "GraphLink" ADD CONSTRAINT "GraphLink_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "GraphLink" ADD CONSTRAINT "GraphLink_graphId_fkey" FOREIGN KEY ("graphId") REFERENCES "Graph"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_CourseToProgram" ADD CONSTRAINT "_CourseToProgram_A_fkey" FOREIGN KEY ("A") REFERENCES "Course"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_CourseToProgram" ADD CONSTRAINT "_CourseToProgram_B_fkey" FOREIGN KEY ("B") REFERENCES "Program"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 79338d4..f8e84ca 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -21,7 +21,7 @@ enum UserRole { } enum ProgramRole { - COORDINATOR + WRITE ADMIN } @@ -46,8 +46,8 @@ model User { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - programs ProgramCoordinator[] - courses CourseCoordinator[] + programs ProgramUser[] + courses CourseUser[] } model Account { @@ -98,14 +98,14 @@ model Program { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt courses Course[] - coordinators ProgramCoordinator[] + users ProgramUser[] } -model ProgramCoordinator { +model ProgramUser { userId String programId Int - role ProgramRole @default(COORDINATOR) + role ProgramRole @default(WRITE) user User @relation(fields: [userId], references: [id], onDelete: Cascade) program Program @relation(fields: [programId], references: [id], onDelete: Cascade) @@ -114,7 +114,7 @@ model ProgramCoordinator { } -model CourseCoordinator { +model CourseUser { userId String courseId Int role CourseRole @default(READ) @@ -140,7 +140,7 @@ model Course { programs Program[] graphs Graph[] links GraphLink[] - coordinators CourseCoordinator[] + users CourseUser[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }