Skip to content
This repository was archived by the owner on Jan 1, 2026. It is now read-only.

Commit be56e1d

Browse files
authored
Improvements to the authentication and registration system (#6)
* Refactor register_user procedure and update schema for username validation * Change parameter types from VARCHAR to TEXT in authenticate_user and register_user functions * Fix parameter order in register_user procedure for consistency * Add is_user_available function to check user availability by username, email, or phone * Update register_user procedure to use language_id directly and enhance schema constraints for user and language tables * Refactor register_user procedure to remove exception handling and simplify user insertion logic; delete obsolete authentication test file. * Enhance task configuration by adding Docker-related tasks for building, running, and managing Docker containers; improve formatting for better readability. * Update password_hash constraint to enforce Argon2id hash format for improved security * Refactor is_user_available function to is_email_available for clarity; update users table to enforce discriminator constraint and add pending_users table for email verification * Reintroduce is_email_available function for email availability checks in users table
1 parent f80724e commit be56e1d

File tree

7 files changed

+133
-93
lines changed

7 files changed

+133
-93
lines changed

.vscode/tasks.json

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
"label": "Flatten SQL Files",
1919
"type": "shell",
2020
"command": "./scripts/flatten-sql.sh",
21-
"dependsOn": ["Start Database"],
21+
"dependsOn": [
22+
"Start Database"
23+
],
2224
"options": {
2325
"cwd": "${workspaceFolder}"
2426
},
@@ -28,8 +30,13 @@
2830
"label": "Rebuild Database",
2931
"type": "shell",
3032
"command": "./scripts/rebuild-db.sh",
31-
"args": ["${input:db_username}", "${input:db_name}"],
32-
"dependsOn": ["Flatten SQL Files"],
33+
"args": [
34+
"${input:db_username}",
35+
"${input:db_name}"
36+
],
37+
"dependsOn": [
38+
"Flatten SQL Files"
39+
],
3340
"options": {
3441
"cwd": "${workspaceFolder}"
3542
},
@@ -81,7 +88,9 @@
8188
"label": "Run Unit Tests",
8289
"type": "shell",
8390
"command": "pg_prove -Q -d ${input:db_name} -U ${input:db_username} database/tests/*.test.sql",
84-
"dependsOn": ["Full Database Rebuild"],
91+
"dependsOn": [
92+
"Full Database Rebuild"
93+
],
8594
"problemMatcher": [],
8695
"group": {
8796
"kind": "test",
@@ -103,6 +112,55 @@
103112
"runOptions": {
104113
"runOn": "default"
105114
}
115+
},
116+
{
117+
"label": "Build Docker Image",
118+
"type": "shell",
119+
"command": "docker build -t ${input:docker_image_name} .",
120+
"group": {
121+
"kind": "build",
122+
"isDefault": false
123+
},
124+
"presentation": {
125+
"panel": "shared",
126+
"close": true
127+
},
128+
"problemMatcher": [],
129+
},
130+
{
131+
"label": "Run Docker Container",
132+
"type": "shell",
133+
"command": "docker run -d -p 5432:5432 --name ${input:docker_container_name} --env-file .env --security-opt no-new-privileges:true --cap-drop=ALL --cap-add=NET_BIND_SERVICE ${input:docker_image_name}",
134+
"presentation": {
135+
"panel": "shared",
136+
"close": true
137+
},
138+
"problemMatcher": [],
139+
},
140+
{
141+
"label": "Remove Docker Container",
142+
"type": "shell",
143+
"command": "docker stop ${input:docker_container_name} ; docker rm ${input:docker_container_name}",
144+
"presentation": {
145+
"panel": "shared",
146+
"close": true
147+
},
148+
"problemMatcher": [],
149+
},
150+
{
151+
"label": "Reset Docker Container",
152+
"type": "shell",
153+
"dependsOn": [
154+
"Remove Docker Container",
155+
"Build Docker Image",
156+
"Run Docker Container"
157+
],
158+
"presentation": {
159+
"panel": "shared",
160+
"close": true
161+
},
162+
"dependsOrder": "sequence",
163+
"problemMatcher": []
106164
}
107165
],
108166
"inputs": [
@@ -117,6 +175,18 @@
117175
"type": "promptString",
118176
"description": "Enter the database name",
119177
"default": "chocomax"
178+
},
179+
{
180+
"id": "docker_image_name",
181+
"type": "promptString",
182+
"description": "Enter the Docker image name",
183+
"default": "chocomax-database-image"
184+
},
185+
{
186+
"id": "docker_container_name",
187+
"type": "promptString",
188+
"description": "Enter the Docker container name",
189+
"default": "chocomax-database-container"
120190
}
121191
],
122192
"options": {

database/functions/authenticate_user.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
-- Authenticate a user and log the result
22
CREATE OR REPLACE FUNCTION authenticate_user(
3-
p_username VARCHAR,
4-
p_password_hash VARCHAR,
3+
p_username TEXT,
4+
p_password_hash TEXT,
55
p_ip_address INET,
66
p_user_agent TEXT
77
) RETURNS BOOLEAN AS $$
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CREATE OR REPLACE FUNCTION is_email_available(
2+
p_email_hash TEXT
3+
)
4+
RETURNS BOOLEAN AS $$
5+
SELECT NOT EXISTS (
6+
SELECT 1 FROM users WHERE email_hash = p_email_hash
7+
);
8+
$$ LANGUAGE sql;
Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,32 @@
11
-- Register a new user
22
CREATE OR REPLACE PROCEDURE register_user(
3+
p_username TEXT,
34
p_email_encrypted TEXT,
45
p_email_hash TEXT,
5-
p_username VARCHAR,
6-
p_password_hash VARCHAR,
6+
p_password_hash TEXT,
77
p_phone_encrypted TEXT,
88
p_phone_hash TEXT,
9-
p_preferred_language_iso_code CHAR(2)
9+
p_language_id INTEGER
1010
)
1111
LANGUAGE plpgsql AS $$
1212
BEGIN
13-
BEGIN
14-
INSERT INTO users (
15-
email_encrypted,
16-
email_hash,
17-
username,
18-
password_hash,
19-
phone_encrypted,
20-
phone_hash,
21-
language_id
22-
)
23-
VALUES (
24-
p_email_encrypted,
25-
p_email_hash,
26-
p_username,
27-
p_password_hash,
28-
p_phone_encrypted,
29-
p_phone_hash,
30-
(SELECT language_id FROM languages WHERE iso_code = p_preferred_language_iso_code)
31-
);
32-
EXCEPTION
33-
WHEN unique_violation THEN
34-
-- Identify the constraint that caused the error
35-
IF SQLERRM LIKE '%username%' THEN
36-
RAISE EXCEPTION 'Username % already exists', p_username;
37-
ELSIF SQLERRM LIKE '%email_hash%' THEN
38-
RAISE EXCEPTION 'Email address already exists';
39-
ELSIF SQLERRM LIKE '%phone_hash%' THEN
40-
RAISE EXCEPTION 'Phone number already exists';
41-
ELSE
42-
RAISE;
43-
END IF;
44-
END;
13+
INSERT INTO users (
14+
username,
15+
email_encrypted,
16+
email_hash,
17+
password_hash,
18+
phone_encrypted,
19+
phone_hash,
20+
language_id
21+
)
22+
VALUES (
23+
p_username,
24+
p_email_encrypted,
25+
p_email_hash,
26+
p_password_hash,
27+
p_phone_encrypted,
28+
p_phone_hash,
29+
p_language_id
30+
);
4531
END;
4632
$$;

database/schema.sql

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,26 +38,37 @@ CREATE TYPE admin_action_target_type AS ENUM ('user', 'product', 'comment', 'ord
3838
CREATE TABLE languages (
3939
language_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
4040
iso_code CHAR(2) UNIQUE,
41-
english_name VARCHAR(30) UNIQUE,
42-
native_name VARCHAR(30) UNIQUE
41+
english_name TEXT UNIQUE CHECK (english_name ~ '^[A-Za-z ]+$'),
42+
native_name TEXT UNIQUE
4343
);
4444

4545
-- User & Security Tables
4646

4747
CREATE TABLE users (
4848
user_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
49-
username VARCHAR(50) UNIQUE NOT NULL,
50-
email_encrypted TEXT NOT NULL,
51-
email_hash TEXT UNIQUE NOT NULL,
49+
username TEXT NOT NULL CHECK (username ~ '^[a-zA-Z0-9_]+$'), -- username must be alphanumeric and can include underscores
50+
discriminator SMALLINT NOT NULL CHECK (discriminator >= 0 AND discriminator <= 9999), -- 4-digit tag
51+
email_encrypted TEXT NOT NULL CHECK (email_encrypted <> ''), -- Encrypted email must not be empty
52+
email_hash TEXT UNIQUE NOT NULL CHECK (email_hash ~ '^[a-f0-9]{64}$'), -- SHA-256 hash of email
5253
is_email_verified BOOLEAN DEFAULT FALSE,
53-
password_hash TEXT NOT NULL,
54-
phone_encrypted TEXT,
55-
phone_hash TEXT UNIQUE,
54+
password_hash TEXT NOT NULL CHECK (password_hash ~ '^\$argon2id\$v=\d+\$m=\d+,t=\d+,p=\d+\$[a-zA-Z0-9+\/=]+\$[a-zA-Z0-9+\/=]+$'), -- Argon2id hash format
55+
phone_encrypted TEXT CHECK (phone_encrypted IS NULL OR phone_encrypted <> ''), -- Encrypted phone can be NULL or cannot be empty
56+
phone_hash TEXT UNIQUE CHECK (phone_hash ~ '^[a-f0-9]{64}$'), -- SHA-256 hash of phone
5657
language_id INTEGER REFERENCES languages (language_id) ON DELETE SET NULL,
58+
display_role TEXT, -- For badges/icons like "owner", "verified seller", etc.
5759
created_at TIMESTAMPTZ DEFAULT current_timestamp,
5860
updated_at TIMESTAMPTZ DEFAULT current_timestamp,
59-
last_login_at TIMESTAMPTZ,
60-
deleted_at TIMESTAMPTZ
61+
last_login_at TIMESTAMPTZ CHECK (last_login_at <= current_timestamp),
62+
deleted_at TIMESTAMPTZ CHECK (deleted_at <= current_timestamp),
63+
UNIQUE (username, discriminator)
64+
);
65+
66+
CREATE TABLE pending_users (
67+
pending_user_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
68+
email_encrypted TEXT NOT NULL CHECK (email_encrypted <> ''), -- Encrypted email must not be empty
69+
email_hash TEXT NOT NULL CHECK (email_hash ~ '^[a-f0-9]{64}$'), -- SHA-256 hash of email
70+
verification_token TEXT NOT NULL UNIQUE CHECK (verification_token ~ '^[a-zA-Z0-9]{32}$'), -- 32-character alphanumeric token
71+
created_at TIMESTAMPTZ DEFAULT current_timestamp
6172
);
6273

6374
CREATE TABLE user_permissions (
@@ -310,7 +321,7 @@ CREATE TABLE user_loyalty_progress (
310321

311322
CREATE TABLE translations (
312323
translation_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
313-
translation_key VARCHAR(100) NOT NULL,
324+
translation_key TEXT NOT NULL UNIQUE CHECK (translation_key ~ '^[a-z0-9_.]+$'), -- Key must be lowercase alphanumeric with underscores and dots
314325
language_id INTEGER NOT NULL REFERENCES languages (language_id) ON DELETE CASCADE,
315326
translation_value TEXT NOT NULL,
316327
created_at TIMESTAMPTZ DEFAULT current_timestamp,

database/tests/authentication.test.sql

Lines changed: 0 additions & 41 deletions
This file was deleted.

database/tests/seed_data.test.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ SELECT is(
88
(
99
SELECT count(*)::INT FROM languages
1010
WHERE iso_code = 'fr'
11+
AND english_name = 'French'
12+
AND native_name = 'Français'
1113
),
1214
1, 'Language with ISO code "fr" exists'
1315
);
@@ -16,6 +18,8 @@ SELECT is(
1618
(
1719
SELECT count(*)::INT FROM languages
1820
WHERE iso_code = 'en'
21+
AND english_name = 'English'
22+
AND native_name = 'English'
1923
),
2024
1, 'Language with ISO code "en" exists'
2125
);
@@ -24,6 +28,8 @@ SELECT is(
2428
(
2529
SELECT count(*)::INT FROM languages
2630
WHERE iso_code = 'es'
31+
AND english_name = 'Spanish'
32+
AND native_name = 'Español'
2733
),
2834
1, 'Language with ISO code "es" exists'
2935
);

0 commit comments

Comments
 (0)