Reject revoked legacy sync API keys
This commit is contained in:
parent
fcf0a07fcc
commit
2e6b451c3d
|
|
@ -36,13 +36,25 @@ func (s *Server) requireAuth(w http.ResponseWriter, r *http.Request) (deviceID,
|
||||||
s.db.Exec("UPDATE server_devices SET last_seen=? WHERE id=?", time.Now().UTC().Format(time.RFC3339), deviceIDVal.String)
|
s.db.Exec("UPDATE server_devices SET last_seen=? WHERE id=?", time.Now().UTC().Format(time.RFC3339), deviceIDVal.String)
|
||||||
return deviceIDVal.String, userIDVal.String, true
|
return deviceIDVal.String, userIDVal.String, true
|
||||||
}
|
}
|
||||||
var count int
|
err = s.db.QueryRow("SELECT id, user_id, revoked_at FROM server_devices WHERE api_key=?", key).Scan(&deviceIDVal, &userIDVal, &revokedAt)
|
||||||
err = s.db.QueryRow("SELECT COUNT(*) FROM server_devices WHERE api_key=?", key).Scan(&count)
|
if err != nil {
|
||||||
if err != nil || count == 0 {
|
|
||||||
jsonErr(w, 401, "invalid API key")
|
jsonErr(w, 401, "invalid API key")
|
||||||
return "", "", false
|
return "", "", false
|
||||||
}
|
}
|
||||||
return "", "", true
|
if revokedAt.Valid && revokedAt.String != "" {
|
||||||
|
jsonErr(w, 401, "device revoked")
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
if userIDVal.Valid && userIDVal.String != "" {
|
||||||
|
var blocked int
|
||||||
|
s.db.QueryRow("SELECT blocked FROM server_users WHERE id=?", userIDVal.String).Scan(&blocked)
|
||||||
|
if blocked != 0 {
|
||||||
|
jsonErr(w, 403, "user blocked")
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.db.Exec("UPDATE server_devices SET last_seen=? WHERE id=?", time.Now().UTC().Format(time.RFC3339), deviceIDVal.String)
|
||||||
|
return deviceIDVal.String, userIDVal.String, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) requireAdmin(w http.ResponseWriter, r *http.Request) bool {
|
func (s *Server) requireAdmin(w http.ResponseWriter, r *http.Request) bool {
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,51 @@ func TestSyncPushPullStoresSequencedOps(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRevokedLegacyAPIKeyCannotPushOrPull(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
s, err := NewServer(filepath.Join(dir, "test.db"), filepath.Join(dir, "data"), &Config{Port: 47732})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewServer: %v", err)
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
s.SetupRoutes()
|
||||||
|
|
||||||
|
now := time.Now().UTC().Format(time.RFC3339)
|
||||||
|
if _, err := s.db.Exec(
|
||||||
|
"INSERT INTO server_devices (id, name, api_key, last_seen, revoked_at, created_at) VALUES (?, ?, ?, ?, ?, ?)",
|
||||||
|
"device-revoked", "Revoked Device", "revoked-key", now, now, now,
|
||||||
|
); err != nil {
|
||||||
|
t.Fatalf("insert device: %v", err)
|
||||||
|
}
|
||||||
|
ts := httptest.NewServer(s.mux)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
pushStatus, pushResp := postJSONStatus(t, ts.URL+"/api/v1/sync/push", "revoked-key", map[string]interface{}{
|
||||||
|
"device_id": "device-revoked",
|
||||||
|
"ops": []map[string]interface{}{},
|
||||||
|
})
|
||||||
|
if pushStatus != http.StatusUnauthorized || pushResp["error"] != "device revoked" {
|
||||||
|
t.Fatalf("push status=%d resp=%#v, want 401 device revoked", pushStatus, pushResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pullStatus, pullResp := postJSONStatus(t, ts.URL+"/api/v1/sync/pull", "revoked-key", map[string]interface{}{
|
||||||
|
"since_sequence": 0,
|
||||||
|
})
|
||||||
|
if pullStatus != http.StatusUnauthorized || pullResp["error"] != "device revoked" {
|
||||||
|
t.Fatalf("pull status=%d resp=%#v, want 401 device revoked", pullStatus, pullResp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func postJSON(t *testing.T, url, token string, body interface{}) map[string]interface{} {
|
func postJSON(t *testing.T, url, token string, body interface{}) map[string]interface{} {
|
||||||
|
t.Helper()
|
||||||
|
status, out := postJSONStatus(t, url, token, body)
|
||||||
|
if status != http.StatusOK {
|
||||||
|
t.Fatalf("post %s status = %d", url, status)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func postJSONStatus(t *testing.T, url, token string, body interface{}) (int, map[string]interface{}) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
if err := json.NewEncoder(&b).Encode(body); err != nil {
|
if err := json.NewEncoder(&b).Encode(body); err != nil {
|
||||||
|
|
@ -149,12 +193,9 @@ func postJSON(t *testing.T, url, token string, body interface{}) map[string]inte
|
||||||
t.Fatalf("post %s: %v", url, err)
|
t.Fatalf("post %s: %v", url, err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
t.Fatalf("post %s status = %d", url, resp.StatusCode)
|
|
||||||
}
|
|
||||||
var out map[string]interface{}
|
var out map[string]interface{}
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
||||||
t.Fatalf("decode response: %v", err)
|
t.Fatalf("decode response: %v", err)
|
||||||
}
|
}
|
||||||
return out
|
return resp.StatusCode, out
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue