(ns auto-ap.routes.auth (:require [auto-ap.datomic.users :as users] [buddy.sign.jwt :as jwt] [clj-http.client :as http] [clj-time.core :as time] [auto-ap.logging :as alog] [config.core :refer [env]] [com.brunobonacci.mulog :as mu] [clojure.java.io :as io] [clojure.edn :as edn] [auto-ap.session-version :as session-version])) (def google-client-id "264081895820-0nndcfo3pbtqf30sro82vgq5r27h8736.apps.googleusercontent.com") (def google-client-secret "OC-WemHurPXYpuIw5cT-B90g") #_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]} (defn make-api-token [] (jwt/sign {:user "API" :exp (time/plus (time/now) (time/days 1000)) :user/role "admin" :user/name "API"} (:jwt-secret env) {:alg :hs512})) (defn gzip [data] (let [data (pr-str data) raw (java.io.ByteArrayOutputStream.)] (with-open [output (-> raw (io/output-stream) (java.util.zip.GZIPOutputStream.))] (io/copy data output)) (.encodeToString (java.util.Base64/getEncoder) (.toByteArray raw)))) (defn gunzip [b64] (let [raw-bytes (.decode (java.util.Base64/getDecoder) b64) raw (java.io.ByteArrayInputStream. raw-bytes) out (java.io.ByteArrayOutputStream.)] (with-open [compressed (-> raw (io/input-stream) (java.util.zip.GZIPInputStream.))] (io/copy compressed out)) (edn/read-string (.toString out)))) (defn user->jwt [user oauth-token] (let [auth (cond-> {:user (:user/name user) :exp (time/plus (time/now) (time/days 30)) :db/id (:db/id user) :user/role (name (:user/role user)) :user/name (:user/name user)} (#{"admin" "read-only"} (name (:user/role user))) (assoc :gz-clients (->> (:user/clients user) (map (fn [c] (select-keys c [:client/code :db/id :client/locations]))) gzip)) (not (#{"read-only" "admin"} (name (:user/role user)))) (assoc :user/clients (->> (:user/clients user) (map (fn [c] (select-keys c [:client/code :db/id :client/locations]))))))] (when (and user oauth-token) auth))) (defn oauth [{{:strs [code state]} :query-params {:strs [host]} :headers :as request}] (try (let [auth (-> "https://accounts.google.com/o/oauth2/token" (http/post {:form-params {"client_id" google-client-id "client_secret" google-client-secret "code" code "redirect_uri" (str (:scheme env) "://" host "/api/oauth") "grant_type" "authorization_code"} :as :json}) :body) token (:access_token auth) profile (-> (http/get "https://www.googleapis.com/oauth2/v1/userinfo" {:headers {"Authorization" (str "Bearer " token)} :as :json}) :body) _ (mu/log ::got-profile :profile profile) user (users/find-or-insert! {:user/provider "google" :user/provider-id (:id profile) :user/email (:email profile) :user/profile-image-url (:picture profile) :user/name (:name profile)})] ;; TODO - these namespaces are not being transmitted/deserialized properly (if-let [jwt (user->jwt user token)] {:status 301 :headers {"Location" (str (or (not-empty state) "/") "?jwt=" (jwt/sign jwt (:jwt-secret env) {:alg :hs512}))} :session {:identity (dissoc jwt :exp) :version session-version/current-session-version}} {:status 401 :body "Couldn't authenticate"})) (catch Exception e (alog/warn ::cant-authenticate :error e) {:status 401 :body (str "Couldn't authenticate " (.toString e))}))) (def routes {"api" {"/oauth" :oauth}}) (def match->handler {:oauth oauth})