[ENHANCEMENT] Demo / Testing App, Updated Build Configuration (#8)

* 🎉 Scaffold example app with SvelteKit barebones skeleton

* 💄 Add TWCSS and base styles with fonts Inter/Fira Mono

* 🔧 Add `exports` and `types` to `package.json` and update `tsconfig.json` for Vite-compatible build output

*  Add local dependency to `svelte-kit-auth` as symlink

* 🔧 Update example app env variables

*  Add basic auth config to example app

* ♻️ Export all providers from `/providers` module

* 🎨 Make `Auth` class default export of lib

* 🚚 Rename `example-app` to `app`

*  Use `file:` instead of `link:` for local dependency to `svelte-kit-auth`

* 🔧 Add `JWT_SECRET_KEY` to env and config

* 🎨 Add `RedditOAuthProvider.profileHandler` for general use and stripping of payload

*  Export auth API routes from app

* ⬆️ Update local deps

*  Add `host` and `basePath` to general config and improve recognition of routes

* 🚨 Exclude `app` from TS build

* 📌 Undo `file:` mapping dependency for usage with Vite

TODO: Needs to be fixed for release.

* 🎨 Enable TS `strict` mode and set target to `es2017`

* 📌 Undo `file:` mapping dependency for usage with Vite

* 🚨 Format and lint files

* 🍱 Add logo

*  Add login and profile routes to example app for showcase and testing

* 💄 Add PrismJS and create homepage with example

* 🔨 Add `build:watch`
This commit is contained in:
Dan6erbond 2021-05-21 17:59:21 +02:00 committed by GitHub
parent 2b21911d22
commit 5d1802fea4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 4972 additions and 81 deletions

View File

@ -12,6 +12,7 @@ module.exports = {
"@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": ["off", { argsIgnorePattern: "^_" }], "@typescript-eslint/no-unused-vars": ["off", { argsIgnorePattern: "^_" }],
"@typescript-eslint/no-non-null-assertion": "off",
}, },
env: { env: {
browser: true, browser: true,

25
app/.eslintrc.cjs Normal file
View File

@ -0,0 +1,25 @@
module.exports = {
root: true,
parser: "@typescript-eslint/parser",
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier",
"../.eslintrc.cjs",
],
plugins: ["svelte3", "@typescript-eslint"],
ignorePatterns: ["*.cjs"],
overrides: [{ files: ["*.svelte"], processor: "svelte3/svelte3" }],
settings: {
"svelte3/typescript": () => require("typescript"),
},
parserOptions: {
sourceType: "module",
ecmaVersion: 2019,
},
env: {
browser: true,
es2017: true,
node: true,
},
};

5
app/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.DS_Store
node_modules
/.svelte-kit
/build
/functions

1
app/.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=true

4
app/.prettierignore Normal file
View File

@ -0,0 +1,4 @@
.svelte-kit/**
static/**
build/**
node_modules/**

4
app/.prettierrc Normal file
View File

@ -0,0 +1,4 @@
{
"trailingComma": "all",
"printWidth": 100
}

38
app/README.md Normal file
View File

@ -0,0 +1,38 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte);
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm init svelte@next
# create a new project in my-app
npm init svelte@next my-app
```
> Note: the `@next` is temporary
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
Before creating a production version of your app, install an [adapter](https://kit.svelte.dev/docs#adapters) for your target environment. Then:
```bash
npm run build
```
> You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production.

39
app/package.json Normal file
View File

@ -0,0 +1,39 @@
{
"name": "~TODO~",
"version": "0.0.1",
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build",
"preview": "svelte-kit preview",
"lint": "prettier --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
"format": "prettier --write --plugin-search-dir=. ."
},
"devDependencies": {
"@sveltejs/kit": "next",
"@types/prismjs": "^1.16.5",
"@typescript-eslint/eslint-plugin": "^4.19.0",
"@typescript-eslint/parser": "^4.19.0",
"autoprefixer": "^10.2.5",
"cssnano": "^5.0.1",
"eslint": "^7.22.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-svelte3": "^3.2.0",
"postcss": "^8.2.10",
"postcss-load-config": "^3.0.1",
"prettier": "~2.2.1",
"prettier-plugin-svelte": "^2.2.0",
"svelte": "^3.34.0",
"svelte-preprocess": "^4.7.1",
"tailwindcss": "^2.1.1",
"tslib": "^2.0.0",
"typescript": "^4.0.0"
},
"type": "module",
"dependencies": {
"@fontsource/fira-mono": "^4.3.0",
"@fontsource/inter": "^4.3.0",
"clsx": "^1.1.1",
"prismjs": "^1.23.0",
"svelte-kit-auth": "link:../"
}
}

23
app/postcss.config.cjs Normal file
View File

@ -0,0 +1,23 @@
const tailwindcss = require("tailwindcss");
const autoprefixer = require("autoprefixer");
const cssnano = require("cssnano");
const mode = process.env.NODE_ENV;
const dev = mode === "development";
module.exports = {
plugins: [
// Some plugins, like postcss-nested, need to run before Tailwind
tailwindcss,
// But others, like autoprefixer, need to run after
autoprefixer,
!dev &&
cssnano({
preset: "default",
}),
],
};

12
app/src/app.html Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%svelte.head%
</head>
<body>
<div id="svelte">%svelte.body%</div>
</body>
</html>

74
app/src/app.postcss Normal file
View File

@ -0,0 +1,74 @@
@import "@fontsource/fira-mono";
@import "@fontsource/inter";
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
font-family: "Inter", Arial, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
}
::-webkit-scrollbar {
width: 4px;
}
::-webkit-scrollbar-track {
width: 6px;
}
::-webkit-scrollbar-thumb {
background-color: #c7c7c7;
border-radius: 24px;
}
::-webkit-scrollbar-thumb:hover {
background-color: #a1a1a1;
border-radius: 24px;
}
body::-webkit-scrollbar {
width: 8px;
}
body::-webkit-scrollbar-track {
width: 12px;
}
body {
min-height: 100vh;
margin: 0;
}
body::before {
content: "";
width: 80vw;
height: 100vh;
position: absolute;
top: 0;
left: 10vw;
z-index: -1;
opacity: 0.05;
}
#svelte {
min-height: 100vh;
display: flex;
flex-direction: column;
}
h1 {
font-size: 2rem;
margin-bottom: 0 0 1em 0;
}
h2 {
font-size: 1rem;
}
@media (min-width: 720px) {
h1 {
font-size: 2.4rem;
}
}

13
app/src/global.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
/// <reference types="@sveltejs/kit" />
interface ImportMetaEnv {
VITE_GOOGLE_OAUTH_CLIENT_ID: string;
VITE_GOOGLE_OAUTH_CLIENT_SECRET: string;
VITE_FACEBOOK_OAUTH_CLIENT_ID: string;
VITE_FACEBOOK_OAUTH_CLIENT_SECRET: string;
VITE_TWITTER_API_KEY: string;
VITE_TWITTER_API_SECRET: string;
VITE_REDDIT_API_KEY: string;
VITE_REDDIT_API_SECRET: string;
JWT_SECRET_KEY: string;
}

15
app/src/hooks.ts Normal file
View File

@ -0,0 +1,15 @@
import type { Handle } from "@sveltejs/kit";
import { appAuth } from "$lib/appAuth";
export const handle: Handle = async ({ request, render }) => {
// TODO https://github.com/sveltejs/kit/issues/1046
if (request.query.has("_method")) {
request.method = request.query.get("_method").toUpperCase();
}
const response = await render(request);
return response;
};
export const { getSession } = appAuth;

58
app/src/lib/appAuth.ts Normal file
View File

@ -0,0 +1,58 @@
import SvelteKitAuth from "svelte-kit-auth";
import {
FacebookAuthProvider,
GoogleOAuthProvider,
RedditOAuthProvider,
TwitterAuthProvider,
} from "svelte-kit-auth/providers";
export const appAuth = new SvelteKitAuth({
providers: [
new GoogleOAuthProvider({
clientId: import.meta.env.VITE_GOOGLE_OAUTH_CLIENT_ID,
clientSecret: import.meta.env.VITE_GOOGLE_OAUTH_CLIENT_SECRET,
profile(profile) {
return { ...profile, provider: "google" };
},
}),
new FacebookAuthProvider({
clientId: import.meta.env.VITE_FACEBOOK_OAUTH_CLIENT_ID,
clientSecret: import.meta.env.VITE_FACEBOOK_OAUTH_CLIENT_SECRET,
profile(profile) {
return { ...profile, provider: "facebook" };
},
}),
new TwitterAuthProvider({
apiKey: import.meta.env.VITE_TWITTER_API_KEY,
apiSecret: import.meta.env.VITE_TWITTER_API_SECRET,
profile(profile) {
return { ...profile, provider: "twitter" };
},
}),
new RedditOAuthProvider({
apiKey: import.meta.env.VITE_REDDIT_API_KEY,
apiSecret: import.meta.env.VITE_REDDIT_API_SECRET,
profile(profile) {
profile = RedditOAuthProvider.profileHandler(profile);
return { ...profile, provider: "reddit" };
},
}),
],
callbacks: {
jwt(token, profile) {
if (profile?.provider) {
const { provider, ...account } = profile;
token = {
...token,
user: {
...token.user,
connections: { [provider]: account },
},
};
}
return token;
},
},
jwtSecret: import.meta.env.JWT_SECRET_KEY,
});

View File

@ -0,0 +1,198 @@
<script lang="ts">
import "../app.postcss";
import { page, session } from "$app/stores";
import { signOut } from "svelte-kit-auth/client";
import clsx from "clsx";
</script>
<svelte:head>
<title>SvelteKitAuth</title>
</svelte:head>
<header
class={clsx(
"flex",
"justify-between",
"fixed",
"top-0",
"left-0",
"right-0",
"w-full",
"p-4",
"items-center",
"shadow-md",
"bg-white",
)}
>
<a href="/" class={clsx("flex", "items-center", "space-x-2")}>
<img src="/logo.svg" class={clsx("h-8", "w-8")} alt="SvelteKitAuth Logo" />
<span class={clsx("text-2xl", "text-orange-500")}>SvelteKitAuth</span>
</a>
<div class={clsx("flex", "items-center", "space-x-2")}>
{#if $session?.user}
<a
href="/profile"
class={clsx(
"flex",
"items-center",
"justify-center",
"hover:no-underline",
"hover:bg-orange-50",
"transition-colors",
"p-2",
"rounded-full",
)}
>
{#if $page.path === "/profile"}
<svg
xmlns="http://www.w3.org/2000/svg"
class={clsx("h-6", "w-6")}
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clip-rule="evenodd"
/>
</svg>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
class={clsx("h-6", "w-6")}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
{/if}
</a>
<button
class={clsx(
"flex",
"items-center",
"justify-center",
"hover:no-underline",
"hover:bg-orange-50",
"transition-colors",
"p-2",
"rounded-full",
)}
on:click={signOut}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class={clsx("h-6", "w-6")}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
/>
</svg>
</button>
{:else}
<a
href="/login"
class={clsx(
"flex",
"items-center",
"justify-center",
"hover:no-underline",
"hover:bg-orange-50",
"transition-colors",
"p-2",
"rounded-full",
)}
class:text-orange-500={$page.path === "/login"}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class={clsx("h-6", "w-6")}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1"
/>
</svg>
</a>
{/if}
<a
href="https://github.com/Dan6erbond/SvelteKitAuth"
target="_blank"
class={clsx(
"flex",
"items-center",
"justify-center",
"hover:no-underline",
"hover:bg-orange-50",
"transition-colors",
"p-2",
"rounded-full",
)}
>
<svg class={clsx("h-6", "w-6")} viewBox="0 0 128 128">
<g fill="currentColor">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"
/>
<path
d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm-.743-.55M28.93 94.535c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zm-.575-.618M31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm0 0M34.573 101.373c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm0 0M39.073 103.324c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm0 0M44.016 103.685c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm0 0M48.614 102.903c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"
/>
</g>
</svg>
</a>
</div>
</header>
<main
class={clsx(
"flex",
"flex-1",
"flex-col",
"p-4",
"w-full",
"max-w-5xl",
"my-0",
"mx-auto",
"box-border",
"mt-16",
"md:mt-20",
"md:px-6",
)}
>
<slot />
</main>
<footer
class={clsx(
"p-4",
"md:p-8",
"shadow-2xl",
"w-full",
"flex",
"items-center",
"flex-col",
"bg-orange-600",
"text-white"
)}
>
<span>© RaviAnand M, 2021</span>
</footer>

View File

@ -0,0 +1,3 @@
import { appAuth } from "$lib/appAuth";
export const { get, post } = appAuth;

100
app/src/routes/index.svelte Normal file
View File

@ -0,0 +1,100 @@
<script context="module">
import { dev } from "$app/env";
import clsx from "clsx";
import Prism from "prismjs";
import { onMount } from "svelte";
import "prismjs/plugins/toolbar/prism-toolbar.css";
import "prismjs/themes/prism-tomorrow.css";
import "clipboard";
export const hydrate = dev;
</script>
<script lang="ts">
const code = `export const appAuth = new SvelteKitAuth({
providers: [
new GoogleOAuthProvider({
clientId: import.meta.env.VITE_GOOGLE_OAUTH_CLIENT_ID,
clientSecret: import.meta.env.VITE_GOOGLE_OAUTH_CLIENT_SECRET,
profile(profile) {
return { ...profile, provider: "google" };
},
}),
],
callbacks: {
jwt(token, profile) {
if (profile?.provider) {
const { provider, ...account } = profile;
token = {
...token,
user: {
...token.user,
connections: { [provider]: account },
},
};
}
return token;
},
},
jwtSecret: import.meta.env.JWT_SECRET_KEY,
});`;
onMount(async () => {
await import("prismjs/components/prism-clike");
await import("prismjs/components/prism-javascript");
await import("prismjs/components/prism-typescript");
Prism.highlightAll();
});
</script>
<div class={clsx("flex", "justify-center", "items-center", "p-6", "md:p-12", "mb-6", "md:mb-12")}>
<img src="/logo.svg" class="h-36 md:h-48" alt="" />
<div class={clsx("space-y-6")}>
<h1 class={clsx("text-center", "text-5xl", "md:text-6xl", "lg:text-7xl", "font-bold")}>
SvelteKitAuth
</h1>
<p class={clsx("text-center", "text-xl")}>
Authentication built from the ground up for SvelteKit.
</p>
</div>
</div>
<div class={clsx("flex", "justify-between", "mb-6", "md:mb-12")}>
<div class={clsx("flex-1", "p-4")}>
<p class={clsx("font-bold", "text-xl", "text-center", "mb-6")}>Easy</p>
<ul class={clsx("text-center", "space-y-2")}>
<ol>Supports OAuth and credential login out of the box</ol>
<ol>Works with SvelteKit and Vite</ol>
<ol>Easily extensible with alternative providers</ol>
<ol>Build dynamic login screens and fetch sessions using client helpers within minutes</ol>
</ul>
</div>
<div class={clsx("flex-1", "p-4")}>
<p class={clsx("font-bold", "text-xl", "text-center", "mb-6")}>Flexible</p>
<ul class={clsx("text-center", "space-y-2")}>
<ol>Add new providers in minutes with class-based architecture</ol>
<ol>Connect to a custom backend with callbacks</ol>
<ol>Transform the session and JWT as you like using hooks</ol>
<ol>Fetch additional account information after the initial sign-on</ol>
</ul>
</div>
<div class={clsx("flex-1", "p-4")}>
<p class={clsx("font-bold", "text-xl", "text-center", "mb-6")}>Secure</p>
<ul class={clsx("text-center", "space-y-2")}>
<ol>Uses signed JWT and HTTP-only cookies for secure sessions</ol>
<ol>Allows you to manually sign and secure JWTs using an object-oriented approach</ol>
<ol>Replay and state validation with supporting providers</ol>
<ol>CSRF token validation</ol>
</ul>
</div>
</div>
<p class={clsx("text-2xl", "text-center", "mb-2")}>Dead-Simple Authentication in Minutes!</p>
<p class={clsx("text-lg", "text-center", "mb-6")}>Test it out for yourself <a href="/login" class={clsx("hover:text-orange-500", "transition-colors")}>here</a>.</p>
<pre class="language-ts">
<code class="language-ts">
{code}
</code>
</pre>

194
app/src/routes/login.svelte Normal file
View File

@ -0,0 +1,194 @@
<script context="module">
import { dev } from "$app/env";
import clsx from "clsx";
export const hydrate = dev;
</script>
<svelte:head>
<title>Login | SvelteKitAuth</title>
</svelte:head>
<h1>Welcome to SvelteKitAuth</h1>
<section
class={clsx(
"flex",
"items-center",
"flex-col",
"mt-4",
"sm:mt-8",
"md:mt-16",
"shadow",
"py-8",
"sm:py-16",
"md:py-32",
"px-8",
"sm:px-16 md:px-32 self-center",
)}
>
<h1 class={clsx("text-xl", "md:text-2xl")}>Login</h1>
<hr class={clsx("my-3", "border-gray-400", "w-full")} />
<p class={clsx("text-sm", "md:text-base", "text-gray-400", "max-w-lg")}>
Login with one of the configured social providers to test the social login.
</p>
<br />
<div class={clsx("flex", "flex-col", "justify-items-stretch", "space-y-4")}>
<a
href="/api/auth/signin/google"
class={clsx(
"text-sm",
"md:text-base",
"inline-flex",
"space-x-4",
"py-2",
"px-4",
"border-gray-400",
"rounded",
"hover:no-underline",
"border",
"hover:bg-gray-100",
"transition-colors",
"items-center",
)}
>
<svg viewBox="0 0 128 128" class={clsx("h-4", "w-4", "md:h-6", "md:w-6")}>
<g id="original">
<path
fill="#fff"
d="M44.59,4.21a63.28,63.28,0,0,0,4.33,120.9,67.6,67.6,0,0,0,32.36.35A57.13,57.13,0,0,0,107.18,112a57.44,57.44,0,0,0,16-26.26,74.33,74.33,0,0,0,1.61-33.58H65.27c0,8.23,0,16.46,0,24.69H99.74A29.72,29.72,0,0,1,87.08,96.37a36.16,36.16,0,0,1-13.93,5.5,41.29,41.29,0,0,1-15.1,0A37.16,37.16,0,0,1,44,95.74a39.3,39.3,0,0,1-14.5-19.42,38.31,38.31,0,0,1,0-24.63,39.25,39.25,0,0,1,9.18-14.91A37.17,37.17,0,0,1,76.13,27a34.28,34.28,0,0,1,13.64,8q5.83-5.8,11.64-11.63c2-2.09,4.18-4.08,6.15-6.22A61.22,61.22,0,0,0,87.2,4.59,64,64,0,0,0,44.59,4.21Z"
/>
<path
fill="#e33629"
d="M44.59,4.21a64,64,0,0,1,42.61.37A61.22,61.22,0,0,1,107.55,17.2c-2,2.14-4.11,4.14-6.15,6.22Q95.58,29.23,89.77,35a34.28,34.28,0,0,0-13.64-8,37.17,37.17,0,0,0-37.46,9.74,39.25,39.25,0,0,0-9.18,14.91L8.76,35.6A63.53,63.53,0,0,1,44.59,4.21Z"
/>
<path
fill="#f8bd00"
d="M3.26,51.5a62.93,62.93,0,0,1,5.5-15.9L29.49,51.69a38.31,38.31,0,0,0,0,24.63q-10.36,8-20.73,16.08A63.33,63.33,0,0,1,3.26,51.5Z"
/>
<path
fill="#587dbd"
d="M65.27,52.15h59.52a74.33,74.33,0,0,1-1.61,33.58,57.44,57.44,0,0,1-16,26.26c-6.69-5.22-13.41-10.4-20.1-15.62A29.72,29.72,0,0,0,99.74,76.83H65.27C65.26,68.61,65.27,60.38,65.27,52.15Z"
/>
<path
fill="#319f43"
d="M8.75,92.4q10.37-8,20.73-16.08A39.3,39.3,0,0,0,44,95.74a37.16,37.16,0,0,0,14.08,6.08,41.29,41.29,0,0,0,15.1,0,36.16,36.16,0,0,0,13.93-5.5c6.69,5.22,13.41,10.4,20.1,15.62a57.13,57.13,0,0,1-25.9,13.47,67.6,67.6,0,0,1-32.36-.35,63,63,0,0,1-23-11.59A63.73,63.73,0,0,1,8.75,92.4Z"
/>
</g>
</svg>
<span>Sign in with Google</span>
</a>
<a
href="/api/auth/signin/facebook"
class={clsx(
"text-sm",
"md:text-base",
"inline-flex",
"space-x-4",
"py-2",
"px-4",
"border-gray-400",
"rounded",
"hover:no-underline",
"border",
"hover:bg-gray-100",
"transition-colors",
"items-center",
)}
>
<svg viewBox="0 0 128 128" class={clsx("h-4", "w-4", "md:h-6", "md:w-6")}>
<g id="original">
<rect
id="Blue"
fill="#3d5a98"
x="4.83"
y="4.83"
width="118.35"
height="118.35"
rx="6.53"
ry="6.53"
/>
<path
id="f"
fill="#fff"
d="M86.48,123.17V77.34h15.38l2.3-17.86H86.48V48.08c0-5.17,1.44-8.7,8.85-8.7h9.46v-16A126.56,126.56,0,0,0,91,22.7C77.38,22.7,68,31,68,46.31V59.48H52.62V77.34H68v45.83Z"
/>
</g>
</svg>
<span>Sign in with Facebook</span>
</a>
<a
href="/api/auth/signin/reddit"
class={clsx(
"text-sm",
"md:text-base",
"inline-flex",
"space-x-4",
"py-2",
"px-4",
"border-gray-400",
"rounded",
"hover:no-underline",
"border",
"hover:bg-gray-100",
"transition-colors",
"items-center",
)}
>
<svg viewBox="0 0 24 24" class={clsx("h-4", "w-4", "md:h-6", "md:w-6")}>
<path
fill="#FF4300"
d="M14.5 15.41C14.58 15.5 14.58 15.69 14.5 15.8C13.77 16.5 12.41 16.56 12 16.56C11.61 16.56 10.25 16.5 9.54 15.8C9.44 15.69 9.44 15.5 9.54 15.41C9.65 15.31 9.82 15.31 9.92 15.41C10.38 15.87 11.33 16 12 16C12.69 16 13.66 15.87 14.1 15.41C14.21 15.31 14.38 15.31 14.5 15.41M10.75 13.04C10.75 12.47 10.28 12 9.71 12C9.14 12 8.67 12.47 8.67 13.04C8.67 13.61 9.14 14.09 9.71 14.08C10.28 14.08 10.75 13.61 10.75 13.04M14.29 12C13.72 12 13.25 12.5 13.25 13.05S13.72 14.09 14.29 14.09C14.86 14.09 15.33 13.61 15.33 13.05C15.33 12.5 14.86 12 14.29 12M22 12C22 17.5 17.5 22 12 22S2 17.5 2 12C2 6.5 6.5 2 12 2S22 6.5 22 12M18.67 12C18.67 11.19 18 10.54 17.22 10.54C16.82 10.54 16.46 10.7 16.2 10.95C15.2 10.23 13.83 9.77 12.3 9.71L12.97 6.58L15.14 7.05C15.16 7.6 15.62 8.04 16.18 8.04C16.75 8.04 17.22 7.57 17.22 7C17.22 6.43 16.75 5.96 16.18 5.96C15.77 5.96 15.41 6.2 15.25 6.55L12.82 6.03C12.75 6 12.68 6.03 12.63 6.07C12.57 6.11 12.54 6.17 12.53 6.24L11.79 9.72C10.24 9.77 8.84 10.23 7.82 10.96C7.56 10.71 7.2 10.56 6.81 10.56C6 10.56 5.35 11.21 5.35 12C5.35 12.61 5.71 13.11 6.21 13.34C6.19 13.5 6.18 13.62 6.18 13.78C6.18 16 8.79 17.85 12 17.85C15.23 17.85 17.85 16.03 17.85 13.78C17.85 13.64 17.84 13.5 17.81 13.34C18.31 13.11 18.67 12.6 18.67 12Z"
/>
</svg>
<span>Sign in with Reddit</span>
</a>
<p class={clsx("text-gray-600", "text-center", "border-gray-400", "border-b", "pb-2")}>
Coming soon.
</p>
<div
class={clsx(
"text-sm",
"md:text-base",
"inline-flex",
"space-x-4",
"py-2",
"px-4",
"border-gray-400",
"rounded",
"hover:no-underline",
"border",
"hover:bg-gray-100",
"transition-colors",
"items-center",
"text-gray-500",
"cursor-not-allowed",
)}
>
<svg
viewBox="0 0 128 128"
class={clsx("h-4", "w-4", "md:h-6", "md:w-6", "filter", "grayscale", "opacity-60")}
>
<g id="surface1">
<path
style=" stroke:none;fill-rule:nonzero;fill:rgb(11.372549%,63.137255%,94.901961%);fill-opacity:1;"
d="M 40.253906 127.636719 C 88.558594 127.636719 114.972656 78.679688 114.972656 36.234375 C 114.972656 34.84375 114.972656 33.457031 114.898438 32.078125 C 120.039062 27.53125 124.476562 21.898438 128 15.445312 C 123.210938 18.046875 118.128906 19.75 112.921875 20.507812 C 118.402344 16.488281 122.503906 10.171875 124.460938 2.734375 C 119.304688 6.476562 113.664062 9.113281 107.78125 10.535156 C 99.644531 -0.0507812 86.710938 -2.644531 76.234375 4.214844 C 65.761719 11.074219 60.351562 25.675781 63.035156 39.832031 C 41.921875 38.539062 22.246094 26.335938 8.914062 6.269531 C 1.933594 20.941406 5.488281 39.722656 17.023438 49.160156 C 12.875 48.988281 8.816406 47.605469 5.191406 45.128906 C 5.191406 45.265625 5.191406 45.402344 5.191406 45.539062 C 5.191406 60.8125 13.976562 73.976562 26.210938 77.03125 C 22.34375 78.320312 18.285156 78.503906 14.347656 77.574219 C 17.785156 90.667969 27.644531 99.644531 38.882812 99.902344 C 29.578125 108.820312 18.089844 113.652344 6.265625 113.621094 C 4.171875 113.621094 2.078125 113.472656 0 113.175781 C 12.007812 122.609375 25.980469 127.617188 40.253906 127.597656 "
/>
</g>
</svg>
<span>Sign in with Twitter</span>
</div>
</div>
<br />
<p class={clsx("text-sm", "md:text-base", "text-orange-400", "max-w-lg")}>
We will never share your identity with anyone else.
</p>
</section>

View File

@ -0,0 +1,215 @@
<script context="module">
import { dev } from "$app/env";
import { session } from "$app/stores";
import clsx from "clsx";
export const hydrate = dev;
</script>
<svelte:head>
<title>Profile | SvelteKitAuth</title>
</svelte:head>
<h1 class={clsx("mb-4")}>Your Profile</h1>
<div class={clsx("flex", "flex-col")}>
<p class={clsx("text-lg", "mb-2")}>Connections</p>
<div
class={clsx(
"grid",
"grid-cols-2",
"sm:grid-cols-3",
"md:grid-cols-4",
"lg:grid-cols-6",
"gap-4",
"mb-6",
)}
>
<div class={clsx("flex", "items-center", "space-x-4")}>
<svg viewBox="0 0 128 128" class={clsx("h-4", "w-4", "md:h-6", "md:w-6")}>
<g id="original">
<path
fill="#fff"
d="M44.59,4.21a63.28,63.28,0,0,0,4.33,120.9,67.6,67.6,0,0,0,32.36.35A57.13,57.13,0,0,0,107.18,112a57.44,57.44,0,0,0,16-26.26,74.33,74.33,0,0,0,1.61-33.58H65.27c0,8.23,0,16.46,0,24.69H99.74A29.72,29.72,0,0,1,87.08,96.37a36.16,36.16,0,0,1-13.93,5.5,41.29,41.29,0,0,1-15.1,0A37.16,37.16,0,0,1,44,95.74a39.3,39.3,0,0,1-14.5-19.42,38.31,38.31,0,0,1,0-24.63,39.25,39.25,0,0,1,9.18-14.91A37.17,37.17,0,0,1,76.13,27a34.28,34.28,0,0,1,13.64,8q5.83-5.8,11.64-11.63c2-2.09,4.18-4.08,6.15-6.22A61.22,61.22,0,0,0,87.2,4.59,64,64,0,0,0,44.59,4.21Z"
/>
<path
fill="#e33629"
d="M44.59,4.21a64,64,0,0,1,42.61.37A61.22,61.22,0,0,1,107.55,17.2c-2,2.14-4.11,4.14-6.15,6.22Q95.58,29.23,89.77,35a34.28,34.28,0,0,0-13.64-8,37.17,37.17,0,0,0-37.46,9.74,39.25,39.25,0,0,0-9.18,14.91L8.76,35.6A63.53,63.53,0,0,1,44.59,4.21Z"
/>
<path
fill="#f8bd00"
d="M3.26,51.5a62.93,62.93,0,0,1,5.5-15.9L29.49,51.69a38.31,38.31,0,0,0,0,24.63q-10.36,8-20.73,16.08A63.33,63.33,0,0,1,3.26,51.5Z"
/>
<path
fill="#587dbd"
d="M65.27,52.15h59.52a74.33,74.33,0,0,1-1.61,33.58,57.44,57.44,0,0,1-16,26.26c-6.69-5.22-13.41-10.4-20.1-15.62A29.72,29.72,0,0,0,99.74,76.83H65.27C65.26,68.61,65.27,60.38,65.27,52.15Z"
/>
<path
fill="#319f43"
d="M8.75,92.4q10.37-8,20.73-16.08A39.3,39.3,0,0,0,44,95.74a37.16,37.16,0,0,0,14.08,6.08,41.29,41.29,0,0,0,15.1,0,36.16,36.16,0,0,0,13.93-5.5c6.69,5.22,13.41,10.4,20.1,15.62a57.13,57.13,0,0,1-25.9,13.47,67.6,67.6,0,0,1-32.36-.35,63,63,0,0,1-23-11.59A63.73,63.73,0,0,1,8.75,92.4Z"
/>
</g>
</svg>
<div class={clsx("flex", "flex-col", "items-start", "space-y-1")}>
{#if $session.user.connections.google}
<p class={clsx("font-bold")}>Signed in as:</p>
{$session.user.connections.google.name}
{:else}
<p class={clsx("font-bold")}>Not signed in</p>
<a
href="/api/auth/signin/google"
class={clsx(
"text-xs",
"md:text-sm",
"py-1",
"px-2",
"border-gray-400",
"rounded",
"hover:no-underline",
"border",
"hover:bg-gray-100",
"transition-colors",
"items-center",
)}
>
Connect
</a>
{/if}
</div>
</div>
<div class={clsx("flex", "items-center", "space-x-4")}>
<svg viewBox="0 0 128 128" class={clsx("h-4", "w-4", "md:h-6", "md:w-6")}>
<g id="original">
<rect
id="Blue"
fill="#3d5a98"
x="4.83"
y="4.83"
width="118.35"
height="118.35"
rx="6.53"
ry="6.53"
/>
<path
id="f"
fill="#fff"
d="M86.48,123.17V77.34h15.38l2.3-17.86H86.48V48.08c0-5.17,1.44-8.7,8.85-8.7h9.46v-16A126.56,126.56,0,0,0,91,22.7C77.38,22.7,68,31,68,46.31V59.48H52.62V77.34H68v45.83Z"
/>
</g>
</svg>
<div class={clsx("flex", "flex-col", "items-start", "space-y-1")}>
{#if $session.user.connections.facebook}
<p class={clsx("font-bold")}>Signed in as:</p>
{$session.user.connections.facebook.name}
{:else}
<p class={clsx("font-bold")}>Not signed in</p>
<a
href="/api/auth/signin/facebook"
class={clsx(
"text-xs",
"md:text-sm",
"py-1",
"px-2",
"border-gray-400",
"rounded",
"hover:no-underline",
"border",
"hover:bg-gray-100",
"transition-colors",
"items-center",
)}
>
Connect
</a>
{/if}
</div>
</div>
<div class={clsx("flex", "items-center", "space-x-4")}>
<svg viewBox="0 0 24 24" class={clsx("h-4", "w-4", "md:h-6", "md:w-6")}>
<path
fill="#FF4300"
d="M14.5 15.41C14.58 15.5 14.58 15.69 14.5 15.8C13.77 16.5 12.41 16.56 12 16.56C11.61 16.56 10.25 16.5 9.54 15.8C9.44 15.69 9.44 15.5 9.54 15.41C9.65 15.31 9.82 15.31 9.92 15.41C10.38 15.87 11.33 16 12 16C12.69 16 13.66 15.87 14.1 15.41C14.21 15.31 14.38 15.31 14.5 15.41M10.75 13.04C10.75 12.47 10.28 12 9.71 12C9.14 12 8.67 12.47 8.67 13.04C8.67 13.61 9.14 14.09 9.71 14.08C10.28 14.08 10.75 13.61 10.75 13.04M14.29 12C13.72 12 13.25 12.5 13.25 13.05S13.72 14.09 14.29 14.09C14.86 14.09 15.33 13.61 15.33 13.05C15.33 12.5 14.86 12 14.29 12M22 12C22 17.5 17.5 22 12 22S2 17.5 2 12C2 6.5 6.5 2 12 2S22 6.5 22 12M18.67 12C18.67 11.19 18 10.54 17.22 10.54C16.82 10.54 16.46 10.7 16.2 10.95C15.2 10.23 13.83 9.77 12.3 9.71L12.97 6.58L15.14 7.05C15.16 7.6 15.62 8.04 16.18 8.04C16.75 8.04 17.22 7.57 17.22 7C17.22 6.43 16.75 5.96 16.18 5.96C15.77 5.96 15.41 6.2 15.25 6.55L12.82 6.03C12.75 6 12.68 6.03 12.63 6.07C12.57 6.11 12.54 6.17 12.53 6.24L11.79 9.72C10.24 9.77 8.84 10.23 7.82 10.96C7.56 10.71 7.2 10.56 6.81 10.56C6 10.56 5.35 11.21 5.35 12C5.35 12.61 5.71 13.11 6.21 13.34C6.19 13.5 6.18 13.62 6.18 13.78C6.18 16 8.79 17.85 12 17.85C15.23 17.85 17.85 16.03 17.85 13.78C17.85 13.64 17.84 13.5 17.81 13.34C18.31 13.11 18.67 12.6 18.67 12Z"
/>
</svg>
<div class={clsx("flex", "flex-col", "items-start", "space-y-1")}>
{#if $session.user.connections.reddit}
<p class={clsx("font-bold")}>Signed in as:</p>
{$session.user.connections.reddit.name}
{:else}
<p class={clsx("font-bold")}>Not signed in</p>
<a
href="/api/auth/signin/reddit"
class={clsx(
"text-xs",
"md:text-sm",
"py-1",
"px-2",
"border-gray-400",
"rounded",
"hover:no-underline",
"border",
"hover:bg-gray-100",
"transition-colors",
"items-center",
)}
>
Connect
</a>
{/if}
</div>
</div>
<div class={clsx("flex", "items-center", "space-x-4")}>
<svg
viewBox="0 0 128 128"
class={clsx("h-4", "w-4", "md:h-6", "md:w-6", "filter", "grayscale", "opacity-60")}
>
<g id="surface1">
<path
style=" stroke:none;fill-rule:nonzero;fill:rgb(11.372549%,63.137255%,94.901961%);fill-opacity:1;"
d="M 40.253906 127.636719 C 88.558594 127.636719 114.972656 78.679688 114.972656 36.234375 C 114.972656 34.84375 114.972656 33.457031 114.898438 32.078125 C 120.039062 27.53125 124.476562 21.898438 128 15.445312 C 123.210938 18.046875 118.128906 19.75 112.921875 20.507812 C 118.402344 16.488281 122.503906 10.171875 124.460938 2.734375 C 119.304688 6.476562 113.664062 9.113281 107.78125 10.535156 C 99.644531 -0.0507812 86.710938 -2.644531 76.234375 4.214844 C 65.761719 11.074219 60.351562 25.675781 63.035156 39.832031 C 41.921875 38.539062 22.246094 26.335938 8.914062 6.269531 C 1.933594 20.941406 5.488281 39.722656 17.023438 49.160156 C 12.875 48.988281 8.816406 47.605469 5.191406 45.128906 C 5.191406 45.265625 5.191406 45.402344 5.191406 45.539062 C 5.191406 60.8125 13.976562 73.976562 26.210938 77.03125 C 22.34375 78.320312 18.285156 78.503906 14.347656 77.574219 C 17.785156 90.667969 27.644531 99.644531 38.882812 99.902344 C 29.578125 108.820312 18.089844 113.652344 6.265625 113.621094 C 4.171875 113.621094 2.078125 113.472656 0 113.175781 C 12.007812 122.609375 25.980469 127.617188 40.253906 127.597656 "
/>
</g>
</svg>
<div class={clsx("flex", "flex-col", "items-start", "space-y-1")}>
{#if $session.user.connections.twitter}
<p class={clsx("font-bold")}>Signed in as:</p>
{$session.user.connections.twitter.name}
{:else}
<p class={clsx("font-bold")}>Not signed in</p>
<div
class={clsx(
"text-xs",
"md:text-sm",
"py-1",
"px-2",
"border-gray-400",
"rounded",
"hover:no-underline",
"border",
"hover:bg-gray-100",
"transition-colors",
"items-center",
"cursor-not-allowed",
"inline-block",
)}
>
Connect
</div>
{/if}
</div>
</div>
</div>
<p class={clsx("text-lg", "mb-2")}>Session</p>
<pre
class={clsx(
"bg-gray-100",
"whitespace-pre-wrap",
"p-3",
)}>
<code>{JSON.stringify($session, null, 2)}</code>
</pre>
</div>

12
app/static/logo.svg Normal file
View File

@ -0,0 +1,12 @@
<svg width="350" height="350" viewBox="0 0 350 350" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M252 131.25V141.75H262.5H273.438C285.725 141.75 295.75 151.766 295.75 164.062V317.188C295.75 329.484 285.725 339.5 273.438 339.5H76.5625C64.275 339.5 54.25 329.484 54.25 317.188V164.062C54.25 151.766 64.275 141.75 76.5625 141.75H87.5H98V131.25V87.5C98 45.0415 132.542 10.5 175 10.5C217.458 10.5 252 45.0415 252 87.5V131.25ZM233.332 141.75H243.832V131.25V87.5C243.832 49.5294 212.971 18.6676 175 18.6676C137.029 18.6676 106.168 49.5294 106.168 87.5V131.25V141.75H116.668H233.332Z" stroke="#F73D00" stroke-width="21"/>
<g clip-path="url(#clip0)">
<path d="M226.422 190.641C213.311 171.88 187.209 166.384 168.444 178.213L135.366 199.244C126.344 204.86 120.089 214.061 118.285 224.457C116.721 233.18 118.045 242.141 122.255 249.908C119.368 254.21 117.443 258.99 116.601 264.008C114.677 274.643 117.203 285.636 123.457 294.359C136.689 313.119 162.67 318.616 181.435 306.786L214.513 285.875C223.535 280.259 229.79 271.058 231.594 260.663C233.158 251.94 231.835 242.978 227.625 235.211C230.511 230.909 232.436 226.13 233.278 221.111C235.323 210.357 232.797 199.363 226.422 190.641Z" fill="#FF3E00"/>
<path d="M165.197 296.152C154.491 298.9 143.305 294.718 137.05 285.756C133.201 280.498 131.757 273.926 132.84 267.474C133.081 266.398 133.321 265.442 133.562 264.367L134.163 262.455L135.847 263.65C139.817 266.518 144.147 268.669 148.838 270.103L150.041 270.461L149.92 271.656C149.8 273.329 150.281 275.121 151.244 276.555C153.168 279.303 156.536 280.618 159.784 279.781C160.506 279.542 161.227 279.303 161.829 278.945L194.787 258.034C196.471 256.958 197.554 255.405 197.914 253.493C198.275 251.581 197.794 249.55 196.712 247.997C194.787 245.248 191.419 244.053 188.171 244.89C187.45 245.129 186.728 245.368 186.126 245.726L173.496 253.732C171.452 255.047 169.166 256.003 166.76 256.6C156.055 259.348 144.868 255.166 138.614 246.204C134.885 240.947 133.321 234.375 134.524 227.922C135.607 221.708 139.456 216.092 144.869 212.747L177.947 191.836C179.992 190.521 182.277 189.565 184.683 188.848C195.388 186.1 206.575 190.282 212.83 199.244C216.679 204.502 218.122 211.074 217.04 217.526C216.799 218.602 216.559 219.558 216.198 220.633L215.596 222.545L213.912 221.35C209.943 218.482 205.613 216.331 200.922 214.897L199.719 214.539L199.839 213.344C199.959 211.671 199.478 209.879 198.516 208.445C196.591 205.697 193.223 204.502 189.976 205.338C189.254 205.577 188.532 205.816 187.931 206.175L154.972 227.086C153.288 228.161 152.206 229.714 151.845 231.626C151.484 233.538 151.965 235.57 153.048 237.123C154.972 239.871 158.34 241.066 161.588 240.23C162.31 239.991 163.032 239.752 163.633 239.393L176.263 231.387C178.308 230.073 180.593 229.117 182.999 228.4C193.704 225.652 204.891 229.834 211.146 238.796C214.995 244.053 216.438 250.625 215.356 257.078C214.273 263.292 210.424 268.908 205.011 272.253L171.933 293.164C169.888 294.479 167.602 295.435 165.197 296.152Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="118" height="141" fill="white" transform="translate(116 172)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

19
app/svelte.config.js Normal file
View File

@ -0,0 +1,19 @@
import preprocess from "svelte-preprocess";
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: [
preprocess({
postcss: true,
}),
],
kit: {
// hydrate the <div id="svelte"> element in src/app.html
target: "#svelte",
},
};
export default config;

48
app/tailwind.config.cjs Normal file
View File

@ -0,0 +1,48 @@
const { tailwindExtractor } = require("tailwindcss/lib/lib/purgeUnusedStyles");
const { fontFamily } = require("tailwindcss/defaultTheme");
const colors = require("tailwindcss/colors");
module.exports = {
mode: "aot",
purge: {
content: ["./src/**/*.{html,js,svelte,ts}"],
options: {
defaultExtractor: (content) => [
// If this stops working, please open an issue at https://github.com/svelte-add/tailwindcss/issues rather than bothering Tailwind Labs about it
...tailwindExtractor(content),
// Match Svelte class: directives (https://github.com/tailwindlabs/tailwindcss/discussions/1731)
...[...content.matchAll(/(?:class:)*([\w\d-/:%.]+)/gm)].map(
([_match, group, ..._rest]) => group,
),
],
},
safelist: [/^svelte-[\d\w]+$/],
},
theme: {
extend: {
fontFamily: {
sans: ["Inter", ...fontFamily.sans],
serif: [...fontFamily.serif],
mono: ["Fira Mono", ...fontFamily.mono],
},
colors: {
transparent: "transparent",
current: "currentColor",
black: colors.black,
white: colors.white,
gray: colors.trueGray,
"cool-gray": colors.blueGray,
blue: colors.blue,
indigo: colors.indigo,
red: colors.rose,
orange: colors.orange,
yellow: colors.amber,
pink: colors.pink,
},
},
},
variants: {
extend: {},
},
plugins: [],
};

30
app/tsconfig.json Normal file
View File

@ -0,0 +1,30 @@
{
"compilerOptions": {
"moduleResolution": "node",
"module": "es2020",
"lib": ["es2020"],
"target": "es2019",
/**
svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
to enforce using \`import type\` instead of \`import\` for Types.
*/
"importsNotUsedAsValues": "error",
"isolatedModules": true,
"resolveJsonModule": true,
/**
To have warnings/errors of the Svelte compiler at the correct position,
enable source maps by default.
*/
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"allowJs": true,
"checkJs": true,
"paths": {
"$lib/*": ["src/lib/*"]
}
},
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"]
}

2404
app/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +1,48 @@
{ {
"name": "SvelteKitAuth", "name": "svelte-kit-auth",
"version": "1.0.0", "version": "1.0.0",
"description": "Authentication library for use with SvelteKit featuring built-in OAuth providers and zero restriction customization!", "description": "Authentication library for use with SvelteKit featuring built-in OAuth providers and zero restriction customization!",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": "./dist/index.js",
"./client": "./dist/client/index.js",
"./providers": "./dist/providers/index.js"
},
"typesVersions": {
"*": {
"client": [
"./dist/client/index.d.ts"
],
"providers": [
"./dist/providers/index.d.ts"
]
}
},
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"build:watch": "tsc --watch",
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"lint": "prettier --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", "lint": "prettier --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
"format": "prettier --write --plugin-search-dir=. ." "format": "prettier --write --plugin-search-dir=. ."
}, },
"keywords": [ "keywords": [
"auth",
"authentication",
"csrf",
"jwt",
"nodejs",
"oauth",
"oauth2",
"oidc",
"sveltejs", "sveltejs",
"sveltekit", "sveltekit",
"auth", "sveltekitauth"
"oauth"
], ],
"author": "RaviAnand Mohabir <moravrav@gmail.com> (https://ravianand.web.app)", "author": "RaviAnand Mohabir <moravrav@gmail.com> (https://ravianand.web.app)",
"repository": "https://github.com/Dan6erbond/SvelteKitAuth",
"license": "MIT", "license": "MIT",
"private": false, "private": false,
"types": "dist/index.d.ts",
"dependencies": { "dependencies": {
"cookie": "^0.4.1", "cookie": "^0.4.1",
"jsonwebtoken": "^8.5.1" "jsonwebtoken": "^8.5.1"

12
res/logo.svg Normal file
View File

@ -0,0 +1,12 @@
<svg width="350" height="350" viewBox="0 0 350 350" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M252 131.25V141.75H262.5H273.438C285.725 141.75 295.75 151.766 295.75 164.062V317.188C295.75 329.484 285.725 339.5 273.438 339.5H76.5625C64.275 339.5 54.25 329.484 54.25 317.188V164.062C54.25 151.766 64.275 141.75 76.5625 141.75H87.5H98V131.25V87.5C98 45.0415 132.542 10.5 175 10.5C217.458 10.5 252 45.0415 252 87.5V131.25ZM233.332 141.75H243.832V131.25V87.5C243.832 49.5294 212.971 18.6676 175 18.6676C137.029 18.6676 106.168 49.5294 106.168 87.5V131.25V141.75H116.668H233.332Z" stroke="#F73D00" stroke-width="21"/>
<g clip-path="url(#clip0)">
<path d="M226.422 190.641C213.311 171.88 187.209 166.384 168.444 178.213L135.366 199.244C126.344 204.86 120.089 214.061 118.285 224.457C116.721 233.18 118.045 242.141 122.255 249.908C119.368 254.21 117.443 258.99 116.601 264.008C114.677 274.643 117.203 285.636 123.457 294.359C136.689 313.119 162.67 318.616 181.435 306.786L214.513 285.875C223.535 280.259 229.79 271.058 231.594 260.663C233.158 251.94 231.835 242.978 227.625 235.211C230.511 230.909 232.436 226.13 233.278 221.111C235.323 210.357 232.797 199.363 226.422 190.641Z" fill="#FF3E00"/>
<path d="M165.197 296.152C154.491 298.9 143.305 294.718 137.05 285.756C133.201 280.498 131.757 273.926 132.84 267.474C133.081 266.398 133.321 265.442 133.562 264.367L134.163 262.455L135.847 263.65C139.817 266.518 144.147 268.669 148.838 270.103L150.041 270.461L149.92 271.656C149.8 273.329 150.281 275.121 151.244 276.555C153.168 279.303 156.536 280.618 159.784 279.781C160.506 279.542 161.227 279.303 161.829 278.945L194.787 258.034C196.471 256.958 197.554 255.405 197.914 253.493C198.275 251.581 197.794 249.55 196.712 247.997C194.787 245.248 191.419 244.053 188.171 244.89C187.45 245.129 186.728 245.368 186.126 245.726L173.496 253.732C171.452 255.047 169.166 256.003 166.76 256.6C156.055 259.348 144.868 255.166 138.614 246.204C134.885 240.947 133.321 234.375 134.524 227.922C135.607 221.708 139.456 216.092 144.869 212.747L177.947 191.836C179.992 190.521 182.277 189.565 184.683 188.848C195.388 186.1 206.575 190.282 212.83 199.244C216.679 204.502 218.122 211.074 217.04 217.526C216.799 218.602 216.559 219.558 216.198 220.633L215.596 222.545L213.912 221.35C209.943 218.482 205.613 216.331 200.922 214.897L199.719 214.539L199.839 213.344C199.959 211.671 199.478 209.879 198.516 208.445C196.591 205.697 193.223 204.502 189.976 205.338C189.254 205.577 188.532 205.816 187.931 206.175L154.972 227.086C153.288 228.161 152.206 229.714 151.845 231.626C151.484 233.538 151.965 235.57 153.048 237.123C154.972 239.871 158.34 241.066 161.588 240.23C162.31 239.991 163.032 239.752 163.633 239.393L176.263 231.387C178.308 230.073 180.593 229.117 182.999 228.4C193.704 225.652 204.891 229.834 211.146 238.796C214.995 244.053 216.438 250.625 215.356 257.078C214.273 263.292 210.424 268.908 205.011 272.253L171.933 293.164C169.888 294.479 167.602 295.435 165.197 296.152Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="118" height="141" fill="white" transform="translate(116 172)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -4,13 +4,16 @@ import type { Headers } from "@sveltejs/kit/types/helper";
import cookie from "cookie"; import cookie from "cookie";
import * as jsonwebtoken from "jsonwebtoken"; import * as jsonwebtoken from "jsonwebtoken";
import type { JWT, Session } from "./interfaces"; import type { JWT, Session } from "./interfaces";
import { join } from "./path";
import type { Provider } from "./providers"; import type { Provider } from "./providers";
interface AuthConfig { interface AuthConfig {
providers?: Provider[]; providers: Provider[];
callbacks?: AuthCallbacks; callbacks?: AuthCallbacks;
jwtSecret?: string; jwtSecret?: string;
jwtExpiresIn?: string | number; jwtExpiresIn?: string | number;
host?: string;
basePath?: string;
} }
interface AuthCallbacks { interface AuthCallbacks {
@ -61,8 +64,13 @@ export class Auth {
return token; return token;
} }
getBaseUrl(host: string) { getBaseUrl(host?: string) {
return `http://${host}`; return this.config?.host ?? `http://${host}`;
}
getPath(path: string, host?: string) {
const uri = join([this.config?.basePath ?? "/api/auth", path]);
return new URL(uri, this.getBaseUrl(host)).pathname;
} }
setToken(headers: Headers, newToken: JWT | any) { setToken(headers: Headers, newToken: JWT | any) {
@ -107,8 +115,7 @@ export class Auth {
} }
const jwt = this.signToken(token); const jwt = this.signToken(token);
console.log(jwt); const redirect = await this.getRedirectUrl(host, redirectUrl ?? undefined);
const redirect = await this.getRedirectUrl(host, redirectUrl);
return { return {
status: 302, status: 302,
@ -122,7 +129,7 @@ export class Auth {
async handleEndpoint(request: ServerRequest): Promise<EndpointOutput> { async handleEndpoint(request: ServerRequest): Promise<EndpointOutput> {
const { path, headers, method, host } = request; const { path, headers, method, host } = request;
if (path === "/api/auth/signout") { if (path === this.getPath("signout")) {
const token = this.setToken(headers, {}); const token = this.setToken(headers, {});
const jwt = this.signToken(token); const jwt = this.signToken(token);
@ -150,9 +157,9 @@ export class Auth {
const match = path.match(/\/api\/auth\/(?<method>signin|callback)\/(?<provider>\w+)/); const match = path.match(/\/api\/auth\/(?<method>signin|callback)\/(?<provider>\w+)/);
if (match) { if (match && match.groups) {
const provider = this.config?.providers?.find( const provider = this.config?.providers?.find(
(provider) => provider.id === match.groups.provider, (provider) => provider.id === match.groups!.provider,
); );
if (provider) { if (provider) {
if (match.groups.method === "signin") { if (match.groups.method === "signin") {
@ -162,14 +169,19 @@ export class Auth {
} }
} }
} }
return {
status: 404,
body: "Not found.",
};
} }
get: RequestHandler = async (request) => { get: RequestHandler = async (request) => {
const { path } = request; const { path } = request;
if (path === "/api/auth/csrf") { if (path === this.getPath("csrf")) {
return { body: "1234" }; // TODO: Generate real token return { body: "1234" }; // TODO: Generate real token
} else if (path === "/api/auth/session") { } else if (path === this.getPath("session")) {
const session = await this.getSession(request); const session = await this.getSession(request);
return { return {
body: { body: {

View File

@ -19,17 +19,19 @@ export async function signIn(provider: string, data?: any, config?: SignInConfig
return await res.json(); return await res.json();
} }
let redirectUrl: string; let redirectUrl: string | undefined;
if (config?.redirectUrl) { if (config?.redirectUrl) {
redirectUrl = config.redirectUrl; redirectUrl = config.redirectUrl;
} else { } else {
let $val: Page; let $val: Page | undefined;
page.subscribe(($) => ($val = $))(); page.subscribe(($) => ($val = $))();
if ($val) {
redirectUrl = `${$val.host}${$val.path}?${$val.query}`; redirectUrl = `${$val.host}${$val.path}?${$val.query}`;
} }
}
const queryData = { const queryData = {
redirect: redirectUrl, redirect: redirectUrl ?? "/",
}; };
const query = new URLSearchParams(queryData); const query = new URLSearchParams(queryData);
const path = `/api/auth/login/${provider}?${query}`; const path = `/api/auth/login/${provider}?${query}`;

View File

@ -1,4 +1,7 @@
export { Auth } from "./auth"; import { Auth } from "./auth";
export { JWT, Session, User } from "./interfaces"; export { JWT, Session, User } from "./interfaces";
export { Provider } from "./providers"; export { Provider } from "./providers";
export { CallbackResult, Profile } from "./types"; export { CallbackResult, Profile } from "./types";
export default Auth;

0
src/jwt.ts Normal file
View File

5
src/path.ts Normal file
View File

@ -0,0 +1,5 @@
export function join(parts: string[], sep = "/") {
const separator = sep || "/";
const replace = new RegExp(separator + "{1,}", "g");
return parts.join(separator).replace(replace, separator);
}

View File

@ -11,7 +11,7 @@ export abstract class Provider<T extends ProviderConfig = ProviderConfig> {
id: string; id: string;
constructor(protected readonly config: T) { constructor(protected readonly config: T) {
this.id = config.id; this.id = config.id!;
} }
getUri(host: string, path: string) { getUri(host: string, path: string) {

View File

@ -28,7 +28,7 @@ export class FacebookAuthProvider extends OAuth2Provider<FacebookAuthProviderCon
const data = { const data = {
client_id: this.config.clientId, client_id: this.config.clientId,
scope: this.config.scope, scope: this.config.scope!,
redirect_uri: this.getCallbackUri(host), redirect_uri: this.getCallbackUri(host),
state, state,
}; };
@ -69,7 +69,7 @@ export class FacebookAuthProvider extends OAuth2Provider<FacebookAuthProviderCon
const data = { const data = {
access_token: tokens.access_token, access_token: tokens.access_token,
fields: this.config.userProfileFields, fields: this.config.userProfileFields!,
}; };
const res = await fetch(`${endpoint}?${new URLSearchParams(data)}`); const res = await fetch(`${endpoint}?${new URLSearchParams(data)}`);

View File

@ -23,7 +23,7 @@ export class GoogleOAuthProvider extends OAuth2Provider<GoogleOAuthProviderConfi
} }
async getProviderMetadata() { async getProviderMetadata() {
const res = await fetch(this.config.discoveryDocument); const res = await fetch(this.config.discoveryDocument!);
const metadata = await res.json(); const metadata = await res.json();
return metadata; return metadata;
} }
@ -39,7 +39,7 @@ export class GoogleOAuthProvider extends OAuth2Provider<GoogleOAuthProviderConfi
const data = { const data = {
response_type: "code", response_type: "code",
client_id: this.config.clientId, client_id: this.config.clientId,
scope: this.config.scope, scope: this.config.scope!,
redirect_uri: this.getCallbackUri(host), redirect_uri: this.getCallbackUri(host),
state, state,
login_hint: "example@provider.com", login_hint: "example@provider.com",

View File

@ -1,2 +1,6 @@
export { Provider } from "./base"; export { Provider } from "./base";
export { GoogleOAuthProvider } from "./google"; export { GoogleOAuthProvider } from "./google";
export { TwitterAuthProvider } from "./twitter";
export { FacebookAuthProvider } from "./facebook";
export { OAuth2Provider } from "./oauth2";
export { RedditOAuthProvider } from "./reddit";

View File

@ -35,7 +35,7 @@ export abstract class OAuth2Provider<T extends OAuth2ProviderConfig> extends Pro
getStateValue(query: URLSearchParams, name: string) { getStateValue(query: URLSearchParams, name: string) {
if (query.get("state")) { if (query.get("state")) {
const state = Buffer.from(query.get("state"), "base64").toString(); const state = Buffer.from(query.get("state")!, "base64").toString();
return state return state
.split(",") .split(",")
.find((state) => state.startsWith(`${name}=`)) .find((state) => state.startsWith(`${name}=`))
@ -47,7 +47,7 @@ export abstract class OAuth2Provider<T extends OAuth2ProviderConfig> extends Pro
const code = query.get("code"); const code = query.get("code");
const redirect = this.getStateValue(query, "redirect"); const redirect = this.getStateValue(query, "redirect");
const tokens = await this.getTokens(code, this.getCallbackUri(host)); const tokens = await this.getTokens(code!, this.getCallbackUri(host));
let user = await this.getUserProfile(tokens); let user = await this.getUserProfile(tokens);
if (this.config.profile) { if (this.config.profile) {

View File

@ -8,55 +8,57 @@ interface RedditOAuthProviderConfig extends OAuth2ProviderConfig {
duration?: "temporary" | "permanent"; duration?: "temporary" | "permanent";
} }
const redditProfileHandler = ({
is_employee,
has_external_account,
snoovatar_img,
verified,
id,
over_18,
is_gold,
is_mod,
awarder_karma,
has_verified_email,
is_suspended,
icon_img,
pref_nightmode,
awardee_karma,
password_set,
link_karma,
total_karma,
name,
created,
created_utc,
comment_karma,
}) => ({
is_employee,
has_external_account,
snoovatar_img,
verified,
id,
over_18,
is_gold,
is_mod,
awarder_karma,
has_verified_email,
is_suspended,
icon_img,
pref_nightmode,
awardee_karma,
password_set,
link_karma,
total_karma,
name,
created,
created_utc,
comment_karma,
});
const defaultConfig: Partial<RedditOAuthProviderConfig> = { const defaultConfig: Partial<RedditOAuthProviderConfig> = {
id: "reddit", id: "reddit",
scope: "identity", scope: "identity",
duration: "temporary", duration: "temporary",
profile: ({ profile: redditProfileHandler,
is_employee,
has_external_account,
snoovatar_img,
verified,
id,
over_18,
is_gold,
is_mod,
awarder_karma,
has_verified_email,
is_suspended,
icon_img,
pref_nightmode,
awardee_karma,
password_set,
link_karma,
total_karma,
name,
created,
created_utc,
comment_karma,
}) => ({
is_employee,
has_external_account,
snoovatar_img,
verified,
id,
over_18,
is_gold,
is_mod,
awarder_karma,
has_verified_email,
is_suspended,
icon_img,
pref_nightmode,
awardee_karma,
password_set,
link_karma,
total_karma,
name,
created,
created_utc,
comment_karma,
}),
}; };
export class RedditOAuthProvider extends OAuth2Provider<RedditOAuthProviderConfig> { export class RedditOAuthProvider extends OAuth2Provider<RedditOAuthProviderConfig> {
@ -67,6 +69,8 @@ export class RedditOAuthProvider extends OAuth2Provider<RedditOAuthProviderConfi
}); });
} }
static profileHandler = redditProfileHandler;
async getSigninUrl({ host }: ServerRequest, state: string) { async getSigninUrl({ host }: ServerRequest, state: string) {
const endpoint = "https://www.reddit.com/api/v1/authorize"; const endpoint = "https://www.reddit.com/api/v1/authorize";
@ -75,8 +79,8 @@ export class RedditOAuthProvider extends OAuth2Provider<RedditOAuthProviderConfi
response_type: "code", response_type: "code",
state, state,
redirect_uri: this.getCallbackUri(host), redirect_uri: this.getCallbackUri(host),
duration: this.config.duration, duration: this.config.duration!,
scope: this.config.scope, scope: this.config.scope!,
}; };
const url = `${endpoint}?${new URLSearchParams(data)}`; const url = `${endpoint}?${new URLSearchParams(data)}`;

View File

@ -75,7 +75,7 @@ export class TwitterAuthProvider extends OAuth2Provider<TwitterAuthProviderConfi
const oauthVerifier = query.get("oauth_verifier"); const oauthVerifier = query.get("oauth_verifier");
const redirect = this.getStateValue(query, "redirect"); const redirect = this.getStateValue(query, "redirect");
const tokens = await this.getTokens(oauthToken, oauthVerifier); const tokens = await this.getTokens(oauthToken!, oauthVerifier!);
let user = await this.getUserProfile(tokens); let user = await this.getUserProfile(tokens);
if (this.config.profile) { if (this.config.profile) {

View File

@ -1,20 +1,21 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es2017",
"module": "commonjs", "module": "es6",
"moduleResolution": "node",
"strict": true,
"noImplicitAny": false,
"declaration": true, "declaration": true,
"removeComments": true, "removeComments": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"target": "es2017",
"sourceMap": true, "sourceMap": true,
"outDir": "./dist", "outDir": "./dist",
"baseUrl": "./", "baseUrl": "./",
"incremental": true "incremental": true
}, },
"exclude": [ "exclude": ["node_modules", "dist", "app"]
"node_modules",
"dist"
]
} }

1289
yarn.lock Normal file

File diff suppressed because it is too large Load Diff