Fix slack and gitlab login

This commit is contained in:
Simon Prévost 2023-08-29 09:50:19 -04:00
parent d04e302e74
commit 69b6d1cf0f
13 changed files with 852 additions and 28 deletions

View File

@ -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 {

View File

@ -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}`)
);
}

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

10
mix.exs
View File

@ -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"},

View File

@ -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"},

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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