├── .env.example
├── .eslintignore
├── .eslintrc.cjs
├── .gitignore
├── .npmrc
├── .prettierignore
├── .prettierrc
├── README.md
├── package-lock.json
├── package.json
├── playwright.config.ts
├── postcss.config.cjs
├── rome.json
├── src
├── app.d.ts
├── app.html
├── app.postcss
├── global.postcss
├── hooks.client.ts
├── hooks.server.ts
├── index.test.ts
├── lib
│ └── supabase.ts
└── routes
│ ├── +layout.server.ts
│ ├── +layout.svelte
│ ├── +page.svelte
│ ├── login
│ ├── +page.server.ts
│ └── +page.svelte
│ ├── logout
│ └── +server.ts
│ └── register
│ ├── +page.server.ts
│ └── +page.svelte
├── static
└── favicon.png
├── svelte.config.js
├── tests
└── test.ts
├── tsconfig.json
└── vite.config.js
/.env.example:
--------------------------------------------------------------------------------
1 | PUBLIC_SUPABASE_URL=""
2 | PUBLIC_SUPABASE_ANON_KEY=""
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
10 | # Ignore files for PNPM, NPM and YARN
11 | pnpm-lock.yaml
12 | package-lock.json
13 | yarn.lock
14 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: '@typescript-eslint/parser',
4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
5 | plugins: ['svelte3', '@typescript-eslint'],
6 | ignorePatterns: ['*.cjs'],
7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
8 | settings: {
9 | 'svelte3/typescript': () => require('typescript')
10 | },
11 | parserOptions: {
12 | sourceType: 'module',
13 | ecmaVersion: 2020
14 | },
15 | env: {
16 | browser: true,
17 | es2017: true,
18 | node: true
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | vite.config.js.timestamp-*
8 | vite.config.ts.timestamp-*
9 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
10 | # Ignore files for PNPM, NPM and YARN
11 | pnpm-lock.yaml
12 | package-lock.json
13 | yarn.lock
14 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "singleQuote": true,
4 | "trailingComma": "none",
5 | "printWidth": 100,
6 | "plugins": ["prettier-plugin-svelte"],
7 | "pluginSearchDirs": ["."],
8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
9 | }
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # create-svelte
2 |
3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
4 |
5 | ## Creating a project
6 |
7 | If you're seeing this, you've probably already done this step. Congrats!
8 |
9 | ```bash
10 | # create a new project in the current directory
11 | npm create svelte@latest
12 |
13 | # create a new project in my-app
14 | npm create svelte@latest my-app
15 | ```
16 |
17 | ## Developing
18 |
19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
20 |
21 | ```bash
22 | npm run dev
23 |
24 | # or start the server and open the app in a new browser tab
25 | npm run dev -- --open
26 | ```
27 |
28 | ## Building
29 |
30 | To create a production version of your app:
31 |
32 | ```bash
33 | npm run build
34 | ```
35 |
36 | You can preview the production build with `npm run preview`.
37 |
38 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sk-supabase-auth",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite dev",
7 | "build": "vite build",
8 | "preview": "vite preview",
9 | "test": "playwright test",
10 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
11 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
12 | "test:unit": "vitest",
13 | "lint": "prettier --plugin-search-dir . --check . && eslint .",
14 | "format": "prettier --plugin-search-dir . --write ."
15 | },
16 | "devDependencies": {
17 | "@playwright/test": "^1.28.1",
18 | "@supabase/auth-helpers-sveltekit": "^0.8.6",
19 | "@supabase/supabase-js": "^2.1.3",
20 | "@sveltejs/adapter-auto": "next",
21 | "@sveltejs/kit": "next",
22 | "@typescript-eslint/eslint-plugin": "^5.45.0",
23 | "@typescript-eslint/parser": "^5.45.0",
24 | "autoprefixer": "^10.4.7",
25 | "eslint": "^8.28.0",
26 | "eslint-config-prettier": "^8.5.0",
27 | "eslint-plugin-svelte3": "^4.0.0",
28 | "postcss": "^8.4.14",
29 | "postcss-load-config": "^4.0.1",
30 | "postcss-preset-env": "^7.8.3",
31 | "postcss-simple-vars": "^7.0.1",
32 | "prettier": "^2.8.0",
33 | "prettier-plugin-svelte": "^2.8.1",
34 | "svelte": "^3.54.0",
35 | "svelte-check": "^2.9.2",
36 | "svelte-preprocess": "^4.10.7",
37 | "tslib": "^2.4.1",
38 | "typescript": "^4.9.3",
39 | "vite": "^4.0.0",
40 | "vitest": "^0.25.3"
41 | },
42 | "type": "module"
43 | }
44 |
--------------------------------------------------------------------------------
/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import type { PlaywrightTestConfig } from '@playwright/test';
2 |
3 | const config: PlaywrightTestConfig = {
4 | webServer: {
5 | command: 'npm run build && npm run preview',
6 | port: 4173
7 | },
8 | testDir: 'tests'
9 | };
10 |
11 | export default config;
12 |
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | const autoprefixer = require("autoprefixer")
2 |
3 | const config = {
4 | plugins: [
5 | require("postcss-simple-vars"),
6 | autoprefixer,
7 | require("postcss-preset-env")({
8 | stage: 1,
9 | }),
10 | ],
11 | }
12 |
13 | module.exports = config
14 |
--------------------------------------------------------------------------------
/rome.json:
--------------------------------------------------------------------------------
1 | {
2 | "linter": {
3 | "enabled": true,
4 | "rules": {
5 | "recommended": true
6 | }
7 | },
8 | "javascript": {
9 | "formatter": {
10 | "semicolons": "asNeeded"
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // See https://kit.svelte.dev/docs/types#app
2 | // for information about these interfaces
3 | // and what to do when importing types
4 |
5 | import type { TypedSupabaseClient } from "@supabase/auth-helpers-sveltekit/dist/types"
6 | import type { Session } from "@supabase/supabase-js"
7 |
8 | declare global {
9 | declare namespace App {
10 | // interface Error {}
11 | interface Locals {
12 | sb: TypedSupabaseClient
13 | session: Session | null
14 | }
15 | interface PageData {
16 | session: import("@supabase/supabase-js").Session | null
17 | }
18 | // interface Platform {}
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %sveltekit.head%
8 |
9 |
10 | %sveltekit.body%
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/app.postcss:
--------------------------------------------------------------------------------
1 | @import 'global.postcss';
2 |
3 | $dark-300: hsl(213, 24%, 7%);
4 | $dark-200: hsl(222, 15%, 13%);
5 | $dark-100: hsl(222, 15%, 19%);
6 | $primary-300: hsl(149, 77%, 46%);
7 | $primary-200: hsl(149, 77%, 56%);
8 | $primary-100: hsl(149, 77%, 66%);
9 | $secondary-300: hsl(289, 90%, 53%);
10 | $secondary-200: hsl(289, 90%, 63%);
11 | $secondary-100: hsl(289, 90%, 73%);
12 | $gray: hsl(212, 15%, 48%);
13 |
14 | body {
15 | background: $dark-200;
16 | color: #ffffff;
17 | font-size: 18px;
18 | }
19 |
20 | main {
21 | padding: 2rem;
22 | text-align: center;
23 | }
24 |
25 | .container {
26 | display: flex;
27 | flex-direction: column;
28 | align-items: center;
29 | height: 100vh;
30 | padding: 0 20px;
31 | }
32 |
33 | .auth-buttons {
34 | display: flex;
35 | justify-content: center;
36 | align-items: center;
37 | margin-top: 1rem;
38 | gap: 1rem;
39 | }
40 |
41 | .card {
42 | display: flex;
43 | flex-direction: column;
44 | margin: 0 auto;
45 | background-color: $dark-200;
46 | border-radius: 10px;
47 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
48 | padding: 32px;
49 | }
50 |
51 | .btn {
52 | color: $dark-300;
53 | border: none;
54 | border-radius: 5px;
55 | padding: 10px 20px;
56 | font-size: 16px;
57 | cursor: pointer;
58 | transition: all 0.2s ease-in-out;
59 |
60 | &:hover {
61 | text-decoration: none;
62 | }
63 | }
64 |
65 | .btn-primary {
66 | background-color: $primary-200;
67 | color: $dark-300;
68 |
69 | &:hover {
70 | background-color: $primary-300;
71 | }
72 | }
73 |
74 | .btn-secondary {
75 | background-color: $secondary-200;
76 | color: $dark-300;
77 |
78 | &:hover {
79 | background-color: $secondary-300;
80 | }
81 | }
82 |
83 | .btn-ghost {
84 | background-color: transparent;
85 | color: #ffff;
86 | border: 2px solid $gray;
87 |
88 | &:hover {
89 | background-color: $dark-100;
90 | color: #ffffff;
91 | }
92 | }
93 |
94 | input {
95 | background-color: $dark-100;
96 | border: $gray 2px solid;
97 | border-radius: 5px;
98 | padding: 10px 20px;
99 | color: #ffffff;
100 | font-size: 16px;
101 | margin-bottom: 20px;
102 |
103 | &:focus {
104 | outline: $primary-300 2px solid;
105 | }
106 | }
107 |
108 | .auth-form {
109 | display: flex;
110 | flex-direction: column;
111 | width: 100%;
112 | max-width: 400px;
113 | min-width: 400px;
114 | margin: 0 auto;
115 |
116 | & button {
117 | margin-bottom: 10px;
118 | }
119 |
120 | & label {
121 | text-align: left;
122 | padding-bottom: 2px;
123 | font-size: 16px;
124 | }
125 | }
126 |
127 | .socials {
128 | width: 100%;
129 | max-width: 400px;
130 | min-width: 400px;
131 | margin: 0 auto;
132 | display: flex;
133 | align-items: center;
134 | gap: 1rem;
135 | padding-top: 10px;
136 |
137 | & .btn {
138 | width: 100%;
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/global.postcss:
--------------------------------------------------------------------------------
1 | /* Write your global styles here, in PostCSS syntax */
2 | /*
3 | 1. Use a more-intuitive box-sizing model.
4 | */
5 | *,
6 | *::before,
7 | *::after {
8 | box-sizing: border-box;
9 | }
10 |
11 | /*
12 | 2. Remove default margin
13 | */
14 | * {
15 | margin: 0;
16 | }
17 |
18 | /*
19 | 3. Allow percentage-based heights in the application
20 | */
21 | html,
22 | body {
23 | height: 100%;
24 | }
25 |
26 | /*
27 | Typographic tweaks!
28 | 4. Add accessible line-height
29 | 5. Improve text rendering
30 | */
31 | body {
32 | line-height: 1.5;
33 | -webkit-font-smoothing: antialiased;
34 | font-family: sans-serif;
35 | }
36 |
37 | /*
38 | 6. Improve media defaults
39 | */
40 | img,
41 | picture,
42 | video,
43 | canvas,
44 | svg {
45 | display: block;
46 | max-width: 100%;
47 | }
48 |
49 | /*
50 | 7. Remove built-in form typography styles
51 | */
52 | input,
53 | button,
54 | textarea,
55 | select {
56 | font: inherit;
57 | }
58 |
59 | /*
60 | 8. Avoid text overflows
61 | */
62 |
63 | @custom-selector :--heading h1, h2, h3, h4, h5, h6;
64 | :--heading {
65 | overflow-wrap: break-word;
66 | }
67 |
68 | a {
69 | text-decoration: none;
70 | color: inherit;
71 | }
72 |
73 | a:hover {
74 | text-decoration: underline;
75 | }
76 |
77 | /*
78 | 9. Create a root stacking context
79 | */
80 | #root {
81 | isolation: isolate;
82 | }
83 |
--------------------------------------------------------------------------------
/src/hooks.client.ts:
--------------------------------------------------------------------------------
1 | import "$lib/supabase"
2 |
--------------------------------------------------------------------------------
/src/hooks.server.ts:
--------------------------------------------------------------------------------
1 | import "$lib/supabase"
2 | import { getSupabase } from "@supabase/auth-helpers-sveltekit"
3 | import type { Handle } from "@sveltejs/kit"
4 |
5 | export const handle: Handle = async ({ event, resolve }) => {
6 | const { session, supabaseClient } = await getSupabase(event)
7 |
8 | event.locals.sb = supabaseClient
9 | event.locals.session = session
10 |
11 | return resolve(event)
12 | }
13 |
--------------------------------------------------------------------------------
/src/index.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from 'vitest';
2 |
3 | describe('sum test', () => {
4 | it('adds 1 + 2 to equal 3', () => {
5 | expect(1 + 2).toBe(3);
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/src/lib/supabase.ts:
--------------------------------------------------------------------------------
1 | import { createClient } from "@supabase/auth-helpers-sveltekit"
2 | import {
3 | PUBLIC_SUPABASE_ANON_KEY,
4 | PUBLIC_SUPABASE_URL,
5 | } from "$env/static/public"
6 |
7 | export const supabaseClient = createClient(
8 | PUBLIC_SUPABASE_URL,
9 | PUBLIC_SUPABASE_ANON_KEY,
10 | )
11 |
--------------------------------------------------------------------------------
/src/routes/+layout.server.ts:
--------------------------------------------------------------------------------
1 | import { getServerSession } from "@supabase/auth-helpers-sveltekit"
2 | import type { LayoutServerLoad } from "./$types"
3 |
4 | export const load: LayoutServerLoad = async (event) => {
5 | console.log("Ran layout load")
6 | return {
7 | session: await getServerSession(event),
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | SvelteKit & Supabase Auth
19 | {#if data.session}
20 | Welcome, {data.session.user.email}
21 |
24 | {:else}
25 | Let's learn how to register and login users!
26 |
30 | {/if}
31 |
32 |
--------------------------------------------------------------------------------
/src/routes/login/+page.server.ts:
--------------------------------------------------------------------------------
1 | import { AuthApiError, type Provider } from "@supabase/supabase-js"
2 | import { fail, redirect } from "@sveltejs/kit"
3 | import type { Actions } from "./$types"
4 |
5 | const OAUTH_PROVIDERS = ["google", "discord", "github"]
6 |
7 | export const actions: Actions = {
8 | login: async ({ request, locals, url }) => {
9 | const provider = url.searchParams.get("provider") as Provider
10 |
11 | if (provider) {
12 | if (!OAUTH_PROVIDERS.includes(provider)) {
13 | return fail(400, {
14 | error: "Provider not supported.",
15 | })
16 | }
17 | const { data, error: err } = await locals.sb.auth.signInWithOAuth({
18 | provider: provider,
19 | })
20 |
21 | if (err) {
22 | console.log(err)
23 | return fail(400, {
24 | message: "Something went wrong.",
25 | })
26 | }
27 |
28 | throw redirect(303, data.url)
29 | }
30 |
31 | const body = Object.fromEntries(await request.formData())
32 |
33 | const { data, error: err } = await locals.sb.auth.signInWithPassword({
34 | email: body.email as string,
35 | password: body.password as string,
36 | })
37 |
38 | if (err) {
39 | if (err instanceof AuthApiError && err.status === 400) {
40 | return fail(400, {
41 | error: "Invalid credentials",
42 | })
43 | }
44 | return fail(500, {
45 | message: "Server error. Try again later.",
46 | })
47 | }
48 |
49 | throw redirect(303, "/")
50 | },
51 | }
52 |
--------------------------------------------------------------------------------
/src/routes/login/+page.svelte:
--------------------------------------------------------------------------------
1 |
29 |
30 |
31 | Login
32 |
39 |
44 |
45 |
--------------------------------------------------------------------------------
/src/routes/logout/+server.ts:
--------------------------------------------------------------------------------
1 | import { error, redirect } from "@sveltejs/kit"
2 | import type { RequestHandler } from "./$types"
3 |
4 | export const POST: RequestHandler = async ({ locals }) => {
5 | const { error: err } = await locals.sb.auth.signOut()
6 |
7 | if (err) {
8 | throw error(500, "Something went wrong logging you out.")
9 | }
10 |
11 | throw redirect(303, "/")
12 | }
13 |
--------------------------------------------------------------------------------
/src/routes/register/+page.server.ts:
--------------------------------------------------------------------------------
1 | import { AuthApiError } from "@supabase/supabase-js"
2 | import { fail, redirect } from "@sveltejs/kit"
3 | import type { Actions } from "./$types"
4 |
5 | export const actions: Actions = {
6 | register: async ({ request, locals }) => {
7 | const body = Object.fromEntries(await request.formData())
8 |
9 | const { data, error: err } = await locals.sb.auth.signUp({
10 | email: body.email as string,
11 | password: body.password as string,
12 | })
13 |
14 | if (err) {
15 | if (err instanceof AuthApiError && err.status === 400) {
16 | return fail(400, {
17 | error: "Invalid email or password",
18 | })
19 | }
20 | return fail(500, {
21 | error: "Server error. Please try again later.",
22 | })
23 | }
24 |
25 | throw redirect(303, "/")
26 | },
27 | }
28 |
--------------------------------------------------------------------------------
/src/routes/register/+page.svelte:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 | Register
6 |
13 |
14 |
--------------------------------------------------------------------------------
/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huntabyte/sk-supabase-oauth/925b82134e25ddd0bc289a96ce4b679fb94fc61b/static/favicon.png
--------------------------------------------------------------------------------
/svelte.config.js:
--------------------------------------------------------------------------------
1 | import preprocess from 'svelte-preprocess';
2 | import adapter from '@sveltejs/adapter-auto';
3 | import { vitePreprocess } from '@sveltejs/kit/vite';
4 |
5 | /** @type {import('@sveltejs/kit').Config} */
6 | const config = {
7 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors
8 | // for more information about preprocessors
9 | preprocess: [
10 | vitePreprocess(),
11 | preprocess({
12 | postcss: true
13 | })
14 | ],
15 |
16 | kit: {
17 | adapter: adapter()
18 | }
19 | };
20 |
21 | export default config;
22 |
--------------------------------------------------------------------------------
/tests/test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test';
2 |
3 | test('index page has expected h1', async ({ page }) => {
4 | await page.goto('/');
5 | expect(await page.textContent('h1')).toBe('Welcome to SvelteKit');
6 | });
7 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true
12 | }
13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
14 | //
15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
16 | // from the referenced tsconfig.json - TypeScript does not merge them in
17 | }
18 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 |
3 | /** @type {import('vite').UserConfig} */
4 | const config = {
5 | plugins: [sveltekit()],
6 | test: {
7 | include: ['src/**/*.{test,spec}.{js,ts}']
8 | }
9 | };
10 |
11 | export default config;
12 |
--------------------------------------------------------------------------------