-
-
Notifications
You must be signed in to change notification settings - Fork 36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add OIDC #374
Add OIDC #374
Changes from 4 commits
7b38244
60df3cd
6e089f4
00eba48
eb2a50a
90ca649
0ad0f47
5d8f0fa
e002d39
3ed8afa
1eef965
0397ba6
1264bf3
f56b4ed
fe1c745
a7705d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
PG_PASS=password | ||
AUTHENTIK_SECRET_KEY=password |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,3 +44,4 @@ npm-debug.log | |
/.idea | ||
/lib/media_server/release.ex | ||
/rel/overlays/bin/ | ||
.env |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
defmodule MediaServerWeb.AuthController do | ||
use MediaServerWeb, :controller | ||
|
||
@doc """ | ||
This action is reached via `/auth/:provider` and redirects to the OAuth2 provider | ||
based on the chosen strategy. | ||
""" | ||
def index(conn, %{"provider" => provider}) do | ||
redirect conn, external: authorize_url!(provider) | ||
end | ||
|
||
def delete(conn, _params) do | ||
conn | ||
|> put_flash(:info, "You have been logged out!") | ||
|> configure_session(drop: true) | ||
|> redirect(to: "/") | ||
end | ||
|
||
@doc """ | ||
This action is reached via `/auth/:provider/callback` is the callback URL that | ||
the OAuth2 provider will redirect the user back to with a `code` that will | ||
be used to request an access token. The access token will then be used to | ||
access protected resources on behalf of the user. | ||
""" | ||
def callback(conn, %{"provider" => provider, "code" => code}) do | ||
# Exchange an auth code for an access token | ||
client = get_token!(provider, code) | ||
|
||
# Request the user's data with the access token | ||
if user = MediaServer.Accounts.get_user_by_email(get_user!(provider, client).email) do | ||
|
||
MediaServerWeb.UserAuth.log_in_user(conn, user) | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thoughts on this? Here it gets a user registered in the Midarr database that matches the email returned by Authentik, then logs them in. What's your take on this, and the auth flow? If a user returned by Authentik doesn't exist in Midarr - register them, then log them in? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wouldn't auto-create users since the source of truth is the OIDC provider, meaning to say they would need to be added to the OIDC provider first and if they aren't an existing user they cannot login. Deny by default. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking oauth / OIDC provider would be an optional alternative, with Midarr still the source of truth for user auth. There would need to be some level of user mapping to support both options. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For now just to keep moving forward - I'll go with the logic as above (user must exist in Midarr and OIDC provider to login). We can continue to improve the flow in future iterations 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I meant to comment and say pretty much that. 😅 |
||
|
||
conn | ||
|> redirect(to: "/") | ||
end | ||
|
||
defp authorize_url!("authentik"), do: Authentik.authorize_url! | ||
defp authorize_url!(_), do: raise "No matching provider available" | ||
|
||
defp get_token!("authentik", code), do: Authentik.get_token!(code: code) | ||
defp get_token!(_, _), do: raise "No matching provider available" | ||
|
||
defp get_user!("authentik", client) do | ||
|
||
token = Map.get(client, :token) |> Map.get(:access_token) |> Jason.decode! |> Map.get("access_token") | ||
|
||
%{body: user} = OAuth2.Client.get!(client, System.get_env("OAUTH_USER_URL"), [ | ||
{"authorization", "Bearer #{ token }"} | ||
]) | ||
|
||
decoded_user = Jason.decode!(user) | ||
|
||
%{name: decoded_user["name"], email: decoded_user["email"]} | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
defmodule Authentik do | ||
@moduledoc """ | ||
An OAuth2 strategy for Authentik. | ||
""" | ||
use OAuth2.Strategy | ||
|
||
alias OAuth2.Strategy.AuthCode | ||
|
||
defp config do | ||
[ | ||
strategy: Authentik, | ||
site: System.get_env("OAUTH_ISSUER_URL"), | ||
authorize_url: System.get_env("OAUTH_AUTHORIZE_URL"), | ||
token_url: System.get_env("OAUTH_TOKEN_URL"), | ||
client_id: System.get_env("OAUTH_CLIENT_ID"), | ||
client_secret: System.get_env("OAUTH_CLIENT_SECRET"), | ||
redirect_uri: System.get_env("OAUTH_REDIRECT_URI") | ||
] | ||
end | ||
|
||
def client do | ||
OAuth2.Client.new(config()) | ||
end | ||
|
||
def authorize_url!(params \\ []) do | ||
OAuth2.Client.authorize_url!(client()) | ||
end | ||
|
||
def get_token!(params \\ [], headers \\ []) do | ||
OAuth2.Client.get_token!(client(), params) | ||
end | ||
|
||
def authorize_url(client, params) do | ||
AuthCode.authorize_url(client, params) | ||
end | ||
|
||
def get_token(client, params, headers) do | ||
client | ||
|> put_header("Accept", "application/json") | ||
|> AuthCode.get_token(params, headers) | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@onedr0p Example environment variables for configuring oauth.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good so far.