diff --git a/pkg/authkit/models/auth.go b/pkg/authkit/models/auth.go index 898eeac..43cbd87 100644 --- a/pkg/authkit/models/auth.go +++ b/pkg/authkit/models/auth.go @@ -16,6 +16,8 @@ type AuthFactorType = int8 const ( PasswordAuthFactor = AuthFactorType(iota) EmailPasswordFactor + InAppNotifyFactor + TimeOtpFactor ) type AuthFactor struct { diff --git a/pkg/internal/http/api/accounts_api.go b/pkg/internal/http/api/accounts_api.go index f3347e1..75400ac 100644 --- a/pkg/internal/http/api/accounts_api.go +++ b/pkg/internal/http/api/accounts_api.go @@ -83,39 +83,7 @@ func getUserinfo(c *fiber.Ctx) error { return c.JSON(resp) } -func getEvents(c *fiber.Ctx) error { - if err := exts.EnsureAuthenticated(c); err != nil { - return err - } - user := c.Locals("user").(models.Account) - take := c.QueryInt("take", 0) - offset := c.QueryInt("offset", 0) - - var count int64 - var events []models.ActionEvent - if err := database.C. - Where(&models.ActionEvent{AccountID: user.ID}). - Model(&models.ActionEvent{}). - Count(&count).Error; err != nil { - return fiber.NewError(fiber.StatusInternalServerError, err.Error()) - } - - if err := database.C. - Order("created_at desc"). - Where(&models.ActionEvent{AccountID: user.ID}). - Limit(take). - Offset(offset). - Find(&events).Error; err != nil { - return fiber.NewError(fiber.StatusInternalServerError, err.Error()) - } - - return c.JSON(fiber.Map{ - "count": count, - "data": events, - }) -} - -func editUserinfo(c *fiber.Ctx) error { +func updateUserinfo(c *fiber.Ctx) error { if err := exts.EnsureAuthenticated(c); err != nil { return err } diff --git a/pkg/internal/http/api/events_api.go b/pkg/internal/http/api/events_api.go new file mode 100644 index 0000000..45148f0 --- /dev/null +++ b/pkg/internal/http/api/events_api.go @@ -0,0 +1,40 @@ +package api + +import ( + "git.solsynth.dev/hypernet/passport/pkg/authkit/models" + "git.solsynth.dev/hypernet/passport/pkg/internal/database" + "git.solsynth.dev/hypernet/passport/pkg/internal/http/exts" + "github.com/gofiber/fiber/v2" +) + +func getEvents(c *fiber.Ctx) error { + if err := exts.EnsureAuthenticated(c); err != nil { + return err + } + user := c.Locals("user").(models.Account) + take := c.QueryInt("take", 0) + offset := c.QueryInt("offset", 0) + + var count int64 + var events []models.ActionEvent + if err := database.C. + Where(&models.ActionEvent{AccountID: user.ID}). + Model(&models.ActionEvent{}). + Count(&count).Error; err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } + + if err := database.C. + Order("created_at desc"). + Where(&models.ActionEvent{AccountID: user.ID}). + Limit(take). + Offset(offset). + Find(&events).Error; err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } + + return c.JSON(fiber.Map{ + "count": count, + "data": events, + }) +} diff --git a/pkg/internal/http/api/factors_api.go b/pkg/internal/http/api/factors_api.go index 0b644a5..ea26446 100644 --- a/pkg/internal/http/api/factors_api.go +++ b/pkg/internal/http/api/factors_api.go @@ -2,9 +2,13 @@ package api import ( "fmt" + + "git.solsynth.dev/hypernet/passport/pkg/authkit/models" + "git.solsynth.dev/hypernet/passport/pkg/internal/database" "git.solsynth.dev/hypernet/passport/pkg/internal/http/exts" "git.solsynth.dev/hypernet/passport/pkg/internal/services" "github.com/gofiber/fiber/v2" + "github.com/samber/lo" ) func getAvailableFactors(c *fiber.Ctx) error { @@ -42,42 +46,87 @@ func requestFactorToken(c *fiber.Ctx) error { } } -func requestResetPassword(c *fiber.Ctx) error { +func listFactor(c *fiber.Ctx) error { + if err := exts.EnsureAuthenticated(c); err != nil { + return err + } + user := c.Locals("user").(models.Account) + + var factors []models.AuthFactor + if err := database.C.Where("account_id = ?", user.ID).Find(&factors).Error; err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } + + return c.JSON(factors) +} + +func createFactor(c *fiber.Ctx) error { + if err := exts.EnsureAuthenticated(c); err != nil { + return err + } + user := c.Locals("user").(models.Account) + var data struct { - UserID uint `json:"user_id" validate:"required"` + Type models.AuthFactorType `json:"type"` + Secret string `json:"secret"` } if err := exts.BindAndValidate(c, &data); err != nil { return err } - user, err := services.GetAccount(data.UserID) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) + typeWhitelist := []models.AuthFactorType{ + models.EmailPasswordFactor, + models.InAppNotifyFactor, + models.TimeOtpFactor, + } + if !lo.Contains(typeWhitelist, data.Type) { + return fiber.NewError(fiber.StatusBadRequest, "invalid factor type") } - if err = services.CheckAbleToResetPassword(user); err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) - } else if err = services.RequestResetPassword(user); err != nil { + // Currently, each type of factor can only be created once + var currentCount int64 + if err := database.C.Model(&models.AuthFactor{}). + Where("account_id = ? AND type = ?", user.ID, data.Type). + Count(¤tCount).Error; err != nil { + return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("unable to check current factor count: %v", err)) + } else if currentCount > 0 { + return fiber.NewError(fiber.StatusBadRequest, "this type of factor already exists") + } + + factor := models.AuthFactor{ + Type: data.Type, + Secret: data.Secret, + Account: user, + AccountID: user.ID, + } + if err := database.C.Create(&factor).Error; err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } + + return c.JSON(factor) +} + +func deleteFactor(c *fiber.Ctx) error { + id, _ := c.ParamsInt("factorId", 0) + + if err := exts.EnsureAuthenticated(c); err != nil { + return err + } + user := c.Locals("user").(models.Account) + + var factor models.AuthFactor + if err := database.C.Where("id = ? AND account_id = ?", id, user.ID).First(&factor).Error; err != nil { + return fiber.NewError(fiber.StatusNotFound, err.Error()) + } + + if factor.Type == models.PasswordAuthFactor { + return fiber.NewError(fiber.StatusBadRequest, "unable to delete password factor") + } + + if err := database.C.Delete(&factor).Error; err != nil { return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } return c.SendStatus(fiber.StatusOK) } - -func confirmResetPassword(c *fiber.Ctx) error { - var data struct { - Code string `json:"code" validate:"required"` - NewPassword string `json:"new_password" validate:"required"` - } - - if err := exts.BindAndValidate(c, &data); err != nil { - return err - } - - if err := services.ConfirmResetPassword(data.Code, data.NewPassword); err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) - } - - return c.SendStatus(fiber.StatusOK) -} diff --git a/pkg/internal/http/api/index.go b/pkg/internal/http/api/index.go index 32164eb..6e2c733 100644 --- a/pkg/internal/http/api/index.go +++ b/pkg/internal/http/api/index.go @@ -60,7 +60,7 @@ func MapAPIs(app *fiber.App, baseURL string) { me.Put("/banner", setBanner) me.Get("/", getUserinfo) - me.Put("/", editUserinfo) + me.Put("/", updateUserinfo) me.Get("/events", getEvents) me.Get("/tickets", getTickets) me.Delete("/tickets/:ticketId", killTicket) @@ -72,6 +72,13 @@ func MapAPIs(app *fiber.App, baseURL string) { me.Put("/status", editStatus) me.Delete("/status", clearStatus) + factors := me.Group("/factors").Name("Factors") + { + factors.Get("/", listFactor) + factors.Post("/", createFactor) + factors.Delete("/:factorId", deleteFactor) + } + relations := me.Group("/relations").Name("Relations") { relations.Post("/", makeFriendship) diff --git a/pkg/internal/http/api/password_api.go b/pkg/internal/http/api/password_api.go new file mode 100644 index 0000000..066c133 --- /dev/null +++ b/pkg/internal/http/api/password_api.go @@ -0,0 +1,47 @@ +package api + +import ( + "git.solsynth.dev/hypernet/passport/pkg/internal/http/exts" + "git.solsynth.dev/hypernet/passport/pkg/internal/services" + "github.com/gofiber/fiber/v2" +) + +func requestResetPassword(c *fiber.Ctx) error { + var data struct { + UserID uint `json:"user_id" validate:"required"` + } + + if err := exts.BindAndValidate(c, &data); err != nil { + return err + } + + user, err := services.GetAccount(data.UserID) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + if err = services.CheckAbleToResetPassword(user); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if err = services.RequestResetPassword(user); err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } + + return c.SendStatus(fiber.StatusOK) +} + +func confirmResetPassword(c *fiber.Ctx) error { + var data struct { + Code string `json:"code" validate:"required"` + NewPassword string `json:"new_password" validate:"required"` + } + + if err := exts.BindAndValidate(c, &data); err != nil { + return err + } + + if err := services.ConfirmResetPassword(data.Code, data.NewPassword); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + return c.SendStatus(fiber.StatusOK) +}