From 5ab102b5506e4cdc320c16ac04c90cecbefa310f Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Thu, 16 Apr 2026 19:17:46 +0200 Subject: [PATCH] refactor: remove auth system Remove Bearer token auth, per-user group ACLs, and auth_test.clj. The server now accepts all requests without authentication. --- bb.edn | 2 +- src/pocketbook/core.cljs | 7 ++- src/pocketbook/server.clj | 92 +++++++++-------------------------- src/pocketbook/sync.cljs | 12 ++--- test/pocketbook/auth_test.clj | 88 --------------------------------- 5 files changed, 33 insertions(+), 168 deletions(-) delete mode 100644 test/pocketbook/auth_test.clj diff --git a/bb.edn b/bb.edn index c1ebf39..e9bf3f7 100644 --- a/bb.edn +++ b/bb.edn @@ -14,7 +14,7 @@ test {:doc "Run all server tests" :task (let [expr (str "(require 'pocketbook.db-test 'pocketbook.transit-test" - " 'pocketbook.server-test 'pocketbook.auth-test)" + " 'pocketbook.server-test)" " (let [r (clojure.test/run-all-tests #\"pocketbook\\..*\")]" " (System/exit (if (and (zero? (:fail r)) (zero? (:error r))) 0 1)))")] (shell "clj" "-M:dev" "-e" expr))} diff --git a/src/pocketbook/core.cljs b/src/pocketbook/core.cljs index a42b470..769fafd 100644 --- a/src/pocketbook/core.cljs +++ b/src/pocketbook/core.cljs @@ -47,7 +47,7 @@ cache ;; atom containing {id -> value} versions ;; atom containing {id -> version} pending ;; atom containing #{id} — unsynced ids - server-opts ;; {:server url :token str} or nil + server-opts ;; {:server url} or nil last-sync ;; atom containing epoch ms ready-ch ;; channel, closed when initial load complete stop-ch ;; channel to signal stop @@ -319,15 +319,14 @@ Options: :server — server URL (e.g. \"http://localhost:8090/sync\") - :token — auth token :cache — custom atom to use (e.g. reagent/atom). Default: cljs.core/atom :interval — sync interval in ms (default 30000)" - [conn group & [{:keys [server token cache interval] + [conn group & [{:keys [server cache interval] :or {interval 30000}}]] (let [cache-atom (or cache (atom {})) versions (atom {}) pending (atom #{}) - server-opts (when server {:server server :token token}) + server-opts (when server {:server server}) last-sync (atom 0) ready-ch (chan 1) stop-ch (chan 1) diff --git a/src/pocketbook/server.clj b/src/pocketbook/server.clj index 5478266..040d7a6 100644 --- a/src/pocketbook/server.clj +++ b/src/pocketbook/server.clj @@ -23,32 +23,8 @@ {:port 8090 :db-path "pocketbook.db" :static-dir nil ;; nil = no static serving, or path like "example/todomvc" - :users nil ;; nil = no auth, or {"alice" {:token "abc" :groups #{"todo"}}} :cors true}) -;; --------------------------------------------------------------------------- -;; Auth -;; --------------------------------------------------------------------------- - -(defn- authenticate - "Check Authorization header against config. Returns user map or nil." - [config req] - (if-let [users (:users config)] - (let [header (get-in req [:headers "authorization"] "") - token (str/replace header #"^Bearer\s+" "")] - (some (fn [[username user]] - (when (= token (:token user)) - (assoc user :username username))) - users)) - ;; No auth configured — allow all - {:username "anonymous" :groups nil})) - -(defn- authorized-group? - "Check if user has access to a specific group." - [user group] - (or (nil? (:groups user)) ;; nil = access to all groups - (contains? (:groups user) group))) - ;; --------------------------------------------------------------------------- ;; SSE — live change notifications ;; --------------------------------------------------------------------------- @@ -87,64 +63,48 @@ (update resp :headers merge {"Access-Control-Allow-Origin" "*" "Access-Control-Allow-Methods" "GET, POST, OPTIONS" - "Access-Control-Allow-Headers" "Content-Type, Authorization" + "Access-Control-Allow-Headers" "Content-Type" "Access-Control-Max-Age" "86400"})) (defn- handle-pull "GET /sync?since=T&group=G — return all docs updated since T in group G." - [ds user req] + [ds req] (let [params (or (:query-params req) (:params req) {}) group (get params "group" (get params :group)) since (parse-long (or (get params "since" (get params :since)) "0"))] (if-not group (transit-response 400 {:error "Missing 'group' parameter"}) - (if-not (authorized-group? user group) - (transit-response 403 {:error "Access denied to group"}) - (let [docs (db/docs-since ds group since)] - (transit-response 200 docs)))))) + (let [docs (db/docs-since ds group since)] + (transit-response 200 docs))))) (defn- handle-push "POST /sync — accept a batch of document writes. Body: [{:id ... :value ... :base-version N} ...] Entries with :deleted true are treated as deletes." - [ds user req] + [ds req] (let [body (t/decode (:body req)) docs (if (map? body) [body] body) - ;; Check all docs belong to authorized groups groups (into #{} (map #(first (str/split (:id %) #":" 2))) docs) - denied (remove #(authorized-group? user %) groups)] - (if (seq denied) - (transit-response 403 {:error (str "Access denied to groups: " (str/join ", " denied))}) - (let [results (mapv (fn [doc] - (if (:deleted doc) - (db/delete! ds {:id (:id doc) - :base-version (:base-version doc 0)}) - (db/upsert! ds {:id (:id doc) - :value (:value doc) - :base-version (:base-version doc 0)}))) - docs)] - ;; Notify SSE listeners for affected groups - (when (some #(= :ok (:status %)) results) - (sse-notify! groups)) - (transit-response 200 results))))) + results (mapv (fn [doc] + (if (:deleted doc) + (db/delete! ds {:id (:id doc) + :base-version (:base-version doc 0)}) + (db/upsert! ds {:id (:id doc) + :value (:value doc) + :base-version (:base-version doc 0)}))) + docs)] + ;; Notify SSE listeners for affected groups + (when (some #(= :ok (:status %)) results) + (sse-notify! groups)) + (transit-response 200 results))) (defn- handle-events "GET /events?group=G — SSE endpoint. Holds connection open." - [config req] + [_config req] (let [params (or (:query-params req) {}) - group (get params "group") - user (authenticate config req)] - (cond - (not user) - (transit-response 401 {:error "Unauthorized"}) - - (not group) + group (get params "group")] + (if-not group (transit-response 400 {:error "Missing 'group' parameter"}) - - (not (authorized-group? user group)) - (transit-response 403 {:error "Access denied to group"}) - - :else (http/with-channel req ch (http/send! ch {:status 200 :headers {"Content-Type" "text/event-stream" @@ -225,13 +185,10 @@ ;; Sync endpoints (= "/sync" (:uri req)) - (let [user (authenticate config req)] - (if-not user - (transit-response 401 {:error "Unauthorized"}) - (case (:request-method req) - :get (handle-pull ds user req) - :post (handle-push ds user req) - (transit-response 405 {:error "Method not allowed"})))) + (case (:request-method req) + :get (handle-pull ds req) + :post (handle-push ds req) + (transit-response 405 {:error "Method not allowed"})) ;; Static files (including / → todomvc.html) :else @@ -258,7 +215,6 @@ server (http/run-server handler {:port (:port config)})] (println (str "🔶 Pocketbook server running on http://localhost:" (:port config))) (println (str " Database: " (:db-path config))) - (println (str " Auth: " (if (:users config) "enabled" "disabled"))) (when (:static-dir config) (println (str " Static: " (:static-dir config))) (println (str " App: http://localhost:" (:port config) "/"))) diff --git a/src/pocketbook/sync.cljs b/src/pocketbook/sync.cljs index 628bd6f..06538d3 100644 --- a/src/pocketbook/sync.cljs +++ b/src/pocketbook/sync.cljs @@ -55,15 +55,14 @@ (defn pull! "Pull documents from server updated since `since` for `group`. Returns a channel yielding {:ok true :docs [...]} or {:ok false :error str}." - [{:keys [server token]} group since] + [{:keys [server]} group since] (let [ch (chan 1) url (str server "?group=" (js/encodeURIComponent group) "&since=" since)] (async/go (let [result (async/ server (str/replace #"/sync$" "") (str/replace #"/$" "")) diff --git a/test/pocketbook/auth_test.clj b/test/pocketbook/auth_test.clj deleted file mode 100644 index 369f163..0000000 --- a/test/pocketbook/auth_test.clj +++ /dev/null @@ -1,88 +0,0 @@ -(ns pocketbook.auth-test - (:require [clojure.test :refer [deftest is testing use-fixtures]] - [pocketbook.server :as server] - [pocketbook.transit :as t]) - (:import [java.io File] - [java.net URI] - [java.net.http HttpClient HttpRequest HttpResponse$BodyHandlers HttpRequest$BodyPublishers])) - -(def ^:dynamic *port* nil) -(def ^:dynamic *server* nil) - -(defn- free-port [] - (with-open [s (java.net.ServerSocket. 0)] - (.getLocalPort s))) - -(def test-users - {"alice" {:token "alice-secret" :groups #{"todo" "settings"}} - "bob" {:token "bob-secret" :groups #{"todo"}}}) - -(use-fixtures :each - (fn [f] - (let [port (free-port) - db-path (str (File/createTempFile "pocketbook-auth-test" ".db")) - srv (server/start! {:port port :db-path db-path :users test-users})] - (Thread/sleep 200) - (try - (binding [*server* srv *port* port] - (f)) - (finally - (server/stop! srv) - (.delete (File. db-path))))))) - -(def ^:private client (HttpClient/newHttpClient)) - -(defn- url [path & [query]] - (str "http://localhost:" *port* path (when query (str "?" query)))) - -(defn- get-transit [path query & [token]] - (let [req (-> (HttpRequest/newBuilder) - (.uri (URI. (url path query))) - (.header "Accept" "application/transit+json") - (cond-> token (.header "Authorization" (str "Bearer " token))) - (.GET) - (.build)) - resp (.send client req (HttpResponse$BodyHandlers/ofByteArray))] - {:status (.statusCode resp) - :body (t/decode (.body resp))})) - -(defn- post-transit [path body & [token]] - (let [bytes (t/encode body) - req (-> (HttpRequest/newBuilder) - (.uri (URI. (url path))) - (.header "Content-Type" "application/transit+json") - (.header "Accept" "application/transit+json") - (cond-> token (.header "Authorization" (str "Bearer " token))) - (.POST (HttpRequest$BodyPublishers/ofByteArray bytes)) - (.build)) - resp (.send client req (HttpResponse$BodyHandlers/ofByteArray))] - {:status (.statusCode resp) - :body (t/decode (.body resp))})) - -(deftest unauthorized-without-token - (let [resp (get-transit "/sync" "group=todo&since=0")] - (is (= 401 (:status resp))))) - -(deftest unauthorized-with-bad-token - (let [resp (get-transit "/sync" "group=todo&since=0" "wrong-token")] - (is (= 401 (:status resp))))) - -(deftest alice-can-access-todo - (let [resp (post-transit "/sync" - [{:id "todo:1" :value {:text "Alice todo"} :base-version 0}] - "alice-secret")] - (is (= 200 (:status resp))) - (is (= :ok (:status (first (:body resp))))))) - -(deftest bob-cannot-access-settings - (let [resp (post-transit "/sync" - [{:id "settings:theme" :value {:dark true} :base-version 0}] - "bob-secret")] - (is (= 403 (:status resp))))) - -(deftest alice-can-access-settings - (let [resp (post-transit "/sync" - [{:id "settings:theme" :value {:dark true} :base-version 0}] - "alice-secret")] - (is (= 200 (:status resp))) - (is (= :ok (:status (first (:body resp)))))))