Fix signup risk of enumeration to test emails
* Now, if a known email is used, it will still return the same result * If a known email is used, we will ping the email address to know that there was a signup attempt
This commit is contained in:
@@ -8,26 +8,21 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type login struct {
|
type login struct {
|
||||||
UserKey string `json:"userkey" binding:"required"`
|
UserKey string `json:"userkey" binding:"required,email"`
|
||||||
Password string `json:"password" binding:"required"`
|
Password string `json:"password" binding:"required"`
|
||||||
TwoFactor string `json:"twofactorcode"`
|
TwoFactor string `json:"twofactorcode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type signup struct {
|
type signup struct {
|
||||||
UserKey string `json:"userkey" binding:"required"`
|
UserKey string `json:"userkey" binding:"required,email"`
|
||||||
Password string `json:"password" binding:"required"`
|
Password string `json:"password" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type failmsg struct {
|
|
||||||
Reason string `json:"reason"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const JwtHeader = "jwt"
|
const JwtHeader = "jwt"
|
||||||
const ServicePath = "TODOPATH"
|
const ServicePath = "TODOPATH"
|
||||||
const ServiceDomain = "TODODOMAIN"
|
const ServiceDomain = "TODODOMAIN"
|
||||||
@@ -36,7 +31,7 @@ func UserSignup() gin.HandlerFunc {
|
|||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
var signupVals signup
|
var signupVals signup
|
||||||
if err := c.ShouldBind(&signupVals); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,21 +40,25 @@ func UserSignup() gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := u.SetPassword(signupVals.Password); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := database.Db.Model(&u).Create(&u).Error; err != nil {
|
if err := u.Create(); err != nil {
|
||||||
if err.Error() == "UNIQUE constraint failed: users.email" {
|
if err.Error() != "UNIQUE constraint failed: users.email" {
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, failmsg{"already exists"})
|
|
||||||
} else {
|
|
||||||
fmt.Println(fmt.Errorf("error: %w", err))
|
fmt.Println(fmt.Errorf("error: %w", err))
|
||||||
c.AbortWithStatus(http.StatusInternalServerError)
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
}
|
|
||||||
return
|
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)
|
||||||
|
}
|
||||||
|
} 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) {
|
return func(c *gin.Context) {
|
||||||
var loginVals login
|
var loginVals login
|
||||||
if err := c.ShouldBind(&loginVals); err != nil {
|
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{}
|
u := models.User{}
|
||||||
@@ -78,7 +77,7 @@ func UserLogin() gin.HandlerFunc {
|
|||||||
|
|
||||||
if err, returnErr := u.Login(loginVals.Password, loginVals.TwoFactor); err != nil {
|
if err, returnErr := u.Login(loginVals.Password, loginVals.TwoFactor); err != nil {
|
||||||
if returnErr {
|
if returnErr {
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, failmsg{err.Error()})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, util.FailMsg{Reason: err.Error()})
|
||||||
} else {
|
} else {
|
||||||
c.AbortWithStatus(http.StatusUnauthorized)
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
}
|
}
|
||||||
@@ -94,11 +93,11 @@ func AdminLogin() gin.HandlerFunc {
|
|||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
var loginVals login
|
var loginVals login
|
||||||
if err := c.ShouldBind(&loginVals); err != nil {
|
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 == "" {
|
if loginVals.TwoFactor == "" {
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, failmsg{"admin access requires 2FA"})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, util.FailMsg{Reason: "admin access requires 2FA"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +109,7 @@ func AdminLogin() gin.HandlerFunc {
|
|||||||
|
|
||||||
if err, returnErr := a.Login(loginVals.Password, loginVals.TwoFactor); err != nil {
|
if err, returnErr := a.Login(loginVals.Password, loginVals.TwoFactor); err != nil {
|
||||||
if returnErr {
|
if returnErr {
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, failmsg{err.Error()})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, util.FailMsg{Reason: err.Error()})
|
||||||
} else {
|
} else {
|
||||||
c.AbortWithStatus(http.StatusUnauthorized)
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
}
|
}
|
||||||
@@ -126,28 +125,28 @@ func genericAuth(expectedRole string) gin.HandlerFunc {
|
|||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
tokenStr := c.GetHeader(JwtHeader)
|
tokenStr := c.GetHeader(JwtHeader)
|
||||||
if tokenStr == "" {
|
if tokenStr == "" {
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, failmsg{"requires `" + JwtHeader + "` header"})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, util.FailMsg{Reason: "requires `" + JwtHeader + "` header"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
claims, err := parseJwt(tokenStr, models.UserHmac)
|
claims, err := parseJwt(tokenStr, models.UserHmac)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.HasPrefix(err.Error(), "token ") || err.Error() == "signature is invalid" {
|
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 {
|
} else {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, failmsg{"something went wrong"})
|
c.AbortWithStatusJSON(http.StatusInternalServerError, util.FailMsg{Reason: "something went wrong"})
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if claims["role"] != expectedRole {
|
if claims["role"] != expectedRole {
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, failmsg{"wrong access role"})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, util.FailMsg{Reason: "wrong access role"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
uid, err := uuid.Parse(claims["sub"].(string))
|
uid, err := uuid.Parse(claims["sub"].(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, failmsg{"cannot extract sub"})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, util.FailMsg{Reason: "cannot extract sub"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/yxzzy-wtf/gin-gonic-prepack/database"
|
"github.com/yxzzy-wtf/gin-gonic-prepack/database"
|
||||||
"github.com/yxzzy-wtf/gin-gonic-prepack/util"
|
"github.com/yxzzy-wtf/gin-gonic-prepack/util"
|
||||||
)
|
)
|
||||||
@@ -42,3 +43,27 @@ func (u *User) ByEmail(email string) error {
|
|||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
10
util/util.go
10
util/util.go
@@ -2,6 +2,7 @@ package util
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
@@ -23,3 +24,12 @@ type PrincipalInfo struct {
|
|||||||
type FailMsg struct {
|
type FailMsg struct {
|
||||||
Reason string `json:"reason"`
|
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)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user