mirror of
https://github.com/thomiceli/opengist.git
synced 2025-01-14 20:02:42 +00:00
177 lines
4.6 KiB
Go
177 lines
4.6 KiB
Go
package auth
|
|
|
|
import (
|
|
"github.com/thomiceli/opengist/internal/auth/totp"
|
|
"github.com/thomiceli/opengist/internal/db"
|
|
"github.com/thomiceli/opengist/internal/web/context"
|
|
"net/url"
|
|
)
|
|
|
|
func BeginTotp(ctx *context.Context) error {
|
|
user := ctx.User
|
|
|
|
if _, hasTotp, err := user.HasMFA(); err != nil {
|
|
return ctx.ErrorRes(500, "Cannot check for user MFA", err)
|
|
} else if hasTotp {
|
|
ctx.AddFlash(ctx.Tr("auth.totp.already-enabled"), "error")
|
|
return ctx.RedirectTo("/settings")
|
|
}
|
|
|
|
ogUrl, err := url.Parse(ctx.GetData("baseHttpUrl").(string))
|
|
if err != nil {
|
|
return ctx.ErrorRes(500, "Cannot parse base URL", err)
|
|
}
|
|
|
|
sess := ctx.GetSession()
|
|
generatedSecret, _ := sess.Values["generatedSecret"].([]byte)
|
|
|
|
totpSecret, qrcode, err, generatedSecret := totp.GenerateQRCode(ctx.User.Username, ogUrl.Hostname(), generatedSecret)
|
|
if err != nil {
|
|
return ctx.ErrorRes(500, "Cannot generate TOTP QR code", err)
|
|
}
|
|
sess.Values["totpSecret"] = totpSecret
|
|
sess.Values["generatedSecret"] = generatedSecret
|
|
ctx.SaveSession(sess)
|
|
|
|
ctx.SetData("totpSecret", totpSecret)
|
|
ctx.SetData("totpQrcode", qrcode)
|
|
|
|
return ctx.HTML_("totp.html")
|
|
|
|
}
|
|
|
|
func FinishTotp(ctx *context.Context) error {
|
|
user := ctx.User
|
|
|
|
if _, hasTotp, err := user.HasMFA(); err != nil {
|
|
return ctx.ErrorRes(500, "Cannot check for user MFA", err)
|
|
} else if hasTotp {
|
|
ctx.AddFlash(ctx.Tr("auth.totp.already-enabled"), "error")
|
|
return ctx.RedirectTo("/settings")
|
|
}
|
|
|
|
dto := &db.TOTPDTO{}
|
|
if err := ctx.Bind(dto); err != nil {
|
|
return ctx.ErrorRes(400, ctx.Tr("error.cannot-bind-data"), err)
|
|
}
|
|
|
|
if err := ctx.Validate(dto); err != nil {
|
|
ctx.AddFlash("Invalid secret", "error")
|
|
return ctx.RedirectTo("/settings/totp/generate")
|
|
}
|
|
|
|
sess := ctx.GetSession()
|
|
secret, ok := sess.Values["totpSecret"].(string)
|
|
if !ok {
|
|
return ctx.ErrorRes(500, "Cannot get TOTP secret from session", nil)
|
|
}
|
|
|
|
if !totp.Validate(dto.Code, secret) {
|
|
ctx.AddFlash(ctx.Tr("auth.totp.invalid-code"), "error")
|
|
|
|
return ctx.RedirectTo("/settings/totp/generate")
|
|
}
|
|
|
|
userTotp := &db.TOTP{
|
|
UserID: ctx.User.ID,
|
|
}
|
|
if err := userTotp.StoreSecret(secret); err != nil {
|
|
return ctx.ErrorRes(500, "Cannot store TOTP secret", err)
|
|
}
|
|
|
|
if err := userTotp.Create(); err != nil {
|
|
return ctx.ErrorRes(500, "Cannot create TOTP", err)
|
|
}
|
|
|
|
ctx.AddFlash("TOTP successfully enabled", "success")
|
|
codes, err := userTotp.GenerateRecoveryCodes()
|
|
if err != nil {
|
|
return ctx.ErrorRes(500, "Cannot generate recovery codes", err)
|
|
}
|
|
|
|
delete(sess.Values, "totpSecret")
|
|
delete(sess.Values, "generatedSecret")
|
|
ctx.SaveSession(sess)
|
|
|
|
ctx.SetData("recoveryCodes", codes)
|
|
return ctx.HTML_("totp.html")
|
|
}
|
|
|
|
func AssertTotp(ctx *context.Context) error {
|
|
var err error
|
|
dto := &db.TOTPDTO{}
|
|
if err := ctx.Bind(dto); err != nil {
|
|
return ctx.ErrorRes(400, ctx.Tr("error.cannot-bind-data"), err)
|
|
}
|
|
|
|
if err := ctx.Validate(dto); err != nil {
|
|
ctx.AddFlash(ctx.Tr("auth.totp.invalid-code"), "error")
|
|
return ctx.RedirectTo("/mfa")
|
|
}
|
|
|
|
sess := ctx.GetSession()
|
|
userId := sess.Values["mfaID"].(uint)
|
|
var userTotp *db.TOTP
|
|
if userTotp, err = db.GetTOTPByUserID(userId); err != nil {
|
|
return ctx.ErrorRes(500, "Cannot get TOTP by UID", err)
|
|
}
|
|
|
|
redirectUrl := "/"
|
|
|
|
var validCode, validRecoveryCode bool
|
|
if validCode, err = userTotp.ValidateCode(dto.Code); err != nil {
|
|
return ctx.ErrorRes(500, "Cannot validate TOTP code", err)
|
|
}
|
|
if !validCode {
|
|
validRecoveryCode, err = userTotp.ValidateRecoveryCode(dto.Code)
|
|
if err != nil {
|
|
return ctx.ErrorRes(500, "Cannot validate TOTP code", err)
|
|
}
|
|
|
|
if !validRecoveryCode {
|
|
ctx.AddFlash(ctx.Tr("auth.totp.invalid-code"), "error")
|
|
return ctx.RedirectTo("/mfa")
|
|
}
|
|
|
|
ctx.AddFlash(ctx.Tr("auth.totp.code-used", dto.Code), "warning")
|
|
redirectUrl = "/settings"
|
|
}
|
|
|
|
sess.Values["user"] = userId
|
|
sess.Options.MaxAge = 60 * 60 * 24 * 365 // 1 year
|
|
delete(sess.Values, "mfaID")
|
|
ctx.SaveSession(sess)
|
|
|
|
return ctx.RedirectTo(redirectUrl)
|
|
}
|
|
|
|
func DisableTotp(ctx *context.Context) error {
|
|
user := ctx.User
|
|
userTotp, err := db.GetTOTPByUserID(user.ID)
|
|
if err != nil {
|
|
return ctx.ErrorRes(500, "Cannot get TOTP by UID", err)
|
|
}
|
|
|
|
if err = userTotp.Delete(); err != nil {
|
|
return ctx.ErrorRes(500, "Cannot delete TOTP", err)
|
|
}
|
|
|
|
ctx.AddFlash(ctx.Tr("auth.totp.disabled"), "success")
|
|
return ctx.RedirectTo("/settings")
|
|
}
|
|
|
|
func RegenerateTotpRecoveryCodes(ctx *context.Context) error {
|
|
user := ctx.User
|
|
userTotp, err := db.GetTOTPByUserID(user.ID)
|
|
if err != nil {
|
|
return ctx.ErrorRes(500, "Cannot get TOTP by UID", err)
|
|
}
|
|
|
|
codes, err := userTotp.GenerateRecoveryCodes()
|
|
if err != nil {
|
|
return ctx.ErrorRes(500, "Cannot generate recovery codes", err)
|
|
}
|
|
|
|
ctx.SetData("recoveryCodes", codes)
|
|
return ctx.HTML_("totp.html")
|
|
}
|