Sasha Goloshchapov

November 23, 2022

GitHub App authentication in darklang

My blog has moved! Read this post at: https://blog.ferrata.dev/github-app-authentication-in-darklang/

My friend introduced me to a project named darklang, or simply Dark. It's a serverless and deployless backend builder. A unique approach that sets it apart (at least for me) is stated on its Introduction page:

Darklang is an integrated language, framework, and editor for building web backends: REST API endpoints, asynchronous background workers, scheduled jobs, and persistent storage. Dark's framework is tightly coupled to the infrastructure, and as you write code you're able to develop from real incoming requests/traces.

Also, it is a functional language!

Dark is a statically-typed functional/imperative hybrid, based loosely on ML. Dark has implicit returns, and makes heavy use of pipelines and functions like List::map

So, having quite a bit of backend and API experience, I was intrigued! But mainly, I wanted to test-drive Dark and see how it felt.

I started a small project that I will share later; it uses GitHub API with App access, and I needed a way to authenticate my GitHub app. Since Dark is under active development, it doesn't have all the bells and whistles yet, but it's impressive how many features are already implemented.

This is a small, wanna-be-guide on creating and authenticating your GitHub app with Dark.

1. Create a new GitHub App

2. Generate a private key

3. Add secrets to your project in Dark:
  • APP_ID - contains GitHub App ID; it can be found in the About section of your GitHub App
  • APP_PEM - contains a PEM certificate from the previous step

4. Install the GitHub App into your profile

5. Create a new function, github_authenticate, in your Dark functions. Add the following implementation:

let now = Date::now
let jwt = JWT::signAndEncodev1
            APP_PEM
            {
              iat : now
                    |>Date::toSeconds
              exp : Date::add now 60 * 10 - 1
                    |>Date::toSeconds
              iss : APP_ID
            }
let headers = {
                Accepts : "application/vnd.github.v3+json"
              }
              |>Dict::set "User-Agent" "DarkGithub"
              |>Dict::merge HttpClient::bearerTokenv1 jwt
let installation = HttpClient::getv5 "https://api.github.com/app/installations" Dict::empty headers
                   |>\r -> r.body
                   |>List::getAtv1 0
let tokensUrl = String::join
                  ["https://api.github.com/app/installations",
                   toString installation.id,"access_tokens"]
                  "/"
let response = HttpClient::postv5 tokensUrl Dict::empty Dict::empty headers
Ok response.body

Here is how it looks in Dark:

image.png


Now we can test it with the following REPL:

let auth = github_authenticate
let headers = {
                Accepts : "application/vnd.github.v3+json"
              }
              |>Dict::set "User-Agent" "DarkGithub"
              |>Dict::merge HttpClient::bearerTokenv1 auth.token
let response = HttpClient::getv5 "https://api.github.com/users/ferrata" Dict::empty headers
Ok response.body

image.png


...and look! It works! 😊

References:

About Sasha Goloshchapov

My blog has moved to its new home at https://blog.ferrata.dev/ 

See you there!