diff --git a/controllers/core/core.go b/controllers/core/core.go index 9248ccb..6c8b18b 100644 --- a/controllers/core/core.go +++ b/controllers/core/core.go @@ -8,26 +8,21 @@ import ( "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt" "github.com/google/uuid" - "github.com/yxzzy-wtf/gin-gonic-prepack/database" "github.com/yxzzy-wtf/gin-gonic-prepack/models" "github.com/yxzzy-wtf/gin-gonic-prepack/util" ) type login struct { - UserKey string `json:"userkey" binding:"required"` + UserKey string `json:"userkey" binding:"required,email"` Password string `json:"password" binding:"required"` TwoFactor string `json:"twofactorcode"` } type signup struct { - UserKey string `json:"userkey" binding:"required"` + UserKey string `json:"userkey" binding:"required,email"` Password string `json:"password" binding:"required"` } -type failmsg struct { - Reason string `json:"reason"` -} - const JwtHeader = "jwt" const ServicePath = "TODOPATH" const ServiceDomain = "TODODOMAIN" @@ -36,7 +31,7 @@ func UserSignup() gin.HandlerFunc { return func(c *gin.Context) { var signupVals signup if err := c.ShouldBind(&signupVals); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, failmsg{"Requires username and password"}) + c.AbortWithStatusJSON(http.StatusBadRequest, util.FailMsg{Reason: "invalid fields, requires userkey=email and password"}) return } @@ -45,21 +40,25 @@ func UserSignup() gin.HandlerFunc { } if err := u.SetPassword(signupVals.Password); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, failmsg{"Bad password"}) + c.AbortWithStatusJSON(http.StatusBadRequest, util.FailMsg{Reason: "bad password"}) return } - if err := database.Db.Model(&u).Create(&u).Error; err != nil { - if err.Error() == "UNIQUE constraint failed: users.email" { - c.AbortWithStatusJSON(http.StatusInternalServerError, failmsg{"already exists"}) - } else { + if err := u.Create(); err != nil { + if err.Error() != "UNIQUE constraint failed: users.email" { fmt.Println(fmt.Errorf("error: %w", err)) c.AbortWithStatus(http.StatusInternalServerError) + return + } else { + // Email conflict means we should still mock verify + go util.SendEmail("Signup Attempt", "Someone tried to sign up with this email. This is a cursory warning. If it was you, good news! You're already signed up!", u.Email) } - return + } else { + // Send verification + go util.SendEmail("Verify Email", "TODO: generate verification token", u.Email) } - c.JSON(http.StatusOK, map[string]string{"id": u.Uid.String()}) + c.JSON(http.StatusOK, util.NextMsg{Next: "verification pending"}) } } @@ -67,7 +66,7 @@ func UserLogin() gin.HandlerFunc { return func(c *gin.Context) { var loginVals login if err := c.ShouldBind(&loginVals); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, failmsg{"Requires username and password"}) + c.AbortWithStatusJSON(http.StatusBadRequest, util.FailMsg{Reason: "Requires username and password"}) } u := models.User{} @@ -78,7 +77,7 @@ func UserLogin() gin.HandlerFunc { if err, returnErr := u.Login(loginVals.Password, loginVals.TwoFactor); err != nil { if returnErr { - c.AbortWithStatusJSON(http.StatusUnauthorized, failmsg{err.Error()}) + c.AbortWithStatusJSON(http.StatusUnauthorized, util.FailMsg{Reason: err.Error()}) } else { c.AbortWithStatus(http.StatusUnauthorized) } @@ -94,11 +93,11 @@ func AdminLogin() gin.HandlerFunc { return func(c *gin.Context) { var loginVals login if err := c.ShouldBind(&loginVals); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, failmsg{"requires username and password"}) + c.AbortWithStatusJSON(http.StatusBadRequest, util.FailMsg{Reason: "requires username and password"}) } if loginVals.TwoFactor == "" { - c.AbortWithStatusJSON(http.StatusUnauthorized, failmsg{"admin access requires 2FA"}) + c.AbortWithStatusJSON(http.StatusUnauthorized, util.FailMsg{Reason: "admin access requires 2FA"}) return } @@ -110,7 +109,7 @@ func AdminLogin() gin.HandlerFunc { if err, returnErr := a.Login(loginVals.Password, loginVals.TwoFactor); err != nil { if returnErr { - c.AbortWithStatusJSON(http.StatusUnauthorized, failmsg{err.Error()}) + c.AbortWithStatusJSON(http.StatusUnauthorized, util.FailMsg{Reason: err.Error()}) } else { c.AbortWithStatus(http.StatusUnauthorized) } @@ -126,28 +125,28 @@ func genericAuth(expectedRole string) gin.HandlerFunc { return func(c *gin.Context) { tokenStr := c.GetHeader(JwtHeader) if tokenStr == "" { - c.AbortWithStatusJSON(http.StatusUnauthorized, failmsg{"requires `" + JwtHeader + "` header"}) + c.AbortWithStatusJSON(http.StatusUnauthorized, util.FailMsg{Reason: "requires `" + JwtHeader + "` header"}) return } claims, err := parseJwt(tokenStr, models.UserHmac) if err != nil { if strings.HasPrefix(err.Error(), "token ") || err.Error() == "signature is invalid" { - c.AbortWithStatusJSON(http.StatusUnauthorized, failmsg{err.Error()}) + c.AbortWithStatusJSON(http.StatusUnauthorized, util.FailMsg{Reason: err.Error()}) } else { fmt.Println(err) - c.AbortWithStatusJSON(http.StatusInternalServerError, failmsg{"something went wrong"}) + c.AbortWithStatusJSON(http.StatusInternalServerError, util.FailMsg{Reason: "something went wrong"}) } return } if claims["role"] != expectedRole { - c.AbortWithStatusJSON(http.StatusUnauthorized, failmsg{"wrong access role"}) + c.AbortWithStatusJSON(http.StatusUnauthorized, util.FailMsg{Reason: "wrong access role"}) return } uid, err := uuid.Parse(claims["sub"].(string)) if err != nil { - c.AbortWithStatusJSON(http.StatusUnauthorized, failmsg{"cannot extract sub"}) + c.AbortWithStatusJSON(http.StatusUnauthorized, util.FailMsg{Reason: "cannot extract sub"}) return } diff --git a/models/user.go b/models/user.go index 6d920be..9de1873 100644 --- a/models/user.go +++ b/models/user.go @@ -5,6 +5,7 @@ import ( "time" "github.com/golang-jwt/jwt" + "github.com/google/uuid" "github.com/yxzzy-wtf/gin-gonic-prepack/database" "github.com/yxzzy-wtf/gin-gonic-prepack/util" ) @@ -42,3 +43,27 @@ func (u *User) ByEmail(email string) error { return nil } + +func (u *User) Create() error { + if u.Uid != uuid.Nil { + return errors.New("cannot create with existing uid") + } + + if err := database.Db.Create(&u).Error; err != nil { + return err + } + + return nil +} + +func (u *User) Save() error { + if u.Uid == uuid.Nil { + return errors.New("cannot save without uid") + } + + if err := database.Db.Save(&u).Error; err != nil { + return err + } + + return nil +} diff --git a/util/util.go b/util/util.go index ae7cc14..851cd48 100644 --- a/util/util.go +++ b/util/util.go @@ -2,6 +2,7 @@ package util import ( "crypto/rand" + "fmt" "github.com/google/uuid" ) @@ -23,3 +24,12 @@ type PrincipalInfo struct { type FailMsg struct { Reason string `json:"reason"` } + +type NextMsg struct { + Next string `json:"nextaction"` +} + +func SendEmail(title string, body string, recipient string) { + //TODO + fmt.Println("Send", title, body, "to", recipient) +}