WIP: Enrollement MFA avec TOTP

This commit is contained in:
William Bouzourène 2024-12-21 21:54:51 +01:00
parent 274a30480e
commit 53c94a490c
Signed by: bouzoure
SSH key fingerprint: SHA256:19MbXpLua4rUtk8tunMesD8KUKb91LXLHg8E/qTooww
10 changed files with 144 additions and 1 deletions

80
controllers/mfa.go Normal file
View file

@ -0,0 +1,80 @@
package controllers
import (
"bytes"
"encoding/base64"
"fmt"
"image/png"
"git.readonly.ch/bouzoure/popvaud-people/helpers"
"git.readonly.ch/bouzoure/popvaud-people/models"
"github.com/gofiber/fiber/v2"
"github.com/pquerna/otp/totp"
)
func TotpEnrollPage(c *fiber.Ctx) error {
db, err := helpers.GetDatabase()
if err != nil {
return err
}
userid, err := helpers.GetSessionUserId(c)
if err != nil {
return err
}
var user models.User
result := db.First(&user, "id = ?", userid)
if result.Error != nil {
return err
}
if user.TotpSercet.Valid {
return fiber.NewError(fiber.StatusForbidden, "Forbidden")
}
sess, err := helpers.GetSessionStore(c)
if err != nil {
return err
}
options := totp.GenerateOpts{
Issuer: "POP Vaud",
AccountName: user.Email,
}
key, err := totp.Generate(options)
if err != nil {
return err
}
img, err := key.Image(200, 200)
if err != nil {
return err
}
var buf bytes.Buffer
err = png.Encode(&buf, img)
if err != nil {
return err
}
imgBase64 := fmt.Sprintf(
"data:image/png;base64,%s",
base64.StdEncoding.EncodeToString(buf.Bytes()),
)
fmt.Println(imgBase64)
sess.Set("totp-enroll-secret", key.Secret())
err = sess.Save()
if err != nil {
return err
}
return c.Render("totp_enroll", fiber.Map{
"PageTitle": "Enregistrement multifacteur",
"QrCode": imgBase64,
"Secret": key.Secret(),
}, "layouts/main")
}

2
go.mod
View file

@ -15,6 +15,7 @@ require (
github.com/Joker/hpp v1.0.0 // indirect
github.com/Joker/jade v1.1.3 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
@ -29,6 +30,7 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/pquerna/otp v1.4.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect

5
go.sum
View file

@ -4,6 +4,8 @@ github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk=
github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -55,6 +57,8 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
@ -64,6 +68,7 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=

View file

@ -2,9 +2,11 @@ package helpers
import (
"errors"
"fmt"
"time"
"git.readonly.ch/bouzoure/popvaud-people/models"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
)
@ -73,3 +75,23 @@ func UserExistsAndIsActive(id uint) (bool, error) {
return true, nil
}
func GetSessionUserId(c *fiber.Ctx) (uint, error) {
sess, err := GetSessionStore(c)
if err != nil {
return 0, err
}
userid := sess.Get("userid")
if userid == nil {
return 0, fmt.Errorf("no value for key userid in session")
}
switch userid.(type) {
case uint:
default:
return 0, fmt.Errorf("userid value is not of type uint as expected")
}
return userid.(uint), nil
}

View file

@ -88,6 +88,7 @@ func main() {
app.Get("/login", controllers.LoginForm)
app.Post("/login", controllers.LoginProcess)
app.Get("/logout", controllers.LogoutProcess)
app.Get("/mfa/totp/enroll", controllers.TotpEnrollPage)
listenAddr := fmt.Sprintf(
"%s:%d",

1
middlewares/mfa.go Normal file
View file

@ -0,0 +1 @@
package middlewares

4
static/events.js Normal file
View file

@ -0,0 +1,4 @@
$(document).ready(function() {
var elem = $("#totp-enroll-image img");
$(elem).attr("src", $(elem).data("image"));
});

2
static/jquery/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -13,6 +13,8 @@ html(lang="fr", data-bs-theme="dark")
body
| {{embed}}
script(src="/static/jquery/jquery.min.js")
script(src="/static/bootstrap/js/bootstrap.bundle.min.js")
script(src="/static/feather/dist/feather.min.js")
script feather.replace();
script feather.replace();
script(src="/static/events.js")

24
views/totp_enroll.pug Normal file
View file

@ -0,0 +1,24 @@
include partials/header.pug
.container
#login-card.my-5
.card
.card-header
| Vérification multifacteur (TOTP)
.card-body
if .MfaError
.alert.alert-danger
| #{.MfaError}
#totp-enroll-image.text-center.my-3
img(data-image=.QrCode)
form#login(method="post")
.mb-3
label.form-label(for="secret") Secret (si pas possible de scanner le code QR)
input#secret.form-control(type="text", value!=.Secret, disabled)
.mb-3
label.form-label(for="otp") Code temporaire
input#otp.form-control(type="text", required, name="otp", placeholder="000000" pattern="[0-9]{6}")
.mt-3.text-end
button.btn.btn-primary(type="submit")
i.me-2(data-feather="check-circle")
| Vérifier