2024-09-13 13:03:35 +00:00
import response from "../../response.js" ;
2024-11-04 04:33:57 +00:00
import bcrypt from "bcryptjs" ;
2024-09-13 13:03:35 +00:00
import jwt from "jsonwebtoken" ;
import nodemailer from "nodemailer" ;
2024-11-20 01:11:01 +00:00
import moment from "moment-timezone" ;
2024-09-13 13:03:35 +00:00
import models from "../../models/index.js" ;
const transporter = nodemailer . createTransport ( {
service : "gmail" ,
auth : {
user : process . env . EMAIL _USER ,
pass : process . env . EMAIL _PASS ,
} ,
} ) ;
export const registerAdmin = async ( req , res ) => {
2024-09-19 10:04:18 +00:00
const { NAME _USERS , EMAIL , PASSWORD , CONFIRM _PASSWORD } = req . body ;
2024-09-13 13:03:35 +00:00
2024-09-19 10:04:18 +00:00
if ( ! NAME _USERS ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Name is required!" , res ) ;
}
2024-09-19 10:04:18 +00:00
if ( ! EMAIL ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Email is required!" , res ) ;
}
2024-09-19 10:04:18 +00:00
if ( ! PASSWORD ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Password is required!" , res ) ;
}
2024-09-19 10:04:18 +00:00
if ( ! CONFIRM _PASSWORD ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Confirm Password is required!" , res ) ;
}
2024-09-19 10:04:18 +00:00
if ( PASSWORD !== CONFIRM _PASSWORD ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Passwords do not match!" , res ) ;
}
try {
const salt = await bcrypt . genSalt ( 10 ) ;
2024-09-19 10:04:18 +00:00
const hashedPassword = await bcrypt . hash ( PASSWORD , salt ) ;
2024-09-13 13:03:35 +00:00
const newUser = await models . User . create ( {
2024-09-19 10:04:18 +00:00
NAME _USERS : NAME _USERS ,
EMAIL : EMAIL ,
2024-09-13 13:03:35 +00:00
PASSWORD : hashedPassword ,
ROLE : "admin" ,
2024-11-20 01:11:01 +00:00
IS _VALIDATED : 1 ,
2024-09-13 13:03:35 +00:00
} ) ;
const adminResponse = {
2024-09-19 10:04:18 +00:00
ID : newUser . ID ,
NAME _USERS : newUser . NAME _USERS ,
EMAIL : newUser . EMAIL ,
ROLE : newUser . ROLE ,
2024-11-20 01:11:01 +00:00
IS _VALIDATED : newUser . IS _VALIDATED ,
2024-09-13 13:03:35 +00:00
} ;
response ( 200 , adminResponse , "Admin registration successful" , res ) ;
} catch ( error ) {
console . log ( error ) ;
if ( error . name === "SequelizeUniqueConstraintError" ) {
return response ( 400 , null , "Email already registered!" , res ) ;
}
response ( 500 , null , "Internal Server Error" , res ) ;
}
} ;
export const registerTeacher = async ( req , res ) => {
2024-09-19 10:04:18 +00:00
const { NAME _USERS , EMAIL , NIP , PASSWORD , CONFIRM _PASSWORD } = req . body ;
2024-09-13 13:03:35 +00:00
2024-09-19 10:04:18 +00:00
if ( ! NAME _USERS ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Name is required!" , res ) ;
}
2024-09-19 10:04:18 +00:00
if ( ! EMAIL ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Email is required!" , res ) ;
}
2024-09-19 10:04:18 +00:00
if ( ! NIP ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "NIP is required for teachers!" , res ) ;
}
2024-09-19 10:04:18 +00:00
if ( ! PASSWORD ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Password is required!" , res ) ;
}
2024-09-19 10:04:18 +00:00
if ( ! CONFIRM _PASSWORD ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Confirm Password is required!" , res ) ;
}
2024-09-19 10:04:18 +00:00
if ( PASSWORD !== CONFIRM _PASSWORD ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Passwords do not match!" , res ) ;
}
const transaction = await models . db . transaction ( ) ;
try {
const salt = await bcrypt . genSalt ( 10 ) ;
2024-09-19 10:04:18 +00:00
const hashedPassword = await bcrypt . hash ( PASSWORD , salt ) ;
2024-09-13 13:03:35 +00:00
const newUser = await models . User . create (
{
2024-09-19 10:04:18 +00:00
NAME _USERS : NAME _USERS ,
EMAIL : EMAIL ,
2024-09-13 13:03:35 +00:00
PASSWORD : hashedPassword ,
ROLE : "teacher" ,
2024-11-07 02:18:27 +00:00
IS _VALIDATED : 0 ,
} ,
{ transaction }
) ;
await models . Teacher . create (
{
ID : newUser . ID ,
NIP : NIP ,
} ,
{ transaction }
) ;
await transaction . commit ( ) ;
2024-11-20 01:11:01 +00:00
const now = moment ( ) . tz ( "Asia/Jakarta" ) ;
const midnight = now . clone ( ) . endOf ( "day" ) ;
const secondsUntilMidnight = midnight . diff ( now , "seconds" ) ;
2024-11-07 02:18:27 +00:00
const token = jwt . sign (
{ userId : newUser . ID } ,
process . env . VERIFY _TOKEN _SECRET ,
2024-11-20 01:11:01 +00:00
{ expiresIn : secondsUntilMidnight }
2024-11-07 02:18:27 +00:00
) ;
const validationLink = ` ${ process . env . CLIENT _URL } /validate-email?token= ${ token } ` ;
await transporter . sendMail ( {
from : process . env . EMAIL _USER ,
to : EMAIL ,
subject : "Email Verification" ,
html : `
< ! DOCTYPE html >
< html >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > Email Verification < / t i t l e >
< style >
body {
margin : 0 ;
padding : 0 ;
font - family : 'Segoe UI' , Arial , sans - serif ;
line - height : 1.6 ;
background - color : # f5f5f5 ;
}
. container {
max - width : 600 px ;
margin : 20 px auto ;
padding : 0 ;
background - color : # ffffff ;
border - radius : 10 px ;
box - shadow : 0 2 px 10 px rgba ( 0 , 0 , 0 , 0.1 ) ;
}
. header {
background - color : # 0090 FF ;
padding : 25 px 20 px ;
text - align : center ;
border - radius : 10 px 10 px 0 0 ;
}
. header h1 {
color : # ffffff ;
margin : 0 ;
font - size : 32 px ;
letter - spacing : 2 px ;
font - weight : bold ;
}
. content {
padding : 40 px 30 px ;
background - color : # ffffff ;
color : # 333333 ;
}
. content h2 {
color : # 0090 FF ;
margin - top : 0 ;
font - size : 24 px ;
margin - bottom : 20 px ;
}
. content p {
margin - bottom : 15 px ;
font - size : 16 px ;
line - height : 1.7 ;
}
. button {
display : inline - block ;
padding : 12 px 35 px ;
background - color : # 0090 FF ;
color : # ffffff ! important ;
text - decoration : none ;
border - radius : 5 px ;
margin : 25 px 0 ;
font - size : 16 px ;
font - weight : bold ;
text - align : center ;
transition : background - color 0.3 s ease ;
box - shadow : 0 2 px 5 px rgba ( 0 , 144 , 255 , 0.3 ) ;
}
. button : hover {
background - color : # 007 acc ;
}
. footer {
background - color : # 0090 FF ;
color : # ffffff ;
padding : 25 px 20 px ;
text - align : center ;
border - radius : 0 0 10 px 10 px ;
font - size : 14 px ;
}
. signature {
margin - top : 30 px ;
padding - top : 20 px ;
border - top : 1 px solid # eee ;
font - weight : bold ;
color : # 0090 FF ;
}
. important - note {
background - color : # f8f9fa ;
padding : 15 px ;
border - left : 4 px solid # 0090 FF ;
margin : 20 px 0 ;
}
< / s t y l e >
< / h e a d >
< body >
< div class = "container" >
< div class = "header" >
< h1 > SEALS < / h 1 >
< / d i v >
< div class = "content" >
< h2 > Hello , $ { NAME _USERS } ! 👋 < / h 2 >
< p > Welcome to SEALS ! We ' re excited to have you on board . < / p >
< p > To get started , please verify your email address by clicking the button below : < / p >
< div style = "text-align: center;" >
< a href = "${validationLink}" class = "button" > Verify Email < / a >
< / d i v >
< div class = "important-note" >
2024-11-20 01:11:01 +00:00
< p style = "margin: 0;" > < strong > Important : < / s t r o n g > T h i s v e r i f i c a t i o n l i n k w i l l e x p i r e a t 1 2 : 0 0 A M W I B . I f y o u d o n ' t c o m p l e t e t h e v e r i f i c a t i o n b y t h i s t i m e , y o u ' l l n e e d t o r e g i s t e r a g a i n . < / p >
2024-11-07 02:18:27 +00:00
< / d i v >
< p > If you didn ' t create an account with SEALS , please ignore this email . < / p >
< div class = "signature" >
< p > Thank you for choosing SEALS ! < / p >
< p style = "margin: 5px 0 0 0;" > The SEALS Team < / p >
< / d i v >
< / d i v >
< div class = "footer" >
< p style = "margin: 0;" > & copy ; 2024 SEALS . All rights reserved . < / p >
< / d i v >
< / d i v >
< / b o d y >
< / h t m l >
` ,
} ) ;
response ( 200 , null , "Teacher registered! Please verify your email." , res ) ;
} catch ( error ) {
console . log ( error ) ;
await transaction . rollback ( ) ;
if ( error . name === "SequelizeUniqueConstraintError" ) {
const field = error . original . sqlMessage . match ( /for key '(.+)'/ ) [ 1 ] ;
if ( field === "teacher_unique_nip" ) {
return response ( 400 , null , "NIP already registered!" , res ) ;
}
if ( field === "user_unique_email" ) {
return response ( 400 , null , "Email already registered!" , res ) ;
}
}
response ( 500 , null , "Internal Server Error" , res ) ;
}
} ;
export const registerStudent = async ( req , res ) => {
const { NAME _USERS , EMAIL , NISN , PASSWORD , CONFIRM _PASSWORD } = req . body ;
if ( ! NAME _USERS ) {
return response ( 400 , null , "Name is required!" , res ) ;
}
if ( ! EMAIL ) {
return response ( 400 , null , "Email is required!" , res ) ;
}
if ( ! NISN ) {
return response ( 400 , null , "NISN is required for students!" , res ) ;
}
if ( ! PASSWORD ) {
return response ( 400 , null , "Password is required!" , res ) ;
}
if ( ! CONFIRM _PASSWORD ) {
return response ( 400 , null , "Confirm Password is required!" , res ) ;
}
if ( PASSWORD !== CONFIRM _PASSWORD ) {
return response ( 400 , null , "Passwords do not match!" , res ) ;
}
const transaction = await models . db . transaction ( ) ;
try {
const salt = await bcrypt . genSalt ( 10 ) ;
const hashedPassword = await bcrypt . hash ( PASSWORD , salt ) ;
const newUser = await models . User . create (
{
NAME _USERS : NAME _USERS ,
EMAIL : EMAIL ,
PASSWORD : hashedPassword ,
ROLE : "student" ,
IS _VALIDATED : 0 ,
} ,
{ transaction }
) ;
await models . Student . create (
{
ID : newUser . ID ,
NISN : NISN ,
} ,
{ transaction }
) ;
await transaction . commit ( ) ;
2024-11-20 01:11:01 +00:00
const now = moment ( ) . tz ( "Asia/Jakarta" ) ;
const midnight = now . clone ( ) . endOf ( "day" ) ;
const secondsUntilMidnight = midnight . diff ( now , "seconds" ) ;
2024-11-07 02:18:27 +00:00
const token = jwt . sign (
{ userId : newUser . ID } ,
process . env . VERIFY _TOKEN _SECRET ,
2024-11-20 01:11:01 +00:00
{ expiresIn : secondsUntilMidnight }
2024-11-07 02:18:27 +00:00
) ;
const validationLink = ` ${ process . env . CLIENT _URL } /validate-email?token= ${ token } ` ;
await transporter . sendMail ( {
from : process . env . EMAIL _USER ,
to : EMAIL ,
subject : "Email Verification" ,
html : `
< ! DOCTYPE html >
< html >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > Email Verification < / t i t l e >
< style >
body {
margin : 0 ;
padding : 0 ;
font - family : 'Segoe UI' , Arial , sans - serif ;
line - height : 1.6 ;
background - color : # f5f5f5 ;
}
. container {
max - width : 600 px ;
margin : 20 px auto ;
padding : 0 ;
background - color : # ffffff ;
border - radius : 10 px ;
box - shadow : 0 2 px 10 px rgba ( 0 , 0 , 0 , 0.1 ) ;
}
. header {
background - color : # 0090 FF ;
padding : 25 px 20 px ;
text - align : center ;
border - radius : 10 px 10 px 0 0 ;
}
. header h1 {
color : # ffffff ;
margin : 0 ;
font - size : 32 px ;
letter - spacing : 2 px ;
font - weight : bold ;
}
. content {
padding : 40 px 30 px ;
background - color : # ffffff ;
color : # 333333 ;
}
. content h2 {
color : # 0090 FF ;
margin - top : 0 ;
font - size : 24 px ;
margin - bottom : 20 px ;
}
. content p {
margin - bottom : 15 px ;
font - size : 16 px ;
line - height : 1.7 ;
}
. button {
display : inline - block ;
padding : 12 px 35 px ;
background - color : # 0090 FF ;
color : # ffffff ! important ;
text - decoration : none ;
border - radius : 5 px ;
margin : 25 px 0 ;
font - size : 16 px ;
font - weight : bold ;
text - align : center ;
transition : background - color 0.3 s ease ;
box - shadow : 0 2 px 5 px rgba ( 0 , 144 , 255 , 0.3 ) ;
}
. button : hover {
background - color : # 007 acc ;
}
. footer {
background - color : # 0090 FF ;
color : # ffffff ;
padding : 25 px 20 px ;
text - align : center ;
border - radius : 0 0 10 px 10 px ;
font - size : 14 px ;
}
. signature {
margin - top : 30 px ;
padding - top : 20 px ;
border - top : 1 px solid # eee ;
font - weight : bold ;
color : # 0090 FF ;
}
. important - note {
background - color : # f8f9fa ;
padding : 15 px ;
border - left : 4 px solid # 0090 FF ;
margin : 20 px 0 ;
}
< / s t y l e >
< / h e a d >
< body >
< div class = "container" >
< div class = "header" >
< h1 > SEALS < / h 1 >
< / d i v >
< div class = "content" >
< h2 > Hello , $ { NAME _USERS } ! 👋 < / h 2 >
< p > Welcome to SEALS ! We ' re excited to have you on board . < / p >
< p > To get started , please verify your email address by clicking the button below : < / p >
< div style = "text-align: center;" >
< a href = "${validationLink}" class = "button" > Verify Email < / a >
< / d i v >
< div class = "important-note" >
2024-11-20 01:11:01 +00:00
< p style = "margin: 0;" > < strong > Important : < / s t r o n g > T h i s v e r i f i c a t i o n l i n k w i l l e x p i r e a t 1 2 : 0 0 A M W I B . I f y o u d o n ' t c o m p l e t e t h e v e r i f i c a t i o n b y t h i s t i m e , y o u ' l l n e e d t o r e g i s t e r a g a i n . < / p >
2024-11-07 02:18:27 +00:00
< / d i v >
< p > If you didn ' t create an account with SEALS , please ignore this email . < / p >
< div class = "signature" >
< p > Thank you for choosing SEALS ! < / p >
< p style = "margin: 5px 0 0 0;" > The SEALS Team < / p >
< / d i v >
< / d i v >
< div class = "footer" >
< p style = "margin: 0;" > & copy ; 2024 SEALS . All rights reserved . < / p >
< / d i v >
< / d i v >
< / b o d y >
< / h t m l >
` ,
} ) ;
response ( 200 , null , "Student registered! Please verify your email." , res ) ;
} catch ( error ) {
console . log ( error ) ;
await transaction . rollback ( ) ;
if ( error . name === "SequelizeUniqueConstraintError" ) {
const field = error . original . sqlMessage . match ( /for key '(.+)'/ ) [ 1 ] ;
if ( field === "student_unique_nisn" ) {
return response ( 400 , null , "NISN already registered!" , res ) ;
}
if ( field === "user_unique_email" ) {
return response ( 400 , null , "Email already registered!" , res ) ;
}
}
response ( 500 , null , "Internal Server Error" , res ) ;
}
} ;
export const validateEmail = async ( req , res ) => {
const { TOKEN } = req . body ;
try {
const decoded = jwt . verify ( TOKEN , process . env . VERIFY _TOKEN _SECRET ) ;
const userId = decoded . userId ;
await models . User . update ( { IS _VALIDATED : 1 } , { where : { ID : userId } } ) ;
response ( 200 , null , "Email successfully validated!" , res ) ;
} catch ( error ) {
response ( 400 , null , "Invalid or expired token" , res ) ;
}
} ;
export const registerTeacherForAdmin = async ( req , res ) => {
const { NAME _USERS , EMAIL , NIP , PASSWORD , CONFIRM _PASSWORD } = req . body ;
if ( ! NAME _USERS ) {
return response ( 400 , null , "Name is required!" , res ) ;
}
if ( ! EMAIL ) {
return response ( 400 , null , "Email is required!" , res ) ;
}
if ( ! NIP ) {
return response ( 400 , null , "NIP is required for teachers!" , res ) ;
}
if ( ! PASSWORD ) {
return response ( 400 , null , "Password is required!" , res ) ;
}
if ( ! CONFIRM _PASSWORD ) {
return response ( 400 , null , "Confirm Password is required!" , res ) ;
}
if ( PASSWORD !== CONFIRM _PASSWORD ) {
return response ( 400 , null , "Passwords do not match!" , res ) ;
}
const transaction = await models . db . transaction ( ) ;
try {
const salt = await bcrypt . genSalt ( 10 ) ;
const hashedPassword = await bcrypt . hash ( PASSWORD , salt ) ;
const newUser = await models . User . create (
{
NAME _USERS : NAME _USERS ,
EMAIL : EMAIL ,
PASSWORD : hashedPassword ,
ROLE : "teacher" ,
IS _VALIDATED : 1 ,
2024-09-13 13:03:35 +00:00
} ,
{ transaction }
) ;
await models . Teacher . create (
{
ID : newUser . ID ,
2024-09-19 10:04:18 +00:00
NIP : NIP ,
2024-09-13 13:03:35 +00:00
} ,
{ transaction }
) ;
await transaction . commit ( ) ;
const teacherResponse = {
2024-09-19 10:04:18 +00:00
ID : newUser . ID ,
NAME _USERS : newUser . NAME _USERS ,
EMAIL : newUser . EMAIL ,
NIP : NIP ,
ROLE : newUser . ROLE ,
2024-11-07 02:18:27 +00:00
IS _VALIDATED : newUser . IS _VALIDATED ,
2024-09-13 13:03:35 +00:00
} ;
response ( 200 , teacherResponse , "Teacher registration successful" , res ) ;
} catch ( error ) {
console . log ( error ) ;
await transaction . rollback ( ) ;
if ( error . name === "SequelizeUniqueConstraintError" ) {
const field = error . original . sqlMessage . match ( /for key '(.+)'/ ) [ 1 ] ;
if ( field === "teacher_unique_nip" ) {
return response ( 400 , null , "NIP already registered!" , res ) ;
}
if ( field === "user_unique_email" ) {
return response ( 400 , null , "Email already registered!" , res ) ;
}
}
response ( 500 , null , "Internal Server Error" , res ) ;
}
} ;
2024-11-07 02:18:27 +00:00
export const registerStudentForAdmin = async ( req , res ) => {
2024-09-19 10:04:18 +00:00
const { NAME _USERS , EMAIL , NISN , PASSWORD , CONFIRM _PASSWORD } = req . body ;
2024-09-13 13:03:35 +00:00
2024-09-19 10:04:18 +00:00
if ( ! NAME _USERS ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Name is required!" , res ) ;
}
2024-09-19 10:04:18 +00:00
if ( ! EMAIL ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Email is required!" , res ) ;
}
2024-09-19 10:04:18 +00:00
if ( ! NISN ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "NISN is required for students!" , res ) ;
}
2024-09-19 10:04:18 +00:00
if ( ! PASSWORD ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Password is required!" , res ) ;
}
2024-09-19 10:04:18 +00:00
if ( ! CONFIRM _PASSWORD ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Confirm Password is required!" , res ) ;
}
2024-09-19 10:04:18 +00:00
if ( PASSWORD !== CONFIRM _PASSWORD ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Passwords do not match!" , res ) ;
}
const transaction = await models . db . transaction ( ) ;
try {
const salt = await bcrypt . genSalt ( 10 ) ;
2024-09-19 10:04:18 +00:00
const hashedPassword = await bcrypt . hash ( PASSWORD , salt ) ;
2024-09-13 13:03:35 +00:00
const newUser = await models . User . create (
{
2024-09-19 10:04:18 +00:00
NAME _USERS : NAME _USERS ,
EMAIL : EMAIL ,
2024-09-13 13:03:35 +00:00
PASSWORD : hashedPassword ,
ROLE : "student" ,
2024-11-07 02:18:27 +00:00
IS _VALIDATED : 1 ,
2024-09-13 13:03:35 +00:00
} ,
{ transaction }
) ;
await models . Student . create (
{
ID : newUser . ID ,
2024-09-19 10:04:18 +00:00
NISN : NISN ,
2024-09-13 13:03:35 +00:00
} ,
{ transaction }
) ;
await transaction . commit ( ) ;
const studentResponse = {
2024-09-19 10:04:18 +00:00
ID : newUser . ID ,
NAME _USERS : newUser . NAME _USERS ,
EMAIL : newUser . EMAIL ,
NISN : NISN ,
ROLE : newUser . ROLE ,
2024-11-07 02:18:27 +00:00
IS _VALIDATED : newUser . IS _VALIDATED ,
2024-09-13 13:03:35 +00:00
} ;
response ( 200 , studentResponse , "Student registration successful" , res ) ;
} catch ( error ) {
console . log ( error ) ;
await transaction . rollback ( ) ;
if ( error . name === "SequelizeUniqueConstraintError" ) {
const field = error . original . sqlMessage . match ( /for key '(.+)'/ ) [ 1 ] ;
if ( field === "student_unique_nisn" ) {
return response ( 400 , null , "NISN already registered!" , res ) ;
}
if ( field === "user_unique_email" ) {
return response ( 400 , null , "Email already registered!" , res ) ;
}
}
response ( 500 , null , "Internal Server Error" , res ) ;
}
} ;
export const loginUser = async ( req , res ) => {
2024-09-19 10:04:18 +00:00
const { EMAIL , PASSWORD } = req . body ;
2024-09-13 13:03:35 +00:00
2024-09-19 10:04:18 +00:00
if ( ! EMAIL ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Email is required!" , res ) ;
}
2024-09-19 10:04:18 +00:00
if ( ! PASSWORD ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Password is required!" , res ) ;
}
try {
2024-09-19 10:04:18 +00:00
const user = await models . User . findOne ( { where : { EMAIL } } ) ;
2024-09-13 13:03:35 +00:00
if ( ! user ) {
return response ( 404 , null , "User data not found!" , res ) ;
}
2024-11-07 02:18:27 +00:00
if ( user . IS _VALIDATED !== 1 ) {
2024-11-20 01:11:01 +00:00
return response (
403 ,
null ,
"User is not validated! Please verify your email first." ,
res
) ;
2024-11-07 02:18:27 +00:00
}
2024-09-19 10:04:18 +00:00
const validPassword = await bcrypt . compare ( PASSWORD , user . PASSWORD ) ;
2024-09-13 13:03:35 +00:00
if ( ! validPassword ) {
return response ( 401 , null , "The password you entered is incorrect!" , res ) ;
}
const accessToken = jwt . sign (
2024-10-03 03:32:34 +00:00
{ ID : user . ID , ROLE : user . ROLE } ,
2024-09-13 13:03:35 +00:00
process . env . ACCESS _TOKEN _SECRET ,
2024-10-03 03:32:34 +00:00
{ expiresIn : "3h" }
) ;
const refreshToken = jwt . sign (
{ ID : user . ID , ROLE : user . ROLE } ,
process . env . REFRESH _TOKEN _SECRET ,
{ expiresIn : "7d" }
) ;
await models . User . update (
{ REFRESH _TOKEN : refreshToken } ,
{ where : { ID : user . ID } }
2024-09-13 13:03:35 +00:00
) ;
2024-10-03 04:09:35 +00:00
res . cookie ( "refreshToken" , refreshToken , {
httpOnly : true ,
secure : process . env . NODE _ENV === "production" ,
2024-11-07 02:18:27 +00:00
maxAge : 7 * 24 * 60 * 60 * 1000 ,
2024-10-03 04:09:35 +00:00
} ) ;
2024-09-13 13:03:35 +00:00
const userResponse = {
2024-09-19 10:04:18 +00:00
ID : user . ID ,
NAME _USERS : user . NAME _USERS ,
EMAIL : user . EMAIL ,
ROLE : user . ROLE ,
TOKEN : ` Bearer ${ accessToken } ` ,
2024-10-03 03:32:34 +00:00
REFRESH _TOKEN : refreshToken ,
2024-09-13 13:03:35 +00:00
} ;
response ( 200 , userResponse , "Login successful" , res ) ;
} catch ( error ) {
console . log ( error ) ;
response ( 500 , null , "Internal Server Error" , res ) ;
}
} ;
2024-10-03 03:32:34 +00:00
export const refreshToken = async ( req , res ) => {
2024-10-03 04:31:56 +00:00
const refreshToken = req . cookies ? . refreshToken || req . body ? . REFRESH _TOKEN ;
2024-10-03 03:32:34 +00:00
2024-10-03 04:09:35 +00:00
if ( ! refreshToken ) {
2024-10-03 03:32:34 +00:00
return response ( 400 , null , "Refresh token is required!" , res ) ;
}
try {
2024-10-03 04:09:35 +00:00
const user = await models . User . findOne ( {
where : { REFRESH _TOKEN : refreshToken } ,
} ) ;
2024-10-03 03:32:34 +00:00
if ( ! user ) {
return response ( 403 , null , "Invalid refresh token!" , res ) ;
}
2024-10-03 04:09:35 +00:00
let decoded ;
try {
decoded = jwt . verify ( refreshToken , process . env . REFRESH _TOKEN _SECRET ) ;
} catch ( err ) {
if ( err . name === "TokenExpiredError" ) {
return response (
401 ,
null ,
"Refresh token expired. Please login again." ,
2024-10-03 03:32:34 +00:00
res
) ;
}
2024-10-03 04:09:35 +00:00
return response ( 403 , null , "Invalid refresh token!" , res ) ;
}
if ( decoded . ID !== user . ID ) {
return response ( 403 , null , "Invalid refresh token data!" , res ) ;
}
const newAccessToken = jwt . sign (
{ ID : user . ID , ROLE : user . ROLE } ,
process . env . ACCESS _TOKEN _SECRET ,
{ expiresIn : "3h" }
) ;
const newRefreshToken = jwt . sign (
{ ID : user . ID , ROLE : user . ROLE } ,
process . env . REFRESH _TOKEN _SECRET ,
{ expiresIn : "7d" }
) ;
await models . User . update (
{ REFRESH _TOKEN : newRefreshToken } ,
{ where : { ID : user . ID } }
) ;
res . cookie ( "refreshToken" , newRefreshToken , {
httpOnly : true ,
secure : process . env . NODE _ENV === "production" ,
2024-11-07 02:18:27 +00:00
maxAge : 7 * 24 * 60 * 60 * 1000 ,
2024-10-03 04:09:35 +00:00
} ) ;
response (
200 ,
{ TOKEN : ` Bearer ${ newAccessToken } ` , REFRESH _TOKEN : newRefreshToken } ,
"Token refreshed successfully" ,
res
2024-10-03 03:32:34 +00:00
) ;
} catch ( error ) {
console . log ( error ) ;
response ( 500 , null , "Internal Server Error" , res ) ;
}
} ;
2024-09-13 13:03:35 +00:00
export const logoutUser = ( req , res ) => {
response ( 200 , null , "You have successfully logged out." , res ) ;
} ;
export const forgotPassword = async ( req , res ) => {
2024-09-19 10:04:18 +00:00
const { EMAIL } = req . body ;
2024-09-13 13:03:35 +00:00
2024-09-19 10:04:18 +00:00
if ( ! EMAIL ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Email is required!" , res ) ;
}
try {
2024-09-19 10:04:18 +00:00
const user = await models . User . findOne ( { where : { EMAIL : EMAIL } } ) ;
2024-09-13 13:03:35 +00:00
if ( ! user ) {
return response ( 404 , null , "Email is not registered!" , res ) ;
}
const resetToken = jwt . sign (
{ id : user . ID } ,
process . env . RESET _PASSWORD _SECRET ,
{
expiresIn : "1h" ,
}
) ;
2024-10-28 02:36:05 +00:00
const resetLink = ` ${ process . env . CLIENT _URL } /resetPassword/ ${ resetToken } ` ;
2024-09-13 13:03:35 +00:00
const mailOptions = {
from : process . env . EMAIL _USER ,
to : user . EMAIL ,
subject : "Password Reset" ,
2024-11-07 02:18:27 +00:00
html : `
< ! DOCTYPE html >
< html >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > Reset Password < / t i t l e >
< style >
body {
margin : 0 ;
padding : 0 ;
font - family : 'Segoe UI' , Arial , sans - serif ;
line - height : 1.6 ;
background - color : # f5f5f5 ;
}
. container {
max - width : 600 px ;
margin : 20 px auto ;
padding : 0 ;
background - color : # ffffff ;
border - radius : 10 px ;
box - shadow : 0 2 px 10 px rgba ( 0 , 0 , 0 , 0.1 ) ;
}
. header {
background - color : # 0090 FF ;
padding : 25 px 20 px ;
text - align : center ;
border - radius : 10 px 10 px 0 0 ;
}
. header h1 {
color : # ffffff ;
margin : 0 ;
font - size : 32 px ;
letter - spacing : 2 px ;
font - weight : bold ;
}
. content {
padding : 40 px 30 px ;
background - color : # ffffff ;
color : # 333333 ;
}
. content h2 {
color : # 0090 FF ;
margin - top : 0 ;
font - size : 24 px ;
margin - bottom : 20 px ;
}
. content p {
margin - bottom : 15 px ;
font - size : 16 px ;
line - height : 1.7 ;
}
. button {
display : inline - block ;
padding : 12 px 35 px ;
background - color : # 0090 FF ;
color : # ffffff ! important ;
text - decoration : none ;
border - radius : 5 px ;
margin : 25 px 0 ;
font - size : 16 px ;
font - weight : bold ;
text - align : center ;
transition : background - color 0.3 s ease ;
box - shadow : 0 2 px 5 px rgba ( 0 , 144 , 255 , 0.3 ) ;
}
. button : hover {
background - color : # 007 acc ;
}
. footer {
background - color : # 0090 FF ;
color : # ffffff ;
padding : 25 px 20 px ;
text - align : center ;
border - radius : 0 0 10 px 10 px ;
font - size : 14 px ;
}
. signature {
margin - top : 30 px ;
padding - top : 20 px ;
border - top : 1 px solid # eee ;
font - weight : bold ;
color : # 0090 FF ;
}
. important - note {
background - color : # f8f9fa ;
padding : 15 px ;
border - left : 4 px solid # 0090 FF ;
margin : 20 px 0 ;
}
. security - warning {
background - color : # fff3cd ;
padding : 15 px ;
border - left : 4 px solid # ffc107 ;
margin : 20 px 0 ;
font - size : 14 px ;
}
< / s t y l e >
< / h e a d >
< body >
< div class = "container" >
< div class = "header" >
< h1 > SEALS < / h 1 >
< / d i v >
< div class = "content" >
< h2 > Password Reset Request < / h 2 >
< p > Hello ! < / p >
< p > We received a request to reset the password for your SEALS account . To proceed with the password reset , please click the button below : < / p >
< div style = "text-align: center;" >
< a href = "${resetLink}" class = "button" > Reset Password < / a >
< / d i v >
< div class = "important-note" >
< p style = "margin: 0;" > < strong > Important : < / s t r o n g > T h i s r e s e t p a s s w o r d l i n k w i l l e x p i r e i n 1 h o u r . P l e a s e r e s e t y o u r p a s s w o r d a s s o o n a s p o s s i b l e t o m a i n t a i n a c c e s s t o y o u r a c c o u n t . < / p >
< / d i v >
< div class = "security-warning" >
< p style = "margin: 0;" > ⚠ ️ If you didn 't request a password reset, please ignore this email or contact our support team if you have concerns about your account' s security . < / p >
< / d i v >
< div class = "signature" >
< p > Thank you for using SEALS ! < / p >
< p style = "margin: 5px 0 0 0;" > The SEALS Team < / p >
< / d i v >
< / d i v >
< div class = "footer" >
< p style = "margin: 0;" > & copy ; 2024 SEALS . All rights reserved . < / p >
< / d i v >
< / d i v >
< / b o d y >
< / h t m l >
` ,
2024-09-13 13:03:35 +00:00
} ;
await transporter . sendMail ( mailOptions ) ;
response ( 200 , null , "Password reset email sent successfully!" , res ) ;
} catch ( error ) {
console . log ( error ) ;
response ( 500 , null , "Internal Server Error" , res ) ;
}
} ;
export const resetPassword = async ( req , res ) => {
2024-09-19 10:04:18 +00:00
const { TOKEN , NEW _PASSWORD , CONFIRM _NEW _PASSWORD } = req . body ;
2024-09-13 13:03:35 +00:00
2024-09-19 10:04:18 +00:00
if ( ! TOKEN ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Token is required!" , res ) ;
}
2024-09-19 10:04:18 +00:00
if ( ! NEW _PASSWORD ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "New password is required!" , res ) ;
}
2024-09-19 10:04:18 +00:00
if ( ! CONFIRM _NEW _PASSWORD ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Confirm new password is required!" , res ) ;
}
2024-09-19 10:04:18 +00:00
if ( NEW _PASSWORD !== CONFIRM _NEW _PASSWORD ) {
2024-09-13 13:03:35 +00:00
return response ( 400 , null , "Passwords do not match!" , res ) ;
}
try {
2024-09-19 10:04:18 +00:00
const decoded = jwt . verify ( TOKEN , process . env . RESET _PASSWORD _SECRET ) ;
2024-09-13 13:03:35 +00:00
const user = await models . User . findOne ( { where : { ID : decoded . id } } ) ;
if ( ! user ) {
return response ( 404 , null , "User data not found!" , res ) ;
}
const salt = await bcrypt . genSalt ( 10 ) ;
2024-09-19 10:04:18 +00:00
const hashedPassword = await bcrypt . hash ( NEW _PASSWORD , salt ) ;
2024-09-13 13:03:35 +00:00
user . PASSWORD = hashedPassword ;
await user . save ( ) ;
response ( 200 , null , "Password has been reset successfully!" , res ) ;
} catch ( error ) {
console . log ( error ) ;
if ( error . name === "TokenExpiredError" ) {
return response ( 400 , null , "Reset token has expired!" , res ) ;
} else {
return response ( 500 , null , "Internal Server Error" , res ) ;
}
}
} ;