mirror of
https://github.com/penpot/penpot.git
synced 2025-05-17 06:46:10 +02:00
♻️ Refactor LDAP auth backend.
And reorganize oauth backend namespaces.
This commit is contained in:
parent
299b29b66f
commit
de394a7d4e
26 changed files with 288 additions and 310 deletions
|
@ -15,14 +15,15 @@
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
|
- Add more improvements to french translation strings [#591](https://github.com/penpot/penpot/pull/591)
|
||||||
- Add some missing database indexes (mainly improves performance on large databases on file-update rpc method, and some background tasks).
|
- Add some missing database indexes (mainly improves performance on large databases on file-update rpc method, and some background tasks).
|
||||||
|
- Fix corner cases on invitation/signup flows.
|
||||||
- Fix problem width handoff code generation [Taiga #1204](https://tree.taiga.io/project/penpot/issue/1204)
|
- Fix problem width handoff code generation [Taiga #1204](https://tree.taiga.io/project/penpot/issue/1204)
|
||||||
- Fix problem with indices refreshing on page changes [#646](https://github.com/penpot/penpot/issues/646)
|
- Fix problem with indices refreshing on page changes [#646](https://github.com/penpot/penpot/issues/646)
|
||||||
- Have language change notification written in the new language [Taiga #1205](https://tree.taiga.io/project/penpot/issue/1205)
|
- Have language change notification written in the new language [Taiga #1205](https://tree.taiga.io/project/penpot/issue/1205)
|
||||||
- Properly handle errors on github, gitlab and ldap auth backends.
|
- Properly handle errors on github, gitlab and ldap auth backends.
|
||||||
- Properly mark profile auth backend (on first register/ auth with 3rd party auth provider).
|
- Properly mark profile auth backend (on first register/ auth with 3rd party auth provider).
|
||||||
- Fix corner cases on invitation/signup flows.
|
- Refactor LDAP auth backend.
|
||||||
- Add more improvements to french translation strings [#591](https://github.com/penpot/penpot/pull/591)
|
|
||||||
|
|
||||||
|
|
||||||
### :heart: Community contributions by (Thank you!)
|
### :heart: Community contributions by (Thank you!)
|
||||||
|
|
|
@ -70,20 +70,11 @@
|
||||||
:telemetry-enabled false
|
:telemetry-enabled false
|
||||||
:telemetry-uri "https://telemetry.penpot.app/"
|
:telemetry-uri "https://telemetry.penpot.app/"
|
||||||
|
|
||||||
;; LDAP auth disabled by default. Set ldap-auth-host to enable
|
:ldap-user-query "(|(uid=$username)(mail=$username))"
|
||||||
;:ldap-auth-host "ldap.mysupercompany.com"
|
:ldap-attrs-username "uid"
|
||||||
;:ldap-auth-port 389
|
:ldap-attrs-email "mail"
|
||||||
;:ldap-bind-dn "cn=admin,dc=ldap,dc=mysupercompany,dc=com"
|
:ldap-attrs-fullname "cn"
|
||||||
;:ldap-bind-password "verysecure"
|
:ldap-attrs-photo "jpegPhoto"
|
||||||
;:ldap-auth-ssl false
|
|
||||||
;:ldap-auth-starttls false
|
|
||||||
;:ldap-auth-base-dn "ou=People,dc=ldap,dc=mysupercompany,dc=com"
|
|
||||||
|
|
||||||
:ldap-auth-user-query "(|(uid=$username)(mail=$username))"
|
|
||||||
:ldap-auth-username-attribute "uid"
|
|
||||||
:ldap-auth-email-attribute "mail"
|
|
||||||
:ldap-auth-fullname-attribute "displayName"
|
|
||||||
:ldap-auth-avatar-attribute "jpegPhoto"
|
|
||||||
|
|
||||||
;; :initial-data-file "resources/initial-data.json"
|
;; :initial-data-file "resources/initial-data.json"
|
||||||
;; :initial-data-project-name "Penpot Oboarding"
|
;; :initial-data-project-name "Penpot Oboarding"
|
||||||
|
@ -152,18 +143,18 @@
|
||||||
(s/def ::github-client-id ::us/string)
|
(s/def ::github-client-id ::us/string)
|
||||||
(s/def ::github-client-secret ::us/string)
|
(s/def ::github-client-secret ::us/string)
|
||||||
|
|
||||||
(s/def ::ldap-auth-host ::us/string)
|
(s/def ::ldap-host ::us/string)
|
||||||
(s/def ::ldap-auth-port ::us/integer)
|
(s/def ::ldap-port ::us/integer)
|
||||||
(s/def ::ldap-bind-dn ::us/string)
|
(s/def ::ldap-bind-dn ::us/string)
|
||||||
(s/def ::ldap-bind-password ::us/string)
|
(s/def ::ldap-bind-password ::us/string)
|
||||||
(s/def ::ldap-auth-ssl ::us/boolean)
|
(s/def ::ldap-ssl ::us/boolean)
|
||||||
(s/def ::ldap-auth-starttls ::us/boolean)
|
(s/def ::ldap-starttls ::us/boolean)
|
||||||
(s/def ::ldap-auth-base-dn ::us/string)
|
(s/def ::ldap-base-dn ::us/string)
|
||||||
(s/def ::ldap-auth-user-query ::us/string)
|
(s/def ::ldap-user-query ::us/string)
|
||||||
(s/def ::ldap-auth-username-attribute ::us/string)
|
(s/def ::ldap-attrs-username ::us/string)
|
||||||
(s/def ::ldap-auth-email-attribute ::us/string)
|
(s/def ::ldap-attrs-email ::us/string)
|
||||||
(s/def ::ldap-auth-fullname-attribute ::us/string)
|
(s/def ::ldap-attrs-fullname ::us/string)
|
||||||
(s/def ::ldap-auth-avatar-attribute ::us/string)
|
(s/def ::ldap-attrs-photo ::us/string)
|
||||||
|
|
||||||
(s/def ::telemetry-enabled ::us/boolean)
|
(s/def ::telemetry-enabled ::us/boolean)
|
||||||
(s/def ::telemetry-with-taiga ::us/boolean)
|
(s/def ::telemetry-with-taiga ::us/boolean)
|
||||||
|
@ -195,18 +186,18 @@
|
||||||
::google-client-secret
|
::google-client-secret
|
||||||
::http-server-port
|
::http-server-port
|
||||||
::host
|
::host
|
||||||
::ldap-auth-avatar-attribute
|
::ldap-attrs-username
|
||||||
::ldap-auth-base-dn
|
::ldap-attrs-email
|
||||||
::ldap-auth-email-attribute
|
::ldap-attrs-fullname
|
||||||
::ldap-auth-fullname-attribute
|
::ldap-attrs-photo
|
||||||
::ldap-auth-host
|
|
||||||
::ldap-auth-port
|
|
||||||
::ldap-auth-ssl
|
|
||||||
::ldap-auth-starttls
|
|
||||||
::ldap-auth-user-query
|
|
||||||
::ldap-auth-username-attribute
|
|
||||||
::ldap-bind-dn
|
::ldap-bind-dn
|
||||||
::ldap-bind-password
|
::ldap-bind-password
|
||||||
|
::ldap-base-dn
|
||||||
|
::ldap-host
|
||||||
|
::ldap-port
|
||||||
|
::ldap-ssl
|
||||||
|
::ldap-starttls
|
||||||
|
::ldap-user-query
|
||||||
::public-uri
|
::public-uri
|
||||||
::profile-complaint-threshold
|
::profile-complaint-threshold
|
||||||
::profile-bounce-threshold
|
::profile-bounce-threshold
|
||||||
|
|
|
@ -80,14 +80,12 @@
|
||||||
(s/def ::rpc map?)
|
(s/def ::rpc map?)
|
||||||
(s/def ::session map?)
|
(s/def ::session map?)
|
||||||
(s/def ::metrics map?)
|
(s/def ::metrics map?)
|
||||||
(s/def ::google-auth map?)
|
(s/def ::oauth map?)
|
||||||
(s/def ::gitlab-auth map?)
|
|
||||||
(s/def ::ldap-auth fn?)
|
|
||||||
(s/def ::storage map?)
|
(s/def ::storage map?)
|
||||||
(s/def ::assets map?)
|
(s/def ::assets map?)
|
||||||
|
|
||||||
(defmethod ig/pre-init-spec ::router [_]
|
(defmethod ig/pre-init-spec ::router [_]
|
||||||
(s/keys :req-un [::rpc ::session ::metrics ::google-auth ::gitlab-auth ::storage ::assets]))
|
(s/keys :req-un [::rpc ::session ::metrics ::oauth ::storage ::assets]))
|
||||||
|
|
||||||
(defmethod ig/init-key ::router
|
(defmethod ig/init-key ::router
|
||||||
[_ cfg]
|
[_ cfg]
|
||||||
|
@ -113,7 +111,7 @@
|
||||||
:body "internal server error"})))))))
|
:body "internal server error"})))))))
|
||||||
|
|
||||||
(defn- create-router
|
(defn- create-router
|
||||||
[{:keys [session rpc google-auth gitlab-auth github-auth metrics ldap-auth svgparse assets] :as cfg}]
|
[{:keys [session rpc oauth metrics svgparse assets] :as cfg}]
|
||||||
(rr/router
|
(rr/router
|
||||||
[["/metrics" {:get (:handler metrics)}]
|
[["/metrics" {:get (:handler metrics)}]
|
||||||
|
|
||||||
|
@ -140,16 +138,14 @@
|
||||||
["/svg" {:post svgparse}]
|
["/svg" {:post svgparse}]
|
||||||
|
|
||||||
["/oauth"
|
["/oauth"
|
||||||
["/google" {:post (:auth-handler google-auth)}]
|
["/google" {:post (get-in oauth [:google :handler])}]
|
||||||
["/google/callback" {:get (:callback-handler google-auth)}]
|
["/google/callback" {:get (get-in oauth [:google :callback-handler])}]
|
||||||
|
|
||||||
["/gitlab" {:post (:auth-handler gitlab-auth)}]
|
["/gitlab" {:post (get-in oauth [:gitlab :handler])}]
|
||||||
["/gitlab/callback" {:get (:callback-handler gitlab-auth)}]
|
["/gitlab/callback" {:get (get-in oauth [:gitlab :callback-handler])}]
|
||||||
|
|
||||||
["/github" {:post (:auth-handler github-auth)}]
|
["/github" {:post (get-in oauth [:github :handler])}]
|
||||||
["/github/callback" {:get (:callback-handler github-auth)}]]
|
["/github/callback" {:get (get-in oauth [:github :callback-handler])}]]
|
||||||
|
|
||||||
["/login-ldap" {:post ldap-auth}]
|
|
||||||
|
|
||||||
["/rpc" {:middleware [(:middleware session)]}
|
["/rpc" {:middleware [(:middleware session)]}
|
||||||
["/query/:type" {:get (:query-handler rpc)}]
|
["/query/:type" {:get (:query-handler rpc)}]
|
||||||
|
|
|
@ -1,127 +0,0 @@
|
||||||
;; 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/.
|
|
||||||
;;
|
|
||||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
|
||||||
;; defined by the Mozilla Public License, v. 2.0.
|
|
||||||
;;
|
|
||||||
;; Copyright (c) 2020 UXBOX Labs SL
|
|
||||||
|
|
||||||
(ns app.http.auth.ldap
|
|
||||||
(:require
|
|
||||||
[app.common.exceptions :as ex]
|
|
||||||
[app.config :as cfg]
|
|
||||||
[clj-ldap.client :as client]
|
|
||||||
[clojure.set :as set]
|
|
||||||
[clojure.spec.alpha :as s]
|
|
||||||
[clojure.string ]
|
|
||||||
[clojure.tools.logging :as log]
|
|
||||||
[integrant.core :as ig]))
|
|
||||||
|
|
||||||
(declare authenticate)
|
|
||||||
(declare create-connection)
|
|
||||||
(declare replace-several)
|
|
||||||
|
|
||||||
|
|
||||||
(s/def ::host ::cfg/ldap-auth-host)
|
|
||||||
(s/def ::port ::cfg/ldap-auth-port)
|
|
||||||
(s/def ::ssl ::cfg/ldap-auth-ssl)
|
|
||||||
(s/def ::starttls ::cfg/ldap-auth-starttls)
|
|
||||||
(s/def ::user-query ::cfg/ldap-auth-user-query)
|
|
||||||
(s/def ::base-dn ::cfg/ldap-auth-base-dn)
|
|
||||||
(s/def ::username-attribute ::cfg/ldap-auth-username-attribute)
|
|
||||||
(s/def ::email-attribute ::cfg/ldap-auth-email-attribute)
|
|
||||||
(s/def ::fullname-attribute ::cfg/ldap-auth-fullname-attribute)
|
|
||||||
(s/def ::avatar-attribute ::cfg/ldap-auth-avatar-attribute)
|
|
||||||
|
|
||||||
(s/def ::rpc map?)
|
|
||||||
(s/def ::session map?)
|
|
||||||
|
|
||||||
(defmethod ig/pre-init-spec :app.http.auth/ldap
|
|
||||||
[_]
|
|
||||||
(s/keys
|
|
||||||
:req-un [::rpc ::session]
|
|
||||||
:opt-un [::host
|
|
||||||
::port
|
|
||||||
::ssl
|
|
||||||
::starttls
|
|
||||||
::username-attribute
|
|
||||||
::base-dn
|
|
||||||
::username-attribute
|
|
||||||
::email-attribute
|
|
||||||
::fullname-attribute
|
|
||||||
::avatar-attribute]))
|
|
||||||
|
|
||||||
(defmethod ig/init-key :app.http.auth/ldap
|
|
||||||
[_ {:keys [session rpc] :as cfg}]
|
|
||||||
(let [conn (create-connection cfg)]
|
|
||||||
(with-meta
|
|
||||||
(fn [request]
|
|
||||||
(let [data (:body-params request)]
|
|
||||||
(when-some [info (authenticate (assoc cfg
|
|
||||||
:conn conn
|
|
||||||
:username (:email data)
|
|
||||||
:password (:password data)))]
|
|
||||||
(let [method-fn (get-in rpc [:methods :mutation :login-or-register])
|
|
||||||
profile (method-fn {:email (:email info)
|
|
||||||
:backend "ldap"
|
|
||||||
:fullname (:fullname info)})
|
|
||||||
|
|
||||||
sxf ((:create session) (:id profile))
|
|
||||||
rsp {:status 200 :body profile}]
|
|
||||||
(sxf request rsp)))))
|
|
||||||
{::conn conn})))
|
|
||||||
|
|
||||||
(defmethod ig/halt-key! ::client
|
|
||||||
[_ handler]
|
|
||||||
(let [{:keys [::conn]} (meta handler)]
|
|
||||||
(when (realized? conn)
|
|
||||||
(.close @conn))))
|
|
||||||
|
|
||||||
(defn- replace-several [s & {:as replacements}]
|
|
||||||
(reduce-kv clojure.string/replace s replacements))
|
|
||||||
|
|
||||||
(defn- create-connection
|
|
||||||
[cfg]
|
|
||||||
(let [params (merge {:host {:address (:host cfg)
|
|
||||||
:port (:port cfg)}}
|
|
||||||
(-> cfg
|
|
||||||
(select-keys [:ssl
|
|
||||||
:starttls
|
|
||||||
:ldap-bind-dn
|
|
||||||
:ldap-bind-password])
|
|
||||||
(set/rename-keys {:ssl :ssl?
|
|
||||||
:starttls :startTLS?
|
|
||||||
:ldap-bind-dn :bind-dn
|
|
||||||
:ldap-bind-password :password})))]
|
|
||||||
(delay
|
|
||||||
(try
|
|
||||||
(client/connect params)
|
|
||||||
(catch Exception e
|
|
||||||
(log/errorf e "Cannot connect to LDAP %s:%s"
|
|
||||||
(:host cfg) (:port cfg)))))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn- authenticate
|
|
||||||
[{:keys [conn username password] :as cfg}]
|
|
||||||
(when-some [conn (some-> conn deref)]
|
|
||||||
(let [user-search-query (replace-several (:user-query cfg) "$username" username)
|
|
||||||
user-attributes (-> cfg
|
|
||||||
(select-keys [:username-attribute
|
|
||||||
:email-attribute
|
|
||||||
:fullname-attribute
|
|
||||||
:avatar-attribute])
|
|
||||||
vals)]
|
|
||||||
(when-some [user-entry (-> conn
|
|
||||||
(client/search (:base-dn cfg)
|
|
||||||
{:filter user-search-query
|
|
||||||
:sizelimit 1
|
|
||||||
:attributes user-attributes})
|
|
||||||
(first))]
|
|
||||||
(when-not (client/bind? conn (:dn user-entry) password)
|
|
||||||
(ex/raise :type :authentication
|
|
||||||
:code :wrong-credentials))
|
|
||||||
(set/rename-keys user-entry {(keyword (:avatar-attribute cfg)) :photo
|
|
||||||
(keyword (:fullname-attribute cfg)) :fullname
|
|
||||||
(keyword (:email-attribute cfg)) :email})))))
|
|
||||||
|
|
|
@ -46,6 +46,11 @@
|
||||||
[err _]
|
[err _]
|
||||||
{:status 401 :body (ex-data err)})
|
{:status 401 :body (ex-data err)})
|
||||||
|
|
||||||
|
|
||||||
|
(defmethod handle-exception :restriction
|
||||||
|
[err _]
|
||||||
|
{:status 400 :body (ex-data err)})
|
||||||
|
|
||||||
(defmethod handle-exception :validation
|
(defmethod handle-exception :validation
|
||||||
[err req]
|
[err req]
|
||||||
(let [header (get-in req [:headers "accept"])
|
(let [header (get-in req [:headers "accept"])
|
||||||
|
|
|
@ -7,12 +7,12 @@
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2020-2021 UXBOX Labs SL
|
;; Copyright (c) 2020-2021 UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.http.auth.github
|
(ns app.http.oauth.github
|
||||||
(:require
|
(:require
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.config :as cfg]
|
[app.config :as cfg]
|
||||||
[app.http.auth.google :as gg]
|
[app.http.oauth.google :as gg]
|
||||||
[app.util.http :as http]
|
[app.util.http :as http]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
[clojure.data.json :as json]
|
[clojure.data.json :as json]
|
||||||
|
@ -137,7 +137,7 @@
|
||||||
(s/def ::session map?)
|
(s/def ::session map?)
|
||||||
(s/def ::tokens fn?)
|
(s/def ::tokens fn?)
|
||||||
|
|
||||||
(defmethod ig/pre-init-spec :app.http.auth/github [_]
|
(defmethod ig/pre-init-spec :app.http.oauth/github [_]
|
||||||
(s/keys :req-un [::public-uri
|
(s/keys :req-un [::public-uri
|
||||||
::session
|
::session
|
||||||
::tokens]
|
::tokens]
|
||||||
|
@ -148,12 +148,12 @@
|
||||||
[_]
|
[_]
|
||||||
(ex/raise :type :not-found))
|
(ex/raise :type :not-found))
|
||||||
|
|
||||||
(defmethod ig/init-key :app.http.auth/github
|
(defmethod ig/init-key :app.http.oauth/github
|
||||||
[_ cfg]
|
[_ cfg]
|
||||||
(if (and (:client-id cfg)
|
(if (and (:client-id cfg)
|
||||||
(:client-secret cfg))
|
(:client-secret cfg))
|
||||||
{:auth-handler #(auth-handler cfg %)
|
{:handler #(auth-handler cfg %)
|
||||||
:callback-handler #(callback-handler cfg %)}
|
:callback-handler #(callback-handler cfg %)}
|
||||||
{:auth-handler default-handler
|
{:handler default-handler
|
||||||
:callback-handler default-handler}))
|
:callback-handler default-handler}))
|
||||||
|
|
|
@ -7,12 +7,12 @@
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2020 UXBOX Labs SL
|
;; Copyright (c) 2020 UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.http.auth.gitlab
|
(ns app.http.oauth.gitlab
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.http.auth.google :as gg]
|
[app.http.oauth.google :as gg]
|
||||||
[app.util.http :as http]
|
[app.util.http :as http]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
[clojure.data.json :as json]
|
[clojure.data.json :as json]
|
||||||
|
@ -140,7 +140,7 @@
|
||||||
(s/def ::session map?)
|
(s/def ::session map?)
|
||||||
(s/def ::tokens fn?)
|
(s/def ::tokens fn?)
|
||||||
|
|
||||||
(defmethod ig/pre-init-spec :app.http.auth/gitlab [_]
|
(defmethod ig/pre-init-spec :app.http.oauth/gitlab [_]
|
||||||
(s/keys :req-un [::public-uri
|
(s/keys :req-un [::public-uri
|
||||||
::session
|
::session
|
||||||
::tokens]
|
::tokens]
|
||||||
|
@ -148,8 +148,7 @@
|
||||||
::client-id
|
::client-id
|
||||||
::client-secret]))
|
::client-secret]))
|
||||||
|
|
||||||
|
(defmethod ig/prep-key :app.http.oauth/gitlab
|
||||||
(defmethod ig/prep-key :app.http.auth/gitlab
|
|
||||||
[_ cfg]
|
[_ cfg]
|
||||||
(d/merge {:base-uri "https://gitlab.com"}
|
(d/merge {:base-uri "https://gitlab.com"}
|
||||||
(d/without-nils cfg)))
|
(d/without-nils cfg)))
|
||||||
|
@ -158,11 +157,11 @@
|
||||||
[_]
|
[_]
|
||||||
(ex/raise :type :not-found))
|
(ex/raise :type :not-found))
|
||||||
|
|
||||||
(defmethod ig/init-key :app.http.auth/gitlab
|
(defmethod ig/init-key :app.http.oauth/gitlab
|
||||||
[_ cfg]
|
[_ cfg]
|
||||||
(if (and (:client-id cfg)
|
(if (and (:client-id cfg)
|
||||||
(:client-secret cfg))
|
(:client-secret cfg))
|
||||||
{:auth-handler #(auth-handler cfg %)
|
{:handler #(auth-handler cfg %)
|
||||||
:callback-handler #(callback-handler cfg %)}
|
:callback-handler #(callback-handler cfg %)}
|
||||||
{:auth-handler default-handler
|
{:handler default-handler
|
||||||
:callback-handler default-handler}))
|
:callback-handler default-handler}))
|
|
@ -7,7 +7,7 @@
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2020-2021 UXBOX Labs SL
|
;; Copyright (c) 2020-2021 UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.http.auth.google
|
(ns app.http.oauth.google
|
||||||
(:require
|
(:require
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
|
@ -50,7 +50,6 @@
|
||||||
(when (= 200 (:status res))
|
(when (= 200 (:status res))
|
||||||
(-> (json/read-str (:body res))
|
(-> (json/read-str (:body res))
|
||||||
(get "access_token"))))
|
(get "access_token"))))
|
||||||
|
|
||||||
(catch Exception e
|
(catch Exception e
|
||||||
(log/error e "unexpected error on get-access-token")
|
(log/error e "unexpected error on get-access-token")
|
||||||
nil)))
|
nil)))
|
||||||
|
@ -60,8 +59,10 @@
|
||||||
(try
|
(try
|
||||||
(let [req {:uri "https://openidconnect.googleapis.com/v1/userinfo"
|
(let [req {:uri "https://openidconnect.googleapis.com/v1/userinfo"
|
||||||
:headers {"Authorization" (str "Bearer " token)}
|
:headers {"Authorization" (str "Bearer " token)}
|
||||||
|
:timeout 6000
|
||||||
:method :get}
|
:method :get}
|
||||||
res (http/send! req)]
|
res (http/send! req)]
|
||||||
|
|
||||||
(when (= 200 (:status res))
|
(when (= 200 (:status res))
|
||||||
(let [data (json/read-str (:body res))]
|
(let [data (json/read-str (:body res))]
|
||||||
{:email (get data "email")
|
{:email (get data "email")
|
||||||
|
@ -78,6 +79,8 @@
|
||||||
info (some->> (get-in request [:params :code])
|
info (some->> (get-in request [:params :code])
|
||||||
(get-access-token cfg)
|
(get-access-token cfg)
|
||||||
(get-user-info cfg))]
|
(get-user-info cfg))]
|
||||||
|
|
||||||
|
|
||||||
(when-not info
|
(when-not info
|
||||||
(ex/raise :type :internal
|
(ex/raise :type :internal
|
||||||
:code :unable-to-auth))
|
:code :unable-to-auth))
|
||||||
|
@ -158,7 +161,7 @@
|
||||||
(s/def ::session map?)
|
(s/def ::session map?)
|
||||||
(s/def ::tokens fn?)
|
(s/def ::tokens fn?)
|
||||||
|
|
||||||
(defmethod ig/pre-init-spec :app.http.auth/google [_]
|
(defmethod ig/pre-init-spec :app.http.oauth/google [_]
|
||||||
(s/keys :req-un [::public-uri
|
(s/keys :req-un [::public-uri
|
||||||
::session
|
::session
|
||||||
::tokens]
|
::tokens]
|
||||||
|
@ -169,11 +172,11 @@
|
||||||
[_]
|
[_]
|
||||||
(ex/raise :type :not-found))
|
(ex/raise :type :not-found))
|
||||||
|
|
||||||
(defmethod ig/init-key :app.http.auth/google
|
(defmethod ig/init-key :app.http.oauth/google
|
||||||
[_ cfg]
|
[_ cfg]
|
||||||
(if (and (:client-id cfg)
|
(if (and (:client-id cfg)
|
||||||
(:client-secret cfg))
|
(:client-secret cfg))
|
||||||
{:auth-handler #(auth-handler cfg %)
|
{:handler #(auth-handler cfg %)
|
||||||
:callback-handler #(callback-handler cfg %)}
|
:callback-handler #(callback-handler cfg %)}
|
||||||
{:auth-handler default-handler
|
{:handler default-handler
|
||||||
:callback-handler default-handler}))
|
:callback-handler default-handler}))
|
|
@ -95,7 +95,7 @@
|
||||||
(= k :profile-id) (assoc acc k (uuid/uuid v))
|
(= k :profile-id) (assoc acc k (uuid/uuid v))
|
||||||
(str/blank? v) acc
|
(str/blank? v) acc
|
||||||
:else (assoc acc k v)))
|
:else (assoc acc k v)))
|
||||||
{}
|
{:id (uuid/next)}
|
||||||
(:context event)))
|
(:context event)))
|
||||||
|
|
||||||
(defn- parse-event
|
(defn- parse-event
|
||||||
|
|
|
@ -87,10 +87,7 @@
|
||||||
:tokens (ig/ref :app.tokens/tokens)
|
:tokens (ig/ref :app.tokens/tokens)
|
||||||
:public-uri (:public-uri config)
|
:public-uri (:public-uri config)
|
||||||
:metrics (ig/ref :app.metrics/metrics)
|
:metrics (ig/ref :app.metrics/metrics)
|
||||||
:google-auth (ig/ref :app.http.auth/google)
|
:oauth (ig/ref :app.http.oauth/all)
|
||||||
:gitlab-auth (ig/ref :app.http.auth/gitlab)
|
|
||||||
:github-auth (ig/ref :app.http.auth/github)
|
|
||||||
:ldap-auth (ig/ref :app.http.auth/ldap)
|
|
||||||
:assets (ig/ref :app.http.assets/handlers)
|
:assets (ig/ref :app.http.assets/handlers)
|
||||||
:svgparse (ig/ref :app.svgparse/handler)
|
:svgparse (ig/ref :app.svgparse/handler)
|
||||||
:storage (ig/ref :app.storage/storage)
|
:storage (ig/ref :app.storage/storage)
|
||||||
|
@ -104,7 +101,12 @@
|
||||||
:cache-max-age (dt/duration {:hours 24})
|
:cache-max-age (dt/duration {:hours 24})
|
||||||
:signature-max-age (dt/duration {:hours 24 :minutes 5})}
|
:signature-max-age (dt/duration {:hours 24 :minutes 5})}
|
||||||
|
|
||||||
:app.http.auth/google
|
:app.http.oauth/all
|
||||||
|
{:google (ig/ref :app.http.oauth/google)
|
||||||
|
:gitlab (ig/ref :app.http.oauth/gitlab)
|
||||||
|
:github (ig/ref :app.http.oauth/github)}
|
||||||
|
|
||||||
|
:app.http.oauth/google
|
||||||
{:rpc (ig/ref :app.rpc/rpc)
|
{:rpc (ig/ref :app.rpc/rpc)
|
||||||
:session (ig/ref :app.http.session/session)
|
:session (ig/ref :app.http.session/session)
|
||||||
:tokens (ig/ref :app.tokens/tokens)
|
:tokens (ig/ref :app.tokens/tokens)
|
||||||
|
@ -112,7 +114,7 @@
|
||||||
:client-id (:google-client-id config)
|
:client-id (:google-client-id config)
|
||||||
:client-secret (:google-client-secret config)}
|
:client-secret (:google-client-secret config)}
|
||||||
|
|
||||||
:app.http.auth/github
|
:app.http.oauth/github
|
||||||
{:rpc (ig/ref :app.rpc/rpc)
|
{:rpc (ig/ref :app.rpc/rpc)
|
||||||
:session (ig/ref :app.http.session/session)
|
:session (ig/ref :app.http.session/session)
|
||||||
:tokens (ig/ref :app.tokens/tokens)
|
:tokens (ig/ref :app.tokens/tokens)
|
||||||
|
@ -120,7 +122,7 @@
|
||||||
:client-id (:github-client-id config)
|
:client-id (:github-client-id config)
|
||||||
:client-secret (:github-client-secret config)}
|
:client-secret (:github-client-secret config)}
|
||||||
|
|
||||||
:app.http.auth/gitlab
|
:app.http.oauth/gitlab
|
||||||
{:rpc (ig/ref :app.rpc/rpc)
|
{:rpc (ig/ref :app.rpc/rpc)
|
||||||
:session (ig/ref :app.http.session/session)
|
:session (ig/ref :app.http.session/session)
|
||||||
:tokens (ig/ref :app.tokens/tokens)
|
:tokens (ig/ref :app.tokens/tokens)
|
||||||
|
@ -129,20 +131,6 @@
|
||||||
:client-id (:gitlab-client-id config)
|
:client-id (:gitlab-client-id config)
|
||||||
:client-secret (:gitlab-client-secret config)}
|
:client-secret (:gitlab-client-secret config)}
|
||||||
|
|
||||||
:app.http.auth/ldap
|
|
||||||
{:host (:ldap-auth-host config)
|
|
||||||
:port (:ldap-auth-port config)
|
|
||||||
:ssl (:ldap-auth-ssl config)
|
|
||||||
:starttls (:ldap-auth-starttls config)
|
|
||||||
:user-query (:ldap-auth-user-query config)
|
|
||||||
:username-attribute (:ldap-auth-username-attribute config)
|
|
||||||
:email-attribute (:ldap-auth-email-attribute config)
|
|
||||||
:fullname-attribute (:ldap-auth-fullname-attribute config)
|
|
||||||
:avatar-attribute (:ldap-auth-avatar-attribute config)
|
|
||||||
:base-dn (:ldap-auth-base-dn config)
|
|
||||||
:session (ig/ref :app.http.session/session)
|
|
||||||
:rpc (ig/ref :app.rpc/rpc)}
|
|
||||||
|
|
||||||
:app.svgparse/svgc
|
:app.svgparse/svgc
|
||||||
{:metrics (ig/ref :app.metrics/metrics)}
|
{:metrics (ig/ref :app.metrics/metrics)}
|
||||||
|
|
||||||
|
|
|
@ -127,6 +127,7 @@
|
||||||
'app.rpc.mutations.viewer
|
'app.rpc.mutations.viewer
|
||||||
'app.rpc.mutations.teams
|
'app.rpc.mutations.teams
|
||||||
'app.rpc.mutations.feedback
|
'app.rpc.mutations.feedback
|
||||||
|
'app.rpc.mutations.ldap
|
||||||
'app.rpc.mutations.verify-token)
|
'app.rpc.mutations.verify-token)
|
||||||
(map (partial process-method cfg))
|
(map (partial process-method cfg))
|
||||||
(into {}))))
|
(into {}))))
|
||||||
|
|
|
@ -265,7 +265,7 @@
|
||||||
(assoc params :file file)))))
|
(assoc params :file file)))))
|
||||||
|
|
||||||
(defn- update-file
|
(defn- update-file
|
||||||
[{:keys [conn] :as cfg} {:keys [file changes session-id] :as params}]
|
[{:keys [conn] :as cfg} {:keys [file changes session-id profile-id] :as params}]
|
||||||
(when (> (:revn params)
|
(when (> (:revn params)
|
||||||
(:revn file))
|
(:revn file))
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
|
@ -287,6 +287,7 @@
|
||||||
(db/insert! conn :file-change
|
(db/insert! conn :file-change
|
||||||
{:id (uuid/next)
|
{:id (uuid/next)
|
||||||
:session-id session-id
|
:session-id session-id
|
||||||
|
:profile-id profile-id
|
||||||
:file-id (:id file)
|
:file-id (:id file)
|
||||||
:revn (:revn file)
|
:revn (:revn file)
|
||||||
:data (:data file)
|
:data (:data file)
|
||||||
|
|
105
backend/src/app/rpc/mutations/ldap.clj
Normal file
105
backend/src/app/rpc/mutations/ldap.clj
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
;; 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/.
|
||||||
|
;;
|
||||||
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) 2020-2021 UXBOX Labs SL
|
||||||
|
|
||||||
|
(ns app.rpc.mutations.ldap
|
||||||
|
(:require
|
||||||
|
[app.common.exceptions :as ex]
|
||||||
|
[app.common.spec :as us]
|
||||||
|
[app.config :as cfg]
|
||||||
|
[app.rpc.mutations.profile :refer [login-or-register]]
|
||||||
|
[app.util.services :as sv]
|
||||||
|
[clj-ldap.client :as ldap]
|
||||||
|
[clojure.spec.alpha :as s]
|
||||||
|
[clojure.string]
|
||||||
|
[clojure.tools.logging :as log]))
|
||||||
|
|
||||||
|
(def cpool
|
||||||
|
(delay
|
||||||
|
(let [params {:ssl? (cfg/get :ldap-ssl)
|
||||||
|
:startTLS? (cfg/get :ldap-starttls)
|
||||||
|
:bind-dn (cfg/get :ldap-bind-dn)
|
||||||
|
:password (cfg/get :ldap-bind-password)
|
||||||
|
:host {:address (cfg/get :ldap-host)
|
||||||
|
:port (cfg/get :ldap-port)}}]
|
||||||
|
(try
|
||||||
|
(ldap/connect params)
|
||||||
|
(catch Exception e
|
||||||
|
(log/errorf e "Cannot connect to LDAP %s:%s"
|
||||||
|
(get-in params [:host :address])
|
||||||
|
(get-in params [:host :port])))))))
|
||||||
|
|
||||||
|
;; --- Mutation: login-with-ldap
|
||||||
|
|
||||||
|
(declare authenticate)
|
||||||
|
|
||||||
|
(s/def ::email ::us/email)
|
||||||
|
(s/def ::password ::us/string)
|
||||||
|
(s/def ::invitation-token ::us/string)
|
||||||
|
|
||||||
|
(s/def ::login-with-ldap
|
||||||
|
(s/keys :req-un [::email ::password]
|
||||||
|
:opt-un [::invitation-token]))
|
||||||
|
|
||||||
|
(sv/defmethod ::login-with-ldap {:auth false :rlimit :password}
|
||||||
|
[{:keys [pool session tokens] :as cfg} {:keys [email password invitation-token] :as params}]
|
||||||
|
(when-not @cpool
|
||||||
|
(ex/raise :type :restriction
|
||||||
|
:code :ldap-disabled
|
||||||
|
:hint "ldap disabled or unable to connect"))
|
||||||
|
|
||||||
|
(let [info (authenticate @cpool params)
|
||||||
|
cfg (assoc cfg :conn pool)]
|
||||||
|
(when-not info
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :wrong-credentials))
|
||||||
|
(let [profile (login-or-register cfg {:email (:email info)
|
||||||
|
:backend (:backend info)
|
||||||
|
:fullname (:fullname info)})]
|
||||||
|
(if-let [token (:invitation-token params)]
|
||||||
|
;; 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 session as logged).
|
||||||
|
(let [claims (tokens :verify {:token token :iss :team-invitation})
|
||||||
|
claims (assoc claims
|
||||||
|
:member-id (:id profile)
|
||||||
|
:member-email (:email profile))
|
||||||
|
token (tokens :generate claims)]
|
||||||
|
(with-meta
|
||||||
|
{:invitation-token token}
|
||||||
|
{:transform-response ((:create session) (:id profile))}))
|
||||||
|
|
||||||
|
(with-meta profile
|
||||||
|
{:transform-response ((:create session) (:id profile))})))))
|
||||||
|
|
||||||
|
(defn- replace-several [s & {:as replacements}]
|
||||||
|
(reduce-kv clojure.string/replace s replacements))
|
||||||
|
|
||||||
|
(defn- get-ldap-user
|
||||||
|
[cpool {:keys [email] :as params}]
|
||||||
|
(let [query (-> (cfg/get :ldap-user-query)
|
||||||
|
(replace-several "$username" email))
|
||||||
|
|
||||||
|
attrs [(cfg/get :ldap-attrs-username)
|
||||||
|
(cfg/get :ldap-attrs-email)
|
||||||
|
(cfg/get :ldap-attrs-photo)
|
||||||
|
(cfg/get :ldap-attrs-fullname)]
|
||||||
|
|
||||||
|
base-dn (cfg/get :ldap-base-dn)
|
||||||
|
params {:filter query :sizelimit 1 :attributes attrs}]
|
||||||
|
(first (ldap/search cpool base-dn params))))
|
||||||
|
|
||||||
|
(defn- authenticate
|
||||||
|
[cpool {:keys [password] :as params}]
|
||||||
|
(when-let [{:keys [dn] :as luser} (get-ldap-user cpool params)]
|
||||||
|
(when (ldap/bind? cpool dn password)
|
||||||
|
{:photo (get luser (keyword (cfg/get :ldap-attrs-photo)))
|
||||||
|
:fullname (get luser (keyword (cfg/get :ldap-attrs-fullname)))
|
||||||
|
:email (get luser (keyword (cfg/get :ldap-attrs-email)))
|
||||||
|
:backend "ldap"})))
|
|
@ -204,10 +204,10 @@
|
||||||
|
|
||||||
(s/def ::login
|
(s/def ::login
|
||||||
(s/keys :req-un [::email ::password]
|
(s/keys :req-un [::email ::password]
|
||||||
:opt-un [::scope]))
|
:opt-un [::scope ::invitation-token]))
|
||||||
|
|
||||||
(sv/defmethod ::login {:auth false :rlimit :password}
|
(sv/defmethod ::login {:auth false :rlimit :password}
|
||||||
[{:keys [pool session] :as cfg} {:keys [email password scope] :as params}]
|
[{:keys [pool session tokens] :as cfg} {:keys [email password scope] :as params}]
|
||||||
(letfn [(check-password [profile password]
|
(letfn [(check-password [profile password]
|
||||||
(when (= (:password profile) "!")
|
(when (= (:password profile) "!")
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
|
@ -227,14 +227,27 @@
|
||||||
profile)]
|
profile)]
|
||||||
|
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(let [prof (-> (profile/retrieve-profile-data-by-email conn email)
|
(let [profile (-> (profile/retrieve-profile-data-by-email conn email)
|
||||||
(validate-profile)
|
(validate-profile)
|
||||||
(profile/strip-private-attrs))
|
(profile/strip-private-attrs))
|
||||||
addt (profile/retrieve-additional-data conn (:id prof))
|
profile (merge profile (profile/retrieve-additional-data conn (:id profile)))]
|
||||||
prof (merge prof addt)]
|
(if-let [token (:invitation-token params)]
|
||||||
(with-meta prof
|
;; If the request comes with an invitation token, this means
|
||||||
{:transform-response ((:create session) (:id prof))})))))
|
;; that user wants to accept it with different user. A very
|
||||||
|
;; strange case but still can happen. In this case, we
|
||||||
|
;; proceed in the same way as in register: regenerate the
|
||||||
|
;; invitation token and return it to the user for proper
|
||||||
|
;; invitation acceptation.
|
||||||
|
(let [claims (tokens :verify {:token token :iss :team-invitation})
|
||||||
|
claims (assoc claims
|
||||||
|
:member-id (:id profile)
|
||||||
|
:member-email (:email profile))
|
||||||
|
token (tokens :generate claims)]
|
||||||
|
(with-meta {:invitation-token token}
|
||||||
|
{:transform-response ((:create session) (:id profile))}))
|
||||||
|
|
||||||
|
(with-meta profile
|
||||||
|
{:transform-response ((:create session) (:id profile))}))))))
|
||||||
|
|
||||||
;; --- Mutation: Logout
|
;; --- Mutation: Logout
|
||||||
|
|
||||||
|
@ -249,12 +262,20 @@
|
||||||
|
|
||||||
;; --- Mutation: Register if not exists
|
;; --- Mutation: Register if not exists
|
||||||
|
|
||||||
|
(declare login-or-register)
|
||||||
|
|
||||||
(s/def ::backend ::us/string)
|
(s/def ::backend ::us/string)
|
||||||
(s/def ::login-or-register
|
(s/def ::login-or-register
|
||||||
(s/keys :req-un [::email ::fullname ::backend]))
|
(s/keys :req-un [::email ::fullname ::backend]))
|
||||||
|
|
||||||
(sv/defmethod ::login-or-register {:auth false}
|
(sv/defmethod ::login-or-register {:auth false}
|
||||||
[{:keys [pool] :as cfg} {:keys [email backend fullname] :as params}]
|
[{:keys [pool] :as cfg} params]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(-> (assoc cfg :conn conn)
|
||||||
|
(login-or-register params))))
|
||||||
|
|
||||||
|
(defn login-or-register
|
||||||
|
[{:keys [conn] :as cfg} {:keys [email backend] :as params}]
|
||||||
(letfn [(populate-additional-data [conn profile]
|
(letfn [(populate-additional-data [conn profile]
|
||||||
(let [data (profile/retrieve-additional-data conn (:id profile))]
|
(let [data (profile/retrieve-additional-data conn (:id profile))]
|
||||||
(merge profile data)))
|
(merge profile data)))
|
||||||
|
@ -275,12 +296,11 @@
|
||||||
(create-profile-initial-data conn profile)
|
(create-profile-initial-data conn profile)
|
||||||
profile))]
|
profile))]
|
||||||
|
|
||||||
(db/with-atomic [conn pool]
|
|
||||||
(let [profile (profile/retrieve-profile-data-by-email conn email)
|
(let [profile (profile/retrieve-profile-data-by-email conn email)
|
||||||
profile (if profile
|
profile (if profile
|
||||||
(populate-additional-data conn profile)
|
(populate-additional-data conn profile)
|
||||||
(register-profile conn params))]
|
(register-profile conn params))]
|
||||||
(profile/strip-private-attrs profile)))))
|
(profile/strip-private-attrs profile))))
|
||||||
|
|
||||||
|
|
||||||
;; --- Mutation: Update Profile (own)
|
;; --- Mutation: Update Profile (own)
|
||||||
|
|
|
@ -303,7 +303,7 @@
|
||||||
team (db/get-by-id conn :team team-id)
|
team (db/get-by-id conn :team team-id)
|
||||||
itoken (tokens :generate
|
itoken (tokens :generate
|
||||||
{:iss :team-invitation
|
{:iss :team-invitation
|
||||||
:exp (dt/in-future "24h")
|
:exp (dt/in-future "6h")
|
||||||
:profile-id (:id profile)
|
:profile-id (:id profile)
|
||||||
:role role
|
:role role
|
||||||
:team-id team-id
|
:team-id team-id
|
||||||
|
|
|
@ -115,7 +115,7 @@
|
||||||
;; If the current session is already matches the invited
|
;; If the current session is already matches the invited
|
||||||
;; member, then just return the token and leave the frontend
|
;; member, then just return the token and leave the frontend
|
||||||
;; app redirect to correct team.
|
;; app redirect to correct team.
|
||||||
(assoc claims :status :created)
|
(assoc claims :state :created)
|
||||||
|
|
||||||
;; If the session does not matches the invited member, replace
|
;; If the session does not matches the invited member, replace
|
||||||
;; the session with a new one matching the invited member.
|
;; the session with a new one matching the invited member.
|
||||||
|
@ -123,7 +123,7 @@
|
||||||
;; user clicking the link he already has access to the email
|
;; user clicking the link he already has access to the email
|
||||||
;; account.
|
;; account.
|
||||||
(with-meta
|
(with-meta
|
||||||
(assoc claims :status :created)
|
(assoc claims :state :created)
|
||||||
{:transform-response ((:create session) member-id)})))
|
{:transform-response ((:create session) member-id)})))
|
||||||
|
|
||||||
;; This happens when member-id is not filled in the invitation but
|
;; This happens when member-id is not filled in the invitation but
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
::spec sname
|
::spec sname
|
||||||
::name (name sname))
|
::name (name sname))
|
||||||
|
|
||||||
sym (symbol (str "service-method-" (name sname)))]
|
sym (symbol (str "sm$" (name sname)))]
|
||||||
`(do
|
`(do
|
||||||
(def ~sym (fn ~args ~@body))
|
(def ~sym (fn ~args ~@body))
|
||||||
(reset-meta! (var ~sym) ~mdata))))
|
(reset-meta! (var ~sym) ~mdata))))
|
||||||
|
|
|
@ -53,6 +53,7 @@ services:
|
||||||
- PENPOT_SMTP_PASSWORD=
|
- PENPOT_SMTP_PASSWORD=
|
||||||
- PENPOT_SMTP_SSL=false
|
- PENPOT_SMTP_SSL=false
|
||||||
- PENPOT_SMTP_TLS=false
|
- PENPOT_SMTP_TLS=false
|
||||||
|
|
||||||
# LDAP setup
|
# LDAP setup
|
||||||
- PENPOT_LDAP_HOST=ldap
|
- PENPOT_LDAP_HOST=ldap
|
||||||
- PENPOT_LDAP_PORT=10389
|
- PENPOT_LDAP_PORT=10389
|
||||||
|
@ -61,10 +62,10 @@ services:
|
||||||
- PENPOT_LDAP_BASE_DN=ou=people,dc=planetexpress,dc=com
|
- PENPOT_LDAP_BASE_DN=ou=people,dc=planetexpress,dc=com
|
||||||
- PENPOT_LDAP_BIND_DN=cn=admin,dc=planetexpress,dc=com
|
- PENPOT_LDAP_BIND_DN=cn=admin,dc=planetexpress,dc=com
|
||||||
- PENPOT_LDAP_BIND_PASSWORD=GoodNewsEveryone
|
- PENPOT_LDAP_BIND_PASSWORD=GoodNewsEveryone
|
||||||
- PENPOT_LDAP_USERNAME_ATTRIBUTE=uid
|
- PENPOT_LDAP_ATTRS_USERNAME=uid
|
||||||
- PENPOT_LDAP_EMAIL_ATTRIBUTE=mail
|
- PENPOT_LDAP_ATTRS_EMAIL=mail
|
||||||
- PENPOT_LDAP_FULLNAME_ATTRIBUTE=displayName
|
- PENPOT_LDAP_ATTRS_FULLNAME=cn
|
||||||
- PENPOT_LDAP_AVATAR_ATTRIBUTE=jpegPhoto
|
- PENPOT_LDAP_ATTRS_PHOTO=jpegPhoto
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:13
|
image: postgres:13
|
||||||
|
|
|
@ -773,7 +773,13 @@
|
||||||
"es" : "Actualizado: %s"
|
"es" : "Actualizado: %s"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"errors.auth.unauthorized" : {
|
"errors.ldap-disabled" : {
|
||||||
|
"translations" : {
|
||||||
|
"en" : "LDAP authentication is disabled.",
|
||||||
|
"es" : "La autheticacion via LDAP esta deshabilitada."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"errors.wrong-credentials" : {
|
||||||
"used-in" : [ "src/app/main/ui/auth/login.cljs" ],
|
"used-in" : [ "src/app/main/ui/auth/login.cljs" ],
|
||||||
"translations" : {
|
"translations" : {
|
||||||
"en" : "Username or password seems to be wrong.",
|
"en" : "Username or password seems to be wrong.",
|
||||||
|
|
|
@ -1160,7 +1160,10 @@ input[type=range]:focus::-ms-fill-upper {
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
padding: $small;
|
padding: $small;
|
||||||
width: 40px;
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
@ -1169,6 +1172,7 @@ input[type=range]:focus::-ms-fill-upper {
|
||||||
font-size: $fs14;
|
font-size: $fs14;
|
||||||
padding: $small;
|
padding: $small;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1227,7 +1231,6 @@ input[type=range]:focus::-ms-fill-upper {
|
||||||
|
|
||||||
&.inline {
|
&.inline {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: $big;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,30 +79,6 @@
|
||||||
(watch [this state s]
|
(watch [this state s]
|
||||||
(rx/of (logged-in profile)))))
|
(rx/of (logged-in profile)))))
|
||||||
|
|
||||||
(defn login-with-ldap
|
|
||||||
[{:keys [email password] :as data}]
|
|
||||||
(us/verify ::login-params data)
|
|
||||||
(ptk/reify ::login-with-ldap
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(merge state (dissoc initial-state :route :router)))
|
|
||||||
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [this state s]
|
|
||||||
(let [{:keys [on-error on-success]
|
|
||||||
:or {on-error identity
|
|
||||||
on-success identity}} (meta data)
|
|
||||||
params {:email email
|
|
||||||
:password password
|
|
||||||
:scope "webapp"}]
|
|
||||||
(->> (rx/timer 100)
|
|
||||||
(rx/mapcat #(rp/mutation :login-with-ldap params))
|
|
||||||
(rx/tap on-success)
|
|
||||||
(rx/catch (fn [err]
|
|
||||||
(on-error err)
|
|
||||||
(rx/empty)))
|
|
||||||
(rx/map logged-in))))))
|
|
||||||
|
|
||||||
;; --- Logout
|
;; --- Logout
|
||||||
|
|
||||||
(def clear-user-data
|
(def clear-user-data
|
||||||
|
@ -131,10 +107,11 @@
|
||||||
|
|
||||||
;; --- Register
|
;; --- Register
|
||||||
|
|
||||||
|
(s/def ::invitation-token ::us/not-empty-string)
|
||||||
|
|
||||||
(s/def ::register
|
(s/def ::register
|
||||||
(s/keys :req-un [::fullname
|
(s/keys :req-un [::fullname ::password ::email]
|
||||||
::password
|
:opt-un [::invitation-token]))
|
||||||
::email]))
|
|
||||||
|
|
||||||
(defn register
|
(defn register
|
||||||
"Create a register event instance."
|
"Create a register event instance."
|
||||||
|
|
|
@ -122,11 +122,5 @@
|
||||||
(seq params))
|
(seq params))
|
||||||
(send-mutation! id form)))
|
(send-mutation! id form)))
|
||||||
|
|
||||||
(defmethod mutation :login-with-ldap
|
|
||||||
[id params]
|
|
||||||
(let [uri (str cfg/public-uri "/api/login-ldap")]
|
|
||||||
(->> (http/send! {:method :post :uri uri :body params})
|
|
||||||
(rx/mapcat handle-response))))
|
|
||||||
|
|
||||||
(def client-error? http/client-error?)
|
(def client-error? http/client-error?)
|
||||||
(def server-error? http/server-error?)
|
(def server-error? http/server-error?)
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
[:& register-success-page {:params params}]
|
[:& register-success-page {:params params}]
|
||||||
|
|
||||||
:auth-login
|
:auth-login
|
||||||
[:& login-page {:locale locale :params params}]
|
[:& login-page {:params params}]
|
||||||
|
|
||||||
:auth-recovery-request
|
:auth-recovery-request
|
||||||
[:& recovery-request-page {:locale locale}]
|
[:& recovery-request-page {:locale locale}]
|
||||||
|
|
|
@ -55,21 +55,40 @@
|
||||||
(rx/subs (fn [{:keys [redirect-uri] :as rsp}]
|
(rx/subs (fn [{:keys [redirect-uri] :as rsp}]
|
||||||
(.replace js/location redirect-uri)))))
|
(.replace js/location redirect-uri)))))
|
||||||
|
|
||||||
|
(defn- login-with-ldap
|
||||||
|
[event params]
|
||||||
|
(dom/prevent-default event)
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(let [{:keys [on-error]} (meta params)]
|
||||||
|
(->> (rp/mutation! :login-with-ldap params)
|
||||||
|
(rx/subs (fn [profile]
|
||||||
|
(if-let [token (:invitation-token profile)]
|
||||||
|
(st/emit! (rt/nav :auth-verify-token {} {:token token}))
|
||||||
|
(st/emit! (da/logged-in profile))))
|
||||||
|
(fn [{:keys [type code] :as error}]
|
||||||
|
(cond
|
||||||
|
(and (= type :restriction)
|
||||||
|
(= code :ldap-disabled))
|
||||||
|
(st/emit! (dm/error (tr "errors.ldap-disabled")))
|
||||||
|
|
||||||
|
(fn? on-error)
|
||||||
|
(on-error error)))))))
|
||||||
|
|
||||||
(mf/defc login-form
|
(mf/defc login-form
|
||||||
[]
|
[{:keys [params] :as props}]
|
||||||
(let [error? (mf/use-state false)
|
(let [error (mf/use-state false)
|
||||||
form (fm/use-form :spec ::login-form
|
form (fm/use-form :spec ::login-form
|
||||||
:inital {})
|
:inital {})
|
||||||
|
|
||||||
on-error
|
on-error
|
||||||
(fn [form event]
|
(fn [_]
|
||||||
(reset! error? true))
|
(reset! error (tr "errors.wrong-credentials")))
|
||||||
|
|
||||||
on-submit
|
on-submit
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(mf/deps form)
|
(mf/deps form)
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(reset! error? false)
|
(reset! error nil)
|
||||||
(let [params (with-meta (:clean-data @form)
|
(let [params (with-meta (:clean-data @form)
|
||||||
{:on-error on-error})]
|
{:on-error on-error})]
|
||||||
(st/emit! (da/login params)))))
|
(st/emit! (da/login params)))))
|
||||||
|
@ -78,17 +97,15 @@
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(mf/deps form)
|
(mf/deps form)
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(reset! error? false)
|
(let [params (merge (:clean-data @form) params)]
|
||||||
(let [params (with-meta (:clean-data @form)
|
(login-with-ldap event (with-meta params {:on-error on-error})))))]
|
||||||
{:on-error on-error})]
|
|
||||||
(st/emit! (da/login-with-ldap params)))))]
|
|
||||||
|
|
||||||
[:*
|
[:*
|
||||||
(when @error?
|
(when-let [message @error]
|
||||||
[:& msgs/inline-banner
|
[:& msgs/inline-banner
|
||||||
{:type :warning
|
{:type :warning
|
||||||
:content (tr "errors.auth.unauthorized")
|
:content message
|
||||||
:on-close #(reset! error? false)}])
|
:on-close #(reset! error nil)}])
|
||||||
|
|
||||||
[:& fm/form {:on-submit on-submit :form form}
|
[:& fm/form {:on-submit on-submit :form form}
|
||||||
[:div.fields-row
|
[:div.fields-row
|
||||||
|
@ -114,13 +131,13 @@
|
||||||
:on-click on-submit-ldap}])]]))
|
:on-click on-submit-ldap}])]]))
|
||||||
|
|
||||||
(mf/defc login-page
|
(mf/defc login-page
|
||||||
[]
|
[{:keys [params] :as props}]
|
||||||
[:div.generic-form.login-form
|
[:div.generic-form.login-form
|
||||||
[:div.form-container
|
[:div.form-container
|
||||||
[:h1 (tr "auth.login-title")]
|
[:h1 (tr "auth.login-title")]
|
||||||
[:div.subtitle (tr "auth.login-subtitle")]
|
[:div.subtitle (tr "auth.login-subtitle")]
|
||||||
|
|
||||||
[:& login-form {}]
|
[:& login-form {:params params}]
|
||||||
|
|
||||||
[:div.links
|
[:div.links
|
||||||
[:div.link-entry
|
[:div.link-entry
|
||||||
|
@ -130,25 +147,25 @@
|
||||||
|
|
||||||
[:div.link-entry
|
[:div.link-entry
|
||||||
[:span (tr "auth.register") " "]
|
[:span (tr "auth.register") " "]
|
||||||
[:a {:on-click #(st/emit! (rt/nav :auth-register))
|
[:a {:on-click #(st/emit! (rt/nav :auth-register {} params))
|
||||||
:tab-index "6"}
|
:tab-index "6"}
|
||||||
(tr "auth.register-submit")]]]
|
(tr "auth.register-submit")]]]
|
||||||
|
|
||||||
(when cfg/google-client-id
|
(when cfg/google-client-id
|
||||||
[:a.btn-ocean.btn-large.btn-google-auth
|
[:a.btn-ocean.btn-large.btn-google-auth
|
||||||
{:on-click login-with-google}
|
{:on-click #(login-with-google % params)}
|
||||||
"Login with Google"])
|
"Login with Google"])
|
||||||
|
|
||||||
(when cfg/gitlab-client-id
|
(when cfg/gitlab-client-id
|
||||||
[:a.btn-ocean.btn-large.btn-gitlab-auth
|
[:a.btn-ocean.btn-large.btn-gitlab-auth
|
||||||
{:on-click login-with-gitlab}
|
{:on-click #(login-with-gitlab % params)}
|
||||||
[:img.logo
|
[:img.logo
|
||||||
{:src "/images/icons/brand-gitlab.svg"}]
|
{:src "/images/icons/brand-gitlab.svg"}]
|
||||||
(tr "auth.login-with-gitlab-submit")])
|
(tr "auth.login-with-gitlab-submit")])
|
||||||
|
|
||||||
(when cfg/github-client-id
|
(when cfg/github-client-id
|
||||||
[:a.btn-ocean.btn-large.btn-github-auth
|
[:a.btn-ocean.btn-large.btn-github-auth
|
||||||
{:on-click login-with-github}
|
{:on-click #(login-with-github % params)}
|
||||||
[:img.logo
|
[:img.logo
|
||||||
{:src "/images/icons/brand-github.svg"}]
|
{:src "/images/icons/brand-github.svg"}]
|
||||||
(tr "auth.login-with-github-submit")])
|
(tr "auth.login-with-github-submit")])
|
||||||
|
|
|
@ -43,13 +43,11 @@
|
||||||
(s/def ::fullname ::us/not-empty-string)
|
(s/def ::fullname ::us/not-empty-string)
|
||||||
(s/def ::password ::us/not-empty-string)
|
(s/def ::password ::us/not-empty-string)
|
||||||
(s/def ::email ::us/email)
|
(s/def ::email ::us/email)
|
||||||
(s/def ::token ::us/not-empty-string)
|
(s/def ::invitation-token ::us/not-empty-string)
|
||||||
|
|
||||||
(s/def ::register-form
|
(s/def ::register-form
|
||||||
(s/keys :req-un [::password
|
(s/keys :req-un [::password ::fullname ::email]
|
||||||
::fullname
|
:opt-un [::invitation-token]))
|
||||||
::email]
|
|
||||||
:opt-un [::token]))
|
|
||||||
|
|
||||||
(mf/defc register-form
|
(mf/defc register-form
|
||||||
[{:keys [params] :as props}]
|
[{:keys [params] :as props}]
|
||||||
|
@ -145,7 +143,7 @@
|
||||||
[:div.links
|
[:div.links
|
||||||
[:div.link-entry
|
[:div.link-entry
|
||||||
[:span (tr "auth.already-have-account") " "]
|
[:span (tr "auth.already-have-account") " "]
|
||||||
[:a {:on-click #(st/emit! (rt/nav :auth-login))
|
[:a {:on-click #(st/emit! (rt/nav :auth-login {} params))
|
||||||
:tab-index "4"}
|
:tab-index "4"}
|
||||||
(tr "auth.login-here")]]
|
(tr "auth.login-here")]]
|
||||||
|
|
||||||
|
|
|
@ -52,10 +52,9 @@
|
||||||
[tdata]
|
[tdata]
|
||||||
(case (:state tdata)
|
(case (:state tdata)
|
||||||
:created
|
:created
|
||||||
(let [message (tr "auth.notifications.team-invitation-accepted")]
|
(st/emit! (dm/success (tr "auth.notifications.team-invitation-accepted"))
|
||||||
(st/emit! (du/fetch-profile)
|
(du/fetch-profile)
|
||||||
(rt/nav :dashboard-projects {:team-id (:team-id tdata)})
|
(rt/nav :dashboard-projects {:team-id (:team-id tdata)}))
|
||||||
(dm/success message)))
|
|
||||||
|
|
||||||
:pending
|
:pending
|
||||||
(let [token (:invitation-token tdata)]
|
(let [token (:invitation-token tdata)]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue