home to your local SPACEGIRL 馃挮 arimelody.space
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 368 lines 14 kB view raw
1package admin 2 3import ( 4 "database/sql" 5 "fmt" 6 "net/http" 7 "net/url" 8 "os" 9 10 "arimelody-web/admin/templates" 11 "arimelody-web/controller" 12 "arimelody-web/log" 13 "arimelody-web/model" 14 15 "golang.org/x/crypto/bcrypt" 16) 17 18func accountHandler(app *model.AppState) http.Handler { 19 mux := http.NewServeMux() 20 21 mux.Handle("/account/totp-setup", totpSetupHandler(app)) 22 mux.Handle("/account/totp-confirm", totpConfirmHandler(app)) 23 mux.Handle("/account/totp-delete", totpDeleteHandler(app)) 24 25 mux.Handle("/account/password", changePasswordHandler(app)) 26 mux.Handle("/account/delete", deleteAccountHandler(app)) 27 28 return mux 29} 30 31func accountIndexHandler(app *model.AppState) http.Handler { 32 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 33 session := r.Context().Value("session").(*model.Session) 34 35 dbTOTPs, err := controller.GetTOTPsForAccount(app.DB, session.Account.ID) 36 if err != nil { 37 fmt.Printf("WARN: Failed to fetch TOTPs: %v\n", err) 38 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 39 } 40 41 type ( 42 TOTP struct { 43 model.TOTP 44 CreatedAtString string 45 } 46 47 accountResponse struct { 48 adminPageData 49 TOTPs []TOTP 50 } 51 ) 52 53 totps := []TOTP{} 54 for _, totp := range dbTOTPs { 55 totps = append(totps, TOTP{ 56 TOTP: totp, 57 CreatedAtString: totp.CreatedAt.Format("02 Jan 2006, 15:04:05"), 58 }) 59 } 60 61 sessionMessage := session.Message 62 sessionError := session.Error 63 controller.SetSessionMessage(app.DB, session, "") 64 controller.SetSessionError(app.DB, session, "") 65 session.Message = sessionMessage 66 session.Error = sessionError 67 68 err = templates.AccountTemplate.Execute(w, accountResponse{ 69 adminPageData: adminPageData{ Path: r.URL.Path, Session: session }, 70 TOTPs: totps, 71 }) 72 if err != nil { 73 fmt.Printf("WARN: Failed to render admin account page: %v\n", err) 74 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 75 } 76 }) 77} 78 79func changePasswordHandler(app *model.AppState) http.Handler { 80 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 81 if r.Method != http.MethodPost { 82 http.NotFound(w, r) 83 return 84 } 85 86 session := r.Context().Value("session").(*model.Session) 87 88 controller.SetSessionMessage(app.DB, session, "") 89 controller.SetSessionError(app.DB, session, "") 90 91 r.ParseForm() 92 93 currentPassword := r.Form.Get("current-password") 94 if err := bcrypt.CompareHashAndPassword([]byte(session.Account.Password), []byte(currentPassword)); err != nil { 95 controller.SetSessionError(app.DB, session, "Incorrect password.") 96 http.Redirect(w, r, "/admin/account", http.StatusFound) 97 return 98 } 99 100 newPassword := r.Form.Get("new-password") 101 102 hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost) 103 if err != nil { 104 fmt.Fprintf(os.Stderr, "WARN: Failed to generate password hash: %v\n", err) 105 controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.") 106 http.Redirect(w, r, "/admin/account", http.StatusFound) 107 return 108 } 109 110 session.Account.Password = string(hashedPassword) 111 err = controller.UpdateAccount(app.DB, session.Account) 112 if err != nil { 113 fmt.Fprintf(os.Stderr, "WARN: Failed to update account password: %v\n", err) 114 controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.") 115 http.Redirect(w, r, "/admin/account", http.StatusFound) 116 return 117 } 118 119 app.Log.Info(log.TYPE_ACCOUNT, "\"%s\" changed password by user request. (%s)", session.Account.Username, controller.ResolveIP(app, r)) 120 121 controller.SetSessionError(app.DB, session, "") 122 controller.SetSessionMessage(app.DB, session, "Password updated successfully.") 123 http.Redirect(w, r, "/admin/account", http.StatusFound) 124 }) 125} 126 127func deleteAccountHandler(app *model.AppState) http.Handler { 128 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 129 if r.Method != http.MethodPost { 130 http.NotFound(w, r) 131 return 132 } 133 134 err := r.ParseForm() 135 if err != nil { 136 http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 137 return 138 } 139 140 if !r.Form.Has("password") { 141 http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 142 return 143 } 144 145 session := r.Context().Value("session").(*model.Session) 146 147 // check password 148 if err := bcrypt.CompareHashAndPassword([]byte(session.Account.Password), []byte(r.Form.Get("password"))); err != nil { 149 app.Log.Warn(log.TYPE_ACCOUNT, "Account \"%s\" attempted account deletion with incorrect password. (%s)", session.Account.Username, controller.ResolveIP(app, r)) 150 controller.SetSessionError(app.DB, session, "Incorrect password.") 151 http.Redirect(w, r, "/admin/account", http.StatusFound) 152 return 153 } 154 155 err = controller.DeleteAccount(app.DB, session.Account.ID) 156 if err != nil { 157 fmt.Fprintf(os.Stderr, "Failed to delete account: %v\n", err) 158 controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.") 159 http.Redirect(w, r, "/admin/account", http.StatusFound) 160 return 161 } 162 163 app.Log.Info(log.TYPE_ACCOUNT, "Account \"%s\" deleted by user request. (%s)", session.Account.Username, controller.ResolveIP(app, r)) 164 165 controller.SetSessionAccount(app.DB, session, nil) 166 controller.SetSessionError(app.DB, session, "") 167 controller.SetSessionMessage(app.DB, session, "Account deleted successfully.") 168 http.Redirect(w, r, "/admin/login", http.StatusFound) 169 }) 170} 171 172type totpConfirmData struct { 173 adminPageData 174 TOTP *model.TOTP 175 NameEscaped string 176 QRBase64Image string 177} 178 179func totpSetupHandler(app *model.AppState) http.Handler { 180 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 181 if r.Method == http.MethodGet { 182 session := r.Context().Value("session").(*model.Session) 183 184 err := templates.TOTPSetupTemplate.Execute(w, adminPageData{ Path: "/account", Session: session }) 185 if err != nil { 186 fmt.Printf("WARN: Failed to render TOTP setup page: %s\n", err) 187 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 188 } 189 return 190 } 191 192 if r.Method != http.MethodPost { 193 http.NotFound(w, r) 194 return 195 } 196 197 err := r.ParseForm() 198 if err != nil { 199 http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 200 return 201 } 202 203 name := r.FormValue("totp-name") 204 if len(name) == 0 { 205 http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 206 return 207 } 208 209 session := r.Context().Value("session").(*model.Session) 210 211 secret := controller.GenerateTOTPSecret(controller.TOTP_SECRET_LENGTH) 212 totp := model.TOTP { 213 AccountID: session.Account.ID, 214 Name: name, 215 Secret: string(secret), 216 } 217 err = controller.CreateTOTP(app.DB, &totp) 218 if err != nil { 219 fmt.Printf("WARN: Failed to create TOTP method: %s\n", err) 220 controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.") 221 err := templates.TOTPSetupTemplate.Execute(w, totpConfirmData{ 222 adminPageData: adminPageData{ Path: r.URL.Path, Session: session }, 223 }) 224 if err != nil { 225 fmt.Printf("WARN: Failed to render TOTP setup page: %s\n", err) 226 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 227 } 228 return 229 } 230 231 qrBase64Image, err := controller.GenerateQRCode( 232 controller.GenerateTOTPURI(session.Account.Username, totp.Secret)) 233 if err != nil { 234 fmt.Fprintf(os.Stderr, "WARN: Failed to generate TOTP QR code: %v\n", err) 235 } 236 237 err = templates.TOTPConfirmTemplate.Execute(w, totpConfirmData{ 238 adminPageData: adminPageData{ Path: r.URL.Path, Session: session }, 239 TOTP: &totp, 240 NameEscaped: url.PathEscape(totp.Name), 241 QRBase64Image: qrBase64Image, 242 }) 243 if err != nil { 244 fmt.Printf("WARN: Failed to render TOTP confirm page: %s\n", err) 245 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 246 } 247 }) 248} 249 250func totpConfirmHandler(app *model.AppState) http.Handler { 251 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 252 if r.Method != http.MethodPost { 253 http.NotFound(w, r) 254 return 255 } 256 257 session := r.Context().Value("session").(*model.Session) 258 259 err := r.ParseForm() 260 if err != nil { 261 http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 262 return 263 } 264 name := r.FormValue("totp-name") 265 if len(name) == 0 { 266 http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 267 return 268 } 269 270 totp, err := controller.GetTOTP(app.DB, session.Account.ID, name) 271 if err != nil { 272 fmt.Printf("WARN: Failed to fetch TOTP method: %v\n", err) 273 controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.") 274 http.Redirect(w, r, "/admin/account", http.StatusFound) 275 return 276 } 277 if totp == nil { 278 http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 279 return 280 } 281 282 qrBase64Image, err := controller.GenerateQRCode( 283 controller.GenerateTOTPURI(session.Account.Username, totp.Secret)) 284 if err != nil { 285 fmt.Fprintf(os.Stderr, "WARN: Failed to generate TOTP QR code: %v\n", err) 286 } 287 288 code := r.FormValue("totp") 289 confirmCode := controller.GenerateTOTP(totp.Secret, 0) 290 confirmCodeOffset := controller.GenerateTOTP(totp.Secret, 1) 291 if len(code) != controller.TOTP_CODE_LENGTH || (code != confirmCode && code != confirmCodeOffset) { 292 session.Error = sql.NullString{ Valid: true, String: "Incorrect TOTP code. Please try again." } 293 err = templates.TOTPConfirmTemplate.Execute(w, totpConfirmData{ 294 adminPageData: adminPageData{ Path: r.URL.Path, Session: session }, 295 TOTP: totp, 296 NameEscaped: url.PathEscape(totp.Name), 297 QRBase64Image: qrBase64Image, 298 }) 299 if err != nil { 300 fmt.Fprintf(os.Stderr, "WARN: Failed to render TOTP setup page: %v\n", err) 301 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 302 } 303 return 304 } 305 306 err = controller.ConfirmTOTP(app.DB, session.Account.ID, name) 307 if err != nil { 308 fmt.Printf("WARN: Failed to confirm TOTP method: %s\n", err) 309 controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.") 310 http.Redirect(w, r, "/admin/account", http.StatusFound) 311 return 312 } 313 314 app.Log.Info(log.TYPE_ACCOUNT, "\"%s\" created TOTP method \"%s\".", session.Account.Username, totp.Name) 315 316 controller.SetSessionError(app.DB, session, "") 317 controller.SetSessionMessage(app.DB, session, fmt.Sprintf("TOTP method \"%s\" created successfully.", totp.Name)) 318 http.Redirect(w, r, "/admin/account", http.StatusFound) 319 }) 320} 321 322func totpDeleteHandler(app *model.AppState) http.Handler { 323 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 324 if r.Method != http.MethodPost { 325 http.NotFound(w, r) 326 return 327 } 328 329 session := r.Context().Value("session").(*model.Session) 330 331 err := r.ParseForm() 332 if err != nil { 333 http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 334 return 335 } 336 name := r.FormValue("totp-name") 337 if len(name) == 0 { 338 http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 339 return 340 } 341 342 totp, err := controller.GetTOTP(app.DB, session.Account.ID, name) 343 if err != nil { 344 fmt.Printf("WARN: Failed to fetch TOTP method: %s\n", err) 345 controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.") 346 http.Redirect(w, r, "/admin/account", http.StatusFound) 347 return 348 } 349 if totp == nil { 350 http.NotFound(w, r) 351 return 352 } 353 354 err = controller.DeleteTOTP(app.DB, session.Account.ID, totp.Name) 355 if err != nil { 356 fmt.Printf("WARN: Failed to delete TOTP method: %s\n", err) 357 controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.") 358 http.Redirect(w, r, "/admin/account", http.StatusFound) 359 return 360 } 361 362 app.Log.Info(log.TYPE_ACCOUNT, "\"%s\" deleted TOTP method \"%s\".", session.Account.Username, totp.Name) 363 364 controller.SetSessionError(app.DB, session, "") 365 controller.SetSessionMessage(app.DB, session, fmt.Sprintf("TOTP method \"%s\" deleted successfully.", totp.Name)) 366 http.Redirect(w, r, "/admin/account", http.StatusFound) 367 }) 368}