package api import ( "arimelody-web/account/controller" "arimelody-web/account/model" "arimelody-web/global" "encoding/json" "fmt" "net/http" "os" "strings" "time" "golang.org/x/crypto/bcrypt" ) func handleLogin() http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.NotFound(w, r) return } type LoginRequest struct { Username string `json:"username"` Password string `json:"password"` } credentials := LoginRequest{} err := json.NewDecoder(r.Body).Decode(&credentials) if err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } account, err := controller.GetAccount(global.DB, credentials.Username) if err != nil { if strings.Contains(err.Error(), "no rows") { http.Error(w, "Invalid username or password", http.StatusBadRequest) return } fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve account: %s\n", err.Error()) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } err = bcrypt.CompareHashAndPassword(account.Password, []byte(credentials.Password)) if err != nil { http.Error(w, "Invalid username or password", http.StatusBadRequest) return } // TODO: sessions and tokens w.WriteHeader(http.StatusOK) w.Write([]byte("Logged in successfully. TODO: Session tokens\n")) }) } func handleAccountRegistration() http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.NotFound(w, r) return } type RegisterRequest struct { Username string `json:"username"` Email string `json:"email"` Password string `json:"password"` Code string `json:"code"` } credentials := RegisterRequest{} err := json.NewDecoder(r.Body).Decode(&credentials) if err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } // make sure code exists in DB invite := model.Invite{} err = global.DB.Get(&invite, "SELECT * FROM invite WHERE code=$1", credentials.Code) if err != nil { if strings.Contains(err.Error(), "no rows") { http.Error(w, "Invalid invite code", http.StatusBadRequest) return } fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve invite: %s\n", err.Error()) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if time.Now().After(invite.ExpiresAt) { http.Error(w, "Invalid invite code", http.StatusBadRequest) _, err = global.DB.Exec("DELETE FROM invite WHERE code=$1", credentials.Code) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %s\n", err.Error()) } return } hashedPassword, err := bcrypt.GenerateFromPassword([]byte(credentials.Password), bcrypt.DefaultCost) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to generate password hash: %s\n", err.Error()) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } account := model.Account{ Username: credentials.Username, Password: hashedPassword, Email: credentials.Email, AvatarURL: "/img/default-avatar.png", } err = controller.CreateAccount(global.DB, &account) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to create account: %s\n", err.Error()) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } _, err = global.DB.Exec("DELETE FROM invite WHERE code=$1", credentials.Code) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %s\n", err.Error()) } w.WriteHeader(http.StatusCreated) w.Write([]byte("Account created successfully\n")) }) } func handleDeleteAccount() http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.NotFound(w, r) return } type LoginRequest struct { Username string `json:"username"` Password string `json:"password"` } credentials := LoginRequest{} err := json.NewDecoder(r.Body).Decode(&credentials) if err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } account, err := controller.GetAccount(global.DB, credentials.Username) if err != nil { if strings.Contains(err.Error(), "no rows") { http.Error(w, "Invalid username or password", http.StatusBadRequest) return } fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve account: %s\n", err.Error()) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } err = bcrypt.CompareHashAndPassword(account.Password, []byte(credentials.Password)) if err != nil { http.Error(w, "Invalid username or password", http.StatusBadRequest) return } err = controller.DeleteAccount(global.DB, account.ID) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete account: %s\n", err.Error()) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) w.Write([]byte("Account deleted successfully\n")) }) }