From 6cfa2bd6d783bab47598954b63a59adbeb05d9c4 Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Sat, 6 Apr 2024 21:10:37 +0100 Subject: [PATCH 1/2] feat(#9): Add sign up --- .env.example | 4 +- package-lock.json | 163 +++++++++++++++++++++++++++++ package.json | 2 + src/hooks.server.ts | 50 ++++++++- src/lib/components/SignUp.svelte | 33 ++++++ src/routes/+layout.server.ts | 10 ++ src/routes/+layout.svelte | 6 ++ src/routes/+layout.ts | 34 ++++++ src/routes/+page.svelte | 11 +- src/routes/auth/confirm/+server.ts | 22 ++++ 10 files changed, 327 insertions(+), 8 deletions(-) create mode 100644 src/lib/components/SignUp.svelte create mode 100644 src/routes/+layout.server.ts create mode 100644 src/routes/+layout.svelte create mode 100644 src/routes/+layout.ts create mode 100644 src/routes/auth/confirm/+server.ts diff --git a/.env.example b/.env.example index 69a4227..14d5bda 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,3 @@ -MONGO_URL="" \ No newline at end of file +MONGO_URL="" +PUBLIC_SUPABASE_URL="" +PUBLIC_SUPABASE_ANON_KEY="" \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c7ffd60..2061f05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "LivingDexTracker", "version": "0.0.1", "dependencies": { + "@supabase/ssr": "^0.1.0", + "@supabase/supabase-js": "^2.42.0", "mongoose": "^8.0.3", "nanoid": "^5.0.4" }, @@ -855,6 +857,112 @@ "win32" ] }, + "node_modules/@supabase/auth-js": { + "version": "2.63.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.63.0.tgz", + "integrity": "sha512-yIgcHnlgv24GxHtVGUhwGqAFDyJkPIC/xjx7HostN08A8yCy8HIfl4JEkTKyBqD1v1L05jNEJOUke4Lf4O1+Qg==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.2.2.tgz", + "integrity": "sha512-sJGq1nludmi7pY/fdtCpyY/pYonx7MfHdN408bqb736guWcVI1AChYVbI4kUM978EuOE4Ci6l7bUudfGg07QRw==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/@supabase/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/@supabase/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.15.0.tgz", + "integrity": "sha512-U4bwBOrhsXWqDjZiYNbVqMBtRGgIIYE0kE5ZNSwsIbeBWfr/UxOMrnkIQUBGIZRhpYW/tw1WnTdRl1AGNyaxcw==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.9.3.tgz", + "integrity": "sha512-lAp50s2n3FhGJFq+wTSXLNIDPw5Y0Wxrgt44eM5nLSA3jZNUUP3Oq2Ccd1CbZdVntPCWLZvJaU//pAd2NE+QnQ==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14", + "@types/phoenix": "^1.5.4", + "@types/ws": "^8.5.10", + "ws": "^8.14.2" + } + }, + "node_modules/@supabase/ssr": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.1.0.tgz", + "integrity": "sha512-bIVrkqjAK5G3KjkIMKYKtAOlCgRRplEWjrlyRyXSOYtgDieiOhk2ZyNAPsEOa1By9OZVxuX5eAW1fitdnuxayw==", + "dependencies": { + "cookie": "^0.5.0", + "ramda": "^0.29.0" + }, + "peerDependencies": { + "@supabase/supabase-js": "^2.33.1" + } + }, + "node_modules/@supabase/ssr/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.5.5.tgz", + "integrity": "sha512-OpLoDRjFwClwc2cjTJZG8XviTiQH4Ik8sCiMK5v7et0MDu2QlXjCAW3ljxJB5+z/KazdMOTnySi+hysxWUPu3w==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.42.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.42.0.tgz", + "integrity": "sha512-1PDqJiA4iG45w3AAu6xkccJ3wPqlGJUoz9CPhScRLLTStxhewYhz0mjryTpXz1kgtNHdUAsirALreezn8UZMjA==", + "dependencies": { + "@supabase/auth-js": "2.63.0", + "@supabase/functions-js": "2.2.2", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.15.0", + "@supabase/realtime-js": "2.9.3", + "@supabase/storage-js": "2.5.5" + } + }, "node_modules/@sveltejs/adapter-auto": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.1.1.tgz", @@ -980,6 +1088,19 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/node": { + "version": "20.12.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.5.tgz", + "integrity": "sha512-BD+BjQ9LS/D8ST9p5uqBxghlN+S42iuNxjsUGjeZobe/ciXzk2qb1B6IXc6AnRLS+yFJRpN2IPEHMzwspfDJNw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/phoenix": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.4.tgz", + "integrity": "sha512-B34A7uot1Cv0XtaHRYDATltAdKx0BvVKNgYNqE4WjtPUa4VQJM7kxeXcVKaH+KS+kCmZ+6w+QaUdcljiheiBJA==" + }, "node_modules/@types/pug": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz", @@ -1005,6 +1126,14 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.1.1.tgz", @@ -2971,6 +3100,15 @@ } ] }, + "node_modules/ramda": { + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.1.tgz", + "integrity": "sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -3512,6 +3650,11 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -4027,6 +4170,26 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index a768bb8..0c5c56e 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,8 @@ }, "type": "module", "dependencies": { + "@supabase/ssr": "^0.1.0", + "@supabase/supabase-js": "^2.42.0", "mongoose": "^8.0.3", "nanoid": "^5.0.4" }, diff --git a/src/hooks.server.ts b/src/hooks.server.ts index ebf14ae..6d6e55e 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,9 +1,49 @@ +import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; +import { createServerClient } from '@supabase/ssr'; import type { Handle } from '@sveltejs/kit'; -import { dbConnect } from '$lib/utils/db'; - -await dbConnect(); export const handle: Handle = async ({ event, resolve }) => { - const response = await resolve(event); - return response; + event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { + cookies: { + get: (key) => event.cookies.get(key), + /** + * Note: You have to add the `path` variable to the + * set and remove method due to sveltekit's cookie API + * requiring this to be set, setting the path to an empty string + * will replicate previous/standard behaviour (https://kit.svelte.dev/docs/types#public-types-cookies) + */ + set: (key, value, options) => { + event.cookies.set(key, value, { ...options, path: '/' }); + }, + remove: (key, options) => { + event.cookies.delete(key, { ...options, path: '/' }); + } + } + }); + + /** + * Unlike `supabase.auth.getSession`, which is unsafe on the server because it + * doesn't validate the JWT, this function validates the JWT by first calling + * `getUser` and aborts early if the JWT signature is invalid. + */ + event.locals.safeGetSession = async () => { + const { + data: { user }, + error + } = await event.locals.supabase.auth.getUser(); + if (error) { + return { session: null, user: null }; + } + + const { + data: { session } + } = await event.locals.supabase.auth.getSession(); + return { session, user }; + }; + + return resolve(event, { + filterSerializedResponseHeaders(name) { + return name === 'content-range'; + } + }); }; diff --git a/src/lib/components/SignUp.svelte b/src/lib/components/SignUp.svelte new file mode 100644 index 0000000..51ce3b3 --- /dev/null +++ b/src/lib/components/SignUp.svelte @@ -0,0 +1,33 @@ + + + + + diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts new file mode 100644 index 0000000..1d33583 --- /dev/null +++ b/src/routes/+layout.server.ts @@ -0,0 +1,10 @@ +import type { LayoutServerLoad } from './$types'; + +export const load: LayoutServerLoad = async ({ locals: { safeGetSession } }) => { + const { session, user } = await safeGetSession(); + + return { + session, + user + }; +}; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..4c0a0db --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,6 @@ + + +
+ +
diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts new file mode 100644 index 0000000..80875ca --- /dev/null +++ b/src/routes/+layout.ts @@ -0,0 +1,34 @@ +import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'; +import type { LayoutLoad } from './$types'; +import { createBrowserClient, isBrowser, parse } from '@supabase/ssr'; + +export const load: LayoutLoad = async ({ fetch, data, depends }) => { + depends('supabase:auth'); + + const supabase = createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { + global: { + fetch + }, + cookies: { + get(key) { + if (!isBrowser()) { + return JSON.stringify(data.session); + } + + const cookie = parse(document.cookie); + return cookie[key]; + } + } + }); + + /** + * It's fine to use `getSession` here, because on the client, `getSession` is + * safe, and on the server, it reads `session` from the `LayoutData`, which + * safely checked the session using `safeGetSession`. + */ + const { + data: { session } + } = await supabase.auth.getSession(); + + return { supabase, session }; +}; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 5982b0a..34fcbc6 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,2 +1,9 @@ -

Welcome to SvelteKit

-

Visit kit.svelte.dev to read the documentation

+ + + diff --git a/src/routes/auth/confirm/+server.ts b/src/routes/auth/confirm/+server.ts new file mode 100644 index 0000000..bd03c7a --- /dev/null +++ b/src/routes/auth/confirm/+server.ts @@ -0,0 +1,22 @@ +import { redirect } from '@sveltejs/kit'; +import { type EmailOtpType } from '@supabase/supabase-js'; + +export const GET = async (event) => { + const { + url, + locals: { supabase } + } = event; + const token_hash = url.searchParams.get('token_hash') as string; + const type = url.searchParams.get('type') as EmailOtpType | null; + const next = url.searchParams.get('next') ?? '/'; + + if (token_hash && type) { + const { error } = await supabase.auth.verifyOtp({ token_hash, type }); + if (!error) { + throw redirect(303, `/${next.slice(1)}`); + } + } + + // return the user to an error page with some instructions + throw redirect(303, '/auth/auth-code-error'); +}; From 869be67c546c2222323e7f6d035b16bfc75f3fa8 Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Sat, 6 Apr 2024 21:46:04 +0100 Subject: [PATCH 2/2] feat(#9): Add sign in and sign out --- src/lib/components/SignIn.svelte | 30 +++++++++++++++++++++ src/lib/components/SignOut.svelte | 20 ++++++++++++++ src/routes/+page.svelte | 44 +++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 src/lib/components/SignIn.svelte create mode 100644 src/lib/components/SignOut.svelte diff --git a/src/lib/components/SignIn.svelte b/src/lib/components/SignIn.svelte new file mode 100644 index 0000000..d1e2d86 --- /dev/null +++ b/src/lib/components/SignIn.svelte @@ -0,0 +1,30 @@ + + + + + diff --git a/src/lib/components/SignOut.svelte b/src/lib/components/SignOut.svelte new file mode 100644 index 0000000..a4cc2bf --- /dev/null +++ b/src/lib/components/SignOut.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 34fcbc6..0e3b845 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,9 +1,53 @@ +{#if userEmail} + Welcome back {userEmail} +
+{/if} +
+ +
+