From bfd259e05c602d8342a2fcacc3a9b3157dcf9f8c Mon Sep 17 00:00:00 2001 From: dynamyc Date: Sun, 3 Mar 2024 21:11:43 +0100 Subject: [PATCH] A generic OpenID Connect implementation (#413) * Add ueberauth_oidc as dependency * Update OIDC dependency Todo: Figure out why the Hex.io one didn't work * Begin OIDC implementation * It.. actually works??? * Minor changes * Change french so it hopefully fits better * Update mix.lock * Break everything, but shall continue on other machine * Add OIDC environment variables to README * Check for OIDC client ID before adding OpenID Worker * Add fancy icon * Remove comments * Fix formatting * Fix formatting... again. * Change back to hex.io's ueberauth_oidc --- README.md | 41 ++-- config/runtime.exs | 17 ++ lib/accent.ex | 5 + mix.exs | 1 + mix.lock | 2 + .../app/components/login-forms/component.ts | 5 + webapp/app/locales/en-us.json | 1 + webapp/app/locales/fr-ca.json | 1 + webapp/app/styles/components/login-forms.scss | 7 + .../app/templates/components/login-forms.hbs | 7 + webapp/public/assets/auth_providers/oidc.svg | 194 ++++++++++++++++++ 11 files changed, 263 insertions(+), 18 deletions(-) create mode 100644 webapp/public/assets/auth_providers/oidc.svg diff --git a/README.md b/README.md index 21bcc141..d67b27e9 100644 --- a/README.md +++ b/README.md @@ -139,24 +139,29 @@ Accent provides a default value for every required environment variable. This me Various login providers are included in Accent using Ueberauth to abstract services. -| Variable | Default | Description | -| -------------------------- | -------------------- | --------------------------------------------------------------------------------------- | -| `DUMMY_LOGIN_ENABLED` | _none_ | If specified, the password-less authentication (with only the email) will be available. | -| `GITHUB_CLIENT_ID` | _none_ | | -| `GITHUB_CLIENT_SECRET` | _none_ | | -| `GITLAB_CLIENT_ID` | _none_ | | -| `GITLAB_CLIENT_SECRET` | _none_ | | -| `GITLAB_SITE_URL` | `https://gitlab.com` | | -| `GOOGLE_API_CLIENT_ID` | _none_ | | -| `GOOGLE_API_CLIENT_SECRET` | _none_ | | -| `SLACK_CLIENT_ID` | _none_ | | -| `SLACK_CLIENT_SECRET` | _none_ | | -| `SLACK_TEAM_ID` | _none_ | | -| `DISCORD_CLIENT_ID` | _none_ | | -| `DISCORD_CLIENT_SECRET` | _none_ | | -| `MICROSOFT_CLIENT_ID` | _none_ | | -| `MICROSOFT_CLIENT_SECRET` | _none_ | | -| `MICROSOFT_TENANT_ID` | _none_ | | +| Variable | Default | Description | +| -------------------------- | ---------------------- | --------------------------------------------------------------------------------------- | +| `DUMMY_LOGIN_ENABLED` | _none_ | If specified, the password-less authentication (with only the email) will be available. | +| `GITHUB_CLIENT_ID` | _none_ | | +| `GITHUB_CLIENT_SECRET` | _none_ | | +| `GITLAB_CLIENT_ID` | _none_ | | +| `GITLAB_CLIENT_SECRET` | _none_ | | +| `GITLAB_SITE_URL` | `https://gitlab.com` | | +| `GOOGLE_API_CLIENT_ID` | _none_ | | +| `GOOGLE_API_CLIENT_SECRET` | _none_ | | +| `SLACK_CLIENT_ID` | _none_ | | +| `SLACK_CLIENT_SECRET` | _none_ | | +| `SLACK_TEAM_ID` | _none_ | | +| `DISCORD_CLIENT_ID` | _none_ | | +| `DISCORD_CLIENT_SECRET` | _none_ | | +| `MICROSOFT_CLIENT_ID` | _none_ | | +| `MICROSOFT_CLIENT_SECRET` | _none_ | | +| `MICROSOFT_TENANT_ID` | _none_ | | +| `OIDC_CLIENT_ID` | _none_ | | +| `OIDC_CLIENT_SECRET` | _none_ | | +| `OIDC_DISCOVERY_URI` | _none_ | | +| `OIDC_UID_FIELD` | `sub` | | +| `OIDC_SCOPE` | `openid profile email` | | ### Email setup diff --git a/config/runtime.exs b/config/runtime.exs index 065c7f72..1b6b8bcd 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -88,6 +88,11 @@ providers = providers = if get_env("AUTH0_CLIENT_ID"), do: [{:auth0, {Ueberauth.Strategy.Auth0, []}} | providers], else: providers +providers = + if get_env("OIDC_CLIENT_ID"), + do: [{:oidc, {Ueberauth.Strategy.OIDC, [default: [provider: :default_oidc, uid_field: :sub]]}} | providers], + else: providers + providers = if get_env("DUMMY_LOGIN_ENABLED"), do: [{:dummy, {Accent.Auth.Ueberauth.DummyStrategy, []}} | providers], @@ -129,6 +134,18 @@ config :ueberauth, Ueberauth.Strategy.Microsoft.OAuth, client_secret: get_env("MICROSOFT_CLIENT_SECRET"), tenant_id: get_env("MICROSOFT_TENANT_ID") +config :ueberauth, Ueberauth.Strategy.OIDC, + default_oidc: [ + fetch_userinfo: true, + uid_field: get_env("OIDC_UID_FIELD") || "sub", + client_id: get_env("OIDC_CLIENT_ID"), + client_secret: get_env("OIDC_CLIENT_SECRET"), + discovery_document_uri: get_env("OIDC_DISCOVERY_URI"), + redirect_uri: "#{static_uri}/auth/oidc/callback", + response_type: "code", + scope: get_env("OIDC_SCOPE") || "openid profile email" + ] + config :accent, Accent.WebappView, path: "priv/static/webapp/index.html", sentry_dsn: get_env("WEBAPP_SENTRY_DSN") || "", diff --git a/lib/accent.ex b/lib/accent.ex index d5509dda..ba8c376f 100644 --- a/lib/accent.ex +++ b/lib/accent.ex @@ -16,6 +16,11 @@ defmodule Accent do {Phoenix.PubSub, [name: Accent.PubSub, adapter: Phoenix.PubSub.PG2]} ] + children = + if Application.get_env(:ueberauth, Ueberauth.Strategy.OIDC)[:default_oidc][:client_id], + do: [{OpenIDConnect.Worker, Application.get_env(:ueberauth, Ueberauth.Strategy.OIDC)} | children], + else: children + if Application.get_env(:sentry, :dsn) do {:ok, _} = Logger.add_backend(Sentry.LoggerBackend) end diff --git a/mix.exs b/mix.exs index 8ee1c0fb..224b65f2 100644 --- a/mix.exs +++ b/mix.exs @@ -91,6 +91,7 @@ defmodule Accent.Mixfile do {:ueberauth_github, "~> 0.7"}, {:ueberauth_discord, "~> 0.5"}, {:ueberauth_auth0, "~> 2.0"}, + {:ueberauth_oidc, "~> 0.1.7"}, # Errors {:sentry, "~> 7.0"}, diff --git a/mix.lock b/mix.lock index 36065c00..44d57731 100644 --- a/mix.lock +++ b/mix.lock @@ -62,6 +62,7 @@ "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, "oauth2": {:hex, :oauth2, "2.1.0", "beb657f393814a3a7a8a15bd5e5776ecae341fd344df425342a3b6f1904c2989", [:mix], [{:tesla, "~> 1.5", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "8ac07f85b3307dd1acfeb0ec852f64161b22f57d0ce0c15e616a1dfc8ebe2b41"}, "oban": {:hex, :oban, "2.17.3", "ddfd5710aadcd550d2e174c8d73ce5f1865601418cf54a91775f20443fb832b7", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "452eada8bfe0d0fefd0740ab5fa8cf3ef6c375df0b4a3c3805d179022a04738a"}, + "openid_connect": {:hex, :openid_connect, "0.2.2", "c05055363330deab39ffd89e609db6b37752f255a93802006d83b45596189c0b", [:mix], [{:httpoison, "~> 1.2", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "735769b6d592124b58edd0582554ce638524c0214cd783d8903d33357d74cc13"}, "p1_utils": {:hex, :p1_utils, "1.0.15", "731f76ae1f31f4554afb2ae629cb5589d53bd13efc72b11f5a7c3b1242f91046", [:rebar3], [], "hexpm", "1d308c3f37d7f770fb39abe3b86701b82d54414bc2499d9499edde3cb50bcf19"}, "parallel_stream": {:hex, :parallel_stream, "1.1.0", "f52f73eb344bc22de335992377413138405796e0d0ad99d995d9977ac29f1ca9", [:mix], [], "hexpm", "684fd19191aedfaf387bbabbeb8ff3c752f0220c8112eb907d797f4592d6e871"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, @@ -103,6 +104,7 @@ "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_google": {:hex, :ueberauth_google, "0.12.1", "90cf49743588193334f7a00da252f92d90bfd178d766c0e4291361681fafec7d", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.10.0", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "7f7deacd679b2b66e3bffb68ecc77aa1b5396a0cbac2941815f253128e458c38"}, "ueberauth_microsoft": {:hex, :ueberauth_microsoft, "0.23.0", "5c78e02a83d821ee45f96216bb6140ba688cc79b8b26e7ff438e3abe24615e1d", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "0c08d98203e6d3069f30306f09a6cb55b95c2bda94d6f8e90f05bd442ee96b82"}, + "ueberauth_oidc": {:git, "https://github.com/DefactoSoftware/ueberauth_oidc.git", "0e46efb1214d848256f2cdfa394b80448a06c9a4", []}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"}, "vega_lite": {:hex, :vega_lite, "0.1.8", "7f6119126ecaf4bc2c1854084370d7091424f5cce4795fbac044eee9963f0752", [:mix], [{:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: false]}], "hexpm", "6c8a9271f850612dd8a90de8d1ebd433590ed07ffef76fc2397c240dc04d3fdc"}, diff --git a/webapp/app/components/login-forms/component.ts b/webapp/app/components/login-forms/component.ts index 2e1c168c..66f6fb1c 100644 --- a/webapp/app/components/login-forms/component.ts +++ b/webapp/app/components/login-forms/component.ts @@ -21,6 +21,7 @@ export default class LoginForms extends Component { discordUrl = `${config.API.AUTHENTICATION_PATH}/discord`; microsoftUrl = `${config.API.AUTHENTICATION_PATH}/microsoft`; auth0Url = `${config.API.AUTHENTICATION_PATH}/auth0`; + oidcUrl = `${config.API.AUTHENTICATION_PATH}/oidc`; get version() { return config.version === '__VERSION__' ? 'dev' : config.version; @@ -62,6 +63,10 @@ export default class LoginForms extends Component { return this.providerIds.includes('microsoft'); } + get oidcLoginEnabled() { + return this.providerIds.includes('oidc'); + } + get dummyUrl() { return `${config.API.AUTHENTICATION_PATH}/dummy/callback?email=${this.username}`; } diff --git a/webapp/app/locales/en-us.json b/webapp/app/locales/en-us.json index b928802f..9ec671b5 100644 --- a/webapp/app/locales/en-us.json +++ b/webapp/app/locales/en-us.json @@ -62,6 +62,7 @@ "slack": "Login with Slack →", "discord": "Login with Discord →", "microsoft": "Login with Microsoft →", + "oidc": "Login with OpenID Connect →", "dummy": "Enter an email and login →" }, "dummy_login_form": { diff --git a/webapp/app/locales/fr-ca.json b/webapp/app/locales/fr-ca.json index 61234819..cea7d94b 100644 --- a/webapp/app/locales/fr-ca.json +++ b/webapp/app/locales/fr-ca.json @@ -78,6 +78,7 @@ "slack": "Se connecter avec Slack →", "discord": "Se connecter avec Discord →", "microsoft": "Se connecter avec Microsoft →", + "oidc": "Connectez-vous avec OIDC →", "dummy": "Entrez un courriel et connectez-vous →" }, "dummy_login_form": { diff --git a/webapp/app/styles/components/login-forms.scss b/webapp/app/styles/components/login-forms.scss index dfe12e81..14b3af53 100644 --- a/webapp/app/styles/components/login-forms.scss +++ b/webapp/app/styles/components/login-forms.scss @@ -218,6 +218,13 @@ a.loginButton { color: #03a5f0; background: lighten(#03a5f0, 48%); } + + &.loginButton--oidc { + border-color: #f7931e; + text-shadow: none; + color: #f7931e; + background: lighten(#f7931e, 48%); + } } .loginButton-logo { diff --git a/webapp/app/templates/components/login-forms.hbs b/webapp/app/templates/components/login-forms.hbs index 5a2d0d9a..72cc2140 100644 --- a/webapp/app/templates/components/login-forms.hbs +++ b/webapp/app/templates/components/login-forms.hbs @@ -92,6 +92,13 @@ {{t 'components.login_forms.auth0'}} {{/if}} + + {{#if this.oidcLoginEnabled}} + + + {{t 'components.login_forms.oidc'}} + + {{/if}} {{/if}} diff --git a/webapp/public/assets/auth_providers/oidc.svg b/webapp/public/assets/auth_providers/oidc.svg new file mode 100644 index 00000000..523e9461 --- /dev/null +++ b/webapp/public/assets/auth_providers/oidc.svg @@ -0,0 +1,194 @@ + + + + + +