mirror of
https://github.com/penpot/penpot.git
synced 2025-05-14 01:46:39 +02:00
🎉 Add generic oauth2/openid-connect authentication subsystem.
This commit is contained in:
parent
9e5923004f
commit
63b95e71a7
17 changed files with 368 additions and 620 deletions
|
@ -8,7 +8,8 @@
|
||||||
- Allow to group assets (components and graphics) [Taiga #1289](https://tree.taiga.io/project/penpot/us/1289)
|
- Allow to group assets (components and graphics) [Taiga #1289](https://tree.taiga.io/project/penpot/us/1289)
|
||||||
- Internal: refactor of http client, replace internal xhr usage with more modern Fetch API.
|
- Internal: refactor of http client, replace internal xhr usage with more modern Fetch API.
|
||||||
- New features for paths: snap points on edition, add/remove nodes, merge/join/split nodes. [Taiga #907](https://tree.taiga.io/project/penpot/us/907)
|
- New features for paths: snap points on edition, add/remove nodes, merge/join/split nodes. [Taiga #907](https://tree.taiga.io/project/penpot/us/907)
|
||||||
|
- Add OpenID-Connect support.
|
||||||
|
- Reimplement social auth providers on top of the generic openid impl.
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
|
|
|
@ -105,6 +105,12 @@
|
||||||
(s/def ::gitlab-client-secret ::us/string)
|
(s/def ::gitlab-client-secret ::us/string)
|
||||||
(s/def ::google-client-id ::us/string)
|
(s/def ::google-client-id ::us/string)
|
||||||
(s/def ::google-client-secret ::us/string)
|
(s/def ::google-client-secret ::us/string)
|
||||||
|
(s/def ::oidc-client-id ::us/string)
|
||||||
|
(s/def ::oidc-client-secret ::us/string)
|
||||||
|
(s/def ::oidc-base-uri ::us/string)
|
||||||
|
(s/def ::oidc-token-uri ::us/string)
|
||||||
|
(s/def ::oidc-auth-uri ::us/string)
|
||||||
|
(s/def ::oidc-user-uri ::us/string)
|
||||||
(s/def ::host ::us/string)
|
(s/def ::host ::us/string)
|
||||||
(s/def ::http-server-port ::us/integer)
|
(s/def ::http-server-port ::us/integer)
|
||||||
(s/def ::http-session-cookie-name ::us/string)
|
(s/def ::http-session-cookie-name ::us/string)
|
||||||
|
@ -178,6 +184,12 @@
|
||||||
::gitlab-client-secret
|
::gitlab-client-secret
|
||||||
::google-client-id
|
::google-client-id
|
||||||
::google-client-secret
|
::google-client-secret
|
||||||
|
::oidc-client-id
|
||||||
|
::oidc-client-secret
|
||||||
|
::oidc-base-uri
|
||||||
|
::oidc-token-uri
|
||||||
|
::oidc-auth-uri
|
||||||
|
::oidc-user-uri
|
||||||
::host
|
::host
|
||||||
::http-server-port
|
::http-server-port
|
||||||
::http-session-idle-max-age
|
::http-session-idle-max-age
|
||||||
|
|
|
@ -149,15 +149,8 @@
|
||||||
["/feedback" {:middleware [(:middleware session)]
|
["/feedback" {:middleware [(:middleware session)]
|
||||||
:post feedback}]
|
:post feedback}]
|
||||||
|
|
||||||
["/oauth"
|
["/auth/oauth/:provider" {:post (:handler oauth)}]
|
||||||
["/google" {:post (get-in oauth [:google :handler])}]
|
["/auth/oauth/:provider/callback" {:get (:callback-handler oauth)}]
|
||||||
["/google/callback" {:get (get-in oauth [:google :callback-handler])}]
|
|
||||||
|
|
||||||
["/gitlab" {:post (get-in oauth [:gitlab :handler])}]
|
|
||||||
["/gitlab/callback" {:get (get-in oauth [:gitlab :callback-handler])}]
|
|
||||||
|
|
||||||
["/github" {:post (get-in oauth [:github :handler])}]
|
|
||||||
["/github/callback" {:get (get-in oauth [:github :callback-handler])}]]
|
|
||||||
|
|
||||||
["/rpc" {:middleware [(:middleware session)
|
["/rpc" {:middleware [(:middleware session)
|
||||||
middleware/activity-logger]}
|
middleware/activity-logger]}
|
||||||
|
|
278
backend/src/app/http/oauth.clj
Normal file
278
backend/src/app/http/oauth.clj
Normal file
|
@ -0,0 +1,278 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
|
(ns app.http.oauth
|
||||||
|
(:require
|
||||||
|
[app.common.exceptions :as ex]
|
||||||
|
[app.common.spec :as us]
|
||||||
|
[app.config :as cf]
|
||||||
|
[app.util.http :as http]
|
||||||
|
[app.util.logging :as l]
|
||||||
|
[app.util.time :as dt]
|
||||||
|
[clojure.data.json :as json]
|
||||||
|
[clojure.spec.alpha :as s]
|
||||||
|
[integrant.core :as ig]
|
||||||
|
[lambdaisland.uri :as u]))
|
||||||
|
|
||||||
|
(defn redirect-response
|
||||||
|
[uri]
|
||||||
|
{:status 302
|
||||||
|
:headers {"location" (str uri)}
|
||||||
|
:body ""})
|
||||||
|
|
||||||
|
(defn generate-error-redirect-uri
|
||||||
|
[cfg]
|
||||||
|
(-> (u/uri (:public-uri cfg))
|
||||||
|
(assoc :path "/#/auth/login")
|
||||||
|
(assoc :query (u/map->query-string {:error "unable-to-auth"}))))
|
||||||
|
|
||||||
|
(defn register-profile
|
||||||
|
[{:keys [rpc] :as cfg} info]
|
||||||
|
(let [method-fn (get-in rpc [:methods :mutation :login-or-register])
|
||||||
|
profile (method-fn {:email (:email info)
|
||||||
|
:backend (:backend info)
|
||||||
|
:fullname (:fullname info)})]
|
||||||
|
(cond-> profile
|
||||||
|
(some? (:invitation-token info))
|
||||||
|
(assoc :invitation-token (:invitation-token info)))))
|
||||||
|
|
||||||
|
(defn generate-redirect-uri
|
||||||
|
[{:keys [tokens] :as cfg} profile]
|
||||||
|
(let [token (or (:invitation-token profile)
|
||||||
|
(tokens :generate {:iss :auth
|
||||||
|
:exp (dt/in-future "15m")
|
||||||
|
:profile-id (:id profile)}))]
|
||||||
|
(-> (u/uri (:public-uri cfg))
|
||||||
|
(assoc :path "/#/auth/verify-token")
|
||||||
|
(assoc :query (u/map->query-string {:token token})))))
|
||||||
|
|
||||||
|
(defn- build-redirect-uri
|
||||||
|
[{:keys [provider] :as cfg}]
|
||||||
|
(let [public (u/uri (:public-uri cfg))]
|
||||||
|
(str (assoc public :path (str "/api/auth/oauth/" (:name provider) "/callback")))))
|
||||||
|
|
||||||
|
(defn- build-auth-uri
|
||||||
|
[{:keys [provider] :as cfg} state]
|
||||||
|
(let [params {:client_id (:client-id provider)
|
||||||
|
:redirect_uri (build-redirect-uri cfg)
|
||||||
|
:response_type "code"
|
||||||
|
:state state
|
||||||
|
:scope (:scope provider)}
|
||||||
|
query (u/map->query-string params)]
|
||||||
|
(-> (u/uri (:auth-uri provider))
|
||||||
|
(assoc :query query)
|
||||||
|
(str))))
|
||||||
|
|
||||||
|
(defn retrieve-access-token
|
||||||
|
[{:keys [provider] :as cfg} code]
|
||||||
|
(try
|
||||||
|
(let [params {:client_id (:client-id provider)
|
||||||
|
:client_secret (:client-secret provider)
|
||||||
|
:code code
|
||||||
|
:grant_type "authorization_code"
|
||||||
|
:redirect_uri (build-redirect-uri cfg)}
|
||||||
|
req {:method :post
|
||||||
|
:headers {"content-type" "application/x-www-form-urlencoded"}
|
||||||
|
:uri (:token-uri provider)
|
||||||
|
:body (u/map->query-string params)}
|
||||||
|
res (http/send! req)]
|
||||||
|
(when (= 200 (:status res))
|
||||||
|
(let [data (json/read-str (:body res))]
|
||||||
|
{:token (get data "access_token")
|
||||||
|
:type (get data "token_type")})))
|
||||||
|
(catch Exception e
|
||||||
|
(l/error :hint "unexpected error on retrieve-access-token"
|
||||||
|
:cause e)
|
||||||
|
nil)))
|
||||||
|
|
||||||
|
(defn- retrieve-user-info
|
||||||
|
[{:keys [provider] :as cfg} tdata]
|
||||||
|
(try
|
||||||
|
(let [req {:uri (:user-uri provider)
|
||||||
|
:headers {"Authorization" (str (:type tdata) " " (:token tdata))}
|
||||||
|
:timeout 6000
|
||||||
|
:method :get}
|
||||||
|
res (http/send! req)]
|
||||||
|
|
||||||
|
(when (= 200 (:status res))
|
||||||
|
(let [data (json/read-str (:body res))]
|
||||||
|
{:email (get data "email")
|
||||||
|
:backend (:name provider)
|
||||||
|
:fullname (get data "name")})))
|
||||||
|
|
||||||
|
(catch Exception e
|
||||||
|
(l/error :hint "unexpected exception on retrieve-user-info"
|
||||||
|
:cause e)
|
||||||
|
nil)))
|
||||||
|
|
||||||
|
(defn retrieve-info
|
||||||
|
[{:keys [tokens] :as cfg} request]
|
||||||
|
(let [state (get-in request [:params :state])
|
||||||
|
state (tokens :verify {:token state :iss :oauth})
|
||||||
|
info (some->> (get-in request [:params :code])
|
||||||
|
(retrieve-access-token cfg)
|
||||||
|
(retrieve-user-info cfg))]
|
||||||
|
(when-not info
|
||||||
|
(ex/raise :type :internal
|
||||||
|
:code :unable-to-auth))
|
||||||
|
|
||||||
|
(cond-> info
|
||||||
|
(some? (:invitation-token state))
|
||||||
|
(assoc :invitation-token (:invitation-token state)))))
|
||||||
|
|
||||||
|
;; --- HTTP HANDLERS
|
||||||
|
|
||||||
|
(defn- auth-handler
|
||||||
|
[{:keys [tokens] :as cfg} request]
|
||||||
|
(let [invitation (get-in request [:params :invitation-token])
|
||||||
|
state (tokens :generate
|
||||||
|
{:iss :oauth
|
||||||
|
:invitation-token invitation
|
||||||
|
:exp (dt/in-future "15m")})
|
||||||
|
uri (build-auth-uri cfg state)]
|
||||||
|
{:status 200
|
||||||
|
:body {:redirect-uri uri}}))
|
||||||
|
|
||||||
|
(defn- callback-handler
|
||||||
|
[{:keys [session] :as cfg} request]
|
||||||
|
(try
|
||||||
|
(let [info (retrieve-info cfg request)
|
||||||
|
profile (register-profile cfg info)
|
||||||
|
uri (generate-redirect-uri cfg profile)
|
||||||
|
sxf ((:create session) (:id profile))]
|
||||||
|
(->> (redirect-response uri)
|
||||||
|
(sxf request)))
|
||||||
|
(catch Exception _e
|
||||||
|
(-> (generate-error-redirect-uri cfg)
|
||||||
|
(redirect-response)))))
|
||||||
|
|
||||||
|
;; --- INIT
|
||||||
|
|
||||||
|
(declare initialize)
|
||||||
|
|
||||||
|
(s/def ::public-uri ::us/not-empty-string)
|
||||||
|
(s/def ::session map?)
|
||||||
|
(s/def ::tokens fn?)
|
||||||
|
(s/def ::rpc map?)
|
||||||
|
|
||||||
|
(defmethod ig/pre-init-spec :app.http.oauth/handlers [_]
|
||||||
|
(s/keys :req-un [::public-uri ::session ::tokens ::rpc]))
|
||||||
|
|
||||||
|
(defn wrap-handler
|
||||||
|
[cfg handler]
|
||||||
|
(fn [request]
|
||||||
|
(let [provider (get-in request [:path-params :provider])
|
||||||
|
provider (get-in @cfg [:providers provider])]
|
||||||
|
(when-not provider
|
||||||
|
(ex/raise :type :not-found
|
||||||
|
:context {:provider provider}
|
||||||
|
:hint "provider not configured"))
|
||||||
|
(-> (assoc @cfg :provider provider)
|
||||||
|
(handler request)))))
|
||||||
|
|
||||||
|
(defmethod ig/init-key :app.http.oauth/handlers
|
||||||
|
[_ cfg]
|
||||||
|
(let [cfg (initialize cfg)]
|
||||||
|
{:handler (wrap-handler cfg auth-handler)
|
||||||
|
:callback-handler (wrap-handler cfg callback-handler)}))
|
||||||
|
|
||||||
|
(defn- discover-oidc-config
|
||||||
|
[{:keys [base-uri] :as opts}]
|
||||||
|
(let [discovery-uri (u/join base-uri ".well-known/openid-configuration")
|
||||||
|
response (http/send! {:method :get :uri (str discovery-uri)})]
|
||||||
|
(when (= 200 (:status response))
|
||||||
|
(let [data (json/read-str (:body response))]
|
||||||
|
(assoc opts
|
||||||
|
:token-uri (get data "token_endpoint")
|
||||||
|
:auth-uri (get data "authorization_endpoint")
|
||||||
|
:user-uri (get data "userinfo_endpoint"))))))
|
||||||
|
|
||||||
|
(defn- initialize-oidc-provider
|
||||||
|
[cfg]
|
||||||
|
(let [opts {:base-uri (cf/get :oidc-base-uri)
|
||||||
|
:client-id (cf/get :oidc-client-id)
|
||||||
|
:client-secret (cf/get :oidc-client-secret)
|
||||||
|
:token-uri (cf/get :oidc-token-uri)
|
||||||
|
:auth-uri (cf/get :oidc-auth-uri)
|
||||||
|
:user-uri (cf/get :oidc-user-uri)
|
||||||
|
:scope "openid profile email name"
|
||||||
|
:name "oidc"}]
|
||||||
|
(if (and (string? (:base-uri opts))
|
||||||
|
(string? (:client-id opts))
|
||||||
|
(string? (:client-secret opts)))
|
||||||
|
(if (and (string? (:token-uri opts))
|
||||||
|
(string? (:user-uri opts))
|
||||||
|
(string? (:auth-uri opts)))
|
||||||
|
(do
|
||||||
|
(l/info :action "initialize" :provider "oid" :method "static")
|
||||||
|
(assoc-in cfg [:providers "oidc"] opts))
|
||||||
|
(let [opts (discover-oidc-config opts)]
|
||||||
|
(l/info :action "initialize" :provider "oid" :method "discover")
|
||||||
|
(assoc-in cfg [:providers "oidc"] opts)))
|
||||||
|
cfg)))
|
||||||
|
|
||||||
|
(defn- initialize-google-provider
|
||||||
|
[cfg]
|
||||||
|
(let [opts {:client-id (cf/get :google-client-id)
|
||||||
|
:client-secret (cf/get :google-client-secret)
|
||||||
|
:scope (str "email profile "
|
||||||
|
"https://www.googleapis.com/auth/userinfo.email "
|
||||||
|
"https://www.googleapis.com/auth/userinfo.profile "
|
||||||
|
"openid")
|
||||||
|
:auth-uri "https://accounts.google.com/o/oauth2/v2/auth"
|
||||||
|
:token-uri "https://oauth2.googleapis.com/token"
|
||||||
|
:user-uri "https://openidconnect.googleapis.com/v1/userinfo"
|
||||||
|
:name "google"}]
|
||||||
|
(if (and (string? (:client-id opts))
|
||||||
|
(string? (:client-secret opts)))
|
||||||
|
(do
|
||||||
|
(l/info :action "initialize" :provider "google")
|
||||||
|
(assoc-in cfg [:providers "google"] opts))
|
||||||
|
cfg)))
|
||||||
|
|
||||||
|
(defn- initialize-github-provider
|
||||||
|
[cfg]
|
||||||
|
(let [opts {:client-id (cf/get :github-client-id)
|
||||||
|
:client-secret (cf/get :github-client-secret)
|
||||||
|
:scope "user:email"
|
||||||
|
:auth-uri "https://github.com/login/oauth/authorize"
|
||||||
|
:token-uri "https://github.com/login/oauth/access_token"
|
||||||
|
:user-uri "https://api.github.com/user"
|
||||||
|
:name "github"}]
|
||||||
|
(if (and (string? (:client-id opts))
|
||||||
|
(string? (:client-secret opts)))
|
||||||
|
(do
|
||||||
|
(l/info :action "initialize" :provider "github")
|
||||||
|
(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)
|
||||||
|
:scope "read_user"
|
||||||
|
:auth-uri (str base "/oauth/authorize")
|
||||||
|
:token-uri (str base "/oauth/token")
|
||||||
|
:user-uri (str base "/api/v4/user")
|
||||||
|
:name "gitlab"}]
|
||||||
|
(if (and (string? (:client-id opts))
|
||||||
|
(string? (:client-secret opts)))
|
||||||
|
(do
|
||||||
|
(l/info :action "initialize" :provider "gitlab")
|
||||||
|
(assoc-in cfg [:providers "gitlab"] opts))
|
||||||
|
cfg)))
|
||||||
|
|
||||||
|
(defn- initialize
|
||||||
|
[cfg]
|
||||||
|
(let [cfg (agent cfg :error-mode :continue)]
|
||||||
|
(send-off cfg initialize-google-provider)
|
||||||
|
(send-off cfg initialize-gitlab-provider)
|
||||||
|
(send-off cfg initialize-github-provider)
|
||||||
|
(send-off cfg initialize-oidc-provider)
|
||||||
|
cfg))
|
|
@ -1,157 +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/.
|
|
||||||
;;
|
|
||||||
;; Copyright (c) UXBOX Labs SL
|
|
||||||
|
|
||||||
(ns app.http.oauth.github
|
|
||||||
(:require
|
|
||||||
[app.common.exceptions :as ex]
|
|
||||||
[app.common.spec :as us]
|
|
||||||
[app.http.oauth.google :as gg]
|
|
||||||
[app.util.http :as http]
|
|
||||||
[app.util.logging :as l]
|
|
||||||
[app.util.time :as dt]
|
|
||||||
[clojure.data.json :as json]
|
|
||||||
[clojure.spec.alpha :as s]
|
|
||||||
[integrant.core :as ig]
|
|
||||||
[lambdaisland.uri :as u]))
|
|
||||||
|
|
||||||
(def base-github-uri
|
|
||||||
(u/uri "https://github.com"))
|
|
||||||
|
|
||||||
(def base-api-github-uri
|
|
||||||
(u/uri "https://api.github.com"))
|
|
||||||
|
|
||||||
(def authorize-uri
|
|
||||||
(assoc base-github-uri :path "/login/oauth/authorize"))
|
|
||||||
|
|
||||||
(def token-url
|
|
||||||
(assoc base-github-uri :path "/login/oauth/access_token"))
|
|
||||||
|
|
||||||
(def user-info-url
|
|
||||||
(assoc base-api-github-uri :path "/user"))
|
|
||||||
|
|
||||||
(def scope "user:email")
|
|
||||||
|
|
||||||
(defn- build-redirect-url
|
|
||||||
[cfg]
|
|
||||||
(let [public (u/uri (:public-uri cfg))]
|
|
||||||
(str (assoc public :path "/api/oauth/github/callback"))))
|
|
||||||
|
|
||||||
(defn- get-access-token
|
|
||||||
[cfg state code]
|
|
||||||
(try
|
|
||||||
(let [params {:client_id (:client-id cfg)
|
|
||||||
:client_secret (:client-secret cfg)
|
|
||||||
:code code
|
|
||||||
:state state
|
|
||||||
:redirect_uri (build-redirect-url cfg)}
|
|
||||||
req {:method :post
|
|
||||||
:headers {"content-type" "application/x-www-form-urlencoded"
|
|
||||||
"accept" "application/json"}
|
|
||||||
:uri (str token-url)
|
|
||||||
:timeout 6000
|
|
||||||
:body (u/map->query-string params)}
|
|
||||||
res (http/send! req)]
|
|
||||||
|
|
||||||
(when (= 200 (:status res))
|
|
||||||
(-> (json/read-str (:body res))
|
|
||||||
(get "access_token"))))
|
|
||||||
|
|
||||||
(catch Exception e
|
|
||||||
(l/error :hint "unexpected error on get-access-token"
|
|
||||||
:cause e)
|
|
||||||
nil)))
|
|
||||||
|
|
||||||
(defn- get-user-info
|
|
||||||
[_ token]
|
|
||||||
(try
|
|
||||||
(let [req {:uri (str user-info-url)
|
|
||||||
:headers {"authorization" (str "token " token)}
|
|
||||||
:timeout 6000
|
|
||||||
:method :get}
|
|
||||||
res (http/send! req)]
|
|
||||||
(when (= 200 (:status res))
|
|
||||||
(let [data (json/read-str (:body res))]
|
|
||||||
{:email (get data "email")
|
|
||||||
:backend "github"
|
|
||||||
:fullname (get data "name")})))
|
|
||||||
(catch Exception e
|
|
||||||
(l/error :hint "unexpected exception on get-user-info"
|
|
||||||
:cause e)
|
|
||||||
nil)))
|
|
||||||
|
|
||||||
(defn- retrieve-info
|
|
||||||
[{:keys [tokens] :as cfg} request]
|
|
||||||
(let [token (get-in request [:params :state])
|
|
||||||
state (tokens :verify {:token token :iss :github-oauth})
|
|
||||||
info (some->> (get-in request [:params :code])
|
|
||||||
(get-access-token cfg state)
|
|
||||||
(get-user-info cfg))]
|
|
||||||
(when-not info
|
|
||||||
(ex/raise :type :internal
|
|
||||||
:code :unable-to-auth))
|
|
||||||
|
|
||||||
(cond-> info
|
|
||||||
(some? (:invitation-token state))
|
|
||||||
(assoc :invitation-token (:invitation-token state)))))
|
|
||||||
|
|
||||||
(defn auth-handler
|
|
||||||
[{:keys [tokens] :as cfg} request]
|
|
||||||
(let [invitation (get-in request [:params :invitation-token])
|
|
||||||
state (tokens :generate {:iss :github-oauth
|
|
||||||
:invitation-token invitation
|
|
||||||
:exp (dt/in-future "15m")})
|
|
||||||
params {:client_id (:client-id cfg)
|
|
||||||
:redirect_uri (build-redirect-url cfg)
|
|
||||||
:state state
|
|
||||||
:scope scope}
|
|
||||||
query (u/map->query-string params)
|
|
||||||
uri (-> authorize-uri
|
|
||||||
(assoc :query query))]
|
|
||||||
{:status 200
|
|
||||||
:body {:redirect-uri (str uri)}}))
|
|
||||||
|
|
||||||
(defn- callback-handler
|
|
||||||
[{:keys [session] :as cfg} request]
|
|
||||||
(try
|
|
||||||
(let [info (retrieve-info cfg request)
|
|
||||||
profile (gg/register-profile cfg info)
|
|
||||||
uri (gg/generate-redirect-uri cfg profile)
|
|
||||||
sxf ((:create session) (:id profile))]
|
|
||||||
(->> (gg/redirect-response uri)
|
|
||||||
(sxf request)))
|
|
||||||
(catch Exception _e
|
|
||||||
(-> (gg/generate-error-redirect-uri cfg)
|
|
||||||
(gg/redirect-response)))))
|
|
||||||
|
|
||||||
|
|
||||||
;; --- ENTRY POINT
|
|
||||||
|
|
||||||
(s/def ::client-id ::us/not-empty-string)
|
|
||||||
(s/def ::client-secret ::us/not-empty-string)
|
|
||||||
(s/def ::public-uri ::us/not-empty-string)
|
|
||||||
(s/def ::session map?)
|
|
||||||
(s/def ::tokens fn?)
|
|
||||||
|
|
||||||
(defmethod ig/pre-init-spec :app.http.oauth/github [_]
|
|
||||||
(s/keys :req-un [::public-uri
|
|
||||||
::session
|
|
||||||
::tokens]
|
|
||||||
:opt-un [::client-id
|
|
||||||
::client-secret]))
|
|
||||||
|
|
||||||
(defn- default-handler
|
|
||||||
[_]
|
|
||||||
(ex/raise :type :not-found))
|
|
||||||
|
|
||||||
(defmethod ig/init-key :app.http.oauth/github
|
|
||||||
[_ cfg]
|
|
||||||
(if (and (:client-id cfg)
|
|
||||||
(:client-secret cfg))
|
|
||||||
{:handler #(auth-handler cfg %)
|
|
||||||
:callback-handler #(callback-handler cfg %)}
|
|
||||||
{:handler default-handler
|
|
||||||
:callback-handler default-handler}))
|
|
||||||
|
|
|
@ -1,166 +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/.
|
|
||||||
;;
|
|
||||||
;; Copyright (c) UXBOX Labs SL
|
|
||||||
|
|
||||||
(ns app.http.oauth.gitlab
|
|
||||||
(:require
|
|
||||||
[app.common.data :as d]
|
|
||||||
[app.common.exceptions :as ex]
|
|
||||||
[app.common.spec :as us]
|
|
||||||
[app.http.oauth.google :as gg]
|
|
||||||
[app.util.http :as http]
|
|
||||||
[app.util.logging :as l]
|
|
||||||
[app.util.time :as dt]
|
|
||||||
[clojure.data.json :as json]
|
|
||||||
[clojure.spec.alpha :as s]
|
|
||||||
[integrant.core :as ig]
|
|
||||||
[lambdaisland.uri :as u]))
|
|
||||||
|
|
||||||
(def scope "read_user")
|
|
||||||
|
|
||||||
(defn- build-redirect-url
|
|
||||||
[cfg]
|
|
||||||
(let [public (u/uri (:public-uri cfg))]
|
|
||||||
(str (assoc public :path "/api/oauth/gitlab/callback"))))
|
|
||||||
|
|
||||||
(defn- build-oauth-uri
|
|
||||||
[cfg]
|
|
||||||
(let [base-uri (u/uri (:base-uri cfg))]
|
|
||||||
(assoc base-uri :path "/oauth/authorize")))
|
|
||||||
|
|
||||||
(defn- build-token-url
|
|
||||||
[cfg]
|
|
||||||
(let [base-uri (u/uri (:base-uri cfg))]
|
|
||||||
(str (assoc base-uri :path "/oauth/token"))))
|
|
||||||
|
|
||||||
(defn- build-user-info-url
|
|
||||||
[cfg]
|
|
||||||
(let [base-uri (u/uri (:base-uri cfg))]
|
|
||||||
(str (assoc base-uri :path "/api/v4/user"))))
|
|
||||||
|
|
||||||
(defn- get-access-token
|
|
||||||
[cfg code]
|
|
||||||
(try
|
|
||||||
(let [params {:client_id (:client-id cfg)
|
|
||||||
:client_secret (:client-secret cfg)
|
|
||||||
:code code
|
|
||||||
:grant_type "authorization_code"
|
|
||||||
:redirect_uri (build-redirect-url cfg)}
|
|
||||||
req {:method :post
|
|
||||||
:headers {"content-type" "application/x-www-form-urlencoded"}
|
|
||||||
:uri (build-token-url cfg)
|
|
||||||
:body (u/map->query-string params)}
|
|
||||||
res (http/send! req)]
|
|
||||||
|
|
||||||
(when (= 200 (:status res))
|
|
||||||
(-> (json/read-str (:body res))
|
|
||||||
(get "access_token"))))
|
|
||||||
|
|
||||||
(catch Exception e
|
|
||||||
(l/error :hint "unexpected error on get-access-token"
|
|
||||||
:cause e)
|
|
||||||
nil)))
|
|
||||||
|
|
||||||
(defn- get-user-info
|
|
||||||
[cfg token]
|
|
||||||
(try
|
|
||||||
(let [req {:uri (build-user-info-url cfg)
|
|
||||||
:headers {"Authorization" (str "Bearer " token)}
|
|
||||||
:timeout 6000
|
|
||||||
:method :get}
|
|
||||||
res (http/send! req)]
|
|
||||||
|
|
||||||
(when (= 200 (:status res))
|
|
||||||
(let [data (json/read-str (:body res))]
|
|
||||||
{:email (get data "email")
|
|
||||||
:backend "gitlab"
|
|
||||||
:fullname (get data "name")})))
|
|
||||||
|
|
||||||
(catch Exception e
|
|
||||||
(l/error :hint "unexpected exception on get-user-info"
|
|
||||||
:cause e)
|
|
||||||
nil)))
|
|
||||||
|
|
||||||
|
|
||||||
(defn- retrieve-info
|
|
||||||
[{:keys [tokens] :as cfg} request]
|
|
||||||
(let [token (get-in request [:params :state])
|
|
||||||
state (tokens :verify {:token token :iss :gitlab-oauth})
|
|
||||||
info (some->> (get-in request [:params :code])
|
|
||||||
(get-access-token cfg)
|
|
||||||
(get-user-info cfg))]
|
|
||||||
(when-not info
|
|
||||||
(ex/raise :type :internal
|
|
||||||
:code :unable-to-auth))
|
|
||||||
|
|
||||||
(cond-> info
|
|
||||||
(some? (:invitation-token state))
|
|
||||||
(assoc :invitation-token (:invitation-token state)))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn- auth-handler
|
|
||||||
[{:keys [tokens] :as cfg} request]
|
|
||||||
(let [invitation (get-in request [:params :invitation-token])
|
|
||||||
state (tokens :generate
|
|
||||||
{:iss :gitlab-oauth
|
|
||||||
:invitation-token invitation
|
|
||||||
:exp (dt/in-future "15m")})
|
|
||||||
|
|
||||||
params {:client_id (:client-id cfg)
|
|
||||||
:redirect_uri (build-redirect-url cfg)
|
|
||||||
:response_type "code"
|
|
||||||
:state state
|
|
||||||
:scope scope}
|
|
||||||
query (u/map->query-string params)
|
|
||||||
uri (-> (build-oauth-uri cfg)
|
|
||||||
(assoc :query query))]
|
|
||||||
{:status 200
|
|
||||||
:body {:redirect-uri (str uri)}}))
|
|
||||||
|
|
||||||
(defn- callback-handler
|
|
||||||
[{:keys [session] :as cfg} request]
|
|
||||||
(try
|
|
||||||
(let [info (retrieve-info cfg request)
|
|
||||||
profile (gg/register-profile cfg info)
|
|
||||||
uri (gg/generate-redirect-uri cfg profile)
|
|
||||||
sxf ((:create session) (:id profile))]
|
|
||||||
(->> (gg/redirect-response uri)
|
|
||||||
(sxf request)))
|
|
||||||
(catch Exception _e
|
|
||||||
(-> (gg/generate-error-redirect-uri cfg)
|
|
||||||
(gg/redirect-response)))))
|
|
||||||
|
|
||||||
(s/def ::client-id ::us/not-empty-string)
|
|
||||||
(s/def ::client-secret ::us/not-empty-string)
|
|
||||||
(s/def ::base-uri ::us/not-empty-string)
|
|
||||||
(s/def ::public-uri ::us/not-empty-string)
|
|
||||||
(s/def ::session map?)
|
|
||||||
(s/def ::tokens fn?)
|
|
||||||
|
|
||||||
(defmethod ig/pre-init-spec :app.http.oauth/gitlab [_]
|
|
||||||
(s/keys :req-un [::public-uri
|
|
||||||
::session
|
|
||||||
::tokens]
|
|
||||||
:opt-un [::base-uri
|
|
||||||
::client-id
|
|
||||||
::client-secret]))
|
|
||||||
|
|
||||||
(defmethod ig/prep-key :app.http.oauth/gitlab
|
|
||||||
[_ cfg]
|
|
||||||
(d/merge {:base-uri "https://gitlab.com"}
|
|
||||||
(d/without-nils cfg)))
|
|
||||||
|
|
||||||
(defn- default-handler
|
|
||||||
[_]
|
|
||||||
(ex/raise :type :not-found))
|
|
||||||
|
|
||||||
(defmethod ig/init-key :app.http.oauth/gitlab
|
|
||||||
[_ cfg]
|
|
||||||
(if (and (:client-id cfg)
|
|
||||||
(:client-secret cfg))
|
|
||||||
{:handler #(auth-handler cfg %)
|
|
||||||
:callback-handler #(callback-handler cfg %)}
|
|
||||||
{:handler default-handler
|
|
||||||
:callback-handler default-handler}))
|
|
|
@ -1,181 +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/.
|
|
||||||
;;
|
|
||||||
;; Copyright (c) UXBOX Labs SL
|
|
||||||
|
|
||||||
(ns app.http.oauth.google
|
|
||||||
(:require
|
|
||||||
[app.common.exceptions :as ex]
|
|
||||||
[app.common.spec :as us]
|
|
||||||
[app.util.http :as http]
|
|
||||||
[app.util.logging :as l]
|
|
||||||
[app.util.time :as dt]
|
|
||||||
[clojure.data.json :as json]
|
|
||||||
[clojure.spec.alpha :as s]
|
|
||||||
[integrant.core :as ig]
|
|
||||||
[lambdaisland.uri :as u]))
|
|
||||||
|
|
||||||
(def base-goauth-uri "https://accounts.google.com/o/oauth2/v2/auth")
|
|
||||||
|
|
||||||
(def scope
|
|
||||||
(str "email profile "
|
|
||||||
"https://www.googleapis.com/auth/userinfo.email "
|
|
||||||
"https://www.googleapis.com/auth/userinfo.profile "
|
|
||||||
"openid"))
|
|
||||||
|
|
||||||
(defn- build-redirect-url
|
|
||||||
[cfg]
|
|
||||||
(let [public (u/uri (:public-uri cfg))]
|
|
||||||
(str (assoc public :path "/api/oauth/google/callback"))))
|
|
||||||
|
|
||||||
(defn- get-access-token
|
|
||||||
[cfg code]
|
|
||||||
(try
|
|
||||||
(let [params {:code code
|
|
||||||
:client_id (:client-id cfg)
|
|
||||||
:client_secret (:client-secret cfg)
|
|
||||||
:redirect_uri (build-redirect-url cfg)
|
|
||||||
:grant_type "authorization_code"}
|
|
||||||
req {:method :post
|
|
||||||
:headers {"content-type" "application/x-www-form-urlencoded"}
|
|
||||||
:uri "https://oauth2.googleapis.com/token"
|
|
||||||
:timeout 6000
|
|
||||||
:body (u/map->query-string params)}
|
|
||||||
res (http/send! req)]
|
|
||||||
|
|
||||||
(when (= 200 (:status res))
|
|
||||||
(-> (json/read-str (:body res))
|
|
||||||
(get "access_token"))))
|
|
||||||
(catch Exception e
|
|
||||||
(l/error :hint "unexpected error on get-access-token"
|
|
||||||
:cause e)
|
|
||||||
nil)))
|
|
||||||
|
|
||||||
(defn- get-user-info
|
|
||||||
[_ token]
|
|
||||||
(try
|
|
||||||
(let [req {:uri "https://openidconnect.googleapis.com/v1/userinfo"
|
|
||||||
:headers {"Authorization" (str "Bearer " token)}
|
|
||||||
:timeout 6000
|
|
||||||
:method :get}
|
|
||||||
res (http/send! req)]
|
|
||||||
|
|
||||||
(when (= 200 (:status res))
|
|
||||||
(let [data (json/read-str (:body res))]
|
|
||||||
{:email (get data "email")
|
|
||||||
:backend "google"
|
|
||||||
:fullname (get data "name")})))
|
|
||||||
(catch Exception e
|
|
||||||
(l/error :hint "unexpected exception on get-user-info"
|
|
||||||
:cause e)
|
|
||||||
nil)))
|
|
||||||
|
|
||||||
(defn- retrieve-info
|
|
||||||
[{:keys [tokens] :as cfg} request]
|
|
||||||
(let [token (get-in request [:params :state])
|
|
||||||
state (tokens :verify {:token token :iss :google-oauth})
|
|
||||||
info (some->> (get-in request [:params :code])
|
|
||||||
(get-access-token cfg)
|
|
||||||
(get-user-info cfg))]
|
|
||||||
|
|
||||||
|
|
||||||
(when-not info
|
|
||||||
(ex/raise :type :internal
|
|
||||||
:code :unable-to-auth))
|
|
||||||
|
|
||||||
(cond-> info
|
|
||||||
(some? (:invitation-token state))
|
|
||||||
(assoc :invitation-token (:invitation-token state)))))
|
|
||||||
|
|
||||||
(defn register-profile
|
|
||||||
[{:keys [rpc] :as cfg} info]
|
|
||||||
(let [method-fn (get-in rpc [:methods :mutation :login-or-register])
|
|
||||||
profile (method-fn {:email (:email info)
|
|
||||||
:backend (:backend info)
|
|
||||||
:fullname (:fullname info)})]
|
|
||||||
(cond-> profile
|
|
||||||
(some? (:invitation-token info))
|
|
||||||
(assoc :invitation-token (:invitation-token info)))))
|
|
||||||
|
|
||||||
(defn generate-redirect-uri
|
|
||||||
[{:keys [tokens] :as cfg} profile]
|
|
||||||
(let [token (or (:invitation-token profile)
|
|
||||||
(tokens :generate {:iss :auth
|
|
||||||
:exp (dt/in-future "15m")
|
|
||||||
:profile-id (:id profile)}))]
|
|
||||||
(-> (u/uri (:public-uri cfg))
|
|
||||||
(assoc :path "/#/auth/verify-token")
|
|
||||||
(assoc :query (u/map->query-string {:token token})))))
|
|
||||||
|
|
||||||
(defn generate-error-redirect-uri
|
|
||||||
[cfg]
|
|
||||||
(-> (u/uri (:public-uri cfg))
|
|
||||||
(assoc :path "/#/auth/login")
|
|
||||||
(assoc :query (u/map->query-string {:error "unable-to-auth"}))))
|
|
||||||
|
|
||||||
(defn redirect-response
|
|
||||||
[uri]
|
|
||||||
{:status 302
|
|
||||||
:headers {"location" (str uri)}
|
|
||||||
:body ""})
|
|
||||||
|
|
||||||
(defn- auth-handler
|
|
||||||
[{:keys [tokens] :as cfg} request]
|
|
||||||
(let [invitation (get-in request [:params :invitation-token])
|
|
||||||
state (tokens :generate
|
|
||||||
{:iss :google-oauth
|
|
||||||
:invitation-token invitation
|
|
||||||
:exp (dt/in-future "15m")})
|
|
||||||
params {:scope scope
|
|
||||||
:access_type "offline"
|
|
||||||
:include_granted_scopes true
|
|
||||||
:state state
|
|
||||||
:response_type "code"
|
|
||||||
:redirect_uri (build-redirect-url cfg)
|
|
||||||
:client_id (:client-id cfg)}
|
|
||||||
query (u/map->query-string params)
|
|
||||||
uri (-> (u/uri base-goauth-uri)
|
|
||||||
(assoc :query query))]
|
|
||||||
|
|
||||||
{:status 200
|
|
||||||
:body {:redirect-uri (str uri)}}))
|
|
||||||
|
|
||||||
(defn- callback-handler
|
|
||||||
[{:keys [session] :as cfg} request]
|
|
||||||
(try
|
|
||||||
(let [info (retrieve-info cfg request)
|
|
||||||
profile (register-profile cfg info)
|
|
||||||
uri (generate-redirect-uri cfg profile)
|
|
||||||
sxf ((:create session) (:id profile))]
|
|
||||||
(->> (redirect-response uri)
|
|
||||||
(sxf request)))
|
|
||||||
(catch Exception _e
|
|
||||||
(-> (generate-error-redirect-uri cfg)
|
|
||||||
(redirect-response)))))
|
|
||||||
|
|
||||||
(s/def ::client-id ::us/not-empty-string)
|
|
||||||
(s/def ::client-secret ::us/not-empty-string)
|
|
||||||
(s/def ::public-uri ::us/not-empty-string)
|
|
||||||
(s/def ::session map?)
|
|
||||||
(s/def ::tokens fn?)
|
|
||||||
|
|
||||||
(defmethod ig/pre-init-spec :app.http.oauth/google [_]
|
|
||||||
(s/keys :req-un [::public-uri
|
|
||||||
::session
|
|
||||||
::tokens]
|
|
||||||
:opt-un [::client-id
|
|
||||||
::client-secret]))
|
|
||||||
|
|
||||||
(defn- default-handler
|
|
||||||
[_]
|
|
||||||
(ex/raise :type :not-found))
|
|
||||||
|
|
||||||
(defmethod ig/init-key :app.http.oauth/google
|
|
||||||
[_ cfg]
|
|
||||||
(if (and (:client-id cfg)
|
|
||||||
(:client-secret cfg))
|
|
||||||
{:handler #(auth-handler cfg %)
|
|
||||||
:callback-handler #(callback-handler cfg %)}
|
|
||||||
{:handler default-handler
|
|
||||||
:callback-handler default-handler}))
|
|
|
@ -86,13 +86,12 @@
|
||||||
:ws {"/ws/notifications" (ig/ref :app.notifications/handler)}}
|
:ws {"/ws/notifications" (ig/ref :app.notifications/handler)}}
|
||||||
|
|
||||||
:app.http/router
|
:app.http/router
|
||||||
{
|
{: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)
|
||||||
:public-uri (cf/get :public-uri)
|
:public-uri (cf/get :public-uri)
|
||||||
:metrics (ig/ref :app.metrics/metrics)
|
:metrics (ig/ref :app.metrics/metrics)
|
||||||
:oauth (ig/ref :app.http.oauth/all)
|
:oauth (ig/ref :app.http.oauth/handlers)
|
||||||
:assets (ig/ref :app.http.assets/handlers)
|
:assets (ig/ref :app.http.assets/handlers)
|
||||||
:storage (ig/ref :app.storage/storage)
|
:storage (ig/ref :app.storage/storage)
|
||||||
:sns-webhook (ig/ref :app.http.awsns/handler)
|
:sns-webhook (ig/ref :app.http.awsns/handler)
|
||||||
|
@ -109,35 +108,11 @@
|
||||||
:app.http.feedback/handler
|
:app.http.feedback/handler
|
||||||
{:pool (ig/ref :app.db/pool)}
|
{:pool (ig/ref :app.db/pool)}
|
||||||
|
|
||||||
:app.http.oauth/all
|
:app.http.oauth/handlers
|
||||||
{: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)
|
||||||
:public-uri (cf/get :public-uri)
|
:public-uri (cf/get :public-uri)}
|
||||||
:client-id (cf/get :google-client-id)
|
|
||||||
:client-secret (cf/get :google-client-secret)}
|
|
||||||
|
|
||||||
:app.http.oauth/github
|
|
||||||
{:rpc (ig/ref :app.rpc/rpc)
|
|
||||||
:session (ig/ref :app.http.session/session)
|
|
||||||
:tokens (ig/ref :app.tokens/tokens)
|
|
||||||
:public-uri (cf/get :public-uri)
|
|
||||||
:client-id (cf/get :github-client-id)
|
|
||||||
:client-secret (cf/get :github-client-secret)}
|
|
||||||
|
|
||||||
:app.http.oauth/gitlab
|
|
||||||
{:rpc (ig/ref :app.rpc/rpc)
|
|
||||||
:session (ig/ref :app.http.session/session)
|
|
||||||
:tokens (ig/ref :app.tokens/tokens)
|
|
||||||
:public-uri (cf/get :public-uri)
|
|
||||||
:base-uri (cf/get :gitlab-base-uri)
|
|
||||||
:client-id (cf/get :gitlab-client-id)
|
|
||||||
:client-secret (cf/get :gitlab-client-secret)}
|
|
||||||
|
|
||||||
;; RLimit definition for password hashing
|
;; RLimit definition for password hashing
|
||||||
:app.rlimits/password
|
:app.rlimits/password
|
||||||
|
|
|
@ -6,5 +6,6 @@
|
||||||
//var penpotGoogleClientID = "<google-client-id-here>";
|
//var penpotGoogleClientID = "<google-client-id-here>";
|
||||||
//var penpotGitlabClientID = "<gitlab-client-id-here>";
|
//var penpotGitlabClientID = "<gitlab-client-id-here>";
|
||||||
//var penpotGithubClientID = "<github-client-id-here>";
|
//var penpotGithubClientID = "<github-client-id-here>";
|
||||||
|
//var penpotOIDCClientID = "<oidc-client-id-here>";
|
||||||
//var penpotLoginWithLDAP = <true|false>;
|
//var penpotLoginWithLDAP = <true|false>;
|
||||||
//var penpotRegistrationEnabled = <true|false>;
|
//var penpotRegistrationEnabled = <true|false>;
|
||||||
|
|
|
@ -69,6 +69,14 @@ update_github_client_id() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_oidc_client_id() {
|
||||||
|
if [ -n "$PENPOT_OIDC_CLIENT_ID" ]; then
|
||||||
|
log "Updating Oidc Client Id: $PENPOT_OIDC_CLIENT_ID"
|
||||||
|
sed -i \
|
||||||
|
-e "s|^//var penpotOIDCClientID = \".*\";|var penpotOIDCClientID = \"$PENPOT_OIDC_CLIENT_ID\";|g" \
|
||||||
|
"$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
update_login_with_ldap() {
|
update_login_with_ldap() {
|
||||||
if [ -n "$PENPOT_LOGIN_WITH_LDAP" ]; then
|
if [ -n "$PENPOT_LOGIN_WITH_LDAP" ]; then
|
||||||
|
@ -95,6 +103,7 @@ update_allow_demo_users /var/www/app/js/config.js
|
||||||
update_google_client_id /var/www/app/js/config.js
|
update_google_client_id /var/www/app/js/config.js
|
||||||
update_gitlab_client_id /var/www/app/js/config.js
|
update_gitlab_client_id /var/www/app/js/config.js
|
||||||
update_github_client_id /var/www/app/js/config.js
|
update_github_client_id /var/www/app/js/config.js
|
||||||
|
update_oidc_client_id /var/www/app/js/config.js
|
||||||
update_login_with_ldap /var/www/app/js/config.js
|
update_login_with_ldap /var/www/app/js/config.js
|
||||||
update_registration_enabled /var/www/app/js/config.js
|
update_registration_enabled /var/www/app/js/config.js
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
.form-container {
|
.form-container {
|
||||||
width: 412px;
|
width: 412px;
|
||||||
|
|
||||||
.btn-ocean {
|
.auth-buttons {
|
||||||
margin-top: $x-big;
|
margin-top: $x-big;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,7 @@
|
||||||
(def google-client-id (obj/get global "penpotGoogleClientID" nil))
|
(def google-client-id (obj/get global "penpotGoogleClientID" nil))
|
||||||
(def gitlab-client-id (obj/get global "penpotGitlabClientID" nil))
|
(def gitlab-client-id (obj/get global "penpotGitlabClientID" nil))
|
||||||
(def github-client-id (obj/get global "penpotGithubClientID" nil))
|
(def github-client-id (obj/get global "penpotGithubClientID" nil))
|
||||||
|
(def oidc-client-id (obj/get global "penpotOIDCClientID" nil))
|
||||||
(def login-with-ldap (obj/get global "penpotLoginWithLDAP" false))
|
(def login-with-ldap (obj/get global "penpotLoginWithLDAP" false))
|
||||||
(def registration-enabled (obj/get global "penpotRegistrationEnabled" true))
|
(def registration-enabled (obj/get global "penpotRegistrationEnabled" true))
|
||||||
(def worker-uri (obj/get global "penpotWorkerURI" "/js/worker.js"))
|
(def worker-uri (obj/get global "penpotWorkerURI" "/js/worker.js"))
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
(ns app.main.repo
|
(ns app.main.repo
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[lambdaisland.uri :as u]
|
[lambdaisland.uri :as u]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
|
@ -84,23 +85,10 @@
|
||||||
([id] (mutation id {}))
|
([id] (mutation id {}))
|
||||||
([id params] (mutation id params)))
|
([id params] (mutation id params)))
|
||||||
|
|
||||||
(defmethod mutation :login-with-google
|
(defmethod mutation :login-with-oauth
|
||||||
[id params]
|
[id {:keys [provider] :as params}]
|
||||||
(let [uri (u/join base-uri "api/oauth/google")]
|
(let [uri (u/join base-uri "api/auth/oauth/" (d/name provider))
|
||||||
(->> (http/send! {:method :post :uri uri :query params})
|
params (dissoc params :provider)]
|
||||||
(rx/map http/conditional-decode-transit)
|
|
||||||
(rx/mapcat handle-response))))
|
|
||||||
|
|
||||||
(defmethod mutation :login-with-gitlab
|
|
||||||
[id params]
|
|
||||||
(let [uri (u/join base-uri "api/oauth/gitlab")]
|
|
||||||
(->> (http/send! {:method :post :uri uri :query params})
|
|
||||||
(rx/map http/conditional-decode-transit)
|
|
||||||
(rx/mapcat handle-response))))
|
|
||||||
|
|
||||||
(defmethod mutation :login-with-github
|
|
||||||
[id params]
|
|
||||||
(let [uri (u/join base-uri "api/oauth/github")]
|
|
||||||
(->> (http/send! {:method :post :uri uri :query params})
|
(->> (http/send! {:method :post :uri uri :query params})
|
||||||
(rx/map http/conditional-decode-transit)
|
(rx/map http/conditional-decode-transit)
|
||||||
(rx/mapcat handle-response))))
|
(rx/mapcat handle-response))))
|
||||||
|
|
|
@ -29,26 +29,10 @@
|
||||||
(s/def ::login-form
|
(s/def ::login-form
|
||||||
(s/keys :req-un [::email ::password]))
|
(s/keys :req-un [::email ::password]))
|
||||||
|
|
||||||
(defn- login-with-google
|
(defn- login-with-oauth
|
||||||
[event params]
|
[event provider params]
|
||||||
(dom/prevent-default event)
|
(dom/prevent-default event)
|
||||||
(->> (rp/mutation! :login-with-google params)
|
(->> (rp/mutation! :login-with-oauth (assoc params :provider provider))
|
||||||
(rx/subs (fn [{:keys [redirect-uri] :as rsp}]
|
|
||||||
(.replace js/location redirect-uri))
|
|
||||||
(fn [{:keys [type] :as error}]
|
|
||||||
(st/emit! (dm/error (tr "errors.google-auth-not-enabled")))))))
|
|
||||||
|
|
||||||
(defn- login-with-gitlab
|
|
||||||
[event params]
|
|
||||||
(dom/prevent-default event)
|
|
||||||
(->> (rp/mutation! :login-with-gitlab params)
|
|
||||||
(rx/subs (fn [{:keys [redirect-uri] :as rsp}]
|
|
||||||
(.replace js/location redirect-uri)))))
|
|
||||||
|
|
||||||
(defn- login-with-github
|
|
||||||
[event params]
|
|
||||||
(dom/prevent-default event)
|
|
||||||
(->> (rp/mutation! :login-with-github params)
|
|
||||||
(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)))))
|
||||||
|
|
||||||
|
@ -127,6 +111,33 @@
|
||||||
{:label (tr "auth.login-with-ldap-submit")
|
{:label (tr "auth.login-with-ldap-submit")
|
||||||
:on-click on-submit-ldap}])]]))
|
:on-click on-submit-ldap}])]]))
|
||||||
|
|
||||||
|
(mf/defc login-buttons
|
||||||
|
[{:keys [params] :as props}]
|
||||||
|
[:div.auth-buttons
|
||||||
|
(when cfg/google-client-id
|
||||||
|
[:a.btn-ocean.btn-large.btn-google-auth
|
||||||
|
{:on-click #(login-with-oauth % :google params)}
|
||||||
|
(tr "auth.login-with-google-submit")])
|
||||||
|
|
||||||
|
(when cfg/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 cfg/github-client-id
|
||||||
|
[:a.btn-ocean.btn-large.btn-github-auth
|
||||||
|
{:on-click #(login-with-oauth % :github params)}
|
||||||
|
[:img.logo
|
||||||
|
{:src "/images/icons/brand-github.svg"}]
|
||||||
|
(tr "auth.login-with-github-submit")])
|
||||||
|
|
||||||
|
(when cfg/oidc-client-id
|
||||||
|
[:a.btn-ocean.btn-large.btn-github-auth
|
||||||
|
{:on-click #(login-with-oauth % :oidc params)}
|
||||||
|
(tr "auth.login-with-oidc-submit")])])
|
||||||
|
|
||||||
(mf/defc login-page
|
(mf/defc login-page
|
||||||
[{:keys [params] :as props}]
|
[{:keys [params] :as props}]
|
||||||
[:div.generic-form.login-form
|
[:div.generic-form.login-form
|
||||||
|
@ -149,24 +160,7 @@
|
||||||
:tab-index "6"}
|
:tab-index "6"}
|
||||||
(tr "auth.register-submit")]])]
|
(tr "auth.register-submit")]])]
|
||||||
|
|
||||||
(when cfg/google-client-id
|
[:& login-buttons {:params params}]
|
||||||
[:a.btn-ocean.btn-large.btn-google-auth
|
|
||||||
{:on-click #(login-with-google % params)}
|
|
||||||
"Login with Google"])
|
|
||||||
|
|
||||||
(when cfg/gitlab-client-id
|
|
||||||
[:a.btn-ocean.btn-large.btn-gitlab-auth
|
|
||||||
{:on-click #(login-with-gitlab % params)}
|
|
||||||
[:img.logo
|
|
||||||
{:src "/images/icons/brand-gitlab.svg"}]
|
|
||||||
(tr "auth.login-with-gitlab-submit")])
|
|
||||||
|
|
||||||
(when cfg/github-client-id
|
|
||||||
[:a.btn-ocean.btn-large.btn-github-auth
|
|
||||||
{:on-click #(login-with-github % params)}
|
|
||||||
[:img.logo
|
|
||||||
{:src "/images/icons/brand-github.svg"}]
|
|
||||||
(tr "auth.login-with-github-submit")])
|
|
||||||
|
|
||||||
(when cfg/allow-demo-users
|
(when cfg/allow-demo-users
|
||||||
[:div.links.demo
|
[:div.links.demo
|
||||||
|
|
|
@ -137,7 +137,6 @@
|
||||||
[:div.notification-text-email (:email params "")]
|
[:div.notification-text-email (:email params "")]
|
||||||
[:div.notification-text (tr "auth.check-your-email")]])
|
[:div.notification-text (tr "auth.check-your-email")]])
|
||||||
|
|
||||||
|
|
||||||
(mf/defc register-page
|
(mf/defc register-page
|
||||||
[{:keys [params] :as props}]
|
[{:keys [params] :as props}]
|
||||||
[:div.form-container
|
[:div.form-container
|
||||||
|
@ -161,24 +160,9 @@
|
||||||
[:span (tr "auth.create-demo-profile") " "]
|
[:span (tr "auth.create-demo-profile") " "]
|
||||||
[:a {:on-click #(st/emit! da/create-demo-profile)
|
[:a {:on-click #(st/emit! da/create-demo-profile)
|
||||||
:tab-index "5"}
|
:tab-index "5"}
|
||||||
(tr "auth.create-demo-account")]])]
|
(tr "auth.create-demo-account")]])
|
||||||
|
|
||||||
(when cfg/google-client-id
|
[:& login/login-buttons {:params params}]]])
|
||||||
[:a.btn-ocean.btn-large.btn-google-auth
|
|
||||||
{:on-click #(login/login-with-google % params)}
|
|
||||||
"Login with Google"])
|
|
||||||
|
|
||||||
(when cfg/gitlab-client-id
|
|
||||||
[:a.btn-ocean.btn-large.btn-gitlab-auth
|
|
||||||
{:on-click #(login/login-with-gitlab % params)}
|
|
||||||
[:img.logo
|
|
||||||
{:src "/images/icons/brand-gitlab.svg"}]
|
|
||||||
(tr "auth.login-with-gitlab-submit")])
|
|
||||||
|
|
||||||
(when cfg/github-client-id
|
|
||||||
[:a.btn-ocean.btn-large.btn-github-auth
|
|
||||||
{:on-click #(login/login-with-github % params)}
|
|
||||||
[:img.logo
|
|
||||||
{:src "/images/icons/brand-github.svg"}]
|
|
||||||
(tr "auth.login-with-github-submit")])])
|
|
||||||
|
|
||||||
|
|
|
@ -64,18 +64,26 @@ msgstr "Enter your details below"
|
||||||
msgid "auth.login-title"
|
msgid "auth.login-title"
|
||||||
msgstr "Great to see you again!"
|
msgstr "Great to see you again!"
|
||||||
|
|
||||||
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
|
#: src/app/main/ui/auth/login.cljs
|
||||||
msgid "auth.login-with-github-submit"
|
msgid "auth.login-with-github-submit"
|
||||||
msgstr "Login with Github"
|
msgstr "Login with Github"
|
||||||
|
|
||||||
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
|
#: src/app/main/ui/auth/login.cljs
|
||||||
msgid "auth.login-with-gitlab-submit"
|
msgid "auth.login-with-gitlab-submit"
|
||||||
msgstr "Login with Gitlab"
|
msgstr "Login with Gitlab"
|
||||||
|
|
||||||
|
#: src/app/main/ui/auth/login.cljs
|
||||||
|
msgid "auth.login-with-google-submit"
|
||||||
|
msgstr "Login with Google"
|
||||||
|
|
||||||
#: src/app/main/ui/auth/login.cljs
|
#: src/app/main/ui/auth/login.cljs
|
||||||
msgid "auth.login-with-ldap-submit"
|
msgid "auth.login-with-ldap-submit"
|
||||||
msgstr "Sign in with LDAP"
|
msgstr "Sign in with LDAP"
|
||||||
|
|
||||||
|
#: src/app/main/ui/auth/login.cljs
|
||||||
|
msgid "auth.login-with-oidc-submit"
|
||||||
|
msgstr "Login with OpenID (SSO)"
|
||||||
|
|
||||||
#: src/app/main/ui/auth/recovery.cljs
|
#: src/app/main/ui/auth/recovery.cljs
|
||||||
msgid "auth.new-password"
|
msgid "auth.new-password"
|
||||||
msgstr "Type a new password"
|
msgstr "Type a new password"
|
||||||
|
|
|
@ -60,18 +60,26 @@ msgstr "Introduce tus datos aquí"
|
||||||
msgid "auth.login-title"
|
msgid "auth.login-title"
|
||||||
msgstr "Encantados de volverte a ver"
|
msgstr "Encantados de volverte a ver"
|
||||||
|
|
||||||
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
|
#: src/app/main/ui/auth/login.cljs
|
||||||
msgid "auth.login-with-github-submit"
|
msgid "auth.login-with-github-submit"
|
||||||
msgstr "Entrar con Github"
|
msgstr "Entrar con Github"
|
||||||
|
|
||||||
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
|
#: src/app/main/ui/auth/login.cljs
|
||||||
msgid "auth.login-with-gitlab-submit"
|
msgid "auth.login-with-gitlab-submit"
|
||||||
msgstr "Entrar con Gitlab"
|
msgstr "Entrar con Gitlab"
|
||||||
|
|
||||||
|
#: src/app/main/ui/auth/login.cljs
|
||||||
|
msgid "auth.login-with-google-submit"
|
||||||
|
msgstr "Entrar con Google"
|
||||||
|
|
||||||
#: src/app/main/ui/auth/login.cljs
|
#: src/app/main/ui/auth/login.cljs
|
||||||
msgid "auth.login-with-ldap-submit"
|
msgid "auth.login-with-ldap-submit"
|
||||||
msgstr "Entrar con LDAP"
|
msgstr "Entrar con LDAP"
|
||||||
|
|
||||||
|
#: src/app/main/ui/auth/login.cljs
|
||||||
|
msgid "auth.login-with-oidc-submit"
|
||||||
|
msgstr "Entrar con OpenID (SSO)"
|
||||||
|
|
||||||
#: src/app/main/ui/auth/recovery.cljs
|
#: src/app/main/ui/auth/recovery.cljs
|
||||||
msgid "auth.new-password"
|
msgid "auth.new-password"
|
||||||
msgstr "Introduce la nueva contraseña"
|
msgstr "Introduce la nueva contraseña"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue