Add link to download a nitrate activation code request (#10900)

This commit is contained in:
Pablo Alba 2026-07-29 08:29:05 +02:00 committed by GitHub
parent 9f92206a1c
commit 70675919c7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 129 additions and 15 deletions

View File

@ -413,6 +413,17 @@
(generate-nitrate-uri "api/connectivity")
schema:connectivity params))
(def ^:private schema:identity
[:map
[:nitrate-id ::sm/text]
[:public-key ::sm/text]])
(defn- get-identity-api
[cfg params]
(request-to-nitrate cfg :get
(generate-nitrate-uri "api/identity")
schema:identity params))
(def ^:private schema:redeem-result
[:map
[:cancel-at [:maybe schema:timestamp]]])
@ -491,6 +502,7 @@
:get-subscription (partial get-subscription-api cfg)
:get-subscription-warning (partial get-subscription-warning-api cfg)
:connectivity (partial get-connectivity-api cfg)
:get-identity (partial get-identity-api cfg)
:redeem-activation-code (partial redeem-activation-code-api cfg)}))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@ -11,6 +11,7 @@
[app.auth.oidc :as oidc]
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.json :as json]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.types.nitrate-permissions :as nitrate-perms]
@ -24,7 +25,8 @@
[app.rpc.nitrate.emails-helper :as neh]
[app.rpc.nitrate.organization-helper :as noh]
[app.rpc.notifications :as notifications]
[app.util.services :as sv]))
[app.util.services :as sv]
[buddy.core.codecs :as bc]))
(defn assert-is-owner [cfg profile-id team-id]
@ -114,6 +116,35 @@
:cause cause)
(throw cause)))))))
(def ^:private activation-code-request-filename
"penpot-activation-code-request.txt")
(sv/defmethod ::get-nitrate-activation-code-request
"Returns a Base64-encoded JSON file requesting a Nitrate activation code.
Payload includes nitrateId, publicKey, email and iat."
{::rpc/auth true
::doc/added "2.20"
::sm/params [:map]
::sm/result ::sm/text}
[cfg {:keys [::rpc/profile-id]}]
(let [profile (db/get cfg :profile {:id profile-id})
nitrate-identity (nitrate/call cfg :get-identity {})]
(when-not nitrate-identity
(ex/raise :type :validation
:code :nitrate-identity-unavailable
:hint "Unable to retrieve nitrate identity"))
(-> (json/encode {:nitrate-id (:nitrate-id nitrate-identity)
:public-key (:public-key nitrate-identity)
:email (:email profile)
:iat (ct/seconds (ct/now))}
:key-fn json/write-camel-key)
(bc/str->bytes)
(bc/bytes->b64-str)
(rph/wrap)
(rph/with-header "content-type" "text/plain")
(rph/with-header "content-disposition"
(str "attachment; filename=\"" activation-code-request-filename "\"")))))
(def ^:private sql:prefix-team-name-and-unset-default
"UPDATE team
SET name = ? || name,

View File

@ -6,16 +6,20 @@
(ns backend-tests.rpc-nitrate-test
(:require
[app.common.json :as json]
[app.common.time :as ct]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as-alias db]
[app.email :as eml]
[app.http :as-alias http]
[app.nitrate :as nitrate]
[app.rpc :as-alias rpc]
[app.rpc.commands.nitrate]
[app.rpc.commands.teams :as teams]
[app.rpc.helpers :as rph]
[backend-tests.helpers :as th]
[buddy.core.codecs :as bc]
[clojure.test :as t]
[cuerdas.core :as str]))
@ -933,3 +937,44 @@
:organization-id org-id})]
(t/is (th/success? out))
(t/is (empty? @sent))))))
(t/deftest get-nitrate-activation-code-request
(let [profile (th/create-profile* 1 {:is-active true})
nitrate-id "nitrate-instance-1"
public-key "-----BEGIN PUBLIC KEY-----\nMIIB\n-----END PUBLIC KEY-----"
now (ct/now)]
(with-redefs [cf/flags (conj cf/flags :nitrate)
ct/now (constantly now)
nitrate/call (fn [_cfg method _params]
(t/is (= :get-identity method))
{:nitrate-id nitrate-id
:public-key public-key})]
(let [out (th/command! {::th/type :get-nitrate-activation-code-request
::rpc/profile-id (:id profile)})
body (-> (:result out)
(bc/b64->str)
(json/decode :key-fn json/read-kebab-key))]
(t/is (th/success? out))
(t/is (= {:nitrate-id nitrate-id
:public-key public-key
:email (:email profile)
:iat (ct/seconds now)}
body))
(let [[_ method-fn] (get-in th/*system* [:app.rpc/methods :get-nitrate-activation-code-request])
result (method-fn {::rpc/profile-id (:id profile)
::rpc/request-at now})
headers (::http/headers (meta result))]
(t/is (rph/wrapped? result))
(t/is (= "text/plain" (get headers "content-type")))
(t/is (= "attachment; filename=\"penpot-activation-code-request.txt\""
(get headers "content-disposition"))))))))
(t/deftest get-nitrate-activation-code-request-identity-unavailable
(let [profile (th/create-profile* 1 {:is-active true})]
(with-redefs [cf/flags (conj cf/flags :nitrate)
nitrate/call (fn [_cfg _method _params] nil)]
(let [out (th/command! {::th/type :get-nitrate-activation-code-request
::rpc/profile-id (:id profile)})]
(t/is (not (th/success? out)))
(t/is (th/ex-of-code? (:error out) :nitrate-identity-unavailable))))))

View File

@ -17,6 +17,7 @@
[app.main.ui.nitrate.nitrate-activation-success-modal]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[app.util.webapi :as wapi]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
@ -63,7 +64,16 @@
(mf/deps on-accept)
(fn [event]
(when (and (= "Enter" (.-key event)) (.-ctrlKey event))
(on-accept event))))]
(on-accept event))))
on-download-request-click
(mf/use-fn
(fn []
(->> (rp/cmd! :get-nitrate-activation-code-request {})
(rx/subs!
(fn [body]
(->> (wapi/create-blob body "text/plain")
(dom/trigger-download "penpot-activation-code-request.txt")))))))]
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-dialog)}
@ -101,7 +111,11 @@
:value (tr "nitrate.code-activation.submit")
:on-click on-accept}]]
[:div {:class (stl/css :footer-text)}
(tr "nitrate.code-activation.footer") " "
(tr "nitrate.code-activation.footer-before")
[:a {:class (stl/css :link)
:on-click on-download-request-click}
(tr "nitrate.code-activation.footer-link")]
(tr "nitrate.code-activation.footer-after") " "
[:a {:class (stl/css :link)
:href "mailto:sales@nitrate.com"}
"sales@nitrate.com"]]]]]))

View File

@ -84,9 +84,9 @@
(tr "nitrate.form.cancel-anytime")]]]
[:p {:class (stl/css :modal-text-medium)}
(tr "nitrate.form.have-code") " " [:a {:class (stl/css :link)
:on-click on-activate-click}
(tr "nitrate.form.enter-code")]]
(tr "nitrate.form.subscribe-with-code") " " [:a {:class (stl/css :link)
:on-click on-activate-click}
(tr "nitrate.form.enter-code")]]
[:p {:class (stl/css :modal-text-medium)}
[:a {:class (stl/css :link) :href dnt/go-to-subscription-url}
@ -102,7 +102,7 @@
"sales@penpot.app"]]
[:div {:class (stl/css :activation-code)}
[:p {:class (stl/css :modal-text-large)}
(tr "nitrate.form.have-code")]
(tr "nitrate.form.subscribe-with-code")]
[:p {:class (stl/css :modal-text-large)}
[:a {:class (stl/css :link)
:on-click on-activate-click}

View File

@ -4372,8 +4372,14 @@ msgid "nitrate.modal-success.title"
msgstr "Welcome to Enterprise!"
#: src/app/main/ui/nitrate/nitrate_code_activation_modal.cljs:104
msgid "nitrate.code-activation.footer"
msgstr "Need a code? Contact us:"
msgid "nitrate.code-activation.footer-before"
msgstr "Need a code? Download your "
msgid "nitrate.code-activation.footer-link"
msgstr "activation code request"
msgid "nitrate.code-activation.footer-after"
msgstr " and contact us:"
#: src/app/main/ui/nitrate/nitrate_code_activation_modal.cljs:86
msgid "nitrate.code-activation.input-label"
@ -4473,8 +4479,8 @@ msgid "nitrate.form.enterprise.price"
msgstr "per member per month"
#: src/app/main/ui/nitrate/nitrate_form.cljs:80, src/app/main/ui/nitrate/nitrate_form.cljs:98
msgid "nitrate.form.have-code"
msgstr "Have an activation code?"
msgid "nitrate.form.subscribe-with-code"
msgstr "Subscribe with an activation code"
#: src/app/main/ui/nitrate/nitrate_form.cljs:86
msgid "nitrate.form.see-plan"

View File

@ -4247,8 +4247,14 @@ msgid "nitrate.modal-success.title"
msgstr "¡Bienvenido a Enterprise!"
#: src/app/main/ui/nitrate/nitrate_code_activation_modal.cljs:104
msgid "nitrate.code-activation.footer"
msgstr "¿Necesitas un código? Contáctanos:"
msgid "nitrate.code-activation.footer-before"
msgstr "¿Necesitas un código? Descarga tu "
msgid "nitrate.code-activation.footer-link"
msgstr "solicitud de código de activación"
msgid "nitrate.code-activation.footer-after"
msgstr " y contáctanos:"
#: src/app/main/ui/nitrate/nitrate_code_activation_modal.cljs:86
msgid "nitrate.code-activation.input-label"
@ -4351,8 +4357,8 @@ msgid "nitrate.form.enterprise.price"
msgstr "por miembro al mes"
#: src/app/main/ui/nitrate/nitrate_form.cljs:80, src/app/main/ui/nitrate/nitrate_form.cljs:98
msgid "nitrate.form.have-code"
msgstr "¿Tienes un código de activación?"
msgid "nitrate.form.subscribe-with-code"
msgstr "Suscribete con un código de activación"
#: src/app/main/ui/nitrate/nitrate_form.cljs:86
msgid "nitrate.form.see-plan"