Skip to content

Commit ac53126

Browse files
feat: add email to users (#64)
* feat: add email to users * fix tests due to merge
1 parent dbd99d0 commit ac53126

File tree

11 files changed

+96
-81
lines changed

11 files changed

+96
-81
lines changed

migrations/001.do.users.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
CREATE TABLE users (
22
id INT AUTO_INCREMENT PRIMARY KEY,
3+
email VARCHAR(255) UNIQUE NOT NULL,
34
username VARCHAR(255) NOT NULL,
45
password VARCHAR(255) NOT NULL,
56
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

scripts/seed-database.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,25 +47,29 @@ async function truncateTables (connection: Connection) {
4747
}
4848

4949
async function seedUsers (connection: Connection) {
50-
const usernames = ['basic', 'moderator', 'admin']
50+
const users = [
51+
{ username: 'basic', email: '[email protected]' },
52+
{ username: 'moderator', email: '[email protected]' },
53+
{ username: 'admin', email: '[email protected]' }
54+
]
5155
const hash = await scryptHash('Password123$')
5256

5357
// The goal here is to create a role hierarchy
5458
// E.g. an admin should have all the roles
5559
const rolesAccumulator: number[] = []
5660

57-
for (const username of usernames) {
61+
for (const user of users) {
5862
const [userResult] = await connection.execute(`
59-
INSERT INTO users (username, password)
60-
VALUES (?, ?)
61-
`, [username, hash])
63+
INSERT INTO users (username, email, password)
64+
VALUES (?, ?, ?)
65+
`, [user.username, user.email, hash])
6266

6367
const userId = (userResult as { insertId: number }).insertId
6468

6569
const [roleResult] = await connection.execute(`
6670
INSERT INTO roles (name)
6771
VALUES (?)
68-
`, [username])
72+
`, [user.username])
6973

7074
const newRoleId = (roleResult as { insertId: number }).insertId
7175

src/routes/api/auth/index.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
2323
}
2424
},
2525
async function (request, reply) {
26-
const { username, password } = request.body
26+
const { email, password } = request.body
2727

2828
return fastify.knex.transaction(async (trx) => {
2929
const user = await trx<Auth>('users')
30-
.select('id', 'username', 'password')
31-
.where({ username })
30+
.select('id', 'username', 'email', 'password')
31+
.where({ email })
3232
.first()
3333

3434
if (user) {
@@ -41,11 +41,12 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
4141
.select('roles.name')
4242
.join('user_roles', 'roles.id', '=', 'user_roles.role_id')
4343
.join('users', 'user_roles.user_id', '=', 'users.id')
44-
.where('users.username', username)
44+
.where('users.email', user.email)
4545

4646
request.session.user = {
4747
id: user.id,
48-
username,
48+
email: user.email,
49+
username: user.username,
4950
roles: roles.map((role) => role.name)
5051
}
5152

@@ -57,7 +58,7 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
5758

5859
reply.status(401)
5960

60-
return { message: 'Invalid username or password.' }
61+
return { message: 'Invalid email or password.' }
6162
}).catch(() => {
6263
reply.internalServerError('Transaction failed.')
6364
})

src/schemas/auth.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import { Static, Type } from '@sinclair/typebox'
2-
import { StringSchema } from './common.js'
2+
import { EmailSchema, StringSchema } from './common.js'
33

44
export const CredentialsSchema = Type.Object({
5-
username: StringSchema,
5+
email: EmailSchema,
66
password: StringSchema
77
})
88

99
export interface Credentials extends Static<typeof CredentialsSchema> {}
1010

11-
export interface Auth extends Omit<Credentials, 'password'> {
11+
export interface Auth {
1212
id: number;
13+
username: string;
14+
email: string,
1315
roles: string[]
1416
}

src/schemas/common.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ export const StringSchema = Type.String({
55
maxLength: 255
66
})
77

8+
export const EmailSchema = Type.String({
9+
format: 'email',
10+
minLength: 1,
11+
maxLength: 255
12+
})
13+
814
export const DateTimeSchema = Type.String({ format: 'date-time' })
915

1016
export const IdSchema = Type.Integer({ minimum: 1 })

src/schemas/users.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ const passwordPattern = '^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]
55
const PasswordSchema = Type.String({
66
pattern: passwordPattern,
77
minLength: 8
8-
98
})
109

1110
export const UpdateCredentialsSchema = Type.Object({

test/helper.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ export function expectValidationError (res: LightMyRequestResponse, expectedMess
2828
assert.strictEqual(message, expectedMessage)
2929
}
3030

31-
async function login (this: FastifyInstance, username: string) {
31+
async function login (this: FastifyInstance, email: string) {
3232
const res = await this.inject({
3333
method: 'POST',
3434
url: '/api/auth/login',
3535
payload: {
36-
username,
36+
email,
3737
password: 'Password123$'
3838
}
3939
})
@@ -51,10 +51,10 @@ async function login (this: FastifyInstance, username: string) {
5151

5252
async function injectWithLogin (
5353
this: FastifyInstance,
54-
username: string,
54+
email: string,
5555
opts: InjectOptions
5656
) {
57-
const cookieValue = await this.login(username)
57+
const cookieValue = await this.login(email)
5858

5959
opts.cookies = {
6060
...opts.cookies,

test/routes/api/api.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ test('GET /api with no login', async (t) => {
1717
test('GET /api with cookie', async (t) => {
1818
const app = await build(t)
1919

20-
const res = await app.injectWithLogin('basic', {
20+
const res = await app.injectWithLogin('basic@example.com', {
2121
url: '/api'
2222
})
2323

test/routes/api/auth/auth.test.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe('Auth api', () => {
1818
method: 'POST',
1919
url: '/api/auth/login',
2020
payload: {
21-
username: 'basic',
21+
email: 'basic@example.com',
2222
password: 'Password123$'
2323
}
2424
})
@@ -37,19 +37,19 @@ describe('Auth api', () => {
3737
const app = await build(t)
3838

3939
const invalidPayload = {
40-
username: '',
40+
email: '',
4141
password: 'Password123$'
4242
}
4343

44-
const res = await app.injectWithLogin('basic', {
44+
const res = await app.injectWithLogin('basic@example.com', {
4545
method: 'POST',
4646
url: '/api/auth/login',
4747
payload: invalidPayload
4848
})
4949

5050
expectValidationError(
5151
res,
52-
'body/username must NOT have fewer than 1 characters'
52+
'body/email must NOT have fewer than 1 characters'
5353
)
5454
})
5555

@@ -60,7 +60,7 @@ describe('Auth api', () => {
6060
method: 'POST',
6161
url: '/api/auth/login',
6262
payload: {
63-
username: 'basic',
63+
email: 'basic@example.com',
6464
password: 'Password123$'
6565
}
6666
})
@@ -76,17 +76,17 @@ describe('Auth api', () => {
7676

7777
const testCases = [
7878
{
79-
username: 'invalid_user',
79+
8080
password: 'password',
81-
description: 'invalid username'
81+
description: 'invalid email'
8282
},
8383
{
84-
username: 'basic',
84+
email: 'basic@example.com',
8585
password: 'wrong_password',
8686
description: 'invalid password'
8787
},
8888
{
89-
username: 'invalid_user',
89+
9090
password: 'wrong_password',
9191
description: 'both invalid'
9292
}
@@ -97,7 +97,7 @@ describe('Auth api', () => {
9797
method: 'POST',
9898
url: '/api/auth/login',
9999
payload: {
100-
username: testCase.username,
100+
email: testCase.email,
101101
password: testCase.password
102102
}
103103
})
@@ -109,7 +109,7 @@ describe('Auth api', () => {
109109
)
110110

111111
assert.deepStrictEqual(JSON.parse(res.payload), {
112-
message: 'Invalid username or password.'
112+
message: 'Invalid email or password.'
113113
})
114114
}
115115
})

0 commit comments

Comments
 (0)