mirror of
https://github.com/penpot/penpot.git
synced 2026-09-09 04:09:38 -04:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f1c88fca2 |
No files matched your search
@@ -58,7 +58,6 @@
|
||||
:objects-storage-fs-directory "assets"
|
||||
|
||||
:auth-token-cookie-name "auth-token"
|
||||
:auth-token-cookie-max-age-absolute (ct/duration {:days 30})
|
||||
|
||||
:assets-path "/internal/assets/"
|
||||
:smtp-default-reply-to "Penpot <no-reply@example.com>"
|
||||
@@ -207,7 +206,6 @@
|
||||
|
||||
[:auth-token-cookie-name {:optional true} :string]
|
||||
[:auth-token-cookie-max-age {:optional true} ::ct/duration]
|
||||
[:auth-token-cookie-max-age-absolute {:optional true} ::ct/duration]
|
||||
|
||||
[:registration-domain-whitelist {:optional true} [::sm/set :string]]
|
||||
[:email-verify-threshold {:optional true} ::ct/duration]
|
||||
|
||||
@@ -36,9 +36,6 @@
|
||||
;; Default age for automatic session renewal
|
||||
(def default-renewal-max-age (ct/duration {:hours 6}))
|
||||
|
||||
;; Default absolute maximum session duration
|
||||
(def default-cookie-max-age-absolute (ct/duration {:days 30}))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; PROTOCOLS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -172,19 +169,15 @@
|
||||
|
||||
(defn- assign-token
|
||||
[cfg session]
|
||||
(let [absolute-max-age (cf/get :auth-token-cookie-max-age-absolute default-cookie-max-age-absolute)
|
||||
claims {:iss "authentication"
|
||||
:aud "penpot"
|
||||
:sid (:id session)
|
||||
:iat (:modified-at session)
|
||||
:uid (:profile-id session)
|
||||
:sso-provider-id (:sso-provider-id session)
|
||||
:sso-session-id (:sso-session-id session)}
|
||||
claims (if (:created-at session)
|
||||
(assoc claims :exp (ct/plus (:created-at session) absolute-max-age))
|
||||
claims)
|
||||
header {:kid 1 :ver 1}
|
||||
token (tokens/generate cfg claims header)]
|
||||
(let [claims {:iss "authentication"
|
||||
:aud "penpot"
|
||||
:sid (:id session)
|
||||
:iat (:modified-at session)
|
||||
:uid (:profile-id session)
|
||||
:sso-provider-id (:sso-provider-id session)
|
||||
:sso-session-id (:sso-session-id session)}
|
||||
header {:kid 1 :ver 1}
|
||||
token (tokens/generate cfg claims header)]
|
||||
(assoc session :token token)))
|
||||
|
||||
(defn create-fn
|
||||
@@ -360,23 +353,15 @@
|
||||
or (updated_at is null and
|
||||
created_at < ?::timestamptz)")
|
||||
|
||||
(def ^:private
|
||||
sql:delete-expired-v2
|
||||
"DELETE FROM http_session_v2
|
||||
WHERE created_at < ?::timestamptz")
|
||||
|
||||
(defn- collect-expired-tasks
|
||||
[{:keys [::db/conn ::tasks/max-age]}]
|
||||
(let [threshold (ct/minus (ct/now) max-age)
|
||||
result-legacy (-> (db/exec-one! conn [sql:delete-expired threshold threshold])
|
||||
(db/get-update-count))
|
||||
result-v2 (-> (db/exec-one! conn [sql:delete-expired-v2 threshold])
|
||||
(db/get-update-count))]
|
||||
result (-> (db/exec-one! conn [sql:delete-expired threshold threshold])
|
||||
(db/get-update-count))]
|
||||
(l/dbg :task "gc"
|
||||
:hint "clean http sessions"
|
||||
:deleted-legacy result-legacy
|
||||
:deleted-v2 result-v2)
|
||||
(+ result-legacy result-v2)))
|
||||
:deleted result)
|
||||
result))
|
||||
|
||||
(defmethod ig/init-key ::tasks/gc
|
||||
[_ {:keys [::tasks/max-age] :as cfg}]
|
||||
|
||||
@@ -277,70 +277,6 @@
|
||||
(t/is (= (:id session) (:sid claims)))
|
||||
(t/is (= (:id profile) (:uid claims)))))
|
||||
|
||||
(t/deftest session-token-contains-exp-claim
|
||||
(let [cfg th/*system*
|
||||
manager (session/inmemory-manager)
|
||||
profile (th/create-profile* 1)
|
||||
session (->> (session/create-session manager {:profile-id (:id profile)
|
||||
:user-agent "user agent"})
|
||||
(#'session/assign-token cfg))
|
||||
claims (tokens/decode cfg (:token session))
|
||||
exp (:exp claims)]
|
||||
(t/is (some? exp) "session token should contain :exp claim")
|
||||
(t/is (ct/inst? exp) "exp should be an instant")))
|
||||
|
||||
(t/deftest session-token-exp-based-on-created-at
|
||||
(let [cfg th/*system*
|
||||
manager (session/inmemory-manager)
|
||||
profile (th/create-profile* 1)
|
||||
session (->> (session/create-session manager {:profile-id (:id profile)
|
||||
:user-agent "user agent"})
|
||||
(#'session/assign-token cfg))
|
||||
claims (tokens/decode cfg (:token session))
|
||||
expected-exp (ct/plus (:created-at session) (ct/duration {:days 30}))]
|
||||
(t/is (some? (:exp claims)) "session token should contain :exp claim")
|
||||
(t/is (= (inst-ms (:exp claims))
|
||||
(inst-ms expected-exp))
|
||||
"exp should equal created-at + 30 days")))
|
||||
|
||||
(t/deftest session-token-past-exp-is-rejected
|
||||
(let [cfg th/*system*
|
||||
manager (session/inmemory-manager)
|
||||
profile (th/create-profile* 1)
|
||||
session (->> (session/create-session manager {:profile-id (:id profile)
|
||||
:user-agent "user agent"})
|
||||
(#'session/assign-token cfg))
|
||||
claims (tokens/decode cfg (:token session))
|
||||
;; Manually create a token with exp in the past
|
||||
past-claims (assoc claims :exp (ct/minus (ct/now) (ct/duration {:days 1})))
|
||||
header {:kid 1 :ver 1}
|
||||
past-token (tokens/generate cfg past-claims header)]
|
||||
(t/is (nil? (session/decode-token cfg past-token))
|
||||
"token with exp in the past should be rejected")))
|
||||
|
||||
(t/deftest session-renewal-preserves-original-exp
|
||||
(let [cfg th/*system*
|
||||
manager (session/inmemory-manager)
|
||||
profile (th/create-profile* 1)
|
||||
handler (-> (fn [req] req)
|
||||
(#'session/wrap-authz {::session/manager manager})
|
||||
(#'mw/wrap-auth {:bearer (partial session/decode-token cfg)
|
||||
:cookie (partial session/decode-token cfg)}))
|
||||
session (->> (session/create-session manager {:profile-id (:id profile)
|
||||
:user-agent "user agent"})
|
||||
(#'session/assign-token cfg))
|
||||
original-exp (:exp (tokens/decode cfg (:token session)))
|
||||
;; Force renewal by setting modified-at to 7 hours ago
|
||||
old-session (assoc session :modified-at (ct/minus (ct/now) (ct/duration {:hours 7})))
|
||||
response (handler (make-dummy-request {:cookies {"auth-token" (:token old-session)}}))
|
||||
{:keys [token claims]} (get response ::http/auth-data)
|
||||
new-exp (:exp claims)]
|
||||
(t/is (some? original-exp) "original token should have :exp")
|
||||
(t/is (some? new-exp) "renewed token should have :exp")
|
||||
(t/is (= (inst-ms original-exp)
|
||||
(inst-ms new-exp))
|
||||
"renewed token should preserve original :exp, not extend it")))
|
||||
|
||||
(t/deftest parse-request-illegal-argument-exception
|
||||
;; clojure.data.json raises IllegalArgumentException (case
|
||||
;; fall-through) on several kinds of malformed input. The
|
||||
|
||||
@@ -660,6 +660,48 @@ PENPOT_INTERNAL_URI: http://penpot-frontend:8080
|
||||
`http://penpot-frontend:8080` used in the docker-compose is a good default and
|
||||
it is recommended to keep it unchanged.
|
||||
|
||||
### MCP
|
||||
|
||||
The MCP server lets AI agents read and edit Penpot files. It runs as a separate
|
||||
`penpot-mcp` container, and the frontend proxies the requests to it. Enable it with
|
||||
the corresponding flag:
|
||||
|
||||
```bash
|
||||
PENPOT_FLAGS: [...] enable-mcp
|
||||
```
|
||||
|
||||
With the flag enabled, the frontend container uses these variables to locate the MCP
|
||||
server:
|
||||
|
||||
```bash
|
||||
# Frontend
|
||||
PENPOT_MCP_URI: http://penpot-mcp:4401
|
||||
PENPOT_MCP_URI_WS: http://penpot-mcp:4402
|
||||
```
|
||||
|
||||
- `PENPOT_MCP_URI`: The URI of the MCP server, used for the streamable HTTP and SSE
|
||||
endpoints.
|
||||
- `PENPOT_MCP_URI_WS`: The URI of the MCP server used for the websocket connection.
|
||||
|
||||
The defaults match the service name used in the official `docker-compose.yaml`. Change
|
||||
them only if your MCP service has a different name or listens on other ports. Both
|
||||
variables are ignored when the `enable-mcp` flag is not set.
|
||||
|
||||
### Internal resolver
|
||||
|
||||
The frontend container resolves the backend, exporter and MCP service names with the
|
||||
DNS servers listed in its `/etc/resolv.conf`. If that autodetection does not work for
|
||||
your setup, set the resolver explicitly:
|
||||
|
||||
```bash
|
||||
# Frontend
|
||||
PENPOT_INTERNAL_RESOLVER: 127.0.0.11
|
||||
```
|
||||
|
||||
- `PENPOT_INTERNAL_RESOLVER`: The DNS server nginx uses to resolve the internal service
|
||||
names. Defaults to the nameservers found in `/etc/resolv.conf`. `127.0.0.11` is the
|
||||
embedded Docker DNS server; use the address of your own resolver on other setups.
|
||||
|
||||
## Other flags
|
||||
|
||||
There are other flags that are useful for a more customized Penpot experience. This section has the list of the flags meant
|
||||
@@ -670,6 +712,9 @@ for the user:
|
||||
- <code class="language-bash">enable-backend-api-doc</code>: Enables the <code class="language-bash">/api/doc</code>
|
||||
endpoint that lists all rpc methods available on backend
|
||||
- <code class="language-bash">disable-login-with-password</code>: allows disable password based login form
|
||||
- <code class="language-bash">enable-mcp</code>: Enables the MCP server integration, so AI agents can
|
||||
read and edit Penpot files. It also makes the frontend proxy the MCP endpoints to the
|
||||
<code class="language-bash">penpot-mcp</code> service. Check the [MCP section][8] to get more detail.
|
||||
- <code class="language-bash">enable-prepl-server</code>: enables PREPL server, used by manage.py and other additional
|
||||
tools to communicate internally with Penpot backend. Check the [CLI section][5] to get more detail.
|
||||
|
||||
@@ -693,3 +738,4 @@ __Since version 2.0.0__
|
||||
[5]: /technical-guide/getting-started/docker#using-the-cli-for-administrative-tasks
|
||||
[6]: /technical-guide/integration/#webhooks
|
||||
[7]: /technical-guide/integration/#access-tokens
|
||||
[8]: /mcp/
|
||||
Reference in new issue
Block a user