Login, JWT and auth overstructure
* Signup -> Login -> JWT-Doot flow now works for users * Administrators cannot currently sign up for obvious reasons * Segmented the main.go methods into a core controller package
This commit is contained in:
158
main.go
158
main.go
@@ -1,15 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/yxzzy-wtf/gin-gonic-prepack/controllers/core"
|
||||
"github.com/yxzzy-wtf/gin-gonic-prepack/database"
|
||||
"github.com/yxzzy-wtf/gin-gonic-prepack/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
_ "github.com/golang-jwt/jwt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -26,162 +25,23 @@ func main() {
|
||||
v1 := r.Group("/v1")
|
||||
|
||||
// Ping functionality
|
||||
v1.GET("/doot", doot())
|
||||
v1.GET("/doot", core.Doot())
|
||||
|
||||
// Standard user login
|
||||
v1.POST("/signup", userSignup())
|
||||
v1.POST("/login", userLogin())
|
||||
v1Sec := v1.Group("/sec", userAuth())
|
||||
v1.POST("/signup", core.UserSignup())
|
||||
v1.POST("/login", core.UserLogin())
|
||||
v1Sec := v1.Group("/sec", core.UserAuth())
|
||||
|
||||
v1Sec.GET("/doot", doot())
|
||||
v1Sec.GET("/doot", core.Doot())
|
||||
|
||||
// Administrative login
|
||||
v1.POST("/admin", adminLogin())
|
||||
v1Admin := v1.Group("/adm", adminAuth())
|
||||
v1.POST("/admin", core.AdminLogin())
|
||||
v1Admin := v1.Group("/adm", core.AdminAuth())
|
||||
|
||||
v1Admin.GET("/doot", doot())
|
||||
v1Admin.GET("/doot", core.Doot())
|
||||
|
||||
// Start server
|
||||
if err := http.ListenAndServe(":9091", r); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func doot() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, map[string]string{"snoot": "dooted"})
|
||||
}
|
||||
}
|
||||
|
||||
type login struct {
|
||||
UserKey string `json:"userkey" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
TwoFactor string `json:"twofactorcode"`
|
||||
}
|
||||
|
||||
type signup struct {
|
||||
UserKey string `json:"userkey" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type failmsg struct {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
const JwtHeader = "jwt"
|
||||
const ServicePath = "TODOPATH"
|
||||
const ServiceDomain = "TODODOMAIN"
|
||||
|
||||
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"})
|
||||
}
|
||||
|
||||
u := models.User{}
|
||||
if err := u.ByEmail(loginVals.UserKey); err != nil {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if err, returnErr := u.Login(loginVals.Password, loginVals.TwoFactor); err != nil {
|
||||
if returnErr {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, failmsg{err.Error()})
|
||||
} else {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
jwt, maxAge := u.GetJwt()
|
||||
c.SetCookie(JwtHeader, jwt, maxAge, ServicePath, ServiceDomain, true, true)
|
||||
}
|
||||
}
|
||||
|
||||
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"})
|
||||
return
|
||||
}
|
||||
|
||||
u := models.User{
|
||||
Email: signupVals.UserKey,
|
||||
}
|
||||
|
||||
if err := u.SetPassword(signupVals.Password); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, failmsg{"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 {
|
||||
fmt.Println(fmt.Errorf("error: %w", err))
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, map[string]string{"id": u.Uid.String()})
|
||||
}
|
||||
}
|
||||
|
||||
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"})
|
||||
}
|
||||
|
||||
if loginVals.TwoFactor == "" {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, failmsg{"admin access requires 2FA"})
|
||||
return
|
||||
}
|
||||
|
||||
a := models.Admin{}
|
||||
if err := a.ByEmail(loginVals.UserKey); err != nil {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if err, returnErr := a.Login(loginVals.Password, loginVals.TwoFactor); err != nil {
|
||||
if returnErr {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, failmsg{err.Error()})
|
||||
} else {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
jwt, maxAge := a.GetJwt()
|
||||
c.SetCookie(JwtHeader, jwt, maxAge, ServicePath, ServiceDomain, true, true)
|
||||
}
|
||||
}
|
||||
|
||||
func userAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
jwt := c.GetHeader(JwtHeader)
|
||||
if jwt == "" {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, failmsg{"requires `" + JwtHeader + "` header"})
|
||||
return
|
||||
}
|
||||
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
func adminAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
jwt := c.GetHeader(JwtHeader)
|
||||
if jwt == "" {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, failmsg{"requires `" + JwtHeader + "` header"})
|
||||
return
|
||||
}
|
||||
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user