2025-01-21 00:20:07 +00:00
|
|
|
package admin
|
|
|
|
|
|
|
|
import (
|
2025-01-26 20:37:20 +00:00
|
|
|
"database/sql"
|
2025-01-21 00:20:07 +00:00
|
|
|
"fmt"
|
|
|
|
"net/http"
|
2025-01-23 12:09:33 +00:00
|
|
|
"net/url"
|
2025-01-21 01:01:33 +00:00
|
|
|
"os"
|
|
|
|
"time"
|
2025-01-21 00:20:07 +00:00
|
|
|
|
|
|
|
"arimelody-web/controller"
|
|
|
|
"arimelody-web/model"
|
|
|
|
|
2025-01-21 01:01:33 +00:00
|
|
|
"golang.org/x/crypto/bcrypt"
|
2025-01-21 00:20:07 +00:00
|
|
|
)
|
|
|
|
|
2025-01-23 00:37:19 +00:00
|
|
|
func accountHandler(app *model.AppState) http.Handler {
|
2025-01-21 17:13:06 +00:00
|
|
|
mux := http.NewServeMux()
|
|
|
|
|
2025-01-23 12:09:33 +00:00
|
|
|
mux.Handle("/totp-setup", totpSetupHandler(app))
|
|
|
|
mux.Handle("/totp-confirm", totpConfirmHandler(app))
|
|
|
|
mux.Handle("/totp-delete/", http.StripPrefix("/totp-delete", totpDeleteHandler(app)))
|
|
|
|
|
2025-01-21 17:13:06 +00:00
|
|
|
mux.Handle("/password", changePasswordHandler(app))
|
2025-01-23 00:37:19 +00:00
|
|
|
mux.Handle("/delete", deleteAccountHandler(app))
|
2025-01-21 17:13:06 +00:00
|
|
|
|
|
|
|
return mux
|
|
|
|
}
|
|
|
|
|
|
|
|
func accountIndexHandler(app *model.AppState) http.Handler {
|
2025-01-21 00:20:07 +00:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2025-01-23 00:37:19 +00:00
|
|
|
session := r.Context().Value("session").(*model.Session)
|
2025-01-21 00:20:07 +00:00
|
|
|
|
2025-01-23 13:53:06 +00:00
|
|
|
dbTOTPs, err := controller.GetTOTPsForAccount(app.DB, session.Account.ID)
|
2025-01-21 00:20:07 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("WARN: Failed to fetch TOTPs: %v\n", err)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
|
2025-01-23 13:53:06 +00:00
|
|
|
type (
|
|
|
|
TOTP struct {
|
|
|
|
model.TOTP
|
|
|
|
CreatedAtString string
|
|
|
|
}
|
|
|
|
|
|
|
|
accountResponse struct {
|
|
|
|
Session *model.Session
|
|
|
|
TOTPs []TOTP
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
totps := []TOTP{}
|
|
|
|
for _, totp := range dbTOTPs {
|
|
|
|
totps = append(totps, TOTP{
|
|
|
|
TOTP: totp,
|
|
|
|
CreatedAtString: totp.CreatedAt.Format("02 Jan 2006, 15:04:05"),
|
|
|
|
})
|
2025-01-21 00:20:07 +00:00
|
|
|
}
|
|
|
|
|
2025-01-23 09:39:40 +00:00
|
|
|
sessionMessage := session.Message
|
|
|
|
sessionError := session.Error
|
|
|
|
controller.SetSessionMessage(app.DB, session, "")
|
|
|
|
controller.SetSessionError(app.DB, session, "")
|
|
|
|
session.Message = sessionMessage
|
|
|
|
session.Error = sessionError
|
|
|
|
|
2025-01-26 20:09:18 +00:00
|
|
|
err = accountTemplate.Execute(w, accountResponse{
|
2025-01-23 00:37:19 +00:00
|
|
|
Session: session,
|
2025-01-21 00:20:07 +00:00
|
|
|
TOTPs: totps,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("WARN: Failed to render admin account page: %v\n", err)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2025-01-23 00:37:19 +00:00
|
|
|
func changePasswordHandler(app *model.AppState) http.Handler {
|
2025-01-21 01:01:33 +00:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if r.Method != http.MethodPost {
|
2025-01-23 00:37:19 +00:00
|
|
|
http.NotFound(w, r)
|
2025-01-21 01:01:33 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-01-23 00:37:19 +00:00
|
|
|
session := r.Context().Value("session").(*model.Session)
|
2025-01-21 01:01:33 +00:00
|
|
|
|
2025-01-23 09:39:40 +00:00
|
|
|
controller.SetSessionMessage(app.DB, session, "")
|
|
|
|
controller.SetSessionError(app.DB, session, "")
|
|
|
|
|
2025-01-23 00:37:19 +00:00
|
|
|
r.ParseForm()
|
2025-01-21 01:01:33 +00:00
|
|
|
|
2025-01-23 00:37:19 +00:00
|
|
|
currentPassword := r.Form.Get("current-password")
|
|
|
|
if err := bcrypt.CompareHashAndPassword([]byte(session.Account.Password), []byte(currentPassword)); err != nil {
|
2025-01-23 09:39:40 +00:00
|
|
|
controller.SetSessionError(app.DB, session, "Incorrect password.")
|
2025-01-23 00:37:19 +00:00
|
|
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
2025-01-21 01:01:33 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-01-23 00:37:19 +00:00
|
|
|
newPassword := r.Form.Get("new-password")
|
2025-01-21 01:01:33 +00:00
|
|
|
|
2025-01-23 09:39:40 +00:00
|
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "WARN: Failed to generate password hash: %v\n", err)
|
|
|
|
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
|
|
|
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
session.Account.Password = string(hashedPassword)
|
|
|
|
err = controller.UpdateAccount(app.DB, session.Account)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "WARN: Failed to update account password: %v\n", err)
|
|
|
|
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
|
|
|
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
controller.SetSessionError(app.DB, session, "")
|
|
|
|
controller.SetSessionMessage(app.DB, session, "Password updated successfully.")
|
2025-01-23 00:37:19 +00:00
|
|
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
2025-01-21 01:01:33 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2025-01-23 00:37:19 +00:00
|
|
|
func deleteAccountHandler(app *model.AppState) http.Handler {
|
2025-01-21 01:01:33 +00:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if r.Method != http.MethodPost {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-01-23 00:37:19 +00:00
|
|
|
err := r.ParseForm()
|
2025-01-21 01:01:33 +00:00
|
|
|
if err != nil {
|
2025-01-23 00:37:19 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
2025-01-21 01:01:33 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-01-23 00:37:19 +00:00
|
|
|
if !r.Form.Has("password") || !r.Form.Has("totp") {
|
|
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
2025-01-21 01:01:33 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-01-23 00:37:19 +00:00
|
|
|
session := r.Context().Value("session").(*model.Session)
|
2025-01-21 01:01:33 +00:00
|
|
|
|
2025-01-23 00:37:19 +00:00
|
|
|
// check password
|
|
|
|
if err := bcrypt.CompareHashAndPassword([]byte(session.Account.Password), []byte(r.Form.Get("password"))); err != nil {
|
|
|
|
fmt.Printf(
|
|
|
|
"[%s] WARN: Account \"%s\" attempted account deletion with incorrect password.\n",
|
2025-01-23 09:39:40 +00:00
|
|
|
time.Now().Format(time.UnixDate),
|
2025-01-23 00:37:19 +00:00
|
|
|
session.Account.Username,
|
|
|
|
)
|
2025-01-23 09:39:40 +00:00
|
|
|
controller.SetSessionError(app.DB, session, "Incorrect password.")
|
2025-01-23 00:37:19 +00:00
|
|
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
2025-01-21 01:01:33 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-01-23 00:37:19 +00:00
|
|
|
totpMethod, err := controller.CheckTOTPForAccount(app.DB, session.Account.ID, r.Form.Get("totp"))
|
2025-01-21 01:01:33 +00:00
|
|
|
if err != nil {
|
2025-01-23 00:37:19 +00:00
|
|
|
fmt.Fprintf(os.Stderr, "Failed to fetch account: %v\n", err)
|
2025-01-23 09:39:40 +00:00
|
|
|
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
|
2025-01-23 00:37:19 +00:00
|
|
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
2025-01-21 01:01:33 +00:00
|
|
|
return
|
|
|
|
}
|
2025-01-23 00:37:19 +00:00
|
|
|
if totpMethod == nil {
|
|
|
|
fmt.Printf(
|
|
|
|
"[%s] WARN: Account \"%s\" attempted account deletion with incorrect TOTP.\n",
|
2025-01-23 09:39:40 +00:00
|
|
|
time.Now().Format(time.UnixDate),
|
2025-01-23 00:37:19 +00:00
|
|
|
session.Account.Username,
|
|
|
|
)
|
2025-01-23 09:39:40 +00:00
|
|
|
controller.SetSessionError(app.DB, session, "Incorrect TOTP.")
|
2025-01-23 00:37:19 +00:00
|
|
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
2025-01-21 01:01:33 +00:00
|
|
|
}
|
|
|
|
|
2025-01-23 00:37:19 +00:00
|
|
|
err = controller.DeleteAccount(app.DB, session.Account.ID)
|
2025-01-21 01:01:33 +00:00
|
|
|
if err != nil {
|
2025-01-23 00:37:19 +00:00
|
|
|
fmt.Fprintf(os.Stderr, "Failed to delete account: %v\n", err)
|
2025-01-23 09:39:40 +00:00
|
|
|
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
|
2025-01-23 00:37:19 +00:00
|
|
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
2025-01-21 17:13:06 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-01-23 00:37:19 +00:00
|
|
|
fmt.Printf(
|
|
|
|
"[%s] INFO: Account \"%s\" deleted by user request.\n",
|
2025-01-23 09:39:40 +00:00
|
|
|
time.Now().Format(time.UnixDate),
|
2025-01-23 00:37:19 +00:00
|
|
|
session.Account.Username,
|
2025-01-21 17:13:06 +00:00
|
|
|
)
|
2025-01-23 00:37:19 +00:00
|
|
|
|
2025-01-23 09:39:40 +00:00
|
|
|
controller.SetSessionAccount(app.DB, session, nil)
|
|
|
|
controller.SetSessionError(app.DB, session, "")
|
2025-01-23 00:37:19 +00:00
|
|
|
controller.SetSessionMessage(app.DB, session, "Account deleted successfully.")
|
|
|
|
http.Redirect(w, r, "/admin/login", http.StatusFound)
|
2025-01-21 01:01:33 +00:00
|
|
|
})
|
|
|
|
}
|
2025-01-23 12:09:33 +00:00
|
|
|
|
2025-01-26 20:37:20 +00:00
|
|
|
type totpConfirmData struct {
|
|
|
|
Session *model.Session
|
|
|
|
TOTP *model.TOTP
|
|
|
|
NameEscaped string
|
|
|
|
QRBase64Image string
|
|
|
|
}
|
|
|
|
|
2025-01-23 12:09:33 +00:00
|
|
|
func totpSetupHandler(app *model.AppState) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if r.Method == http.MethodGet {
|
|
|
|
type totpSetupData struct {
|
|
|
|
Session *model.Session
|
|
|
|
}
|
|
|
|
|
|
|
|
session := r.Context().Value("session").(*model.Session)
|
|
|
|
|
2025-01-26 20:09:18 +00:00
|
|
|
err := totpSetupTemplate.Execute(w, totpSetupData{ Session: session })
|
2025-01-23 12:09:33 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("WARN: Failed to render TOTP setup page: %s\n", err)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.Method != http.MethodPost {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err := r.ParseForm()
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
name := r.FormValue("totp-name")
|
|
|
|
if len(name) == 0 {
|
|
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
session := r.Context().Value("session").(*model.Session)
|
|
|
|
|
|
|
|
secret := controller.GenerateTOTPSecret(controller.TOTP_SECRET_LENGTH)
|
|
|
|
totp := model.TOTP {
|
|
|
|
AccountID: session.Account.ID,
|
|
|
|
Name: name,
|
|
|
|
Secret: string(secret),
|
|
|
|
}
|
|
|
|
err = controller.CreateTOTP(app.DB, &totp)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("WARN: Failed to create TOTP method: %s\n", err)
|
|
|
|
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
|
2025-01-26 20:37:20 +00:00
|
|
|
err := totpSetupTemplate.Execute(w, totpConfirmData{ Session: session })
|
2025-01-23 12:09:33 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("WARN: Failed to render TOTP setup page: %s\n", err)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-01-26 20:09:18 +00:00
|
|
|
qrBase64Image, err := controller.GenerateQRCode(
|
|
|
|
controller.GenerateTOTPURI(session.Account.Username, totp.Secret))
|
|
|
|
if err != nil {
|
2025-01-26 20:37:20 +00:00
|
|
|
fmt.Fprintf(os.Stderr, "WARN: Failed to generate TOTP QR code: %v\n", err)
|
2025-01-26 20:09:18 +00:00
|
|
|
}
|
|
|
|
|
2025-01-26 20:37:20 +00:00
|
|
|
err = totpConfirmTemplate.Execute(w, totpConfirmData{
|
2025-01-23 12:09:33 +00:00
|
|
|
Session: session,
|
|
|
|
TOTP: &totp,
|
|
|
|
NameEscaped: url.PathEscape(totp.Name),
|
2025-01-26 20:09:18 +00:00
|
|
|
QRBase64Image: qrBase64Image,
|
2025-01-23 12:09:33 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("WARN: Failed to render TOTP confirm page: %s\n", err)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func totpConfirmHandler(app *model.AppState) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if r.Method != http.MethodPost {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
session := r.Context().Value("session").(*model.Session)
|
|
|
|
|
|
|
|
err := r.ParseForm()
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
name := r.FormValue("totp-name")
|
|
|
|
if len(name) == 0 {
|
|
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
code := r.FormValue("totp")
|
|
|
|
if len(code) != controller.TOTP_CODE_LENGTH {
|
|
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
totp, err := controller.GetTOTP(app.DB, session.Account.ID, name)
|
|
|
|
if err != nil {
|
2025-01-26 20:37:20 +00:00
|
|
|
fmt.Printf("WARN: Failed to fetch TOTP method: %v\n", err)
|
2025-01-23 12:09:33 +00:00
|
|
|
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
|
|
|
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if totp == nil {
|
|
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-01-26 20:37:20 +00:00
|
|
|
qrBase64Image, err := controller.GenerateQRCode(
|
|
|
|
controller.GenerateTOTPURI(session.Account.Username, totp.Secret))
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "WARN: Failed to generate TOTP QR code: %v\n", err)
|
|
|
|
}
|
|
|
|
|
2025-01-23 12:09:33 +00:00
|
|
|
confirmCode := controller.GenerateTOTP(totp.Secret, 0)
|
|
|
|
if code != confirmCode {
|
|
|
|
confirmCodeOffset := controller.GenerateTOTP(totp.Secret, 1)
|
|
|
|
if code != confirmCodeOffset {
|
2025-01-26 20:37:20 +00:00
|
|
|
session.Error = sql.NullString{ Valid: true, String: "Incorrect TOTP code. Please try again." }
|
2025-01-26 20:09:18 +00:00
|
|
|
err = totpConfirmTemplate.Execute(w, totpConfirmData{
|
2025-01-23 12:09:33 +00:00
|
|
|
Session: session,
|
|
|
|
TOTP: totp,
|
2025-01-26 20:37:20 +00:00
|
|
|
NameEscaped: url.PathEscape(totp.Name),
|
|
|
|
QRBase64Image: qrBase64Image,
|
2025-01-23 12:09:33 +00:00
|
|
|
})
|
2025-01-26 20:37:20 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "WARN: Failed to render TOTP setup page: %v\n", err)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
}
|
2025-01-23 12:09:33 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-26 20:37:20 +00:00
|
|
|
err = controller.ConfirmTOTP(app.DB, session.Account.ID, name)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("WARN: Failed to confirm TOTP method: %s\n", err)
|
|
|
|
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
|
|
|
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-01-23 12:09:33 +00:00
|
|
|
controller.SetSessionError(app.DB, session, "")
|
|
|
|
controller.SetSessionMessage(app.DB, session, fmt.Sprintf("TOTP method \"%s\" created successfully.", totp.Name))
|
|
|
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func totpDeleteHandler(app *model.AppState) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if r.Method != http.MethodGet {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-01-26 00:48:19 +00:00
|
|
|
if len(r.URL.Path) < 2 {
|
2025-01-23 12:09:33 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
2025-01-26 00:48:19 +00:00
|
|
|
name := r.URL.Path[1:]
|
2025-01-23 12:09:33 +00:00
|
|
|
|
|
|
|
session := r.Context().Value("session").(*model.Session)
|
|
|
|
|
|
|
|
totp, err := controller.GetTOTP(app.DB, session.Account.ID, name)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("WARN: Failed to fetch TOTP method: %s\n", err)
|
|
|
|
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
|
|
|
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if totp == nil {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = controller.DeleteTOTP(app.DB, session.Account.ID, totp.Name)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("WARN: Failed to delete TOTP method: %s\n", err)
|
|
|
|
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
|
|
|
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
controller.SetSessionError(app.DB, session, "")
|
|
|
|
controller.SetSessionMessage(app.DB, session, fmt.Sprintf("TOTP method \"%s\" deleted successfully.", totp.Name))
|
|
|
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
|
|
|
})
|
|
|
|
}
|