home to your local SPACEGIRL 💫 arimelody.space
1
fork

Configure Feed

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

remove account API endpoints

account management should be done on the frontend.
some work will need to be done to generate API keys for external clients,
but notably some API endpoints are currently used by the frontend using session tokens.

-213
-201
api/account.go
··· 1 - package api 2 - 3 - import ( 4 - "arimelody-web/controller" 5 - "arimelody-web/model" 6 - "encoding/json" 7 - "fmt" 8 - "net/http" 9 - "os" 10 - "strings" 11 - "time" 12 - 13 - "golang.org/x/crypto/bcrypt" 14 - ) 15 - 16 - func handleLogin(app *model.AppState) http.HandlerFunc { 17 - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 18 - if r.Method != http.MethodPost { 19 - http.NotFound(w, r) 20 - return 21 - } 22 - 23 - type LoginRequest struct { 24 - Username string `json:"username"` 25 - Password string `json:"password"` 26 - } 27 - 28 - credentials := LoginRequest{} 29 - err := json.NewDecoder(r.Body).Decode(&credentials) 30 - if err != nil { 31 - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 32 - return 33 - } 34 - 35 - account, err := controller.GetAccount(app.DB, credentials.Username) 36 - if err != nil { 37 - fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve account: %v\n", err) 38 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 39 - return 40 - } 41 - if account == nil { 42 - http.Error(w, "Invalid username or password", http.StatusBadRequest) 43 - return 44 - } 45 - 46 - err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(credentials.Password)) 47 - if err != nil { 48 - http.Error(w, "Invalid username or password", http.StatusBadRequest) 49 - return 50 - } 51 - 52 - token, err := controller.CreateToken(app.DB, account.ID, r.UserAgent()) 53 - type LoginResponse struct { 54 - Token string `json:"token"` 55 - ExpiresAt time.Time `json:"expires_at"` 56 - } 57 - 58 - err = json.NewEncoder(w).Encode(LoginResponse{ 59 - Token: token.Token, 60 - ExpiresAt: token.ExpiresAt, 61 - }) 62 - if err != nil { 63 - fmt.Fprintf(os.Stderr, "WARN: Failed to return session token: %v\n", err) 64 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 65 - } 66 - }) 67 - } 68 - 69 - func handleAccountRegistration(app *model.AppState) http.HandlerFunc { 70 - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 71 - if r.Method != http.MethodPost { 72 - http.NotFound(w, r) 73 - return 74 - } 75 - 76 - type RegisterRequest struct { 77 - Username string `json:"username"` 78 - Email string `json:"email"` 79 - Password string `json:"password"` 80 - Invite string `json:"invite"` 81 - } 82 - 83 - credentials := RegisterRequest{} 84 - err := json.NewDecoder(r.Body).Decode(&credentials) 85 - if err != nil { 86 - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 87 - return 88 - } 89 - 90 - // make sure code exists in DB 91 - invite, err := controller.GetInvite(app.DB, credentials.Invite) 92 - if err != nil { 93 - fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve invite: %v\n", err) 94 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 95 - return 96 - } 97 - if invite == nil { 98 - http.Error(w, "Invalid invite code", http.StatusBadRequest) 99 - return 100 - } 101 - 102 - if time.Now().After(invite.ExpiresAt) { 103 - err := controller.DeleteInvite(app.DB, invite.Code) 104 - if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %v\n", err) } 105 - http.Error(w, "Invalid invite code", http.StatusBadRequest) 106 - return 107 - } 108 - 109 - hashedPassword, err := bcrypt.GenerateFromPassword([]byte(credentials.Password), bcrypt.DefaultCost) 110 - if err != nil { 111 - fmt.Fprintf(os.Stderr, "WARN: Failed to generate password hash: %v\n", err) 112 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 113 - return 114 - } 115 - 116 - account := model.Account{ 117 - Username: credentials.Username, 118 - Password: string(hashedPassword), 119 - Email: credentials.Email, 120 - AvatarURL: "/img/default-avatar.png", 121 - } 122 - err = controller.CreateAccount(app.DB, &account) 123 - if err != nil { 124 - if strings.HasPrefix(err.Error(), "pq: duplicate key") { 125 - http.Error(w, "An account with that username already exists", http.StatusBadRequest) 126 - return 127 - } 128 - fmt.Fprintf(os.Stderr, "WARN: Failed to create account: %v\n", err) 129 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 130 - return 131 - } 132 - 133 - err = controller.DeleteInvite(app.DB, invite.Code) 134 - if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %v\n", err) } 135 - 136 - token, err := controller.CreateToken(app.DB, account.ID, r.UserAgent()) 137 - type LoginResponse struct { 138 - Token string `json:"token"` 139 - ExpiresAt time.Time `json:"expires_at"` 140 - } 141 - 142 - err = json.NewEncoder(w).Encode(LoginResponse{ 143 - Token: token.Token, 144 - ExpiresAt: token.ExpiresAt, 145 - }) 146 - if err != nil { 147 - fmt.Fprintf(os.Stderr, "WARN: Failed to return session token: %v\n", err) 148 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 149 - } 150 - }) 151 - } 152 - 153 - func handleDeleteAccount(app *model.AppState) http.HandlerFunc { 154 - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 155 - if r.Method != http.MethodPost { 156 - http.NotFound(w, r) 157 - return 158 - } 159 - 160 - type LoginRequest struct { 161 - Username string `json:"username"` 162 - Password string `json:"password"` 163 - } 164 - 165 - credentials := LoginRequest{} 166 - err := json.NewDecoder(r.Body).Decode(&credentials) 167 - if err != nil { 168 - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 169 - return 170 - } 171 - 172 - account, err := controller.GetAccount(app.DB, credentials.Username) 173 - if err != nil { 174 - if strings.Contains(err.Error(), "no rows") { 175 - http.Error(w, "Invalid username or password", http.StatusBadRequest) 176 - return 177 - } 178 - fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve account: %v\n", err) 179 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 180 - return 181 - } 182 - 183 - err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(credentials.Password)) 184 - if err != nil { 185 - http.Error(w, "Invalid password", http.StatusBadRequest) 186 - return 187 - } 188 - 189 - // TODO: check TOTP 190 - 191 - err = controller.DeleteAccount(app.DB, account.Username) 192 - if err != nil { 193 - fmt.Fprintf(os.Stderr, "WARN: Failed to delete account: %v\n", err) 194 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 195 - return 196 - } 197 - 198 - w.WriteHeader(http.StatusOK) 199 - w.Write([]byte("Account deleted successfully\n")) 200 - }) 201 - }
-12
api/api.go
··· 13 13 func Handler(app *model.AppState) http.Handler { 14 14 mux := http.NewServeMux() 15 15 16 - // ACCOUNT ENDPOINTS 17 - 18 - /* 19 - // temporarily disabling these 20 - // accounts should really be handled via the frontend rn, and juggling 21 - // two different token bearer methods kinda sucks!! 22 - // i'll look into generating API tokens on the frontend in the future 23 16 // TODO: generate API keys on the frontend 24 - 25 - mux.Handle("/v1/login", handleLogin()) 26 - mux.Handle("/v1/register", handleAccountRegistration()) 27 - mux.Handle("/v1/delete-account", handleDeleteAccount()) 28 - */ 29 17 30 18 // ARTIST ENDPOINTS 31 19