sveltekit-auth-example 1.0.29 → 1.0.30
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 +5 -0
- package/package.json +5 -5
- package/src/app.html +1 -1
- package/src/lib/google.ts +63 -0
- package/src/routes/+layout.svelte +18 -19
- package/src/routes/login/+page.svelte +5 -6
- package/src/routes/register/+page.svelte +5 -5
- package/src/stores.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# Backlog
|
|
2
2
|
* Add password complexity checking on /register and /profile pages (only checks for length currently despite what the pages say)
|
|
3
3
|
|
|
4
|
+
# 1.0.30
|
|
5
|
+
* Fixed bug where opening /login or /register would fail to render Sign in With Google button (onMount in +layout.svelte loads after children's onMount)
|
|
6
|
+
* Fix bad path for favicon
|
|
7
|
+
* Update dependencies
|
|
8
|
+
|
|
4
9
|
# 1.0.29
|
|
5
10
|
* Fixed bug in hooks.server.ts - new version of SvelteKit complains about modifying cookie after `const response = await resolve(event)` so moved it up two lines.
|
|
6
11
|
* Update dependencies
|
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.30",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Nate Stuyvesant",
|
|
7
7
|
"license": "https://github.com/nstuyvesant/sveltekit-auth-example/blob/master/LICENSE",
|
|
@@ -46,8 +46,8 @@
|
|
|
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.42.0",
|
|
50
|
+
"@typescript-eslint/parser": "^5.42.0",
|
|
51
51
|
"bootstrap": "^5.2.2",
|
|
52
52
|
"eslint": "^8.26.0",
|
|
53
53
|
"eslint-config-prettier": "^8.5.0",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"svelte": "^3.52.0",
|
|
61
61
|
"svelte-check": "^2.9.2",
|
|
62
62
|
"svelte-preprocess": "^4.10.7",
|
|
63
|
-
"tslib": "^2.4.
|
|
63
|
+
"tslib": "^2.4.1",
|
|
64
64
|
"typescript": "^4.8.4",
|
|
65
|
-
"vite": "^3.2.
|
|
65
|
+
"vite": "^3.2.2"
|
|
66
66
|
}
|
|
67
67
|
}
|
package/src/app.html
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
|
-
<link rel="icon" href="%sveltekit.assets
|
|
5
|
+
<link rel="icon" href="%sveltekit.assets%/favicon.png" sizes="any" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
7
|
<script nonce="%sveltekit.nonce%" src="https://accounts.google.com/gsi/client" async defer></script>
|
|
8
8
|
%sveltekit.head%
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Page } from '@sveltejs/kit'
|
|
2
|
+
import { page } from '$app/stores'
|
|
3
|
+
import { goto } from '$app/navigation'
|
|
4
|
+
import { PUBLIC_GOOGLE_CLIENT_ID } from '$env/static/public'
|
|
5
|
+
import { googleInitialized, loginSession } from '../stores'
|
|
6
|
+
import type { Readable } from 'svelte/store'
|
|
7
|
+
|
|
8
|
+
export function renderGoogleButton() {
|
|
9
|
+
const btn = document.getElementById('googleButton')
|
|
10
|
+
if (btn) {
|
|
11
|
+
google.accounts.id.renderButton(btn, {
|
|
12
|
+
type: 'standard',
|
|
13
|
+
theme: 'filled_blue',
|
|
14
|
+
size: 'large',
|
|
15
|
+
width: '367'
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function initializeGoogleAccounts() {
|
|
21
|
+
let initialized = false
|
|
22
|
+
const unsubscribe = googleInitialized.subscribe(value => {
|
|
23
|
+
initialized = value;
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
if (!initialized) {
|
|
27
|
+
window.google.accounts.id.initialize({
|
|
28
|
+
client_id: PUBLIC_GOOGLE_CLIENT_ID,
|
|
29
|
+
callback: googleCallback
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
googleInitialized.set(true)
|
|
33
|
+
}
|
|
34
|
+
unsubscribe()
|
|
35
|
+
|
|
36
|
+
async function googleCallback(response: google.accounts.id.CredentialResponse) {
|
|
37
|
+
const res = await fetch('/auth/google', {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: {
|
|
40
|
+
'Content-Type': 'application/json'
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify({ token: response.credential })
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
if (res.ok) {
|
|
46
|
+
const fromEndpoint = await res.json()
|
|
47
|
+
loginSession.set(fromEndpoint.user) // update loginSession store
|
|
48
|
+
const { role } = fromEndpoint.user
|
|
49
|
+
|
|
50
|
+
let referrer
|
|
51
|
+
const unsubscribe = page.subscribe(p => {
|
|
52
|
+
referrer = p.url.searchParams.get('referrer');
|
|
53
|
+
})
|
|
54
|
+
unsubscribe()
|
|
55
|
+
|
|
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
|
+
}
|
|
63
|
+
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
import { goto, beforeNavigate } from '$app/navigation'
|
|
5
5
|
import { page } from '$app/stores'
|
|
6
6
|
import { loginSession, toast } from '../stores'
|
|
7
|
-
import {
|
|
7
|
+
import { initializeGoogleAccounts } from '$lib/google'
|
|
8
|
+
|
|
8
9
|
import 'bootstrap/scss/bootstrap.scss' // preferred way to load Bootstrap SCSS for hot module reloading
|
|
9
10
|
|
|
10
11
|
export let data: LayoutServerData
|
|
@@ -25,30 +26,16 @@
|
|
|
25
26
|
})
|
|
26
27
|
|
|
27
28
|
onMount(async () => {
|
|
29
|
+
initializeGoogleAccounts()
|
|
30
|
+
|
|
28
31
|
await import('bootstrap/js/dist/collapse') // lots of ways to load Bootstrap but prefer this approach to avoid SSR issues
|
|
29
32
|
await import('bootstrap/js/dist/dropdown')
|
|
30
33
|
Toast = (await import('bootstrap/js/dist/toast')).default
|
|
31
|
-
window.google.accounts.id.initialize({
|
|
32
|
-
client_id: PUBLIC_GOOGLE_CLIENT_ID,
|
|
33
|
-
callback: googleCallback
|
|
34
|
-
})
|
|
35
34
|
|
|
36
|
-
if (!$loginSession)
|
|
35
|
+
if (!$loginSession) google.accounts.id.prompt()
|
|
37
36
|
})
|
|
38
37
|
|
|
39
|
-
async function
|
|
40
|
-
// Request server delete httpOnly cookie called loginSession
|
|
41
|
-
const url = '/auth/logout'
|
|
42
|
-
const res = await fetch(url, {
|
|
43
|
-
method: 'POST'
|
|
44
|
-
})
|
|
45
|
-
if (res.ok) {
|
|
46
|
-
loginSession.set(undefined) // delete loginSession.user from
|
|
47
|
-
goto('/login')
|
|
48
|
-
} else console.error(`Logout not successful: ${res.statusText} (${res.status})`)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function googleCallback(response: GoogleCredentialResponse) {
|
|
38
|
+
async function googleCallback(response: google.accounts.id.CredentialResponse) {
|
|
52
39
|
const res = await fetch('/auth/google', {
|
|
53
40
|
method: 'POST',
|
|
54
41
|
headers: {
|
|
@@ -69,6 +56,18 @@
|
|
|
69
56
|
}
|
|
70
57
|
}
|
|
71
58
|
|
|
59
|
+
async function logout() {
|
|
60
|
+
// Request server delete httpOnly cookie called loginSession
|
|
61
|
+
const url = '/auth/logout'
|
|
62
|
+
const res = await fetch(url, {
|
|
63
|
+
method: 'POST'
|
|
64
|
+
})
|
|
65
|
+
if (res.ok) {
|
|
66
|
+
loginSession.set(undefined) // delete loginSession.user from
|
|
67
|
+
goto('/login')
|
|
68
|
+
} else console.error(`Logout not successful: ${res.statusText} (${res.status})`)
|
|
69
|
+
}
|
|
70
|
+
|
|
72
71
|
const openToast = (open: boolean) => {
|
|
73
72
|
if (open) {
|
|
74
73
|
const toastDiv = <HTMLDivElement> document.getElementById('authToast')
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { page } from '$app/stores'
|
|
5
5
|
import { loginSession } from '../../stores'
|
|
6
6
|
import { focusOnFirstError } from '$lib/focus'
|
|
7
|
+
import { initializeGoogleAccounts, renderGoogleButton } from '$lib/google'
|
|
7
8
|
|
|
8
9
|
let focusedField: HTMLInputElement
|
|
9
10
|
let message: string
|
|
@@ -31,12 +32,10 @@
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
onMount(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
width: '367'
|
|
39
|
-
})
|
|
35
|
+
onMount(() => {
|
|
36
|
+
initializeGoogleAccounts()
|
|
37
|
+
renderGoogleButton()
|
|
38
|
+
|
|
40
39
|
focusedField.focus()
|
|
41
40
|
})
|
|
42
41
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from 'svelte'
|
|
3
3
|
import { goto } from '$app/navigation'
|
|
4
|
+
import { page } from '$app/stores'
|
|
4
5
|
import { loginSession } from '../../stores'
|
|
5
6
|
import { focusOnFirstError } from '$lib/focus'
|
|
7
|
+
import { initializeGoogleAccounts, renderGoogleButton } from '$lib/google'
|
|
6
8
|
|
|
7
9
|
let focusedField: HTMLInputElement
|
|
8
10
|
|
|
@@ -44,12 +46,10 @@
|
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
onMount(() => {
|
|
49
|
+
initializeGoogleAccounts()
|
|
50
|
+
renderGoogleButton()
|
|
51
|
+
|
|
47
52
|
focusedField.focus()
|
|
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
55
|
async function registerLocal(user: User) {
|
package/src/stores.ts
CHANGED
|
@@ -9,3 +9,5 @@ export const toast = writable({
|
|
|
9
9
|
// While server determines whether the user is logged in by examining RequestEvent.locals.user, the
|
|
10
10
|
// loginSession is updated so all parts of the SPA client-side see the user and role.
|
|
11
11
|
export const loginSession = <Writable<User>> writable(undefined)
|
|
12
|
+
|
|
13
|
+
export const googleInitialized = writable(false)
|