Current section
Files
Jump to
Current section
Files
src/spotless.gleam
import gleam/http
import gleam/int
import gleam/list
import gleam/option.{None, Some}
import gleam/uri
import midas/task as t
import ogre/origin.{Origin}
import spotless/oauth_2_1 as oa
import spotless/proof_key_for_code_exchange as pkce
/// The authenticate function and therefore all named helpers builds upon the oauth_2_1.authorize function
/// This use the `task.Follow` effect and keeps the process state in memory as a continuation.
/// Keeping the state in memory is not possible if the state needs to be serialized in any way.
/// For example a server that would keep code_verifier in a session cannot keep a gleam continuation in a session.
///
/// Instead use the `_server` function and use `oauth_2_1.start`
pub fn authenticate(service, scope, state, port, code_challenge_method) {
let origin = Origin(http.Https, "spotless.run", None)
let client_id = "http://localhost:" <> int.to_string(port)
let redirect_uri =
uri.Uri(
scheme: Some("http"),
userinfo: None,
host: Some("localhost"),
port: Some(port),
path: "/",
query: None,
fragment: None,
)
let server =
oa.AuthorizationServer(
issuer: "https://spotless.run",
authorization_endpoint: #(origin, "/authorize/" <> service),
token_endpoint: #(origin, "/token"),
pushed_authorization_request_endpoint: None,
)
let app = oa.App(oa.Public, client_id, redirect_uri)
oa.authorize(server, app, None, scope, state, code_challenge_method)
}
fn spotless_server(service) {
let origin = Origin(http.Https, "spotless.run", None)
oa.AuthorizationServer(
issuer: "https://spotless.run",
authorization_endpoint: #(origin, "/authorize/" <> service),
token_endpoint: #(origin, "/token"),
pushed_authorization_request_endpoint: None,
)
}
pub fn local(port, path) {
let client_id = "http://localhost:" <> int.to_string(port)
let redirect_uri =
uri.Uri(
scheme: Some("http"),
userinfo: None,
host: Some("localhost"),
port: Some(port),
path: path,
query: None,
fragment: None,
)
oa.App(oa.Public, client_id, redirect_uri)
}
pub fn bsky(port, scope, state, keypair) {
// If state is empty string that value is not returned from oauth server
let origin = Origin(http.Https, "bsky.social", None)
// No port on client id
let client_id =
"http://localhost?scope=atproto%20transition:generic%20transition:email%20transition:chat.bsky"
let redirect_uri =
uri.Uri(
scheme: Some("http"),
userinfo: None,
host: Some("127.0.0.1"),
port: Some(port),
path: "/",
query: None,
fragment: None,
)
let server =
oa.AuthorizationServer(
issuer: "https://bsky.social",
authorization_endpoint: #(origin, "/oauth/authorize"),
token_endpoint: #(origin, "/oauth/token"),
pushed_authorization_request_endpoint: Some(#(
#(origin, "/oauth/par"),
True,
)),
)
let app = oa.App(oa.Public, client_id, redirect_uri)
oa.authorize(server, app, Some(keypair), scope, state, pkce.S256)
}
const services = [
"dnsimple", "dropbox", "github", "google", "linkedin", "netlify", "strava",
"twitter", "vimeo",
]
pub fn server(service) {
case list.contains(services, service) {
True -> Ok(spotless_server(service))
False -> Error(Nil)
}
}
// Currently DNSimple don't use scopes
pub fn dnsimple(port) {
use response <- t.do(authenticate("dnsimple", [], "", port, pkce.S256))
t.done(response.access_token)
}
pub fn dropbox(port, scopes) {
use response <- t.do(authenticate("dropbox", scopes, "", port, pkce.S256))
t.done(response.access_token)
}
pub fn github(port, scopes) {
use response <- t.do(authenticate("github", scopes, "", port, pkce.S256))
t.done(response.access_token)
}
pub fn google(port, scopes) {
use response <- t.do(authenticate("google", scopes, "", port, pkce.S256))
t.done(response.access_token)
}
pub fn linkedin(port, scopes) {
use response <- t.do(authenticate("linkedin", scopes, "", port, pkce.S256))
t.done(response.access_token)
}
pub fn netlify(port, scopes) {
use response <- t.do(authenticate("netlify", scopes, "", port, pkce.S256))
t.done(response.access_token)
}
pub fn strava(port, scopes) {
use response <- t.do(authenticate("strava", scopes, "", port, pkce.S256))
t.done(response.access_token)
}
pub fn twitter(port, scopes) {
use response <- t.do(authenticate("twitter", scopes, "", port, pkce.S256))
t.done(response.access_token)
}
pub fn vimeo(port, scopes) {
use response <- t.do(authenticate("vimeo", scopes, "", port, pkce.S256))
t.done(response.access_token)
}