(ns auto-ap.ssr.auth (:require [auto-ap.session-version :as session-version] [auto-ap.ssr.hx :as hx] [auto-ap.ssr.svg :as svg] [buddy.sign.jwt :as jwt] [config.core :refer [env]] [hiccup2.core :as hiccup] [hiccup.util :as hu])) (defn logout [request] {:status 301 :headers {"Location" "/login"} :session {}}) (defn impersonate [request] {:status 200 :session {:identity (dissoc (jwt/unsign (get-in request [:query-params "jwt"]) (:jwt-secret env) {:alg :hs512}) :exp) :version session-version/current-session-version}}) (defn login-url ([] (login-url nil)) ([next] (let [client-id "264081895820-0nndcfo3pbtqf30sro82vgq5r27h8736.apps.googleusercontent.com" redirect-uri (str (:base-url env) "/api/oauth")] (str (hu/url "https://accounts.google.com/o/oauth2/auth" (cond-> {"access_type" "online" "client_id" client-id "redirect_uri" redirect-uri "response_type" "code" "max_auth_age" "0" "scope" "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile"} next (assoc "state" (hu/url-encode next)))))))) (defn- login-page [contents] {:status 200 :headers {"Content-Type" "text/html"} :body (str "" (hiccup/html [:html [:head [:meta {:charset "utf-8"}] [:meta {:name "viewport" :content "width=device-width, initial-scale=1"}] [:title "Integreat ยท Sign In"] [:link {:rel "icon" :type "image/png" :href "/favicon.png"}] [:link {:rel "stylesheet" :href "/output.css"}] [:script {:defer true :src "https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"}] [:style "body{background:linear-gradient(160deg,#79b52e 0%,#009cea 100%);min-height:100vh}@keyframes slideUp{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}"]] [:body contents]]))}) (defn- page-contents [request] [:div {:x-data (hx/json {:showError false :errorDetails ""}) "@htmx:response-error.camel" "errorDetails = $event.detail.xhr.response; showError=true;"} [:div.fixed.top-0.left-0.right-0.z-50.mx-auto.max-w-md.w-full.px-4.pt-6 {:x-show "showError" "x-transition:enter" "transition duration-200 ease-out" "x-transition:enter-start" "opacity-0 -translate-y-3" "x-transition:enter-end" "opacity-100 translate-y-0"} [:div.relative.bg-white.rounded-xl.shadow-xl.border.border-red-200.p-4 [:button.absolute.right-3.top-3.p-1.text-red-400.hover:text-red-600 {"@click" "showError=false"} svg/filled-x] [:div.flex.items-start.gap-3 [:div.flex-shrink-0.w-5.h-5.text-red-500 svg/alert] [:div.flex-1.min-w-0 [:p.text-sm.font-medium.text-gray-900 "Something went wrong"] [:div.text-xs.text-gray-500.mt-0.5 "Our team has been notified. Please try again." [:span {:x-data (hx/json {"e" false})} " " [:a.text-xs.underline.cursor-pointer.text-gray-500.hover:text-gray-700 {"@click" "e=true"} "Details"] [:pre.text-xs.mt-1.font-mono.text-red-600.bg-red-50.p-2.rounded {:x-show "e" :x-text "errorDetails"}]]]]]]] [:div.flex.items-center.justify-center.min-h-screen.px-4 [:div.w-full.max-w-lg [:div.flex.flex-col.items-center.mb-10 [:img {:src "/img/logo-big.png" :alt "Integreat" :class "h-16 brightness-0 invert"}]] [:div.bg-white.rounded-2xl.shadow-2xl.p-10 {:style "animation: slideUp 0.4s ease-out forwards; opacity: 0;"} [:div.flex.flex-col.items-center.gap-8 [:div.text-center [:h1.text-2xl.font-bold.text-gray-900 "Sign in to Integreat"] [:p.mt-2.text-base.text-gray-500 "Use your Google account to continue"]] [:a {:href (login-url (get (:query-params request) "redirect-to")) :class "w-full max-w-xs flex items-center justify-center gap-3 px-6 py-3.5 text-base font-semibold rounded-xl border-2 border-gray-200 text-gray-700 bg-white hover:bg-gray-50 hover:border-gray-300 shadow-md hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-400 transition-all duration-150"} svg/google "Sign in with Google"]] [:p.mt-2.text-center.text-xs.text-gray-400 "By signing in, you agree to our " [:a.underline.hover:text-gray-600 {:href "/terms"} "Terms of Service"] " and " [:a.underline.hover:text-gray-600 {:href "/privacy"} "Privacy Policy"]]]]]]) (defn login [request] (login-page (page-contents request)))