Verify and password reset
* Users can now request a password reset and reset with their token
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
"github.com/google/uuid"
|
"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/models"
|
||||||
"github.com/yxzzy-wtf/gin-gonic-prepack/util"
|
"github.com/yxzzy-wtf/gin-gonic-prepack/util"
|
||||||
)
|
)
|
||||||
@@ -23,6 +24,15 @@ type signup struct {
|
|||||||
Password string `json:"password" binding:"required"`
|
Password string `json:"password" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type forgotten struct {
|
||||||
|
UserKey string `json:"userkey" binding:"required,email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type reset struct {
|
||||||
|
Token string `json:"token" binding:"required"`
|
||||||
|
NewPassword string `json:"password" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
const JwtHeader = "jwt"
|
const JwtHeader = "jwt"
|
||||||
|
|
||||||
func UserSignup() gin.HandlerFunc {
|
func UserSignup() gin.HandlerFunc {
|
||||||
@@ -53,7 +63,8 @@ func UserSignup() gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Send verification
|
// Send verification
|
||||||
go util.SendEmail("Verify Email", "TODO: generateverification token", u.Email)
|
verifyToken := u.GetVerificationJwt()
|
||||||
|
go util.SendEmail("Verify Email", "Helloooo! Go here to verify: http://localhost:9091/v1/verify?verify="+verifyToken, u.Email)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, util.NextMsg{Next: "verification pending"})
|
c.JSON(http.StatusOK, util.NextMsg{Next: "verification pending"})
|
||||||
@@ -65,6 +76,7 @@ func UserLogin() gin.HandlerFunc {
|
|||||||
var loginVals login
|
var loginVals login
|
||||||
if err := c.ShouldBind(&loginVals); err != nil {
|
if err := c.ShouldBind(&loginVals); err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, util.FailMsg{Reason: "Requires username and password"})
|
c.AbortWithStatusJSON(http.StatusBadRequest, util.FailMsg{Reason: "Requires username and password"})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
u := models.User{}
|
u := models.User{}
|
||||||
@@ -87,6 +99,120 @@ func UserLogin() gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UserVerify() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
verifyJwt, _ := c.GetQuery("verify")
|
||||||
|
|
||||||
|
claims, err := parseJwt(verifyJwt, models.UserHmac)
|
||||||
|
if err != nil || claims["role"] != "verify" {
|
||||||
|
fmt.Println("bad claim or role not 'verify'", err)
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yay! Jwt is a verify token, let's verify the linked user
|
||||||
|
uid, err := uuid.Parse(claims["sub"].(string))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("sub should ALWAYS be valid uuid at this point??", err)
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
verifying := models.User{
|
||||||
|
Auth: models.Auth{
|
||||||
|
Base: models.Base{
|
||||||
|
Uid: uid,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := database.Db.Find(&verifying).Error; err != nil {
|
||||||
|
fmt.Println("could not find user", err)
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if verifying.Verified {
|
||||||
|
// User already verified
|
||||||
|
c.JSON(http.StatusOK, util.NextMsg{Next: "verified"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
verifying.Verified = true
|
||||||
|
if err := verifying.Save(); err != nil {
|
||||||
|
fmt.Println("could not verify user", err)
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, util.NextMsg{Next: "verified"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserForgotPassword() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var forgotVals forgotten
|
||||||
|
if err := c.ShouldBind(&forgotVals); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, util.FailMsg{Reason: "requires email"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
u := models.User{}
|
||||||
|
if err := u.ByEmail(forgotVals.UserKey); err == nil {
|
||||||
|
// Actually send renew token
|
||||||
|
forgotJwt := u.GetResetPasswordJwt()
|
||||||
|
go util.SendEmail("Forgot Password", "Token to reset password: "+forgotJwt, u.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, util.NextMsg{Next: "check email to reset"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserResetForgottenPassword() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var resetVals reset
|
||||||
|
if err := c.ShouldBind(&resetVals); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, util.FailMsg{Reason: "requires new pass and token"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, err := parseJwt(resetVals.Token, models.UserHmac)
|
||||||
|
if err != nil || claims["role"] != "reset" {
|
||||||
|
fmt.Println("bad claim or role not 'reset'", err)
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, err := uuid.Parse(claims["sub"].(string))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("sub should ALWAYS be valid uuid at this point??", err)
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resetting := models.User{
|
||||||
|
Auth: models.Auth{
|
||||||
|
Base: models.Base{
|
||||||
|
Uid: uid,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := database.Db.Find(&resetting).Error; err != nil {
|
||||||
|
fmt.Println("could not find user", err)
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resetting.SetPassword(resetVals.NewPassword)
|
||||||
|
if err := resetting.Save(); err != nil {
|
||||||
|
fmt.Println("could not save user", err)
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, util.NextMsg{Next: "login"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func AdminLogin() gin.HandlerFunc {
|
func AdminLogin() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
var loginVals login
|
var loginVals login
|
||||||
|
|||||||
5
main.go
5
main.go
@@ -27,9 +27,12 @@ func main() {
|
|||||||
// Ping functionality
|
// Ping functionality
|
||||||
v1.GET("/doot", core.Doot())
|
v1.GET("/doot", core.Doot())
|
||||||
|
|
||||||
// Standard user login
|
// Standard user signup, verify, login and forgot/reset pw
|
||||||
v1.POST("/signup", core.UserSignup())
|
v1.POST("/signup", core.UserSignup())
|
||||||
v1.POST("/login", core.UserLogin())
|
v1.POST("/login", core.UserLogin())
|
||||||
|
v1.GET("/verify", core.UserVerify())
|
||||||
|
v1.POST("/forgot", core.UserForgotPassword())
|
||||||
|
v1.POST("/reset", core.UserResetForgottenPassword())
|
||||||
v1Sec := v1.Group("/sec", core.UserAuth())
|
v1Sec := v1.Group("/sec", core.UserAuth())
|
||||||
|
|
||||||
v1Sec.GET("/doot", core.Doot())
|
v1Sec.GET("/doot", core.Doot())
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ func (a *Auth) SetPassword(pass string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const VerifiedRequired = false
|
|
||||||
|
|
||||||
func (a *Auth) Login(pass string, tfCode string) (error, bool) {
|
func (a *Auth) Login(pass string, tfCode string) (error, bool) {
|
||||||
if err := a.CheckPassword(pass); err != nil {
|
if err := a.CheckPassword(pass); err != nil {
|
||||||
return err, false
|
return err, false
|
||||||
@@ -32,7 +30,7 @@ func (a *Auth) Login(pass string, tfCode string) (error, bool) {
|
|||||||
return err, true
|
return err, true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !a.Verified && VerifiedRequired {
|
if !a.Verified {
|
||||||
return errors.New("not yet verified"), true
|
return errors.New("not yet verified"), true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,40 @@ func (u *User) GetJwt() (string, int) {
|
|||||||
return jstr, int(userJwtDuration.Seconds())
|
return jstr, int(userJwtDuration.Seconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) GetVerificationJwt() string {
|
||||||
|
j := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||||
|
"sub": u.Uid.String(),
|
||||||
|
"iat": time.Now().Unix(),
|
||||||
|
"exp": time.Now().Add(time.Hour * 24).Unix(),
|
||||||
|
"role": "verify",
|
||||||
|
})
|
||||||
|
|
||||||
|
jstr, err := j.SignedString(UserHmac)
|
||||||
|
if err != nil {
|
||||||
|
// we should ALWAYS be able to build and sign a str
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return jstr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) GetResetPasswordJwt() string {
|
||||||
|
j := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||||
|
"sub": u.Uid.String(),
|
||||||
|
"iat": time.Now().Unix(),
|
||||||
|
"exp": time.Now().Add(time.Minute * 15).Unix(),
|
||||||
|
"role": "reset",
|
||||||
|
})
|
||||||
|
|
||||||
|
jstr, err := j.SignedString(UserHmac)
|
||||||
|
if err != nil {
|
||||||
|
// we should ALWAYS be able to build and sign a str
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return jstr
|
||||||
|
}
|
||||||
|
|
||||||
func (u *User) ByEmail(email string) error {
|
func (u *User) ByEmail(email string) error {
|
||||||
if err := database.Db.Where("email = ?", email).First(&u).Error; err != nil {
|
if err := database.Db.Where("email = ?", email).First(&u).Error; err != nil {
|
||||||
return errors.New("not found")
|
return errors.New("not found")
|
||||||
|
|||||||
Reference in New Issue
Block a user