From a0b43b4bb20b79127269d367fe6fb1fa54c97469 Mon Sep 17 00:00:00 2001 From: John Warwick Date: Sat, 3 Apr 2021 18:39:21 -0400 Subject: [PATCH 1/6] Added API Keys to Account Settings --- src/clj/web/api.clj | 7 +++++- src/clj/web/api_keys.clj | 32 ++++++++++++++++++++++++++ src/cljs/nr/account.cljs | 42 ++++++++++++++++++++++++++++++++++- src/cljs/nr/translations.cljs | 10 +++++++-- 4 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 src/clj/web/api_keys.clj diff --git a/src/clj/web/api.clj b/src/clj/web/api.clj index 2c5e8b226f..e780cbc5f5 100644 --- a/src/clj/web/api.clj +++ b/src/clj/web/api.clj @@ -10,6 +10,7 @@ [web.admin :as admin] [web.tournament :as tournament] [web.decks :as decks] + [web.api-keys :as api-keys] [compojure.route :as route] [ring.middleware.params :refer [wrap-params]] [ring.middleware.keyword-params :refer [wrap-keyword-params]] @@ -94,7 +95,11 @@ (GET "/data/decks" [] decks/decks-handler) (POST "/data/decks" [] decks/decks-create-handler) (PUT "/data/decks" [] decks/decks-save-handler) - (DELETE "/data/decks/:id" [] decks/decks-delete-handler)) + (DELETE "/data/decks/:id" [] decks/decks-delete-handler) + + (GET "/data/api-keys" [] api-keys/api-keys-handler) + (POST "/data/api-keys" [] api-keys/api-keys-create-handler) + (DELETE "/data/api-keys/:id" [] api-keys/api-keys-delete-handler)) (defroutes tournament-routes (GET "/tournament-auth/:username" [] tournament/auth)) diff --git a/src/clj/web/api_keys.clj b/src/clj/web/api_keys.clj new file mode 100644 index 0000000000..71f979c64d --- /dev/null +++ b/src/clj/web/api_keys.clj @@ -0,0 +1,32 @@ +(ns web.api-keys + (:require [web.db :refer [db object-id]] + [web.utils :refer [response]] + [web.ws :as ws] + [monger.collection :as mc] + [monger.result :refer [acknowledged?]]) + (:import org.bson.types.ObjectId)) + +(defn api-keys-handler [req] + (if-let [user (:user req)] + (response 200 (mc/find-maps db "api-keys" {:username (:username user)})) + (response 401 {:message "Unauthorized"}))) + +(defn api-keys-create-handler [{{username :username} :user}] + (if username + (let [new-key (java.util.UUID/randomUUID)] + (response 200 (mc/insert-and-return db "api-keys" + {:username username + :date (java.util.Date.) + :api-key new-key}))) + (response 401 {:message "Unauthorized"}))) + +(defn api-keys-delete-handler [{{username :username} :user + {id :id} :params}] + (try + (if (and username id) + (if (acknowledged? (mc/remove db "api-keys" {:_id (object-id id) :username username})) + (response 200 {:message "Deleted"}) + (response 403 {:message "Forbidden"})) + (response 401 {:message "Unauthorized"})) + (catch Exception ex + (response 409 {:message "Unknown API Key"})))) diff --git a/src/cljs/nr/account.cljs b/src/cljs/nr/account.cljs index bbd14fab83..f6f8154f24 100644 --- a/src/cljs/nr/account.cljs +++ b/src/cljs/nr/account.cljs @@ -5,7 +5,7 @@ [goog.dom :as gdom] [nr.auth :refer [valid-email?] :as auth] [nr.appstate :refer [app-state]] - [nr.ajax :refer [POST GET PUT]] + [nr.ajax :refer [POST GET PUT DELETE]] [nr.avatar :refer [avatar]] [nr.translations :refer [tr]] [nr.utils :refer [non-game-toast set-scroll-top store-scroll-top]] @@ -191,6 +191,42 @@ (reagent-modals/close-modal!))} (tr [:settings.cancel "Cancel"])]]]]))) +(defn- update-api-keys-response [response s] + (if (= 200 (:status response)) + (do + (go (swap! s assoc :api-keys (:json ( (:date d) js/Date. js/moment (.format "DD-MMM-YYYY, HH:mm "))] + [:span.title (:api-key d "")]]))]] + [:button {:on-click #(do (.preventDefault %) + (create-api-key s))} + (tr [:settings.create-api-key "Create API Key"])]])) + (defn account-content [user s scroll-top] (r/create-class { @@ -441,6 +477,8 @@ :on-click #(remove-user-from-block-list % s)} "X" ] [:span.blocked-user-name (str " " bu)]]))] + [api-keys s] + [:section [:button.float-right (tr [:settings.update-profile "Update Profile"])] [:span.flash-message (:flash-message @s)]]]])})) @@ -480,6 +518,8 @@ (when (= 200 (:status response)) (swap! state assoc :email (:email (:json response)))))) + (go (swap! state assoc :api-keys (:json ( Date: Sun, 4 Apr 2021 11:50:35 -0400 Subject: [PATCH 2/6] Add API Key options to game creation --- src/clj/game/core/set_up.clj | 3 ++- src/clj/web/api_keys.clj | 20 ++++++++++++++------ src/clj/web/auth.clj | 2 +- src/clj/web/lobby.clj | 3 ++- src/cljs/nr/account.cljs | 12 +++++++----- src/cljs/nr/gamelobby.cljs | 26 ++++++++++++++++++++++---- src/cljs/nr/translations.cljs | 4 +++- 7 files changed, 51 insertions(+), 19 deletions(-) diff --git a/src/clj/game/core/set_up.clj b/src/clj/game/core/set_up.clj index 5fc111277e..ac90bae71f 100644 --- a/src/clj/game/core/set_up.clj +++ b/src/clj/game/core/set_up.clj @@ -79,7 +79,7 @@ (defn- init-game-state "Initialises the game state" - [{:keys [players gameid timer spectatorhands save-replay room]}] + [{:keys [players gameid timer spectatorhands api-access save-replay room]}] (let [corp (some #(when (corp? %) %) players) runner (some #(when (runner? %) %) players) corp-deck (create-deck (:deck corp)) @@ -101,6 +101,7 @@ (t/now) {:timer timer :spectatorhands spectatorhands + :api-access api-access :save-replay save-replay} (new-corp (:user corp) corp-identity corp-options (map #(assoc % :zone [:deck]) corp-deck) corp-deck-id corp-quote) (new-runner (:user runner) runner-identity runner-options (map #(assoc % :zone [:deck]) runner-deck) runner-deck-id runner-quote))))) diff --git a/src/clj/web/api_keys.clj b/src/clj/web/api_keys.clj index 71f979c64d..963ecd353a 100644 --- a/src/clj/web/api_keys.clj +++ b/src/clj/web/api_keys.clj @@ -13,11 +13,16 @@ (defn api-keys-create-handler [{{username :username} :user}] (if username - (let [new-key (java.util.UUID/randomUUID)] - (response 200 (mc/insert-and-return db "api-keys" - {:username username - :date (java.util.Date.) - :api-key new-key}))) + (let [new-key (java.util.UUID/randomUUID) + new-entry (mc/insert db "api-keys" + {:username username + :date (java.util.Date.) + :api-key new-key})] + (if (acknowledged? new-entry) + (if (acknowledged? (mc/update db "users" {:username username} {"$set" {:has-api-keys true}})) + (response 201 {:message "Created API Key"}) + (response 500 {:message "Failed to update user info"})) + (response 500 {:message "Failed to create API Key"}))) (response 401 {:message "Unauthorized"}))) (defn api-keys-delete-handler [{{username :username} :user @@ -25,7 +30,10 @@ (try (if (and username id) (if (acknowledged? (mc/remove db "api-keys" {:_id (object-id id) :username username})) - (response 200 {:message "Deleted"}) + (let [key-count (mc/count db "api-keys" {:username username})] + (when-not (pos? key-count) + (mc/update db "users" {:username username} {"$set" {:has-api-keys false}})) + (response 200 {:message "Deleted"})) (response 403 {:message "Forbidden"})) (response 401 {:message "Unauthorized"})) (catch Exception ex diff --git a/src/clj/web/auth.clj b/src/clj/web/auth.clj index 4f8b292dfd..23a286b35c 100644 --- a/src/clj/web/auth.clj +++ b/src/clj/web/auth.clj @@ -50,7 +50,7 @@ (defn wrap-user [handler] (fn [{db :system/db :keys [cookies] :as req}] - (let [user-keys [:_id :username :emailhash :isadmin :ismoderator :tournament-organizer :special :options :stats] + (let [user-keys [:_id :username :emailhash :isadmin :ismoderator :tournament-organizer :special :options :stats :has-api-keys] auth-cookie (get cookies "session") user (when auth-cookie (unsign-token (:value auth-cookie))) diff --git a/src/clj/web/lobby.clj b/src/clj/web/lobby.clj index 5dc8d0b7f1..e8dd1a3195 100644 --- a/src/clj/web/lobby.clj +++ b/src/clj/web/lobby.clj @@ -251,7 +251,7 @@ (defmethod ws/-msg-handler :lobby/create [{{{:keys [username] :as user} :user} :ring-req client-id :client-id - {:keys [title format timer allow-spectator save-replay + {:keys [title format timer allow-spectator save-replay api-access spectatorhands password room side]} :?data}] (let [gameid (java.util.UUID/randomUUID) game {:date (java.util.Date.) @@ -259,6 +259,7 @@ :title title :allow-spectator allow-spectator :save-replay save-replay + :api-access api-access :spectatorhands spectatorhands :mute-spectators false :password (when (not-empty password) (bcrypt/encrypt password)) diff --git a/src/cljs/nr/account.cljs b/src/cljs/nr/account.cljs index f6f8154f24..ba162e64b2 100644 --- a/src/cljs/nr/account.cljs +++ b/src/cljs/nr/account.cljs @@ -192,11 +192,13 @@ (tr [:settings.cancel "Cancel"])]]]]))) (defn- update-api-keys-response [response s] - (if (= 200 (:status response)) - (do - (go (swap! s assoc :api-keys (:json ( Date: Fri, 23 Apr 2021 14:04:53 -0400 Subject: [PATCH 3/6] Fix post-rebase errors --- src/clj/web/lobby.clj | 2 ++ src/cljs/nr/gamelobby.cljs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/clj/web/lobby.clj b/src/clj/web/lobby.clj index e8dd1a3195..163f0553f9 100644 --- a/src/clj/web/lobby.clj +++ b/src/clj/web/lobby.clj @@ -49,6 +49,8 @@ game-lobby-updates (atom {}) send-ready (atom true)] + (def lobby-only-keys [:messages :spectators :mute-spectators :spectatorhands :timer :api-access]) + (defn- game-public-view "Strips private server information from a game map, preparing to send the game to clients." [gameid game] diff --git a/src/cljs/nr/gamelobby.cljs b/src/cljs/nr/gamelobby.cljs index 176693546d..c537e0e008 100644 --- a/src/cljs/nr/gamelobby.cljs +++ b/src/cljs/nr/gamelobby.cljs @@ -214,7 +214,7 @@ (swap! app-state dissoc :editing-game) (ws/ws-send! [:lobby/create (select-keys @s [:title :password :allow-spectator :save-replay - :spectatorhands :side :format :room :timer])]))))))) + :spectatorhands :side :format :room :timer :api-access])]))))))) (defn leave-lobby [s] (ws/ws-send! [:lobby/leave]) From 13caeabc2d6a080aa265172e3672a664b03c4f6f Mon Sep 17 00:00:00 2001 From: John Warwick Date: Mon, 26 Apr 2021 10:32:43 -0400 Subject: [PATCH 4/6] Handle db passed in as an arg --- src/clj/web/api_keys.clj | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/clj/web/api_keys.clj b/src/clj/web/api_keys.clj index 963ecd353a..9abaee98b1 100644 --- a/src/clj/web/api_keys.clj +++ b/src/clj/web/api_keys.clj @@ -1,17 +1,19 @@ (ns web.api-keys - (:require [web.db :refer [db object-id]] + (:require [web.mongodb :refer [object-id]] [web.utils :refer [response]] [web.ws :as ws] [monger.collection :as mc] [monger.result :refer [acknowledged?]]) (:import org.bson.types.ObjectId)) -(defn api-keys-handler [req] - (if-let [user (:user req)] - (response 200 (mc/find-maps db "api-keys" {:username (:username user)})) +(defn api-keys-handler [{db :system/db + {username :username} :user}] + (if username + (response 200 (mc/find-maps db "api-keys" {:username username})) (response 401 {:message "Unauthorized"}))) -(defn api-keys-create-handler [{{username :username} :user}] +(defn api-keys-create-handler [{db :system/db + {username :username} :user}] (if username (let [new-key (java.util.UUID/randomUUID) new-entry (mc/insert db "api-keys" @@ -25,7 +27,8 @@ (response 500 {:message "Failed to create API Key"}))) (response 401 {:message "Unauthorized"}))) -(defn api-keys-delete-handler [{{username :username} :user +(defn api-keys-delete-handler [{db :system/db + {username :username} :user {id :id} :params}] (try (if (and username id) From 797b0c2526f81c7ac342e0cc3e7b887d8f95c21b Mon Sep 17 00:00:00 2001 From: John Warwick Date: Mon, 26 Apr 2021 18:20:58 -0400 Subject: [PATCH 5/6] Add /game/deck API endpoint --- src/clj/tasks/index.clj | 3 ++- src/clj/web/api.clj | 8 ++++--- src/clj/web/game_api.clj | 49 ++++++++++++++++++++++++++++++++++++++++ src/clj/web/lobby.clj | 11 +++++++-- 4 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 src/clj/web/game_api.clj diff --git a/src/clj/tasks/index.clj b/src/clj/tasks/index.clj index effafb33e9..2ed1dec40d 100644 --- a/src/clj/tasks/index.clj +++ b/src/clj/tasks/index.clj @@ -7,7 +7,8 @@ (def ^:const indexes (let [case-insensitive-index-opts {:collation {:locale "en" :strength (int 2)}}] - [["cards" (array-map :code 1)] + [["api-keys" (array-map :api-key 1)] + ["cards" (array-map :code 1)] ["cards" (array-map :previous-versions 1)] ["cards" (array-map :type 1)] ["decks" (array-map :username 1)] diff --git a/src/clj/web/api.clj b/src/clj/web/api.clj index e780cbc5f5..8029fe625b 100644 --- a/src/clj/web/api.clj +++ b/src/clj/web/api.clj @@ -11,6 +11,7 @@ [web.tournament :as tournament] [web.decks :as decks] [web.api-keys :as api-keys] + [web.game-api :as game-api] [compojure.route :as route] [ring.middleware.params :refer [wrap-params]] [ring.middleware.keyword-params :refer [wrap-keyword-params]] @@ -55,7 +56,8 @@ (POST "/register" [] auth/register-handler) (POST "/login" [] auth/login-handler) (POST "/forgot" [] auth/forgot-password-handler) - (POST "/reset/:token" [] auth/reset-password-handler)) + (POST "/reset/:token" [] auth/reset-password-handler) + (GET "/game/deck" [] game-api/deck-handler)) (defroutes admin-routes (POST "/admin/announce" [] admin/announce-create-handler) @@ -111,8 +113,8 @@ (defroutes routes (wrap-routes private-routes wrap-anti-forgery) - (wrap-routes public-CSRF-routes wrap-anti-forgery) - public-routes) + public-routes + (wrap-routes public-CSRF-routes wrap-anti-forgery)) (defn wrap-return-favicon [handler] (fn [req] diff --git a/src/clj/web/game_api.clj b/src/clj/web/game_api.clj new file mode 100644 index 0000000000..d6d12b290c --- /dev/null +++ b/src/clj/web/game_api.clj @@ -0,0 +1,49 @@ +(ns web.game-api + (:require [web.mongodb :refer [object-id]] + [web.decks :as decks] + [web.lobby :as lobby] + [web.utils :refer [response]] + [monger.collection :as mc])) + +(defn- make-link [host path] (str host path)) + +(defn- make-card-info [host card] + {:title (:title (:card card)) + :qty (:qty card) + :image (make-link host (get-in card [:card :images :en :default :stock]))}) + +(defn- get-deck-id [username game] + (if (:started game) + (let [state @(:state game) + side (if (= username (get-in state [:runner :user :username])) :runner :corp)] + (get-in state [side :deck-id])) + (:_id (:deck (first (filter #(= username (get-in % [:user :username])) (:players game))))))) + +(defn- get-deck [db username game] + (if-let [deck-id (get-deck-id username game)] + (decks/update-deck (mc/find-one-as-map db "decks" {:_id (object-id deck-id) :username username})) + nil)) + +(defn deck-handler [{db :system/db + scheme :scheme + headers :headers}] + (if-let [api-key (get headers "x-jnet-api")] + (let [api-uuid (try + (java.util.UUID/fromString api-key) + (catch Exception e nil)) + api-record (mc/find-one-as-map db "api-keys" {:api-key api-uuid} ["username"]) + username (:username api-record)] + (if username + (let [game (lobby/game-for-username username) + allow-access (:api-access game)] + (if (and game allow-access) + (if-let [deck (get-deck db username game)] + (let [host (str (name scheme) "://" (get headers "host"))] + (response 200 {:name (:name deck) + :identity {:title (get-in deck [:identity :title]) + :image (make-link host (get-in deck [:identity :images :en :default :stock]))} + :cards (map #(make-card-info host %) (:cards deck))})) + (response 204 {:message "No deck selected"})) + (response 403 {:message "No game for key or API Access not enabled"}))) + (response 404 {:message "Unknown X-JNet-API key"}))) + (response 400 {:message "No X-JNet-API header specified"}))) diff --git a/src/clj/web/lobby.clj b/src/clj/web/lobby.clj index 163f0553f9..4bd1877ca8 100644 --- a/src/clj/web/lobby.clj +++ b/src/clj/web/lobby.clj @@ -37,14 +37,21 @@ [client-id] (get @all-games (get @client-gameids client-id))) +(defn- username-is-player + [username game] + (some #(= username (get-in % [:user :username])) (:players game))) + +(defn game-for-username + "Returns the game map the given username is playing (but not spectating)" + [username] + (first (filter #(username-is-player username %) (vals @all-games)))) + (defn lobby-clients "Returns a seq of all client-ids playing or spectating a gameid." [gameid] (let [game (game-for-id gameid)] (keep :ws-id (concat (:players game) (:spectators game))))) -(def lobby-only-keys [:messages :spectators :mute-spectators :spectatorhands :timer]) - (let [public-lobby-updates (atom {}) game-lobby-updates (atom {}) send-ready (atom true)] From da094548eca663bf79edba8f8bea056014f1f7c2 Mon Sep 17 00:00:00 2001 From: John Warwick Date: Tue, 27 Apr 2021 08:17:02 -0400 Subject: [PATCH 6/6] Hanging parens --- src/cljs/nr/gamelobby.cljs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cljs/nr/gamelobby.cljs b/src/cljs/nr/gamelobby.cljs index c537e0e008..778fe242ed 100644 --- a/src/cljs/nr/gamelobby.cljs +++ b/src/cljs/nr/gamelobby.cljs @@ -541,8 +541,7 @@ :on-change #(swap! s assoc :api-access (.. % -target -checked))}] (tr [:lobby.api-access "Allow API access to game information"]) (when (not has-keys) - (str " " (tr [:lobby.api-requires-key "(Requires an API Key in Settings)"]))) - ]]) + (str " " (tr [:lobby.api-requires-key "(Requires an API Key in Settings)"])))]]) [:div.infobox.blue-shade {:style {:display (if (:api-access @s) "block" "none")}} [:p "This allows access to information about your game to 3rd party extensions. Requires an API Key to be created in Settings"]]]]])))