sveltekit-auth-example 1.0.9 → 1.0.12

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 CHANGED
@@ -10,7 +10,7 @@ module.exports = {
10
10
  },
11
11
  parserOptions: {
12
12
  sourceType: 'module',
13
- ecmaVersion: 2019
13
+ ecmaVersion: 2020
14
14
  },
15
15
  env: {
16
16
  browser: true,
package/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ # 1.0.12
2
+ * Remove unnecessary reference from app.d.ts
3
+ * Remove commented lines in svelte.config.js
4
+
5
+ # 1.0.10
6
+ * Bump dependencies
7
+ * Adjust for changes to SvelteKit
8
+ * Improve typings
9
+
1
10
  # 1.0.9
2
11
  * Bump dependencies
3
12
  * Adjust for changes to SvelteKit with respect to vite
package/README.md CHANGED
@@ -31,8 +31,8 @@ The forgot password functionality uses SendInBlue to send the email. You would n
31
31
 
32
32
  ## Prerequisites
33
33
  - PostgreSQL 14 or higher
34
- - Node.js 16.16.0 or higher
35
- - npm 8.14.0 or higher
34
+ - Node.js 18.7.0 or higher
35
+ - npm 8.15.0 or higher
36
36
  - Google API client
37
37
  - SendInBlue account (only used for emailing password reset link - the sample can run without it but forgot password will not work)
38
38
 
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.9",
4
+ "version": "1.0.12",
5
5
  "private": false,
6
6
  "author": "Nate Stuyvesant",
7
7
  "license": "https://github.com/nstuyvesant/sveltekit-auth-example/blob/master/LICENSE",
@@ -33,8 +33,8 @@
33
33
  "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ."
34
34
  },
35
35
  "engines": {
36
- "node": "~16.16.0",
37
- "npm": "^8.14.0"
36
+ "node": "~18.7.0",
37
+ "npm": "^8.15.1"
38
38
  },
39
39
  "type": "module",
40
40
  "dependencies": {
@@ -42,30 +42,30 @@
42
42
  "dotenv": "^16.0.1",
43
43
  "google-auth-library": "^8.1.1",
44
44
  "jsonwebtoken": "^8.5.1",
45
- "pg": "^8.7.3",
46
- "pg-native": "^3.0.0"
45
+ "pg": "^8.7.3"
47
46
  },
48
47
  "devDependencies": {
49
48
  "@sveltejs/adapter-node": "latest",
50
49
  "@sveltejs/kit": "latest",
50
+ "@types/bootstrap": "5.2.1",
51
51
  "@types/cookie": "^0.5.1",
52
52
  "@types/jsonwebtoken": "^8.5.8",
53
53
  "@types/pg": "^8.6.5",
54
- "@typescript-eslint/eslint-plugin": "^5.30.6",
55
- "@typescript-eslint/parser": "^5.30.6",
56
- "bootstrap": "^5.1.3",
57
- "bootstrap-icons": "^1.9.0",
58
- "eslint": "^8.19.0",
54
+ "@typescript-eslint/eslint-plugin": "^5.32.0",
55
+ "@typescript-eslint/parser": "^5.32.0",
56
+ "bootstrap": "^5.2.0",
57
+ "bootstrap-icons": "^1.9.1",
58
+ "eslint": "^8.21.0",
59
59
  "eslint-config-prettier": "^8.5.0",
60
60
  "eslint-plugin-svelte3": "^4.0.0",
61
61
  "prettier": "^2.7.1",
62
62
  "prettier-plugin-svelte": "^2.7.0",
63
- "sass": "^1.53.0",
63
+ "sass": "^1.54.0",
64
64
  "svelte": "^3.49.0",
65
65
  "svelte-check": "^2.8.0",
66
66
  "svelte-preprocess": "^4.10.7",
67
67
  "tslib": "^2.4.0",
68
68
  "typescript": "^4.7.4",
69
- "vite": "^3.0.0"
69
+ "vite": "^3.0.4"
70
70
  }
71
71
  }
@@ -1,6 +1,4 @@
1
- /// <reference types="@sveltejs/kit" />
2
-
3
- /* eslint-disable @typescript-eslint/no-explicit-any */
1
+ /// <reference types="bootstrap" />
4
2
 
5
3
  // See https://kit.svelte.dev/docs/typescript
6
4
  // for information about these interfaces
@@ -12,18 +10,12 @@ declare namespace App {
12
10
  // interface Platform {}
13
11
 
14
12
  interface Session {
15
- reservationDate: Date
16
- scheduledClass?: ScheduledClass
17
13
  user?: User
18
14
  }
19
15
 
20
16
  // interface Stuff {}
21
17
  }
22
18
 
23
- interface ImportMetaEnv {
24
- VITE_GOOGLE_CLIENT_ID: string
25
- }
26
-
27
19
  type AuthenticationResult = {
28
20
  statusCode: number
29
21
  status: string
@@ -36,14 +28,31 @@ type Credentials = {
36
28
  password: string
37
29
  }
38
30
 
31
+ interface GoogleCredentialResponse {
32
+ credential: string
33
+ select_by:
34
+ | 'auto'
35
+ | 'user'
36
+ | 'user_1tap'
37
+ | 'user_2tap'
38
+ | 'btn'
39
+ | 'btn_confirm'
40
+ | 'btn_add_session'
41
+ | 'btn_confirm_add_session'
42
+ }
43
+
44
+ interface ImportMetaEnv {
45
+ VITE_GOOGLE_CLIENT_ID: string
46
+ }
47
+
39
48
  type MessageAddressee = {
40
49
  email: string
41
50
  name?: string
42
51
  }
43
52
 
44
53
  type Message = {
45
- sender?: MessageAddressee[]
46
- to: MessageAddressee[]
54
+ sender?: MessageAddressee
55
+ to?: MessageAddressee[]
47
56
  subject: string
48
57
  htmlContent?: string
49
58
  textContent?: string
@@ -51,9 +60,30 @@ type Message = {
51
60
  contact?: Person
52
61
  }
53
62
 
63
+ interface SendInBlueContact {
64
+ updateEnabled: boolean
65
+ email: string
66
+ emailBlacklisted: boolean
67
+ attributes: {
68
+ NAME: string
69
+ SURNAME: string
70
+ }
71
+ }
72
+
73
+ interface SendInBlueMessage extends Message {
74
+ sender: MessageAddressee
75
+ to: MessageAddressee[]
76
+ }
77
+
78
+ interface SendInBlueRequest extends RequestInit {
79
+ headers: {
80
+ 'api-key': string
81
+ }
82
+ }
83
+
54
84
  type User = {
55
- id?: number
56
- role?: 'student' | 'teacher' | 'admin'
85
+ id: number
86
+ role: 'student' | 'teacher' | 'admin'
57
87
  password?: string
58
88
  firstName?: string
59
89
  lastName?: string
@@ -66,6 +96,10 @@ type UserSession = {
66
96
  user: User
67
97
  }
68
98
 
99
+ /* eslint-disable @typescript-eslint/no-explicit-any */
100
+
69
101
  interface Window {
70
102
  google?: any
103
+ grecaptcha: any
104
+ bootstrap: Bootstrap
71
105
  }
package/src/hooks.ts CHANGED
@@ -14,7 +14,7 @@ async function attachUserToRequest(sessionId: string, event: RequestEvent) {
14
14
 
15
15
  function deleteCookieIfNoUser(event: RequestEvent, response: Response) {
16
16
  if (!event.locals.user) {
17
- response.headers['Set-Cookie'] = `session=; Path=/; HttpOnly; SameSite=Lax; Expires=${new Date().toUTCString()}`
17
+ response.headers.set('Set-Cookie', `session=; Path=/; HttpOnly; SameSite=Lax; Expires=${new Date().toUTCString()}`)
18
18
  }
19
19
  }
20
20
 
@@ -27,7 +27,9 @@ export const handle: Handle = async ({ event, resolve }) => {
27
27
  await attachUserToRequest(cookies.session, event)
28
28
  }
29
29
 
30
- const response = await resolve(event)
30
+ const response = await resolve(event, {
31
+ ssr: !event.request.url.includes('/admin')
32
+ })
31
33
 
32
34
  // after endpoint or page is called
33
35
  deleteCookieIfNoUser(event, response)
package/src/lib/auth.ts CHANGED
@@ -10,15 +10,15 @@ type Page = Readable<{
10
10
  error: Error | null;
11
11
  }>
12
12
 
13
- export default function useAuth(page: Page, session: Writable<any>, goto) {
13
+ export default function useAuth(page: Page, session: Writable<any>, goto: (url: string | URL, opts?: { replaceState?: boolean; noscroll?: boolean; keepfocus?: boolean; state?: any; }) => Promise<any>) {
14
14
 
15
15
  // Required to use session.set()
16
- let sessionValue
16
+ let sessionValue: App.Session
17
17
  session.subscribe(value => {
18
18
  sessionValue = value
19
19
  })
20
20
 
21
- let referrer
21
+ let referrer: string | null
22
22
  page.subscribe(value => {
23
23
  referrer = value.url.searchParams.get('referrer')
24
24
  })
@@ -67,7 +67,7 @@ export default function useAuth(page: Page, session: Writable<any>, goto) {
67
67
  }))
68
68
  }
69
69
 
70
- async function googleCallback(response) {
70
+ async function googleCallback(response: GoogleCredentialResponse) {
71
71
  const res = await fetch('/auth/google', {
72
72
  method: 'POST',
73
73
  headers: {
@@ -105,12 +105,14 @@ export default function useAuth(page: Page, session: Writable<any>, goto) {
105
105
  throw new Error(fromEndpoint.message)
106
106
  }
107
107
  } catch (err) {
108
- console.error('Login error', err)
109
- throw new Error(err.message)
108
+ if (err instanceof Error) {
109
+ console.error('Login error', err)
110
+ throw new Error(err.message)
111
+ }
110
112
  }
111
113
  }
112
114
 
113
- async function loginLocal(credentials) {
115
+ async function loginLocal(credentials: Credentials) {
114
116
  try {
115
117
  const res = await fetch('/auth/login', {
116
118
  method: 'POST',
@@ -131,8 +133,10 @@ export default function useAuth(page: Page, session: Writable<any>, goto) {
131
133
  throw new Error(fromEndpoint.message)
132
134
  }
133
135
  } catch (err) {
134
- console.error('Login error', err)
135
- throw new Error(err.message)
136
+ if (err instanceof Error) {
137
+ console.error('Login error', err)
138
+ throw new Error(err.message)
139
+ }
136
140
  }
137
141
  }
138
142
 
package/src/lib/focus.ts CHANGED
@@ -1,8 +1,6 @@
1
- type FocusResult = (formName: HTMLFormElement) => void
2
-
3
- export const focusOnFirstError: FocusResult = (form: HTMLFormElement) => {
1
+ export const focusOnFirstError = (form: HTMLFormElement) => {
4
2
  for(const field of form.elements) {
5
- if (!field.checkValidity()) {
3
+ if (field instanceof HTMLInputElement && !field.checkValidity()) {
6
4
  field.focus()
7
5
  break
8
6
  }
@@ -1,15 +1,18 @@
1
1
  <script lang="ts" context="module">
2
- export const load = ({ error, status }) => {
2
+ import type { Load } from '@sveltejs/kit'
3
+
4
+ export const load: Load = ({ error, status }) => {
5
+ const { message } = <Error> error
3
6
  return {
4
7
  props: {
5
- errorMessage: `${status}: ${error.message}`
8
+ errorMessage: `${status}: ${message}`
6
9
  }
7
10
  }
8
11
  }
9
12
  </script>
10
13
 
11
14
  <script lang="ts">
12
- export let errorMessage
15
+ export let errorMessage = ''
13
16
  </script>
14
17
 
15
18
  <h1>Error</h1>
@@ -8,12 +8,7 @@
8
8
  // Vue.js Composition API style
9
9
  const { loadScript, initializeSignInWithGoogle, logout } = useAuth(page, session, goto)
10
10
 
11
- let sessionValue
12
- session.subscribe(value => {
13
- sessionValue = value
14
- })
15
-
16
- let Toast
11
+ let Toast: any
17
12
 
18
13
  onMount(async() => {
19
14
  await import('bootstrap/js/dist/collapse')
package/src/routes/_db.ts CHANGED
@@ -5,13 +5,13 @@ import pg from 'pg'
5
5
 
6
6
  dotenv.config()
7
7
 
8
- const pgNativePool = new pg.native.Pool({
8
+ const pool = new pg.Pool({
9
9
  max: 10, // default
10
- connectionString: process.env['DATABASE_URL'],
10
+ connectionString: process.env.DATABASE_URL,
11
11
  ssl: {
12
12
  rejectUnauthorized: false
13
13
  }
14
14
  })
15
15
 
16
16
  type PostgresQueryResult = (sql: string, params?: any[]) => Promise<QueryResult<any>>
17
- export const query: PostgresQueryResult = (sql, params?) => pgNativePool.query(sql, params)
17
+ export const query: PostgresQueryResult = (sql, params?) => pool.query(sql, params)
@@ -2,14 +2,14 @@ import dotenv from 'dotenv'
2
2
 
3
3
  dotenv.config()
4
4
 
5
- const SEND_IN_BLUE_KEY = process.env['SEND_IN_BLUE_KEY']
6
- const SEND_IN_BLUE_URL = process.env['SEND_IN_BLUE_URL']
7
- const SEND_IN_BLUE_FROM = <MessageAddressee> JSON.parse(process.env['SEND_IN_BLUE_FROM'])
8
- const SEND_IN_BLUE_ADMINS = <MessageAddressee> JSON.parse(process.env['SEND_IN_BLUE_ADMINS'])
5
+ const SEND_IN_BLUE_KEY = process.env.SEND_IN_BLUE_KEY
6
+ const SEND_IN_BLUE_URL = process.env.SEND_IN_BLUE_URL
7
+ const SEND_IN_BLUE_FROM = <MessageAddressee> JSON.parse(process.env.SEND_IN_BLUE_FROM || '')
8
+ const SEND_IN_BLUE_ADMINS = <MessageAddressee> JSON.parse(process.env.SEND_IN_BLUE_ADMINS || '')
9
9
 
10
10
  // POST or PUT submission to SendInBlue
11
- const submit = async (method, url, data) => {
12
- const response: Response = await fetch(`${SEND_IN_BLUE_URL}${url}`, {
11
+ const submit = async (method: string, url: string, data: Partial<SendInBlueContact> | SendInBlueMessage) => {
12
+ const response: Response = await fetch(`${SEND_IN_BLUE_URL}${url}`, <SendInBlueRequest> {
13
13
  method,
14
14
  headers: {
15
15
  'Content-Type': 'application/json',
@@ -23,7 +23,7 @@ const submit = async (method, url, data) => {
23
23
  }
24
24
  }
25
25
 
26
- const sender = SEND_IN_BLUE_FROM
26
+ const sender = SEND_IN_BLUE_FROM
27
27
  const to = SEND_IN_BLUE_ADMINS
28
28
 
29
- export const sendMessage = async (message: Message) => submit('POST', '/v3/smtp/email', { sender, to: [to], ...message })
29
+ export const sendMessage = async (message: Message) => submit('POST', '/v3/smtp/email', { sender, to: [to], ...message })
@@ -1,9 +1,9 @@
1
1
  <script context="module" lang="ts">
2
2
  import type { Load } from '@sveltejs/kit'
3
3
 
4
- export const load: Load = async ({ session }) => {
4
+ export const load: Load = async ({ fetch, session }) => {
5
5
  const authorized = ['admin']
6
- if (!authorized.includes(session.user?.role)) {
6
+ if (session.user && !authorized.includes(session.user.role)) {
7
7
  return {
8
8
  status: 302,
9
9
  redirect: '/login?referrer=/admin'
@@ -28,7 +28,7 @@
28
28
  </script>
29
29
 
30
30
  <script lang="ts">
31
- export let message
31
+ export let message = ''
32
32
  </script>
33
33
 
34
34
  <svelte:head>
@@ -0,0 +1,5 @@
1
+ import type { RequestEvent } from "@sveltejs/kit"
2
+
3
+ export function requestAuthorized(event: RequestEvent, roles: string[]): boolean {
4
+ return !!event.locals.user && roles.includes(event.locals.user.role)
5
+ }
@@ -1,9 +1,8 @@
1
1
  import type { RequestHandler } from '@sveltejs/kit'
2
+ import { requestAuthorized } from './_auth'
2
3
 
3
- export const get: RequestHandler = async event => {
4
- const authorized = ['admin']
5
-
6
- if (!event.locals.user || !authorized.includes(event.locals.user.role)) {
4
+ export const GET: RequestHandler = async event => {
5
+ if (!requestAuthorized(event, ['admin'])) {
7
6
  return {
8
7
  status: 401,
9
8
  body: {
@@ -1,9 +1,8 @@
1
1
  import type { RequestHandler } from '@sveltejs/kit'
2
+ import { requestAuthorized } from './_auth'
2
3
 
3
- export const get: RequestHandler = async event=> {
4
- const authorized = ['admin', 'teacher']
5
-
6
- if (!event.locals.user || !authorized.includes(event.locals.user.role)) {
4
+ export const GET: RequestHandler = async event=> {
5
+ if (!requestAuthorized(event, ['admin', 'teacher'])) {
7
6
  return {
8
7
  status: 401,
9
8
  body: {
@@ -1,10 +1,9 @@
1
1
  import type { RequestHandler } from '@sveltejs/kit'
2
2
  import { query } from '../../_db'
3
+ import { requestAuthorized } from './_auth'
3
4
 
4
- export const put: RequestHandler = async event => {
5
- const authorized = ['admin', 'teacher', 'student']
6
-
7
- if (!event.locals.user || !authorized.includes(event.locals.user.role)) {
5
+ export const PUT: RequestHandler = async event => {
6
+ if (!requestAuthorized(event, ['admin', 'teacher', 'student'])) {
8
7
  return {
9
8
  status: 401,
10
9
  body: {
@@ -1,7 +1,7 @@
1
1
  import type { RequestHandler } from '@sveltejs/kit'
2
2
  import { query } from '../_db'
3
3
 
4
- export const post: RequestHandler = async event => {
4
+ export const POST: RequestHandler = async event => {
5
5
  const { slug } = event.params
6
6
 
7
7
  let result
@@ -6,10 +6,10 @@ import { query } from '../_db'
6
6
  import { sendMessage } from '../_send-in-blue'
7
7
 
8
8
  dotenv.config()
9
- const DOMAIN = process.env['DOMAIN']
10
- const JWT_SECRET: Secret = process.env['JWT_SECRET']
9
+ const DOMAIN = process.env.DOMAIN
10
+ const JWT_SECRET = process.env.JWT_SECRET
11
11
 
12
- export const post: RequestHandler = async event => {
12
+ export const POST: RequestHandler = 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])
@@ -18,7 +18,7 @@ export const post: RequestHandler = async event => {
18
18
  const { userId } = rows[0]
19
19
  // Create JWT with userId expiring in 30 mins
20
20
  const secret = JWT_SECRET
21
- const token = jwt.sign({ subject: userId }, secret, {
21
+ const token = jwt.sign({ subject: userId }, <Secret> secret, {
22
22
  expiresIn: '30m'
23
23
  })
24
24
 
@@ -4,7 +4,7 @@ import { query } from '../_db';
4
4
  import { config } from '$lib/config'
5
5
 
6
6
  // Verify JWT per https://developers.google.com/identity/gsi/web/guides/verify-google-id-token
7
- async function getGoogleUserFromJWT(token: string): Promise<User> {
7
+ async function getGoogleUserFromJWT(token: string): Promise<Partial<User>> {
8
8
  try {
9
9
  const clientId = config.googleClientId
10
10
  const client = new OAuth2Client(clientId)
@@ -13,29 +13,34 @@ async function getGoogleUserFromJWT(token: string): Promise<User> {
13
13
  audience: clientId
14
14
  });
15
15
  const payload = ticket.getPayload()
16
+ if (!payload) throw new Error('Google authentication did not get the expected payload')
16
17
  return {
17
- firstName: payload['given_name'],
18
- lastName: payload['family_name'],
18
+ firstName: payload['given_name'] || 'UnknownFirstName',
19
+ lastName: payload['family_name'] || 'UnknownLastName',
19
20
  email: payload['email']
20
21
  }
21
- } catch (error) {
22
- throw new Error(`Google user could not be authenticated: ${error.message}`)
22
+ } catch (err) {
23
+ let message = ''
24
+ if (err instanceof Error) message = err.message
25
+ throw new Error(`Google user could not be authenticated: ${message}`)
23
26
  }
24
27
  }
25
28
 
26
29
  // Upsert user and get session ID
27
- async function upsertGoogleUser(user: User): Promise<UserSession> {
30
+ async function upsertGoogleUser(user: Partial<User>): Promise<UserSession> {
28
31
  try {
29
32
  const sql = `SELECT start_gmail_user_session($1) AS user_session;`
30
33
  const { rows } = await query(sql, [JSON.stringify(user)])
31
34
  return <UserSession> rows[0].user_session
32
- } catch (error) {
33
- throw new Error(`GMail user could not be upserted: ${error.message}`)
35
+ } catch (err) {
36
+ let message = ''
37
+ if (err instanceof Error) message = err.message
38
+ throw new Error(`GMail user could not be upserted: ${message}`)
34
39
  }
35
40
  }
36
41
 
37
42
  // Returns local user if Google user authenticated (and authorized our app)
38
- export const post: RequestHandler = async event => {
43
+ export const POST: RequestHandler = async event => {
39
44
  try {
40
45
  const { token } = await event.request.json()
41
46
  const user = await getGoogleUserFromJWT(token)
@@ -54,11 +59,13 @@ export const post: RequestHandler = async event => {
54
59
  user: userSession.user
55
60
  }
56
61
  }
57
- } catch (error) {
62
+ } catch (err) {
63
+ let message = ''
64
+ if (err instanceof Error) message = err.message
58
65
  return { // session cookie deleted by hooks.js handle()
59
66
  status: 401,
60
67
  body: {
61
- message: error.message
68
+ message: message
62
69
  }
63
70
  }
64
71
  }
@@ -34,7 +34,7 @@
34
34
 
35
35
  const resetPassword = async () => {
36
36
  message = ''
37
- const form = document.forms['reset']
37
+ const form = <HTMLFormElement> document.getElementById('reset')
38
38
 
39
39
  if (!passwordMatch()) {
40
40
  confirmPassword.classList.add('is-invalid')
@@ -6,9 +6,9 @@ import { query } from '../../_db'
6
6
 
7
7
  dotenv.config()
8
8
 
9
- const JWT_SECRET = process.env['JWT_SECRET']
9
+ const JWT_SECRET = <jwt.Secret> process.env.JWT_SECRET
10
10
 
11
- export const put: RequestHandler = async event => {
11
+ export const PUT: RequestHandler = async event => {
12
12
  const body = await event.request.json()
13
13
  const { token, password } = body
14
14
 
@@ -14,7 +14,7 @@
14
14
 
15
15
  const sendPasswordReset = async () => {
16
16
  message = ''
17
- const form = document.forms['forgot']
17
+ const form = <HTMLFormElement> document.getElementById('forgot')
18
18
 
19
19
  if (form.checkValidity()) {
20
20
  if (email.toLowerCase().includes('gmail.com')) {
@@ -16,14 +16,16 @@
16
16
 
17
17
  async function login() {
18
18
  message = ''
19
- const form = document.forms['signIn']
19
+ const form = <HTMLFormElement> document.getElementById('signIn')
20
20
 
21
21
  if (form.checkValidity()) {
22
22
  try {
23
23
  await loginLocal(credentials)
24
24
  } catch (err) {
25
- console.error('Login error', err.message)
26
- message = err.message
25
+ if (err instanceof Error) {
26
+ console.error('Login error', err.message)
27
+ message = err.message
28
+ }
27
29
  }
28
30
  } else {
29
31
  form.classList.add('was-validated')
@@ -3,7 +3,7 @@
3
3
 
4
4
  export const load: Load = ({ session }) => {
5
5
  const authorized = ['admin', 'teacher', 'student'] // must be logged-in
6
- if (!authorized.includes(session.user?.role)) {
6
+ if (session.user && !authorized.includes(session.user.role)) {
7
7
  return {
8
8
  status: 302,
9
9
  redirect: '/login?referrer=/profile'
@@ -20,6 +20,7 @@
20
20
  </script>
21
21
 
22
22
  <script lang="ts">
23
+ import { onMount } from 'svelte'
23
24
  import { session } from '$app/stores'
24
25
  import { focusOnFirstError } from '$lib/focus';
25
26
 
@@ -28,11 +29,15 @@
28
29
  let confirmPassword: HTMLInputElement
29
30
  export let user: User
30
31
 
32
+ onMount(() => {
33
+ focusedField.focus()
34
+ })
35
+
31
36
  async function update() {
32
37
  message = ''
33
- const form = document.forms['profile']
38
+ const form = <HTMLFormElement> document.getElementById('profile')
34
39
 
35
- if (!user.email.includes('gmail.com') && !passwordMatch()) {
40
+ if (!user?.email?.includes('gmail.com') && !passwordMatch()) {
36
41
  confirmPassword.classList.add('is-invalid')
37
42
  return
38
43
  }
@@ -72,7 +77,7 @@
72
77
  <h4><strong>Profile</strong></h4>
73
78
  <p>Update your information.</p>
74
79
  <form id="profile" autocomplete="on" novalidate class="mt-3">
75
- {#if !user.email.includes('gmail.com')}
80
+ {#if !user?.email?.includes('gmail.com')}
76
81
  <div class="mb-3">
77
82
  <label class="form-label" for="email">Email</label>
78
83
  <input bind:this={focusedField} type="email" class="form-control" bind:value={user.email} required placeholder="Email" id="email" autocomplete="email"/>
@@ -92,7 +97,7 @@
92
97
  {/if}
93
98
  <div class="mb-3">
94
99
  <label class="form-label" for="firstName">First name</label>
95
- <input bind:value={user.firstName} class="form-control" id="firstName" required placeholder="First name" autocomplete="given-name"/>
100
+ <input bind:this={focusedField} bind:value={user.firstName} class="form-control" id="firstName" required placeholder="First name" autocomplete="given-name"/>
96
101
  <div class="invalid-feedback">First name required</div>
97
102
  </div>
98
103
  <div class="mb-3">
@@ -23,7 +23,9 @@
23
23
 
24
24
  let focusedField: HTMLInputElement
25
25
 
26
- let user = {
26
+ let user: User = {
27
+ id: 0,
28
+ role: 'student',
27
29
  firstName: '',
28
30
  lastName: '',
29
31
  password: '',
@@ -34,7 +36,7 @@
34
36
  let message: string
35
37
 
36
38
  async function register() {
37
- const form = document.forms['register']
39
+ const form = <HTMLFormElement> document.getElementById('register')
38
40
  message = ''
39
41
 
40
42
  if (!passwordMatch()) {
@@ -46,8 +48,10 @@
46
48
  try {
47
49
  await registerLocal(user)
48
50
  } catch (err) {
49
- console.error('Login error', err.message)
50
- message = err.message
51
+ if (err instanceof Error) {
52
+ message = err.message
53
+ console.error('Login error', message)
54
+ }
51
55
  }
52
56
  } else {
53
57
  form.classList.add('was-validated')
@@ -59,7 +63,7 @@
59
63
  onMount(() => {
60
64
  focusedField.focus()
61
65
  initializeSignInWithGoogle()
62
- google.accounts.id.renderButton(
66
+ window.google.accounts.id.renderButton(
63
67
  document.getElementById('googleButton'),
64
68
  { theme: 'filled_blue', size: 'large', width: '367' } // customization attributes
65
69
  )
@@ -1,9 +1,9 @@
1
1
  <script context="module" lang="ts">
2
2
  import type { Load } from '@sveltejs/kit'
3
3
 
4
- export const load: Load = async ({ session }) => {
4
+ export const load: Load = async ({ fetch, session }) => {
5
5
  const authorized = ['admin', 'teacher']
6
- if (!authorized.includes(session.user?.role)) {
6
+ if (!session.user || !authorized.includes(session.user.role)) {
7
7
  return {
8
8
  status: 302,
9
9
  redirect: '/login?referrer=/teachers'
@@ -27,7 +27,7 @@
27
27
  </script>
28
28
 
29
29
  <script lang="ts">
30
- export let message
30
+ export let message = ''
31
31
  </script>
32
32
 
33
33
  <svelte:head>
package/svelte.config.js CHANGED
@@ -5,8 +5,6 @@ const production = process.env.NODE_ENV === 'production'
5
5
 
6
6
  const baseCsp = [
7
7
  'self',
8
- 'ws://127.0.0.1:3000/',
9
- // 'strict-dynamic', // issues with datepicker on classes, add to calendar scripts
10
8
  'https://www.gstatic.com/recaptcha/', // recaptcha
11
9
  'https://accounts.google.com/gsi/', // sign-in w/google
12
10
  'https://www.google.com/recaptcha/', // recapatcha
@@ -31,8 +29,7 @@ const config = {
31
29
  'img-src': ['data:', 'blob:', ...baseCsp],
32
30
  'style-src': ['unsafe-inline', ...baseCsp],
33
31
  'object-src': ['none'],
34
- 'base-uri': ['self'],
35
- // 'require-trusted-types-for': ["'script'"] // will require effort to get this working
32
+ 'base-uri': ['self']
36
33
  }
37
34
  }
38
35
  }