feat(grfn/bbbg): Allow Organizers to sign in via Discord
Allow users with the Organizers role to sign in via a Discord Oauth2 handshake, creating a user in the users table and adding the ID of that user to the session. Change-Id: I39d9e17433e71b07314b9eabb787fb9214289772 Reviewed-on: https://cl.tvl.fyi/c/depot/+/4409 Tested-by: BuildkiteCI Reviewed-by: grfn <grfn@gws.fyi> Autosubmit: grfn <grfn@gws.fyi>
This commit is contained in:
parent
1205b42ee0
commit
2bc7429641
10 changed files with 409 additions and 18 deletions
10
users/grfn/bbbg/src/bbbg/db/user.clj
Normal file
10
users/grfn/bbbg/src/bbbg/db/user.clj
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
(ns bbbg.db.user
|
||||
(:require [bbbg.db :as db]
|
||||
[bbbg.user :as user]))
|
||||
|
||||
(defn create! [db attrs]
|
||||
(db/insert! db
|
||||
:public.user
|
||||
(select-keys attrs [::user/id
|
||||
::user/username
|
||||
::user/discord-user-id])))
|
||||
43
users/grfn/bbbg/src/bbbg/discord.clj
Normal file
43
users/grfn/bbbg/src/bbbg/discord.clj
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
(ns bbbg.discord
|
||||
(:refer-clojure :exclude [get])
|
||||
(:require [clj-http.client :as http]
|
||||
[clojure.string :as str]
|
||||
[bbbg.util.core :as u]))
|
||||
|
||||
(def base-uri "https://discord.com/api")
|
||||
|
||||
(defn api-uri [path]
|
||||
(str base-uri
|
||||
(when-not (str/starts-with? path "/") "/")
|
||||
path))
|
||||
|
||||
(defn get
|
||||
([token path]
|
||||
(get token path {}))
|
||||
([token path params]
|
||||
(:body
|
||||
(http/get (api-uri path)
|
||||
(-> params
|
||||
(assoc :accept :json
|
||||
:as :json)
|
||||
(assoc-in [:headers "authorization"]
|
||||
(str "Bearer " (:token token))))))))
|
||||
|
||||
(defn me [token]
|
||||
(get token "/users/@me"))
|
||||
|
||||
(defn guilds [token]
|
||||
(get token "/users/@me/guilds"))
|
||||
|
||||
(defn guild-member [token guild-id]
|
||||
(get token (str "/users/@me/guilds/" guild-id "/member")))
|
||||
|
||||
(comment
|
||||
(def token {:token (u/pass "bbbg/test-token")})
|
||||
(me token)
|
||||
(guilds token)
|
||||
(guild-member token "841295283564052510")
|
||||
|
||||
(get token "/guilds/841295283564052510/roles")
|
||||
|
||||
)
|
||||
83
users/grfn/bbbg/src/bbbg/discord/auth.clj
Normal file
83
users/grfn/bbbg/src/bbbg/discord/auth.clj
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
(ns bbbg.discord.auth
|
||||
(:require
|
||||
[bbbg.discord :as discord]
|
||||
[bbbg.util.core :as u]
|
||||
clj-time.coerce
|
||||
[clojure.spec.alpha :as s]
|
||||
[config.core :refer [env]]
|
||||
[ring.middleware.oauth2 :refer [wrap-oauth2]]))
|
||||
|
||||
(s/def ::client-id string?)
|
||||
(s/def ::client-secret string?)
|
||||
(s/def ::bbbg-guild-id string?)
|
||||
(s/def ::bbbg-organizer-role string?)
|
||||
|
||||
(s/def ::config (s/keys :req [::client-id
|
||||
::client-secret
|
||||
::bbbg-guild-id
|
||||
::bbbg-organizer-role]))
|
||||
|
||||
;;;
|
||||
|
||||
(defn env->config []
|
||||
(s/assert
|
||||
::config
|
||||
{::client-id (:discord-client-id env)
|
||||
::client-secret (:discord-client-secret env)
|
||||
::bbbg-guild-id (:bbbg-guild-id env "841295283564052510")
|
||||
::bbbg-organizer-role (:bbbg-organizer-role
|
||||
env
|
||||
;; TODO this might not be the right id
|
||||
"902593101758091294")}))
|
||||
|
||||
(defn dev-config []
|
||||
(s/assert
|
||||
::config
|
||||
{::client-id (u/pass "bbbg/discord-client-id")
|
||||
::client-secret (u/pass "bbbg/discord-client-secret")
|
||||
::bbbg-guild-id "841295283564052510"
|
||||
;; TODO this might not be the right id
|
||||
::bbbg-organizer-role "874846495873040395"}))
|
||||
|
||||
;;;
|
||||
|
||||
(def access-token-url
|
||||
"https://discord.com/api/oauth2/token")
|
||||
|
||||
(def authorization-url
|
||||
"https://discord.com/api/oauth2/authorize")
|
||||
|
||||
(def revoke-url
|
||||
"https://discord.com/api/oauth2/token/revoke")
|
||||
|
||||
(def scopes ["guilds"
|
||||
"guilds.members.read"
|
||||
"identify"])
|
||||
|
||||
(defn discord-oauth-profile [env]
|
||||
{:authorize-uri authorization-url
|
||||
:access-token-uri access-token-url
|
||||
:client-id (::client-id env)
|
||||
:client-secret (::client-secret env)
|
||||
:scopes scopes
|
||||
:launch-uri "/auth/discord"
|
||||
:redirect-uri "/auth/discord/redirect"
|
||||
:landing-uri "/auth/success"})
|
||||
|
||||
(defn wrap-discord-auth [handler env]
|
||||
(wrap-oauth2 handler {:discord (discord-oauth-profile env)}))
|
||||
|
||||
(defn check-discord-auth
|
||||
"Check that the user with the given token has the correct level of discord
|
||||
auth"
|
||||
[{::keys [bbbg-guild-id bbbg-organizer-role]} token]
|
||||
(and (some (comp #{bbbg-guild-id} :id)
|
||||
(discord/guilds token))
|
||||
(some #{bbbg-organizer-role}
|
||||
(:roles (discord/guild-member token bbbg-guild-id)))))
|
||||
|
||||
(comment
|
||||
(#'ring.middleware.oauth2/valid-profile?
|
||||
(discord-oauth-profile
|
||||
(dev-config)))
|
||||
)
|
||||
|
|
@ -1,17 +1,49 @@
|
|||
(ns bbbg.handlers.home
|
||||
(:require
|
||||
[bbbg.db.user :as db.user]
|
||||
[bbbg.discord.auth :as discord.auth]
|
||||
[bbbg.handlers.core :refer [page-response]]
|
||||
[compojure.core :refer [GET routes]]))
|
||||
[bbbg.user :as user]
|
||||
[bbbg.views.flash :as flash]
|
||||
[compojure.core :refer [GET routes]]
|
||||
[ring.util.response :refer [redirect]]
|
||||
[bbbg.discord :as discord]))
|
||||
|
||||
(defn- home-page []
|
||||
(defn- home-page [{:keys [authenticated?]}]
|
||||
[:nav.home-nav
|
||||
[:ul
|
||||
[:li [:a {:href "/signup-forms"}
|
||||
"Event Signup Form"]]
|
||||
[:li [:a {:href "/login"}
|
||||
"Sign In"]]]])
|
||||
(when-not authenticated?
|
||||
[:li [:a {:href "/auth/discord"}
|
||||
"Sign In"]])]])
|
||||
|
||||
(defn home-routes [_env]
|
||||
(defn auth-failure []
|
||||
[:div.auth-failure
|
||||
[:p
|
||||
"Sorry, only users with the Organizers role in discord can sign in"]
|
||||
[:p
|
||||
[:a {:href "/"} "Go Back"]]])
|
||||
|
||||
(defn home-routes [{:keys [db] :as env}]
|
||||
(routes
|
||||
(GET "/" []
|
||||
(page-response (home-page)))))
|
||||
(GET "/" request
|
||||
(let [authenticated? (some? (get-in request [:session ::user/id]))]
|
||||
(page-response (home-page {:authenticated? authenticated?}))))
|
||||
|
||||
(GET "/auth/success" request
|
||||
(let [token (get-in request [:oauth2/access-tokens :discord])]
|
||||
(if (discord.auth/check-discord-auth env token)
|
||||
(let [discord-user (discord/me token)
|
||||
user (db.user/create!
|
||||
db
|
||||
#::user{:username (:username discord-user)
|
||||
:discord-user-id (:id discord-user)})]
|
||||
(-> (redirect "/")
|
||||
(assoc-in [:session ::user/id] (::user/id user))
|
||||
(flash/add-flash
|
||||
{:flash/message "Successfully Signed In"
|
||||
:flash/type :success})))
|
||||
(->
|
||||
(page-response (auth-failure))
|
||||
(assoc :status 401)))))))
|
||||
|
|
|
|||
8
users/grfn/bbbg/src/bbbg/user.clj
Normal file
8
users/grfn/bbbg/src/bbbg/user.clj
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
(ns bbbg.user
|
||||
(:require [clojure.spec.alpha :as s]))
|
||||
|
||||
(s/def ::id uuid?)
|
||||
|
||||
(s/def ::discord-id string?)
|
||||
|
||||
(s/def ::username string?)
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
(ns bbbg.util.core
|
||||
(:import java.util.UUID))
|
||||
(:require
|
||||
[clojure.java.shell :refer [sh]]
|
||||
[clojure.string :as str])
|
||||
(:import
|
||||
java.util.UUID))
|
||||
|
||||
(defn remove-nils
|
||||
"Remove all keys with nil values from m"
|
||||
|
|
@ -115,3 +119,12 @@
|
|||
(cons f (step (rest s) (conj seen (distinction-fn f)))))))
|
||||
xs seen)))]
|
||||
(step coll #{})))
|
||||
|
||||
(defn pass [n]
|
||||
(let [{:keys [exit out err]} (sh "pass" n)]
|
||||
(if (= 0 exit)
|
||||
(str/trim out)
|
||||
(throw (Exception.
|
||||
(format "`pass` command failed\nStandard output:%s\nStandard Error:%s"
|
||||
out
|
||||
err))))))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
(ns bbbg.web
|
||||
(:require
|
||||
[bbbg.discord.auth :as discord.auth :refer [wrap-discord-auth]]
|
||||
[bbbg.handlers.attendees :as attendees]
|
||||
[bbbg.handlers.events :as events]
|
||||
[bbbg.handlers.home :as home]
|
||||
|
|
@ -7,6 +8,7 @@
|
|||
[bbbg.styles :refer [stylesheet]]
|
||||
[bbbg.util.core :as u]
|
||||
[bbbg.views.flash :refer [wrap-page-flash]]
|
||||
clj-time.coerce
|
||||
[clojure.spec.alpha :as s]
|
||||
[com.stuartsierra.component :as component]
|
||||
[compojure.core :refer [GET routes]]
|
||||
|
|
@ -27,8 +29,10 @@
|
|||
(s/and bytes? #(= 16 (count %))))
|
||||
|
||||
(s/def ::config
|
||||
(s/keys :req [::port]
|
||||
:opt [::cookie-secret]))
|
||||
(s/merge
|
||||
(s/keys :req [::port]
|
||||
:opt [::cookie-secret])
|
||||
::discord.auth/config))
|
||||
|
||||
(s/fdef make-server
|
||||
:args (s/cat :config ::config))
|
||||
|
|
@ -45,14 +49,18 @@
|
|||
(s/assert
|
||||
::config
|
||||
(u/remove-nils
|
||||
{::port (:port env 8888)
|
||||
::cookie-secret (some-> env :cookie-secret string->cookie-secret)})))
|
||||
(merge
|
||||
{::port (:port env 8888)
|
||||
::cookie-secret (some-> env :cookie-secret string->cookie-secret)}
|
||||
(discord.auth/env->config)))))
|
||||
|
||||
(defn dev-config []
|
||||
(s/assert
|
||||
::config
|
||||
{::port 8888
|
||||
::cookie-secret (into-array Byte/TYPE (repeat 16 0))}))
|
||||
(merge
|
||||
{::port 8888
|
||||
::cookie-secret (into-array Byte/TYPE (repeat 16 0))}
|
||||
(discord.auth/dev-config))))
|
||||
|
||||
;;;
|
||||
|
||||
|
|
@ -72,11 +80,16 @@
|
|||
|
||||
(defn middleware [app env]
|
||||
(-> app
|
||||
(wrap-discord-auth env)
|
||||
wrap-keyword-params
|
||||
wrap-params
|
||||
wrap-page-flash
|
||||
wrap-flash
|
||||
(wrap-session {:store (cookie-store {:key (:cookie-secret env)})})))
|
||||
(wrap-session {:store (cookie-store
|
||||
{:key (:cookie-secret env)
|
||||
:readers {'clj-time/date-time
|
||||
clj-time.coerce/from-string}})
|
||||
:cookie-attrs {:same-site :lax}})))
|
||||
|
||||
(defn handler [env]
|
||||
(-> (app-routes env)
|
||||
|
|
@ -96,8 +109,12 @@
|
|||
(dissoc this ::shutdown-fn))
|
||||
this)))
|
||||
|
||||
(defn make-server [{::keys [port cookie-secret]}]
|
||||
(defn make-server [{::keys [port cookie-secret]
|
||||
:as env}]
|
||||
(component/using
|
||||
(map->WebServer {:port port
|
||||
:cookie-secret cookie-secret})
|
||||
(map->WebServer
|
||||
(merge
|
||||
{:port port
|
||||
:cookie-secret cookie-secret}
|
||||
env))
|
||||
[:db]))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue