Andrey Antukh 7704eef169 ✨ Add granular access tokens for the admin API
Admin methods now require an implicit admin:<method> permission
carried by an access token, with superuser bypassing every check.
Superusers can issue, list and revoke scoped tokens (including for
other profiles) from new admin commands and a panel screen; the
vocabulary is derived from the registered methods and validated on
issue. Token calls additionally require a live owner, and the share
link stays anonymous.

AI-assisted-by: muse-spark-1.3-contributor
2026-11-02 15:37:35 +01:00

72 lines
2.6 KiB
Clojure

;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS SUBSIDIARY SL
(ns app.rpc.admin.misc
"Admin miscellaneous commands, served through `/api/admin/methods`.
Small diagnostic tools that do not deserve their own namespace:
currently just the virtual clock. Access control lives in
`wrap-authentication` (the implicit `admin:<method>` permission,
bypassed by superuser), not here."
(:require
[app.common.exceptions :as ex]
[app.common.schema :as-alias sm]
[app.common.time :as ct]
[app.config :as cf]
[app.rpc :as-alias rpc]
[app.rpc.doc :as doc]
[app.setup.clock :as clock]
[app.util.services :as sv]))
;; ----------------------------------------------------------------
;; Virtual clock
;; ----------------------------------------------------------------
(def schema:get-virtual-clock-result
[:map
[:offset-millis {:optional true} ::sm/int]
[:now ct/schema:inst]
[:clock ::sm/text]])
(sv/defmethod ::get-virtual-clock
{::doc/added "2.20"
::sm/params [:map]
::sm/result schema:get-virtual-clock-result}
[_ {:keys [::rpc/profile-id]}]
(let [offset (clock/get-offset profile-id)]
{:offset-millis (some-> offset (.toMillis))
:now (ct/now)
:clock (if offset "offset" "system")}))
(def schema:set-virtual-clock-params
[:map {:title "set-virtual-clock-params"}
[:offset {:optional true} ct/schema:duration]
[:reset {:optional true} ::sm/boolean]])
(sv/defmethod ::set-virtual-clock
{::doc/added "2.20"
::sm/params schema:set-virtual-clock-params
::sm/result schema:get-virtual-clock-result}
[_ {:keys [::rpc/profile-id offset reset]}]
(when (= "production" (cf/get :tenant))
(ex/raise :type :restriction
:code :operation-not-allowed
:hint "virtual clock cannot be changed in production"))
(when (and (nil? offset) (not reset))
(ex/raise :type :validation
:code :missing-arguments
:hint "either offset or reset is required"))
;; Mirror `/dbg`: a reset (or a zero duration) clears the offset.
;; The raw millis go out untouched: the response encoder would
;; re-format an already formatted string.
(if (or reset (and (some? offset) (.isZero ^java.time.Duration offset)))
(clock/assign-offset profile-id nil)
(clock/assign-offset profile-id offset))
(let [current (clock/get-offset profile-id)]
{:offset-millis (some-> current (.toMillis))
:now (ct/now)
:clock (if current "offset" "system")}))