149 lines
4.2 KiB
Go
149 lines
4.2 KiB
Go
|
package account
|
||
|
|
||
|
import (
|
||
|
"net/http"
|
||
|
|
||
|
"code.nonshy.com/nonshy/website/pkg/config"
|
||
|
"code.nonshy.com/nonshy/website/pkg/log"
|
||
|
"code.nonshy.com/nonshy/website/pkg/models"
|
||
|
"code.nonshy.com/nonshy/website/pkg/session"
|
||
|
"code.nonshy.com/nonshy/website/pkg/templates"
|
||
|
"github.com/pquerna/otp"
|
||
|
"github.com/pquerna/otp/totp"
|
||
|
)
|
||
|
|
||
|
// 2FA Setup page (/account/two-factor/setup)
|
||
|
func Setup2FA() http.HandlerFunc {
|
||
|
tmpl := templates.Must("account/two_factor_setup.html")
|
||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
// Load the current user.
|
||
|
currentUser, err := session.CurrentUser(r)
|
||
|
if err != nil {
|
||
|
session.FlashError(w, r, "Couldn't get CurrentUser: %s", err)
|
||
|
templates.Redirect(w, r.URL.Path)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Get their current 2FA settings.
|
||
|
tf := models.Get2FA(currentUser.ID)
|
||
|
|
||
|
// If they aren't already set up, prepare a new TOTP secret for first-time setup.
|
||
|
var key *otp.Key
|
||
|
if tf.IsNew() {
|
||
|
// Generate new TOTP parameters.
|
||
|
if newKey, err := totp.Generate(totp.GenerateOpts{
|
||
|
Issuer: config.Title,
|
||
|
AccountName: currentUser.Username,
|
||
|
}); err != nil {
|
||
|
session.FlashError(w, r, "Error generating TOTP: %s", err)
|
||
|
templates.Redirect(w, "/me")
|
||
|
return
|
||
|
} else {
|
||
|
key = newKey
|
||
|
}
|
||
|
|
||
|
// Set the secret.
|
||
|
tf.SetSecret(key.URL())
|
||
|
|
||
|
// Save it.
|
||
|
if err := tf.Save(); err != nil {
|
||
|
session.FlashError(w, r, "Error saving TOTP settings to the database: %s", err)
|
||
|
templates.Redirect(w, "/me")
|
||
|
return
|
||
|
}
|
||
|
} else {
|
||
|
// Reconstruct the stored TOTP key.
|
||
|
secret, err := tf.GetSecret()
|
||
|
if err != nil {
|
||
|
session.FlashError(w, r, "Error retrieving 2FA secret: %s", err)
|
||
|
templates.Redirect(w, "/me")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Reconstruct the OTP key object.
|
||
|
if k, err := otp.NewKeyFromURL(secret); err != nil {
|
||
|
session.FlashError(w, r, "Error retrieving TOTP key: %s", err)
|
||
|
templates.Redirect(w, "/me")
|
||
|
return
|
||
|
} else {
|
||
|
key = k
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// POST form actions.
|
||
|
if r.Method == http.MethodPost {
|
||
|
var intent = r.PostFormValue("intent")
|
||
|
switch intent {
|
||
|
case "setup-verify":
|
||
|
// Setup: verify correct enrollment.
|
||
|
var (
|
||
|
code = r.PostFormValue("code")
|
||
|
valid = totp.Validate(code, key.Secret())
|
||
|
)
|
||
|
|
||
|
// Valid?
|
||
|
if !valid {
|
||
|
session.FlashError(w, r, "The passcode you submitted didn't seem correct. Try a new six-digit code.")
|
||
|
templates.Redirect(w, r.URL.Path)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// OK!
|
||
|
tf.Enabled = true
|
||
|
if err := tf.Save(); err != nil {
|
||
|
session.FlashError(w, r, "Error saving your TOTP settings to the database: %s", err)
|
||
|
} else {
|
||
|
session.Flash(w, r, "The authentication code was validated successfully! Two-Factor Authentication is now active for your account.")
|
||
|
}
|
||
|
case "regenerate-backup-codes":
|
||
|
// Re-generate backup codes.
|
||
|
if err := tf.GenerateBackupCodes(); err != nil {
|
||
|
session.FlashError(w, r, "Error generating backup codes: %s", err)
|
||
|
} else {
|
||
|
// Save the changes.
|
||
|
if err := tf.Save(); err != nil {
|
||
|
session.FlashError(w, r, "Error saving your TOTP settings to the database: %s", err)
|
||
|
} else {
|
||
|
session.Flash(w, r, "Your backup codes have been regenerated!")
|
||
|
}
|
||
|
}
|
||
|
case "disable":
|
||
|
// Disable 2FA. User password is required.
|
||
|
var password = r.PostFormValue("password")
|
||
|
if err := currentUser.CheckPassword(password); err != nil {
|
||
|
session.FlashError(w, r, "Couldn't disable 2FA: the password you entered is incorrect.")
|
||
|
} else {
|
||
|
// Delete the 2FA configuration.
|
||
|
if err := tf.Delete(); err != nil {
|
||
|
session.FlashError(w, r, "Couldn't delete 2FA setting from the database: %s", err)
|
||
|
} else {
|
||
|
session.Flash(w, r, "Your 2FA settings have been cleared and disabled.")
|
||
|
}
|
||
|
}
|
||
|
default:
|
||
|
session.FlashError(w, r, "Unknown intent: %s", intent)
|
||
|
}
|
||
|
|
||
|
templates.Redirect(w, r.URL.Path)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Generate the QR code.
|
||
|
qrCode, err := tf.QRCodeAsDataURL(key)
|
||
|
if err != nil {
|
||
|
log.Error("TwoFactor: Couldn't create QR code: %s", err)
|
||
|
}
|
||
|
|
||
|
var vars = map[string]interface{}{
|
||
|
"TwoFactor": tf,
|
||
|
"Key": key,
|
||
|
"QRCode": qrCode,
|
||
|
}
|
||
|
|
||
|
if err := tmpl.Execute(w, r, vars); err != nil {
|
||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||
|
return
|
||
|
}
|
||
|
})
|
||
|
}
|