Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
B
BACKEND CHALLENGES
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Phạm Quang Bảo
BACKEND CHALLENGES
Commits
e627251c
Commit
e627251c
authored
May 19, 2026
by
Phạm Quang Bảo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(challenge_4): add api send otp and verify otp
parent
96ed263f
Changes
19
Show whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
540 additions
and
120 deletions
+540
-120
package.json
code/package.json
+3
-0
pnpm-lock.yaml
code/pnpm-lock.yaml
+28
-0
index.ts
code/src/controllers/api/v1.0/auth/login/index.ts
+3
-3
index.ts
code/src/controllers/api/v1.0/auth/profile/index.ts
+3
-3
index.ts
code/src/controllers/api/v1.0/auth/register/index.ts
+3
-3
index.ts
code/src/controllers/api/v1.0/auth/send-otp/index.ts
+62
-0
index.ts
code/src/controllers/api/v1.0/auth/verify-otp/index.ts
+48
-0
swagger-output.json
code/src/docs/swagger/swagger-output.json
+108
-0
index.ts
code/src/index.ts
+3
-0
authorization.ts
code/src/middlewares/authorization.ts
+1
-1
user_auth.ts
code/src/models/user_auth.ts
+19
-1
LoginProvider.ts
code/src/providers/LoginProvider.ts
+0
-47
ProfileProvider.ts
code/src/providers/ProfileProvider.ts
+0
-14
RegisterProvider.ts
code/src/providers/RegisterProvider.ts
+0
-48
authService.ts
code/src/services/authService.ts
+159
-0
mailService.ts
code/src/services/mailService.ts
+45
-0
config.ts
code/src/templates/swagger/config.ts
+4
-0
schema.ts
code/src/templates/swagger/sendOTP/schema.ts
+22
-0
schema.ts
code/src/templates/swagger/verifyOTP/schema.ts
+29
-0
No files found.
code/package.json
View file @
e627251c
...
...
@@ -20,10 +20,12 @@
},
"dependencies"
:
{
"bcrypt"
:
"^6.0.0"
,
"dotenv"
:
"^17.4.2"
,
"express"
:
"^5.2.1"
,
"express-automatic-routes"
:
"^1.1.0"
,
"jsonwebtoken"
:
"^9.0.3"
,
"module-alias"
:
"^2.3.4"
,
"nodemailer"
:
"^8.0.7"
,
"pg"
:
"^8.20.0"
,
"pg-hstore"
:
"^2.3.4"
,
"sequelize"
:
"^6.37.8"
,
...
...
@@ -36,6 +38,7 @@
"@types/express"
:
"^5.0.6"
,
"@types/jsonwebtoken"
:
"^9.0.10"
,
"@types/node"
:
"^25.7.0"
,
"@types/nodemailer"
:
"^8.0.0"
,
"@types/swagger-jsdoc"
:
"^6.0.4"
,
"@types/swagger-ui-express"
:
"^4.1.8"
,
"ts-node"
:
"^10.9.2"
,
...
...
code/pnpm-lock.yaml
View file @
e627251c
...
...
@@ -11,6 +11,9 @@ importers:
bcrypt
:
specifier
:
^6.0.0
version
:
6.0.0
dotenv
:
specifier
:
^17.4.2
version
:
17.4.2
express
:
specifier
:
^5.2.1
version
:
5.2.1
...
...
@@ -23,6 +26,9 @@ importers:
module-alias
:
specifier
:
^2.3.4
version
:
2.3.4
nodemailer
:
specifier
:
^8.0.7
version
:
8.0.7
pg
:
specifier
:
^8.20.0
version
:
8.20.0
...
...
@@ -54,6 +60,9 @@ importers:
'
@types/node'
:
specifier
:
^25.7.0
version
:
25.7.0
'
@types/nodemailer'
:
specifier
:
^8.0.0
version
:
8.0.0
'
@types/swagger-jsdoc'
:
specifier
:
^6.0.4
version
:
6.0.4
...
...
@@ -308,6 +317,9 @@ packages:
'
@types/node@25.7.0'
:
resolution
:
{
integrity
:
sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==
}
'
@types/nodemailer@8.0.0'
:
resolution
:
{
integrity
:
sha512-fyf8jWULsCo0d0BuoQ75i6IeoHs47qcqxWc7yUdUcV0pOZGjUTTOvwdG1PRXUDqN/8A64yQdQdnA2pZgcdi+cA==
}
'
@types/qs@6.15.1'
:
resolution
:
{
integrity
:
sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==
}
...
...
@@ -449,6 +461,10 @@ packages:
resolution
:
{
integrity
:
sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
}
engines
:
{
node
:
'
>=6.0.0'
}
dotenv@17.4.2
:
resolution
:
{
integrity
:
sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==
}
engines
:
{
node
:
'
>=12'
}
dottie@2.0.7
:
resolution
:
{
integrity
:
sha512-7lAK2A0b3zZr3UC5aE69CPdCFR4RHW1o2Dr74TqFykxkUCBXSRJum/yPc7g8zRHJqWKomPLHwFLLoUnn8PXXRg==
}
deprecated
:
Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
...
...
@@ -697,6 +713,10 @@ packages:
resolution
:
{
integrity
:
sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==
}
hasBin
:
true
nodemailer@8.0.7
:
resolution
:
{
integrity
:
sha512-pkjE4mkBzQjdJT4/UmlKl3pX0rC9fZmjh7c6C9o7lv66Ac6w9WCnzPzhbPNxwZAzlF4mdq4CSWB5+FbK6FWCow==
}
engines
:
{
node
:
'
>=6.0.0'
}
object-inspect@1.13.4
:
resolution
:
{
integrity
:
sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==
}
engines
:
{
node
:
'
>=
0.4'
}
...
...
@@ -1196,6 +1216,10 @@ snapshots:
dependencies
:
undici-types
:
7.21.0
'
@types/nodemailer@8.0.0'
:
dependencies
:
'
@types/node'
:
25.7.0
'
@types/qs@6.15.1'
:
{}
'
@types/range-parser@1.2.7'
:
{}
...
...
@@ -1322,6 +1346,8 @@ snapshots:
dependencies
:
esutils
:
2.0.3
dotenv@17.4.2
:
{}
dottie@2.0.7
:
{}
dunder-proto@1.0.1
:
...
...
@@ -1596,6 +1622,8 @@ snapshots:
node-gyp-build@4.8.4
:
{}
nodemailer@8.0.7
:
{}
object-inspect@1.13.4
:
{}
on-finished@2.4.1
:
...
...
code/src/controllers/api/v1.0/auth/login/index.ts
View file @
e627251c
import
{
LoginProvider
}
from
"#providers/LoginProvider.js
"
;
import
{
AuthService
}
from
"#services/authService
"
;
import
{
Application
}
from
"express"
import
{
Resource
}
from
"express-automatic-routes"
export
default
(
_express
:
Application
)
=>
{
const
loginProvider
=
new
LoginProvider
();
const
authService
=
new
AuthService
();
return
<
Resource
>
{
/**
...
...
@@ -31,7 +31,7 @@ export default (_express: Application) => {
post
:
{
handler
:
async
(
req
,
res
)
=>
{
try
{
const
login
=
await
loginProvider
.
loginUser
(
req
.
body
);
const
login
=
await
authService
.
loginUser
(
req
.
body
);
return
res
.
status
(
200
).
json
(
login
);
}
catch
(
error
)
{
...
...
code/src/controllers/api/v1.0/auth/profile/index.ts
View file @
e627251c
import
{
authMiddleware
}
from
"#middlewares/authorization"
;
import
{
ProfileProvider
}
from
"#providers/ProfileProvider.js
"
;
import
{
AuthService
}
from
"#services/authService
"
;
import
{
Application
}
from
"express"
;
import
{
Resource
}
from
"express-automatic-routes"
;
export
default
(
_express
:
Application
)
=>
{
const
profileProvider
=
new
ProfileProvider
();
const
authService
=
new
AuthService
();
return
<
Resource
>
{
/**
...
...
@@ -30,7 +30,7 @@ export default (_express: Application) => {
try
{
const
userId
=
(
req
as
any
).
user
.
id
;
const
profile
=
await
profileProvider
.
profileProvid
er
(
userId
);
const
profile
=
await
authService
.
profileUs
er
(
userId
);
return
res
.
status
(
200
).
json
(
profile
);
}
catch
(
error
)
{
...
...
code/src/controllers/api/v1.0/auth/register/index.ts
View file @
e627251c
import
{
RegisterProvider
}
from
"#providers/RegisterProvider.js
"
;
import
{
AuthService
}
from
"#services/authService
"
;
import
{
Application
}
from
"express"
;
import
{
Resource
}
from
"express-automatic-routes"
;
export
default
(
_express
:
Application
)
=>
{
const
registerProvider
=
new
RegisterProvider
();
const
authService
=
new
AuthService
();
return
<
Resource
>
{
/**
...
...
@@ -41,7 +41,7 @@ export default (_express: Application) => {
return
res
.
status
(
400
).
json
({
error
:
'Mật khẩu phải có ít nhất 6 ký tự'
});
}
const
register
=
await
registerProvider
.
registerUser
(
req
.
body
);
const
register
=
await
authService
.
registerUser
(
req
.
body
);
return
res
.
status
(
201
).
json
(
register
);
}
catch
(
error
)
{
console
.
error
(
'Error registering user:'
,
error
);
...
...
code/src/controllers/api/v1.0/auth/send-otp/index.ts
0 → 100644
View file @
e627251c
import
{
authMiddleware
}
from
"#middlewares/authorization"
;
import
{
AuthService
}
from
"#services/authService.js"
;
import
{
MailService
}
from
"#services/mailService.js"
;
import
{
Application
}
from
"express"
;
import
{
Resource
}
from
"express-automatic-routes"
;
export
default
(
_express
:
Application
)
=>
{
const
authService
=
new
AuthService
();
const
mailService
=
new
MailService
();
return
<
Resource
>
{
/**
* @openapi
* /api/v1.0/auth/send-otp:
* post:
* tags: [Auth]
* security:
* - bearerAuth: []
* description: Gửi mã OTP đến email của người dùng
* responses:
* 201:
* description: Gửi mã OTP thành công
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/sendOtpResponse"
*/
post
:
{
middleware
:
[
authMiddleware
],
handler
:
async
(
req
,
res
)
=>
{
try
{
const
userId
=
(
req
as
any
).
user
.
id
;
const
email
=
(
req
as
any
).
user
.
email
;
// get otp
const
otp
=
await
authService
.
otpUser
(
userId
);
// send mail
const
mailResult
=
await
mailService
.
sendOtpEmail
(
email
,
otp
);
if
(
!
mailResult
||
!
mailResult
.
success
)
{
console
.
error
(
'Gửi mail thất bại:'
,
mailResult
?.
error
);
return
res
.
status
(
500
).
json
({
email
:
email
,
message
:
'Không thể gửi email OTP. Vui lòng thử lại sau.'
,
error
:
mailResult
?.
error
});
}
return
res
.
status
(
200
).
json
({
email
:
email
,
message
:
'OTP đã được gửi đến email của bạn'
});
}
catch
(
error
)
{
return
res
.
status
(
500
).
json
({
error
:
(
error
as
Error
).
message
});
}
}
}
}
}
\ No newline at end of file
code/src/controllers/api/v1.0/auth/verify-otp/index.ts
0 → 100644
View file @
e627251c
import
{
authMiddleware
}
from
"#middlewares/authorization"
;
import
{
AuthService
}
from
"#services/authService.js"
;
import
{
Application
}
from
"express"
;
import
{
Resource
}
from
"express-automatic-routes"
;
export
default
(
_express
:
Application
)
=>
{
const
authService
=
new
AuthService
();
return
<
Resource
>
{
/**
* @openapi
* /api/v1.0/auth/verify-otp:
* post:
* tags: [Auth]
* security:
* - bearerAuth: []
* description: Xác minh mã OTP của người dùng
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/verifyOtpInput"
* responses:
* 201:
* description: Xác minh mã OTP thành công
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/verifyOtpResponse"
*/
post
:
{
middleware
:
[
authMiddleware
],
handler
:
async
(
req
,
res
)
=>
{
try
{
const
userId
=
(
req
as
any
).
user
.
id
;
const
verifyResult
=
await
authService
.
verifyOtp
(
userId
,
req
.
body
.
otp
);
res
.
status
(
201
).
json
(
verifyResult
);
}
catch
(
error
)
{
return
res
.
status
(
500
).
json
({
error
:
(
error
as
Error
).
message
});
}
}
}
}
}
\ No newline at end of file
code/src/docs/swagger/swagger-output.json
View file @
e627251c
...
...
@@ -308,6 +308,48 @@
"example"
:
"123 Main St, Anytown, USA"
}
}
},
"sendOtpResponse"
:
{
"type"
:
"object"
,
"example"
:
{
"email"
:
"phamquangbao@example.com"
,
"message"
:
"OTP đã được gửi đến email của bạn"
},
"properties"
:
{
"email"
:
{
"type"
:
"string"
,
"format"
:
"email"
,
"example"
:
"phamquangbao@example.com"
},
"message"
:
{
"type"
:
"string"
,
"example"
:
"OTP đã được gửi đến email của bạn"
}
}
},
"verifyOtpResponse"
:
{
"type"
:
"object"
,
"example"
:
{
"message"
:
"Xác minh OTP thành công"
},
"properties"
:
{
"message"
:
{
"type"
:
"string"
,
"example"
:
"Xác minh OTP thành công"
}
}
},
"verifyOtpInput"
:
{
"type"
:
"object"
,
"example"
:
{
"otp"
:
"123456"
},
"properties"
:
{
"otp"
:
{
"type"
:
"string"
,
"example"
:
"123456"
}
}
}
},
"parameters"
:
{
...
...
@@ -437,6 +479,72 @@
}
}
},
"/api/v1.0/auth/send-otp"
:
{
"post"
:
{
"tags"
:
[
"Auth"
],
"security"
:
[
{
"bearerAuth"
:
[]
}
],
"description"
:
"Gửi mã OTP đến email của người dùng"
,
"responses"
:
{
"201"
:
{
"description"
:
"Gửi mã OTP thành công"
,
"content"
:
{
"application/json"
:
{
"schema"
:
{
"type"
:
"array"
,
"items"
:
{
"$ref"
:
"#/components/schemas/sendOtpResponse"
}
}
}
}
}
}
}
},
"/api/v1.0/auth/verify-otp"
:
{
"post"
:
{
"tags"
:
[
"Auth"
],
"security"
:
[
{
"bearerAuth"
:
[]
}
],
"description"
:
"Xác minh mã OTP của người dùng"
,
"requestBody"
:
{
"required"
:
true
,
"content"
:
{
"application/json"
:
{
"schema"
:
{
"$ref"
:
"#/components/schemas/verifyOtpInput"
}
}
}
},
"responses"
:
{
"201"
:
{
"description"
:
"Xác minh mã OTP thành công"
,
"content"
:
{
"application/json"
:
{
"schema"
:
{
"type"
:
"array"
,
"items"
:
{
"$ref"
:
"#/components/schemas/verifyOtpResponse"
}
}
}
}
}
}
}
},
"/api/v1.0/classes/{id}"
:
{
"get"
:
{
"tags"
:
[
...
...
code/src/index.ts
View file @
e627251c
...
...
@@ -2,8 +2,11 @@ import express from 'express';
import
{
resolve
}
from
'path'
;
import
_autoroutes
from
'express-automatic-routes'
;
import
swaggerUi
from
'swagger-ui-express'
;
import
dotenv
from
'dotenv'
;
import
swaggerFile
from
'#/docs/swagger/swagger-output.json'
;
dotenv
.
config
();
const
app
=
express
()
const
port
=
3000
...
...
code/src/middlewares/authorization.ts
View file @
e627251c
...
...
@@ -2,7 +2,7 @@ import jwt from 'jsonwebtoken';
import
{
Request
,
Response
,
NextFunction
}
from
'express'
;
//demo
const
JWT_SECRET
=
'1234567890
'
;
const
JWT_SECRET
=
process
.
env
.
JWT_SECRET
||
'
'
;
export
const
authMiddleware
=
(
req
:
any
,
res
:
Response
,
next
:
NextFunction
)
=>
{
const
authHeader
=
req
.
headers
.
authorization
;
...
...
code/src/models/user_auth.ts
View file @
e627251c
...
...
@@ -7,11 +7,14 @@ export interface user_authAttributes {
user_id
?:
string
;
password_hash
?:
string
;
created_at
?:
Date
;
otp_code
?:
string
;
otp_expiry
?:
Date
;
active
?:
boolean
;
}
export
type
user_authPk
=
"id"
;
export
type
user_authId
=
user_auth
[
user_authPk
];
export
type
user_authOptionalAttributes
=
"id"
|
"user_id"
|
"password_hash"
|
"created_at"
;
export
type
user_authOptionalAttributes
=
"id"
|
"user_id"
|
"password_hash"
|
"created_at"
|
"otp_code"
|
"otp_expiry"
|
"active"
;
export
type
user_authCreationAttributes
=
Optional
<
user_authAttributes
,
user_authOptionalAttributes
>
;
export
class
user_auth
extends
Model
<
user_authAttributes
,
user_authCreationAttributes
>
implements
user_authAttributes
{
...
...
@@ -19,6 +22,9 @@ export class user_auth extends Model<user_authAttributes, user_authCreationAttri
user_id
?:
string
;
password_hash
?:
string
;
created_at
?:
Date
;
otp_code
?:
string
;
otp_expiry
?:
Date
;
active
?:
boolean
;
// user_auth belongsTo users via user_id
user
!
:
users
;
...
...
@@ -50,6 +56,18 @@ export class user_auth extends Model<user_authAttributes, user_authCreationAttri
type
:
DataTypes
.
DATE
,
allowNull
:
true
,
defaultValue
:
Sequelize
.
Sequelize
.
fn
(
'now'
)
},
otp_code
:
{
type
:
DataTypes
.
STRING
(
6
),
allowNull
:
true
},
otp_expiry
:
{
type
:
DataTypes
.
DATE
,
allowNull
:
true
},
active
:
{
type
:
DataTypes
.
BOOLEAN
,
allowNull
:
true
}
},
{
tableName
:
'user_auth'
,
...
...
code/src/providers/LoginProvider.ts
deleted
100644 → 0
View file @
96ed263f
import
{
models
}
from
"#models/sequelize-config.js"
;
import
bcrypt
from
'bcrypt'
;
import
jwt
from
'jsonwebtoken'
;
interface
LoginInput
{
email
:
string
;
password
:
string
;
}
export
class
LoginProvider
{
// demo
private
readonly
JWT_SECRET
=
'1234567890'
;
async
loginUser
(
input
:
LoginInput
)
{
const
user
=
await
models
.
users
.
findOne
({
where
:
{
email
:
input
.
email
},
});
if
(
!
user
)
{
throw
new
Error
(
'Email hoặc mật khẩu không đúng'
);
}
const
auth
=
await
models
.
user_auth
.
findOne
({
where
:
{
user_id
:
user
?.
id
}
});
const
passwordMatch
=
await
bcrypt
.
compare
(
input
.
password
,
auth
?.
password_hash
||
''
);
if
(
!
passwordMatch
)
{
throw
new
Error
(
'Email hoặc mật khẩu không đúng'
);
}
const
payload
=
{
id
:
user
.
id
,
email
:
user
.
email
,
role_id
:
user
.
role_id
};
const
token
=
jwt
.
sign
(
payload
,
this
.
JWT_SECRET
,
{
expiresIn
:
'1h'
,
});
return
{
accessToken
:
token
};
}
}
\ No newline at end of file
code/src/providers/ProfileProvider.ts
deleted
100644 → 0
View file @
96ed263f
import
{
models
}
from
"#models/sequelize-config.js"
;
export
class
ProfileProvider
{
async
profileProvider
(
userId
:
number
)
{
const
user
=
await
models
.
users
.
findByPk
(
userId
);
if
(
!
user
)
{
throw
new
Error
(
'Người dùng không tồn tại'
);
}
return
user
;
}
}
\ No newline at end of file
code/src/providers/RegisterProvider.ts
deleted
100644 → 0
View file @
96ed263f
import
{
models
,
sequelize
}
from
'#models/sequelize-config.js'
;
import
bcrypt
from
'bcrypt'
;
interface
RegisterInput
{
name
:
string
;
email
:
string
;
password
:
string
;
}
export
class
RegisterProvider
{
async
registerUser
(
input
:
RegisterInput
)
{
const
salt
=
await
bcrypt
.
genSalt
(
10
);
const
passwordHash
=
await
bcrypt
.
hash
(
input
.
password
,
salt
);
try
{
const
result
=
await
sequelize
.
transaction
(
async
(
t
)
=>
{
const
registeredUser
=
await
models
.
users
.
create
(
{
name
:
input
.
name
,
email
:
input
.
email
,
},
{
transaction
:
t
}
);
await
models
.
user_auth
.
create
(
{
user_id
:
registeredUser
.
id
,
password_hash
:
passwordHash
,
},
{
transaction
:
t
}
);
return
{
id
:
registeredUser
.
id
,
name
:
registeredUser
.
name
,
email
:
registeredUser
.
email
,
};
});
return
result
;
}
catch
(
error
)
{
console
.
error
(
'Error during user registration:'
,
error
);
throw
new
Error
(
'Failed to register user'
);
}
}
}
\ No newline at end of file
code/src/services/authService.ts
0 → 100644
View file @
e627251c
import
{
models
,
sequelize
}
from
"#models/sequelize-config.js"
;
import
bcrypt
from
'bcrypt'
;
import
jwt
from
'jsonwebtoken'
;
import
crypto
from
'crypto'
;
interface
LoginInput
{
email
:
string
;
password
:
string
;
}
interface
RegisterInput
{
name
:
string
;
email
:
string
;
password
:
string
;
}
export
class
AuthService
{
// demo
private
readonly
JWT_SECRET
=
process
.
env
.
JWT_SECRET
||
''
;
async
loginUser
(
input
:
LoginInput
)
{
const
user
=
await
models
.
users
.
findOne
({
where
:
{
email
:
input
.
email
},
});
if
(
!
user
)
{
throw
new
Error
(
'Email hoặc mật khẩu không đúng'
);
}
const
auth
=
await
models
.
user_auth
.
findOne
({
where
:
{
user_id
:
user
?.
id
}
});
const
passwordMatch
=
await
bcrypt
.
compare
(
input
.
password
,
auth
?.
password_hash
||
''
);
if
(
!
passwordMatch
)
{
throw
new
Error
(
'Email hoặc mật khẩu không đúng'
);
}
const
payload
=
{
id
:
user
.
id
,
email
:
user
.
email
,
role_id
:
user
.
role_id
};
const
token
=
jwt
.
sign
(
payload
,
this
.
JWT_SECRET
,
{
expiresIn
:
'1h'
,
});
return
{
accessToken
:
token
};
}
async
registerUser
(
input
:
RegisterInput
)
{
const
salt
=
await
bcrypt
.
genSalt
(
10
);
const
passwordHash
=
await
bcrypt
.
hash
(
input
.
password
,
salt
);
try
{
const
result
=
await
sequelize
.
transaction
(
async
(
t
)
=>
{
const
registeredUser
=
await
models
.
users
.
create
(
{
name
:
input
.
name
,
email
:
input
.
email
,
},
{
transaction
:
t
}
);
await
models
.
user_auth
.
create
(
{
user_id
:
registeredUser
.
id
,
password_hash
:
passwordHash
,
},
{
transaction
:
t
}
);
return
{
id
:
registeredUser
.
id
,
name
:
registeredUser
.
name
,
email
:
registeredUser
.
email
,
};
});
return
result
;
}
catch
(
error
)
{
console
.
error
(
'Error during user registration:'
,
error
);
throw
new
Error
(
'Failed to register user'
);
}
}
async
profileUser
(
userId
:
number
)
{
const
user
=
await
models
.
users
.
findByPk
(
userId
);
if
(
!
user
)
{
throw
new
Error
(
'Người dùng không tồn tại'
);
}
return
user
;
}
// otp
async
otpUser
(
userId
:
string
)
{
// create otp
const
generateOTP
=
()
=>
{
const
otp
=
crypto
.
randomInt
(
100000
,
1000000
);
return
otp
.
toString
().
padStart
(
6
,
'0'
);
};
try
{
const
otpCode
=
generateOTP
();
await
models
.
user_auth
.
update
(
{
otp_code
:
otpCode
,
otp_expiry
:
new
Date
(
Date
.
now
()
+
5
*
60
*
1000
),
active
:
false
,
},
{
where
:
{
user_id
:
userId
},
},
);
return
otpCode
;
}
catch
(
error
)
{
console
.
error
(
'Error during OTP generation:'
,
error
);
throw
new
Error
(
'Failed to generate OTP'
);
}
}
// verify otp
async
verifyOtp
(
userId
:
string
,
otp
:
string
)
{
try
{
const
userAuth
=
await
models
.
user_auth
.
findOne
(
{
where
:
{
user_id
:
userId
,
otp_code
:
otp
}
},
);
if
(
!
userAuth
)
{
throw
new
Error
(
'Mã OTP không đúng'
);
}
else
if
(
userAuth
.
otp_expiry
&&
userAuth
.
otp_expiry
<
new
Date
())
{
throw
new
Error
(
'Mã OTP đã hết hạn'
);
}
else
{
await
models
.
user_auth
.
update
(
{
otp_code
:
''
,
otp_expiry
:
null
as
any
,
active
:
true
,
},
{
where
:
{
user_id
:
userId
}
}
);
return
{
message
:
'Xác minh OTP thành công'
};
}
}
catch
(
error
)
{
console
.
error
(
'Error during OTP verification:'
,
error
);
throw
new
Error
(
'Failed to verify OTP'
);
}
}
}
\ No newline at end of file
code/src/services/mailService.ts
0 → 100644
View file @
e627251c
import
nodemailer
from
'nodemailer'
;
export
class
MailService
{
private
transporter
=
nodemailer
.
createTransport
({
service
:
'gmail'
,
auth
:
{
user
:
process
.
env
.
EMAIL_USER
,
pass
:
process
.
env
.
EMAIL_PASS
}
});
async
sendOtpEmail
(
toEmail
:
string
,
otp
:
string
)
{
try
{
const
mailOptions
=
{
from
:
`"Hệ thống xác thực" <
${
process
.
env
.
EMAIL_USER
}
>`
,
to
:
toEmail
,
subject
:
'Mã xác thực OTP của bạn'
,
html
:
`
<div style="font-family: sans-serif; line-height: 1.5; color: #333;">
<h2>Xác minh tài khoản</h2>
<p>Chào bạn,</p>
<p>Mã OTP để hoàn tất đăng ký/đăng nhập của bạn là:</p>
<p style="font-size: 32px; font-weight: bold; color: #007bff; letter-spacing: 4px;">
${
otp
}
</p>
<p>Mã này sẽ hết hạn sau <b>5 phút</b>.</p>
<p>Nếu bạn không thực hiện yêu cầu này, vui lòng bỏ qua email.</p>
<hr />
<small>Đây là email tự động, vui lòng không trả lời.</small>
</div>
`
,
};
await
this
.
transporter
.
sendMail
(
mailOptions
);
return
{
success
:
true
,
};
}
catch
(
error
)
{
console
.
error
(
'Lỗi gửi mail:'
,
error
);
return
{
success
:
false
,
error
:
(
error
as
Error
).
message
};
}
};
}
\ No newline at end of file
code/src/templates/swagger/config.ts
View file @
e627251c
...
...
@@ -6,6 +6,8 @@ import type { Options } from 'swagger-jsdoc';
import
registerSchemas
from
'./register/schemas.js'
;
import
loginSchemas
from
'./login/schemas.js'
;
import
authProfileSchemas
from
'./authProfile/schema.js'
;
import
sendOTPSchemas
from
'./sendOTP/schema.js'
;
import
verifyOTPSchemas
from
'./verifyOTP/schema.js'
;
const
swaggerOptions
:
Options
=
{
definition
:
{
...
...
@@ -28,6 +30,8 @@ const swaggerOptions: Options = {
...
registerSchemas
,
...
loginSchemas
,
...
authProfileSchemas
,
...
sendOTPSchemas
,
...
verifyOTPSchemas
,
},
parameters
:
{
filters
:
{
...
...
code/src/templates/swagger/sendOTP/schema.ts
0 → 100644
View file @
e627251c
const
sendOTPSchemas
=
{
sendOtpResponse
:
{
type
:
'object'
,
example
:
{
email
:
'phamquangbao@example.com'
,
message
:
'OTP đã được gửi đến email của bạn'
,
},
properties
:
{
email
:
{
type
:
'string'
,
format
:
'email'
,
example
:
'phamquangbao@example.com'
,
},
message
:
{
type
:
'string'
,
example
:
'OTP đã được gửi đến email của bạn'
,
},
},
},
};
export
default
sendOTPSchemas
;
\ No newline at end of file
code/src/templates/swagger/verifyOTP/schema.ts
0 → 100644
View file @
e627251c
const
verifyOTPSchemas
=
{
verifyOtpResponse
:
{
type
:
'object'
,
example
:
{
message
:
'Xác minh OTP thành công'
,
},
properties
:
{
message
:
{
type
:
'string'
,
example
:
'Xác minh OTP thành công'
,
},
},
},
verifyOtpInput
:
{
type
:
'object'
,
example
:
{
otp
:
'123456'
,
},
properties
:
{
otp
:
{
type
:
'string'
,
example
:
'123456'
,
},
},
},
};
export
default
verifyOTPSchemas
;
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment