mirror of
https://github.com/ordinary-dev/phoenix
synced 2024-09-20 03:40:29 +05:00
Start using JWT tokens
I thought this was a good idea. Pros: fewer database calls. Cons: there is no way to revoke the token (except for changing the secret key). I rewrote the authorization as a middleware. Request handlers no longer need to validate the user.
This commit is contained in:
parent
7f42a90be6
commit
2c08171c7a
|
@ -1,26 +1,14 @@
|
||||||
package backend
|
package backend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Admin struct {
|
type Admin struct {
|
||||||
ID uint64 `gorm:"primaryKey"`
|
ID uint64 `gorm:"primaryKey"`
|
||||||
Username string `gorm:"unique;notNull"`
|
Username string `gorm:"unique;notNull"`
|
||||||
Bcrypt string `gorm:"notNull"`
|
Bcrypt string `gorm:"notNull"`
|
||||||
AccessTokens []AccessToken
|
|
||||||
}
|
|
||||||
|
|
||||||
type AccessToken struct {
|
|
||||||
ID uint64 `gorm:"primaryKey"`
|
|
||||||
Value string `gorm:"notNull"`
|
|
||||||
AdminID uint64 `gorm:"notNull"`
|
|
||||||
ValidUntil time.Time `gorm:"NotNull"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CountAdmins(db *gorm.DB) int64 {
|
func CountAdmins(db *gorm.DB) int64 {
|
||||||
|
@ -64,39 +52,3 @@ func AuthorizeAdmin(db *gorm.DB, username string, password string) (Admin, error
|
||||||
|
|
||||||
return admin, nil
|
return admin, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateAccessToken(db *gorm.DB, adminID uint64) (AccessToken, error) {
|
|
||||||
bytes := make([]byte, 64)
|
|
||||||
if _, err := rand.Read(bytes); err != nil {
|
|
||||||
return AccessToken{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
accessToken := AccessToken{
|
|
||||||
AdminID: adminID,
|
|
||||||
Value: base64.StdEncoding.EncodeToString(bytes),
|
|
||||||
// Valid for 1 month
|
|
||||||
ValidUntil: time.Now().AddDate(0, 1, 0),
|
|
||||||
}
|
|
||||||
result := db.Create(&accessToken)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
return AccessToken{}, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return accessToken, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ValidateToken(db *gorm.DB, value string) error {
|
|
||||||
var token AccessToken
|
|
||||||
result := db.Where("value = ?", value).First(&token)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
if time.Now().After(token.ValidUntil) {
|
|
||||||
return errors.New("Access token expired")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ func GetDatabaseConnection(cfg *config.Config) (*gorm.DB, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate the schema
|
// Migrate the schema
|
||||||
db.AutoMigrate(&Admin{}, &AccessToken{}, &Group{}, &Link{})
|
db.AutoMigrate(&Admin{}, &Group{}, &Link{})
|
||||||
|
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -18,6 +18,7 @@ require (
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/joho/godotenv v1.5.1 // indirect
|
github.com/joho/godotenv v1.5.1 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -22,6 +22,8 @@ github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg
|
||||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
|
36
main.go
36
main.go
|
@ -35,55 +35,63 @@ func main() {
|
||||||
r.LoadHTMLGlob("templates/*")
|
r.LoadHTMLGlob("templates/*")
|
||||||
r.Static("/assets", "./assets")
|
r.Static("/assets", "./assets")
|
||||||
|
|
||||||
|
r.GET("/signin", func(c *gin.Context) {
|
||||||
|
views.ShowLoginForm(c)
|
||||||
|
})
|
||||||
|
r.POST("/signin", func(c *gin.Context) {
|
||||||
|
views.AuthorizeUser(c, db, cfg)
|
||||||
|
})
|
||||||
|
|
||||||
|
protected := r.Group("/")
|
||||||
|
protected.Use(func(c *gin.Context) {
|
||||||
|
views.AuthMiddleware(c, cfg)
|
||||||
|
})
|
||||||
|
|
||||||
// Main page
|
// Main page
|
||||||
r.GET("/", func(c *gin.Context) {
|
protected.GET("/", func(c *gin.Context) {
|
||||||
views.ShowMainPage(c, db)
|
views.ShowMainPage(c, db)
|
||||||
})
|
})
|
||||||
|
|
||||||
r.GET("/settings", func(c *gin.Context) {
|
protected.GET("/settings", func(c *gin.Context) {
|
||||||
views.ShowSettings(c, db)
|
views.ShowSettings(c, db)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Create new user
|
// Create new user
|
||||||
r.POST("/users", func(c *gin.Context) {
|
protected.POST("/users", func(c *gin.Context) {
|
||||||
views.CreateUser(c, db)
|
views.CreateUser(c, db, cfg)
|
||||||
})
|
|
||||||
|
|
||||||
r.POST("/signin", func(c *gin.Context) {
|
|
||||||
views.AuthorizeUser(c, db)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Create new group
|
// Create new group
|
||||||
r.POST("/groups", func(c *gin.Context) {
|
protected.POST("/groups", func(c *gin.Context) {
|
||||||
views.CreateGroup(c, db)
|
views.CreateGroup(c, db)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Update group
|
// Update group
|
||||||
// HTML forms cannot be submitted using PUT or PATCH methods without javascript.
|
// HTML forms cannot be submitted using PUT or PATCH methods without javascript.
|
||||||
r.POST("/groups/:id/put", func(c *gin.Context) {
|
protected.POST("/groups/:id/put", func(c *gin.Context) {
|
||||||
views.UpdateGroup(c, db)
|
views.UpdateGroup(c, db)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Delete group
|
// Delete group
|
||||||
// HTML forms cannot be submitted using the DELETE method without javascript.
|
// HTML forms cannot be submitted using the DELETE method without javascript.
|
||||||
r.POST("/groups/:id/delete", func(c *gin.Context) {
|
protected.POST("/groups/:id/delete", func(c *gin.Context) {
|
||||||
views.DeleteGroup(c, db)
|
views.DeleteGroup(c, db)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Create new link
|
// Create new link
|
||||||
r.POST("/links", func(c *gin.Context) {
|
protected.POST("/links", func(c *gin.Context) {
|
||||||
views.CreateLink(c, db)
|
views.CreateLink(c, db)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Update link.
|
// Update link.
|
||||||
// HTML forms cannot be submitted using PUT or PATCH methods without javascript.
|
// HTML forms cannot be submitted using PUT or PATCH methods without javascript.
|
||||||
r.POST("/links/:id/put", func(c *gin.Context) {
|
protected.POST("/links/:id/put", func(c *gin.Context) {
|
||||||
views.UpdateLink(c, db)
|
views.UpdateLink(c, db)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Delete link
|
// Delete link
|
||||||
// HTML forms cannot be submitted using the DELETE method without javascript.
|
// HTML forms cannot be submitted using the DELETE method without javascript.
|
||||||
r.POST("/links/:id/delete", func(c *gin.Context) {
|
protected.POST("/links/:id/delete", func(c *gin.Context) {
|
||||||
views.DeleteLink(c, db)
|
views.DeleteLink(c, db)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
110
views/auth.go
110
views/auth.go
|
@ -2,10 +2,14 @@ package views
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"github.com/ordinary-dev/phoenix/backend"
|
"github.com/ordinary-dev/phoenix/backend"
|
||||||
|
"github.com/ordinary-dev/phoenix/config"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ShowRegistrationForm(c *gin.Context) {
|
func ShowRegistrationForm(c *gin.Context) {
|
||||||
|
@ -27,30 +31,108 @@ func ShowLoginForm(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Requires the user to log in before viewing the page.
|
// Requires the user to log in before viewing the page.
|
||||||
// In case of an error, it shows the login page or the error page.
|
|
||||||
// Returns error if the user is not authorized.
|
// Returns error if the user is not authorized.
|
||||||
// If `nil` is returned instead of an error, it is safe to display protected content.
|
// If `nil` is returned instead of an error, it is safe to display protected content.
|
||||||
func RequireAuth(c *gin.Context, db *gorm.DB) error {
|
func RequireAuth(c *gin.Context, cfg *config.Config) (*jwt.RegisteredClaims, error) {
|
||||||
number_of_accounts := backend.CountAdmins(db)
|
|
||||||
|
|
||||||
// First run
|
|
||||||
if number_of_accounts == 0 {
|
|
||||||
ShowRegistrationForm(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenValue, err := c.Cookie("phoenix-token")
|
tokenValue, err := c.Cookie("phoenix-token")
|
||||||
|
|
||||||
// Anonymous visitor
|
// Anonymous visitor
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ShowLoginForm(c)
|
return nil, err
|
||||||
return errors.New("User is not authorized")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = backend.ValidateToken(db, tokenValue)
|
// Check token
|
||||||
|
token, err := jwt.ParseWithClaims(tokenValue, &jwt.RegisteredClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
// Validate the alg
|
||||||
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
|
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(cfg.SecretKey), nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
claims, ok := token.Claims.(*jwt.RegisteredClaims)
|
||||||
|
if !ok || !token.Valid {
|
||||||
|
return nil, errors.New("Token is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AuthMiddleware(c *gin.Context, cfg *config.Config) {
|
||||||
|
claims, err := RequireAuth(c, cfg)
|
||||||
|
if err != nil {
|
||||||
|
c.Redirect(http.StatusFound, "/signin")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new token if the old one is about to expire
|
||||||
|
if time.Now().Add(12 * time.Hour).After(claims.ExpiresAt.Time) {
|
||||||
|
newToken, err := GetJWTToken(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ShowError(c, err)
|
ShowError(c, err)
|
||||||
return errors.New("Access token is invalid")
|
return
|
||||||
|
}
|
||||||
|
SetTokenCookie(c, newToken)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
func GetJWTToken(cfg *config.Config) (string, error) {
|
||||||
|
claims := jwt.RegisteredClaims{
|
||||||
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
|
||||||
|
}
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
return token.SignedString([]byte(cfg.SecretKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateUser(c *gin.Context, db *gorm.DB, cfg *config.Config) {
|
||||||
|
// Try to create a user.
|
||||||
|
username := c.PostForm("username")
|
||||||
|
password := c.PostForm("password")
|
||||||
|
_, err := backend.CreateAdmin(db, username, password)
|
||||||
|
if err != nil {
|
||||||
|
ShowError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate access token.
|
||||||
|
token, err := GetJWTToken(cfg)
|
||||||
|
if err != nil {
|
||||||
|
ShowError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
SetTokenCookie(c, token)
|
||||||
|
|
||||||
|
// Redirect to homepage.
|
||||||
|
c.Redirect(http.StatusFound, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func AuthorizeUser(c *gin.Context, db *gorm.DB, cfg *config.Config) {
|
||||||
|
// Check credentials.
|
||||||
|
username := c.PostForm("username")
|
||||||
|
password := c.PostForm("password")
|
||||||
|
_, err := backend.AuthorizeAdmin(db, username, password)
|
||||||
|
if err != nil {
|
||||||
|
ShowError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate an access token.
|
||||||
|
token, err := GetJWTToken(cfg)
|
||||||
|
if err != nil {
|
||||||
|
ShowError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
SetTokenCookie(c, token)
|
||||||
|
|
||||||
|
// Redirect to homepage.
|
||||||
|
c.Redirect(http.StatusFound, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save token for 29 days in cookies
|
||||||
|
func SetTokenCookie(c *gin.Context, token string) {
|
||||||
|
c.SetCookie("phoenix-token", token, 60*60*24*29, "/", "", false, true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,4 +13,5 @@ func ShowError(c *gin.Context, err error) {
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
c.Abort()
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateGroup(c *gin.Context, db *gorm.DB) {
|
func CreateGroup(c *gin.Context, db *gorm.DB) {
|
||||||
if err := RequireAuth(c, db); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save new group to the database.
|
// Save new group to the database.
|
||||||
group := backend.Group{
|
group := backend.Group{
|
||||||
Name: c.PostForm("groupName"),
|
Name: c.PostForm("groupName"),
|
||||||
|
@ -27,10 +23,6 @@ func CreateGroup(c *gin.Context, db *gorm.DB) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateGroup(c *gin.Context, db *gorm.DB) {
|
func UpdateGroup(c *gin.Context, db *gorm.DB) {
|
||||||
if err := RequireAuth(c, db); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ShowError(c, err)
|
ShowError(c, err)
|
||||||
|
@ -54,10 +46,6 @@ func UpdateGroup(c *gin.Context, db *gorm.DB) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteGroup(c *gin.Context, db *gorm.DB) {
|
func DeleteGroup(c *gin.Context, db *gorm.DB) {
|
||||||
if err := RequireAuth(c, db); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ShowError(c, err)
|
ShowError(c, err)
|
||||||
|
|
|
@ -8,10 +8,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func ShowMainPage(c *gin.Context, db *gorm.DB) {
|
func ShowMainPage(c *gin.Context, db *gorm.DB) {
|
||||||
if err := RequireAuth(c, db); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a list of groups with links
|
// Get a list of groups with links
|
||||||
var groups []backend.Group
|
var groups []backend.Group
|
||||||
result := db.
|
result := db.
|
||||||
|
|
|
@ -9,10 +9,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateLink(c *gin.Context, db *gorm.DB) {
|
func CreateLink(c *gin.Context, db *gorm.DB) {
|
||||||
if err := RequireAuth(c, db); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
groupID, err := strconv.ParseUint(c.PostForm("groupID"), 10, 32)
|
groupID, err := strconv.ParseUint(c.PostForm("groupID"), 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ShowError(c, err)
|
ShowError(c, err)
|
||||||
|
@ -34,10 +30,6 @@ func CreateLink(c *gin.Context, db *gorm.DB) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateLink(c *gin.Context, db *gorm.DB) {
|
func UpdateLink(c *gin.Context, db *gorm.DB) {
|
||||||
if err := RequireAuth(c, db); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ShowError(c, err)
|
ShowError(c, err)
|
||||||
|
@ -62,10 +54,6 @@ func UpdateLink(c *gin.Context, db *gorm.DB) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteLink(c *gin.Context, db *gorm.DB) {
|
func DeleteLink(c *gin.Context, db *gorm.DB) {
|
||||||
if err := RequireAuth(c, db); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ShowError(c, err)
|
ShowError(c, err)
|
||||||
|
|
|
@ -8,10 +8,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func ShowSettings(c *gin.Context, db *gorm.DB) {
|
func ShowSettings(c *gin.Context, db *gorm.DB) {
|
||||||
if err := RequireAuth(c, db); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a list of groups with links
|
// Get a list of groups with links
|
||||||
var groups []backend.Group
|
var groups []backend.Group
|
||||||
result := db.
|
result := db.
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
package views
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/ordinary-dev/phoenix/backend"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func CreateUser(c *gin.Context, db *gorm.DB) {
|
|
||||||
// If at least 1 administator exists, require authorization
|
|
||||||
if backend.CountAdmins(db) > 0 {
|
|
||||||
tokenValue, err := c.Cookie("phoenix-token")
|
|
||||||
|
|
||||||
// Anonymous visitor
|
|
||||||
if err != nil {
|
|
||||||
err = errors.New("At least 1 user exists, you have to sign in first")
|
|
||||||
ShowError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = backend.ValidateToken(db, tokenValue)
|
|
||||||
if err != nil {
|
|
||||||
ShowError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// User is authorized or no user exists.
|
|
||||||
// Try to create a user.
|
|
||||||
username := c.PostForm("username")
|
|
||||||
password := c.PostForm("password")
|
|
||||||
admin, err := backend.CreateAdmin(db, username, password)
|
|
||||||
if err != nil {
|
|
||||||
ShowError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate access token.
|
|
||||||
token, err := backend.CreateAccessToken(db, admin.ID)
|
|
||||||
if err != nil {
|
|
||||||
ShowError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
SetTokenCookie(c, token)
|
|
||||||
|
|
||||||
// Redirect to homepage.
|
|
||||||
c.Redirect(http.StatusFound, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
func AuthorizeUser(c *gin.Context, db *gorm.DB) {
|
|
||||||
// Check credentials.
|
|
||||||
username := c.PostForm("username")
|
|
||||||
password := c.PostForm("password")
|
|
||||||
admin, err := backend.AuthorizeAdmin(db, username, password)
|
|
||||||
if err != nil {
|
|
||||||
ShowError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate an access token.
|
|
||||||
token, err := backend.CreateAccessToken(db, admin.ID)
|
|
||||||
if err != nil {
|
|
||||||
ShowError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
SetTokenCookie(c, token)
|
|
||||||
|
|
||||||
// Redirect to homepage.
|
|
||||||
c.Redirect(http.StatusFound, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save token for 29 days in cookies
|
|
||||||
func SetTokenCookie(c *gin.Context, token backend.AccessToken) {
|
|
||||||
c.SetCookie("phoenix-token", token.Value, 60*60*24*29, "/", "", false, true)
|
|
||||||
}
|
|
Loading…
Reference in a new issue