diff --git a/controllers/authController.go b/controllers/authController.go index 4b4553d..d1124ea 100644 --- a/controllers/authController.go +++ b/controllers/authController.go @@ -18,13 +18,13 @@ import ( var teacherCollection *mongo.Collection = database.OpenCollection(database.Client, "teachers") var studentCollection *mongo.Collection = database.OpenCollection(database.Client, "students") var contactCollection *mongo.Collection = database.OpenCollection(database.Client, "contacts") +var adminCollection *mongo.Collection = database.OpenCollection(database.Client, "admins") const SecretKey = "secret" func Enroll(c *fiber.Ctx) error { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - var data map[string]string + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) if err := c.BodyParser(&data); err != nil { cancel() @@ -35,6 +35,25 @@ func Enroll(c *fiber.Ctx) error { }) } + // Check if admin sent request + if data["aid"] == "" { + cancel() + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "success": false, + "message": "admin id required", + }) + } + + var admin models.Admin + err := adminCollection.FindOne(ctx, bson.M{"aid": data["aid"]}).Decode(&admin) + if err != nil { + cancel() + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "success": false, + "message": "invalid admin id", + }) + } + // Check minimum enroll field requirements are met if data["firstname"] == "" || data["lastname"] == "" || data["age"] == "" || data["gradelevel"] == "" || data["dob"] == "" || data["email"] == "" { cancel() @@ -67,6 +86,15 @@ func Enroll(c *fiber.Ctx) error { student.TempPassword = true // Send student personal email temp password + var sid string + for { + sid = GenerateID() + if ValidateID(sid) == true { + break + } + } + student.SID = sid + student.Created_at, _ = time.Parse(time.RFC3339, time.Now().Format(time.RFC3339)) student.Updated_at, _ = time.Parse(time.RFC3339, time.Now().Format(time.RFC3339)) student.ID = primitive.NewObjectID() @@ -89,9 +117,8 @@ func Enroll(c *fiber.Ctx) error { } func RegisterTeacher(c *fiber.Ctx) error { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - var data map[string]string + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) if err := c.BodyParser(&data); err != nil { cancel() @@ -102,6 +129,25 @@ func RegisterTeacher(c *fiber.Ctx) error { }) } + // Check if admin sent request + if data["aid"] == "" { + cancel() + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "success": false, + "message": "admin id required", + }) + } + + var admin models.Admin + err := adminCollection.FindOne(ctx, bson.M{"aid": data["aid"]}).Decode(&admin) + if err != nil { + cancel() + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "success": false, + "message": "invalid admin id", + }) + } + // Check minimum register teacher field requirements are met if data["firstname"] == "" || data["lastname"] == "" || data["dob"] == "" || data["email"] == "" { cancel() @@ -123,6 +169,15 @@ func RegisterTeacher(c *fiber.Ctx) error { teacher.TempPassword = true // Send teacher personal email temp password + var tid string + for { + tid = GenerateID() + if ValidateID(tid) == true { + break + } + } + teacher.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)) teacher.ID = primitive.NewObjectID() @@ -144,6 +199,87 @@ func RegisterTeacher(c *fiber.Ctx) error { }) } +func CreateAdmin(c *fiber.Ctx) error { + 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 if admin sent request + if data["aid"] == "" { + cancel() + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "success": false, + "message": "admin id required", + }) + } + + var checkadmin models.Admin + err := adminCollection.FindOne(ctx, bson.M{"aid": data["aid"]}).Decode(&checkadmin) + if err != nil { + cancel() + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "success": false, + "message": "invalid admin id", + }) + } + + // Check minimum register teacher field requirements are met + if data["firstname"] == "" || data["lastname"] == "" || data["dob"] == "" || data["email"] == "" { + cancel() + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "success": false, + "message": "missing required fields", + }) + } + + var admin models.Admin + admin.FirstName = data["firstname"] + admin.LastName = data["lastname"] + admin.Email = data["email"] + + tempPass := admin.GeneratePassword(12, 1, 1, 1) + admin.Password = admin.HashPassword(tempPass) + admin.TempPassword = true + // Send teacher personal email temp password + + var aid string + for { + aid = GenerateID() + if ValidateID(aid) == true { + break + } + } + admin.AID = aid + + admin.Created_at, _ = time.Parse(time.RFC3339, time.Now().Format(time.RFC3339)) + admin.Updated_at, _ = time.Parse(time.RFC3339, time.Now().Format(time.RFC3339)) + admin.ID = primitive.NewObjectID() + + _, insertErr := adminCollection.InsertOne(ctx, admin) + if insertErr != nil { + cancel() + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "success": false, + "message": "the admin could not be inserted", + "error": insertErr, + }) + } + defer cancel() + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "success": true, + "message": "successfully inserted admin", + }) +} + func StudentLogin(c *fiber.Ctx) error { var data map[string]string ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) @@ -237,7 +373,7 @@ func TeacherLogin(c *fiber.Ctx) error { } var teacher models.Teacher - err := studentCollection.FindOne(ctx, bson.M{"tid": data["tid"]}).Decode(&teacher) + err := teacherCollection.FindOne(ctx, bson.M{"tid": data["tid"]}).Decode(&teacher) defer cancel() if err != nil { @@ -284,6 +420,76 @@ func TeacherLogin(c *fiber.Ctx) error { }) } +func AdminLogin(c *fiber.Ctx) error { + 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 + if data["aid"] == "" || data["password"] == "" { + cancel() + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "success": false, + "message": "missing required fields", + }) + } + + var admin models.Admin + err := adminCollection.FindOne(ctx, bson.M{"aid": data["aid"]}).Decode(&admin) + defer cancel() + + if err != nil { + cancel() + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "success": false, + "message": "admin not found", + "error": err, + }) + } + defer cancel() + + var verified bool = admin.ComparePasswords(data["password"]) + if verified == false { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "success": false, + "message": "incorrect password", + }) + } + + claims := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{ + Issuer: admin.AID, + ExpiresAt: time.Now().Add(time.Hour * 24).Unix(), // 1 Day + }) + token, err := claims.SignedString([]byte(SecretKey)) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "success": false, + "message": "could not log in", + }) + } + + cookie := fiber.Cookie{ + Name: "jwt", + Value: token, + Expires: time.Now().Add(time.Hour * 24), + HTTPOnly: true, + } + c.Cookie(&cookie) + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "success": true, + "message": "correct password", + }) +} + func Student(c *fiber.Ctx) error { cookie := c.Cookies("jwt") @@ -346,6 +552,37 @@ func Teacher(c *fiber.Ctx) error { }) } +func Admin(c *fiber.Ctx) error { + cookie := c.Cookies("jwt") + + token, err := jwt.ParseWithClaims(cookie, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) { + return []byte(SecretKey), nil + }) + if err != nil { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "success": false, + "message": "not authorized", + }) + } + + claims := token.Claims.(*jwt.StandardClaims) + + var admin models.Admin + findErr := adminCollection.FindOne(context.TODO(), bson.M{"aid": claims.Issuer}).Decode(&admin) + if findErr != nil { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "success": false, + "message": "admin not found", + }) + } + + return c.Status(fiber.StatusAccepted).JSON(fiber.Map{ + "success": true, + "message": "successfully logged into admin", + "result": admin, + }) +} + // Should work for both teacher and student ends func Logout(c *fiber.Ctx) error { cookie := fiber.Cookie{ @@ -375,8 +612,27 @@ func UpdateStudentName(c *fiber.Ctx) error { }) } + // Check if admin sent request + if data["aid"] == "" { + cancel() + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "success": false, + "message": "admin id required", + }) + } + + var admin models.Admin + err := adminCollection.FindOne(ctx, bson.M{"aid": data["aid"]}).Decode(&admin) + if err != nil { + cancel() + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "success": false, + "message": "invalid admin id", + }) + } + // Check id and names are included - if data["_id"] == "" || data["firstname"] == "" || data["middlename"] == "" || data["lastname"] == "" { + if data["sid"] == "" || data["firstname"] == "" || data["middlename"] == "" || data["lastname"] == "" { cancel() return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, @@ -384,15 +640,6 @@ func UpdateStudentName(c *fiber.Ctx) error { }) } - studentObjectId, err := primitive.ObjectIDFromHex(data["_id"]) - if err != nil { - cancel() - return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ - "success": false, - "message": "student not found", - "error": err, - }) - } update_time, _ := time.Parse(time.RFC3339, time.Now().Format(time.RFC3339)) update := bson.M{ "$set": bson.M{ @@ -405,7 +652,7 @@ func UpdateStudentName(c *fiber.Ctx) error { result, updateErr := studentCollection.UpdateOne( ctx, - bson.M{"_id": studentObjectId}, + bson.M{"sid": data["sid"]}, update, ) if updateErr != nil { @@ -438,8 +685,27 @@ func UpdateStudentGradeLevel(c *fiber.Ctx) error { }) } + // Check if admin sent request + if data["aid"] == "" { + cancel() + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "success": false, + "message": "admin id required", + }) + } + + var admin models.Admin + err := adminCollection.FindOne(ctx, bson.M{"aid": data["aid"]}).Decode(&admin) + if err != nil { + cancel() + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "success": false, + "message": "invalid admin id", + }) + } + // Check required fields are included - if data["_id"] == "" || data["gradelevel"] == "" { + if data["sid"] == "" || data["gradelevel"] == "" { cancel() return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, @@ -447,15 +713,6 @@ func UpdateStudentGradeLevel(c *fiber.Ctx) error { }) } - studentObjectId, err := primitive.ObjectIDFromHex(data["_id"]) - if err != nil { - cancel() - return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ - "success": false, - "message": "student not found", - "error": err, - }) - } update_time, _ := time.Parse(time.RFC3339, time.Now().Format(time.RFC3339)) update := bson.M{ "$set": bson.M{ @@ -466,7 +723,7 @@ func UpdateStudentGradeLevel(c *fiber.Ctx) error { result, updateErr := studentCollection.UpdateOne( ctx, - bson.M{"_id": studentObjectId}, + bson.M{"sid": data["sid"]}, update, ) if updateErr != nil { @@ -590,6 +847,25 @@ func UpdateTeacherName(c *fiber.Ctx) error { }) } + // Check if admin sent request + if data["aid"] == "" { + cancel() + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "success": false, + "message": "admin id required", + }) + } + + var admin models.Admin + err := adminCollection.FindOne(ctx, bson.M{"aid": data["aid"]}).Decode(&admin) + if err != nil { + cancel() + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "success": false, + "message": "invalid admin id", + }) + } + // Check id and names are included if data["_id"] == "" || data["firstname"] == "" || data["middlename"] == "" || data["lastname"] == "" { cancel() @@ -599,13 +875,13 @@ func UpdateTeacherName(c *fiber.Ctx) error { }) } - teacherObjectId, err := primitive.ObjectIDFromHex(data["_id"]) - if err != nil { + teacherObjectId, idErr := primitive.ObjectIDFromHex(data["_id"]) + if idErr != nil { cancel() return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ "success": false, "message": "teacher not found", - "error": err, + "error": idErr, }) } update_time, _ := time.Parse(time.RFC3339, time.Now().Format(time.RFC3339)) @@ -653,6 +929,25 @@ func CreateContact(c *fiber.Ctx) error { }) } + // Check if admin sent request + if data["aid"] == "" { + cancel() + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "success": false, + "message": "admin id required", + }) + } + + var admin models.Admin + err := adminCollection.FindOne(ctx, bson.M{"aid": data["aid"]}).Decode(&admin) + if err != nil { + cancel() + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "success": false, + "message": "invalid admin id", + }) + } + // Check required fields are included if data["sid"] == "" || data["firstname"] == "" || data["lastname"] == "" || data["homephone"] == "" || data["email"] == "" || data["priority"] == "" || data["relation"] == "" { cancel() @@ -724,16 +1019,6 @@ func RemoveContact(c *fiber.Ctx) error { } func UpdateContactName(c *fiber.Ctx) error { - var data map[string]string - - if err := c.BodyParser(&data); err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ - "success": false, - "message": "Failed to parse body", - "error": err, - }) - } - return c.Status(fiber.StatusNotImplemented).JSON(fiber.Map{ "success": nil, "message": "not implimented", @@ -741,16 +1026,6 @@ func UpdateContactName(c *fiber.Ctx) error { } func UpdateContactAddress(c *fiber.Ctx) error { - var data map[string]string - - if err := c.BodyParser(&data); err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ - "success": false, - "message": "Failed to parse body", - "error": err, - }) - } - return c.Status(fiber.StatusNotImplemented).JSON(fiber.Map{ "success": nil, "message": "not implimented", @@ -758,16 +1033,6 @@ func UpdateContactAddress(c *fiber.Ctx) error { } func UpdateContactPhone(c *fiber.Ctx) error { - var data map[string]string - - if err := c.BodyParser(&data); err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ - "success": false, - "message": "Failed to parse body", - "error": err, - }) - } - return c.Status(fiber.StatusNotImplemented).JSON(fiber.Map{ "success": nil, "message": "not implimented", @@ -775,16 +1040,6 @@ func UpdateContactPhone(c *fiber.Ctx) error { } func UpdateContactEmail(c *fiber.Ctx) error { - var data map[string]string - - if err := c.BodyParser(&data); err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ - "success": false, - "message": "Failed to parse body", - "error": err, - }) - } - return c.Status(fiber.StatusNotImplemented).JSON(fiber.Map{ "success": nil, "message": "not implimented", @@ -792,16 +1047,6 @@ func UpdateContactEmail(c *fiber.Ctx) error { } func UpdateContactPriority(c *fiber.Ctx) error { - var data map[string]string - - if err := c.BodyParser(&data); err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ - "success": false, - "message": "Failed to parse body", - "error": err, - }) - } - return c.Status(fiber.StatusNotImplemented).JSON(fiber.Map{ "success": nil, "message": "not implimented", diff --git a/controllers/idManager.go b/controllers/idManager.go new file mode 100644 index 0000000..8019df0 --- /dev/null +++ b/controllers/idManager.go @@ -0,0 +1,47 @@ +package controllers + +import ( + "context" + "crypto/rand" + "io" + "school-management/database" + "school-management/models" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" +) + +var idCollection *mongo.Collection = database.OpenCollection(database.Client, "cids") + +var table = [...]byte{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'} + +func ValidateID(id string) bool { // true: valid id, false: id already in use + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + err := idCollection.FindOne(ctx, bson.M{"cid": id}) + cancel() + if err != nil && err.Err() == mongo.ErrNoDocuments { + var newID models.Id + newID.CID = id + newID.ID = primitive.NewObjectID() + _, insertErr := idCollection.InsertOne(ctx, newID) + if insertErr != nil { + return false + } + return true + } + return false +} + +func GenerateID() string { + b := make([]byte, 6) + n, err := io.ReadAtLeast(rand.Reader, b, 6) + if n != 6 { + panic(err) + } + for i := 0; i < len(b); i++ { + b[i] = table[int(b[i])%len(table)] + } + return string(b) +} diff --git a/models/adminModel.go b/models/adminModel.go new file mode 100644 index 0000000..23d45be --- /dev/null +++ b/models/adminModel.go @@ -0,0 +1,93 @@ +package models + +import ( + "math/rand" + "strings" + "time" + "unicode" + + "golang.org/x/crypto/bcrypt" + + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type Admin struct { + ID primitive.ObjectID `bson:"_id"` + FirstName string `json:"firstname" validate:"required"` + LastName string `json:"lastname" validate:"required"` + Email string `json:"email" validate:"required"` + Password string `json:"-" validate:"min=10,max=32"` + TempPassword bool `json:"temppassword"` + AID string `json:"aid"` + Created_at time.Time `json:"created_at"` + Updated_at time.Time `json:"updated_at"` +} + +func (s *Admin) HashPassword(password string) string { + hash, _ := bcrypt.GenerateFromPassword([]byte(password), 14) + return string(hash) +} + +func (s *Admin) ComparePasswords(password string) bool { + err := bcrypt.CompareHashAndPassword([]byte(s.Password), []byte(password)) + if err != nil { + return false + } + return true +} + +func (s *Admin) CheckPasswordStrength(password string) bool { + + var hasUpper bool = true + for _, r := range password { + if !unicode.IsUpper(r) && unicode.IsLetter(r) { + hasUpper = false + } + } + + var hasLower bool = true + for _, r := range password { + if !unicode.IsLower(r) && unicode.IsLetter(r) { + hasLower = false + } + } + + if strings.ContainsAny(password, specialCharSet) && hasLower && hasUpper { + return true + } else { + return false + } +} + +func (s *Admin) GeneratePassword(passwordLength, minSpecialChar, minNum, minUpperCase int) string { + var password strings.Builder + + //Set special character + for i := 0; i < minSpecialChar; i++ { + random := rand.Intn(len(specialCharSet)) + password.WriteString(string(specialCharSet[random])) + } + + //Set numeric + for i := 0; i < minNum; i++ { + random := rand.Intn(len(numberSet)) + password.WriteString(string(numberSet[random])) + } + + //Set uppercase + for i := 0; i < minUpperCase; i++ { + random := rand.Intn(len(upperCharSet)) + password.WriteString(string(upperCharSet[random])) + } + + remainingLength := passwordLength - minSpecialChar - minNum - minUpperCase + for i := 0; i < remainingLength; i++ { + random := rand.Intn(len(allCharSet)) + password.WriteString(string(allCharSet[random])) + } + inRune := []rune(password.String()) + rand.Shuffle(len(inRune), func(i, j int) { + inRune[i], inRune[j] = inRune[j], inRune[i] + }) + return string(inRune) +} diff --git a/models/idModel.go b/models/idModel.go new file mode 100644 index 0000000..976d882 --- /dev/null +++ b/models/idModel.go @@ -0,0 +1,10 @@ +package models + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type Id struct { + ID primitive.ObjectID `bson:"_id"` + CID string `bson:"cid"` // custom id for admin, teahcer or student +}