Skip to content

Commit 71647f3

Browse files
committed
feat(auth): Implement email and GitHub authentication routes with session management
- Added new authentication routes for email registration, login, and GitHub OAuth. - Introduced authUser, authSession, and authKey tables in the database schema. - Implemented password hashing using argon2 and session management with lucia-auth. - Created a global authentication hook to populate request.user and request.session. - Added security policies and data validation using zod schemas. - Established cookie management for session handling and CSRF protection.
1 parent 7a9d5f3 commit 71647f3

File tree

19 files changed

+1573
-22
lines changed

19 files changed

+1573
-22
lines changed

package-lock.json

Lines changed: 454 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

services/backend/SECURITY.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Security Policy
2+
3+
This document outlines security procedures and policies for the backend service.
4+
5+
## Reporting a Vulnerability
6+
7+
If you discover a security vulnerability, please report it to us as soon as possible. We appreciate your efforts to disclose your findings responsibly. Please email us at [SECURITY_CONTACT_EMAIL_ADDRESS_HERE - *you'll need to replace this*] with a detailed description of the vulnerability and steps to reproduce it.
8+
9+
We will acknowledge receipt of your vulnerability report promptly and work with you to understand and address the issue. We ask that you do not publicly disclose the vulnerability until we have had a chance to remediate it.
10+
11+
## Password Hashing
12+
13+
User passwords are never stored in plaintext. We employ a strong, adaptive hashing algorithm to protect user credentials.
14+
15+
- **Algorithm:** We will use `argon2id`, which is a part of the Argon2 family of algorithms (Argon2id is generally recommended as it provides resistance against both side-channel attacks and GPU cracking attacks).
16+
- **Salt Generation:** A unique, cryptographically secure salt is automatically generated for each user's password by the `argon2` library at the time of account creation or password change. This salt is then stored as part of the resulting hash string.
17+
- **Parameters:** We use appropriate parameters for `argon2` (e.g., memory cost, time cost, and parallelism) to ensure that the hashing process is computationally intensive, making brute-force attacks significantly more difficult. These parameters are chosen to balance security with acceptable performance on our servers and may be adjusted based on hardware improvements over time.
18+
- **Verification:** During login, the provided password and the stored salt (extracted from the hash string) are used to re-compute the hash. This newly computed hash is then compared against the stored hash in a constant-time manner (handled by the `argon2` library's verify function) to help prevent timing attacks.
19+
20+
This approach ensures that even if the database were compromised, recovering the original passwords would be computationally infeasible.
21+
22+
## Session Management
23+
24+
User sessions are managed using `lucia-auth`.
25+
26+
- Session identifiers are cryptographically random and stored in secure, HTTP-only cookies to prevent XSS attacks from accessing them.
27+
- Sessions have defined expiration times (both active and idle timeouts) to limit the window of opportunity for session hijacking.
28+
29+
## Data Validation
30+
31+
All incoming data from clients (e.g., API request bodies, URL parameters) is rigorously validated using `zod` schemas on the server-side before being processed. This helps prevent common vulnerabilities such as injection attacks and unexpected data handling errors.
32+
33+
## Dependencies
34+
35+
We strive to keep our dependencies up-to-date and regularly review them for known vulnerabilities. Automated tools may be used to scan for vulnerabilities in our dependency tree.
36+
37+
## Infrastructure Security
38+
39+
[Placeholder: Add details about infrastructure security, e.g., network configuration, firewalls, access controls, HTTPS enforcement, etc., as applicable to your deployment environment.]
40+
41+
## Incident Response
42+
43+
[Placeholder: Outline your incident response plan. Who to contact, steps to take, etc.]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CREATE TABLE `users` (
2+
`id` text PRIMARY KEY NOT NULL,
3+
`email` text NOT NULL,
4+
`name` text,
5+
`created_at` integer DEFAULT (cast((julianday('now') - 2440587.5)*86400000 as integer)) NOT NULL,
6+
`updated_at` integer DEFAULT (cast((julianday('now') - 2440587.5)*86400000 as integer)) NOT NULL
7+
);
8+
--> statement-breakpoint
9+
CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
CREATE TABLE `authKey` (
2+
`id` text PRIMARY KEY NOT NULL,
3+
`user_id` text NOT NULL,
4+
`primary_key` text NOT NULL,
5+
`hashed_password` text,
6+
`expires` integer
7+
);
8+
--> statement-breakpoint
9+
CREATE TABLE `authSession` (
10+
`id` text PRIMARY KEY NOT NULL,
11+
`user_id` text NOT NULL,
12+
`expires_at` integer NOT NULL
13+
);
14+
--> statement-breakpoint
15+
CREATE TABLE `authUser` (
16+
`id` text PRIMARY KEY NOT NULL,
17+
`username` text NOT NULL,
18+
`email` text NOT NULL,
19+
`auth_type` text NOT NULL,
20+
`first_name` text,
21+
`last_name` text,
22+
`github_id` text,
23+
`hashed_password` text
24+
);
25+
--> statement-breakpoint
26+
CREATE UNIQUE INDEX `authUser_username_unique` ON `authUser` (`username`);--> statement-breakpoint
27+
CREATE UNIQUE INDEX `authUser_email_unique` ON `authUser` (`email`);--> statement-breakpoint
28+
CREATE UNIQUE INDEX `authUser_github_id_unique` ON `authUser` (`github_id`);
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
{
2+
"version": "6",
3+
"dialect": "sqlite",
4+
"id": "5c8a3126-7005-4e63-83e7-813a3b21f686",
5+
"prevId": "00000000-0000-0000-0000-000000000000",
6+
"tables": {
7+
"users": {
8+
"name": "users",
9+
"columns": {
10+
"id": {
11+
"name": "id",
12+
"type": "text",
13+
"primaryKey": true,
14+
"notNull": true,
15+
"autoincrement": false
16+
},
17+
"email": {
18+
"name": "email",
19+
"type": "text",
20+
"primaryKey": false,
21+
"notNull": true,
22+
"autoincrement": false
23+
},
24+
"name": {
25+
"name": "name",
26+
"type": "text",
27+
"primaryKey": false,
28+
"notNull": false,
29+
"autoincrement": false
30+
},
31+
"created_at": {
32+
"name": "created_at",
33+
"type": "integer",
34+
"primaryKey": false,
35+
"notNull": true,
36+
"autoincrement": false,
37+
"default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
38+
},
39+
"updated_at": {
40+
"name": "updated_at",
41+
"type": "integer",
42+
"primaryKey": false,
43+
"notNull": true,
44+
"autoincrement": false,
45+
"default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
46+
}
47+
},
48+
"indexes": {
49+
"users_email_unique": {
50+
"name": "users_email_unique",
51+
"columns": [
52+
"email"
53+
],
54+
"isUnique": true
55+
}
56+
},
57+
"foreignKeys": {},
58+
"compositePrimaryKeys": {},
59+
"uniqueConstraints": {},
60+
"checkConstraints": {}
61+
}
62+
},
63+
"views": {},
64+
"enums": {},
65+
"_meta": {
66+
"schemas": {},
67+
"tables": {},
68+
"columns": {}
69+
},
70+
"internal": {
71+
"indexes": {}
72+
}
73+
}
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
{
2+
"version": "6",
3+
"dialect": "sqlite",
4+
"id": "d6eedd77-5a05-4196-80bd-d81e8a42b7f1",
5+
"prevId": "5c8a3126-7005-4e63-83e7-813a3b21f686",
6+
"tables": {
7+
"authKey": {
8+
"name": "authKey",
9+
"columns": {
10+
"id": {
11+
"name": "id",
12+
"type": "text",
13+
"primaryKey": true,
14+
"notNull": true,
15+
"autoincrement": false
16+
},
17+
"user_id": {
18+
"name": "user_id",
19+
"type": "text",
20+
"primaryKey": false,
21+
"notNull": true,
22+
"autoincrement": false
23+
},
24+
"primary_key": {
25+
"name": "primary_key",
26+
"type": "text",
27+
"primaryKey": false,
28+
"notNull": true,
29+
"autoincrement": false
30+
},
31+
"hashed_password": {
32+
"name": "hashed_password",
33+
"type": "text",
34+
"primaryKey": false,
35+
"notNull": false,
36+
"autoincrement": false
37+
},
38+
"expires": {
39+
"name": "expires",
40+
"type": "integer",
41+
"primaryKey": false,
42+
"notNull": false,
43+
"autoincrement": false
44+
}
45+
},
46+
"indexes": {},
47+
"foreignKeys": {},
48+
"compositePrimaryKeys": {},
49+
"uniqueConstraints": {},
50+
"checkConstraints": {}
51+
},
52+
"authSession": {
53+
"name": "authSession",
54+
"columns": {
55+
"id": {
56+
"name": "id",
57+
"type": "text",
58+
"primaryKey": true,
59+
"notNull": true,
60+
"autoincrement": false
61+
},
62+
"user_id": {
63+
"name": "user_id",
64+
"type": "text",
65+
"primaryKey": false,
66+
"notNull": true,
67+
"autoincrement": false
68+
},
69+
"expires_at": {
70+
"name": "expires_at",
71+
"type": "integer",
72+
"primaryKey": false,
73+
"notNull": true,
74+
"autoincrement": false
75+
}
76+
},
77+
"indexes": {},
78+
"foreignKeys": {},
79+
"compositePrimaryKeys": {},
80+
"uniqueConstraints": {},
81+
"checkConstraints": {}
82+
},
83+
"authUser": {
84+
"name": "authUser",
85+
"columns": {
86+
"id": {
87+
"name": "id",
88+
"type": "text",
89+
"primaryKey": true,
90+
"notNull": true,
91+
"autoincrement": false
92+
},
93+
"username": {
94+
"name": "username",
95+
"type": "text",
96+
"primaryKey": false,
97+
"notNull": true,
98+
"autoincrement": false
99+
},
100+
"email": {
101+
"name": "email",
102+
"type": "text",
103+
"primaryKey": false,
104+
"notNull": true,
105+
"autoincrement": false
106+
},
107+
"auth_type": {
108+
"name": "auth_type",
109+
"type": "text",
110+
"primaryKey": false,
111+
"notNull": true,
112+
"autoincrement": false
113+
},
114+
"first_name": {
115+
"name": "first_name",
116+
"type": "text",
117+
"primaryKey": false,
118+
"notNull": false,
119+
"autoincrement": false
120+
},
121+
"last_name": {
122+
"name": "last_name",
123+
"type": "text",
124+
"primaryKey": false,
125+
"notNull": false,
126+
"autoincrement": false
127+
},
128+
"github_id": {
129+
"name": "github_id",
130+
"type": "text",
131+
"primaryKey": false,
132+
"notNull": false,
133+
"autoincrement": false
134+
},
135+
"hashed_password": {
136+
"name": "hashed_password",
137+
"type": "text",
138+
"primaryKey": false,
139+
"notNull": false,
140+
"autoincrement": false
141+
}
142+
},
143+
"indexes": {
144+
"authUser_username_unique": {
145+
"name": "authUser_username_unique",
146+
"columns": [
147+
"username"
148+
],
149+
"isUnique": true
150+
},
151+
"authUser_email_unique": {
152+
"name": "authUser_email_unique",
153+
"columns": [
154+
"email"
155+
],
156+
"isUnique": true
157+
},
158+
"authUser_github_id_unique": {
159+
"name": "authUser_github_id_unique",
160+
"columns": [
161+
"github_id"
162+
],
163+
"isUnique": true
164+
}
165+
},
166+
"foreignKeys": {},
167+
"compositePrimaryKeys": {},
168+
"uniqueConstraints": {},
169+
"checkConstraints": {}
170+
},
171+
"users": {
172+
"name": "users",
173+
"columns": {
174+
"id": {
175+
"name": "id",
176+
"type": "text",
177+
"primaryKey": true,
178+
"notNull": true,
179+
"autoincrement": false
180+
},
181+
"email": {
182+
"name": "email",
183+
"type": "text",
184+
"primaryKey": false,
185+
"notNull": true,
186+
"autoincrement": false
187+
},
188+
"name": {
189+
"name": "name",
190+
"type": "text",
191+
"primaryKey": false,
192+
"notNull": false,
193+
"autoincrement": false
194+
},
195+
"created_at": {
196+
"name": "created_at",
197+
"type": "integer",
198+
"primaryKey": false,
199+
"notNull": true,
200+
"autoincrement": false,
201+
"default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
202+
},
203+
"updated_at": {
204+
"name": "updated_at",
205+
"type": "integer",
206+
"primaryKey": false,
207+
"notNull": true,
208+
"autoincrement": false,
209+
"default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
210+
}
211+
},
212+
"indexes": {
213+
"users_email_unique": {
214+
"name": "users_email_unique",
215+
"columns": [
216+
"email"
217+
],
218+
"isUnique": true
219+
}
220+
},
221+
"foreignKeys": {},
222+
"compositePrimaryKeys": {},
223+
"uniqueConstraints": {},
224+
"checkConstraints": {}
225+
}
226+
},
227+
"views": {},
228+
"enums": {},
229+
"_meta": {
230+
"schemas": {},
231+
"tables": {},
232+
"columns": {}
233+
},
234+
"internal": {
235+
"indexes": {}
236+
}
237+
}

0 commit comments

Comments
 (0)