feat: draft implementation of oidc protocol
This commit is contained in:
parent
1eb567cd6d
commit
489c39e16c
21 changed files with 468 additions and 33 deletions
|
@ -241,6 +241,18 @@ defmodule Comfycamp.Accounts do
|
|||
token
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generate a pair of bearer and refresh tokens.
|
||||
"""
|
||||
def generate_oauth_tokens(user) do
|
||||
{bearer_token_value, bearer_token} = UserToken.build_bearer_token(user)
|
||||
Repo.insert!(bearer_token)
|
||||
{refresh_token_value, refresh_token} = UserToken.build_refresh_token(user)
|
||||
Repo.insert!(refresh_token)
|
||||
|
||||
{bearer_token_value, refresh_token_value}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the user with the given signed token.
|
||||
"""
|
||||
|
|
|
@ -46,6 +46,22 @@ defmodule Comfycamp.Accounts.UserToken do
|
|||
{token, %UserToken{token: token, context: "session", user_id: user.id}}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generate a bearer token for oauth.
|
||||
"""
|
||||
def build_bearer_token(user) do
|
||||
token = :crypto.strong_rand_bytes(@rand_size)
|
||||
{token, %UserToken{token: token, context: "bearer", user_id: user.id}}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generate a refresh token that may be exchanged for a new bearer token.
|
||||
"""
|
||||
def build_refresh_token(user) do
|
||||
token = :crypto.strong_rand_bytes(@rand_size)
|
||||
{token, %UserToken{token: token, context: "refresh", user_id: user.id}}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the token is valid and returns its underlying lookup query.
|
||||
|
||||
|
|
|
@ -5,9 +5,10 @@ defmodule Comfycamp.SSO do
|
|||
|
||||
import Ecto.Query, warn: false
|
||||
alias Comfycamp.Repo
|
||||
alias Comfycamp.Rand
|
||||
|
||||
alias Comfycamp.SSO.OIDCApp
|
||||
alias Comfycamp.SSO.OIDCCode
|
||||
alias Comfycamp.SSO.OIDCRedirectURI
|
||||
|
||||
@doc """
|
||||
Returns the list of oidc_apps.
|
||||
|
@ -36,7 +37,44 @@ defmodule Comfycamp.SSO do
|
|||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_oidc_app!(id), do: Repo.get!(OIDCApp, id)
|
||||
def get_oidc_app!(id) do
|
||||
query =
|
||||
from a in OIDCApp,
|
||||
preload: [:redirect_uris],
|
||||
where: a.client_id == ^id
|
||||
|
||||
Repo.one!(query)
|
||||
end
|
||||
|
||||
def get_oidc_app_by_secret!(client_secret) do
|
||||
query =
|
||||
from a in OIDCApp,
|
||||
where: a.client_secret == ^client_secret
|
||||
|
||||
Repo.one!(query)
|
||||
end
|
||||
|
||||
def has_redirect_uri?(client_id, redirect_uri) do
|
||||
query =
|
||||
from a in OIDCApp,
|
||||
join: u in assoc(a, :redirect_uris),
|
||||
where: u.uri == ^redirect_uri and a.client_id == ^client_id
|
||||
|
||||
Repo.aggregate(query, :count) >= 1
|
||||
end
|
||||
|
||||
def get_oidc_redirect_uri!(id), do: Repo.get(OIDCRedirectURI, id)
|
||||
|
||||
def get_oidc_code!(value) do
|
||||
ten_minutes_ago = DateTime.utc_now() |> DateTime.add(-600, :second)
|
||||
|
||||
query =
|
||||
from c in OIDCCode,
|
||||
preload: [:oidc_app],
|
||||
where: c.value == ^value and c.inserted_at >= ^ten_minutes_ago
|
||||
|
||||
Repo.one!(query)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a oidc_app.
|
||||
|
@ -51,13 +89,24 @@ defmodule Comfycamp.SSO do
|
|||
|
||||
"""
|
||||
def create_oidc_app(attrs \\ %{}) do
|
||||
app = %OIDCApp{
|
||||
client_id: Rand.get_random_string(20),
|
||||
client_secret: Rand.get_random_string(32)
|
||||
}
|
||||
%OIDCApp{}
|
||||
|> OIDCApp.creation_changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
app
|
||||
|> OIDCApp.changeset(attrs)
|
||||
@doc """
|
||||
Create a temporary code for OIDC app
|
||||
that may be exchanged for an access token.
|
||||
"""
|
||||
def create_oidc_code(attrs \\ %{}) do
|
||||
%OIDCCode{}
|
||||
|> OIDCCode.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
def create_oidc_redirect_uri(attrs \\ %{}) do
|
||||
%OIDCRedirectURI{}
|
||||
|> OIDCRedirectURI.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
|
@ -75,7 +124,7 @@ defmodule Comfycamp.SSO do
|
|||
"""
|
||||
def update_oidc_app(%OIDCApp{} = oidc_app, attrs) do
|
||||
oidc_app
|
||||
|> OIDCApp.changeset(attrs)
|
||||
|> OIDCApp.update_changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
|
@ -95,6 +144,14 @@ defmodule Comfycamp.SSO do
|
|||
Repo.delete(oidc_app)
|
||||
end
|
||||
|
||||
def delete_oidc_code(%OIDCCode{} = code) do
|
||||
Repo.delete(code)
|
||||
end
|
||||
|
||||
def delete_oidc_redirect_uri(%OIDCRedirectURI{} = uri) do
|
||||
Repo.delete(uri)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking oidc_app changes.
|
||||
|
||||
|
@ -105,6 +162,10 @@ defmodule Comfycamp.SSO do
|
|||
|
||||
"""
|
||||
def change_oidc_app(%OIDCApp{} = oidc_app, attrs \\ %{}) do
|
||||
OIDCApp.changeset(oidc_app, attrs)
|
||||
OIDCApp.update_changeset(oidc_app, attrs)
|
||||
end
|
||||
|
||||
def change_oidc_redirect_uri(%OIDCRedirectURI{} = oidc_redirect_uri, attrs \\ %{}) do
|
||||
OIDCRedirectURI.changeset(oidc_redirect_uri, attrs)
|
||||
end
|
||||
end
|
||||
|
|
21
lib/comfycamp/sso/id_token.ex
Normal file
21
lib/comfycamp/sso/id_token.ex
Normal file
|
@ -0,0 +1,21 @@
|
|||
defmodule Comfycamp.SSO.IDToken do
|
||||
defstruct [:iss, :sub, :aud, :exp, :iat]
|
||||
|
||||
def build_id_token(user, client_id) do
|
||||
{_, now} = DateTime.now("Etc/UTC")
|
||||
issued_at = DateTime.to_unix(now)
|
||||
|
||||
expires_at =
|
||||
now
|
||||
|> DateTime.add(1, :day)
|
||||
|> DateTime.to_unix()
|
||||
|
||||
%Comfycamp.SSO.IDToken{
|
||||
iss: "https://" <> System.get_env("PHX_HOST"),
|
||||
sub: Integer.to_string(user.id),
|
||||
aud: client_id,
|
||||
exp: expires_at,
|
||||
iat: issued_at
|
||||
}
|
||||
end
|
||||
end
|
|
@ -2,21 +2,35 @@ defmodule Comfycamp.SSO.OIDCApp do
|
|||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Comfycamp.SSO.OIDCCode
|
||||
alias Comfycamp.SSO.OIDCRedirectURI
|
||||
alias Comfycamp.Rand
|
||||
|
||||
@derive {Phoenix.Param, key: :client_id}
|
||||
@primary_key {:client_id, :string, autogenerate: false}
|
||||
schema "oidc_apps" do
|
||||
field :enabled, :boolean, default: false
|
||||
field :name, :string
|
||||
field :client_secret, :string
|
||||
field :name, :string
|
||||
field :enabled, :boolean, default: false
|
||||
|
||||
has_many :codes, OIDCCode, foreign_key: :oidc_app_id
|
||||
has_many :redirect_uris, OIDCRedirectURI, foreign_key: :oidc_app_id
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(oidc_app, attrs) do
|
||||
def update_changeset(oidc_app, attrs) do
|
||||
oidc_app
|
||||
|> cast(attrs, [:name, :enabled])
|
||||
|> validate_required([:name, :enabled])
|
||||
|> validate_length(:name, min: 2, max: 48)
|
||||
end
|
||||
|
||||
def creation_changeset(oidc_app, attrs) do
|
||||
oidc_app
|
||||
|> update_changeset(attrs)
|
||||
|> change(client_id: Rand.get_random_string(20))
|
||||
|> change(client_secret: Rand.get_random_string(32))
|
||||
end
|
||||
end
|
||||
|
|
31
lib/comfycamp/sso/oidc_code.ex
Normal file
31
lib/comfycamp/sso/oidc_code.ex
Normal file
|
@ -0,0 +1,31 @@
|
|||
defmodule Comfycamp.SSO.OIDCCode do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Comfycamp.Accounts.User
|
||||
alias Comfycamp.SSO.OIDCApp
|
||||
alias Comfycamp.Rand
|
||||
|
||||
@derive {Phoenix.Param, key: :value}
|
||||
@primary_key {:value, :string, autogenerate: false}
|
||||
schema "oidc_codes" do
|
||||
field :redirect_uri, :string
|
||||
belongs_to :user, User
|
||||
|
||||
belongs_to :oidc_app, OIDCApp,
|
||||
type: :string,
|
||||
foreign_key: :oidc_app_id,
|
||||
references: :client_id
|
||||
|
||||
timestamps(type: :utc_datetime, updated_at: false)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(oidc_code, attrs) do
|
||||
oidc_code
|
||||
|> cast(attrs, [:user_id, :oidc_app_id, :redirect_uri])
|
||||
|> change(value: Rand.get_random_string(12))
|
||||
|> validate_required([:value, :user_id, :oidc_app_id, :redirect_uri])
|
||||
|> validate_length(:value, min: 6, max: 255)
|
||||
end
|
||||
end
|
22
lib/comfycamp/sso/oidc_redirect_uri.ex
Normal file
22
lib/comfycamp/sso/oidc_redirect_uri.ex
Normal file
|
@ -0,0 +1,22 @@
|
|||
defmodule Comfycamp.SSO.OIDCRedirectURI do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Comfycamp.SSO.OIDCApp
|
||||
|
||||
schema "oidc_redirect_uris" do
|
||||
field :uri, :string
|
||||
|
||||
belongs_to :oidc_app, OIDCApp,
|
||||
type: :string,
|
||||
foreign_key: :oidc_app_id,
|
||||
references: :client_id
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(uri, attrs) do
|
||||
uri
|
||||
|> cast(attrs, [:uri, :oidc_app_id])
|
||||
|> validate_required([:uri, :oidc_app_id])
|
||||
end
|
||||
end
|
3
lib/comfycamp/token.ex
Normal file
3
lib/comfycamp/token.ex
Normal file
|
@ -0,0 +1,3 @@
|
|||
defmodule Comfycamp.Token do
|
||||
use Joken.Config
|
||||
end
|
|
@ -1,24 +1,110 @@
|
|||
defmodule ComfycampWeb.OauthController do
|
||||
use ComfycampWeb, :controller
|
||||
|
||||
alias Comfycamp.Accounts
|
||||
alias Comfycamp.SSO
|
||||
alias Comfycamp.SSO.OIDCApp
|
||||
alias Comfycamp.SSO.OIDCCode
|
||||
alias Comfycamp.SSO.IDToken
|
||||
alias Comfycamp.Token
|
||||
|
||||
def confirm(conn, %{"client_id" => client_id, "response_type" => "code"} = params) do
|
||||
app = %OIDCApp{enabled: true} = SSO.get_oidc_app!(client_id)
|
||||
@doc """
|
||||
Check the request parameters and current user status,
|
||||
then ask the user to confirm that he wants to share his info with Relying Party.
|
||||
"""
|
||||
def authorize(conn, %{"client_id" => client_id, "redirect_uri" => redirect_uri} = params) do
|
||||
app = SSO.get_oidc_app!(client_id)
|
||||
current_user = conn.assigns.current_user
|
||||
|
||||
render(conn, :confirm,
|
||||
with {:is_approved, true} <- {:is_approved, current_user.is_approved},
|
||||
{:response_type, "code"} <- {:response_type, params["response_type"]},
|
||||
{:is_enabled, true} <- {:is_enabled, app.enabled},
|
||||
{:has_uri, true} <- {:has_uri, SSO.has_redirect_uri?(client_id, redirect_uri)} do
|
||||
render(conn, :authorize,
|
||||
page_title: "Подтвердите вход",
|
||||
app_name: app.name,
|
||||
params: URI.encode_query(params)
|
||||
)
|
||||
else
|
||||
{:is_approved, false} ->
|
||||
render(conn, :error,
|
||||
description:
|
||||
"Ваш аккаунт ещё не был одобрен, подождите немного или свяжитесь с администратором"
|
||||
)
|
||||
|
||||
{:response_type, response_type} ->
|
||||
render(conn, :error, description: "Неподдерживаемый response type: #{response_type}")
|
||||
|
||||
{:is_enabled, false} ->
|
||||
render(conn, :error, description: "Приложение отключено")
|
||||
|
||||
{:has_uri, false} ->
|
||||
render(conn, :error, description: "Redirect URI не зарегистрирован или отсутствует")
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generate an Authorization Code and redirect the user back to Relying Party.
|
||||
"""
|
||||
def generate_code(conn, %{"client_id" => client_id, "redirect_uri" => redirect_uri} = params) do
|
||||
%OIDCApp{enabled: true} = SSO.get_oidc_app!(client_id)
|
||||
app = SSO.get_oidc_app!(client_id)
|
||||
current_user = conn.assigns.current_user
|
||||
|
||||
uri = build_redirect_uri(redirect_uri, "test_code", params["state"])
|
||||
with {:is_approved, true} <- {:is_approved, current_user.is_approved},
|
||||
{:is_enabled, true} <- {:is_enabled, app.enabled},
|
||||
{:has_uri, true} <- {:has_uri, SSO.has_redirect_uri?(client_id, redirect_uri)} do
|
||||
{:ok, code} =
|
||||
SSO.create_oidc_code(%{
|
||||
oidc_app_id: client_id,
|
||||
user_id: conn.assigns.current_user.id,
|
||||
redirect_uri: redirect_uri
|
||||
})
|
||||
|
||||
uri = build_redirect_uri(redirect_uri, code.value, params["state"])
|
||||
redirect(conn, external: uri)
|
||||
else
|
||||
{:is_approved, false} ->
|
||||
render(conn, :error,
|
||||
description:
|
||||
"Ваш аккаунт ещё не был одобрен, подождите немного или свяжитесь с администратором"
|
||||
)
|
||||
|
||||
{:is_enabled, false} ->
|
||||
render(conn, :error, description: "Приложение отключено")
|
||||
|
||||
{:has_uri, false} ->
|
||||
render(conn, :error, description: "Redirect URI не зарегистрирован или отсутствует")
|
||||
end
|
||||
end
|
||||
|
||||
def token(conn, %{"code" => code_value, "redirect_uri" => redirect_uri}) do
|
||||
# Check that code is still valid and redirect uri has not been altered.
|
||||
%OIDCCode{redirect_uri: ^redirect_uri} = code = SSO.get_oidc_code!(code_value)
|
||||
|
||||
# Get client secret.
|
||||
[auth_header] = Plug.Conn.get_req_header(conn, "authorization")
|
||||
"Basic " <> client_secret = auth_header
|
||||
|
||||
# Check that client provided a valid secret for an active OIDC app.
|
||||
%OIDCApp{enabled: true} = oidc_app = SSO.get_oidc_app_by_secret!(client_secret)
|
||||
|
||||
# Check that OIDC app is referenced by provided code.
|
||||
app_client_id = oidc_app.client_id
|
||||
^app_client_id = code.oidc_app.client_id
|
||||
|
||||
# Delete the code.
|
||||
SSO.delete_oidc_code(code)
|
||||
|
||||
{access_token, refresh_token} = Accounts.generate_oauth_tokens(conn.assigns.current_user)
|
||||
|
||||
id_token = IDToken.build_id_token(conn.assigns.current_user, oidc_app.client_id)
|
||||
{:ok, signed_id_token, _claims} = Token.generate_and_sign(id_token)
|
||||
|
||||
render(conn, :token,
|
||||
access_token: access_token,
|
||||
refresh_token: refresh_token,
|
||||
id_token: signed_id_token
|
||||
)
|
||||
end
|
||||
|
||||
defp build_redirect_uri(redirect_uri, code, state) do
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
defmodule ComfycampWeb.OauthHTML do
|
||||
use ComfycampWeb, :html
|
||||
|
||||
def confirm(assigns) do
|
||||
def authorize(assigns) do
|
||||
~H"""
|
||||
<h1>Подтвердите вход</h1>
|
||||
<p>Приложению "<%= @app_name %>" будут доступны:</p>
|
||||
|
@ -13,4 +13,11 @@ defmodule ComfycampWeb.OauthHTML do
|
|||
<.link href={"/oauth/generate_code?#{@params}"} method="POST">Разрешить доступ</.link>
|
||||
"""
|
||||
end
|
||||
|
||||
def error(assigns) do
|
||||
~H"""
|
||||
<h1>Ошибка</h1>
|
||||
<p><%= @description %></p>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
|
10
lib/comfycamp_web/controllers/oauth_json.ex
Normal file
10
lib/comfycamp_web/controllers/oauth_json.ex
Normal file
|
@ -0,0 +1,10 @@
|
|||
defmodule ComfycampWeb.OauthJSON do
|
||||
def token(%{access_token: access_token, refresh_token: refresh_token, id_token: id_token}) do
|
||||
%{
|
||||
access_token: access_token,
|
||||
token_type: "Bearer",
|
||||
refresh_token: refresh_token,
|
||||
id_token: id_token
|
||||
}
|
||||
end
|
||||
end
|
|
@ -16,5 +16,26 @@
|
|||
<:item title="Enabled"><%= @oidc_app.enabled %></:item>
|
||||
</.list>
|
||||
|
||||
<ul>
|
||||
<%= for uri <- @oidc_app.redirect_uris do %>
|
||||
<li>
|
||||
<%= uri.uri %>
|
||||
<.link
|
||||
href={~p"/admin/oidc_apps/#{@oidc_app}/redirect_uris/#{uri}"}
|
||||
method="delete"
|
||||
data-confirm="Хотите удалить ссылку?"
|
||||
>
|
||||
Удалить
|
||||
</.link>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
<.link navigate={~p"/admin/oidc_apps/#{@oidc_app}/redirect_uris/new"}>
|
||||
<.button>
|
||||
Добавить redirect URI
|
||||
</.button>
|
||||
</.link>
|
||||
|
||||
<.back navigate={~p"/admin/oidc_apps"}>Back to OpenID apps</.back>
|
||||
</div>
|
||||
|
|
39
lib/comfycamp_web/controllers/oidc_uri_controller.ex
Normal file
39
lib/comfycamp_web/controllers/oidc_uri_controller.ex
Normal file
|
@ -0,0 +1,39 @@
|
|||
defmodule ComfycampWeb.OIDCRedirectURIController do
|
||||
use ComfycampWeb, :controller
|
||||
|
||||
alias Comfycamp.SSO
|
||||
alias Comfycamp.SSO.OIDCRedirectURI
|
||||
|
||||
def new(conn, %{"client_id" => client_id}) do
|
||||
changeset = SSO.change_oidc_redirect_uri(%OIDCRedirectURI{})
|
||||
oidc_app = SSO.get_oidc_app!(client_id)
|
||||
|
||||
conn
|
||||
|> put_layout(html: :admin)
|
||||
|> render(:new, changeset: changeset, oidc_app: oidc_app)
|
||||
end
|
||||
|
||||
def create(conn, %{"client_id" => client_id, "oidc_redirect_uri" => uri_params}) do
|
||||
oidc_app = SSO.get_oidc_app!(client_id)
|
||||
uri_params = Map.put(uri_params, "oidc_app_id", client_id)
|
||||
|
||||
case SSO.create_oidc_redirect_uri(uri_params) do
|
||||
{:ok, _uri} ->
|
||||
conn
|
||||
|> redirect(to: ~p"/admin/oidc_apps/#{client_id}")
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
conn
|
||||
|> put_layout(html: :admin)
|
||||
|> render(:new, changeset: changeset, oidc_app: oidc_app)
|
||||
end
|
||||
end
|
||||
|
||||
def delete(conn, %{"client_id" => client_id, "id" => uri_id}) do
|
||||
uri = SSO.get_oidc_redirect_uri!(uri_id)
|
||||
{:ok, _uri} = SSO.delete_oidc_redirect_uri(uri)
|
||||
|
||||
conn
|
||||
|> redirect(to: ~p"/admin/oidc_apps/#{client_id}")
|
||||
end
|
||||
end
|
26
lib/comfycamp_web/controllers/oidc_uri_html.ex
Normal file
26
lib/comfycamp_web/controllers/oidc_uri_html.ex
Normal file
|
@ -0,0 +1,26 @@
|
|||
defmodule ComfycampWeb.OIDCRedirectURIHTML do
|
||||
use ComfycampWeb, :html
|
||||
|
||||
def new(assigns) do
|
||||
~H"""
|
||||
<div>
|
||||
<.header>Новый redirect URI</.header>
|
||||
<.uri_form changeset={@changeset} action={~p"/admin/oidc_apps/#{@oidc_app}/redirect_uris"} />
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
def uri_form(assigns) do
|
||||
~H"""
|
||||
<.simple_form :let={f} for={@changeset} action={@action}>
|
||||
<.error :if={@changeset.action}>
|
||||
Что-то пошло не так
|
||||
</.error>
|
||||
<.input field={f[:uri]} type="url" label="Redirect URI" />
|
||||
<:actions>
|
||||
<.button>Сохранить</.button>
|
||||
</:actions>
|
||||
</.simple_form>
|
||||
"""
|
||||
end
|
||||
end
|
|
@ -28,9 +28,11 @@ defmodule ComfycampWeb.Router do
|
|||
get "/cinema", CinemaController, :index
|
||||
end
|
||||
|
||||
# scope "/api", ComfycampWeb do
|
||||
# pipe_through :api
|
||||
# end
|
||||
scope "/", ComfycampWeb do
|
||||
pipe_through :api
|
||||
|
||||
post "/oauth/token", OauthController, :token
|
||||
end
|
||||
|
||||
# Enable LiveDashboard and Swoosh mailbox preview in development
|
||||
if Application.compile_env(:comfycamp, :dev_routes) do
|
||||
|
@ -68,9 +70,6 @@ defmodule ComfycampWeb.Router do
|
|||
scope "/", ComfycampWeb do
|
||||
pipe_through [:browser, :require_authenticated_user]
|
||||
|
||||
get "/oauth", OauthController, :confirm
|
||||
post "/oauth/generate_code", OauthController, :generate_code
|
||||
|
||||
live_session :require_authenticated_user,
|
||||
on_mount: [{ComfycampWeb.UserAuth, :ensure_authenticated}] do
|
||||
live "/users/settings", UserSettingsLive, :edit
|
||||
|
@ -78,6 +77,17 @@ defmodule ComfycampWeb.Router do
|
|||
end
|
||||
end
|
||||
|
||||
scope "/", ComfycampWeb do
|
||||
pipe_through [:browser]
|
||||
|
||||
scope "/" do
|
||||
pipe_through [:require_authenticated_user]
|
||||
|
||||
get "/oauth/authorize", OauthController, :authorize
|
||||
post "/oauth/generate_code", OauthController, :generate_code
|
||||
end
|
||||
end
|
||||
|
||||
scope "/", ComfycampWeb do
|
||||
pipe_through [:browser]
|
||||
|
||||
|
@ -98,6 +108,10 @@ defmodule ComfycampWeb.Router do
|
|||
resources "/notes", NotesEditorController
|
||||
resources "/users", UserEditorController, only: [:index, :show]
|
||||
resources "/oidc_apps", OIDCAppController
|
||||
|
||||
resources "/oidc_apps/:client_id/redirect_uris", OIDCRedirectURIController,
|
||||
only: [:new, :create, :delete]
|
||||
|
||||
put "/users/:id/approve", UserEditorController, :approve
|
||||
put "/users/:id/disapprove", UserEditorController, :disapprove
|
||||
end
|
||||
|
|
3
mix.exs
3
mix.exs
|
@ -53,7 +53,8 @@ defmodule Comfycamp.MixProject do
|
|||
{:bandit, "~> 1.2"},
|
||||
# Markdown rendering
|
||||
{:earmark, "~> 1.4"},
|
||||
{:gen_smtp, "~> 1.2"}
|
||||
{:gen_smtp, "~> 1.2"},
|
||||
{:joken, "~> 2.6"}
|
||||
]
|
||||
end
|
||||
|
||||
|
|
2
mix.lock
2
mix.lock
|
@ -19,6 +19,8 @@
|
|||
"gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"},
|
||||
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
|
||||
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
|
||||
"joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"},
|
||||
"jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"},
|
||||
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
|
||||
"mint": {:hex, :mint, "1.6.0", "88a4f91cd690508a04ff1c3e28952f322528934be541844d54e0ceb765f01d5e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "3c5ae85d90a5aca0a49c0d8b67360bbe407f3b54f1030a111047ff988e8fefaa"},
|
||||
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
|
||||
|
|
20
priv/repo/migrations/20240910201629_oidc_code.exs
Normal file
20
priv/repo/migrations/20240910201629_oidc_code.exs
Normal file
|
@ -0,0 +1,20 @@
|
|||
defmodule Comfycamp.Repo.Migrations.OidcCode do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:oidc_codes, primary_key: false) do
|
||||
add :value, :string, null: false, primary_key: true
|
||||
add :user_id, references(:users, on_delete: :delete_all), null: false
|
||||
|
||||
add :oidc_app_id,
|
||||
references(:oidc_apps, type: :string, column: :client_id, on_delete: :delete_all),
|
||||
null: false
|
||||
|
||||
timestamps(type: :utc_datetime, updated_at: false)
|
||||
end
|
||||
end
|
||||
|
||||
def down do
|
||||
drop table(:oidc_codes)
|
||||
end
|
||||
end
|
20
priv/repo/migrations/20240912162936_oidc_redirect_uri.exs
Normal file
20
priv/repo/migrations/20240912162936_oidc_redirect_uri.exs
Normal file
|
@ -0,0 +1,20 @@
|
|||
defmodule Comfycamp.Repo.Migrations.OidcRedirectUri do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:oidc_redirect_uris) do
|
||||
add :uri, :string, null: false
|
||||
|
||||
add :oidc_app_id,
|
||||
references(:oidc_apps, type: :string, column: :client_id, on_delete: :delete_all),
|
||||
null: false
|
||||
end
|
||||
|
||||
create unique_index(:oidc_redirect_uris, [:uri])
|
||||
end
|
||||
|
||||
def down do
|
||||
drop unique_index(:oidc_redirect_uris, [:uri])
|
||||
drop table(:oidc_redirect_uris)
|
||||
end
|
||||
end
|
9
priv/repo/migrations/20240913184848_oidc_code_uri.exs
Normal file
9
priv/repo/migrations/20240913184848_oidc_code_uri.exs
Normal file
|
@ -0,0 +1,9 @@
|
|||
defmodule Comfycamp.Repo.Migrations.OidcCodeUri do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:oidc_codes) do
|
||||
add :redirect_uri, :string, null: false, default: "https://example.com"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -17,7 +17,7 @@ defmodule Comfycamp.SSOTest do
|
|||
|
||||
test "get_oidc_app!/1 returns the oidc_app with given id" do
|
||||
oidc_app = oidc_app_fixture()
|
||||
assert SSO.get_oidc_app!(oidc_app.client_id) == oidc_app
|
||||
assert SSO.get_oidc_app!(oidc_app.client_id).client_id == oidc_app.client_id
|
||||
end
|
||||
|
||||
test "create_oidc_app/1 with valid data creates a oidc_app" do
|
||||
|
@ -55,7 +55,7 @@ defmodule Comfycamp.SSOTest do
|
|||
test "update_oidc_app/2 with invalid data returns error changeset" do
|
||||
oidc_app = oidc_app_fixture()
|
||||
assert {:error, %Ecto.Changeset{}} = SSO.update_oidc_app(oidc_app, @invalid_attrs)
|
||||
assert oidc_app == SSO.get_oidc_app!(oidc_app.client_id)
|
||||
assert oidc_app.client_id == SSO.get_oidc_app!(oidc_app.client_id).client_id
|
||||
end
|
||||
|
||||
test "delete_oidc_app/1 deletes the oidc_app" do
|
||||
|
|
Loading…
Reference in a new issue