mirror of
https://github.com/penpot/penpot.git
synced 2025-05-29 09:56:20 +02:00
172 lines
5.4 KiB
Clojure
172 lines
5.4 KiB
Clojure
;; 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.http.auth.github
|
|
(:require
|
|
[app.common.exceptions :as ex]
|
|
[app.common.spec :as us]
|
|
[app.config :as cfg]
|
|
[app.db :as db]
|
|
[app.http.session :as session]
|
|
[app.util.http :as http]
|
|
[app.util.time :as dt]
|
|
[clojure.data.json :as json]
|
|
[clojure.spec.alpha :as s]
|
|
[clojure.tools.logging :as log]
|
|
[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 code state]
|
|
(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)
|
|
:body (u/map->query-string params)}
|
|
res (http/send! req)]
|
|
|
|
(when (not= 200 (:status res))
|
|
(ex/raise :type :internal
|
|
:code :invalid-response-from-github
|
|
:context {:status (:status res)
|
|
:body (:body res)}))
|
|
(try
|
|
(let [data (json/read-str (:body res))]
|
|
(get data "access_token"))
|
|
(catch Throwable e
|
|
(log/error "unexpected error on parsing response body from github access token request" e)
|
|
nil))))
|
|
|
|
(defn- get-user-info
|
|
[token]
|
|
(let [req {:uri (str user-info-url)
|
|
:headers {"authorization" (str "token " token)}
|
|
:method :get}
|
|
res (http/send! req)]
|
|
|
|
(when (not= 200 (:status res))
|
|
(ex/raise :type :internal
|
|
:code :invalid-response-from-github
|
|
:context {:status (:status res)
|
|
:body (:body res)}))
|
|
|
|
(try
|
|
(let [data (json/read-str (:body res))]
|
|
{:email (get data "email")
|
|
:fullname (get data "name")})
|
|
(catch Throwable e
|
|
(log/error "unexpected error on parsing response body from github access token request" e)
|
|
nil))))
|
|
|
|
(defn auth
|
|
[{:keys [tokens] :as cfg} request]
|
|
(let [state (tokens :generate
|
|
{:iss :github-oauth
|
|
:exp (dt/in-future "15m")})
|
|
|
|
params {:client_id (:client-id cfg/config)
|
|
:redirect_uri (build-redirect-url)
|
|
: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
|
|
[{:keys [tokens rpc session] :as cfg} request]
|
|
(let [state (get-in request [:params :state])
|
|
_ (tokens :verify {:token state :iss :github-oauth})
|
|
info (some-> (get-in request [:params :code])
|
|
(get-access-token state)
|
|
(get-user-info))]
|
|
|
|
(when-not info
|
|
(ex/raise :type :authentication
|
|
:code :unable-to-authenticate-with-github))
|
|
|
|
(let [method-fn (get-in rpc [:method :mutation :login-or-register])
|
|
profile (method-fn {:email (:email info)
|
|
:fullname (:fullname info)})
|
|
uagent (get-in request [:headers "user-agent"])
|
|
|
|
token (tokens :generate
|
|
{:iss :auth
|
|
:exp (dt/in-future "15m")
|
|
:profile-id (:id profile)})
|
|
|
|
uri (-> (u/uri (:public-uri cfg/config))
|
|
(assoc :path "/#/auth/verify-token")
|
|
(assoc :query (u/map->query-string {:token token})))
|
|
|
|
sid (session/create! session {:profile-id (:id profile)
|
|
:user-agent uagent})]
|
|
|
|
{:status 302
|
|
:headers {"location" (str uri)}
|
|
:cookies (session/cookies session/cookies {:value sid})
|
|
:body ""})))
|
|
|
|
;; --- 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.auth/github [_]
|
|
(s/keys :req-un [::public-uri
|
|
::session
|
|
::tokens]
|
|
:opt-un [::client-id
|
|
::client-secret]))
|
|
|
|
(defn- default-handler
|
|
[req]
|
|
(ex/raise :type :not-found))
|
|
|
|
(defmethod ig/init-key :app.http.auth/github
|
|
[_ cfg]
|
|
(if (and (:client-id cfg)
|
|
(:client-secret cfg))
|
|
{:auth-handler #(auth cfg %)
|
|
:callback-handler #(callback cfg %)}
|
|
{:auth-handler default-handler
|
|
:callback-handler default-handler}))
|
|
|