Skip to content

Commit

Permalink
garden-id: revamp github restrictions, allow specifying login names o…
Browse files Browse the repository at this point in the history
…r id (#295)

* garden-id: revamp github restrictions, allow specifying login names or id

* garden-id: don't silently fail when the GitHub token is invalid

* garden-id: allow ifn for github login or id restrictions
  • Loading branch information
leahneukirchen authored Apr 5, 2024
1 parent d1ae6f5 commit 06b67b5
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 24 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,21 @@ You can configure authorization by passing a map as the second argument to `next

### Github

To only allow members of a certain Github organization or team to access your application, use:
To only allow certain Github users, members of a certain Github
organization or team to access your application, use:

`(nextjournal.garden-id/wrap-auth my-app {:github [["organization"]... ["organization" "team"]...]})`
`(nextjournal.garden-id/wrap-auth my-app {:github [RESTRICTIONS...]})`

Possible restrictions are:

* `{:login "githubhandle"}`: the user `githubhandle`
* `{:id 1234567}`: the user with the Github ID 1234567
* `{:login ifn}`: call ifn with the GitHub login handle, pass if returns true
* `{:id ifn}`: call ifn with the GitHub id, pass if returns true
* `{:organization "myorg"}`: members of the organization `myorg`.
* `{:organization "myorg" :team "myteam"}`: members of the team `myteam` of the organization `myorg`.

The user is permitted if they pass any listed restriction.

You need a valid Github API token in the environment variable `GITHUB_API_TOKEN` that is scoped to read the organization members.

Expand Down
67 changes: 45 additions & 22 deletions src/nextjournal/garden_id.clj
Original file line number Diff line number Diff line change
Expand Up @@ -118,37 +118,60 @@
;; else
(app req))))

(defn- validate-github-member? [user-id org team]
;; first map user-id to login name, then look up team membership
(let [username (-> (http/get (format "https://api.github.com/user/%s" user-id)
{:headers {"Authorization"
(str "token " github-api-token)}
:throw false})
(defn validate-github-member? [user-id {:keys [team organization login id]}]
;; first map user-id to login name, which the other API needs
(let [res (http/get (format "https://api.github.com/user/%s" user-id)
{:headers {"Authorization"
(str "token " github-api-token)}
:throw false})
_ (when (= 401 (:status res))
(throw (ex-info "Bad GitHub credentials for garden-id verification"
{:body (:body res)})))
;; status 404 (user doesn't exist (anymore)) just fails validation later
username (-> res
:body
(json/parse-string true)
:login)
url (if team
(format "https://api.github.com/orgs/%s/teams/%s/memberships/%s"
org team username)
(format "https://api.github.com/orgs/%s/memberships/%s"
org username))
resp (http/get url {:headers {"Authorization"
(str "token " github-api-token)}
:throw false})]
(and (= 200 (:status resp))
(= "active" (:state (json/parse-string (:body resp) true))))))
:login)]
(cond
(ifn? login)
(login username)

(ifn? id)
(id user-id)

login
(= username login)

(and id user-id)
(= (str user-id) (str id))

organization
(let [url (if team
(format "https://api.github.com/orgs/%s/teams/%s/memberships/%s"
organization team username)
(format "https://api.github.com/orgs/%s/memberships/%s"
organization username))
resp (http/get url {:headers {"Authorization"
(str "token " github-api-token)}
:throw false})]
(and (= 200 (:status resp))
(= "active" (:state (json/parse-string (:body resp) true)))))

:else false)))

(defn- validate-extra-claims? [claims opts]
(or (empty? opts)

(when (:github opts)
(and (= (:issuer claims) "https://github.com/login/oauth/access_token")
(or (empty? (:github opts))
(some (fn [org-and-team]
(validate-github-member? (:github_id claims)
(first org-and-team)
(second org-and-team)))
(:github opts)))))
(let [restrictions (for [restriction (:github opts)]
(if (vector? restriction) ; legacy syntax
{:organization (first restriction)
:team (second restriction)}
restriction))]
(some (partial validate-github-member? (:github_id claims))
restrictions)))))

(when (:apple opts)
(= (:issuer claims) "https://appleid.apple.com"))))
Expand Down

0 comments on commit 06b67b5

Please sign in to comment.