sveltekit-auth-example 1.0.11 → 1.0.14
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/.eslintrc.cjs +1 -1
- package/.prettierignore +13 -0
- package/.prettierrc +4 -1
- package/CHANGELOG.md +18 -0
- package/README.md +17 -20
- package/package.json +19 -18
- package/src/app.d.ts +7 -8
- package/src/app.html +1 -0
- package/src/hooks.ts +3 -13
- package/src/lib/auth.ts +49 -75
- package/src/routes/+error.svelte +6 -0
- package/src/routes/+layout.server.ts +8 -0
- package/src/routes/{__layout.svelte → +layout.svelte} +18 -14
- package/src/routes/{index.svelte → +page.svelte} +0 -0
- package/src/routes/admin/+page.server.ts +14 -0
- package/src/routes/admin/+page.svelte +12 -0
- package/src/routes/api/v1/user/+server.ts +21 -0
- package/src/routes/auth/[slug]/+server.ts +66 -0
- package/src/routes/auth/{forgot.ts → forgot/+server.ts} +5 -7
- package/src/routes/auth/{google.ts → google/+server.ts} +17 -21
- package/src/routes/auth/reset/{index.ts → +server.ts} +9 -12
- package/src/routes/auth/reset/{[token].svelte → [token]/+page.svelte} +4 -15
- package/src/routes/auth/reset/[token]/+page.ts +7 -0
- package/src/routes/{forgot.svelte → forgot/+page.svelte} +2 -2
- package/src/routes/{info.svelte → info/+page.svelte} +0 -0
- package/src/routes/{login.svelte → login/+page.svelte} +3 -3
- package/src/routes/profile/+page.server.ts +15 -0
- package/src/routes/{profile.svelte → profile/+page.svelte} +7 -25
- package/src/routes/register/+page.server.ts +10 -0
- package/src/routes/{register.svelte → register/+page.svelte} +6 -23
- package/src/routes/teachers/+page.server.ts +13 -0
- package/src/routes/teachers/+page.svelte +12 -0
- package/src/stores.ts +11 -1
- package/svelte.config.js +1 -3
- package/src/routes/__error.svelte +0 -19
- package/src/routes/admin.svelte +0 -40
- package/src/routes/api/v1/_auth.ts +0 -5
- package/src/routes/api/v1/admin.ts +0 -20
- package/src/routes/api/v1/teacher.ts +0 -20
- package/src/routes/api/v1/user.ts +0 -35
- package/src/routes/auth/[slug].ts +0 -93
- package/src/routes/teachers.svelte +0 -39
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { error, json } from '@sveltejs/kit'
|
|
2
|
+
import type { Action } from './$types'
|
|
3
|
+
import { query } from '../../_db'
|
|
4
|
+
|
|
5
|
+
export const POST: Action = async (event) => {
|
|
6
|
+
const { slug } = event.params
|
|
7
|
+
|
|
8
|
+
let result
|
|
9
|
+
let sql
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
switch (slug) {
|
|
13
|
+
case 'logout':
|
|
14
|
+
if (event.locals.user) {
|
|
15
|
+
// if user is null, they are logged out anyway (session might have ended)
|
|
16
|
+
sql = `CALL delete_session($1);`
|
|
17
|
+
result = await query(sql, [event.locals.user.id])
|
|
18
|
+
}
|
|
19
|
+
return new Response(JSON.stringify({ message: 'Logout successful.' }), {
|
|
20
|
+
headers: {
|
|
21
|
+
'Set-Cookie': `session=; Path=/; SameSite=Lax; HttpOnly; Expires=${new Date().toUTCString()}`
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
case 'login':
|
|
25
|
+
sql = `SELECT authenticate($1) AS "authenticationResult";`
|
|
26
|
+
break
|
|
27
|
+
case 'register':
|
|
28
|
+
sql = `SELECT register($1) AS "authenticationResult";`
|
|
29
|
+
break
|
|
30
|
+
default:
|
|
31
|
+
throw error(404, 'Invalid endpoint.')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Only /auth/login and /auth/register at this point
|
|
35
|
+
const body = await event.request.json()
|
|
36
|
+
|
|
37
|
+
// While client checks for these to be non-null, register() in the database does not
|
|
38
|
+
if (slug == 'register' && (!body.email || !body.password || !body.firstName || !body.lastName))
|
|
39
|
+
throw error(400, 'Please supply all required fields: email, password, first and last name.')
|
|
40
|
+
|
|
41
|
+
result = await query(sql, [JSON.stringify(body)])
|
|
42
|
+
} catch (err) {
|
|
43
|
+
throw error(503, 'Could not communicate with database.')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const { authenticationResult }: { authenticationResult: AuthenticationResult } = result.rows[0]
|
|
47
|
+
|
|
48
|
+
if (!authenticationResult.user)
|
|
49
|
+
// includes when a user tries to register an existing email account with wrong password
|
|
50
|
+
throw error(authenticationResult.statusCode, authenticationResult.status)
|
|
51
|
+
|
|
52
|
+
// Ensures hooks.ts:handle() will not delete cookie just set
|
|
53
|
+
event.locals.user = authenticationResult.user
|
|
54
|
+
|
|
55
|
+
return json(
|
|
56
|
+
{
|
|
57
|
+
message: authenticationResult.status,
|
|
58
|
+
user: authenticationResult.user
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
headers: {
|
|
62
|
+
'Set-Cookie': `session=${authenticationResult.sessionId}; Path=/; SameSite=Lax; HttpOnly;`
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Action } from './$types'
|
|
2
2
|
import type { Secret } from 'jsonwebtoken'
|
|
3
3
|
import jwt from 'jsonwebtoken'
|
|
4
4
|
import dotenv from 'dotenv'
|
|
5
|
-
import { query } from '
|
|
6
|
-
import { sendMessage } from '
|
|
5
|
+
import { query } from '../../_db'
|
|
6
|
+
import { sendMessage } from '../../_send-in-blue'
|
|
7
7
|
|
|
8
8
|
dotenv.config()
|
|
9
9
|
const DOMAIN = process.env.DOMAIN
|
|
10
10
|
const JWT_SECRET = process.env.JWT_SECRET
|
|
11
11
|
|
|
12
|
-
export const POST:
|
|
12
|
+
export const POST: Action = async event => {
|
|
13
13
|
const body = await event.request.json()
|
|
14
14
|
const sql = `SELECT id as "userId" FROM users WHERE email = $1 LIMIT 1;`
|
|
15
15
|
const { rows } = await query(sql, [body.email])
|
|
@@ -36,7 +36,5 @@ export const POST: RequestHandler = async event => {
|
|
|
36
36
|
sendMessage(message)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
return {
|
|
40
|
-
status: 204
|
|
41
|
-
}
|
|
39
|
+
return new Response(undefined, { status: 204 })
|
|
42
40
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { error } from '@sveltejs/kit'
|
|
2
|
+
import type { Action } from './$types'
|
|
2
3
|
import { OAuth2Client } from 'google-auth-library'
|
|
3
|
-
import { query } from '
|
|
4
|
+
import { query } from '../../_db';
|
|
4
5
|
import { config } from '$lib/config'
|
|
5
6
|
|
|
6
7
|
// Verify JWT per https://developers.google.com/identity/gsi/web/guides/verify-google-id-token
|
|
@@ -13,7 +14,8 @@ async function getGoogleUserFromJWT(token: string): Promise<Partial<User>> {
|
|
|
13
14
|
audience: clientId
|
|
14
15
|
});
|
|
15
16
|
const payload = ticket.getPayload()
|
|
16
|
-
if (!payload) throw
|
|
17
|
+
if (!payload) throw error(500, 'Google authentication did not get the expected payload')
|
|
18
|
+
|
|
17
19
|
return {
|
|
18
20
|
firstName: payload['given_name'] || 'UnknownFirstName',
|
|
19
21
|
lastName: payload['family_name'] || 'UnknownLastName',
|
|
@@ -22,7 +24,7 @@ async function getGoogleUserFromJWT(token: string): Promise<Partial<User>> {
|
|
|
22
24
|
} catch (err) {
|
|
23
25
|
let message = ''
|
|
24
26
|
if (err instanceof Error) message = err.message
|
|
25
|
-
throw
|
|
27
|
+
throw error(500,`Google user could not be authenticated: ${message}`)
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -35,12 +37,12 @@ async function upsertGoogleUser(user: Partial<User>): Promise<UserSession> {
|
|
|
35
37
|
} catch (err) {
|
|
36
38
|
let message = ''
|
|
37
39
|
if (err instanceof Error) message = err.message
|
|
38
|
-
throw
|
|
40
|
+
throw error(500,`Gmail user could not be upserted: ${message}`)
|
|
39
41
|
}
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
// Returns local user if Google user authenticated (and authorized our app)
|
|
43
|
-
export const POST:
|
|
45
|
+
export const POST: Action = async event => {
|
|
44
46
|
try {
|
|
45
47
|
const { token } = await event.request.json()
|
|
46
48
|
const user = await getGoogleUserFromJWT(token)
|
|
@@ -49,24 +51,18 @@ export const POST: RequestHandler = async event => {
|
|
|
49
51
|
// Prevent hooks.ts's handler() from deleting cookie thinking no one has authenticated
|
|
50
52
|
event.locals.user = userSession.user
|
|
51
53
|
|
|
52
|
-
return {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
message: 'Successful Google Sign-In.',
|
|
59
|
-
user: userSession.user
|
|
54
|
+
return new Response(JSON.stringify({
|
|
55
|
+
message: 'Successful Google Sign-In.',
|
|
56
|
+
user: userSession.user
|
|
57
|
+
}), {
|
|
58
|
+
headers: {
|
|
59
|
+
'Set-Cookie': `session=${userSession.id}; Path=/; SameSite=Lax; HttpOnly;`}
|
|
60
60
|
}
|
|
61
|
-
|
|
61
|
+
)
|
|
62
|
+
|
|
62
63
|
} catch (err) {
|
|
63
64
|
let message = ''
|
|
64
65
|
if (err instanceof Error) message = err.message
|
|
65
|
-
|
|
66
|
-
status: 401,
|
|
67
|
-
body: {
|
|
68
|
-
message: message
|
|
69
|
-
}
|
|
70
|
-
}
|
|
66
|
+
throw error(401, message)
|
|
71
67
|
}
|
|
72
68
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { json as json$1 } from '@sveltejs/kit';
|
|
1
2
|
import dotenv from 'dotenv'
|
|
2
3
|
import type { RequestHandler } from '@sveltejs/kit'
|
|
3
4
|
import type { JwtPayload } from 'jsonwebtoken'
|
|
@@ -21,19 +22,15 @@ export const PUT: RequestHandler = async event => {
|
|
|
21
22
|
const sql = `CALL reset_password($1, $2);`
|
|
22
23
|
await query(sql, [userId, password])
|
|
23
24
|
|
|
24
|
-
return {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
message: 'Password successfully reset.'
|
|
28
|
-
}
|
|
29
|
-
}
|
|
25
|
+
return json$1({
|
|
26
|
+
message: 'Password successfully reset.'
|
|
27
|
+
})
|
|
30
28
|
} catch (error) {
|
|
31
29
|
// Technically, I should check error.message to make sure it's not a DB issue
|
|
32
|
-
return {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
30
|
+
return json$1({
|
|
31
|
+
message: 'Password reset token expired.'
|
|
32
|
+
}, {
|
|
33
|
+
status: 403
|
|
34
|
+
})
|
|
38
35
|
}
|
|
39
36
|
}
|
|
@@ -1,22 +1,11 @@
|
|
|
1
|
-
<script context="module" lang="ts">
|
|
2
|
-
import type { Load } from '@sveltejs/kit'
|
|
3
|
-
|
|
4
|
-
export const load: Load = async event => {
|
|
5
|
-
return {
|
|
6
|
-
props: {
|
|
7
|
-
token: event.params.token
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
</script>
|
|
12
|
-
|
|
13
1
|
<script lang="ts">
|
|
2
|
+
import type { PageData } from './$types'
|
|
14
3
|
import { onMount } from 'svelte'
|
|
15
4
|
import { goto } from '$app/navigation'
|
|
16
|
-
import { toast } from '
|
|
5
|
+
import { toast } from '../../../../stores'
|
|
17
6
|
import { focusOnFirstError } from '$lib/focus'
|
|
18
7
|
|
|
19
|
-
export let
|
|
8
|
+
export let data: PageData
|
|
20
9
|
|
|
21
10
|
let focusedField: HTMLInputElement
|
|
22
11
|
let password: string
|
|
@@ -49,7 +38,7 @@
|
|
|
49
38
|
'Content-Type': 'application/json'
|
|
50
39
|
},
|
|
51
40
|
body: JSON.stringify({
|
|
52
|
-
token,
|
|
41
|
+
token: data.token,
|
|
53
42
|
password
|
|
54
43
|
})
|
|
55
44
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from 'svelte'
|
|
3
3
|
import { goto } from '$app/navigation'
|
|
4
|
-
import { toast } from '
|
|
4
|
+
import { toast } from '../../stores'
|
|
5
5
|
import { focusOnFirstError } from '$lib/focus'
|
|
6
6
|
|
|
7
7
|
let focusedField: HTMLInputElement
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
if (form.checkValidity()) {
|
|
20
20
|
if (email.toLowerCase().includes('gmail.com')) {
|
|
21
|
-
return message = '
|
|
21
|
+
return message = 'Gmail passwords must be reset on Manage Your Google Account.'
|
|
22
22
|
}
|
|
23
23
|
const url = `/auth/forgot`
|
|
24
24
|
const res = await fetch(url, {
|
|
File without changes
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from 'svelte'
|
|
3
3
|
import { goto } from '$app/navigation'
|
|
4
|
-
import { page
|
|
4
|
+
import { page } from '$app/stores'
|
|
5
|
+
import { loginSession } from '../../stores'
|
|
5
6
|
import useAuth from '$lib/auth'
|
|
6
7
|
import { focusOnFirstError } from '$lib/focus'
|
|
7
8
|
|
|
8
|
-
const {
|
|
9
|
+
const { initializeSignInWithGoogle, loginLocal } = useAuth(page, loginSession, goto)
|
|
9
10
|
|
|
10
11
|
let focusedField: HTMLInputElement
|
|
11
12
|
let message: string
|
|
@@ -34,7 +35,6 @@
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
onMount(async() => {
|
|
37
|
-
await loadScript() // probably cached
|
|
38
38
|
initializeSignInWithGoogle('googleButton')
|
|
39
39
|
focusedField.focus()
|
|
40
40
|
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { redirect } from '@sveltejs/kit';
|
|
2
|
+
import type { PageServerLoad } from './$types'
|
|
3
|
+
|
|
4
|
+
export const load: PageServerLoad = async ({ locals }) => {
|
|
5
|
+
const { user } = locals // populated by /src/hooks.ts
|
|
6
|
+
|
|
7
|
+
const authorized = ['admin', 'teacher', 'student'] // must be logged-in
|
|
8
|
+
if (user && !authorized.includes(user.role)) {
|
|
9
|
+
throw redirect(302, '/login?referrer=/profile')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
user
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -1,33 +1,15 @@
|
|
|
1
|
-
<script context="module" lang="ts">
|
|
2
|
-
import type { Load } from '@sveltejs/kit'
|
|
3
|
-
|
|
4
|
-
export const load: Load = ({ session }) => {
|
|
5
|
-
const authorized = ['admin', 'teacher', 'student'] // must be logged-in
|
|
6
|
-
if (session.user && !authorized.includes(session.user.role)) {
|
|
7
|
-
return {
|
|
8
|
-
status: 302,
|
|
9
|
-
redirect: '/login?referrer=/profile'
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return {
|
|
14
|
-
props: {
|
|
15
|
-
// Clone session.user so unsaved changes are NOT retained
|
|
16
|
-
user: JSON.parse(JSON.stringify(session.user))
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
</script>
|
|
21
|
-
|
|
22
1
|
<script lang="ts">
|
|
2
|
+
import type { PageData } from './$types'
|
|
23
3
|
import { onMount } from 'svelte'
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
4
|
+
import { focusOnFirstError } from '$lib/focus'
|
|
5
|
+
import { loginSession } from '../../stores'
|
|
6
|
+
|
|
7
|
+
export let data: PageData
|
|
8
|
+
const { user } : {user : User } = data
|
|
26
9
|
|
|
27
10
|
let focusedField: HTMLInputElement
|
|
28
11
|
let message: string
|
|
29
12
|
let confirmPassword: HTMLInputElement
|
|
30
|
-
export let user: User
|
|
31
13
|
|
|
32
14
|
onMount(() => {
|
|
33
15
|
focusedField.focus()
|
|
@@ -43,7 +25,6 @@
|
|
|
43
25
|
}
|
|
44
26
|
|
|
45
27
|
if (form.checkValidity()) {
|
|
46
|
-
$session.user = JSON.parse(JSON.stringify(user)) // deep clone
|
|
47
28
|
const url = '/api/v1/user'
|
|
48
29
|
const res = await fetch(url, {
|
|
49
30
|
method: 'PUT',
|
|
@@ -54,6 +35,7 @@
|
|
|
54
35
|
})
|
|
55
36
|
const reply = await res.json()
|
|
56
37
|
message = reply.message
|
|
38
|
+
$loginSession = JSON.parse(JSON.stringify(user)) // update loginSession store
|
|
57
39
|
} else {
|
|
58
40
|
form.classList.add('was-validated')
|
|
59
41
|
focusOnFirstError(form)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { redirect } from '@sveltejs/kit';
|
|
2
|
+
import type { PageServerLoad } from './$types'
|
|
3
|
+
|
|
4
|
+
export const load: PageServerLoad = ({ locals }) => {
|
|
5
|
+
const { user } = locals
|
|
6
|
+
if (user) { // Redirect to home if user is logged in already
|
|
7
|
+
throw redirect(302, '/');
|
|
8
|
+
}
|
|
9
|
+
return {}
|
|
10
|
+
}
|
|
@@ -1,25 +1,12 @@
|
|
|
1
|
-
<script context="module" lang="ts">
|
|
2
|
-
import type { Load } from '@sveltejs/kit'
|
|
3
|
-
|
|
4
|
-
export const load: Load = ({ session }) => {
|
|
5
|
-
if (session.user) { // Do not display if user is logged in
|
|
6
|
-
return {
|
|
7
|
-
status: 302,
|
|
8
|
-
redirect: '/'
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
return {}
|
|
12
|
-
}
|
|
13
|
-
</script>
|
|
14
|
-
|
|
15
1
|
<script lang="ts">
|
|
16
2
|
import { onMount } from 'svelte'
|
|
17
3
|
import { goto } from '$app/navigation'
|
|
18
|
-
import { page
|
|
4
|
+
import { page } from '$app/stores'
|
|
5
|
+
import { loginSession } from '../../stores'
|
|
19
6
|
import useAuth from '$lib/auth'
|
|
20
7
|
import { focusOnFirstError } from '$lib/focus'
|
|
21
8
|
|
|
22
|
-
const { initializeSignInWithGoogle, registerLocal } = useAuth(page,
|
|
9
|
+
const { initializeSignInWithGoogle, registerLocal } = useAuth(page, loginSession, goto)
|
|
23
10
|
|
|
24
11
|
let focusedField: HTMLInputElement
|
|
25
12
|
|
|
@@ -50,7 +37,7 @@
|
|
|
50
37
|
} catch (err) {
|
|
51
38
|
if (err instanceof Error) {
|
|
52
39
|
message = err.message
|
|
53
|
-
console.
|
|
40
|
+
console.log('Login error', message)
|
|
54
41
|
}
|
|
55
42
|
}
|
|
56
43
|
} else {
|
|
@@ -62,11 +49,7 @@
|
|
|
62
49
|
|
|
63
50
|
onMount(() => {
|
|
64
51
|
focusedField.focus()
|
|
65
|
-
initializeSignInWithGoogle()
|
|
66
|
-
window.google.accounts.id.renderButton(
|
|
67
|
-
document.getElementById('googleButton'),
|
|
68
|
-
{ theme: 'filled_blue', size: 'large', width: '367' } // customization attributes
|
|
69
|
-
)
|
|
52
|
+
initializeSignInWithGoogle('googleButton')
|
|
70
53
|
})
|
|
71
54
|
|
|
72
55
|
|
|
@@ -121,7 +104,7 @@
|
|
|
121
104
|
</div>
|
|
122
105
|
|
|
123
106
|
{#if message}
|
|
124
|
-
<p>{message}</p>
|
|
107
|
+
<p class="text-danger">{message}</p>
|
|
125
108
|
{/if}
|
|
126
109
|
|
|
127
110
|
<button type="button" on:click={register} class="btn btn-primary btn-lg">Register</button>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { redirect } from '@sveltejs/kit'
|
|
2
|
+
import type { PageServerLoad } from './$types'
|
|
3
|
+
|
|
4
|
+
export const load: PageServerLoad = async ({locals}) => {
|
|
5
|
+
const authorized = ['admin', 'teacher']
|
|
6
|
+
if (!locals.user || !authorized.includes(locals.user.role)) {
|
|
7
|
+
throw redirect(302, '/login?referrer=/teachers')
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
message: 'Teachers or Admin-only content from endpoint.'
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/stores.ts
CHANGED
|
@@ -4,4 +4,14 @@ export const toast = writable({
|
|
|
4
4
|
title: '',
|
|
5
5
|
body: '',
|
|
6
6
|
isOpen: false
|
|
7
|
-
})
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
// While server determines whether the user is logged in by examining RequestEvent.locals.user, the
|
|
10
|
+
// loginSession is updated so all parts of the SPA client-side see the user and role.
|
|
11
|
+
|
|
12
|
+
export const defaultUser: User = {
|
|
13
|
+
id: 0, // the not-logged-in user id
|
|
14
|
+
role: 'student' // default role for users who are not logged in
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const loginSession = writable(defaultUser)
|
package/svelte.config.js
CHANGED
|
@@ -5,7 +5,6 @@ const production = process.env.NODE_ENV === 'production'
|
|
|
5
5
|
|
|
6
6
|
const baseCsp = [
|
|
7
7
|
'self',
|
|
8
|
-
// 'strict-dynamic', // issues with datepicker on classes, add to calendar scripts
|
|
9
8
|
'https://www.gstatic.com/recaptcha/', // recaptcha
|
|
10
9
|
'https://accounts.google.com/gsi/', // sign-in w/google
|
|
11
10
|
'https://www.google.com/recaptcha/', // recapatcha
|
|
@@ -30,8 +29,7 @@ const config = {
|
|
|
30
29
|
'img-src': ['data:', 'blob:', ...baseCsp],
|
|
31
30
|
'style-src': ['unsafe-inline', ...baseCsp],
|
|
32
31
|
'object-src': ['none'],
|
|
33
|
-
'base-uri': ['self']
|
|
34
|
-
// 'require-trusted-types-for': ["'script'"] // will require effort to get this working
|
|
32
|
+
'base-uri': ['self']
|
|
35
33
|
}
|
|
36
34
|
}
|
|
37
35
|
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
<script lang="ts" context="module">
|
|
2
|
-
import type { Load } from '@sveltejs/kit'
|
|
3
|
-
|
|
4
|
-
export const load: Load = ({ error, status }) => {
|
|
5
|
-
const { message } = <Error> error
|
|
6
|
-
return {
|
|
7
|
-
props: {
|
|
8
|
-
errorMessage: `${status}: ${message}`
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
</script>
|
|
13
|
-
|
|
14
|
-
<script lang="ts">
|
|
15
|
-
export let errorMessage = ''
|
|
16
|
-
</script>
|
|
17
|
-
|
|
18
|
-
<h1>Error</h1>
|
|
19
|
-
<h4>{errorMessage}</h4>
|
package/src/routes/admin.svelte
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
<script context="module" lang="ts">
|
|
2
|
-
import type { Load } from '@sveltejs/kit'
|
|
3
|
-
|
|
4
|
-
export const load: Load = async ({ fetch, session }) => {
|
|
5
|
-
const authorized = ['admin']
|
|
6
|
-
if (session.user && !authorized.includes(session.user.role)) {
|
|
7
|
-
return {
|
|
8
|
-
status: 302,
|
|
9
|
-
redirect: '/login?referrer=/admin'
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const url = '/api/v1/admin'
|
|
14
|
-
const res = await fetch(url, {
|
|
15
|
-
method: 'GET',
|
|
16
|
-
headers: { 'Content-Type': 'application/json' }
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
// if !res.ok, error is returned as message
|
|
20
|
-
const { message } = await res.json()
|
|
21
|
-
return {
|
|
22
|
-
props: {
|
|
23
|
-
message
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
</script>
|
|
29
|
-
|
|
30
|
-
<script lang="ts">
|
|
31
|
-
export let message = ''
|
|
32
|
-
</script>
|
|
33
|
-
|
|
34
|
-
<svelte:head>
|
|
35
|
-
<title>Administration</title>
|
|
36
|
-
</svelte:head>
|
|
37
|
-
|
|
38
|
-
<h1>Admin</h1>
|
|
39
|
-
<h4>Admin Role</h4>
|
|
40
|
-
<p>{message}</p>
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { RequestHandler } from '@sveltejs/kit'
|
|
2
|
-
import { requestAuthorized } from './_auth'
|
|
3
|
-
|
|
4
|
-
export const GET: RequestHandler = async event => {
|
|
5
|
-
if (!requestAuthorized(event, ['admin'])) {
|
|
6
|
-
return {
|
|
7
|
-
status: 401,
|
|
8
|
-
body: {
|
|
9
|
-
message: 'Unauthorized - admin role required.'
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
status: 200,
|
|
16
|
-
body: {
|
|
17
|
-
message: 'Admin-only content from endpoint.'
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { RequestHandler } from '@sveltejs/kit'
|
|
2
|
-
import { requestAuthorized } from './_auth'
|
|
3
|
-
|
|
4
|
-
export const GET: RequestHandler = async event=> {
|
|
5
|
-
if (!requestAuthorized(event, ['admin', 'teacher'])) {
|
|
6
|
-
return {
|
|
7
|
-
status: 401,
|
|
8
|
-
body: {
|
|
9
|
-
message: 'Unauthorized - admin or teacher role required.'
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
status: 200,
|
|
16
|
-
body: {
|
|
17
|
-
message: 'Teachers or Admin-only content from endpoint.'
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { RequestHandler } from '@sveltejs/kit'
|
|
2
|
-
import { query } from '../../_db'
|
|
3
|
-
import { requestAuthorized } from './_auth'
|
|
4
|
-
|
|
5
|
-
export const PUT: RequestHandler = async event => {
|
|
6
|
-
if (!requestAuthorized(event, ['admin', 'teacher', 'student'])) {
|
|
7
|
-
return {
|
|
8
|
-
status: 401,
|
|
9
|
-
body: {
|
|
10
|
-
message: 'Unauthorized - must be logged-in.'
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const sql = `CALL update_user($1, $2);`
|
|
16
|
-
try {
|
|
17
|
-
// Only permit update of the authenticated user
|
|
18
|
-
const body = await event.request.json()
|
|
19
|
-
await query(sql, [event.locals.user.id, JSON.stringify(body)])
|
|
20
|
-
} catch (error) {
|
|
21
|
-
return {
|
|
22
|
-
status: 503,
|
|
23
|
-
body: {
|
|
24
|
-
message: 'Could not communicate with database.'
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
status: 200,
|
|
31
|
-
body: {
|
|
32
|
-
message: 'Successfully updated user profile.'
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|