sveltekit-auth-example 1.0.37 → 1.0.38
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/.env-sample +2 -4
- package/CHANGELOG.md +4 -0
- package/README.md +7 -7
- package/package.json +6 -5
- package/src/app.d.ts +2 -40
- package/src/lib/server/sendgrid.ts +17 -0
- package/src/routes/auth/forgot/+server.ts +9 -7
- package/src/lib/server/send-in-blue.ts +0 -22
package/.env-sample
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
DATABASE_URL=postgres://REPLACE_WITH_USER:REPLACE_WITH_PASSWORD@localhost:5432/auth
|
|
2
2
|
DOMAIN=http://localhost:3000
|
|
3
3
|
JWT_SECRET=replace_with_your_own
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
SEND_IN_BLUE_FROM='{ "email":"sender@example.com", "name":"First Last" }'
|
|
7
|
-
SEND_IN_BLUE_ADMINS='{ "email":"admin@example.com", "name":"First Last" }'
|
|
4
|
+
SENDGRID_KEY=replace_with_your_own
|
|
5
|
+
SENDGRID_SENDER=replace_with_your_own
|
|
8
6
|
PUBLIC_GOOGLE_CLIENT_ID=REPLACE_WITH_YOUR_OWN
|
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
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.38
|
|
5
|
+
* Switch from SendInBlue to Sendgrid for email
|
|
6
|
+
* Bump SvelteKit, adapter-node, and vite
|
|
7
|
+
|
|
4
8
|
# 1.0.37
|
|
5
9
|
* Bump SvelteKit, svelte-check, and a few dev dependencies
|
|
6
10
|
|
package/README.md
CHANGED
|
@@ -27,14 +27,14 @@ The website supports two types of authentication:
|
|
|
27
27
|
|
|
28
28
|
> There is some overhead to checking the user session in a database each time versus using a JWT; however, validating each request avoids problems discussed in [this article](https://redis.com/blog/json-web-tokens-jwt-are-dangerous-for-user-sessions/) and [this one](https://scotch.io/bar-talk/why-jwts-suck-as-session-tokens). For a high-volume website, I would use Redis or the equivalent.
|
|
29
29
|
|
|
30
|
-
The forgot password / password reset functionality uses a JWT and [**
|
|
30
|
+
The forgot password / password reset functionality uses a JWT and [**SendGrid**](https://www.sendgrid.com) to send the email. You would need to have a **SendGrid** account and set two environmental variables. Email sending is in /src/routes/auth/forgot.ts. This code could easily be replaced by nodemailer or something similar. Note: I have no affliation with **SendGrid** (used their API in another project).
|
|
31
31
|
|
|
32
32
|
## Prerequisites
|
|
33
33
|
- PostgreSQL 14.5 or higher
|
|
34
34
|
- Node.js 18.11.0 or higher
|
|
35
35
|
- npm 9.1.1 or higher
|
|
36
36
|
- Google API client
|
|
37
|
-
-
|
|
37
|
+
- Twilio SendGrid account (only used for emailing password reset link - the sample can run without it but forgot password will not work)
|
|
38
38
|
|
|
39
39
|
## Setting up the project
|
|
40
40
|
|
|
@@ -55,15 +55,15 @@ psql -d postgres -f db_create.sql
|
|
|
55
55
|
|
|
56
56
|
2. Create a **Google API client ID** per [these instructions](https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid). Make sure you include `http://localhost:3000`, `http://localhost` in the Authorized JavaScript origins and `http://localhost:3000/auth/google/callback` in the Authorized redirect URIs for your Client ID for Web application. ** Do not access the site using http://127.0.0.1:3000 ** - use `http://localhost:3000` or it will not work.
|
|
57
57
|
|
|
58
|
-
3. Create
|
|
58
|
+
3. [Create a free Twilio SendGrid account](https://signup.sendgrid.com) and generate an API Key following [this documentation](https://docs.sendgrid.com/ui/account-and-settings/api-keys) and add a sender as documented [here](https://docs.sendgrid.com/ui/sending-email/senders).
|
|
59
|
+
|
|
60
|
+
4. Create an **.env** file at the top level of the project with the following values (substituting your own id and PostgreSQL username and password):
|
|
59
61
|
```bash
|
|
60
62
|
DATABASE_URL=postgres://user:password@localhost:5432/auth
|
|
61
63
|
DOMAIN=http://localhost:3000
|
|
62
64
|
JWT_SECRET=replace_with_your_own
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
SEND_IN_BLUE_FROM='{ "email":"jdoe@example.com", "name":"John Doe" }'
|
|
66
|
-
SEND_IN_BLUE_ADMINS='{ "email":"jdoe@example.com", "name":"John Doe" }'
|
|
65
|
+
SENDGRID_KEY=replace_with_your_own
|
|
66
|
+
SENDGRID_SENDER=replace_with_your_own
|
|
67
67
|
PUBLIC_GOOGLE_CLIENT_ID=replace_with_your_own
|
|
68
68
|
```
|
|
69
69
|
|
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.38",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Nate Stuyvesant",
|
|
7
7
|
"license": "https://github.com/nstuyvesant/sveltekit-auth-example/blob/master/LICENSE",
|
|
@@ -33,23 +33,24 @@
|
|
|
33
33
|
},
|
|
34
34
|
"engines": {
|
|
35
35
|
"node": ">=18.11.0",
|
|
36
|
-
"npm": "^9.1.
|
|
36
|
+
"npm": "^9.1.3"
|
|
37
37
|
},
|
|
38
38
|
"type": "module",
|
|
39
39
|
"dependencies": {
|
|
40
|
+
"@sendgrid/mail": "^7.7.0",
|
|
40
41
|
"pg": "^8.8.0"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@sveltejs/adapter-node": "latest",
|
|
44
45
|
"@sveltejs/kit": "latest",
|
|
45
46
|
"@types/bootstrap": "5.2.6",
|
|
46
|
-
"@types/google.accounts": "0.0.
|
|
47
|
+
"@types/google.accounts": "0.0.4",
|
|
47
48
|
"@types/jsonwebtoken": "^8.5.9",
|
|
48
49
|
"@types/pg": "^8.6.5",
|
|
49
50
|
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
|
50
51
|
"@typescript-eslint/parser": "^5.45.0",
|
|
51
52
|
"bootstrap": "^5.2.3",
|
|
52
|
-
"eslint": "^8.
|
|
53
|
+
"eslint": "^8.29.0",
|
|
53
54
|
"eslint-config-prettier": "^8.5.0",
|
|
54
55
|
"eslint-plugin-svelte3": "^4.0.0",
|
|
55
56
|
"google-auth-library": "^8.7.0",
|
|
@@ -62,6 +63,6 @@
|
|
|
62
63
|
"svelte-preprocess": "^4.10.7",
|
|
63
64
|
"tslib": "^2.4.1",
|
|
64
65
|
"typescript": "^4.9.3",
|
|
65
|
-
"vite": "^3.2.
|
|
66
|
+
"vite": "^3.2.5"
|
|
66
67
|
}
|
|
67
68
|
}
|
package/src/app.d.ts
CHANGED
|
@@ -17,10 +17,8 @@ declare namespace App {
|
|
|
17
17
|
DATABASE_URL: string
|
|
18
18
|
DOMAIN: string
|
|
19
19
|
JWT_SECRET: string
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
SEND_IN_BLUE_FROM: string
|
|
23
|
-
SEND_IN_BLUE_ADMINS: string
|
|
20
|
+
SENDGRID_KEY: string
|
|
21
|
+
SENDGRID_SENDER: string
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
interface PublicEnv { // $env/static/public
|
|
@@ -40,42 +38,6 @@ interface Credentials {
|
|
|
40
38
|
password: string
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
interface MessageAddressee {
|
|
44
|
-
email: string
|
|
45
|
-
name?: string
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
interface Message {
|
|
49
|
-
sender?: MessageAddressee
|
|
50
|
-
to?: MessageAddressee[]
|
|
51
|
-
subject: string
|
|
52
|
-
htmlContent?: string
|
|
53
|
-
textContent?: string
|
|
54
|
-
tags?: string[]
|
|
55
|
-
contact?: Person
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
interface SendInBlueContact {
|
|
59
|
-
updateEnabled: boolean
|
|
60
|
-
email: string
|
|
61
|
-
emailBlacklisted: boolean
|
|
62
|
-
attributes: {
|
|
63
|
-
NAME: string
|
|
64
|
-
SURNAME: string
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
interface SendInBlueMessage extends Message {
|
|
69
|
-
sender: MessageAddressee
|
|
70
|
-
to: MessageAddressee[]
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
interface SendInBlueRequest extends RequestInit {
|
|
74
|
-
headers: {
|
|
75
|
-
'api-key': string
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
41
|
interface UserProperties {
|
|
80
42
|
id: number
|
|
81
43
|
expires?: string // ISO-8601 datetime
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { MailDataRequired } from '@sendgrid/mail'
|
|
2
|
+
import sgMail from '@sendgrid/mail'
|
|
3
|
+
import { env } from '$env/dynamic/private'
|
|
4
|
+
|
|
5
|
+
export const sendMessage = async (message: Partial<MailDataRequired>) => {
|
|
6
|
+
const { SENDGRID_SENDER, SENDGRID_KEY } = env
|
|
7
|
+
try {
|
|
8
|
+
sgMail.setApiKey(SENDGRID_KEY)
|
|
9
|
+
const completeMessage = <MailDataRequired> {
|
|
10
|
+
from: SENDGRID_SENDER, // default sender can be altered
|
|
11
|
+
...message
|
|
12
|
+
}
|
|
13
|
+
await sgMail.send(completeMessage)
|
|
14
|
+
} catch (errSendingMail) {
|
|
15
|
+
console.error(errSendingMail)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { RequestHandler } from './$types'
|
|
2
|
-
import { JWT_SECRET, DOMAIN } from '$env/static/private'
|
|
3
1
|
import type { Secret } from 'jsonwebtoken'
|
|
2
|
+
import type { MailDataRequired } from '@sendgrid/mail'
|
|
3
|
+
import type { RequestHandler } from './$types'
|
|
4
4
|
import jwt from 'jsonwebtoken'
|
|
5
|
+
import { JWT_SECRET, DOMAIN, SENDGRID_SENDER } from '$env/static/private'
|
|
5
6
|
import { query } from '$lib/server/db'
|
|
6
|
-
import { sendMessage } from '$lib/server/
|
|
7
|
+
import { sendMessage } from '$lib/server/sendgrid'
|
|
7
8
|
|
|
8
9
|
export const POST: RequestHandler = async event => {
|
|
9
10
|
const body = await event.request.json()
|
|
@@ -19,11 +20,12 @@ export const POST: RequestHandler = async event => {
|
|
|
19
20
|
})
|
|
20
21
|
|
|
21
22
|
// Email URL with token to user
|
|
22
|
-
const message:
|
|
23
|
-
to:
|
|
23
|
+
const message: MailDataRequired = {
|
|
24
|
+
to: { email: body.email },
|
|
25
|
+
from: SENDGRID_SENDER,
|
|
24
26
|
subject: 'Password reset',
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
categories: ['account'],
|
|
28
|
+
html: `
|
|
27
29
|
<a href="${DOMAIN}/auth/reset/${token}">Reset my password</a>. Your browser will open and ask you to provide a
|
|
28
30
|
new password with a confirmation then redirect you to your login page.
|
|
29
31
|
`
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { SEND_IN_BLUE_KEY, SEND_IN_BLUE_URL, SEND_IN_BLUE_FROM, SEND_IN_BLUE_ADMINS } from '$env/static/private'
|
|
2
|
-
|
|
3
|
-
const sender = <MessageAddressee> JSON.parse(SEND_IN_BLUE_FROM || '')
|
|
4
|
-
const to = <MessageAddressee> JSON.parse(SEND_IN_BLUE_ADMINS || '')
|
|
5
|
-
|
|
6
|
-
// POST or PUT submission to SendInBlue
|
|
7
|
-
const submit = async (method: string, url: string, data: Partial<SendInBlueContact> | SendInBlueMessage) => {
|
|
8
|
-
const response: Response = await fetch(`${SEND_IN_BLUE_URL}${url}`, <SendInBlueRequest> {
|
|
9
|
-
method,
|
|
10
|
-
headers: {
|
|
11
|
-
'Content-Type': 'application/json',
|
|
12
|
-
'api-key': SEND_IN_BLUE_KEY
|
|
13
|
-
},
|
|
14
|
-
body: JSON.stringify(data)
|
|
15
|
-
})
|
|
16
|
-
if (!response.ok) {
|
|
17
|
-
console.error('Error from SendInBlue:', response)
|
|
18
|
-
throw new Error(`Error communicating with SendInBlue.`)
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const sendMessage = async (message: Message) => submit('POST', '/v3/smtp/email', { sender, to: [to], ...message })
|