diff --git a/lib/comfycamp/accounts.ex b/lib/comfycamp/accounts.ex index db4b61e..e4ec82a 100644 --- a/lib/comfycamp/accounts.ex +++ b/lib/comfycamp/accounts.ex @@ -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. """ diff --git a/lib/comfycamp/accounts/user_token.ex b/lib/comfycamp/accounts/user_token.ex index 418b8eb..6ff4abd 100644 --- a/lib/comfycamp/accounts/user_token.ex +++ b/lib/comfycamp/accounts/user_token.ex @@ -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. diff --git a/lib/comfycamp/sso.ex b/lib/comfycamp/sso.ex index 8cb5090..357391c 100644 --- a/lib/comfycamp/sso.ex +++ b/lib/comfycamp/sso.ex @@ -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 diff --git a/lib/comfycamp/sso/id_token.ex b/lib/comfycamp/sso/id_token.ex new file mode 100644 index 0000000..d79da48 --- /dev/null +++ b/lib/comfycamp/sso/id_token.ex @@ -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 diff --git a/lib/comfycamp/sso/oidc_app.ex b/lib/comfycamp/sso/oidc_app.ex index d0dc376..53bbfa4 100644 --- a/lib/comfycamp/sso/oidc_app.ex +++ b/lib/comfycamp/sso/oidc_app.ex @@ -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 diff --git a/lib/comfycamp/sso/oidc_code.ex b/lib/comfycamp/sso/oidc_code.ex new file mode 100644 index 0000000..86e2af1 --- /dev/null +++ b/lib/comfycamp/sso/oidc_code.ex @@ -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 diff --git a/lib/comfycamp/sso/oidc_redirect_uri.ex b/lib/comfycamp/sso/oidc_redirect_uri.ex new file mode 100644 index 0000000..e91993c --- /dev/null +++ b/lib/comfycamp/sso/oidc_redirect_uri.ex @@ -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 diff --git a/lib/comfycamp/token.ex b/lib/comfycamp/token.ex new file mode 100644 index 0000000..6ce97c7 --- /dev/null +++ b/lib/comfycamp/token.ex @@ -0,0 +1,3 @@ +defmodule Comfycamp.Token do + use Joken.Config +end diff --git a/lib/comfycamp_web/controllers/oauth_controller.ex b/lib/comfycamp_web/controllers/oauth_controller.ex index d64eeba..e0392be 100644 --- a/lib/comfycamp_web/controllers/oauth_controller.ex +++ b/lib/comfycamp_web/controllers/oauth_controller.ex @@ -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, - page_title: "Подтвердите вход", - app_name: app.name, - params: URI.encode_query(params) - ) + 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"]) - redirect(conn, external: uri) + 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 diff --git a/lib/comfycamp_web/controllers/oauth_html.ex b/lib/comfycamp_web/controllers/oauth_html.ex index 1836192..4564efe 100644 --- a/lib/comfycamp_web/controllers/oauth_html.ex +++ b/lib/comfycamp_web/controllers/oauth_html.ex @@ -1,7 +1,7 @@ defmodule ComfycampWeb.OauthHTML do use ComfycampWeb, :html - def confirm(assigns) do + def authorize(assigns) do ~H"""

Подтвердите вход

Приложению "<%= @app_name %>" будут доступны:

@@ -13,4 +13,11 @@ defmodule ComfycampWeb.OauthHTML do <.link href={"/oauth/generate_code?#{@params}"} method="POST">Разрешить доступ """ end + + def error(assigns) do + ~H""" +

Ошибка

+

<%= @description %>

+ """ + end end diff --git a/lib/comfycamp_web/controllers/oauth_json.ex b/lib/comfycamp_web/controllers/oauth_json.ex new file mode 100644 index 0000000..6a93c9c --- /dev/null +++ b/lib/comfycamp_web/controllers/oauth_json.ex @@ -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 diff --git a/lib/comfycamp_web/controllers/oidc_app_html/show.html.heex b/lib/comfycamp_web/controllers/oidc_app_html/show.html.heex index 38c08d6..fe017ee 100644 --- a/lib/comfycamp_web/controllers/oidc_app_html/show.html.heex +++ b/lib/comfycamp_web/controllers/oidc_app_html/show.html.heex @@ -16,5 +16,26 @@ <:item title="Enabled"><%= @oidc_app.enabled %> + + + <.link navigate={~p"/admin/oidc_apps/#{@oidc_app}/redirect_uris/new"}> + <.button> + Добавить redirect URI + + + <.back navigate={~p"/admin/oidc_apps"}>Back to OpenID apps diff --git a/lib/comfycamp_web/controllers/oidc_uri_controller.ex b/lib/comfycamp_web/controllers/oidc_uri_controller.ex new file mode 100644 index 0000000..95075cc --- /dev/null +++ b/lib/comfycamp_web/controllers/oidc_uri_controller.ex @@ -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 diff --git a/lib/comfycamp_web/controllers/oidc_uri_html.ex b/lib/comfycamp_web/controllers/oidc_uri_html.ex new file mode 100644 index 0000000..373be2f --- /dev/null +++ b/lib/comfycamp_web/controllers/oidc_uri_html.ex @@ -0,0 +1,26 @@ +defmodule ComfycampWeb.OIDCRedirectURIHTML do + use ComfycampWeb, :html + + def new(assigns) do + ~H""" +
+ <.header>Новый redirect URI + <.uri_form changeset={@changeset} action={~p"/admin/oidc_apps/#{@oidc_app}/redirect_uris"} /> +
+ """ + end + + def uri_form(assigns) do + ~H""" + <.simple_form :let={f} for={@changeset} action={@action}> + <.error :if={@changeset.action}> + Что-то пошло не так + + <.input field={f[:uri]} type="url" label="Redirect URI" /> + <:actions> + <.button>Сохранить + + + """ + end +end diff --git a/lib/comfycamp_web/router.ex b/lib/comfycamp_web/router.ex index 83e9cd2..e25cf87 100644 --- a/lib/comfycamp_web/router.ex +++ b/lib/comfycamp_web/router.ex @@ -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 diff --git a/mix.exs b/mix.exs index d9031e7..0dffa35 100644 --- a/mix.exs +++ b/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 diff --git a/mix.lock b/mix.lock index c8c937d..5e832be 100644 --- a/mix.lock +++ b/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"}, diff --git a/priv/repo/migrations/20240910201629_oidc_code.exs b/priv/repo/migrations/20240910201629_oidc_code.exs new file mode 100644 index 0000000..2c2aeac --- /dev/null +++ b/priv/repo/migrations/20240910201629_oidc_code.exs @@ -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 diff --git a/priv/repo/migrations/20240912162936_oidc_redirect_uri.exs b/priv/repo/migrations/20240912162936_oidc_redirect_uri.exs new file mode 100644 index 0000000..711b736 --- /dev/null +++ b/priv/repo/migrations/20240912162936_oidc_redirect_uri.exs @@ -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 diff --git a/priv/repo/migrations/20240913184848_oidc_code_uri.exs b/priv/repo/migrations/20240913184848_oidc_code_uri.exs new file mode 100644 index 0000000..3178f51 --- /dev/null +++ b/priv/repo/migrations/20240913184848_oidc_code_uri.exs @@ -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 diff --git a/test/comfycamp/sso_test.exs b/test/comfycamp/sso_test.exs index e708f93..a138511 100644 --- a/test/comfycamp/sso_test.exs +++ b/test/comfycamp/sso_test.exs @@ -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