From 69b6d1cf0f3755d9780b9c5f133f7f30f63e14d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Pr=C3=A9vost?= Date: Tue, 29 Aug 2023 09:50:19 -0400 Subject: [PATCH] Fix slack and gitlab login --- cli/src/services/document-paths-fetcher.ts | 3 - .../services/formatters/document-export.ts | 2 +- config/runtime.exs | 9 +- lib/accent/auth/user_remote/authenticator.ex | 8 +- lib/accent/endpoint.ex | 1 + lib/web/controllers/auth_controller.ex | 11 +- lib/web/router.ex | 2 + mix.exs | 10 +- mix.lock | 20 +- .../lib/ueberauth/strategy/gitlab.ex | 231 +++++++++++ .../lib/ueberauth/strategy/gitlab/oauth.ex | 105 +++++ .../lib/ueberauth/strategy/slack.ex | 374 ++++++++++++++++++ .../lib/ueberauth/strategy/slack/oauth.ex | 104 +++++ 13 files changed, 852 insertions(+), 28 deletions(-) create mode 100644 vendor/ueberauth_gitlab_strategy/lib/ueberauth/strategy/gitlab.ex create mode 100644 vendor/ueberauth_gitlab_strategy/lib/ueberauth/strategy/gitlab/oauth.ex create mode 100644 vendor/ueberauth_slack/lib/ueberauth/strategy/slack.ex create mode 100644 vendor/ueberauth_slack/lib/ueberauth/strategy/slack/oauth.ex diff --git a/cli/src/services/document-paths-fetcher.ts b/cli/src/services/document-paths-fetcher.ts index a92930922..5e8e2f8cd 100644 --- a/cli/src/services/document-paths-fetcher.ts +++ b/cli/src/services/document-paths-fetcher.ts @@ -1,10 +1,7 @@ -import * as path from 'path'; - // Types import {DocumentPath} from '../types/document-path'; import {Project} from '../types/project'; import Document from './document'; -import {DocumentConfig} from '../types/document-config'; import {fetchFromRevisions} from './revision-slug-fetcher'; export default class DocumentPathsFetcher { diff --git a/cli/src/services/formatters/document-export.ts b/cli/src/services/formatters/document-export.ts index f0cf26e6b..9a329a50b 100644 --- a/cli/src/services/formatters/document-export.ts +++ b/cli/src/services/formatters/document-export.ts @@ -10,7 +10,7 @@ export default class DocumentExportFormatter extends Base { chalk.green('↓'), chalk.bold.white(path), chalk.gray.dim(documentPath), - chalk.gray.dim(`→ ${language}`), + chalk.gray.dim(`→ ${language}`) ); } diff --git a/config/runtime.exs b/config/runtime.exs index e56b61b37..90094e986 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -55,7 +55,10 @@ providers = providers = if get_env("SLACK_CLIENT_ID"), - do: [{:slack, {Ueberauth.Strategy.Slack, [team: get_env("SLACK_TEAM_ID")]}} | providers], + do: [ + {:slack, {Ueberauth.Strategy.Slack, [default_user_scope: "users:read", team: get_env("SLACK_TEAM_ID")]}} + | providers + ], else: providers providers = @@ -74,7 +77,9 @@ providers = else: providers providers = - if get_env("MICROSOFT_CLIENT_ID"), do: [{:microsoft, {Ueberauth.Strategy.Microsoft, []}} | providers], else: providers + if get_env("MICROSOFT_CLIENT_ID"), + do: [{:microsoft, {Ueberauth.Strategy.Microsoft, [prompt: "select_account"]}} | providers], + else: providers providers = if get_env("AUTH0_CLIENT_ID"), do: [{:auth0, {Ueberauth.Strategy.Auth0, []}} | providers], else: providers diff --git a/lib/accent/auth/user_remote/authenticator.ex b/lib/accent/auth/user_remote/authenticator.ex index be83ccd03..6e1268c0d 100644 --- a/lib/accent/auth/user_remote/authenticator.ex +++ b/lib/accent/auth/user_remote/authenticator.ex @@ -25,7 +25,7 @@ defmodule Accent.UserRemote.Authenticator do defp map_user(info, provider) do %User{ provider: to_string(provider), - fullname: info.name, + fullname: normalize_name(info.name), picture_url: normalize_picture_url(info.image, provider), email: normalize_email(info.email), uid: normalize_email(info.email) @@ -35,6 +35,12 @@ defmodule Accent.UserRemote.Authenticator do defp normalize_picture_url("https://lh3.googleusercontent.com/a/default-user" <> _, :google), do: nil defp normalize_picture_url(url, _provider), do: url + defp normalize_name(nil), do: nil + + defp normalize_name(name) do + String.trim(name) + end + defp normalize_email(email) do String.downcase(email) end diff --git a/lib/accent/endpoint.ex b/lib/accent/endpoint.ex index b17461b00..7ff0a10c2 100644 --- a/lib/accent/endpoint.ex +++ b/lib/accent/endpoint.ex @@ -29,6 +29,7 @@ defmodule Accent.Endpoint do plug(Phoenix.CodeReloader) end + plug(Plug.Session, store: :cookie, key: "accent", signing_salt: "accent-signing-salt-used-for-callback-auth") plug(Plug.RequestId) plug(Plug.Logger) diff --git a/lib/web/controllers/auth_controller.ex b/lib/web/controllers/auth_controller.ex index 94052a510..5fe64e751 100644 --- a/lib/web/controllers/auth_controller.ex +++ b/lib/web/controllers/auth_controller.ex @@ -2,8 +2,13 @@ defmodule Accent.AuthController do use Phoenix.Controller alias Accent.UserRemote.Authenticator + alias Ueberauth.Strategy.Helpers - plug :ueberauth + plug(Ueberauth, base_path: "/auth") + + def request(conn, _params) do + render(conn, "request.html", callback_url: Helpers.callback_url(conn)) + end def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _) do case Authenticator.authenticate(auth) do @@ -18,8 +23,4 @@ defmodule Accent.AuthController do def callback(conn, _) do redirect(conn, to: "/") end - - def ueberauth(conn, _) do - Ueberauth.call(conn, Ueberauth.init()) - end end diff --git a/lib/web/router.ex b/lib/web/router.ex index ac67b0642..25115a1e4 100644 --- a/lib/web/router.ex +++ b/lib/web/router.ex @@ -30,6 +30,8 @@ defmodule Accent.Router do pipeline :browser do plug :accepts, ~w(json html) + plug :fetch_session + plug(:protect_from_forgery) plug :put_secure_browser_headers, %{"x-frame-options" => ""} end diff --git a/mix.exs b/mix.exs index 12ad61ad3..29a2bfd48 100644 --- a/mix.exs +++ b/mix.exs @@ -78,15 +78,13 @@ defmodule Accent.Mixfile do {:xml_builder, "~> 2.0"}, # Auth - {:oauth2, "~> 2.0", override: true}, - {:ueberauth, "~> 0.6.0"}, + {:ueberauth, "~> 0.10"}, + {:oauth2, "~> 2.0"}, + {:ueberauth_microsoft, "~> 0.21"}, {:ueberauth_google, "~> 0.6"}, {:ueberauth_github, "~> 0.7"}, - {:ueberauth_gitlab_strategy, "~> 0.3"}, - {:ueberauth_slack, github: "ueberauth/ueberauth_slack", ref: "525594c870f959ab"}, {:ueberauth_discord, "~> 0.5"}, - {:ueberauth_microsoft, "~> 0.7"}, - {:ueberauth_auth0, "~> 0.8"}, + {:ueberauth_auth0, "~> 2.0"}, # Errors {:sentry, "~> 7.0"}, diff --git a/mix.lock b/mix.lock index a0829fd03..cacaf7943 100644 --- a/mix.lock +++ b/mix.lock @@ -23,14 +23,14 @@ "dataloader": {:hex, :dataloader, "2.0.0", "49b42d60b9bb06d761a71d7b034c4b34787957e713d4fae15387a25fcd639112", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:opentelemetry_process_propagator, "~> 0.2.1", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "09d61781b76ce216e395cdbc883ff00d00f46a503e215c22722dba82507dfef0"}, "db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, - "dialyxir": {:hex, :dialyxir, "1.3.0", "fd1672f0922b7648ff9ce7b1b26fcf0ef56dda964a459892ad15f6b4410b5284", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "00b2a4bcd6aa8db9dcb0b38c1225b7277dca9bc370b6438715667071a304696f"}, + "dialyxir": {:hex, :dialyxir, "1.4.0", "6b698401c16de79e8596b73dca63762255e70e4bbe26423530e173917220d5fc", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "c7ecaa1da27debae488ab09d9827ec58a0161c7821972b6d2cb26c1614648849"}, "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, "ecto_dev_logger": {:hex, :ecto_dev_logger, "0.9.0", "cb631469ac1940e97655d6fce85905b792ac9250ab18b19c664978b79f8dad59", [:mix], [{:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "2e8bc98b4ae4fcc7108896eef7da5a109afad829f4fb2eb46d677fdc9101c2d5"}, "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.13", "9947637f82b92dcec93d44ad09ba24d1990bd7ca69e1c68981fb3b6f8bd18829", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "0f2288e6163f6aacd7e59545a56adc8df7d2079d18be7d3d6159d10f4dffc396"}, "ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "erlsom": {:hex, :erlsom, "1.5.1", "c8fe2babd33ff0846403f6522328b8ab676f896b793634cfe7ef181c05316c03", [:rebar3], [], "hexpm", "7965485494c5844dd127656ac40f141aadfa174839ec1be1074e7edf5b4239eb"}, - "excoveralls": {:hex, :excoveralls, "0.17.0", "279f124dba347903bb654bc40745c493ae265d45040001b4899ea1edf88078c7", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "08b638d114387a888f9cb8d65f2a0021ec04c3e447b793efa7c1e734aba93004"}, + "excoveralls": {:hex, :excoveralls, "0.17.1", "83fa7906ef23aa7fc8ad7ee469c357a63b1b3d55dd701ff5b9ce1f72442b2874", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "95bc6fda953e84c60f14da4a198880336205464e75383ec0f570180567985ae0"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"}, "gettext": {:hex, :gettext, "0.20.0", "75ad71de05f2ef56991dbae224d35c68b098dd0e26918def5bb45591d5c8d429", [:mix], [], "hexpm", "1c03b177435e93a47441d7f681a7040bd2a816ece9e2666d1c9001035121eb3d"}, @@ -86,14 +86,14 @@ "tesla": {:hex, :tesla, "1.7.0", "a62dda2f80d4f8a925eb7b8c5b78c461e0eb996672719fe1a63b26321a5f8b4e", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2e64f01ebfdb026209b47bc651a0e65203fcff4ae79c11efb73c4852b00dc313"}, "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, - "ueberauth": {:hex, :ueberauth, "0.6.3", "d42ace28b870e8072cf30e32e385579c57b9cc96ec74fa1f30f30da9c14f3cc0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "afc293d8a1140d6591b53e3eaf415ca92842cb1d32fad3c450c6f045f7f91b60"}, - "ueberauth_auth0": {:hex, :ueberauth_auth0, "0.8.1", "75a116461963be163fce755904b7ad2b84d8acfd799410f3ef7713e06d048594", [:mix], [{:oauth2, "~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.6", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "cb98340a5849b102f0eb02ddd68ba5af83bc6efe3a2fd068ce6e299a1c17f7f0"}, - "ueberauth_discord": {:hex, :ueberauth_discord, "0.6.0", "d6ec040e4195c4138b9a959c79024ab4c213ba1aed9fc08099ecff141a6486da", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.6.3", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "c5ea960191c1d6c3a974947cae4d57efa565a9a0796b8e82bee45fac7ae2fabc"}, - "ueberauth_github": {:hex, :ueberauth_github, "0.8.0", "2216c8cdacee0de6245b422fb397921b64a29416526985304e345dab6a799d17", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.6.0", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "b65ccc001a7b0719ba069452f3333d68891f4613ae787a340cce31e2a43307a3"}, - "ueberauth_gitlab_strategy": {:hex, :ueberauth_gitlab_strategy, "0.3.1", "9598ba4cdb1d059b13e8d6603dbd903b7965b059f31bc43c91d727277365cd39", [:mix], [{:oauth2, "~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.4", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "15f65b46b922e760e21af30fbe4e0c22c86d045ad0c636c067c2852358d0d9cd"}, - "ueberauth_google": {:hex, :ueberauth_google, "0.10.0", "ae00e7228207be977d5cdd0a562e39961851cea74f513aab6446cb51468f283c", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.6.3", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "148e2575fd24a29b8bcaa44f9d3d1a38550a74ceed645f5059d4516b93992650"}, - "ueberauth_microsoft": {:hex, :ueberauth_microsoft, "0.12.0", "c0df56d0ac77d765212ed0e33581524cc4e5cf1b63fa70d2193fcf0b72e133f3", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.6", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "405717f65211484f7bbd0521a1990b8a7169b417db7bcb9a1cdb8ecab7d1934d"}, - "ueberauth_slack": {:git, "https://github.com/ueberauth/ueberauth_slack.git", "525594c870f959aba67acc759d5c1a588ee75e9e", [ref: "525594c870f959ab"]}, + "ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"}, + "ueberauth_auth0": {:hex, :ueberauth_auth0, "2.1.0", "0632d5844049fa2f26823f15e1120aa32f27df6f27ce515a4b04641736594bf4", [:mix], [{:oauth2, "~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "8d3b30fa27c95c9e82c30c4afb016251405706d2e9627e603c3c9787fd1314fc"}, + "ueberauth_discord": {:hex, :ueberauth_discord, "0.7.0", "463f6dfe1ed10a76739331ce8e1dd3600ab611f10524dd828eb3aa50e76e9d43", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "d6f98ef91abb4ddceada4b7acba470e0e68c4d2de9735ff2f24172a8e19896b4"}, + "ueberauth_github": {:hex, :ueberauth_github, "0.8.3", "1c478629b4c1dae446c68834b69194ad5cead3b6c67c913db6fdf64f37f0328f", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "ae0ab2879c32cfa51d7287a48219b262bfdab0b7ec6629f24160564247493cc6"}, + "ueberauth_gitlab_strategy": {:hex, :ueberauth_gitlab_strategy, "0.4.0", "96605d304ebb87ce508eccbeb1f94da9ea1c9da20d8913771b6cf24a6cc6c633", [:mix], [{:oauth2, "~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7.0", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "e86e2e794bb063c07c05a6b1301b73f2be3ba9308d8f47ecc4d510ef9226091e"}, + "ueberauth_google": {:hex, :ueberauth_google, "0.10.3", "eb1d3973578105e884861facff641e6c03209d621532f988d071dd6d7a46f73b", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.10.0", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "2462ca9652acc936e0738691869d024e3e262f83ba9f6b4e874b961812290038"}, + "ueberauth_microsoft": {:hex, :ueberauth_microsoft, "0.21.0", "a86eedaf3e3d3535f4b9cf3196099e543cce29b3cd6dedd6f77f84dba1e948b7", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "52880ac872f4d60bcf5a0c3866373e06b1df4d386b1637e81e4fff06e868124d"}, + "ueberauth_slack": {:hex, :ueberauth_slack, "0.7.0", "91dfd089371a6c5a21a505b3e3e140cced95d4cdc7b73afb5337bcf5f3c91a00", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:oauth2, "~> 1.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "5cba654352596f74a9e2547a19a3aab56634f2a0b928e93cd659ac7d05bf790e"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "vega_lite": {:hex, :vega_lite, "0.1.8", "7f6119126ecaf4bc2c1854084370d7091424f5cce4795fbac044eee9963f0752", [:mix], [{:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: false]}], "hexpm", "6c8a9271f850612dd8a90de8d1ebd433590ed07ffef76fc2397c240dc04d3fdc"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, diff --git a/vendor/ueberauth_gitlab_strategy/lib/ueberauth/strategy/gitlab.ex b/vendor/ueberauth_gitlab_strategy/lib/ueberauth/strategy/gitlab.ex new file mode 100644 index 000000000..f681232d1 --- /dev/null +++ b/vendor/ueberauth_gitlab_strategy/lib/ueberauth/strategy/gitlab.ex @@ -0,0 +1,231 @@ +defmodule Ueberauth.Strategy.Gitlab do + @moduledoc """ + Provides an Ueberauth strategy for authenticating with Gitlab. + + ### Setup + + Create an application in Gitlab for you to use. + + Register a new application at: [your gitlab developer page](https://gitlab.com/settings/developers) and get the `client_id` and `client_secret`. + + Include the provider in your configuration for Ueberauth + + config :ueberauth, Ueberauth, + providers: [ + gitlab: { Ueberauth.Strategy.Gitlab, [] } + ] + + Then include the configuration for gitlab. + + config :ueberauth, Ueberauth.Strategy.Gitlab.OAuth, + client_id: System.get_env("GITLAB_CLIENT_ID"), + client_secret: System.get_env("GITLAB_CLIENT_SECRET") + + If you haven't already, create a pipeline and setup routes for your callback handler + + pipeline :auth do + Ueberauth.plug "/auth" + end + + scope "/auth" do + pipe_through [:browser, :auth] + + get "/:provider/callback", AuthController, :callback + end + + + Create an endpoint for the callback where you will handle the `Ueberauth.Auth` struct + + defmodule MyApp.AuthController do + use MyApp.Web, :controller + + def callback_phase(%{ assigns: %{ ueberauth_failure: fails } } = conn, _params) do + # do things with the failure + end + + def callback_phase(%{ assigns: %{ ueberauth_auth: auth } } = conn, params) do + # do things with the auth + end + end + + You can edit the behaviour of the Strategy by including some options when you register your provider. + + To set the `uid_field` + + config :ueberauth, Ueberauth, + providers: [ + gitlab: { Ueberauth.Strategy.Gitlab, [uid_field: :email] } + ] + + Default is `:id` + + To set the default 'scopes' (permissions): + + config :ueberauth, Ueberauth, + providers: [ + gitlab: { Ueberauth.Strategy.Gitlab, [default_scope: "api read_user read_registry", api_version: "v4"] } + ] + + Default is "api read_user read_registry" + """ + use Ueberauth.Strategy, + uid_field: :id, + default_scope: "api read_user read_registry", + send_redirect_uri: true, + oauth2_module: Ueberauth.Strategy.Gitlab.OAuth + + alias Ueberauth.Auth.Info + alias Ueberauth.Auth.Credentials + alias Ueberauth.Auth.Extra + + @doc """ + Handles the initial redirect to the gitlab authentication page. + + To customize the scope (permissions) that are requested by gitlab include them as part of your url: + + "/auth/gitlab?scope=api read_user read_registry" + """ + def handle_request!(conn) do + opts = + [] + |> with_scopes(conn) + |> with_state_param(conn) + |> with_redirect_uri(conn) + + module = option(conn, :oauth2_module) + redirect!(conn, apply(module, :authorize_url!, [opts])) + end + + @doc """ + Handles the callback from Gitlab. When there is a failure from Gitlab the failure is included in the + `ueberauth_failure` struct. Otherwise the information returned from Gitlab is returned in the `Ueberauth.Auth` struct. + """ + def handle_callback!(%Plug.Conn{params: %{"code" => code}} = conn) do + module = option(conn, :oauth2_module) + token = apply(module, :get_token!, [[code: code]]) + + if token.access_token == nil do + set_errors!(conn, [ + error(token.other_params["error"], token.other_params["error_description"]) + ]) + else + fetch_user(conn, token) + end + end + + @doc false + def handle_callback!(conn) do + set_errors!(conn, [error("missing_code", "No code received")]) + end + + @doc """ + Cleans up the private area of the connection used for passing the raw Gitlab response around during the callback. + """ + def handle_cleanup!(conn) do + conn + |> put_private(:gitlab_user, nil) + |> put_private(:gitlab_token, nil) + end + + @doc """ + Fetches the uid field from the Gitlab response. This defaults to the option `uid_field` which in-turn defaults to `id` + """ + def uid(conn) do + user = + conn + |> option(:uid_field) + |> to_string + + conn.private.gitlab_user[user] + end + + @doc """ + Includes the credentials from the Gitlab response. + """ + def credentials(conn) do + token = conn.private.gitlab_token + scope_string = token.other_params["scope"] || "" + scopes = String.split(scope_string, ",") + + %Credentials{ + token: token.access_token, + refresh_token: token.refresh_token, + expires_at: token.expires_at, + token_type: token.token_type, + expires: !!token.expires_at, + scopes: scopes + } + end + + @doc """ + Fetches the fields to populate the info section of the `Ueberauth.Auth` struct. + """ + def info(conn) do + user = conn.private.gitlab_user + + %Info{ + name: user["name"], + nickname: user["username"], + email: user["email"], + location: user["location"], + image: user["avatar_url"], + urls: %{ + web_url: user["web_url"], + website_url: user["website_url"] + } + } + end + + @doc """ + Stores the raw information (including the token) obtained from the Gitlab callback. + """ + def extra(conn) do + %Extra{ + raw_info: %{ + token: conn.private.gitlab_token, + user: conn.private.gitlab_user + } + } + end + + defp fetch_user(conn, token) do + conn = put_private(conn, :gitlab_token, token) + api_ver = option(conn, :api_ver) || "v4" + + case Ueberauth.Strategy.Gitlab.OAuth.get(token, "/api/#{api_ver}/user") do + {:ok, %OAuth2.Response{status_code: 401, body: _body}} -> + set_errors!(conn, [error("token", "unauthorized")]) + + {:ok, %OAuth2.Response{status_code: status_code, body: user}} + when status_code in 200..399 -> + put_private(conn, :gitlab_user, user) + + {:error, %OAuth2.Error{reason: reason}} -> + set_errors!(conn, [error("OAuth2", reason)]) + + {:error, %OAuth2.Response{body: %{"message" => reason}}} -> + set_errors!(conn, [error("OAuth2", reason)]) + + {:error, _} -> + set_errors!(conn, [error("OAuth2", "uknown error")]) + end + end + + defp option(conn, key) do + Keyword.get(options(conn) || [], key, Keyword.get(default_options(), key)) + end + + defp with_scopes(opts, conn) do + scopes = conn.params["scope"] || option(conn, :default_scope) + + opts |> Keyword.put(:scope, scopes) + end + + defp with_redirect_uri(opts, conn) do + if option(conn, :send_redirect_uri) do + opts |> Keyword.put(:redirect_uri, callback_url(conn)) + else + opts + end + end +end diff --git a/vendor/ueberauth_gitlab_strategy/lib/ueberauth/strategy/gitlab/oauth.ex b/vendor/ueberauth_gitlab_strategy/lib/ueberauth/strategy/gitlab/oauth.ex new file mode 100644 index 000000000..d73783ae0 --- /dev/null +++ b/vendor/ueberauth_gitlab_strategy/lib/ueberauth/strategy/gitlab/oauth.ex @@ -0,0 +1,105 @@ +defmodule Ueberauth.Strategy.Gitlab.OAuth do + @moduledoc """ + An implementation of OAuth2 for gitlab. + + To add your `client_id` and `client_secret` include these values in your configuration. + + config :ueberauth, Ueberauth.Strategy.Gitlab.OAuth, + client_id: System.get_env("GITLAB_CLIENT_ID"), + client_secret: System.get_env("GITLAB_CLIENT_SECRET") + """ + use OAuth2.Strategy + + @defaults [ + strategy: __MODULE__, + site: "https://gitlab.com", + authorize_url: "https://gitlab.com/oauth/authorize", + token_url: "https://gitlab.com/oauth/token", + api_version: "v4", + token_method: :post + ] + + @doc """ + Construct a client for requests to Gitlab. + + Optionally include any OAuth2 options here to be merged with the defaults. + + Ueberauth.Strategy.Gitlab.OAuth.client(redirect_uri: "http://localhost:4000/auth/gitlab/callback") + + This will be setup automatically for you in `Ueberauth.Strategy.Gitlab`. + These options are only useful for usage outside the normal callback phase of Ueberauth. + """ + def client(opts \\ []) do + config = + :ueberauth + |> Application.fetch_env!(Ueberauth.Strategy.Gitlab.OAuth) + |> check_config_key_exists(:client_id) + |> check_config_key_exists(:client_secret) + + client_opts = + @defaults + |> Keyword.merge(config) + |> Keyword.merge(opts) + + json_library = Ueberauth.json_library() + + OAuth2.Client.new(client_opts) + |> OAuth2.Client.put_serializer("application/json", json_library) + end + + @doc """ + Provides the authorize url for the request phase of Ueberauth. No need to call this usually. + """ + def authorize_url!(params \\ [], opts \\ []) do + opts + |> client + |> OAuth2.Client.authorize_url!(params) + end + + def get(token, url, headers \\ [], opts \\ []) do + [token: token] + |> client + |> put_param("access_token", token) + |> OAuth2.Client.get(url, headers, opts) + end + + def get_token!(params \\ [], options \\ []) do + headers = Keyword.get(options, :headers, []) + options = Keyword.get(options, :options, []) + client_options = Keyword.get(options, :client_options, []) + client = OAuth2.Client.get_token!(client(client_options), params, headers, options) + client.token + end + + # Strategy Callbacks + + def authorize_url(client, params) do + client + |> put_param("response_type", "code") + |> put_param("redirect_uri", client().redirect_uri) + + OAuth2.Strategy.AuthCode.authorize_url(client, params) + end + + def get_token(client, params, headers) do + client + |> put_param("client_id", client().client_id) + |> put_param("client_secret", client().client_secret) + |> put_param("grant_type", "authorization_code") + |> put_param("redirect_uri", client().redirect_uri) + |> put_header("Accept", "application/json") + |> OAuth2.Strategy.AuthCode.get_token(params, headers) + end + + defp check_config_key_exists(config, key) when is_list(config) do + unless Keyword.has_key?(config, key) do + raise "#{inspect(key)} missing from config :ueberauth, Ueberauth.Strategy.Gitlab" + end + + config + end + + defp check_config_key_exists(_, _) do + raise "Config :ueberauth, Ueberauth.Strategy.Gitlab is not a keyword list, as expected" + end +end diff --git a/vendor/ueberauth_slack/lib/ueberauth/strategy/slack.ex b/vendor/ueberauth_slack/lib/ueberauth/strategy/slack.ex new file mode 100644 index 000000000..97e755f13 --- /dev/null +++ b/vendor/ueberauth_slack/lib/ueberauth/strategy/slack.ex @@ -0,0 +1,374 @@ +defmodule Ueberauth.Strategy.Slack do + @moduledoc """ + Implements an ÜeberauthSlack strategy for authentication with Slack V2 OAuth API. + + When configuring the strategy in the Üeberauth providers, you can specify some defaults. + + * `uid_field` - The field to use as the UID field. This can be any populated field in the info struct. Default `:email` + * `default_scope` - The scope to request by default from slack (permissions). Default "users:read" + * `default_users_scope` - The scope to request by default from slack (permissions). Default "users:read" + * `oauth2_module` - The OAuth2 module to use. Default Ueberauth.Strategy.Slack.OAuth + + ```elixir + + config :ueberauth, Ueberauth, + providers: [ + slack: { Ueberauth.Strategy.Slack, [uid_field: :nickname, default_scope: "users:read,users:write"] } + ] + ``` + """ + use Ueberauth.Strategy, + uid_field: :email, + default_scope: "users:read", + default_user_scope: "", + oauth2_module: Ueberauth.Strategy.Slack.OAuth + + alias Ueberauth.Auth.Info + alias Ueberauth.Auth.Credentials + alias Ueberauth.Auth.Extra + + # When handling the request just redirect to Slack + @doc false + def handle_request!(conn) do + scopes = conn.params["scope"] || option(conn, :default_scope) + user_scopes = conn.params["user_scope"] || option(conn, :default_user_scope) + team = option(conn, :team) + + opts = [scope: scopes, user_scope: user_scopes] + opts = with_state_param(opts, conn) + opts = if team, do: Keyword.put(opts, :team, team), else: opts + + callback_url = callback_url(conn) + + callback_url = + if String.ends_with?(callback_url, "?"), + do: String.slice(callback_url, 0..-2), + else: callback_url + + opts = Keyword.put(opts, :redirect_uri, callback_url) + module = option(conn, :oauth2_module) + + redirect!(conn, apply(module, :authorize_url!, [opts])) + end + + # When handling the callback, if there was no errors we need to + # make two calls. The first, to fetch the slack auth is so that we can get hold of + # the user id so we can make a query to fetch the user info. + # So that it is available later to build the auth struct, we put it in the private section of the conn. + @doc false + def handle_callback!(%Plug.Conn{params: %{"code" => code}} = conn) do + module = option(conn, :oauth2_module) + params = [code: code] + redirect_uri = get_redirect_uri(conn) + + options = %{ + options: [ + client_options: [redirect_uri: redirect_uri] + ] + } + + case apply(module, :get_token!, [params, options]) do + {%{access_token: nil}, %{access_token: nil} = user_token} -> + set_errors!(conn, [ + error(user_token.other_params["error"], user_token.other_params["error_description"]) + ]) + + {bot_token, %{access_token: nil}} -> + handle_token(conn, bot_token) + |> store_bot_token(bot_token) + + {bot_token, user_token} -> + handle_token(conn, user_token) + |> store_bot_token(bot_token) + end + end + + # If we don't match code, then we have an issue + @doc false + def handle_callback!(conn) do + set_errors!(conn, [error("missing_code", "No code received")]) + end + + defp handle_token(conn, token) do + conn + |> store_token(token) + |> fetch_auth(token) + |> fetch_identity(token) + |> fetch_user(token) + |> fetch_team(token) + end + + # We store the token for use later when fetching the slack auth and user and constructing the auth struct. + @doc false + defp store_token(conn, token) do + put_private(conn, :slack_token, token) + end + + defp store_bot_token(conn, token) do + put_private(conn, :slack_bot_token, token) + end + + # Remove the temporary storage in the conn for our data. Run after the auth struct has been built. + @doc false + def handle_cleanup!(conn) do + conn + |> put_private(:slack_auth, nil) + |> put_private(:slack_identity, nil) + |> put_private(:slack_user, nil) + |> put_private(:slack_token, nil) + |> put_private(:slack_bot_token, nil) + end + + # The structure of the requests is such that it is difficult to provide cusomization for the uid field. + # instead, we allow selecting any field from the info struct + @doc false + def uid(conn) do + Map.get(info(conn), option(conn, :uid_field)) + end + + @doc false + def credentials(conn) do + token = conn.private.slack_token + bot_token = conn.private.slack_bot_token + auth = conn.private[:slack_auth] + identity = conn.private[:slack_identity] + user = conn.private[:slack_user] + scope_string = token.other_params["scope"] || "" + scopes = String.split(scope_string, ",") + + %Credentials{ + token: token.access_token, + refresh_token: token.refresh_token, + expires_at: token.expires_at, + token_type: token.token_type, + expires: !!token.expires_at, + scopes: scopes, + other: + Map.merge( + %{ + user: get_in(auth, ["user"]), + user_id: get_in(auth, ["user_id"]) || get_in(identity, ["user", "id"]), + team: get_in(auth, ["team"]) || get_in(identity, ["team", "name"]), + team_id: get_in(auth, ["team_id"]) || get_in(identity, ["team", "id"]), + team_domain: get_in(identity, ["team", "domain"]), + team_url: get_in(auth, ["url"]), + bot_token: bot_token.access_token + }, + user_credentials(user) + ) + } + end + + @doc false + def info(conn) do + user = conn.private[:slack_user] + auth = conn.private[:slack_auth] + identity = conn.private[:slack_identity] + + profile = get_in(user, ["profile"]) || get_in(identity, ["user"]) || %{} + + image_urls = + profile + |> Map.keys() + |> Enum.filter(&(&1 =~ ~r/^image_/)) + |> Enum.into(%{}, &{&1, profile[&1]}) + + team_image_urls = + (identity || %{}) + |> Map.get("team", %{}) + |> Enum.filter(fn {key, _value} -> key =~ ~r/^image_/ end) + |> Enum.into(%{}, fn {key, value} -> {"team_#{key}", value} end) + + %Info{ + name: name_from_user(user) || get_in(identity, ["user", "name"]), + nickname: get_in(user, ["name"]), + email: get_in(profile, ["email"]), + image: get_in(profile, ["image_48"]), + urls: + image_urls + |> Map.merge(team_image_urls) + |> Map.merge(%{ + team_url: get_in(auth, ["url"]) + }) + } + end + + @doc false + def extra(conn) do + %Extra{ + raw_info: %{ + auth: conn.private[:slack_auth], + identity: conn.private[:slack_identity], + token: conn.private[:slack_token], + bot_token: conn.private[:slack_bot_token], + user: conn.private[:slack_user], + team: conn.private[:slack_team] + } + } + end + + defp user_credentials(nil), do: %{} + + defp user_credentials(user) do + %{ + has_2fa: user["has_2fa"], + is_admin: user["is_admin"], + is_owner: user["is_owner"], + is_primary_owner: user["is_primary_owner"], + is_restricted: user["is_restricted"], + is_ultra_restricted: user["is_ultra_restricted"] + } + end + + # Before we can fetch the user, we first need to fetch the auth to find out what the user id is. + defp fetch_auth(conn, token) do + scope_string = token.other_params["scope"] || "" + scopes = String.split(scope_string, ",") + + case Ueberauth.Strategy.Slack.OAuth.get(token, "/auth.test") do + {:ok, %OAuth2.Response{status_code: 401, body: _body}} -> + set_errors!(conn, [error("token", "unauthorized")]) + + {:ok, %OAuth2.Response{status_code: status_code, body: auth}} + when status_code in 200..399 -> + cond do + auth["ok"] -> + put_private(conn, :slack_auth, auth) + + auth["error"] == "invalid_auth" && Enum.member?(scopes, "identity.basic") -> + # If the token has only the "identity.basic" scope then it may error + # at the "auth.test" endpoint but still succeed at the + # "identity.basic" endpoint. + # In this case we rely on fetch_identity to set the error if the + # token is invalid. + conn + + true -> + set_errors!(conn, [error(auth["error"], auth["error"])]) + end + + {:error, %OAuth2.Error{reason: reason}} -> + set_errors!(conn, [error("OAuth2", reason)]) + end + end + + defp fetch_identity(conn, token) do + scope_string = token.other_params["scope"] || "" + scopes = String.split(scope_string, ",") + + case "identity.basic" in scopes do + false -> + conn + + true -> + case Ueberauth.Strategy.Slack.OAuth.get(token, "/users.identity") do + {:ok, %OAuth2.Response{status_code: 401, body: _body}} -> + set_errors!(conn, [error("token", "unauthorized")]) + + {:ok, %OAuth2.Response{status_code: status_code, body: identity}} + when status_code in 200..399 -> + if identity["ok"] do + put_private(conn, :slack_identity, identity) + else + set_errors!(conn, [error(identity["error"], identity["error"])]) + end + + {:error, %OAuth2.Error{reason: reason}} -> + set_errors!(conn, [error("OAuth2", reason)]) + end + end + end + + # If the call to fetch the auth fails, we're going to have failures already in place. + # If this happens don't try and fetch the user and just let it fail. + defp fetch_user(%Plug.Conn{assigns: %{ueberauth_failure: _fails}} = conn, _), do: conn + + # Given the auth and token we can now fetch the user. + defp fetch_user(conn, token) do + scope_string = token.other_params["scope"] || "" + scopes = String.split(scope_string, ",") + + case "users:read" in scopes do + false -> + conn + + true -> + auth = conn.private.slack_auth + + case Ueberauth.Strategy.Slack.OAuth.get(token, "/users.info", %{user: auth["user_id"]}) do + {:ok, %OAuth2.Response{status_code: 401, body: _body}} -> + set_errors!(conn, [error("token", "unauthorized")]) + + {:ok, %OAuth2.Response{status_code: status_code, body: user}} + when status_code in 200..399 -> + if user["ok"] do + put_private(conn, :slack_user, user["user"]) + else + set_errors!(conn, [error(user["error"], user["error"])]) + end + + {:error, %OAuth2.Error{reason: reason}} -> + set_errors!(conn, [error("OAuth2", reason)]) + end + end + end + + defp fetch_team(%Plug.Conn{assigns: %{ueberauth_failure: _fails}} = conn, _), do: conn + + defp fetch_team(conn, token) do + scope_string = token.other_params["scope"] || "" + scopes = String.split(scope_string, ",") + + case "team:read" in scopes do + false -> + conn + + true -> + case Ueberauth.Strategy.Slack.OAuth.get(token, "/team.info") do + {:ok, %OAuth2.Response{status_code: 401, body: _body}} -> + set_errors!(conn, [error("token", "unauthorized")]) + + {:ok, %OAuth2.Response{status_code: status_code, body: team}} + when status_code in 200..399 -> + if team["ok"] do + put_private(conn, :slack_team, team["team"]) + else + set_errors!(conn, [error(team["error"], team["error"])]) + end + + {:error, %OAuth2.Error{reason: reason}} -> + set_errors!(conn, [error("OAuth2", reason)]) + end + end + end + + # Fetch the name to use. We try to start with the most specific name avaialble and + # fallback to the least. + defp name_from_user(nil), do: nil + + defp name_from_user(user) do + [ + user["profile"]["real_name_normalized"], + user["profile"]["real_name"], + user["real_name"], + user["name"] + ] + |> Enum.reject(&(&1 == "" || &1 == nil)) + |> List.first() + end + + defp option(conn, key) do + Keyword.get(options(conn), key, Keyword.get(default_options(), key)) + end + + defp get_redirect_uri(%Plug.Conn{} = conn) do + config = Application.get_env(:ueberauth, Ueberauth) + redirect_uri = Keyword.get(config, :redirect_uri) + + if is_nil(redirect_uri) do + callback_url(conn) + else + redirect_uri + end + end +end diff --git a/vendor/ueberauth_slack/lib/ueberauth/strategy/slack/oauth.ex b/vendor/ueberauth_slack/lib/ueberauth/strategy/slack/oauth.ex new file mode 100644 index 000000000..75c2bb47c --- /dev/null +++ b/vendor/ueberauth_slack/lib/ueberauth/strategy/slack/oauth.ex @@ -0,0 +1,104 @@ +defmodule Ueberauth.Strategy.Slack.OAuth do + @moduledoc """ + An implementation of OAuth2 for Slack OAuth V2 API. + To add your `client_id` and `client_secret` include these values in your configuration. + config :ueberauth, Ueberauth.Strategy.Slack.OAuth, + client_id: System.get_env("SLACK_CLIENT_ID"), + client_secret: System.get_env("SLACK_CLIENT_SECRET") + The JSON serializer used is the same as `Ueberauth` so if you need to + customize it, you can configure it in the `Ueberauth` configuration: + config :ueberauth, Ueberauth, + json_library: Poison # Defaults to Jason + """ + use OAuth2.Strategy + + @defaults [ + strategy: __MODULE__, + site: "https://slack.com/api", + authorize_url: "https://slack.com/oauth/v2/authorize", + token_url: "https://slack.com/api/oauth.v2.access" + ] + + def client(opts \\ []) do + slack_config = Application.get_env(:ueberauth, Ueberauth.Strategy.Slack.OAuth) + + client_opts = + @defaults + |> Keyword.merge(slack_config) + |> Keyword.merge(opts) + + json_library = Ueberauth.json_library() + + client_opts + |> OAuth2.Client.new() + |> OAuth2.Client.put_serializer("application/json", json_library) + end + + def get(token, url, params \\ %{}, headers \\ [], opts \\ []) do + url = + [token: token] + |> client() + |> to_url(url, params) + + headers = [{"authorization", "Bearer #{token.access_token}"}] ++ headers + OAuth2.Client.get(client(), url, headers, opts) + end + + def authorize_url!(params \\ [], opts \\ []) do + opts + |> client + |> OAuth2.Client.authorize_url!(params) + end + + @doc """ + Returns two tokens from Slack API, a "bot token" and a "user token" + """ + @spec get_token!(list(), map()) :: {%OAuth2.AccessToken{} | nil, %OAuth2.AccessToken{} | nil} + def get_token!(params \\ [], options \\ %{}) do + headers = Map.get(options, :headers, []) + options = Map.get(options, :options, []) + client_options = Keyword.get(options, :client_options, []) + + client = OAuth2.Client.get_token!(client(client_options), params, headers, options) + + split_token(client.token) + end + + defp split_token(nil), do: {nil, nil} + + defp split_token(token) do + {token, OAuth2.AccessToken.new(token.other_params["authed_user"])} + end + + # Strategy Callbacks + + def authorize_url(client, params) do + OAuth2.Strategy.AuthCode.authorize_url(client, params) + end + + def get_token(client, params, headers) do + client + |> put_param("client_secret", client.client_secret) + |> put_header("Accept", "application/json") + |> OAuth2.Strategy.AuthCode.get_token(params, headers) + end + + defp endpoint("/" <> _path = endpoint, client), do: client.site <> endpoint + defp endpoint(endpoint, _client), do: endpoint + + defp to_url(client, endpoint, params) do + client_endpoint = + client + |> Map.get(endpoint, endpoint) + |> endpoint(client) + + final_endpoint = + if params do + client_endpoint <> "?" <> URI.encode_query(params) + else + client_endpoint + end + + final_endpoint + end +end