finally fix oauth login

This commit is contained in:
flynn 2019-01-14 02:39:40 +01:00
parent 3ea91cd11f
commit 661435beb4
8 changed files with 191 additions and 48 deletions

23
cat.iml
View file

@ -21,6 +21,7 @@
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="vega" level="application" />
<orderEntry type="library" name="Leiningen: aopalliance:1.0" level="project" />
<orderEntry type="library" name="Leiningen: args4j:2.0.26" level="project" />
<orderEntry type="library" name="Leiningen: binaryage/devtools:0.9.10" level="project" />
<orderEntry type="library" name="Leiningen: binaryage/env-config:0.2.2" level="project" />
@ -50,6 +51,7 @@
<orderEntry type="library" name="Leiningen: co.deps/ring-etag-middleware:0.2.0" level="project" />
<orderEntry type="library" name="Leiningen: com.andrewmcveigh/cljs-time:0.5.2" level="project" />
<orderEntry type="library" name="Leiningen: com.carouselapps/to-jdbc-uri:0.5.0" level="project" />
<orderEntry type="library" name="Leiningen: com.cemerick/friend:0.2.3" level="project" />
<orderEntry type="library" name="Leiningen: com.cognitect/transit-clj:0.8.313" level="project" />
<orderEntry type="library" name="Leiningen: com.cognitect/transit-cljs:0.8.256" level="project" />
<orderEntry type="library" name="Leiningen: com.cognitect/transit-java:0.8.337" level="project" />
@ -64,6 +66,7 @@
<orderEntry type="library" name="Leiningen: com.google.code.gson/gson:2.7" level="project" />
<orderEntry type="library" name="Leiningen: com.google.errorprone/error_prone_annotations:2.0.18" level="project" />
<orderEntry type="library" name="Leiningen: com.google.guava/guava:25.1-jre" level="project" />
<orderEntry type="library" name="Leiningen: com.google.inject/guice:2.0" level="project" />
<orderEntry type="library" name="Leiningen: com.google.j2objc/j2objc-annotations:1.1" level="project" />
<orderEntry type="library" name="Leiningen: com.google.javascript/closure-compiler-externs:v20180805" level="project" />
<orderEntry type="library" name="Leiningen: com.google.javascript/closure-compiler-unshaded:v20180805" level="project" />
@ -78,10 +81,10 @@
<orderEntry type="library" name="Leiningen: com.stuartsierra/component:0.3.2" level="project" />
<orderEntry type="library" name="Leiningen: com.stuartsierra/dependency:0.2.0" level="project" />
<orderEntry type="library" name="Leiningen: com.zaxxer/HikariCP:3.2.0" level="project" />
<orderEntry type="library" name="Leiningen: commons-codec:1.8" level="project" />
<orderEntry type="library" name="Leiningen: commons-codec:1.6" level="project" />
<orderEntry type="library" name="Leiningen: commons-fileupload:1.3.3" level="project" />
<orderEntry type="library" name="Leiningen: commons-io:2.6" level="project" />
<orderEntry type="library" name="Leiningen: commons-logging:1.2" level="project" />
<orderEntry type="library" name="Leiningen: commons-logging:1.1.1" level="project" />
<orderEntry type="library" name="Leiningen: compojure:1.6.1" level="project" />
<orderEntry type="library" name="Leiningen: conman:0.8.3" level="project" />
<orderEntry type="library" name="Leiningen: cprop:0.1.13" level="project" />
@ -129,14 +132,16 @@
<orderEntry type="library" name="Leiningen: net.cgrand/sjacket:0.1.1" level="project" />
<orderEntry type="library" name="Leiningen: net.incongru.watchservice/barbary-watchservice:1.0" level="project" />
<orderEntry type="library" name="Leiningen: net.java.dev.jna/jna:3.2.2" level="project" />
<orderEntry type="library" name="Leiningen: net.jcip/jcip-annotations:1.0" level="project" />
<orderEntry type="library" name="Leiningen: net.jodah/expiringmap:0.5.8" level="project" />
<orderEntry type="library" name="Leiningen: net.sourceforge.nekohtml/nekohtml:1.9.10" level="project" />
<orderEntry type="library" name="Leiningen: nrepl/bencode:1.0.0" level="project" />
<orderEntry type="library" name="Leiningen: nrepl:0.5.3" level="project" />
<orderEntry type="library" name="Leiningen: ns-tracker:0.3.1" level="project" />
<orderEntry type="library" name="Leiningen: org.apache.commons/commons-compress:1.9" level="project" />
<orderEntry type="library" name="Leiningen: org.apache.commons/commons-lang3:3.1" level="project" />
<orderEntry type="library" name="Leiningen: org.apache.httpcomponents/httpclient:4.5" level="project" />
<orderEntry type="library" name="Leiningen: org.apache.httpcomponents/httpcore:4.4.1" level="project" />
<orderEntry type="library" name="Leiningen: org.apache.httpcomponents/httpclient:4.3.5" level="project" />
<orderEntry type="library" name="Leiningen: org.apache.httpcomponents/httpcore:4.3.2" level="project" />
<orderEntry type="library" name="Leiningen: org.apache.httpcomponents/httpmime:4.5" level="project" />
<orderEntry type="library" name="Leiningen: org.bouncycastle/bcpkix-jdk15on:1.54" level="project" />
<orderEntry type="library" name="Leiningen: org.bouncycastle/bcprov-jdk15on:1.54" level="project" />
@ -145,12 +150,12 @@
<orderEntry type="library" name="Leiningen: org.clojure/clojure:1.10.0" level="project" />
<orderEntry type="library" name="Leiningen: org.clojure/clojurescript:1.10.439" level="project" />
<orderEntry type="library" name="Leiningen: org.clojure/core.async:0.4.474" level="project" />
<orderEntry type="library" name="Leiningen: org.clojure/core.cache:0.6.5" level="project" />
<orderEntry type="library" name="Leiningen: org.clojure/core.cache:0.6.3" level="project" />
<orderEntry type="library" name="Leiningen: org.clojure/core.memoize:0.5.9" level="project" />
<orderEntry type="library" name="Leiningen: org.clojure/core.rrb-vector:0.0.11" level="project" />
<orderEntry type="library" name="Leiningen: org.clojure/core.specs.alpha:0.2.44" level="project" />
<orderEntry type="library" name="Leiningen: org.clojure/data.json:0.2.6" level="project" />
<orderEntry type="library" name="Leiningen: org.clojure/data.priority-map:0.0.7" level="project" />
<orderEntry type="library" name="Leiningen: org.clojure/data.priority-map:0.0.2" level="project" />
<orderEntry type="library" name="Leiningen: org.clojure/google-closure-library-third-party:0.0-20170809-b9c14c6b" level="project" />
<orderEntry type="library" name="Leiningen: org.clojure/google-closure-library:0.0-20170809-b9c14c6b" level="project" />
<orderEntry type="library" name="Leiningen: org.clojure/java.classpath:0.2.3" level="project" />
@ -174,8 +179,10 @@
<orderEntry type="library" name="Leiningen: org.jboss.spec.javax.websocket/jboss-websocket-api_1.1_spec:1.1.0.Final" level="project" />
<orderEntry type="library" name="Leiningen: org.jboss.xnio/xnio-api:3.3.6.Final" level="project" />
<orderEntry type="library" name="Leiningen: org.jboss.xnio/xnio-nio:3.3.6.Final" level="project" />
<orderEntry type="library" name="Leiningen: org.mindrot/jbcrypt:0.3m" level="project" />
<orderEntry type="library" name="Leiningen: org.mozilla/rhino:1.7R5" level="project" />
<orderEntry type="library" name="Leiningen: org.msgpack/msgpack:0.6.12" level="project" />
<orderEntry type="library" name="Leiningen: org.openid4java/openid4java-nodeps:0.9.6" level="project" />
<orderEntry type="library" name="Leiningen: org.ow2.asm/asm-all:4.2" level="project" />
<orderEntry type="library" name="Leiningen: org.ow2.asm/asm:5.1" level="project" />
<orderEntry type="library" name="Leiningen: org.postgresql/postgresql:42.2.5" level="project" />
@ -208,10 +215,12 @@
<orderEntry type="library" name="Leiningen: ring/ring-ssl:0.3.0" level="project" />
<orderEntry type="library" name="Leiningen: selmer:1.12.5" level="project" />
<orderEntry type="library" name="Leiningen: simple-lein-profile-merge:0.1.4" level="project" />
<orderEntry type="library" name="Leiningen: slingshot:0.12.2" level="project" />
<orderEntry type="library" name="Leiningen: slingshot:0.12.1" level="project" />
<orderEntry type="library" name="Leiningen: strictly-specking-standalone:0.1.1" level="project" />
<orderEntry type="library" name="Leiningen: suspendable:0.1.1" level="project" />
<orderEntry type="library" name="Leiningen: tigris:0.1.1" level="project" />
<orderEntry type="library" name="Leiningen: virgil:0.1.6" level="project" />
<orderEntry type="library" name="Leiningen: xerces/xercesImpl:2.8.1" level="project" />
<orderEntry type="library" name="Leiningen: xml-apis:1.3.03" level="project" />
</component>
</module>

View file

@ -4,6 +4,7 @@
;:url "http://example.com/FIXME"
:dependencies [[buddy "2.0.0"]
[com.cemerick/friend "0.2.3"]
[cheshire "5.8.1"]
[clj-oauth "1.5.5"]
[clojure.java-time "0.3.2"]
@ -19,6 +20,7 @@
[markdown-clj "1.0.5"]
[metosin/muuntaja "0.6.3"]
[metosin/ring-http-response "0.9.1"]
[slingshot "0.12.1"]
[mount "0.1.15"]
[nrepl "0.5.3"]
[org.clojure/clojure "1.10.0"]

View file

@ -4,7 +4,7 @@
<title>Something bad happened</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% style "/assets/bootstrap/css/bootstrap.min.css" %}
{% style "/assets/bulma/css/bulma.css" %}
<style type="text/css">
html {
height: 100%;
@ -13,19 +13,22 @@
overflow: hidden;
width: 100%;
}
html body {
height: 100%;
margin: 0;
padding: 0;
width: 100%;
}
html .container-fluid {
html .section {
display: table;
height: 100%;
padding: 0;
width: 100%;
}
html .row-fluid {
html .container {
display: table-cell;
height: 100%;
vertical-align: middle;
@ -33,21 +36,18 @@
</style>
</head>
<body>
<div class="container-fluid">
<div class="row-fluid">
<div class="col-lg-12">
<div class="centering text-center">
<div class="text-center">
<h1><span class="text-danger">Error: {{status}}</span></h1>
<hr>
{% if title %}
<h2 class="without-margin">{{title}}</h2>
{% endif %}
{% if message %}
<h4 class="text-danger">{{message}}</h4>
{% endif %}
</div>
</div>
<div class="section">
<div class="container">
<div class="has-text-centered">
<h1 class="title is-3"><span class="has-text-danger">Error: {{status}}</span></h1>
<hr>
{% if title %}
<h2 class="subtitle is-3">{{title}}</h2>
{% endif %}
{% if message %}
<h4 class="subtitle is-5 has-text-danger">{{message}}</h4>
{% endif %}
</div>
</div>
</div>

View file

@ -26,7 +26,7 @@
<div class="navbar-start">
<div class="navbar-item">
{% if user %}
Hello User!
Hello {{ user.username }}
{% else %}
Please login to use the graph
{% endif %}
@ -63,12 +63,18 @@
<div class="navbar-end">
<div class="navbar-item">
<div class="buttons">
{% if user %}
<a class="button is-light" href="/logout">
<strong>Logout</strong>
</a>
{% else %}
<a class="button is-info">
<strong>Sign up</strong>
</a>
<a class="button is-light">
<a class="button is-light" href="/oauth/oauth-init">
Log in
</a>
{% endif %}
</div>
</div>
</div>

100
src/clj/cat/moauth.clj Normal file
View file

@ -0,0 +1,100 @@
(ns cat.moauth
(:require [cat.config :refer [env]]
[clj-http.client :as httpclient]
[slingshot.slingshot :refer [try+]]
[clojure.tools.logging :as log]
[cat.layout :refer [error-page]]))
; Inspired by https://leonid.shevtsov.me/post/oauth2-is-easy/
(defn- oauth2-params []
{:client-id (env :oauth-consumer-key)
:client-secret (env :oauth-consumer-secret)
:authorize-uri (env :authorize-uri)
:redirect-uri (str (env :app-host) "/oauth/oauth-callback")
:access-token-uri (env :access-token-uri)
;:scope "activity profile"
})
; To authorize, redirect the user to the sign in / grant page
(defn- authorize-uri
[client-params ;csrf-token
]
(str
(:authorize-uri client-params)
"?"
(httpclient/generate-query-string {:response_type "code"
:client_id (:client-id client-params)
:redirect_uri (:redirect-uri client-params)})
;"response_type=code"
;"&client_id="
;(url-encode (:client-id client-params))
;"&redirect_uri="
;(url-encode (:redirect-uri client-params))
;"&scope="
;(url-encode (:scope client-params))
;"&state="
;(url-encode csrf-token)
))
(defn authorize-api-uri
"let the user authorize access by redirecting to the signin / grant page
of the used oauth api"
[]
(authorize-uri (oauth2-params)))
(defn get-authentication-response
"Request an access token with the obtained unique code from the grant page"
[csrf-token {:keys [state code]}]
(if (or true (= csrf-token state))
(try
(do
(log/info "Requesting access token with code " code)
(let [oauth2-params (oauth2-params)
access-token (httpclient/post (:access-token-uri oauth2-params)
{:form-params {:code code
:grant_type "authorization_code"
:client_id (:client-id oauth2-params)
:client_secret (:client-secret oauth2-params)
:redirect_uri (:redirect-uri oauth2-params)}
;:basic-auth [(:client-id oauth2-params) (:client-secret oauth2-params)]
:as :json
})]
(println "Access token response:" access-token)
(:body access-token)))
(catch Exception e (log/error "Something terrible happened..." e)))
nil))
(defn get-user-info
"User info API call"
[access-token]
(let [url (str (env :user-api-uri))]
(-> (httpclient/get url {:oauth-token access-token
:as :json})
:body)
))
; Refresh token when it expires
(defn- refresh-tokens
"Request a new token pair"
[refresh-token]
(try+
(let [oauth2-params (oauth2-params)
{{access-token :access_token refresh-token :refresh_token} :body}
(httpclient/post (:access-token-uri oauth2-params)
{:form-params {:grant_type "refresh_token"
:refresh_token refresh-token}
:basic-auth [(:client-id oauth2-params) (:client-secret oauth2-params)]
:as :json})]
[access-token refresh-token])
(catch [:status 401] _ nil)))
(defn get-fresh-tokens
"Returns current token pair if they have not expired, or a refreshed token pair otherwise"
[access-token refresh-token]
(try+
(and (get-user-info access-token)
[access-token refresh-token])
(catch [:status 401] _ (refresh-tokens refresh-token))))

View file

@ -16,14 +16,17 @@
(defn oauth-callback-uri
"Generates the oauth request callback URI"
[{:keys [headers]}]
(str (headers "x-forwarded-proto") "://" (headers "host") "/oauth/oauth-callback"))
(let [callback-url (str "http://" (headers "host") "/oauth/oauth-callback")]
(println "Generated callback url:" callback-url)
callback-url))
(defn fetch-request-token
"Fetches a request token."
[request]
(let [callback-uri (oauth-callback-uri request)]
(log/info "Fetching request token using callback-uri" callback-uri)
(oauth/request-token consumer (oauth-callback-uri request))))
(log/info "Oauth consumer: " consumer)
(oauth/request-token consumer callback-uri {:grant_type "authorization_code"})))
(defn fetch-access-token
[request_token]
@ -31,5 +34,9 @@
(defn auth-redirect-uri
"Gets the URI the user should be redirected to when authenticating."
[request-token]
(str (oauth/user-approval-uri consumer request-token)))
([request]
(auth-redirect-uri request ""))
([request request-token]
(str (oauth/user-approval-uri consumer request-token {:response_type "code"
:client_id (env :oauth-consumer-key)
:redirect_uri (oauth-callback-uri request)}))))

View file

@ -7,7 +7,8 @@
[struct.core :as st]
[clojure.edn :as edn]
[clojure.tools.logging :as log]
[clojure.data.json :as json]))
[clojure.data.json :as json]
[oauth.client :as oauth]))
(def user-schema
[[:name st/required st/string]
@ -29,10 +30,11 @@
(db/get-users))
(defroutes home-routes
(GET "/" []
(GET "/" req
(let [users (get-users)
relations (get-relations)]
(home-page {:relations relations :users users :user {}})))
(log/info (str "Session: " (:session req)))
(home-page {:relations relations :users users :user (get-in req [:session :user])})))
;(GET "/docs" []
; (-> (response/ok (-> "docs/docs.md" io/resource slurp))
; (response/header "Content-Type" "text/plain; charset=utf-8")))

View file

@ -3,32 +3,49 @@
[compojure.core :refer [defroutes GET]]
[clojure.java.io :as io]
[cat.oauth :as oauth]
[clojure.tools.logging :as log]))
[clojure.tools.logging :as log]
[cat.moauth :as mo]))
(defn set-user! [user session redirect-url]
(-> (found redirect-url)
(assoc :session (assoc session :user user))))
(defn remove-user! [session redirect-url]
(-> (found redirect-url)
(assoc :session (dissoc session :user))))
(defn clear-session! [redirect-url]
(-> (found redirect-url)
(dissoc :session)))
(defn oauth-init
"Initiates the Twitter OAuth"
[request]
(-> (oauth/fetch-request-token request)
:oauth_token
oauth/auth-redirect-uri
(-> (mo/authorize-api-uri)
found))
(defn oauth-callback
"Handles the callback from Twitter."
[{:keys [session params]}]
"Handles the callback from adams."
[req_token {:keys [params session]}]
; oauth request was denied by user
(if (:denied params)
(-> (found "/")
(assoc :flash {:denied true}))
; fetch the request token and do anything else you wanna do if not denied.
(let [{:keys [user_id screen_name]} (oauth/fetch-access-token params)]
(log/info "successfully authenticated as" user_id screen_name)
(-> (found "/")
(assoc :session
(assoc session :user-id user_id :screen-name screen_name))))))
(let [{:keys [access_token refresh_token]} (mo/get-authentication-response nil req_token)]
(log/info "Successfully fetched access-id: " access_token)
(log/info "Fetching user info")
(let [user (mo/get-user-info access_token)]
(log/info "User info: " user)
(set-user! user session "/")))))
;(catch [:status 401] _
; (error-page {:status 401
; :title "Error authenticating"
; :message "Please contact your system administrator to fix this issue"}))
(defroutes oauth-routes
(GET "/oauth/oauth-init" req (oauth-init req))
(GET "/oauth/oauth-callback" [& req_token :as req] (oauth-callback req)))
(GET "/oauth/oauth-init" req (oauth-init req))
(GET "/oauth/oauth-callback" [& req_token :as req] (oauth-callback req_token req))
(GET "/logout" req (remove-user! (:session req) "/")))