sveltekit-auth-example 1.0.24 → 1.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/package.json +8 -8
- package/src/app.html +1 -1
- package/src/routes/+layout.svelte +40 -5
- package/src/routes/login/+page.svelte +34 -4
- package/src/routes/register/+page.svelte +33 -5
- package/src/lib/auth.ts +0 -130
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sveltekit-auth-example",
|
|
3
3
|
"description": "SvelteKit Authentication Example",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.25",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Nate Stuyvesant",
|
|
7
7
|
"license": "https://github.com/nstuyvesant/sveltekit-auth-example/blob/master/LICENSE",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"format": "prettier --write ."
|
|
33
33
|
},
|
|
34
34
|
"engines": {
|
|
35
|
-
"node": "~18.9.
|
|
35
|
+
"node": "~18.9.1",
|
|
36
36
|
"npm": "^8.19.2"
|
|
37
37
|
},
|
|
38
38
|
"type": "module",
|
|
@@ -46,22 +46,22 @@
|
|
|
46
46
|
"@types/google.accounts": "0.0.2",
|
|
47
47
|
"@types/jsonwebtoken": "^8.5.9",
|
|
48
48
|
"@types/pg": "^8.6.5",
|
|
49
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
50
|
-
"@typescript-eslint/parser": "^5.
|
|
49
|
+
"@typescript-eslint/eslint-plugin": "^5.38.0",
|
|
50
|
+
"@typescript-eslint/parser": "^5.38.0",
|
|
51
51
|
"bootstrap": "^5.2.1",
|
|
52
|
-
"eslint": "^8.
|
|
52
|
+
"eslint": "^8.24.0",
|
|
53
53
|
"eslint-config-prettier": "^8.5.0",
|
|
54
54
|
"eslint-plugin-svelte3": "^4.0.0",
|
|
55
|
-
"google-auth-library": "^8.5.
|
|
55
|
+
"google-auth-library": "^8.5.2",
|
|
56
56
|
"jsonwebtoken": "^8.5.1",
|
|
57
57
|
"prettier": "^2.7.1",
|
|
58
58
|
"prettier-plugin-svelte": "^2.7.0",
|
|
59
|
-
"sass": "^1.
|
|
59
|
+
"sass": "^1.55.0",
|
|
60
60
|
"svelte": "^3.50.1",
|
|
61
61
|
"svelte-check": "^2.9.0",
|
|
62
62
|
"svelte-preprocess": "^4.10.7",
|
|
63
63
|
"tslib": "^2.4.0",
|
|
64
64
|
"typescript": "^4.8.3",
|
|
65
|
-
"vite": "^3.1.
|
|
65
|
+
"vite": "^3.1.3"
|
|
66
66
|
}
|
|
67
67
|
}
|
package/src/app.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<link rel="icon" href="%sveltekit.assets%//favicon.png" sizes="any" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
|
-
<script src="https://accounts.google.com/gsi/client" async defer></script>
|
|
7
|
+
<script nonce="%sveltekit.nonce%" src="https://accounts.google.com/gsi/client" async defer></script>
|
|
8
8
|
%sveltekit.head%
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { goto } from '$app/navigation'
|
|
5
5
|
import { page } from '$app/stores'
|
|
6
6
|
import { loginSession, toast } from '../stores'
|
|
7
|
-
import
|
|
7
|
+
import { PUBLIC_GOOGLE_CLIENT_ID } from '$env/static/public'
|
|
8
8
|
import 'bootstrap/scss/bootstrap.scss' // preferred way to load Bootstrap SCSS for hot module reloading
|
|
9
9
|
|
|
10
10
|
export let data: LayoutServerData
|
|
@@ -13,18 +13,53 @@
|
|
|
13
13
|
const { user } = data
|
|
14
14
|
$loginSession = user
|
|
15
15
|
|
|
16
|
-
// Vue.js Composition API style
|
|
17
|
-
const { initializeSignInWithGoogle, logout } = useAuth(page, loginSession, goto)
|
|
18
|
-
|
|
19
16
|
let Toast: any
|
|
20
17
|
|
|
21
18
|
onMount(async () => {
|
|
22
19
|
await import('bootstrap/js/dist/collapse') // lots of ways to load Bootstrap but prefer this approach to avoid SSR issues
|
|
23
20
|
await import('bootstrap/js/dist/dropdown')
|
|
24
21
|
Toast = (await import('bootstrap/js/dist/toast')).default
|
|
25
|
-
|
|
22
|
+
window.google.accounts.id.initialize({
|
|
23
|
+
client_id: PUBLIC_GOOGLE_CLIENT_ID,
|
|
24
|
+
callback: googleCallback
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
if (!$loginSession) window.google.accounts.id.prompt()
|
|
26
28
|
})
|
|
27
29
|
|
|
30
|
+
async function logout() {
|
|
31
|
+
// Request server delete httpOnly cookie called loginSession
|
|
32
|
+
const url = '/auth/logout'
|
|
33
|
+
const res = await fetch(url, {
|
|
34
|
+
method: 'POST'
|
|
35
|
+
})
|
|
36
|
+
if (res.ok) {
|
|
37
|
+
loginSession.set(undefined) // delete loginSession.user from
|
|
38
|
+
goto('/login')
|
|
39
|
+
} else console.error(`Logout not successful: ${res.statusText} (${res.status})`)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function googleCallback(response: GoogleCredentialResponse) {
|
|
43
|
+
const res = await fetch('/auth/google', {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: {
|
|
46
|
+
'Content-Type': 'application/json'
|
|
47
|
+
},
|
|
48
|
+
body: JSON.stringify({ token: response.credential })
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
if (res.ok) {
|
|
52
|
+
const fromEndpoint = await res.json()
|
|
53
|
+
loginSession.set(fromEndpoint.user) // update loginSession store
|
|
54
|
+
const { role } = fromEndpoint.user
|
|
55
|
+
const referrer = $page.url.searchParams.get('referrer')
|
|
56
|
+
if (referrer) return goto(referrer)
|
|
57
|
+
if (role === 'teacher') return goto('/teachers')
|
|
58
|
+
if (role === 'admin') return goto('/admin')
|
|
59
|
+
if (location.pathname === '/login') goto('/') // logged in so go home
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
28
63
|
const openToast = (open: boolean) => {
|
|
29
64
|
if (open) {
|
|
30
65
|
const toastDiv = <HTMLDivElement> document.getElementById('authToast')
|
|
@@ -3,11 +3,8 @@
|
|
|
3
3
|
import { goto } from '$app/navigation'
|
|
4
4
|
import { page } from '$app/stores'
|
|
5
5
|
import { loginSession } from '../../stores'
|
|
6
|
-
import useAuth from '$lib/auth'
|
|
7
6
|
import { focusOnFirstError } from '$lib/focus'
|
|
8
7
|
|
|
9
|
-
const { initializeSignInWithGoogle, loginLocal } = useAuth(page, loginSession, goto)
|
|
10
|
-
|
|
11
8
|
let focusedField: HTMLInputElement
|
|
12
9
|
let message: string
|
|
13
10
|
const credentials: Credentials = {
|
|
@@ -35,9 +32,42 @@
|
|
|
35
32
|
}
|
|
36
33
|
|
|
37
34
|
onMount(async() => {
|
|
38
|
-
|
|
35
|
+
window.google.accounts.id.renderButton(document.getElementById('googleButton'), {
|
|
36
|
+
theme: 'filled_blue',
|
|
37
|
+
size: 'large',
|
|
38
|
+
width: '367'
|
|
39
|
+
})
|
|
39
40
|
focusedField.focus()
|
|
40
41
|
})
|
|
42
|
+
|
|
43
|
+
async function loginLocal(credentials: Credentials) {
|
|
44
|
+
try {
|
|
45
|
+
const res = await fetch('/auth/login', {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
body: JSON.stringify(credentials),
|
|
48
|
+
headers: {
|
|
49
|
+
'Content-Type': 'application/json'
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
const fromEndpoint = await res.json()
|
|
53
|
+
if (res.ok) {
|
|
54
|
+
loginSession.set(fromEndpoint.user)
|
|
55
|
+
const { role } = fromEndpoint.user
|
|
56
|
+
const referrer = $page.url.searchParams.get('referrer')
|
|
57
|
+
if (referrer) return goto(referrer)
|
|
58
|
+
if (role === 'teacher') return goto('/teachers')
|
|
59
|
+
if (role === 'admin') return goto('/admin')
|
|
60
|
+
return goto('/')
|
|
61
|
+
} else {
|
|
62
|
+
throw new Error(fromEndpoint.message)
|
|
63
|
+
}
|
|
64
|
+
} catch (err) {
|
|
65
|
+
if (err instanceof Error) {
|
|
66
|
+
console.error('Login error', err)
|
|
67
|
+
throw new Error(err.message)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
41
71
|
</script>
|
|
42
72
|
|
|
43
73
|
<svelte:head>
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from 'svelte'
|
|
3
3
|
import { goto } from '$app/navigation'
|
|
4
|
-
import { page } from '$app/stores'
|
|
5
4
|
import { loginSession } from '../../stores'
|
|
6
|
-
import useAuth from '$lib/auth'
|
|
7
5
|
import { focusOnFirstError } from '$lib/focus'
|
|
8
6
|
|
|
9
|
-
const { initializeSignInWithGoogle, registerLocal } = useAuth(page, loginSession, goto)
|
|
10
|
-
|
|
11
7
|
let focusedField: HTMLInputElement
|
|
12
8
|
|
|
13
9
|
let user: User = {
|
|
@@ -49,9 +45,41 @@
|
|
|
49
45
|
|
|
50
46
|
onMount(() => {
|
|
51
47
|
focusedField.focus()
|
|
52
|
-
|
|
48
|
+
window.google.accounts.id.renderButton(document.getElementById('googleButton'), {
|
|
49
|
+
theme: 'filled_blue',
|
|
50
|
+
size: 'large',
|
|
51
|
+
width: '367'
|
|
52
|
+
})
|
|
53
53
|
})
|
|
54
54
|
|
|
55
|
+
async function registerLocal(user: User) {
|
|
56
|
+
try {
|
|
57
|
+
const res = await fetch('/auth/register', {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
body: JSON.stringify(user), // server ignores user.role - always set it to 'student' (lowest priv)
|
|
60
|
+
headers: {
|
|
61
|
+
'Content-Type': 'application/json'
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
if (!res.ok) {
|
|
65
|
+
if (res.status == 401)
|
|
66
|
+
// user already existed and passwords didn't match (otherwise, we login the user)
|
|
67
|
+
throw new Error('Sorry, that username is already in use.')
|
|
68
|
+
throw new Error(res.statusText) // should only occur if there's a database error
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// res.ok
|
|
72
|
+
const fromEndpoint = await res.json()
|
|
73
|
+
loginSession.set(fromEndpoint.user) // update store so user is logged in
|
|
74
|
+
goto('/')
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.error('Register error', err)
|
|
77
|
+
if (err instanceof Error) {
|
|
78
|
+
throw new Error(err.message)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
55
83
|
const passwordMatch = () => {
|
|
56
84
|
if (!user) return false // placate TypeScript
|
|
57
85
|
if (!user.password) user.password = ''
|
package/src/lib/auth.ts
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
|
|
3
|
-
import type { Page } from '@sveltejs/kit'
|
|
4
|
-
import type { Readable, Writable } from 'svelte/store'
|
|
5
|
-
import { PUBLIC_GOOGLE_CLIENT_ID } from '$env/static/public'
|
|
6
|
-
|
|
7
|
-
export default function useAuth(
|
|
8
|
-
page: Readable<Page>,
|
|
9
|
-
loginSession: Writable<User>,
|
|
10
|
-
goto: (
|
|
11
|
-
url: string | URL,
|
|
12
|
-
opts?: { replaceState?: boolean; noscroll?: boolean; keepfocus?: boolean; state?: any }
|
|
13
|
-
) => Promise<any>
|
|
14
|
-
) {
|
|
15
|
-
let user: User
|
|
16
|
-
loginSession.subscribe((value) => {
|
|
17
|
-
user = value
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
let referrer: string | null
|
|
21
|
-
page.subscribe((value) => {
|
|
22
|
-
referrer = value.url.searchParams.get('referrer')
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
async function googleCallback(response: GoogleCredentialResponse) {
|
|
26
|
-
const res = await fetch('/auth/google', {
|
|
27
|
-
method: 'POST',
|
|
28
|
-
headers: {
|
|
29
|
-
'Content-Type': 'application/json'
|
|
30
|
-
},
|
|
31
|
-
body: JSON.stringify({ token: response.credential })
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
if (res.ok) {
|
|
35
|
-
const fromEndpoint = await res.json()
|
|
36
|
-
loginSession.set(fromEndpoint.user) // update loginSession store
|
|
37
|
-
const { role } = fromEndpoint.user
|
|
38
|
-
if (referrer) return goto(referrer)
|
|
39
|
-
if (role === 'teacher') return goto('/teachers')
|
|
40
|
-
if (role === 'admin') return goto('/admin')
|
|
41
|
-
if (location.pathname === '/login') goto('/') // logged in so go home
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function initializeSignInWithGoogle(htmlId?: string) {
|
|
46
|
-
const { id } = window.google.accounts // assumes <script src="https://accounts.google.com/gsi/client" async defer></script> is in app.html
|
|
47
|
-
id.initialize({ client_id: PUBLIC_GOOGLE_CLIENT_ID, callback: googleCallback })
|
|
48
|
-
|
|
49
|
-
if (htmlId) {
|
|
50
|
-
// render button instead of prompt
|
|
51
|
-
return id.renderButton(document.getElementById(htmlId), {
|
|
52
|
-
theme: 'filled_blue',
|
|
53
|
-
size: 'large',
|
|
54
|
-
width: '367'
|
|
55
|
-
})
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (!user) id.prompt()
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async function registerLocal(user: User) {
|
|
62
|
-
try {
|
|
63
|
-
const res = await fetch('/auth/register', {
|
|
64
|
-
method: 'POST',
|
|
65
|
-
body: JSON.stringify(user), // server ignores user.role - always set it to 'student' (lowest priv)
|
|
66
|
-
headers: {
|
|
67
|
-
'Content-Type': 'application/json'
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
if (!res.ok) {
|
|
71
|
-
if (res.status == 401)
|
|
72
|
-
// user already existed and passwords didn't match (otherwise, we login the user)
|
|
73
|
-
throw new Error('Sorry, that username is already in use.')
|
|
74
|
-
throw new Error(res.statusText) // should only occur if there's a database error
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// res.ok
|
|
78
|
-
const fromEndpoint = await res.json()
|
|
79
|
-
loginSession.set(fromEndpoint.user) // update store so user is logged in
|
|
80
|
-
goto('/')
|
|
81
|
-
} catch (err) {
|
|
82
|
-
console.error('Register error', err)
|
|
83
|
-
if (err instanceof Error) {
|
|
84
|
-
throw new Error(err.message)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async function loginLocal(credentials: Credentials) {
|
|
90
|
-
try {
|
|
91
|
-
const res = await fetch('/auth/login', {
|
|
92
|
-
method: 'POST',
|
|
93
|
-
body: JSON.stringify(credentials),
|
|
94
|
-
headers: {
|
|
95
|
-
'Content-Type': 'application/json'
|
|
96
|
-
}
|
|
97
|
-
})
|
|
98
|
-
const fromEndpoint = await res.json()
|
|
99
|
-
if (res.ok) {
|
|
100
|
-
loginSession.set(fromEndpoint.user)
|
|
101
|
-
const { role } = fromEndpoint.user
|
|
102
|
-
if (referrer) return goto(referrer)
|
|
103
|
-
if (role === 'teacher') return goto('/teachers')
|
|
104
|
-
if (role === 'admin') return goto('/admin')
|
|
105
|
-
return goto('/')
|
|
106
|
-
} else {
|
|
107
|
-
throw new Error(fromEndpoint.message)
|
|
108
|
-
}
|
|
109
|
-
} catch (err) {
|
|
110
|
-
if (err instanceof Error) {
|
|
111
|
-
console.error('Login error', err)
|
|
112
|
-
throw new Error(err.message)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async function logout() {
|
|
118
|
-
// Request server delete httpOnly cookie called loginSession
|
|
119
|
-
const url = '/auth/logout'
|
|
120
|
-
const res = await fetch(url, {
|
|
121
|
-
method: 'POST'
|
|
122
|
-
})
|
|
123
|
-
if (res.ok) {
|
|
124
|
-
loginSession.set(undefined) // delete loginSession.user from
|
|
125
|
-
goto('/login')
|
|
126
|
-
} else console.error(`Logout not successful: ${res.statusText} (${res.status})`)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return { initializeSignInWithGoogle, registerLocal, loginLocal, logout }
|
|
130
|
-
}
|