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.Set("userid", user.ID)
|
||||||
sess.Save()
|
sess.Save()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,5 +16,13 @@ func LogoutProcess(c *fiber.Ctx) error {
|
||||||
return err
|
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("/")
|
return c.Redirect("/")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,26 @@ func TotpEnrollPage(c *fiber.Ctx) error {
|
||||||
|
|
||||||
sess.Set("totp-verified", "yes")
|
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")
|
redirectId := c.Query("redirect")
|
||||||
redirectUrl := "/"
|
redirectUrl := "/"
|
||||||
|
|
||||||
|
|
@ -164,6 +184,26 @@ func TotpVerifyPage(c *fiber.Ctx) error {
|
||||||
if c.Method() == "POST" {
|
if c.Method() == "POST" {
|
||||||
otp := c.FormValue("otp")
|
otp := c.FormValue("otp")
|
||||||
if totp.Validate(otp, user.TotpSecret.String) {
|
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")
|
redirectId := c.Query("redirect")
|
||||||
redirectUrl := "/"
|
redirectUrl := "/"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ func connectDatabase() (*gorm.DB, error) {
|
||||||
|
|
||||||
err = database.AutoMigrate(
|
err = database.AutoMigrate(
|
||||||
&models.User{},
|
&models.User{},
|
||||||
|
&models.UserSavedSession{},
|
||||||
&models.Section{},
|
&models.Section{},
|
||||||
&models.Role{},
|
&models.Role{},
|
||||||
&models.UserRole{},
|
&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())
|
app.Use(helmet.New())
|
||||||
|
|
||||||
// Security middlewares
|
// Security middlewares
|
||||||
|
app.Use(middlewares.SavedSessionMiddleware)
|
||||||
app.Use(middlewares.AuthMiddleware)
|
app.Use(middlewares.AuthMiddleware)
|
||||||
app.Use(middlewares.WelcomeMiddleware)
|
app.Use(middlewares.WelcomeMiddleware)
|
||||||
app.Use(middlewares.MfaEnrollMiddleware)
|
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 (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
@ -25,3 +26,13 @@ type UserRole struct {
|
||||||
SectionID uint
|
SectionID uint
|
||||||
Section Section
|
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
|
required
|
||||||
>
|
>
|
||||||
</div>
|
</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">
|
<div class="text-end">
|
||||||
<button class="btn btn-primary" type="submit">
|
<button class="btn btn-primary" type="submit">
|
||||||
<i class="me-1 bi-box-arrow-in-right"></i>
|
<i class="me-1 bi-box-arrow-in-right"></i>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue