package admin import ( "fmt" "net/http" "os" "time" "arimelody-web/controller" "arimelody-web/model" "golang.org/x/crypto/bcrypt" ) func accountHandler(app *model.AppState) http.Handler { mux := http.NewServeMux() mux.Handle("/password", changePasswordHandler(app)) mux.Handle("/delete", deleteAccountHandler(app)) mux.Handle("/", accountIndexHandler(app)) return mux } func accountIndexHandler(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { session := r.Context().Value("session").(*model.Session) totps, err := controller.GetTOTPsForAccount(app.DB, session.Account.ID) if err != nil { fmt.Printf("WARN: Failed to fetch TOTPs: %v\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } type accountResponse struct { Session *model.Session TOTPs []model.TOTP } err = pages["account"].Execute(w, accountResponse{ Session: session, 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) } }) } func changePasswordHandler(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) r.ParseForm() currentPassword := r.Form.Get("current-password") if err := bcrypt.CompareHashAndPassword([]byte(session.Account.Password), []byte(currentPassword)); err != nil { controller.SetSessionMessage(app.DB, session, "Incorrect password.") http.Redirect(w, r, "/admin/account", http.StatusFound) return } newPassword := r.Form.Get("new-password") controller.SetSessionMessage(app.DB, session, fmt.Sprintf("Updating password to %s", newPassword)) http.Redirect(w, r, "/admin/account", http.StatusFound) }) } func deleteAccountHandler(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 } err := r.ParseForm() if err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } if !r.Form.Has("password") || !r.Form.Has("totp") { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } session := r.Context().Value("session").(*model.Session) // 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", time.Now().Format("2006-02-01 15:04:05"), session.Account.Username, ) controller.SetSessionMessage(app.DB, session, "Incorrect password.") http.Redirect(w, r, "/admin/account", http.StatusFound) return } totpMethod, err := controller.CheckTOTPForAccount(app.DB, session.Account.ID, r.Form.Get("totp")) if err != nil { fmt.Fprintf(os.Stderr, "Failed to fetch account: %v\n", err) controller.SetSessionMessage(app.DB, session, "Something went wrong. Please try again.") http.Redirect(w, r, "/admin/account", http.StatusFound) return } if totpMethod == nil { fmt.Printf( "[%s] WARN: Account \"%s\" attempted account deletion with incorrect TOTP.\n", time.Now().Format("2006-02-01 15:04:05"), session.Account.Username, ) controller.SetSessionMessage(app.DB, session, "Incorrect TOTP.") http.Redirect(w, r, "/admin/account", http.StatusFound) } err = controller.DeleteAccount(app.DB, session.Account.ID) if err != nil { fmt.Fprintf(os.Stderr, "Failed to delete account: %v\n", err) controller.SetSessionMessage(app.DB, session, "Something went wrong. Please try again.") http.Redirect(w, r, "/admin/account", http.StatusFound) return } fmt.Printf( "[%s] INFO: Account \"%s\" deleted by user request.\n", time.Now().Format("2006-02-01 15:04:05"), session.Account.Username, ) session.Account = nil controller.SetSessionMessage(app.DB, session, "Account deleted successfully.") http.Redirect(w, r, "/admin/login", http.StatusFound) }) }