better error handling for oauth

restructure route handlers
This commit is contained in:
flynn 2019-06-08 23:51:04 +02:00
parent af90e8639a
commit fd54e61a49
5 changed files with 100 additions and 91 deletions

View file

@ -1,17 +1,19 @@
(defproject cat "0.1.0-SNAPSHOT" (defproject cat "0.1.0-SNAPSHOT"
:description "A cuddle graph for zeus people" :description "A cuddle graph for zeus people"
;:url "http://example.com/FIXME" ;:url "http://example.com/FIXME"
:dependencies [[buddy "2.0.0"] :dependencies [[buddy "2.0.0"]
[com.cemerick/friend "0.2.3"]
[cheshire "5.8.1"] [cheshire "5.8.1"]
[clj-oauth "1.5.5"] [clj-oauth "1.5.5"]
[clojure.java-time "0.3.2"] [clojure.java-time "0.3.2"]
[com.cemerick/friend "0.2.3"]
[com.cognitect/transit-clj "0.8.313"] [com.cognitect/transit-clj "0.8.313"]
[com.google.protobuf/protobuf-java "3.6.1"]
[compojure "1.6.1"] [compojure "1.6.1"]
[conman "0.8.3"] [conman "0.8.3"]
[cprop "0.1.13"] [cprop "0.1.13"]
[funcool/promesa "1.9.0"]
[funcool/struct "1.3.0"] [funcool/struct "1.3.0"]
[luminus-immutant "0.2.4"] [luminus-immutant "0.2.4"]
[luminus-migrations "0.6.3"] [luminus-migrations "0.6.3"]
@ -20,17 +22,16 @@
[markdown-clj "1.0.5"] [markdown-clj "1.0.5"]
[metosin/muuntaja "0.6.3"] [metosin/muuntaja "0.6.3"]
[metosin/ring-http-response "0.9.1"] [metosin/ring-http-response "0.9.1"]
[slingshot "0.12.1"] [metosin/vega-tools "0.2.0"]
[mount "0.1.15"] [mount "0.1.15"]
[mysql/mysql-connector-java "8.0.12"]
[nrepl "0.5.3"] [nrepl "0.5.3"]
[org.clojure/clojure "1.10.0"] [org.clojure/clojure "1.10.0"]
[org.clojure/clojurescript "1.10.439" :scope "provided"] [org.clojure/clojurescript "1.10.439" :scope "provided"]
[org.clojure/tools.cli "0.4.1"] [org.clojure/tools.cli "0.4.1"]
[org.clojure/tools.logging "0.4.1"] [org.clojure/tools.logging "0.4.1"]
;[org.postgresql/postgresql "42.2.5"] ;[org.postgresql/postgresql "42.2.5"]
[mysql/mysql-connector-java "8.0.12"] ;https://www.webjars.org/
[com.google.protobuf/protobuf-java "3.6.1"]
;https://www.webjars.org/
[org.webjars.npm/bulma "0.7.2"] [org.webjars.npm/bulma "0.7.2"]
[org.webjars/font-awesome "5.6.1"] [org.webjars/font-awesome "5.6.1"]
[org.webjars/webjars-locator "0.34"] [org.webjars/webjars-locator "0.34"]
@ -38,8 +39,7 @@
[ring/ring-core "1.7.1"] [ring/ring-core "1.7.1"]
[ring/ring-defaults "0.3.2"] [ring/ring-defaults "0.3.2"]
[selmer "1.12.5"] [selmer "1.12.5"]
[metosin/vega-tools "0.2.0"] [slingshot "0.12.1"]]
[funcool/promesa "1.9.0"]]

View file

@ -2,30 +2,42 @@
(:require [cat.middleware :as middleware] (:require [cat.middleware :as middleware]
[cat.layout :refer [error-page]] [cat.layout :refer [error-page]]
[cat.routes.home :refer [home-routes]] [cat.routes.home :refer [home-routes]]
[cat.routes.oauth :refer [oauth-routes]] [cat.routes.oauth :refer [oauth-init oauth-callback clear-session!]]
[cat.routes.admin :refer [admin-routes]] [cat.routes.admin :refer [set-admin! create-new-relation! create-user!]]
[compojure.core :refer [routes wrap-routes]] [compojure.core :refer [routes defroutes GET POST wrap-routes]]
[ring.util.http-response :as response] [ring.util.http-response :as response]
[compojure.route :as route] [compojure.route :as route]
[cat.env :refer [defaults]] [cat.env :refer [defaults]]
[clojure.tools.logging :as log]
[mount.core :as mount])) [mount.core :as mount]))
(mount/defstate init-app (mount/defstate init-app
:start ((or (:init defaults) identity)) :start ((or (:init defaults) identity))
:stop ((or (:stop defaults) identity))) :stop ((or (:stop defaults) identity)))
(defroutes oauth-routes
(GET "/oauth/oauth-init" req (oauth-init req))
(GET "/oauth/oauth-callback" req (oauth-callback req))
(GET "/logout" req (clear-session! "/")))
(defroutes admin-routes
(GET "/admin/enable" req (set-admin! req true))
(GET "/admin/disable" req (set-admin! req false))
(POST "/relations" req (create-new-relation! req))
(POST "/users" req (create-user! req)))
(mount/defstate app (mount/defstate app
:start :start
(middleware/wrap-base (middleware/wrap-base
(routes (routes
(-> #'home-routes (-> #'home-routes
(wrap-routes middleware/wrap-csrf) (wrap-routes middleware/wrap-csrf)
(wrap-routes middleware/wrap-formats)) (wrap-routes middleware/wrap-formats))
#'oauth-routes #'oauth-routes
(-> #'admin-routes (-> #'admin-routes
(wrap-routes middleware/wrap-restricted)) (wrap-routes middleware/wrap-restricted))
(route/not-found (route/not-found
(:body (:body
(error-page {:status 404 (error-page {:status 404
:title "page not found"})))))) :title "page not found"}))))))

View file

@ -12,19 +12,19 @@
:client-secret (env :oauth-consumer-secret) :client-secret (env :oauth-consumer-secret)
:authorize-uri (env :authorize-uri) :authorize-uri (env :authorize-uri)
:redirect-uri (str (env :app-host) "/oauth/oauth-callback") :redirect-uri (str (env :app-host) "/oauth/oauth-callback")
:access-token-uri (env :access-token-uri) :access-token-uri (env :access-token-uri)})
})
; To authorize, redirect the user to the sign in / grant page ; To authorize, redirect the user to the sign in / grant page
(defn- authorize-uri (defn- authorize-uri
[client-params #_csrf-token] [client-params #_csrf-token]
(str (str
(:authorize-uri client-params) (:authorize-uri client-params)
"?" "?"
(httpclient/generate-query-string {:response_type "code" (httpclient/generate-query-string {:response_type "code"
:client_id (:client-id client-params) :client_id (:client-id client-params)
:redirect_uri (:redirect-uri client-params)}) :redirect_uri (:redirect-uri client-params)})
;"response_type=code" ;"response_type=code"
;"&client_id=" ;"&client_id="
;(url-encode (:client-id client-params)) ;(url-encode (:client-id client-params))
@ -34,7 +34,7 @@
;(url-encode (:scope client-params)) ;(url-encode (:scope client-params))
;"&state=" ;"&state="
;(url-encode csrf-token) ;(url-encode csrf-token)
)) ))
(defn authorize-api-uri (defn authorize-api-uri
"let the user authorize access by redirecting to the signin / grant page "let the user authorize access by redirecting to the signin / grant page
@ -51,20 +51,21 @@
(do (do
(log/debug "Requesting access token with code " code) (log/debug "Requesting access token with code " code)
(let [oauth2-params (oauth2-params) (let [oauth2-params (oauth2-params)
access-token (httpclient/post (:access-token-uri oauth2-params) resp (httpclient/post (:access-token-uri oauth2-params)
{:form-params {:code code {:form-params {:code code
:grant_type "authorization_code" :grant_type "authorization_code"
:client_id (:client-id oauth2-params) :client_id (:client-id oauth2-params)
:client_secret (:client-secret oauth2-params) :client_secret (:client-secret oauth2-params)
:redirect_uri (:redirect-uri oauth2-params)} :redirect_uri (:redirect-uri oauth2-params)}
;:basic-auth [(:client-id oauth2-params) (:client-secret oauth2-params)]
:as :json :as :json
:insecure? true :throw-exceptions false
})] :insecure? true})]
(log/debug "Access token response:" access-token) (condp = (:status resp)
(:body access-token))) 200 (:body resp)
(catch Exception e (log/error "Something terrible happened..." e))) 401 (-> {:status 401 :body "Invalid authentication credentials"})
nil)) {:status 500 :body "Something went pear-shape when trying to authenticate"})))
)
(log/info "Invalid csrf token whilst authenticating")))
(defn get-user-info (defn get-user-info
"User info API call" "User info API call"
@ -73,30 +74,31 @@
(-> (httpclient/get url {:oauth-token access-token (-> (httpclient/get url {:oauth-token access-token
:as :json :as :json
:insecure? true}) :insecure? true})
:body) :body)))
))
; Refresh token when it expires ; Refresh token when it expires
(defn- refresh-tokens (defn- refresh-tokens
"Request a new token pair" "Request a new token pair"
[refresh-token] [refresh-token]
(try+ (try+
(let [oauth2-params (oauth2-params) (let [oauth2-params (oauth2-params)
{{access-token :access_token refresh-token :refresh_token} :body} {{access-token :access_token refresh-token :refresh_token} :body}
(httpclient/post (:access-token-uri oauth2-params) (httpclient/post (:access-token-uri oauth2-params)
{:form-params {:grant_type "refresh_token" {:form-params {:grant_type "refresh_token"
:refresh_token refresh-token} :refresh_token refresh-token}
:basic-auth [(:client-id oauth2-params) (:client-secret oauth2-params)] :basic-auth [(:client-id oauth2-params) (:client-secret oauth2-params)]
:as :json :as :json
:insecure? true})] :insecure? true})]
[access-token refresh-token]) [access-token refresh-token])
(catch [:status 401] _ nil))) (catch [:status 401] _ nil)))
(defn get-fresh-tokens (defn get-fresh-tokens
"Returns current token pair if they have not expired, or a refreshed token pair otherwise" "Returns current token pair if they have not expired, or a refreshed token pair otherwise"
[access-token refresh-token] [access-token refresh-token]
(try+ (try+
(and (get-user-info access-token) (and (get-user-info access-token)
[access-token refresh-token]) [access-token refresh-token])
(catch [:status 401] _ (refresh-tokens refresh-token)))) (catch [:status 401] _ (refresh-tokens refresh-token))))

View file

@ -1,6 +1,5 @@
(ns cat.routes.admin (ns cat.routes.admin
(:require [cat.db.core :refer [*db*] :as db] (:require [cat.db.core :refer [*db*] :as db]
[compojure.core :refer [defroutes GET POST]]
[struct.core :as st] [struct.core :as st]
[clojure.tools.logging :as log] [clojure.tools.logging :as log]
[ring.util.http-response :as response])) [ring.util.http-response :as response]))
@ -13,29 +12,28 @@
[[:from_id st/required st/integer-str] [[:from_id st/required st/integer-str]
[:to_id st/required st/integer-str]]) [:to_id st/required st/integer-str]])
(defroutes admin-routes (defn set-admin! [req enabled?]
(GET "/admin/enable" req (-> (response/found "/") (-> (response/found "/")
(assoc :session (assoc-in (:session req) [:user :admin :enabled] true)))) (assoc :session (assoc-in (:session req) [:user :admin :enabled] enabled?))))
(GET "/admin/disable" req (-> (response/found "/")
(assoc :session (assoc-in (:session req) [:user :admin :enabled] false))))
(POST "/relations" req (defn create-new-relation! [req]
(let [data (:params req) [err result] (st/validate data relation-schema)] (let [data (:params req)
(log/info "Post to " (:uri req)) [err result] (st/validate data relation-schema)]
(if (nil? err) (if (nil? err)
(do (do
(db/create-relation! result) (log/info "Admin creates relation from " (:from_id data) "to" (:to_id data))
(response/found "/")) (db/create-relation! result)
(do (response/found "/"))
(response/bad-request "Incorrect input"))))) (do
(POST "/users" req (response/bad-request "Incorrect input")))))
(let [data (:params req)]
(log/info "Post to " (:uri req)) (defn create-user! [req]
(println data) (let [data (:params req)]
(if (st/valid? data user-schema) (println data)
(do (if (st/valid? data user-schema)
(db/create-user! (assoc data :zeusid nil)) (do
(response/found "/")) (log/info "Admin creates user: " (:name data))
(do (db/create-user! (assoc data :zeusid nil))
(response/bad-request "Incorrect input"))))) (response/found "/"))
) (do
(response/bad-request "Incorrect input")))))

View file

@ -29,7 +29,7 @@
(assoc :session nil))) (assoc :session nil)))
(defn oauth-init (defn oauth-init
"Initiates the Twitter OAuth" "Initiates the OAuth"
[request] [request]
(let [reee (mo/authorize-api-uri)] (let [reee (mo/authorize-api-uri)]
(log/debug "authorize uri: " reee) (log/debug "authorize uri: " reee)
@ -40,13 +40,14 @@
"Handles the callback from adams with the access_token "Handles the callback from adams with the access_token
Fetches the user from the database, creating a new one if not found Fetches the user from the database, creating a new one if not found
Sets the user in the session and redirects back to origin \"/\" " Sets the user in the session and redirects back to origin \"/\" "
[req_token {:keys [params session]}]
[{:keys [params session]}]
; oauth request was denied by user ; oauth request was denied by user
(if (:denied params) (if (:denied params)
(-> (found "/") (-> (found "/")
(assoc :flash {:denied true})) (assoc :flash {:denied true}))
; fetch the request token and do anything else you wanna do if not denied. ; fetch the request token and do anything else you wanna do if not denied.
(let [{:keys [access_token refresh_token]} (mo/get-authentication-response nil req_token) (let [{:keys [access_token refresh_token]} (mo/get-authentication-response nil params)
fetched-user (mo/get-user-info access_token) fetched-user (mo/get-user-info access_token)
local-user (db/get-zeus-user {:zeusid (:id fetched-user)})] local-user (db/get-zeus-user {:zeusid (:id fetched-user)})]
(if local-user (if local-user
@ -78,7 +79,3 @@
; 401 (println error)))) ; 401 (println error))))
(defroutes oauth-routes
(GET "/oauth/oauth-init" req (oauth-init req))
(GET "/oauth/oauth-callback" [& req_token :as req] (oauth-callback req_token req))
(GET "/logout" req (clear-session! "/")))