Implement saved sessions
This commit is contained in:
parent
025b984314
commit
fa0e917d34
9 changed files with 187 additions and 0 deletions
|
|
@ -51,6 +51,10 @@ func LoginForm(c *fiber.Ctx) error {
|
|||
}
|
||||
}
|
||||
|
||||
if c.FormValue("save_session") == "on" {
|
||||
sess.Set("create-saved-session", "yes")
|
||||
}
|
||||
|
||||
sess.Set("userid", user.ID)
|
||||
sess.Save()
|
||||
|
||||
|
|
|
|||
|
|
@ -16,5 +16,13 @@ func LogoutProcess(c *fiber.Ctx) error {
|
|||
return err
|
||||
}
|
||||
|
||||
sessionUUID := c.Cookies("saved-session-uuid")
|
||||
if len(sessionUUID) > 0 {
|
||||
helpers.RemoveSavedSession(sessionUUID)
|
||||
|
||||
c.ClearCookie("saved-session-uuid")
|
||||
c.ClearCookie("saved-session-secret")
|
||||
}
|
||||
|
||||
return c.Redirect("/")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,6 +91,26 @@ func TotpEnrollPage(c *fiber.Ctx) error {
|
|||
|
||||
sess.Set("totp-verified", "yes")
|
||||
|
||||
if sess.Get("create-saved-session") == "yes" {
|
||||
savedSession, secret, err := helpers.CreateSavedSession(user.ID)
|
||||
if err == nil {
|
||||
cookieUUID := fiber.Cookie{
|
||||
Name: "saved-session-uuid",
|
||||
Value: savedSession.UUID,
|
||||
Expires: savedSession.Expiration,
|
||||
}
|
||||
|
||||
cookieSecret := fiber.Cookie{
|
||||
Name: "saved-session-secret",
|
||||
Value: secret,
|
||||
Expires: savedSession.Expiration,
|
||||
}
|
||||
|
||||
c.Cookie(&cookieUUID)
|
||||
c.Cookie(&cookieSecret)
|
||||
}
|
||||
}
|
||||
|
||||
redirectId := c.Query("redirect")
|
||||
redirectUrl := "/"
|
||||
|
||||
|
|
@ -164,6 +184,26 @@ func TotpVerifyPage(c *fiber.Ctx) error {
|
|||
if c.Method() == "POST" {
|
||||
otp := c.FormValue("otp")
|
||||
if totp.Validate(otp, user.TotpSecret.String) {
|
||||
if sess.Get("create-saved-session") == "yes" {
|
||||
savedSession, secret, err := helpers.CreateSavedSession(user.ID)
|
||||
if err == nil {
|
||||
cookieUUID := fiber.Cookie{
|
||||
Name: "saved-session-uuid",
|
||||
Value: savedSession.UUID,
|
||||
Expires: savedSession.Expiration,
|
||||
}
|
||||
|
||||
cookieSecret := fiber.Cookie{
|
||||
Name: "saved-session-secret",
|
||||
Value: secret,
|
||||
Expires: savedSession.Expiration,
|
||||
}
|
||||
|
||||
c.Cookie(&cookieUUID)
|
||||
c.Cookie(&cookieSecret)
|
||||
}
|
||||
}
|
||||
|
||||
redirectId := c.Query("redirect")
|
||||
redirectUrl := "/"
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ func connectDatabase() (*gorm.DB, error) {
|
|||
|
||||
err = database.AutoMigrate(
|
||||
&models.User{},
|
||||
&models.UserSavedSession{},
|
||||
&models.Section{},
|
||||
&models.Role{},
|
||||
&models.UserRole{},
|
||||
|
|
|
|||
56
helpers/saved_session.go
Normal file
56
helpers/saved_session.go
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
package helpers
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"time"
|
||||
|
||||
"git.readonly.ch/bouzoure/pop-camarades/models"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func CreateSavedSession(userid uint) (models.UserSavedSession, string, error) {
|
||||
var savedSession models.UserSavedSession
|
||||
|
||||
db, err := GetDatabase()
|
||||
if err != nil {
|
||||
return savedSession, "", err
|
||||
}
|
||||
|
||||
secret := GenerateSecureToken(30)
|
||||
hashedSecret, err := HashPassword(secret)
|
||||
if err != nil {
|
||||
return savedSession, "", err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
expiration := now.AddDate(0, 0, 30)
|
||||
|
||||
savedSession.UserID = userid
|
||||
savedSession.UUID = uuid.NewString()
|
||||
savedSession.Secret = hashedSecret
|
||||
savedSession.Expiration = expiration
|
||||
|
||||
db.Create(&savedSession)
|
||||
|
||||
return savedSession, secret, nil
|
||||
}
|
||||
|
||||
func RemoveSavedSession(uuid string) error {
|
||||
db, err := GetDatabase()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db.Delete(&models.UserSavedSession{}, "uuid = ?", uuid)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GenerateSecureToken(length int) string {
|
||||
b := make([]byte, length)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return ""
|
||||
}
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
1
main.go
1
main.go
|
|
@ -89,6 +89,7 @@ func main() {
|
|||
app.Use(helmet.New())
|
||||
|
||||
// Security middlewares
|
||||
app.Use(middlewares.SavedSessionMiddleware)
|
||||
app.Use(middlewares.AuthMiddleware)
|
||||
app.Use(middlewares.WelcomeMiddleware)
|
||||
app.Use(middlewares.MfaEnrollMiddleware)
|
||||
|
|
|
|||
55
middlewares/saved_session.go
Normal file
55
middlewares/saved_session.go
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
package middlewares
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.readonly.ch/bouzoure/pop-camarades/helpers"
|
||||
"git.readonly.ch/bouzoure/pop-camarades/models"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func SavedSessionMiddleware(c *fiber.Ctx) error {
|
||||
sessionUUID := c.Cookies("saved-session-uuid")
|
||||
sessionSecret := c.Cookies("saved-session-secret")
|
||||
|
||||
if len(sessionUUID) > 0 && len(sessionSecret) > 0 {
|
||||
db, err := helpers.GetDatabase()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var savedSession models.UserSavedSession
|
||||
result := db.Find(
|
||||
&savedSession,
|
||||
"uuid = ? AND expiration >= ?",
|
||||
sessionUUID,
|
||||
time.Now(),
|
||||
)
|
||||
|
||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||
c.ClearCookie("saved-session-uuid")
|
||||
c.ClearCookie("saved-session-secret")
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
if helpers.CheckPasswordHash(sessionSecret, savedSession.Secret) {
|
||||
sess, err := helpers.GetSessionStore(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sess.Set("userid", savedSession.UserID)
|
||||
sess.Set("totp-verified", "yes")
|
||||
sess.Save()
|
||||
}
|
||||
}
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package models
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
|
@ -25,3 +26,13 @@ type UserRole struct {
|
|||
SectionID uint
|
||||
Section Section
|
||||
}
|
||||
|
||||
// TODO: Autoclean expired sessions
|
||||
type UserSavedSession struct {
|
||||
gorm.Model
|
||||
UserID uint
|
||||
User User
|
||||
UUID string
|
||||
Secret string
|
||||
Expiration time.Time
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,17 @@
|
|||
required
|
||||
>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input me-2"
|
||||
id="save_session"
|
||||
name="save_session"
|
||||
>
|
||||
<label for="save_session" class="form-label">
|
||||
Se souvenir de moi
|
||||
</label>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
<i class="me-1 bi-box-arrow-in-right"></i>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue