Skip to content

Commit

Permalink
feat(auth): add update of email/password
Browse files Browse the repository at this point in the history
  • Loading branch information
jlenon7 committed May 4, 2024
1 parent c8c180b commit 268d827
Show file tree
Hide file tree
Showing 19 changed files with 612 additions and 65 deletions.
24 changes: 21 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
"devDependencies": {
"@athenna/test": "^4.23.0",
"@athenna/tsconfig": "^4.12.0",
"@types/bcrypt": "^5.0.2",
"@types/jsonwebtoken": "^9.0.6",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.7.4",
"eslint": "^8.36.0",
Expand Down Expand Up @@ -170,6 +172,11 @@
"make:terminator": "@athenna/http/commands/MakeTerminatorCommand",
"serve": {
"path": "@athenna/core/commands/ServeCommand",
"nodemon": {
"ignore": [
"storage/*"
]
},
"stayAlive": true
},
"build": {
Expand Down
32 changes: 30 additions & 2 deletions src/controllers/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,36 @@ export class AuthController {
return response.status(201).send(user)
}

public async verifyEmail({ request, response }: Context) {
await this.authService.verifyEmail(request.query('emailToken'))
public async confirm({ request, response }: Context) {
await this.authService.confirm(request.query('token'))

return response.status(204)
}

public async confirmEmailChange({ request, response }: Context) {
await this.authService.confirmEmailChange(
request.query('email'),
request.query('token')
)

return response.status(204)
}

public async confirmPasswordChange({ request, response }: Context) {
await this.authService.confirmPasswordChange(
request.query('password'),
request.query('token')
)

return response.status(204)
}

public async confirmEmailPasswordChange({ request, response }: Context) {
await this.authService.confirmEmailPasswordChange(
request.query('email'),
request.query('password'),
request.query('token')
)

return response.status(204)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class Users extends BaseMigration {
builder.string('name').notNullable()
builder.string('email').unique().notNullable()
builder.string('password').notNullable()
builder.string('email_token').notNullable()
builder.string('token').unique().notNullable()
builder.timestamp('email_verified_at').defaultTo(null)
builder.timestamps(true, true, false)
builder.timestamp('deleted_at').defaultTo(null)
Expand Down
4 changes: 2 additions & 2 deletions src/database/seeders/user.seeder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ export class UserSeeder extends BaseSeeder {
name: 'Admin',
email: '[email protected]',
password: await bcrypt.hash('12345', 10),
emailToken: Uuid.generate(),
token: Uuid.generate(),
emailVerifiedAt: new Date()
})

const userCustomer = await User.create({
name: 'Customer',
email: '[email protected]',
password: await bcrypt.hash('12345', 10),
emailToken: Uuid.generate(),
token: Uuid.generate(),
emailVerifiedAt: new Date()
})

Expand Down
29 changes: 27 additions & 2 deletions src/models/user.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import bcrypt from 'bcrypt'
import { Role } from '#src/models/role'
import { RoleUser } from '#src/models/roleuser'
import { Column, BaseModel, BelongsToMany } from '@athenna/database'
Expand All @@ -15,8 +16,8 @@ export class User extends BaseModel {
@Column({ isHidden: true, isNullable: false })
public password: string

@Column({ name: 'email_token', isUnique: true, isNullable: false })
public emailToken: string
@Column({ isUnique: true, isNullable: false })
public token: string

@Column({ name: 'email_verified_at' })
public emailVerifiedAt: Date
Expand All @@ -33,6 +34,30 @@ export class User extends BaseModel {
@BelongsToMany(() => Role, () => RoleUser)
public roles: Role[]

public isEmailEqual(email: string) {
/**
* If there are no email to validate,
* it means no change is going to be made.
*/
if (!email) {
return true
}

return this.email === email
}

public isPasswordEqual(password: string) {
/**
* If there are no password to validate,
* it means no change is going to be made.
*/
if (!password) {
return true
}

return bcrypt.compareSync(password, this.password)
}

public static attributes(): Partial<User> {
return {}
}
Expand Down
38 changes: 34 additions & 4 deletions src/providers/queueworker.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,50 @@ export default class QueueWorkerProvider extends ServiceProvider {
public intervals = []

public async boot() {
this.processByQueue('user:register', async user => {
this.processByQueue('user:confirm', async user => {
return Mail.from('[email protected]')
.to(user.email)
.subject('Athenna Account Activation')
.view('mail/register', { user })
.subject('Athenna Account Confirmation')
.view('mail/confirm', { user })
.send()
})

this.processByQueue('user:email', async ({ user, token, email }) => {
return Mail.from('[email protected]')
.to(user.email)
.subject('Athenna Email Change')
.view('mail/change-email', { user, email, token })
.send()
})

this.processByQueue('user:password', async ({ user, token, password }) => {
return Mail.from('[email protected]')
.to(user.email)
.subject('Athenna Password Change')
.view('mail/change-password', { user, password, token })
.send()
})

this.processByQueue(
'user:email:password',
async ({ user, token, email, password }) => {
return Mail.from('[email protected]')
.to(user.email)
.subject('Athenna Email & Password Change')
.view('mail/change-email-password', { user, email, password, token })
.send()
}
)
}

public async shutdown() {
this.intervals.forEach(interval => clearInterval(interval))
}

public processByQueue(queueName: string, processor: any) {
public processByQueue(
queueName: string,
processor: (data: any) => any | Promise<any>
) {
const interval = setInterval(async () => {
const queue = await Queue.queue(queueName)

Expand Down
84 changes: 84 additions & 0 deletions src/resources/views/mail/change-email-password.edge
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Change Email & Password</title>
<style>
body {
font-family: '-apple-system', 'BlinkMacSystemFont', 'Helvetica', 'Arial', 'sans-serif', 'apple color emoji';
margin: 0;
padding: 0;
background-color: #252529;
color: #e3e3e3;
text-align: center;
}
.header {
background-color: #1d1e25;
padding: 20px;
}
.header h1 {
margin: 0;
font-size: 30px;
}
.activation {
padding: 10px 100px;
}
.activation p {
margin: 0;
font-size: 20px;
margin-top: 10px;
margin-bottom: 30px;
}
.help {
padding: 10px 200px;
}
.help p {
margin: 0;
font-size: 16px;
margin-top: 10px;
margin-bottom: 30px;
}
a {
color: #ffff92;
}
a.button {
padding: 10px 24px;
border: 1px;
font-size: 20px;
border-radius: 3px;
color: #e3e3e3;
font-weight: 500;
background-color: #715bf6;
text-decoration: none;
}
.minerva-img {
max-width: 200px;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="header">
<img src="https://athenna.io/img/logos/minerva.png" alt="Minerva Logo" class="minerva-img">
<h1>Hey there {{ user.name }}!</h1>
</div>
<div class="activation">
<p>
We are sending you this email because you have requested to
change your account email to <b>{{ email }}</b> and also to
change your <b>password</b>. To confirm the update, click the link bellow:
</p>
<a class="button" href="{{ `${Config.get('http.url')}/api/v1/confirm/email/password?token=${token}&email=${email}&password=${password}` }}">CONFIRM EMAIL & PASSWORD CHANGE</a>
</div>
<div class="help">
<p>
If this was not you or if you have any questions, please email us
at <a href="mailto:[email protected]">[email protected]</a> or
visit our FAQS, you can also chat with a real human during our
operating hours. They can answer questions about your account.
</p>
</div>
</body>
</html>

Loading

0 comments on commit 268d827

Please sign in to comment.