sveltekit-auth-example 5.5.0 → 5.6.1
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 +19 -1
- package/README.md +12 -12
- package/db_create.sh +13 -0
- package/db_create.sql +15 -376
- package/db_schema.sql +369 -0
- package/package.json +5 -2
- package/src/app.d.ts +22 -1
- package/src/hooks.server.ts +47 -12
- package/src/lib/app-state.svelte.ts +8 -0
- package/src/lib/auth-redirect.ts +4 -0
- package/src/lib/focus.ts +8 -0
- package/src/lib/google.ts +17 -0
- package/src/lib/server/db.ts +9 -0
- package/src/lib/server/email/index.ts +1 -0
- package/src/lib/server/email/mfa-code.ts +22 -0
- package/src/lib/server/email/password-reset.ts +6 -0
- package/src/lib/server/email/verify-email.ts +6 -0
- package/src/lib/server/sendgrid.ts +9 -0
- package/src/routes/+layout.server.ts +10 -1
- package/src/routes/+layout.svelte +103 -28
- package/src/routes/admin/+page.server.ts +8 -0
- package/src/routes/api/v1/user/+server.ts +20 -0
- package/src/routes/auth/[slug]/+server.ts +9 -2
- package/src/routes/auth/forgot/+server.ts +10 -0
- package/src/routes/auth/google/+server.ts +35 -4
- package/src/routes/auth/login/+server.ts +67 -10
- package/src/routes/auth/logout/+server.ts +10 -0
- package/src/routes/auth/mfa/+server.ts +75 -0
- package/src/routes/auth/register/+server.ts +21 -1
- package/src/routes/auth/reset/+server.ts +15 -0
- package/src/routes/auth/reset/[token]/+page.svelte +16 -8
- package/src/routes/auth/reset/[token]/+page.ts +8 -0
- package/src/routes/auth/verify/[token]/+server.ts +12 -1
- package/src/routes/forgot/+page.svelte +13 -8
- package/src/routes/layout.css +3 -3
- package/src/routes/login/+page.server.ts +8 -0
- package/src/routes/login/+page.svelte +222 -77
- package/src/routes/profile/+page.server.ts +9 -0
- package/src/routes/profile/+page.svelte +32 -12
- package/src/routes/register/+page.server.ts +8 -1
- package/src/routes/register/+page.svelte +160 -122
- package/src/routes/teachers/+page.server.ts +9 -0
- package/src/service-worker.ts +17 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,9 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
- Add password complexity checking on /register and /profile pages (only checks for length currently despite what the pages say)
|
|
4
4
|
|
|
5
|
+
# 5.6.1
|
|
6
|
+
|
|
7
|
+
- Add JSDoc comments throughout the codebase (`app.d.ts`, server hooks, route handlers, lib utilities, Svelte components)
|
|
8
|
+
|
|
9
|
+
# 5.6.0
|
|
10
|
+
|
|
11
|
+
- Split `db_create.sql` into `db_create.sql` (role + database creation) and `db_schema.sql` (schema, functions, seed data)
|
|
12
|
+
- Add `db_create.sh` shell wrapper to run both files in sequence
|
|
13
|
+
- `db_schema.sql` is pure SQL and fully prettier-formattable
|
|
14
|
+
- README updated to use `bash db_create.sh`
|
|
15
|
+
|
|
5
16
|
# 5.5.0
|
|
6
17
|
|
|
7
|
-
-
|
|
18
|
+
- Multi-factor authentication (MFA) via email for local accounts (Google Sign In is exempt)
|
|
19
|
+
- 6-digit OTP code sent via SendGrid, expires in 10 minutes
|
|
20
|
+
- 30-day trusted-device cookie (signed JWT) suppresses MFA on subsequent logins from the same device
|
|
21
|
+
- New `mfa_codes` table in PostgreSQL with `create_mfa_code()` and `verify_mfa_code()` functions
|
|
22
|
+
- New `/auth/mfa` endpoint verifies the code, creates the session, and issues the trusted-device cookie
|
|
23
|
+
- Login page shows an inline MFA step when a code is required
|
|
24
|
+
- `/auth/mfa` added to rate-limited paths
|
|
25
|
+
- Extract email templates into dedicated files under `src/lib/server/email/` (`password-reset.ts`, `verify-email.ts`, `mfa-code.ts`)
|
|
8
26
|
- `src/lib/server/email/index.ts` re-exports all templates for clean imports
|
|
9
27
|
|
|
10
28
|
# 5.1.3
|
package/README.md
CHANGED
|
@@ -8,14 +8,14 @@ A complete, production-ready authentication and authorization starter for **Svel
|
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
|
|
|
12
|
-
|
|
13
|
-
| ✅ Local accounts (email + password)
|
|
14
|
-
| ✅ Multi-factor authentication (MFA) | ✅ Email verification
|
|
15
|
-
| ✅ Forgot password / email reset (SendGrid)
|
|
16
|
-
| ✅ Session management + timeout
|
|
17
|
-
| ✅ Role-based access control
|
|
18
|
-
| ✅ Content Security Policy (CSP)
|
|
11
|
+
| | |
|
|
12
|
+
| ---------------------------------------------- | --------------------------------------- |
|
|
13
|
+
| ✅ Local accounts (email + password) | ✅ Sign in with Google / Google One Tap |
|
|
14
|
+
| ✅ Multi-factor authentication (MFA via email) | ✅ Email verification |
|
|
15
|
+
| ✅ Forgot password / email reset (SendGrid) | ✅ User profile management |
|
|
16
|
+
| ✅ Session management + timeout | ✅ Rate limiting |
|
|
17
|
+
| ✅ Role-based access control | ✅ Password complexity enforcement |
|
|
18
|
+
| ✅ Content Security Policy (CSP) | ✅ OWASP-compliant password hashing |
|
|
19
19
|
|
|
20
20
|
## Stack
|
|
21
21
|
|
|
@@ -44,7 +44,7 @@ cd sveltekit-auth-example
|
|
|
44
44
|
yarn install
|
|
45
45
|
|
|
46
46
|
# Create PostgreSQL database (only works if you have PostgreSQL installed)
|
|
47
|
-
|
|
47
|
+
bash db_create.sh
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
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` and `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.
|
|
@@ -84,9 +84,9 @@ yarn preview
|
|
|
84
84
|
|
|
85
85
|
The db_create.sql script adds three users to the database with obvious roles:
|
|
86
86
|
|
|
87
|
-
| Email
|
|
88
|
-
|
|
89
|
-
| admin@example.com
|
|
87
|
+
| Email | Password | Role |
|
|
88
|
+
| ------------------- | ---------- | ------- |
|
|
89
|
+
| admin@example.com | admin123 | admin |
|
|
90
90
|
| teacher@example.com | teacher123 | teacher |
|
|
91
91
|
| student@example.com | student123 | student |
|
|
92
92
|
|
package/db_create.sh
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Creates the auth role and database, then applies the schema.
|
|
5
|
+
# Usage: bash db_create.sh
|
|
6
|
+
#
|
|
7
|
+
# Optionally override the superuser with PGUSER env var:
|
|
8
|
+
# PGUSER=myuser bash db_create.sh
|
|
9
|
+
|
|
10
|
+
psql -d postgres -f db_create.sql
|
|
11
|
+
psql -d auth -f db_schema.sql
|
|
12
|
+
|
|
13
|
+
echo "Database created successfully."
|
package/db_create.sql
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
--
|
|
2
|
-
-- $ psql -d postgres -f db_create.sql
|
|
1
|
+
-- Run via db_create.sh or:
|
|
2
|
+
-- $ psql -d postgres -f db_create.sql && psql -d auth -f db_schema.sql
|
|
3
3
|
|
|
4
4
|
-- Create role if not already there
|
|
5
|
-
DO
|
|
6
|
-
$do$
|
|
5
|
+
DO $do$
|
|
7
6
|
BEGIN
|
|
8
7
|
IF NOT EXISTS (
|
|
9
8
|
SELECT -- SELECT list can stay empty for this
|
|
@@ -16,381 +15,21 @@ END
|
|
|
16
15
|
$do$;
|
|
17
16
|
|
|
18
17
|
-- Forcefully disconnect anyone
|
|
19
|
-
SELECT
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
SELECT
|
|
19
|
+
pid,
|
|
20
|
+
pg_terminate_backend(pid)
|
|
21
|
+
FROM
|
|
22
|
+
pg_stat_activity
|
|
23
|
+
WHERE
|
|
24
|
+
datname = 'auth'
|
|
25
|
+
AND pid <> pg_backend_pid();
|
|
22
26
|
|
|
23
27
|
DROP DATABASE IF EXISTS auth;
|
|
24
28
|
|
|
25
29
|
CREATE DATABASE auth
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
WITH
|
|
31
|
+
OWNER = auth ENCODING = 'UTF8' CONNECTION
|
|
32
|
+
LIMIT
|
|
33
|
+
= -1;
|
|
30
34
|
|
|
31
35
|
COMMENT ON DATABASE auth IS 'SvelteKit Auth Example';
|
|
32
|
-
|
|
33
|
-
-- Connect to auth database
|
|
34
|
-
\connect auth
|
|
35
|
-
|
|
36
|
-
-- Required for password hashing
|
|
37
|
-
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
38
|
-
|
|
39
|
-
-- Required to generate UUIDs for sessions
|
|
40
|
-
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
41
|
-
|
|
42
|
-
-- Required for case-insensitive text (email_address domain)
|
|
43
|
-
CREATE EXTENSION IF NOT EXISTS citext;
|
|
44
|
-
|
|
45
|
-
-- Using hard-coded roles (often this would be a table)
|
|
46
|
-
CREATE TYPE public.roles AS ENUM
|
|
47
|
-
('student', 'teacher', 'admin');
|
|
48
|
-
|
|
49
|
-
ALTER TYPE public.roles OWNER TO auth;
|
|
50
|
-
|
|
51
|
-
-- Domains
|
|
52
|
-
CREATE DOMAIN public.email_address AS citext CHECK (
|
|
53
|
-
length(VALUE) <= 254
|
|
54
|
-
AND VALUE = btrim(VALUE)
|
|
55
|
-
AND VALUE ~* '^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$'
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
COMMENT ON DOMAIN public.email_address IS 'RFC-compliant email address (case-insensitive, max 254 chars)';
|
|
59
|
-
|
|
60
|
-
CREATE DOMAIN public.persons_name AS text CHECK (length(VALUE) <= 20) NOT NULL;
|
|
61
|
-
|
|
62
|
-
COMMENT ON DOMAIN public.persons_name IS 'Person first or last name (max 20 characters)';
|
|
63
|
-
|
|
64
|
-
CREATE DOMAIN public.phone_number AS text CHECK (
|
|
65
|
-
VALUE IS NULL
|
|
66
|
-
OR length(VALUE) <= 50
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
COMMENT ON DOMAIN public.phone_number IS 'Phone number (max 50 characters)';
|
|
70
|
-
|
|
71
|
-
CREATE TABLE IF NOT EXISTS public.users (
|
|
72
|
-
id integer NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
|
|
73
|
-
role roles NOT NULL DEFAULT 'student'::roles,
|
|
74
|
-
email email_address NOT NULL,
|
|
75
|
-
password character varying(80) COLLATE pg_catalog."default",
|
|
76
|
-
first_name persons_name,
|
|
77
|
-
last_name persons_name,
|
|
78
|
-
opt_out boolean NOT NULL DEFAULT false,
|
|
79
|
-
email_verified boolean NOT NULL DEFAULT false,
|
|
80
|
-
phone phone_number,
|
|
81
|
-
CONSTRAINT users_pkey PRIMARY KEY (id),
|
|
82
|
-
CONSTRAINT users_email_unique UNIQUE (email)
|
|
83
|
-
) TABLESPACE pg_default;
|
|
84
|
-
|
|
85
|
-
ALTER TABLE public.users OWNER to auth;
|
|
86
|
-
|
|
87
|
-
CREATE INDEX users_first_name_index
|
|
88
|
-
ON public.users USING btree
|
|
89
|
-
(first_name COLLATE pg_catalog."default" ASC NULLS LAST)
|
|
90
|
-
TABLESPACE pg_default;
|
|
91
|
-
|
|
92
|
-
CREATE INDEX users_last_name_index
|
|
93
|
-
ON public.users USING btree
|
|
94
|
-
(last_name COLLATE pg_catalog."default" ASC NULLS LAST)
|
|
95
|
-
TABLESPACE pg_default;
|
|
96
|
-
|
|
97
|
-
CREATE INDEX users_password
|
|
98
|
-
ON public.users USING btree
|
|
99
|
-
(password COLLATE pg_catalog."default" ASC NULLS LAST)
|
|
100
|
-
TABLESPACE pg_default;
|
|
101
|
-
|
|
102
|
-
CREATE TABLE IF NOT EXISTS public.sessions (
|
|
103
|
-
id uuid NOT NULL DEFAULT uuid_generate_v4(),
|
|
104
|
-
user_id integer NOT NULL,
|
|
105
|
-
expires timestamptz NOT NULL DEFAULT (CURRENT_TIMESTAMP + INTERVAL '2 hours'),
|
|
106
|
-
CONSTRAINT sessions_pkey PRIMARY KEY (id),
|
|
107
|
-
CONSTRAINT sessions_user_fkey FOREIGN KEY (user_id)
|
|
108
|
-
REFERENCES public.users (id) MATCH SIMPLE
|
|
109
|
-
ON UPDATE CASCADE
|
|
110
|
-
ON DELETE CASCADE,
|
|
111
|
-
CONSTRAINT sessions_one_per_user UNIQUE (user_id)
|
|
112
|
-
) TABLESPACE pg_default;
|
|
113
|
-
|
|
114
|
-
ALTER TABLE public.sessions OWNER to auth;
|
|
115
|
-
|
|
116
|
-
CREATE OR REPLACE FUNCTION public.authenticate(
|
|
117
|
-
input json,
|
|
118
|
-
OUT response json)
|
|
119
|
-
RETURNS json
|
|
120
|
-
LANGUAGE 'plpgsql'
|
|
121
|
-
COST 100
|
|
122
|
-
VOLATILE PARALLEL UNSAFE
|
|
123
|
-
AS $BODY$
|
|
124
|
-
DECLARE
|
|
125
|
-
input_email text := trim(input->>'email');
|
|
126
|
-
input_password text := input->>'password';
|
|
127
|
-
v_user users%ROWTYPE;
|
|
128
|
-
BEGIN
|
|
129
|
-
IF input_email IS NULL OR input_password IS NULL THEN
|
|
130
|
-
response := json_build_object('statusCode', 400, 'status', 'Please provide an email address and password to authenticate.', 'user', NULL, 'sessionId', NULL);
|
|
131
|
-
RETURN;
|
|
132
|
-
END IF;
|
|
133
|
-
|
|
134
|
-
SELECT * INTO v_user FROM users
|
|
135
|
-
WHERE email = input_email AND password = crypt(input_password, password) LIMIT 1;
|
|
136
|
-
|
|
137
|
-
IF NOT FOUND THEN
|
|
138
|
-
response := json_build_object('statusCode', 401, 'status', 'Invalid username/password combination.', 'user', NULL, 'sessionId', NULL);
|
|
139
|
-
ELSIF NOT v_user.email_verified THEN
|
|
140
|
-
response := json_build_object('statusCode', 403, 'status', 'Please verify your email address before logging in.', 'user', NULL, 'sessionId', NULL);
|
|
141
|
-
ELSE
|
|
142
|
-
response := json_build_object(
|
|
143
|
-
'statusCode', 200,
|
|
144
|
-
'status', 'Login successful.',
|
|
145
|
-
'user', json_build_object('id', v_user.id, 'role', v_user.role, 'email', input_email, 'firstName', v_user.first_name, 'lastName', v_user.last_name, 'phone', v_user.phone, 'optOut', v_user.opt_out),
|
|
146
|
-
'sessionId', create_session(v_user.id)
|
|
147
|
-
);
|
|
148
|
-
END IF;
|
|
149
|
-
END;
|
|
150
|
-
$BODY$;
|
|
151
|
-
|
|
152
|
-
ALTER FUNCTION public.authenticate(json) OWNER TO auth;
|
|
153
|
-
|
|
154
|
-
CREATE OR REPLACE FUNCTION public.create_session(
|
|
155
|
-
input_user_id integer)
|
|
156
|
-
RETURNS uuid
|
|
157
|
-
LANGUAGE 'sql'
|
|
158
|
-
COST 100
|
|
159
|
-
VOLATILE PARALLEL UNSAFE
|
|
160
|
-
AS $BODY$
|
|
161
|
-
-- Remove expired sessions (index-friendly cleanup)
|
|
162
|
-
DELETE FROM sessions WHERE expires < CURRENT_TIMESTAMP;
|
|
163
|
-
-- Remove any existing session(s) for this user
|
|
164
|
-
DELETE FROM sessions WHERE user_id = input_user_id;
|
|
165
|
-
-- Create the new session
|
|
166
|
-
INSERT INTO sessions(user_id) VALUES (input_user_id) RETURNING sessions.id;
|
|
167
|
-
$BODY$;
|
|
168
|
-
|
|
169
|
-
ALTER FUNCTION public.create_session(integer) OWNER TO auth;
|
|
170
|
-
|
|
171
|
-
CREATE OR REPLACE FUNCTION public.get_session(input_session_id uuid)
|
|
172
|
-
RETURNS json
|
|
173
|
-
LANGUAGE 'sql'
|
|
174
|
-
AS $BODY$
|
|
175
|
-
SELECT json_build_object(
|
|
176
|
-
'id', sessions.user_id,
|
|
177
|
-
'role', users.role,
|
|
178
|
-
'email', users.email,
|
|
179
|
-
'firstName', users.first_name,
|
|
180
|
-
'lastName', users.last_name,
|
|
181
|
-
'phone', users.phone,
|
|
182
|
-
'optOut', users.opt_out,
|
|
183
|
-
'expires', sessions.expires
|
|
184
|
-
) AS user
|
|
185
|
-
FROM sessions
|
|
186
|
-
INNER JOIN users ON sessions.user_id = users.id
|
|
187
|
-
WHERE sessions.id = input_session_id AND expires > CURRENT_TIMESTAMP LIMIT 1;
|
|
188
|
-
$BODY$;
|
|
189
|
-
|
|
190
|
-
ALTER FUNCTION public.get_session(uuid) OWNER TO auth;
|
|
191
|
-
|
|
192
|
-
-- Like get_session but also bumps the expiry (sliding sessions).
|
|
193
|
-
-- Returns NULL if the session is expired or does not exist.
|
|
194
|
-
CREATE OR REPLACE FUNCTION public.get_and_update_session(input_session_id uuid)
|
|
195
|
-
RETURNS json
|
|
196
|
-
LANGUAGE 'plpgsql'
|
|
197
|
-
AS $BODY$
|
|
198
|
-
DECLARE
|
|
199
|
-
result json;
|
|
200
|
-
BEGIN
|
|
201
|
-
UPDATE sessions
|
|
202
|
-
SET expires = CURRENT_TIMESTAMP + INTERVAL '2 hours'
|
|
203
|
-
WHERE id = input_session_id AND expires > CURRENT_TIMESTAMP;
|
|
204
|
-
|
|
205
|
-
IF NOT FOUND THEN
|
|
206
|
-
RETURN NULL;
|
|
207
|
-
END IF;
|
|
208
|
-
|
|
209
|
-
SELECT json_build_object(
|
|
210
|
-
'id', sessions.user_id,
|
|
211
|
-
'role', users.role,
|
|
212
|
-
'email', users.email,
|
|
213
|
-
'firstName', users.first_name,
|
|
214
|
-
'lastName', users.last_name,
|
|
215
|
-
'phone', users.phone,
|
|
216
|
-
'optOut', users.opt_out,
|
|
217
|
-
'expires', sessions.expires
|
|
218
|
-
) INTO result
|
|
219
|
-
FROM sessions
|
|
220
|
-
INNER JOIN users ON sessions.user_id = users.id
|
|
221
|
-
WHERE sessions.id = input_session_id;
|
|
222
|
-
|
|
223
|
-
RETURN result;
|
|
224
|
-
END;
|
|
225
|
-
$BODY$;
|
|
226
|
-
|
|
227
|
-
ALTER FUNCTION public.get_and_update_session(uuid) OWNER TO auth;
|
|
228
|
-
|
|
229
|
-
CREATE OR REPLACE FUNCTION public.verify_email_and_create_session(input_id integer)
|
|
230
|
-
RETURNS uuid
|
|
231
|
-
LANGUAGE 'plpgsql'
|
|
232
|
-
COST 100
|
|
233
|
-
VOLATILE PARALLEL UNSAFE
|
|
234
|
-
AS $BODY$
|
|
235
|
-
DECLARE
|
|
236
|
-
session_id uuid;
|
|
237
|
-
BEGIN
|
|
238
|
-
UPDATE users SET email_verified = true WHERE id = input_id;
|
|
239
|
-
SELECT create_session(input_id) INTO session_id;
|
|
240
|
-
RETURN session_id;
|
|
241
|
-
END;
|
|
242
|
-
$BODY$;
|
|
243
|
-
|
|
244
|
-
ALTER FUNCTION public.verify_email_and_create_session(integer) OWNER TO auth;
|
|
245
|
-
|
|
246
|
-
CREATE OR REPLACE FUNCTION public.register(
|
|
247
|
-
input json,
|
|
248
|
-
OUT user_session json)
|
|
249
|
-
RETURNS json
|
|
250
|
-
LANGUAGE 'plpgsql'
|
|
251
|
-
COST 100
|
|
252
|
-
VOLATILE PARALLEL UNSAFE
|
|
253
|
-
AS $BODY$
|
|
254
|
-
DECLARE
|
|
255
|
-
input_email text := trim(input->>'email');
|
|
256
|
-
input_first_name text := trim(input->>'firstName');
|
|
257
|
-
input_last_name text := trim(input->>'lastName');
|
|
258
|
-
input_phone text := trim(input->>'phone');
|
|
259
|
-
input_password text := input->>'password';
|
|
260
|
-
BEGIN
|
|
261
|
-
PERFORM id FROM users WHERE email = input_email;
|
|
262
|
-
IF NOT FOUND THEN
|
|
263
|
-
INSERT INTO users(role, password, email, first_name, last_name, phone)
|
|
264
|
-
VALUES('student', crypt(input_password, gen_salt('bf', 12)), input_email, input_first_name, input_last_name, input_phone)
|
|
265
|
-
RETURNING
|
|
266
|
-
json_build_object(
|
|
267
|
-
'sessionId', create_session(users.id),
|
|
268
|
-
'user', json_build_object('id', users.id, 'role', 'student', 'email', input_email, 'firstName', input_first_name, 'lastName', input_last_name, 'phone', input_phone, 'optOut', users.opt_out)
|
|
269
|
-
) INTO user_session;
|
|
270
|
-
ELSE -- user is registering account that already exists so set sessionId and user to null so client can let them know
|
|
271
|
-
SELECT authenticate(input) INTO user_session;
|
|
272
|
-
END IF;
|
|
273
|
-
END;
|
|
274
|
-
$BODY$;
|
|
275
|
-
|
|
276
|
-
ALTER FUNCTION public.register(json) OWNER TO auth;
|
|
277
|
-
|
|
278
|
-
CREATE OR REPLACE FUNCTION public.start_gmail_user_session(
|
|
279
|
-
input json,
|
|
280
|
-
OUT user_session json)
|
|
281
|
-
RETURNS json
|
|
282
|
-
LANGUAGE 'plpgsql'
|
|
283
|
-
COST 100
|
|
284
|
-
VOLATILE PARALLEL UNSAFE
|
|
285
|
-
AS $BODY$
|
|
286
|
-
DECLARE
|
|
287
|
-
input_email varchar(80) := LOWER(TRIM((input->>'email')::varchar));
|
|
288
|
-
input_first_name varchar(20) := TRIM((input->>'firstName')::varchar);
|
|
289
|
-
input_last_name varchar(20) := TRIM((input->>'lastName')::varchar);
|
|
290
|
-
BEGIN
|
|
291
|
-
-- Google verifies email ownership; mark user as verified on every sign-in
|
|
292
|
-
UPDATE users SET email_verified = true WHERE email = input_email;
|
|
293
|
-
SELECT json_build_object('id', create_session(users.id), 'user', json_build_object('id', users.id, 'role', users.role, 'email', input_email, 'firstName', users.first_name, 'lastName', users.last_name, 'phone', users.phone)) INTO user_session FROM users WHERE email = input_email;
|
|
294
|
-
IF NOT FOUND THEN
|
|
295
|
-
INSERT INTO users(role, email, first_name, last_name, email_verified)
|
|
296
|
-
VALUES('student', input_email, input_first_name, input_last_name, true)
|
|
297
|
-
RETURNING
|
|
298
|
-
json_build_object(
|
|
299
|
-
'id', create_session(users.id),
|
|
300
|
-
'user', json_build_object('id', users.id, 'role', 'student', 'email', input_email, 'firstName', input_first_name, 'lastName', input_last_name, 'phone', null)
|
|
301
|
-
) INTO user_session;
|
|
302
|
-
END IF;
|
|
303
|
-
END;
|
|
304
|
-
$BODY$;
|
|
305
|
-
|
|
306
|
-
ALTER FUNCTION public.start_gmail_user_session(json) OWNER TO auth;
|
|
307
|
-
|
|
308
|
-
CREATE PROCEDURE public.delete_session(input_id integer)
|
|
309
|
-
LANGUAGE sql
|
|
310
|
-
AS $$
|
|
311
|
-
DELETE FROM sessions WHERE user_id = input_id;
|
|
312
|
-
$$;
|
|
313
|
-
|
|
314
|
-
CREATE OR REPLACE PROCEDURE public.delete_user(input_id integer)
|
|
315
|
-
LANGUAGE sql
|
|
316
|
-
AS $$
|
|
317
|
-
DELETE FROM users WHERE id = input_id;
|
|
318
|
-
$$;
|
|
319
|
-
|
|
320
|
-
ALTER PROCEDURE public.delete_user(integer) OWNER TO auth;
|
|
321
|
-
|
|
322
|
-
CREATE OR REPLACE PROCEDURE public.reset_password(IN input_id integer, IN input_password text)
|
|
323
|
-
LANGUAGE plpgsql
|
|
324
|
-
AS $procedure$
|
|
325
|
-
BEGIN
|
|
326
|
-
UPDATE users SET password = crypt(input_password, gen_salt('bf', 12)) WHERE id = input_id;
|
|
327
|
-
END;
|
|
328
|
-
$procedure$
|
|
329
|
-
;
|
|
330
|
-
|
|
331
|
-
ALTER PROCEDURE public.reset_password(integer, text) OWNER TO auth;
|
|
332
|
-
|
|
333
|
-
CREATE OR REPLACE PROCEDURE public.upsert_user(input json)
|
|
334
|
-
LANGUAGE plpgsql
|
|
335
|
-
AS $BODY$
|
|
336
|
-
DECLARE
|
|
337
|
-
input_id integer := COALESCE((input->>'id')::integer,0);
|
|
338
|
-
input_role roles := COALESCE((input->>'role')::roles, 'student');
|
|
339
|
-
input_email varchar(80) := LOWER(TRIM((input->>'email')::varchar));
|
|
340
|
-
input_password varchar(80) := COALESCE((input->>'password')::varchar, '');
|
|
341
|
-
input_first_name varchar(20) := TRIM((input->>'firstName')::varchar);
|
|
342
|
-
input_last_name varchar(20) := TRIM((input->>'lastName')::varchar);
|
|
343
|
-
input_phone varchar(23) := TRIM((input->>'phone')::varchar);
|
|
344
|
-
BEGIN
|
|
345
|
-
IF input_id = 0 THEN
|
|
346
|
-
INSERT INTO users (role, email, password, first_name, last_name, phone, email_verified)
|
|
347
|
-
VALUES (
|
|
348
|
-
input_role, input_email, crypt(input_password, gen_salt('bf', 12)),
|
|
349
|
-
input_first_name, input_last_name, input_phone, true);
|
|
350
|
-
ELSE
|
|
351
|
-
UPDATE users SET
|
|
352
|
-
role = input_role,
|
|
353
|
-
email = input_email,
|
|
354
|
-
email_verified = true,
|
|
355
|
-
password = CASE WHEN input_password = ''
|
|
356
|
-
THEN password -- leave as is (we are updating fields other than the password)
|
|
357
|
-
ELSE crypt(input_password, gen_salt('bf', 12))
|
|
358
|
-
END,
|
|
359
|
-
first_name = input_first_name,
|
|
360
|
-
last_name = input_last_name,
|
|
361
|
-
phone = input_phone
|
|
362
|
-
WHERE id = input_id;
|
|
363
|
-
END IF;
|
|
364
|
-
END;
|
|
365
|
-
$BODY$;
|
|
366
|
-
|
|
367
|
-
ALTER PROCEDURE public.upsert_user(json) OWNER TO auth;
|
|
368
|
-
|
|
369
|
-
CREATE OR REPLACE PROCEDURE public.update_user(input_id integer, input json)
|
|
370
|
-
LANGUAGE plpgsql
|
|
371
|
-
AS $BODY$
|
|
372
|
-
DECLARE
|
|
373
|
-
input_email varchar(80) := LOWER(TRIM((input->>'email')::varchar));
|
|
374
|
-
input_password varchar(80) := COALESCE((input->>'password')::varchar, '');
|
|
375
|
-
input_first_name varchar(20) := TRIM((input->>'firstName')::varchar);
|
|
376
|
-
input_last_name varchar(20) := TRIM((input->>'lastName')::varchar);
|
|
377
|
-
input_phone varchar(23) := TRIM((input->>'phone')::varchar);
|
|
378
|
-
BEGIN
|
|
379
|
-
UPDATE users SET
|
|
380
|
-
email = input_email,
|
|
381
|
-
password = CASE WHEN input_password = ''
|
|
382
|
-
THEN password -- leave as is (we are updating fields other than the password)
|
|
383
|
-
ELSE crypt(input_password, gen_salt('bf', 12))
|
|
384
|
-
END,
|
|
385
|
-
first_name = input_first_name,
|
|
386
|
-
last_name = input_last_name,
|
|
387
|
-
phone = input_phone
|
|
388
|
-
WHERE id = input_id;
|
|
389
|
-
END;
|
|
390
|
-
$BODY$;
|
|
391
|
-
|
|
392
|
-
ALTER PROCEDURE public.update_user(integer, json) OWNER TO auth;
|
|
393
|
-
|
|
394
|
-
CALL public.upsert_user('{"id":0, "role":"admin", "email":"admin@example.com", "password":"admin123", "firstName":"Jane", "lastName":"Doe", "phone":"412-555-1212"}'::json);
|
|
395
|
-
CALL public.upsert_user('{"id":0, "role":"teacher", "email":"teacher@example.com", "password":"teacher123", "firstName":"John", "lastName":"Doe", "phone":"724-555-1212"}'::json);
|
|
396
|
-
CALL public.upsert_user('{"id":0, "role":"student", "email":"student@example.com", "password":"student123", "firstName":"Justin", "lastName":"Case", "phone":"814-555-1212"}'::json);
|