diff --git a/CHANGES.md b/CHANGES.md index b6f35bad8..b8a720fb9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ ### :sparkles: New features - Set an artboard as the file thumbnail [Taiga #1526](https://tree.taiga.io/project/penpot/us/1526) +- Social login redesign [Taiga #2974](https://tree.taiga.io/project/penpot/task/2974) - Add border radius to our artboars [Taiga #2056](https://tree.taiga.io/project/penpot/us/2056) - Allow send multiple team invitations at once [Taiga #2798](https://tree.taiga.io/project/penpot/us/2798) - Persist color palette and color picker across refresh [Taiga #1660](https://tree.taiga.io/project/penpot/issue/1660) @@ -18,6 +19,9 @@ - New focus mode in workspace [Taiga #2748](https://tree.taiga.io/project/penpot/us/2748) - Changed text shapes to be displayed as natives SVG text elements [Taiga #2759](https://tree.taiga.io/project/penpot/us/2759) - Texts now can have strokes, multiple fills and can be used as masks +- Add the ability to specify the attr for retrieve the email on OIDC integration [#1460](https://github.com/penpot/penpot/issues/1460) +- Allow registration with invitation token when registration is disabled +- Add the ability to disable standard, password login [Taiga #2999](https://tree.taiga.io/project/penpot/us/2999) ### :bug: Bugs fixed diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index b11ceae63..6dfbfc7ae 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -90,7 +90,7 @@ (s/def ::flags ::us/set-of-keywords) -;; DEPRECATED PROPERTIES: should be removed in 1.10 +;; DEPRECATED PROPERTIES (s/def ::registration-enabled ::us/boolean) (s/def ::smtp-enabled ::us/boolean) (s/def ::telemetry-enabled ::us/boolean) @@ -138,11 +138,15 @@ (s/def ::oidc-scopes ::us/set-of-str) (s/def ::oidc-roles ::us/set-of-str) (s/def ::oidc-roles-attr ::us/keyword) +(s/def ::oidc-email-attr ::us/keyword) +(s/def ::oidc-name-attr ::us/keyword) (s/def ::host ::us/string) (s/def ::http-server-port ::us/integer) (s/def ::http-server-host ::us/string) -(s/def ::http-server-min-threads ::us/integer) -(s/def ::http-server-max-threads ::us/integer) +(s/def ::http-server-max-body-size ::us/integer) +(s/def ::http-server-max-multipart-body-size ::us/integer) +(s/def ::http-server-io-threads ::us/integer) +(s/def ::http-server-worker-threads ::us/integer) (s/def ::http-session-idle-max-age ::dt/duration) (s/def ::http-session-updater-batch-max-age ::dt/duration) (s/def ::http-session-updater-batch-max-size ::us/integer) @@ -239,12 +243,16 @@ ::oidc-user-uri ::oidc-scopes ::oidc-roles-attr + ::oidc-email-attr + ::oidc-name-attr ::oidc-roles ::host ::http-server-host ::http-server-port - ::http-server-max-threads - ::http-server-min-threads + ::http-server-max-body-size + ::http-server-max-multipart-body-size + ::http-server-io-threads + ::http-server-worker-threads ::http-session-idle-max-age ::http-session-updater-batch-max-age ::http-session-updater-batch-max-size @@ -339,8 +347,8 @@ (when (ex/ex-info? e) (println ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;") (println "Error on validating configuration:") - (println (:explain (ex-data e)) - (println ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"))) + (println (us/pretty-explain (ex-data e))) + (println ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;")) (throw e)))) (def version diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index fa8643879..010bc941f 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -7,9 +7,7 @@ (ns app.http (:require [app.common.data :as d] - [app.common.exceptions :as ex] [app.common.logging :as l] - [app.common.spec :as us] [app.http.doc :as doc] [app.http.errors :as errors] [app.http.middleware :as middleware] @@ -31,40 +29,46 @@ (s/def ::handler fn?) (s/def ::router some?) -(s/def ::port ::us/integer) -(s/def ::host ::us/string) -(s/def ::name ::us/string) -(s/def ::executors (s/map-of keyword? ::wrk/executor)) +(s/def ::port integer?) +(s/def ::host string?) +(s/def ::name string?) -;; (s/def ::max-threads ::cf/http-server-max-threads) -;; (s/def ::min-threads ::cf/http-server-min-threads) +(s/def ::max-body-size integer?) +(s/def ::max-multipart-body-size integer?) +(s/def ::io-threads integer?) +(s/def ::worker-threads integer?) (defmethod ig/prep-key ::server [_ cfg] (merge {:name "http" :port 6060 - :host "0.0.0.0"} + :host "0.0.0.0" + :max-body-size (* 1024 1024 24) ; 24 MiB + :max-multipart-body-size (* 1024 1024 120)} ; 120 MiB (d/without-nils cfg))) (defmethod ig/pre-init-spec ::server [_] - (s/keys :req-un [::port ::host ::name ::executors] - :opt-un [::router ::handler])) + (s/and + (s/keys :req-un [::port ::host ::name ::max-body-size ::max-multipart-body-size] + :opt-un [::router ::handler ::io-threads ::worker-threads ::wrk/executor]) + (fn [cfg] + (or (contains? cfg :router) + (contains? cfg :handler))))) (defmethod ig/init-key ::server - [_ {:keys [handler router port name host executors] :as cfg}] - (l/info :hint "starting http server" - :port port :host host :name name) - + [_ {:keys [handler router port name host] :as cfg}] + (l/info :hint "starting http server" :port port :host host :name name) (let [options {:http/port port :http/host host - :ring/async true - :xnio/dispatch (:default executors)} - handler (cond - (fn? handler) handler - (some? router) (wrap-router cfg router) - :else (ex/raise :type :internal - :code :invalid-argument - :hint "Missing `handler` or `router` option.")) + :http/max-body-size (:max-body-size cfg) + :http/max-multipart-body-size (:max-multipart-body-size cfg) + :xnio/io-threads (:io-threads cfg) + :xnio/worker-threads (:worker-threads cfg) + :xnio/dispatch (:executor cfg) + :ring/async true} + handler (if (some? router) + (wrap-router cfg router) + handler) server (yt/server handler (d/without-nils options))] (assoc cfg :server (yt/start! server)))) @@ -97,7 +101,7 @@ (handler request respond (fn [cause] (l/error :hint "unexpected error processing request" - ::l/context (errors/get-error-context request cause) + ::l/context (errors/get-context request) :query-string (yrq/query request) :cause cause) (respond (yrs/response 500 "internal server error"))))))) diff --git a/backend/src/app/http/debug.clj b/backend/src/app/http/debug.clj index 53f70a584..92daf1f63 100644 --- a/backend/src/app/http/debug.clj +++ b/backend/src/app/http/debug.clj @@ -162,7 +162,8 @@ (let [context (dissoc report :trace :cause :params :data :spec-problems :spec-explain :spec-value :error :explain :hint) - params {:context (with-out-str (fpp/pprint context {:width 300})) + params {:context (with-out-str + (fpp/pprint context {:width 200})) :hint (:hint report) :spec-explain (:spec-explain report) :spec-problems (:spec-problems report) diff --git a/backend/src/app/http/errors.clj b/backend/src/app/http/errors.clj index 40eaf9fc3..95d4f9819 100644 --- a/backend/src/app/http/errors.clj +++ b/backend/src/app/http/errors.clj @@ -21,27 +21,17 @@ (yrq/get-header request "x-real-ip") (yrq/remote-addr request))) -(defn get-error-context - [request error] - (let [data (ex-data error)] - (merge - {:path (:uri request) - :method (:request-method request) - :hint (ex-message error) - :params (:params request) - - :spec-problems (some->> data ::s/problems (take 10) seq vec) - :spec-value (some->> data ::s/value) - :data (some-> data (dissoc ::s/problems ::s/value ::s/spec)) - :ip-addr (parse-client-ip request) - :profile-id (:profile-id request)} - - (let [headers (:headers request)] - {:user-agent (get headers "user-agent") - :frontend-version (get headers "x-frontend-version" "unknown")}) - - (when (and data (::s/problems data)) - {:spec-explain (us/pretty-explain data)})))) +(defn get-context + [request] + (merge + {:path (:uri request) + :method (:request-method request) + :params (:params request) + :ip-addr (parse-client-ip request) + :profile-id (:profile-id request)} + (let [headers (:headers request)] + {:user-agent (get headers "user-agent") + :frontend-version (get headers "x-frontend-version" "unknown")}))) (defmulti handle-exception (fn [err & _rest] @@ -70,7 +60,7 @@ (let [edata (ex-data error) explain (us/pretty-explain edata)] (l/error ::l/raw (ex-message error) - ::l/context (get-error-context request error) + ::l/context (get-context request) :cause error) (yrs/response :status 500 :body {:type :server-error @@ -96,7 +86,7 @@ (handle-exception (:handling edata) request) (do (l/error ::l/raw (ex-message error) - ::l/context (get-error-context request error) + ::l/context (get-context request) :cause error) (yrs/response 500 {:type :server-error :code :unexpected @@ -107,7 +97,7 @@ [error request] (let [state (.getSQLState ^java.sql.SQLException error)] (l/error ::l/raw (ex-message error) - ::l/context (get-error-context request error) + ::l/context (get-context request) :cause error) (cond (= state "57014") diff --git a/backend/src/app/http/oauth.clj b/backend/src/app/http/oauth.clj index fffdcfbd1..4e2748529 100644 --- a/backend/src/app/http/oauth.clj +++ b/backend/src/app/http/oauth.clj @@ -57,7 +57,8 @@ :grant_type "authorization_code" :redirect_uri (build-redirect-uri cfg)} req {:method :post - :headers {"content-type" "application/x-www-form-urlencoded"} + :headers {"content-type" "application/x-www-form-urlencoded" + "accept" "application/json"} :uri (:token-uri provider) :body (u/map->query-string params)}] (p/then @@ -69,42 +70,61 @@ :type (get data :token_type)}) (ex/raise :type :internal :code :unable-to-retrieve-token - ::http-status status - ::http-body body)))))) + :http-status status + :http-body body)))))) (defn- retrieve-user-info [{:keys [provider http-client] :as cfg} tdata] - (p/then - (http-client {:uri (:user-uri provider) - :headers {"Authorization" (str (:type tdata) " " (:token tdata))} - :timeout 6000 - :method :get}) - (fn [{:keys [status body] :as res}] - (if (= 200 status) - (let [info (json/read body) - info {:backend (:name provider) - :email (get info :email) - :fullname (get info :name) - :props (->> (dissoc info :name :email) - (qualify-props provider))}] + (letfn [(retrieve [] + (http-client {:uri (:user-uri provider) + :headers {"Authorization" (str (:type tdata) " " (:token tdata))} + :timeout 6000 + :method :get})) - (when-not (s/valid? ::info info) - (l/warn :hint "received incomplete profile info object (please set correct scopes)" - :info (pr-str info)) - (ex/raise :type :internal - :code :unable-to-auth - :hint "no user info")) - info) - (ex/raise :type :internal - :code :unable-to-retrieve-user-info - ::http-status status - ::http-body body))))) + (validate-response [{:keys [status body] :as res}] + (when-not (= 200 status) + (ex/raise :type :internal + :code :unable-to-retrieve-user-info + :hint "unable to retrieve user info" + :http-status status + :http-body body)) + res) + + (get-email [info] + (let [attr-kw (cf/get :oidc-email-attr :email)] + (get info attr-kw))) + + (get-name [info] + (let [attr-kw (cf/get :oidc-name-attr :name)] + (get info attr-kw))) + + (process-response [{:keys [body]}] + (let [info (json/read body)] + {:backend (:name provider) + :email (get-email info) + :fullname (get-name info) + :props (->> (dissoc info :name :email) + (qualify-props provider))})) + + (validate-info [info] + (when-not (s/valid? ::info info) + (l/warn :hint "received incomplete profile info object (please set correct scopes)" + :info (pr-str info)) + (ex/raise :type :internal + :code :incomplete-user-info + :hint "inconmplete user info" + :info info)) + info)] + + (-> (retrieve) + (p/then' validate-response) + (p/then' process-response) + (p/then' validate-info)))) (s/def ::backend ::us/not-empty-string) (s/def ::email ::us/not-empty-string) (s/def ::fullname ::us/not-empty-string) (s/def ::props (s/map-of ::us/keyword any?)) - (s/def ::info (s/keys :req-un [::backend ::email @@ -112,7 +132,7 @@ ::props])) (defn retrieve-info - [{:keys [tokens provider] :as cfg} request] + [{:keys [tokens provider] :as cfg} {:keys [params] :as request}] (letfn [(validate-oidc [info] ;; If the provider is OIDC, we can proceed to check ;; roles if they are defined. @@ -143,9 +163,15 @@ (map? (:props state)) (update :props merge (:props state))))] - (let [state (get-in request [:params :state]) - state (tokens :verify {:token state :iss :oauth}) - code (get-in request [:params :code])] + (when-let [error (get params :error)] + (ex/raise :type :internal + :code :error-on-retrieving-code + :error-id error + :error-desc (get params :error_description))) + + (let [state (get params :state) + code (get params :code) + state (tokens :verify {:token state :iss :oauth})] (-> (p/resolved code) (p/then #(retrieve-access-token cfg %)) (p/then #(retrieve-user-info cfg %)) @@ -224,15 +250,18 @@ (redirect-response uri)))) (defn- auth-handler - [{:keys [tokens] :as cfg} {:keys [params] :as request} respond _] - (let [props (extract-utm-props params) - state (tokens :generate - {:iss :oauth - :invitation-token (:invitation-token params) - :props props - :exp (dt/in-future "15m")}) - uri (build-auth-uri cfg state)] - (respond (yrs/response 200 {:redirect-uri uri})))) + [{:keys [tokens] :as cfg} {:keys [params] :as request} respond raise] + (try + (let [props (extract-utm-props params) + state (tokens :generate + {:iss :oauth + :invitation-token (:invitation-token params) + :props props + :exp (dt/in-future "15m")}) + uri (build-auth-uri cfg state)] + (respond (yrs/response 200 {:redirect-uri uri}))) + (catch Throwable cause + (raise cause)))) (defn- callback-handler [cfg request respond _] @@ -242,7 +271,7 @@ (generate-redirect cfg request info profile))) (handle-error [cause] - (l/warn :hint "error on oauth process" :cause cause) + (l/error :hint "error on oauth process" :cause cause) (respond (generate-error-redirect cfg cause)))] (-> (process-request) @@ -385,17 +414,16 @@ (assoc-in cfg [:providers "github"] opts)) cfg))) - (defn- initialize-gitlab-provider [cfg] (let [base (cf/get :gitlab-base-uri "https://gitlab.com") opts {:base-uri base :client-id (cf/get :gitlab-client-id) :client-secret (cf/get :gitlab-client-secret) - :scopes #{"read_user"} + :scopes #{"openid" "profile" "email"} :auth-uri (str base "/oauth/authorize") :token-uri (str base "/oauth/token") - :user-uri (str base "/api/v4/user") + :user-uri (str base "/oauth/userinfo") :name "gitlab"}] (if (and (string? (:client-id opts)) (string? (:client-secret opts))) diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index ae80ffaec..952c4d640 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -254,7 +254,7 @@ "select * from audit_log where archived_at is null order by created_at asc - limit 1000 + limit 256 for update skip locked;") (defn archive-events @@ -298,8 +298,9 @@ (if (= (:status resp) 204) true (do - (l/warn :hint "unable to archive events" - :resp-status (:status resp)) + (l/error :hint "unable to archive events" + :resp-status (:status resp) + :resp-body (:body resp)) false)))) (mark-as-archived [conn rows] diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 7b54ad9f7..ec0808a8c 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -113,7 +113,8 @@ :host (cf/get :http-server-host) :router (ig/ref :app.http/router) :metrics (ig/ref :app.metrics/metrics) - :executors (ig/ref :app.worker/executors)} + :executor (ig/ref [::default :app.worker/executor]) + :io-threads (cf/get :http-server-io-threads)} :app.http/router {:assets (ig/ref :app.http.assets/handlers) diff --git a/backend/src/app/metrics.clj b/backend/src/app/metrics.clj index 254c9d56b..2ae0223dc 100644 --- a/backend/src/app/metrics.clj +++ b/backend/src/app/metrics.clj @@ -262,10 +262,3 @@ :gauge (make-gauge props) :summary (make-summary props) :histogram (make-histogram props))) - -;; (defn instrument-jetty! -;; [^CollectorRegistry registry ^StatisticsHandler handler] -;; (doto (JettyStatisticsCollector. handler) -;; (.register registry)) -;; nil) - diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index 540e5e22b..963c36416 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -19,7 +19,6 @@ [app.rpc.queries.profile :as profile] [app.rpc.rlimit :as rlimit] [app.storage :as sto] - [app.util.async :as async] [app.util.services :as sv] [app.util.time :as dt] [buddy.hashers :as hashers] @@ -101,8 +100,14 @@ (sv/defmethod ::prepare-register-profile {:auth false} [{:keys [pool tokens] :as cfg} params] (when-not (contains? cf/flags :registration) - (ex/raise :type :restriction - :code :registration-disabled)) + (if-not (contains? params :invitation-token) + (ex/raise :type :restriction + :code :registration-disabled) + (let [invitation (tokens :verify {:token (:invitation-token params) :iss :team-invitation})] + (when-not (= (:email params) (:member-email invitation)) + (ex/raise :type :restriction + :code :email-does-not-match-invitation + :hint "email should match the invitation"))))) (when-let [domains (cf/get :registration-domain-whitelist)] (when-not (email-domain-in-whitelist? domains (:email params)) @@ -129,6 +134,7 @@ :backend "penpot" :iss :prepared-register :exp (dt/in-future "48h")} + token (tokens :generate params)] {:token token})) @@ -149,7 +155,6 @@ [{:keys [conn tokens session] :as cfg} {:keys [token] :as params}] (let [claims (tokens :verify {:token token :iss :prepared-register}) params (merge params claims)] - (check-profile-existence! conn params) (let [is-active (or (:is-active params) @@ -158,10 +163,8 @@ (create-profile conn) (create-profile-relations conn) (decode-profile-row)) - invitation (when-let [token (:invitation-token params)] (tokens :verify {:token token :iss :team-invitation}))] - (cond ;; If invitation token comes in params, this is because the user comes from team-invitation process; ;; in this case, regenerate token and send back to the user a new invitation token (and mark current @@ -280,10 +283,14 @@ :opt-un [::scope ::invitation-token])) (sv/defmethod ::login - {:auth false - ::async/dispatch :default - ::rlimit/permits (cf/get :rlimit-password)} + {:auth false ::rlimit/permits (cf/get :rlimit-password)} [{:keys [pool session tokens] :as cfg} {:keys [email password] :as params}] + + (when-not (contains? cf/flags :login) + (ex/raise :type :restriction + :code :login-disabled + :hint "login is disabled in this instance")) + (letfn [(check-password [profile password] (when (= (:password profile) "!") (ex/raise :type :validation diff --git a/backend/src/app/storage.clj b/backend/src/app/storage.clj index cff6c3363..218835149 100644 --- a/backend/src/app/storage.clj +++ b/backend/src/app/storage.clj @@ -259,7 +259,7 @@ ;; A task responsible to permanently delete already marked as deleted ;; storage files. The storage objects are practically never marked to ;; be deleted directly by the api call. The touched-gc is responsible -;; collect the usage of the object and mark it as deleted. +;; of collecting the usage of the object and mark it as deleted. (declare sql:retrieve-deleted-objects-chunk) diff --git a/backend/test/app/services_profile_test.clj b/backend/test/app/services_profile_test.clj index fb0f4980e..c87e7cfa8 100644 --- a/backend/test/app/services_profile_test.clj +++ b/backend/test/app/services_profile_test.clj @@ -7,6 +7,7 @@ (ns app.services-profile-test (:require [app.common.uuid :as uuid] + [app.config :as cf] [app.db :as db] [app.rpc.mutations.profile :as profile] [app.test-helpers :as th] @@ -195,6 +196,56 @@ (t/is (nil? error)))) )) +(t/deftest prepare-and-register-with-invitation-and-disabled-registration-1 + (with-redefs [app.config/flags [:disable-registration]] + (let [tokens-fn (:app.tokens/tokens th/*system*) + itoken (tokens-fn :generate + {:iss :team-invitation + :exp (dt/in-future "48h") + :role :editor + :team-id uuid/zero + :member-email "user@example.com"}) + data {::th/type :prepare-register-profile + :invitation-token itoken + :email "user@example.com" + :password "foobar"} + + {:keys [result error] :as out} (th/mutation! data)] + (t/is (nil? error)) + (t/is (map? result)) + (t/is (string? (:token result))) + + (let [rtoken (:token result) + data {::th/type :register-profile + :token rtoken + :fullname "foobar"} + + {:keys [result error] :as out} (th/mutation! data)] + ;; (th/print-result! out) + (t/is (nil? error)) + (t/is (map? result)) + (t/is (string? (:invitation-token result))))))) + +(t/deftest prepare-and-register-with-invitation-and-disabled-registration-2 + (with-redefs [app.config/flags [:disable-registration]] + (let [tokens-fn (:app.tokens/tokens th/*system*) + itoken (tokens-fn :generate + {:iss :team-invitation + :exp (dt/in-future "48h") + :role :editor + :team-id uuid/zero + :member-email "user2@example.com"}) + + data {::th/type :prepare-register-profile + :invitation-token itoken + :email "user@example.com" + :password "foobar"} + {:keys [result error] :as out} (th/mutation! data)] + (t/is (th/ex-info? error)) + (t/is (= :restriction (th/ex-type error))) + (t/is (= :email-does-not-match-invitation (th/ex-code error)))))) + + (t/deftest prepare-register-with-registration-disabled (th/with-mocks {#'app.config/flags nil} (let [data {::th/type :prepare-register-profile diff --git a/backend/test/app/test_helpers.clj b/backend/test/app/test_helpers.clj index f51bccc86..e626f4dd1 100644 --- a/backend/test/app/test_helpers.clj +++ b/backend/test/app/test_helpers.clj @@ -313,6 +313,14 @@ [v] (instance? clojure.lang.ExceptionInfo v)) +(defn ex-type + [e] + (:type (ex-data e))) + +(defn ex-code + [e] + (:code (ex-data e))) + (defn ex-of-type? [e type] (let [data (ex-data e)] diff --git a/common/src/app/common/exceptions.cljc b/common/src/app/common/exceptions.cljc index dbc63a8ba..b3b73e5a9 100644 --- a/common/src/app/common/exceptions.cljc +++ b/common/src/app/common/exceptions.cljc @@ -23,11 +23,12 @@ ::cause])) (defn error - [& {:keys [hint cause ::data] :as params}] + [& {:keys [hint cause ::data type] :as params}] (s/assert ::error-params params) (let [payload (-> params (dissoc :cause ::data) - (merge data))] + (merge data)) + hint (or hint (pr-str type))] (ex-info hint payload cause))) (defmacro raise diff --git a/common/src/app/common/flags.cljc b/common/src/app/common/flags.cljc index 149f71c65..2bdaab823 100644 --- a/common/src/app/common/flags.cljc +++ b/common/src/app/common/flags.cljc @@ -12,7 +12,7 @@ (def default "A common flags that affects both: backend and frontend." [:enable-registration - :enable-demo-users]) + :enable-login]) (defn parse [& flags] diff --git a/common/src/app/common/logging.cljc b/common/src/app/common/logging.cljc index 663f4beae..44cadc35d 100644 --- a/common/src/app/common/logging.cljc +++ b/common/src/app/common/logging.cljc @@ -8,8 +8,10 @@ (:require [app.common.exceptions :as ex] [app.common.uuid :as uuid] + [app.common.spec :as us] [clojure.pprint :refer [pprint]] [cuerdas.core :as str] + [clojure.spec.alpha :as s] [fipp.edn :as fpp] #?(:clj [io.aviso.exception :as ie]) #?(:cljs [goog.log :as glog])) @@ -152,6 +154,18 @@ [logger level] (.isEnabled ^Logger logger ^Level level))) +#?(:clj + (defn get-error-context + [error] + (when-let [data (ex-data error)] + (merge + {:hint (ex-message error) + :spec-problems (some->> data ::s/problems (take 10) seq vec) + :spec-value (some->> data ::s/value) + :data (some-> data (dissoc ::s/problems ::s/value ::s/spec))} + (when (and data (::s/problems data)) + {:spec-explain (us/pretty-explain data)}))))) + (defmacro log [& {:keys [level cause ::logger ::async ::raw ::context] :or {async true} :as props}] (if (:ns &env) ; CLJS @@ -169,7 +183,9 @@ ~(if async `(send-off logging-agent (fn [_#] - (with-context (into {:id (uuid/next)} ~context) + (with-context (merge {:id (uuid/next)} + (get-error-context ~cause) + ~context) (->> (or ~raw (build-map-message ~props)) (write-log! ~logger-sym ~level-sym ~cause))))) diff --git a/common/src/app/common/spec.cljc b/common/src/app/common/spec.cljc index 083011b7b..03f3ddc37 100644 --- a/common/src/app/common/spec.cljc +++ b/common/src/app/common/spec.cljc @@ -140,22 +140,26 @@ ;; --- SPEC: set of Keywords -(s/def ::set-of-keywords - (s/conformer - (fn [s] - (let [xform (comp - (map (fn [s] - (cond - (string? s) (keyword s) - (keyword? s) s - :else nil))) - (filter identity))] - (cond - (set? s) (into #{} xform s) - (string? s) (into #{} xform (str/words s)) - :else ::s/invalid))) - (fn [s] - (str/join " " (map name s))))) +(letfn [(conform-fn [dest s] + (let [xform (keep (fn [s] + (cond + (string? s) (keyword s) + (keyword? s) s + :else nil)))] + (cond + (set? s) (into dest xform s) + (string? s) (into dest xform (str/words s)) + :else ::s/invalid)))] + + (s/def ::set-of-keywords + (s/conformer + (fn [s] (conform-fn #{} s)) + (fn [s] (str/join " " (map name s))))) + + (s/def ::vec-of-keywords + (s/conformer + (fn [s] (conform-fn [] s)) + (fn [s] (str/join " " (map name s)))))) ;; --- SPEC: email diff --git a/frontend/resources/images/icons/brand-gitlab.svg b/frontend/resources/images/icons/brand-gitlab.svg index 8dc42e815..04993577b 100644 --- a/frontend/resources/images/icons/brand-gitlab.svg +++ b/frontend/resources/images/icons/brand-gitlab.svg @@ -1,10 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 13.1072 13.10542" preserveAspectRatio="xMinYMin meet"> - <path d="M6.5534 13.1502l2.41333-7.42742H4.14l2.41334 7.42743z" fill="#e24329"/> - <path d="M6.5534 13.15016L4.14 5.72273H.75783l5.79556 7.42743z" fill="#fc6d26"/> - <path d="M.75783 5.72273L.02446 7.97991a.49964.49964 0 00.18147.5586l6.34746 4.6117L.75777 5.72278z" fill="#fca326"/> - <path d="M.75783 5.72278H4.14L2.68654 1.24927c-.0748-.2302-.40045-.23015-.4752 0L.75783 5.72278z" fill="#e24329"/> - <path d="M6.5534 13.15016l2.41333-7.42743h3.38223l-5.79562 7.42743z" fill="#fc6d26"/> - <path d="M12.34896 5.72273l.73336 2.25718" fill="#fca326"/> - <path d="M12.34896 5.72278H8.96673l1.45351-4.47351c.0748-.2302.40045-.23015.4752 0l1.45352 4.47351z" fill="#e24329"/> - <path d="M12.34937 5.72273l.73337 2.25718a.49964.49964 0 01-.18147.5586l-6.34746 4.6117 5.79561-7.42742z" fill="#fca326"/> -</svg> +<svg viewBox="3658.551 302.026 20 17.949" width="20" height="17.949" xmlns="http://www.w3.org/2000/svg" style="-webkit-print-color-adjust:exact"><path d="m3668.55 319.974 3.685-11.043h-7.364l3.68 11.043ZM3659.71 308.932l-1.122 3.355a.733.733 0 0 0 .277.83l9.685 6.857-8.84-11.042ZM3659.71 308.931h5.16l-2.22-6.65c-.114-.34-.61-.34-.727 0l-2.213 6.65Z" style="fill:#fff"/><path d="m3677.396 308.932 1.118 3.355a.733.733 0 0 1-.276.83l-9.688 6.857 8.846-11.042ZM3677.396 308.931h-5.16l2.216-6.65c.114-.34.61-.34.727 0l2.217 6.65ZM3668.55 319.974l3.685-11.042h5.16l-8.845 11.042ZM3668.55 319.974l-8.84-11.042h5.16l3.68 11.042Z" style="fill:#fff"/></svg> \ No newline at end of file diff --git a/frontend/resources/images/icons/brand-google.svg b/frontend/resources/images/icons/brand-google.svg new file mode 100644 index 000000000..dba95de0f --- /dev/null +++ b/frontend/resources/images/icons/brand-google.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="5345 -1143 500 500"><path fill="#fff" fill-rule="evenodd" d="M5845-887c0-18-1-35-4-51h-240v96h137c-6 32-24 58-51 76v63h82c49-44 76-108 76-184z" clip-rule="evenodd"/><path fill="#fff" fill-rule="evenodd" d="M5601-643c68 0 126-22 168-60l-82-63a156 156 0 0 1-229-79h-85v64c42 82 128 138 228 138z" clip-rule="evenodd"/><path fill="#fff" fill-rule="evenodd" d="M5458-845a148 148 0 0 1 0-95v-65h-85a246 246 0 0 0 0 224z" clip-rule="evenodd"/><path fill="#fff" fill-rule="evenodd" d="M5601-1043c37 0 71 12 97 37l73-72a256 256 0 0 0-399 73l86 65c20-59 76-103 143-103z" clip-rule="evenodd"/></svg> diff --git a/frontend/resources/images/icons/brand-openid.svg b/frontend/resources/images/icons/brand-openid.svg new file mode 100644 index 000000000..a335b6664 --- /dev/null +++ b/frontend/resources/images/icons/brand-openid.svg @@ -0,0 +1 @@ +<svg viewBox="7437 302 20.011 18.182" width="20.011" height="18.182" xmlns="http://www.w3.org/2000/svg" style="-webkit-print-color-adjust:exact"><path d="M7455.039 309.1c-1.9-1.183-4.555-1.918-7.46-1.918-5.845 0-10.579 2.922-10.579 6.526 0 3.3 3.945 6.007 9.055 6.473v-1.9c-3.442-.43-6.024-2.313-6.024-4.573 0-2.564 3.37-4.662 7.549-4.662 2.08 0 3.962.52 5.325 1.363l-1.937 1.202h6.043v-3.73l-1.972 1.22Zm-8.984-5.146v16.227l3.03-1.9V302l-3.03 1.954Z" style="fill:#fff;fill-opacity:1"/></svg> \ No newline at end of file diff --git a/frontend/resources/styles/main/layouts/login.scss b/frontend/resources/styles/main/layouts/login.scss index fa79a3995..574343485 100644 --- a/frontend/resources/styles/main/layouts/login.scss +++ b/frontend/resources/styles/main/layouts/login.scss @@ -72,20 +72,44 @@ width: 412px; .auth-buttons { - margin-top: $size-6; + margin: $size-6 0 $size-4 0; + display: flex; + justify-content: center; + column-gap: 17px; } form { - margin: 2rem 0; + margin: 2rem 0 0.5rem 0; } } + .btn-large { + flex-grow: 1; + font-size: 14px; + font-family: sourcesanspro; + font-style: normal; + font-weight: normal; + } + .btn-google-auth { + background-color: #4285f4; + color: $color-white; margin-bottom: $size-4; text-decoration: none; + .logo { + width: 20px; + height: 20px; + margin-right: 1rem; + } + &:hover { + background-color: #2065d7; + color: $color-white; + } } .btn-gitlab-auth { + background-color: #fc6d26; + color: $color-white; margin-bottom: $size-4; text-decoration: none; @@ -94,9 +118,16 @@ height: 20px; margin-right: 1rem; } + + &:hover { + background-color: #ee5f18; + color: $color-white; + } } .btn-github-auth { + background-color: #4c4c4c; + color: $color-white; margin-bottom: $size-4; text-decoration: none; @@ -105,6 +136,15 @@ height: 20px; margin-right: 1rem; } + + &:hover { + background-color: #2f2f2f; + color: $color-white; + } + } + + .link-oidc { + text-align: center; } .separator { @@ -112,6 +152,18 @@ justify-content: center; width: 100%; text-transform: uppercase; + text-align: center; + + .text { + margin: 0 10px; + color: $color-gray-40; + } + + .line { + border: 1px solid $color-gray-10; + flex-grow: 10; + margin: auto; + } } .links { diff --git a/frontend/src/app/config.cljs b/frontend/src/app/config.cljs index 58b03b299..456f163a7 100644 --- a/frontend/src/app/config.cljs +++ b/frontend/src/app/config.cljs @@ -86,6 +86,9 @@ (def browser (atom (parse-browser))) (def platform (atom (parse-platform))) +(def terms-of-service-uri (obj/get global "penpotTermsOfServiceURI" nil)) +(def privacy-policy-uri (obj/get global "penpotPrivacyPolicyURI" nil)) + ;; maintain for backward compatibility (let [login-with-ldap (obj/get global "penpotLoginWithLDAP" false) registration (obj/get global "penpotRegistrationEnabled" true)] @@ -135,5 +138,3 @@ (str (cond-> (u/join public-uri "assets/by-file-media-id/") (true? thumbnail?) (u/join (str id "/thumbnail")) (false? thumbnail?) (u/join (str id)))))) - - diff --git a/frontend/src/app/main/ui/auth.cljs b/frontend/src/app/main/ui/auth.cljs index 9c79ed2dd..3b00adacb 100644 --- a/frontend/src/app/main/ui/auth.cljs +++ b/frontend/src/app/main/ui/auth.cljs @@ -6,6 +6,7 @@ (ns app.main.ui.auth (:require + [app.config :as cf] [app.main.ui.auth.login :refer [login-page]] [app.main.ui.auth.recovery :refer [recovery-page]] [app.main.ui.auth.recovery-request :refer [recovery-request-page]] @@ -15,6 +16,23 @@ [app.util.i18n :as i18n :refer [tr]] [rumext.alpha :as mf])) +(mf/defc terms-login + [] + (let [show-all? (and cf/terms-of-service-uri cf/privacy-policy-uri) + show-terms? (some? cf/terms-of-service-uri) + show-privacy? (some? cf/privacy-policy-uri)] + + (when show-all? + [:div.terms-login + (when show-terms? + [:a {:href cf/terms-of-service-uri :target "_blank"} "Terms of service"]) + + (when show-all? + [:span "and"]) + + (when show-privacy? + [:a {:href cf/privacy-policy-uri :target "_blank"} "Privacy policy"])]))) + (mf/defc auth [{:keys [route] :as props}] (let [section (get-in route [:data :name]) @@ -48,7 +66,5 @@ :auth-recovery [:& recovery-page {:params params}]) - [:div.terms-login - [:a {:href "https://penpot.app/terms.html" :target "_blank"} "Terms of service"] - [:span "and"] - [:a {:href "https://penpot.app/privacy.html" :target "_blank"} "Privacy policy"]]]])) + [:& terms-login {}]]])) + diff --git a/frontend/src/app/main/ui/auth/login.cljs b/frontend/src/app/main/ui/auth/login.cljs index 936d5ebb0..41e2c7e71 100644 --- a/frontend/src/app/main/ui/auth/login.cljs +++ b/frontend/src/app/main/ui/auth/login.cljs @@ -97,7 +97,6 @@ (login-with-ldap event (with-meta params {:on-error on-error :on-success on-succes})))))] - [:* (when-let [message @error] [:& msgs/inline-banner @@ -123,9 +122,10 @@ :label (tr "auth.password")}]] [:div.buttons-stack - [:& fm/submit-button - {:label (tr "auth.login-submit") - :data-test "login-submit"}] + (when (contains? @cf/flags :login) + [:& fm/submit-button + {:label (tr "auth.login-submit") + :data-test "login-submit"}]) (when (contains? @cf/flags :login-with-ldap) [:& fm/submit-button @@ -136,50 +136,69 @@ [{:keys [params] :as props}] [:div.auth-buttons (when cf/google-client-id - [:a.btn-ocean.btn-large.btn-google-auth + [:a.btn-primary.btn-large.btn-google-auth {:on-click #(login-with-oauth % :google params)} + [:span.logo i/brand-google] (tr "auth.login-with-google-submit")]) - (when cf/gitlab-client-id - [:a.btn-ocean.btn-large.btn-gitlab-auth - {:on-click #(login-with-oauth % :gitlab params)} - [:img.logo - {:src "/images/icons/brand-gitlab.svg"}] - (tr "auth.login-with-gitlab-submit")]) - (when cf/github-client-id - [:a.btn-ocean.btn-large.btn-github-auth + [:a.btn-primary.btn-large.btn-github-auth {:on-click #(login-with-oauth % :github params)} - [:img.logo - {:src "/images/icons/brand-github.svg"}] + [:span.logo i/brand-github] (tr "auth.login-with-github-submit")]) + (when cf/gitlab-client-id + [:a.btn-primary.btn-large.btn-gitlab-auth + {:on-click #(login-with-oauth % :gitlab params)} + [:span.logo i/brand-gitlab] + (tr "auth.login-with-gitlab-submit")]) + (when cf/oidc-client-id - [:a.btn-ocean.btn-large.btn-github-auth + [:a.btn-primary.btn-large.btn-github-auth {:on-click #(login-with-oauth % :oidc params)} + [:span.logo i/brand-openid] (tr "auth.login-with-oidc-submit")])]) +(mf/defc login-button-oidc + [{:keys [params] :as props}] + (when cf/oidc-client-id + [:div.link-entry.link-oidc + [:a {:on-click #(login-with-oauth % :oidc params)} + (tr "auth.login-with-oidc-submit")]])) + (mf/defc login-page [{:keys [params] :as props}] [:div.generic-form.login-form [:div.form-container [:h1 {:data-test "login-title"} (tr "auth.login-title")] - [:div.subtitle (tr "auth.login-subtitle")] - - [:& login-form {:params params}] (when show-alt-login-buttons? [:* - [:span.separator (tr "labels.or")] + [:span.separator + [:span.line] + [:span.text (tr "labels.continue-with")] + [:span.line]] [:div.buttons - [:& login-buttons {:params params}]]]) + [:& login-buttons {:params params}]] + + (when (or (contains? @cf/flags :login) + (contains? @cf/flags :login-with-ldap)) + [:span.separator + [:span.line] + [:span.text (tr "labels.or")] + [:span.line]])]) + + (when (or (contains? @cf/flags :login) + (contains? @cf/flags :login-with-ldap)) + [:& login-form {:params params}]) [:div.links - [:div.link-entry - [:a {:on-click #(st/emit! (rt/nav :auth-recovery-request)) - :data-test "forgot-password"} - (tr "auth.forgot-password")]] + (when (contains? @cf/flags :login) + [:div.link-entry + [:a {:on-click #(st/emit! (rt/nav :auth-recovery-request)) + :data-test "forgot-password"} + (tr "auth.forgot-password")]]) (when (contains? @cf/flags :registration) [:div.link-entry diff --git a/frontend/src/app/main/ui/auth/register.cljs b/frontend/src/app/main/ui/auth/register.cljs index 84ed3de24..9cb0ed293 100644 --- a/frontend/src/app/main/ui/auth/register.cljs +++ b/frontend/src/app/main/ui/auth/register.cljs @@ -121,15 +121,25 @@ (when (contains? @cf/flags :demo-warning) [:& demo-warning]) + (when login/show-alt-login-buttons? + [:* + [:span.separator + [:span.line] + [:span.text (tr "labels.continue-with")] + [:span.line]] + + [:div.buttons + [:& login/login-buttons {:params params}]] + + (when (or (contains? @cf/flags :login) + (contains? @cf/flags :login-with-ldap)) + [:span.separator + [:span.line] + [:span.text (tr "labels.or")] + [:span.line]])]) + [:& register-form {:params params}] - (when login/show-alt-login-buttons? - [:* - [:span.separator (tr "labels.or")] - - [:div.buttons - [:& login/login-buttons {:params params}]]]) - [:div.links [:div.link-entry [:span (tr "auth.already-have-account") " "] diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index db8a4ead5..d6ff5f0da 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -170,6 +170,10 @@ (def uppercase (icon-xref :uppercase)) (def user (icon-xref :user)) +(def brand-openid (icon-xref :brand-openid)) +(def brand-github (icon-xref :brand-github)) +(def brand-gitlab (icon-xref :brand-gitlab)) +(def brand-google (icon-xref :brand-google)) (def loader-pencil (mf/html diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index e042e8d6d..c1af242ae 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -56,10 +56,6 @@ msgstr "تسجيل الدخول هنا" msgid "auth.login-submit" msgstr "تسجيل الدخول" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "أدخل التفاصيل أدناه" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" msgstr "سعيد برؤيتك مجددا!" diff --git a/frontend/translations/ca.po b/frontend/translations/ca.po index af65d41fe..c960744f7 100644 --- a/frontend/translations/ca.po +++ b/frontend/translations/ca.po @@ -59,10 +59,6 @@ msgstr "Inicieu la sessió aquí" msgid "auth.login-submit" msgstr "Entra" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "Introduïu les vostres dades a continuació" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" msgstr "Ens agrada tornar a veure-vos!" diff --git a/frontend/translations/da.po b/frontend/translations/da.po index 16ebcb64d..9f09c8dbd 100644 --- a/frontend/translations/da.po +++ b/frontend/translations/da.po @@ -59,10 +59,6 @@ msgstr "Log på her" msgid "auth.login-submit" msgstr "Log på" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "Indtast dine oplysninger nedenunder" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" msgstr "Fedt at se dig igen!" diff --git a/frontend/translations/de.po b/frontend/translations/de.po index beafaf131..d2dd6c2b9 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -59,10 +59,6 @@ msgstr "Hier einloggen" msgid "auth.login-submit" msgstr "Anmelden" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "Geben Sie unten Ihre Daten ein" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" msgstr "Schön, Sie wiederzusehen!" diff --git a/frontend/translations/el.po b/frontend/translations/el.po index f3100b1c4..582c5da77 100644 --- a/frontend/translations/el.po +++ b/frontend/translations/el.po @@ -54,10 +54,6 @@ msgstr "Συνδεθείτε εδώ" msgid "auth.login-submit" msgstr "Συνδεθείτε" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "Εισαγάγετε τα στοιχεία σας παρακάτω" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" msgstr "Χαίρομαι που σας ξαναδώ" diff --git a/frontend/translations/en.po b/frontend/translations/en.po index bcd036717..6eacdf3a1 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -57,25 +57,21 @@ msgstr "Login here" msgid "auth.login-submit" msgstr "Login" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "Enter your details below" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" msgstr "Great to see you again!" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" -msgstr "Login with GitHub" +msgstr "GitHub" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-gitlab-submit" -msgstr "Login with Gitlab" +msgstr "Gitlab" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-google-submit" -msgstr "Login with Google" +msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" @@ -83,7 +79,7 @@ msgstr "Login with LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "Login with OpenID (SSO)" +msgstr "OpenID Connect" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -1231,6 +1227,9 @@ msgstr "Old password" msgid "labels.only-yours" msgstr "Only yours" +msgid "labels.continue-with" +msgstr "Continue with" + msgid "labels.or" msgstr "or" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index f557a462b..963881642 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -59,25 +59,21 @@ msgstr "Entra aquí" msgid "auth.login-submit" msgstr "Entrar" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "Introduce tus datos aquí" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" msgstr "¡Un placer verte de nuevo!" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" -msgstr "Entrar con Github" +msgstr "Github" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-gitlab-submit" -msgstr "Entrar con Gitlab" +msgstr "Gitlab" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-google-submit" -msgstr "Entrar con Google" +msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" @@ -85,7 +81,7 @@ msgstr "Entrar con LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "Entrar con OpenID (SSO)" +msgstr "OpenID Connect" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -1232,6 +1228,9 @@ msgstr "Contraseña anterior" msgid "labels.only-yours" msgstr "Sólo los tuyos" +msgid "labels.continue-with" +msgstr "Continúa con" + msgid "labels.or" msgstr "o" diff --git a/frontend/translations/fr.po b/frontend/translations/fr.po index 69fc42465..88c306d11 100644 --- a/frontend/translations/fr.po +++ b/frontend/translations/fr.po @@ -59,10 +59,6 @@ msgstr "Se connecter ici" msgid "auth.login-submit" msgstr "Se connecter" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "Entrez vos informations ci‑dessous" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" msgstr "Ravi de vous revoir !" diff --git a/frontend/translations/he.po b/frontend/translations/he.po index 5d3be3b37..2aa0f8b37 100644 --- a/frontend/translations/he.po +++ b/frontend/translations/he.po @@ -56,10 +56,6 @@ msgstr "כניסה מכאן" msgid "auth.login-submit" msgstr "כניסה" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "נא למלא את הפרטים שלך להלן" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" msgstr "שמחים לראות אותך שוב!" diff --git a/frontend/translations/id.po b/frontend/translations/id.po index f9380e177..adc798da5 100644 --- a/frontend/translations/id.po +++ b/frontend/translations/id.po @@ -63,10 +63,6 @@ msgstr "Masuk disini" msgid "auth.login-submit" msgstr "Masuk" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "Masukkan detail anda di bawah ini" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" msgstr "Senang bertemu denganmu lagi!" diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index cd0442519..54a936db9 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -59,10 +59,6 @@ msgstr "Entrar aqui" msgid "auth.login-submit" msgstr "Entrar" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "Insira seus dados abaixo" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" msgstr "Bom te ver de novo!" diff --git a/frontend/translations/ro.po b/frontend/translations/ro.po index 26c1f7e93..14f72441b 100644 --- a/frontend/translations/ro.po +++ b/frontend/translations/ro.po @@ -60,10 +60,6 @@ msgstr "Conectează-te" msgid "auth.login-submit" msgstr "Intră în cont" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "Introduceți detaliile dvs. mai jos" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" msgstr "Mă bucur să te văd din nou!" diff --git a/frontend/translations/ru.po b/frontend/translations/ru.po index c8c118ca5..dac4374a0 100644 --- a/frontend/translations/ru.po +++ b/frontend/translations/ru.po @@ -48,10 +48,6 @@ msgstr "Войти здесь" msgid "auth.login-submit" msgstr "Вход" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "Введите информацию о себе ниже" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" msgstr "Рады видеть Вас снова!" diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po index bb1120a81..33a992d25 100644 --- a/frontend/translations/tr.po +++ b/frontend/translations/tr.po @@ -59,10 +59,6 @@ msgstr "Buradan oturum açın" msgid "auth.login-submit" msgstr "Oturum aç" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "Bilgilerini aşağıdaki alana gir" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" msgstr "Seni tekrar görmek süper!" diff --git a/frontend/translations/zh_CN.po b/frontend/translations/zh_CN.po index 43cfecabd..b29c3026b 100644 --- a/frontend/translations/zh_CN.po +++ b/frontend/translations/zh_CN.po @@ -55,10 +55,6 @@ msgstr "在这里登录" msgid "auth.login-submit" msgstr "登录" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "请在下面输入你的详细信息" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" msgstr "很高兴又见到你!" diff --git a/frontend/translations/zh_Hant.po b/frontend/translations/zh_Hant.po index dcfd8e9bc..9627b07e1 100644 --- a/frontend/translations/zh_Hant.po +++ b/frontend/translations/zh_Hant.po @@ -55,10 +55,6 @@ msgstr "在此登入" msgid "auth.login-submit" msgstr "登入" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "在下方輸入您的詳細資訊" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" msgstr "很高興再次見到你!"