Skip to content

Commit 6d34967

Browse files
committed
add functions to query the Slack API for oauth exchange
1 parent 5083f31 commit 6d34967

File tree

6 files changed

+65
-0
lines changed

6 files changed

+65
-0
lines changed

lib/api.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@ module type Slack = sig
1616
val send_notification : ctx:Context.t -> msg:post_message_req -> (unit, string) Result.t Lwt.t
1717

1818
val send_chat_unfurl : ctx:Context.t -> chat_unfurl_req -> (unit, string) Result.t Lwt.t
19+
20+
val update_access_token_of_context : ctx:Context.t -> code:string -> (unit, string) Result.t Lwt.t
1921
end

lib/api_local.ml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ module Slack_base : Api.Slack = struct
2828
let send_notification ~ctx:_ ~msg:_ = Lwt.return @@ Error "undefined for local setup"
2929

3030
let send_chat_unfurl ~ctx:_ _ = Lwt.return @@ Error "undefined for local setup"
31+
32+
let update_access_token_of_context ~ctx:_ ~code:_ = Lwt.return @@ Error "undefined for local setup"
3133
end
3234

3335
module Slack : Api.Slack = struct
@@ -38,6 +40,10 @@ module Slack : Api.Slack = struct
3840
Stdio.printf "will notify #%s\n" msg.channel;
3941
Stdio.printf "%s\n" json;
4042
Lwt.return @@ Ok ()
43+
44+
let update_access_token_of_context ~ctx:_ ~code:_ =
45+
Stdio.printf "will generate token\n";
46+
Lwt.return @@ Ok ()
4147
end
4248

4349
module Slack_simple : Api.Slack = struct

lib/api_remote.ml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,36 @@ module Slack : Api.Slack = struct
120120
)
121121
| Error e -> Lwt.return @@ fmt_error "error while querying %s: %s\nfailed to unfurl Slack links" url e
122122
)
123+
124+
let access_token_of_code ~(ctx : Context.t) ~code =
125+
let secrets = Context.get_secrets_exn ctx in
126+
let body = `Form [ "code", code ] in
127+
match secrets.slack_client_id with
128+
| None -> Lwt.return @@ Error "slack_client_id is undefined"
129+
| Some client_id ->
130+
match secrets.slack_client_secret with
131+
| None -> Lwt.return @@ Error "slack_client_secret is undefined"
132+
| Some client_secret ->
133+
let auth_header = Base64.encode_string @@ sprintf "%s:%s" client_id client_secret in
134+
let headers = [ Printf.sprintf "Authorization: Basic %s" auth_header ] in
135+
( match%lwt Common.http_request ~body ~headers `POST "https://slack.com/api/oauth.v2.access" with
136+
| Error e -> Lwt.return @@ Error e
137+
| Ok data ->
138+
let response = Slack_j.oauth_access_res_of_string data in
139+
( match response.access_token, response.error with
140+
| Some access_token, _ -> Lwt.return @@ Ok access_token
141+
| None, Some e -> Lwt.return @@ Error e
142+
| None, None -> Lwt.return @@ Error "an unknown error occurred while getting access token"
143+
)
144+
)
145+
146+
let update_access_token_of_context ~ctx ~code =
147+
let secrets = Context.get_secrets_exn ctx in
148+
match%lwt access_token_of_code ~ctx ~code with
149+
| Error e -> Lwt.return @@ Error e
150+
| Ok access_token ->
151+
let secrets = { secrets with slack_access_token = Some access_token } in
152+
ctx.secrets <- Some secrets;
153+
let data = Config_j.string_of_secrets secrets |> Yojson.Basic.from_string |> Yojson.Basic.pretty_to_string in
154+
Lwt.return @@ write_to_local_file ~data ctx.secrets_filepath
123155
end

lib/config.atd

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,11 @@ type secrets = {
4949
?slack_access_token : string nullable;
5050
(* Slack uses this secret to sign requests; provide to verify incoming Slack requests *)
5151
?slack_signing_secret : string nullable;
52+
(* Client ID of the Slack bot; used for OAuth authentication *)
53+
?slack_client_id : string nullable;
54+
(* Client secret of the Slack bot; used for OAuth authentication *)
55+
?slack_client_secret : string nullable;
56+
(* if specified, maintains state b/w OAuth request and callback to prevent CSRF
57+
see: https://tools.ietf.org/html/rfc6749#section-4.1.1 *)
58+
?slack_oauth_state : string nullable;
5259
}

lib/slack.atd

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,10 @@ type chat_unfurl_res = {
118118
ok: bool;
119119
?error: string option;
120120
}
121+
122+
(* expected payload when exchanging oauth code for access token *)
123+
type oauth_access_res = {
124+
ok: bool;
125+
?access_token: string option;
126+
?error: string option;
127+
}

lib/slack.ml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,3 +373,14 @@ let validate_signature ?(version = "v0") ?signing_key ~headers body =
373373
let basestring = Printf.sprintf "%s:%s:%s" version timestamp body in
374374
let expected_signature = Printf.sprintf "%s=%s" version (Common.sign_string_sha256 ~key ~basestring) in
375375
if String.equal expected_signature signature then Ok () else Error "signatures don't match"
376+
377+
let validate_state ?oauth_state ~args =
378+
match oauth_state with
379+
| None -> Ok ()
380+
| Some expected_state ->
381+
match List.Assoc.find args "state" ~equal:String.equal with
382+
| None -> Error "argument `state` not found in slack authorization request"
383+
| Some state ->
384+
match String.equal state expected_state with
385+
| false -> Error "argument `state` is present in slack authorization request but does not match expected value"
386+
| true -> Ok ()

0 commit comments

Comments
 (0)