diff --git a/controllers/authController.go b/controllers/authController.go index 18c9f57..64ac815 100644 --- a/controllers/authController.go +++ b/controllers/authController.go @@ -245,26 +245,51 @@ func RegisterTeacher(c *fiber.Ctx) error { } var teacher models.Teacher - teacher.FirstName = data["firstname"].(string) - teacher.MiddleName = data["middlename"].(string) - teacher.LastName = data["lastname"].(string) - teacher.Email = data["email"].(string) - teacher.Province = data["province"].(string) - teacher.City = data["city"].(string) - teacher.Postal = data["postal"].(string) - teacher.DOB = data["postal"].(string) + teacher.PersonalData.FirstName = data["firstname"].(string) + teacher.PersonalData.MiddleName = data["middlename"].(string) + teacher.PersonalData.LastName = data["lastname"].(string) + teacher.PersonalData.Email = data["email"].(string) + teacher.PersonalData.Province = data["province"].(string) + teacher.PersonalData.City = data["city"].(string) + teacher.PersonalData.Postal = data["postal"].(string) + teacher.PersonalData.DOB = data["postal"].(string) - teacher.SchoolEmail = teacher.GenerateSchoolEmail() + var photo models.Photo + photo.Name = uuid.New().String() + photo.Created_at, _ = time.Parse(time.RFC3339, time.Now().Format(time.RFC3339)) + photo.Updated_at, _ = time.Parse(time.RFC3339, time.Now().Format(time.RFC3339)) + photo.ID = primitive.NewObjectID() + + defaultImage, _ := os.ReadFile("./database/defaultImage.txt") + photo.Base64 = string(defaultImage) + + teacher.SchoolData.PhotoName = photo.Name + + var schoolEmail string = "" + offset := 0 + for { + schoolEmail = teacher.GenerateSchoolEmail(offset, schoolEmail) + if !teacher.EmailExists(schoolEmail) { + break + } + offset++ + } + teacher.AccountData.SchoolEmail = schoolEmail + teacher.AccountData.HashHistory = []string{} + + // Disable login block + teacher.AccountData.AccountDisabled = false + teacher.AccountData.Attempts = 0 var tempPass string = teacher.GeneratePassword(12, 1, 1, 1) - teacher.Password = teacher.HashPassword(tempPass) - teacher.TempPassword = true + teacher.AccountData.Password = teacher.HashPassword(tempPass) + teacher.AccountData.TempPassword = true // Send teacher personal email temp password message := []byte("Your temporary password is " + tempPass) auth := smtp.PlainAuth("", systemEmail, systemPassword, "smtp.gmail.com") - err := smtp.SendMail("smtp.gmail.com:587", auth, systemEmail, []string{teacher.Email}, message) + err := smtp.SendMail("smtp.gmail.com:587", auth, systemEmail, []string{teacher.PersonalData.Email}, message) if err != nil { cancel() return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ @@ -283,7 +308,7 @@ func RegisterTeacher(c *fiber.Ctx) error { break } } - teacher.TID = tid + teacher.SchoolData.TID = tid teacher.Created_at, _ = time.Parse(time.RFC3339, time.Now().Format(time.RFC3339)) teacher.Updated_at, _ = time.Parse(time.RFC3339, time.Now().Format(time.RFC3339)) @@ -541,7 +566,7 @@ func TeacherLogin(c *fiber.Ctx) error { } var teacher models.Teacher - err := teacherCollection.FindOne(ctx, bson.M{"tid": data["tid"]}).Decode(&teacher) + err := teacherCollection.FindOne(ctx, bson.M{"schooldata.tid": data["tid"]}).Decode(&teacher) defer cancel() if err != nil { @@ -563,7 +588,7 @@ func TeacherLogin(c *fiber.Ctx) error { } claims := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{ - Issuer: teacher.TID, + Issuer: teacher.SchoolData.TID, ExpiresAt: time.Now().Add(time.Hour * 24).Unix(), // 1 Day }) token, err := claims.SignedString([]byte(SecretKey)) @@ -762,7 +787,7 @@ func Teacher(c *fiber.Ctx) error { claims := token.Claims.(*jwt.StandardClaims) var teacher models.Teacher - findErr := teacherCollection.FindOne(context.TODO(), bson.M{"tid": claims.Issuer}).Decode(&teacher) + findErr := teacherCollection.FindOne(context.TODO(), bson.M{"schooldata.tid": claims.Issuer}).Decode(&teacher) if findErr != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, diff --git a/controllers/updateController.go b/controllers/updateController.go index 6e0ca58..6ba2c82 100644 --- a/controllers/updateController.go +++ b/controllers/updateController.go @@ -1083,7 +1083,7 @@ func UpdateTeacherHomeroom(c *fiber.Ctx) error { result, updateErr := teacherCollection.UpdateOne( ctx, - bson.M{"tid": data["tid"]}, + bson.M{"schooldata.tid": data["tid"]}, update, ) if updateErr != nil { @@ -1111,9 +1111,88 @@ func UpdateTeacherPassword(c *fiber.Ctx) error { } func ResetTeacherPassword(c *fiber.Ctx) error { - return c.Status(fiber.StatusNotImplemented).JSON(fiber.Map{ - "success": nil, - "message": "not implimented", + var data map[string]string + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + + if err := c.BodyParser(&data); err != nil { + cancel() + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "success": false, + "message": "Failed to parse body", + "error": err, + }) + } + + // Check required fields are included (email must be personal email) + if data["tid"] == "" || data["email"] == "" { + cancel() + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "success": false, + "message": "missing required fields", + }) + } + + var teacher models.Teacher + findErr := teacherCollection.FindOne(context.TODO(), bson.M{"schooldata.tid": data["tid"]}).Decode(&teacher) + if findErr != nil { + cancel() + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "success": false, + "message": "teacher not found", + }) + } + + if teacher.PersonalData.Email != data["email"] { + cancel() + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "success": false, + "message": "Your personal email is incorrect", + }) + } + + tempPass := teacher.GeneratePassword(12, 1, 1, 1) + update_time, _ := time.Parse(time.RFC3339, time.Now().Format(time.RFC3339)) + update := bson.M{ + "$set": bson.M{ + "accountdata.password": teacher.HashPassword(tempPass), + "accountdata.temppassword": true, + "updated_at": update_time, + }, + } + + result, updateErr := studentCollection.UpdateOne( + ctx, + bson.M{"schooldata.tid": data["tid"]}, + update, + ) + if updateErr != nil { + cancel() + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "success": false, + "message": "the teacher password could not be updated", + "error": updateErr, + }) + } + defer cancel() + + // Send student personal email temp password + message := []byte("Your temporary password is " + tempPass) + auth := smtp.PlainAuth("", systemEmail, systemPassword, "smtp.gmail.com") + + err := smtp.SendMail("smtp.gmail.com:587", auth, systemEmail, []string{teacher.PersonalData.Email}, message) + if err != nil { + cancel() + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "success": false, + "message": "Could not send password to teachers email", + "error": err, + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "success": true, + "message": "successfully updated teacher password", + "result": result, }) } @@ -1151,17 +1230,17 @@ func UpdateTeacherAddress(c *fiber.Ctx) error { update_time, _ := time.Parse(time.RFC3339, time.Now().Format(time.RFC3339)) update := bson.M{ "$set": bson.M{ - "address": data["address"], - "city": data["city"], - "province": data["province"], - "postal": data["postal"], - "updated_at": update_time, + "personaldata.address": data["address"], + "personaldata.city": data["city"], + "personaldata.province": data["province"], + "personaldata.postal": data["postal"], + "updated_at": update_time, }, } result, updateErr := teacherCollection.UpdateOne( ctx, - bson.M{"tid": data["tid"]}, + bson.M{"schooldata.tid": data["tid"]}, update, ) if updateErr != nil { @@ -1222,14 +1301,14 @@ func UpdateTeacherEmail(c *fiber.Ctx) error { update_time, _ := time.Parse(time.RFC3339, time.Now().Format(time.RFC3339)) update := bson.M{ "$set": bson.M{ - "email": data["email"], - "updated_at": update_time, + "personaldata.email": data["email"], + "updated_at": update_time, }, } result, updateErr := teacherCollection.UpdateOne( ctx, - bson.M{"tid": data["tid"]}, + bson.M{"schooldata.tid": data["tid"]}, update, ) if updateErr != nil { @@ -1292,10 +1371,10 @@ func UpdateTeacherName(c *fiber.Ctx) error { update_time, _ := time.Parse(time.RFC3339, time.Now().Format(time.RFC3339)) update := bson.M{ "$set": bson.M{ - "firstname": data["firstname"], - "middlename": data["middlename"], - "lastname": data["lastname"], - "updated_at": update_time, + "personaldata.firstname": data["firstname"], + "personaldata.middlename": data["middlename"], + "personaldata.lastname": data["lastname"], + "updated_at": update_time, }, } diff --git a/models/studentModel.go b/models/studentModel.go index c00826a..e4bef65 100644 --- a/models/studentModel.go +++ b/models/studentModel.go @@ -85,10 +85,10 @@ func (s *Student) GenerateSchoolEmail(offset int, lastEmail string) string { email = lastEmail[:offset] + strings.ToLower(string(s.PersonalData.FirstName[offset])) + lastEmail[offset:] } if offset == len([]rune(s.PersonalData.FirstName))-1 { - email = strings.ToLower(s.PersonalData.FirstName) + "." + strings.ToLower(s.PersonalData.LastName) + email = strings.ToLower(s.PersonalData.FirstName) + "." + strings.ToLower(s.PersonalData.LastName) + "@surreyschools.ca" } if offset > len([]rune(s.PersonalData.FirstName))-1 { - email = strings.ToLower(s.PersonalData.FirstName) + "." + strings.ToLower(s.PersonalData.LastName) + strconv.Itoa(offset-len([]rune(s.PersonalData.FirstName))) + email = strings.ToLower(s.PersonalData.FirstName) + "." + strings.ToLower(s.PersonalData.LastName) + strconv.Itoa(offset-len([]rune(s.PersonalData.FirstName))) + "@surreyschools.ca" } return email } diff --git a/models/teacherModel.go b/models/teacherModel.go index faac569..620bb3c 100644 --- a/models/teacherModel.go +++ b/models/teacherModel.go @@ -1,12 +1,17 @@ package models import ( + "context" "math/rand" + "strconv" "strings" "time" "unicode" + "github.com/SowinskiBraeden/school-management-api/database" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" "golang.org/x/crypto/bcrypt" ) @@ -18,25 +23,36 @@ var ( allCharSet = lowerCharSet + upperCharSet + specialCharSet + numberSet ) +var teacherCollection *mongo.Collection = database.OpenCollection(database.Client, "teachers") + type Teacher struct { ID primitive.ObjectID `bson:"_id"` - FirstName string `json:"firstname" validate:"required"` - MiddleName string `json:"middlename"` - LastName string `json:"lastname" validate:"required"` - Email string `json:"email" validate:"required"` - SchoolEmail string `json:"schoolemail"` - Password string `json:"-" validate:"min=10,max=32"` - TempPassword bool `json:"temppassword"` - TID string `json:"tid"` // Teacher ID - Homeroom string `json:"homeroom"` - Address string `json:"address"` - City string `json:"city"` - Province string `json:"province"` - Postal string `json:"postal"` - DOB string `json:"dob" validate:"required"` - Photo string `json:"string"` - Created_at time.Time `json:"created_at"` - Updated_at time.Time `json:"updated_at"` + PersonalData struct { + FirstName string `json:"firstname" validate:"required"` + MiddleName string `json:"middlename"` + LastName string `json:"lastname" validate:"required"` + Email string `json:"email" validate:"required"` + Address string `json:"address"` + City string `json:"city"` + Province string `json:"province"` + Postal string `json:"postal"` + DOB string `json:"dob" validate:"required"` + } `json:"personaldata"` + SchoolData struct { + TID string `json:"tid"` // Teacher ID + Homeroom string `json:"homeroom"` + PhotoName string `json:"photoname"` // name of photo in db + } `json:"schooldata"` + AccountData struct { + SchoolEmail string `json:"schoolemail"` + Password string `json:"-" validate:"min=10,max=32"` + AccountDisabled bool `bson:"accountdisabled"` + TempPassword bool `json:"temppassword"` + Attempts int `json:"attempts"` // login attempts max 5 + HashHistory []string `json:"-"` // List of old hashed passwords (not including auto generated passwords) + } `json:"accountdata"` + Created_at time.Time `json:"created_at"` + Updated_at time.Time `json:"updated_at"` } func (t *Teacher) HashPassword(password string) string { @@ -44,13 +60,31 @@ func (t *Teacher) HashPassword(password string) string { return string(hash) } -func (t *Teacher) GenerateSchoolEmail() string { - var email string = strings.ToLower(t.LastName) + "_" + strings.ToLower(string(t.FirstName[0])) + "@surreyschools.ca" +func (t *Teacher) EmailExists(email string) bool { + var teacher Teacher + findErr := teacherCollection.FindOne(context.TODO(), bson.M{"accountdata.schoolemail": email}).Decode(&teacher) + if findErr != nil { + return false + } + return true +} + +func (t *Teacher) GenerateSchoolEmail(offset int, lastEmail string) string { + var email string = strings.ToLower(t.PersonalData.LastName) + "_" + strings.ToLower(string(t.PersonalData.FirstName[0])) + "@surreyschools.ca" + if offset > 0 && offset < len([]rune(t.PersonalData.FirstName))-1 { + email = lastEmail[:strings.Index(lastEmail, "_")+offset+1] + strings.ToLower(string(t.PersonalData.FirstName[offset])) + "@surreyschools.ca" + } + if offset == len([]rune(t.PersonalData.FirstName))-1 { + email = strings.ToLower(t.PersonalData.LastName) + "_" + strings.ToLower(t.PersonalData.FirstName) + "@surreyschools.ca" + } + if offset > len([]rune(t.PersonalData.FirstName))-1 { + email = strings.ToLower(t.PersonalData.LastName) + "_" + strings.ToLower(t.PersonalData.FirstName) + strconv.Itoa(offset-len([]rune(t.PersonalData.FirstName))) + "@surreyschools.ca" + } return email } func (t *Teacher) ComparePasswords(password string) bool { - err := bcrypt.CompareHashAndPassword([]byte(t.Password), []byte(password)) + err := bcrypt.CompareHashAndPassword([]byte(t.AccountData.Password), []byte(password)) if err != nil { return false }