diff --git a/api/account.go b/api/account.go deleted file mode 100644 index 0a9a7f9..0000000 --- a/api/account.go +++ /dev/null @@ -1,201 +0,0 @@ -package api - -import ( - "arimelody-web/controller" - "arimelody-web/model" - "encoding/json" - "fmt" - "net/http" - "os" - "strings" - "time" - - "golang.org/x/crypto/bcrypt" -) - -func handleLogin(app *model.AppState) 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(app.DB, credentials.Username) - if err != nil { - fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve account: %v\n", err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } - if account == nil { - http.Error(w, "Invalid username or password", http.StatusBadRequest) - return - } - - err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(credentials.Password)) - if err != nil { - http.Error(w, "Invalid username or password", http.StatusBadRequest) - return - } - - token, err := controller.CreateToken(app.DB, account.ID, r.UserAgent()) - type LoginResponse struct { - Token string `json:"token"` - ExpiresAt time.Time `json:"expires_at"` - } - - err = json.NewEncoder(w).Encode(LoginResponse{ - Token: token.Token, - ExpiresAt: token.ExpiresAt, - }) - if err != nil { - fmt.Fprintf(os.Stderr, "WARN: Failed to return session token: %v\n", err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - } - }) -} - -func handleAccountRegistration(app *model.AppState) 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"` - Invite string `json:"invite"` - } - - 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, err := controller.GetInvite(app.DB, credentials.Invite) - if err != nil { - fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve invite: %v\n", err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } - if invite == nil { - http.Error(w, "Invalid invite code", http.StatusBadRequest) - return - } - - if time.Now().After(invite.ExpiresAt) { - err := controller.DeleteInvite(app.DB, invite.Code) - if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %v\n", err) } - http.Error(w, "Invalid invite code", http.StatusBadRequest) - return - } - - hashedPassword, err := bcrypt.GenerateFromPassword([]byte(credentials.Password), bcrypt.DefaultCost) - if err != nil { - fmt.Fprintf(os.Stderr, "WARN: Failed to generate password hash: %v\n", err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } - - account := model.Account{ - Username: credentials.Username, - Password: string(hashedPassword), - Email: credentials.Email, - AvatarURL: "/img/default-avatar.png", - } - err = controller.CreateAccount(app.DB, &account) - if err != nil { - if strings.HasPrefix(err.Error(), "pq: duplicate key") { - http.Error(w, "An account with that username already exists", http.StatusBadRequest) - return - } - fmt.Fprintf(os.Stderr, "WARN: Failed to create account: %v\n", err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } - - err = controller.DeleteInvite(app.DB, invite.Code) - if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %v\n", err) } - - token, err := controller.CreateToken(app.DB, account.ID, r.UserAgent()) - type LoginResponse struct { - Token string `json:"token"` - ExpiresAt time.Time `json:"expires_at"` - } - - err = json.NewEncoder(w).Encode(LoginResponse{ - Token: token.Token, - ExpiresAt: token.ExpiresAt, - }) - if err != nil { - fmt.Fprintf(os.Stderr, "WARN: Failed to return session token: %v\n", err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - } - }) -} - -func handleDeleteAccount(app *model.AppState) 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(app.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: %v\n", err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } - - err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(credentials.Password)) - if err != nil { - http.Error(w, "Invalid password", http.StatusBadRequest) - return - } - - // TODO: check TOTP - - err = controller.DeleteAccount(app.DB, account.Username) - if err != nil { - fmt.Fprintf(os.Stderr, "WARN: Failed to delete account: %v\n", err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) - w.Write([]byte("Account deleted successfully\n")) - }) -} diff --git a/api/api.go b/api/api.go index b16d45d..9489126 100644 --- a/api/api.go +++ b/api/api.go @@ -13,20 +13,8 @@ import ( func Handler(app *model.AppState) http.Handler { mux := http.NewServeMux() - // ACCOUNT ENDPOINTS - - /* - // temporarily disabling these - // accounts should really be handled via the frontend rn, and juggling - // two different token bearer methods kinda sucks!! - // i'll look into generating API tokens on the frontend in the future // TODO: generate API keys on the frontend - mux.Handle("/v1/login", handleLogin()) - mux.Handle("/v1/register", handleAccountRegistration()) - mux.Handle("/v1/delete-account", handleDeleteAccount()) - */ - // ARTIST ENDPOINTS mux.Handle("/v1/artist/", http.StripPrefix("/v1/artist", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {