diff --git a/config/runtime.exs b/config/runtime.exs index f577a2f..b731712 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -40,6 +40,18 @@ cors_origin = System.get_env("CORS_ORIGIN", "*") config :cors_plug, origin: cors_origin +# Zitadel Configuration +config :policy_service, :zitadel, + issuer: System.get_env("ZITADEL_ISSUER"), + client_id: System.get_env("ZITADEL_CLIENT_ID"), + client_secret: System.get_env("ZITADEL_CLIENT_SECRET"), + required_scopes: [ + "openid", + "profile", + "urn:zitadel:iam:user:resourceowner", + "urn:zitadel:iam:org:projects:roles" + ] + # ## Using releases # # If you use `mix release`, you need to explicitly enable the server diff --git a/flake.nix b/flake.nix index 82e6675..aaa6d2f 100644 --- a/flake.nix +++ b/flake.nix @@ -21,7 +21,7 @@ mixFodDeps = beamPackages.fetchMixDeps { inherit pname version; src = ./.; - sha256 = "sha256-ZyrQiL5sOCC+V9S7rVGrCNEqm/TDZkjIx6Y+MZy7+6s="; + sha256 = "sha256-YqPo8102nqTd6cAxt6O2R+nLLa9UfRLza4qojxoMtqM="; }; package = beamPackages.mixRelease { inherit pname version mixFodDeps; diff --git a/lib/policy_service/aggregates/life_policy_application.ex b/lib/policy_service/aggregates/life_policy_application.ex index 9614cb9..675a4cd 100644 --- a/lib/policy_service/aggregates/life_policy_application.ex +++ b/lib/policy_service/aggregates/life_policy_application.ex @@ -32,7 +32,8 @@ defmodule PolicyService.Aggregates.LifePolicyApplication do def validate_insured_object(_), do: {:error, :invalid_life_details} - def validate_insured(%{"type" => "corporate"}), do: {:error, :life_insurance_requires_individual} + def validate_insured(%{"type" => "corporate"}), + do: {:error, :life_insurance_requires_individual} def validate_insured(%{"type" => "individual", "gender" => gender} = insured) when is_binary(gender) and byte_size(gender) > 0 do diff --git a/lib/policy_service/application.ex b/lib/policy_service/application.ex index 4b78d7f..fd1388c 100644 --- a/lib/policy_service/application.ex +++ b/lib/policy_service/application.ex @@ -5,6 +5,10 @@ defmodule PolicyService.Application do use Application + defp get_zitadel_config(key) do + Application.get_env(:policy_service, :zitadel)[key] + end + @impl true def start(_type, _args) do children = [ @@ -19,6 +23,11 @@ defmodule PolicyService.Application do PolicyService.Repo, {DNSCluster, query: Application.get_env(:policy_service, :dns_cluster_query) || :ignore}, {Phoenix.PubSub, name: PolicyService.PubSub, pool_size: 1}, + {Oidcc.ProviderConfiguration.Worker, + %{ + issuer: get_zitadel_config(:issuer), + name: PolicyService.ZitadelProvider + }}, PolicyServiceWeb.Endpoint ] diff --git a/lib/policy_service_web/api_spec.ex b/lib/policy_service_web/api_spec.ex index 039051e..69e24b5 100644 --- a/lib/policy_service_web/api_spec.ex +++ b/lib/policy_service_web/api_spec.ex @@ -1,5 +1,5 @@ defmodule PolicyServiceWeb.ApiSpec do - alias OpenApiSpex.{OpenApi, Info, Server} + alias OpenApiSpex.{OpenApi, Info, Server, Components, SecurityScheme} alias OpenApiSpex.{Info, OpenApi, Paths, Server} alias PolicyServiceWeb.{Endpoint, Router} @behaviour OpenApi @@ -16,7 +16,18 @@ defmodule PolicyServiceWeb.ApiSpec do version: "1.0" }, # Populate the paths from a phoenix router - paths: Paths.from_router(Router) + paths: Paths.from_router(Router), + components: %Components{ + securitySchemes: %{ + "bearerAuth" => %SecurityScheme{ + type: "http", + scheme: "bearer", + bearerFormat: "JWT", + description: "Zitadel JWT bearer token for authentication" + } + } + }, + security: [%{"bearerAuth" => []}] } # Discover request/response schemas from path specs |> OpenApiSpex.resolve_schema_modules() diff --git a/lib/policy_service_web/controllers/policy_controller.ex b/lib/policy_service_web/controllers/policy_controller.ex index b67f143..dd2df97 100644 --- a/lib/policy_service_web/controllers/policy_controller.ex +++ b/lib/policy_service_web/controllers/policy_controller.ex @@ -31,7 +31,7 @@ defmodule PolicyServiceWeb.PolicyController do ) def index(conn, params) do - org_id = conn.assigns[:org_id] || "test" + org_id = conn.assigns[:org_id] case PolicyQueries.list_by_org(org_id, params) do {:ok, {policies, meta}} -> @@ -63,7 +63,7 @@ defmodule PolicyServiceWeb.PolicyController do ) def show(conn, %{"application_id" => application_id}) do - org_id = conn.assigns[:org_id] || "test" + org_id = conn.assigns[:org_id] case PolicyQueries.get_by_application_id(org_id, application_id) do {:ok, policy} -> @@ -89,8 +89,8 @@ defmodule PolicyServiceWeb.PolicyController do def create(conn, params) do application_id = Ecto.UUID.generate() - org_id = conn.assigns[:org_id] || "test" - submitted_by = conn.assigns[:user_id] || "test" + org_id = conn.assigns[:org_id] + submitted_by = conn.assigns[:user_id] with {:ok, policy_type} <- parse_policy_type(params["policy_type"]), {:ok, insured} <- parse_insured(params["insured"]), @@ -173,7 +173,7 @@ defmodule PolicyServiceWeb.PolicyController do ) def accept(conn, %{"application_id" => application_id} = params) do - org_id = conn.assigns[:org_id] || "test" + org_id = conn.assigns[:org_id] with {:ok, policy} <- PolicyQueries.get_by_application_id(org_id, application_id) do command = diff --git a/lib/policy_service_web/plugs/authentication_plug.ex b/lib/policy_service_web/plugs/authentication_plug.ex new file mode 100644 index 0000000..60dae65 --- /dev/null +++ b/lib/policy_service_web/plugs/authentication_plug.ex @@ -0,0 +1,200 @@ +defmodule PolicyServiceWeb.Plugs.AuthenticationPlug do + @moduledoc """ + Authentication plug for validating JWT tokens using Zitadel. + + This plug validates JWT tokens using the oidcc library and extracts + user claims for use throughout the application. + """ + + import Plug.Conn + require Logger + + @doc """ + Initializes the authentication plug. + + ## Options + - :provider - The OIDCC provider configuration worker name (required) + - :client_id - OAuth2 client ID (required) - can be a string or {module, function, args} tuple + - :client_secret - OAuth2 client secret (required) - can be a string or {module, function, args} tuple + - :required_scopes - List of required scopes (optional) + """ + def init(opts) do + provider = Keyword.fetch!(opts, :provider) + client_id = Keyword.fetch!(opts, :client_id) + client_secret = Keyword.fetch!(opts, :client_secret) + required_scopes = Keyword.get(opts, :required_scopes, []) + + %{ + provider: provider, + client_id: resolve_config(client_id), + client_secret: resolve_config(client_secret), + required_scopes: required_scopes + } + end + + defp resolve_config({module, function, args}) + when is_atom(module) and is_atom(function) and is_list(args) do + apply(module, function, args) + end + + defp resolve_config(value) when is_binary(value), do: value + defp resolve_config(value) when is_function(value, 0), do: value.() + defp resolve_config(value), do: value + + @doc """ + Authenticates the request by validating the JWT token. + + Extracts the Authorization header, validates the JWT token using + Zitadel's JWKS, and assigns the extracted claims to the connection. + """ + def call(conn, config) do + with {:ok, token} <- extract_token(conn), + {:ok, validated_token} <- validate_token(token, config), + {:ok, claims} <- extract_claims(validated_token) do + conn + |> assign(:authenticated, true) + |> assign(:current_user, claims) + |> assign(:user_id, claims.user_id) + |> assign(:org_id, claims.org_id) + |> assign(:roles, claims.roles) + |> assign(:scopes, claims.scopes) + else + {:error, :missing_token} -> + handle_missing_token(conn) + + {:error, :invalid_token} = error -> + handle_invalid_token(conn, error) + + {:error, :expired_token} = error -> + handle_expired_token(conn, error) + + {:error, :insufficient_scopes} = error -> + handle_insufficient_scopes(conn, error) + + {:error, reason} = error -> + handle_authentication_error(conn, error, reason) + end + end + + defp extract_token(conn) do + case get_req_header(conn, "authorization") do + ["Bearer " <> token] when token != "" -> + {:ok, token} + + ["Bearer " <> token] when token == "" -> + {:error, :missing_token} + + ["bearer " <> token] when token != "" -> + {:ok, token} + + [] -> + {:error, :missing_token} + + _ -> + {:error, :invalid_token} + end + end + + defp validate_token(token, config) do + try do + case Oidcc.Token.validate_jwt( + token, + config.provider, + %{} + ) do + {:ok, validated_token} -> + validate_required_scopes(validated_token, config) + + {:error, :invalid_token} -> + Logger.warning("Invalid JWT token provided") + {:error, :invalid_token} + + {:error, :expired_token} -> + Logger.warning("Expired JWT token provided") + {:error, :expired_token} + + {:error, reason} -> + Logger.error("Token validation failed: #{inspect(reason)}") + {:error, :invalid_token} + end + rescue + error -> + Logger.error("Token validation error: #{inspect(error)}") + {:error, :invalid_token} + end + end + + defp validate_required_scopes(validated_token, config) do + required_scopes = config.required_scopes || [] + + if required_scopes == [] do + {:ok, validated_token} + else + token_scopes = PolicyServiceWeb.Plugs.ClaimsExtractor.get_scopes(validated_token.claims) + + if has_all_required_scopes?(token_scopes, required_scopes) do + {:ok, validated_token} + else + missing_scopes = required_scopes -- token_scopes + Logger.warning("Token missing required scopes: #{inspect(missing_scopes)}") + {:error, :insufficient_scopes} + end + end + end + + defp has_all_required_scopes?(token_scopes, required_scopes) do + Enum.all?(required_scopes, fn scope -> scope in token_scopes end) + end + + defp extract_claims(validated_token) do + try do + claims = PolicyServiceWeb.Plugs.ClaimsExtractor.extract_claims(validated_token) + PolicyServiceWeb.Plugs.ClaimsExtractor.validate_claims!(validated_token.claims) + {:ok, claims} + rescue + error -> + Logger.error("Failed to extract claims: #{inspect(error)}") + {:error, :invalid_claims} + end + end + + defp handle_missing_token(conn) do + conn + |> put_resp_content_type("application/json") + |> send_resp(401, Jason.encode!(%{error: "Missing authentication token"})) + |> halt() + end + + defp handle_invalid_token(conn, _error) do + conn + |> put_resp_content_type("application/json") + |> send_resp(401, Jason.encode!(%{error: "Invalid authentication token"})) + |> halt() + end + + defp handle_expired_token(conn, _error) do + conn + |> put_resp_content_type("application/json") + |> send_resp(401, Jason.encode!(%{error: "Authentication token has expired"})) + |> halt() + end + + defp handle_insufficient_scopes(conn, _error) do + conn + |> put_resp_content_type("application/json") + |> send_resp( + 403, + Jason.encode!(%{error: "Insufficient permissions - required scopes not granted"}) + ) + |> halt() + end + + defp handle_authentication_error(conn, _error, reason) do + Logger.error("Authentication error: #{inspect(reason)}") + + conn + |> put_resp_content_type("application/json") + |> send_resp(401, Jason.encode!(%{error: "Authentication failed"})) + |> halt() + end +end diff --git a/lib/policy_service_web/plugs/authorization_plug.ex b/lib/policy_service_web/plugs/authorization_plug.ex new file mode 100644 index 0000000..e920e01 --- /dev/null +++ b/lib/policy_service_web/plugs/authorization_plug.ex @@ -0,0 +1,184 @@ +defmodule PolicyServiceWeb.Plugs.AuthorizationPlug do + @moduledoc """ + Authorization plug for enforcing role-based access control. + + This plug checks if authenticated users have the required roles + and scopes to access protected resources. + """ + + import Plug.Conn + require Logger + + @doc """ + Initializes the authorization plug. + + ## Options + - :required_roles - List of roles that can access the resource (optional) + - :required_scopes - List of scopes required to access the resource (optional) + - :resource_owner_check - Function to check if user owns the resource (optional) + """ + def init(opts) do + %{ + required_roles: Keyword.get(opts, :required_roles, []), + required_scopes: Keyword.get(opts, :required_scopes, []), + resource_owner_check: Keyword.get(opts, :resource_owner_check, nil) + } + end + + @doc """ + Authorizes the request by checking roles and scopes. + + Verifies that the authenticated user has the required roles and scopes + to access the requested resource. + """ + def call(conn, config) do + user_roles = conn.assigns[:roles] || [] + user_scopes = conn.assigns[:scopes] || [] + user_id = conn.assigns[:user_id] + org_id = conn.assigns[:org_id] + + with :ok <- check_roles(user_roles, config.required_roles), + :ok <- check_scopes(user_scopes, config.required_scopes), + :ok <- check_resource_ownership(conn, config.resource_owner_check, user_id, org_id) do + conn + else + {:error, :insufficient_role} -> + handle_insufficient_role(conn, config.required_roles) + + {:error, :insufficient_scope} -> + handle_insufficient_scope(conn, config.required_scopes) + + {:error, :not_owner} -> + handle_not_owner(conn) + end + end + + defp check_roles(_user_roles, required_roles) when required_roles == [] do + :ok + end + + defp check_roles(user_roles, required_roles) do + if has_any_role?(user_roles, required_roles) do + :ok + else + Logger.warning( + "User with roles #{inspect(user_roles)} lacks required roles: #{inspect(required_roles)}" + ) + + {:error, :insufficient_role} + end + end + + defp check_scopes(_user_scopes, required_scopes) when required_scopes == [] do + :ok + end + + defp check_scopes(user_scopes, required_scopes) do + if has_all_scopes?(user_scopes, required_scopes) do + :ok + else + Logger.warning( + "User with scopes #{inspect(user_scopes)} lacks required scopes: #{inspect(required_scopes)}" + ) + + {:error, :insufficient_scope} + end + end + + defp check_resource_ownership(_conn, nil, _user_id, _org_id) do + :ok + end + + defp check_resource_ownership(conn, check_function, user_id, org_id) do + try do + if check_function.(conn, user_id, org_id) do + :ok + else + {:error, :not_owner} + end + rescue + error -> + Logger.error("Resource ownership check failed: #{inspect(error)}") + {:error, :not_owner} + end + end + + @doc """ + Checks if the user has any of the required roles. + + Supports role hierarchy where admin has all permissions. + """ + def has_any_role?(user_roles, required_roles) do + cond do + "admin" in user_roles -> + true + + Enum.any?(required_roles, fn role -> role in user_roles end) -> + true + + true -> + false + end + end + + @doc """ + Checks if the user has all of the required scopes. + """ + def has_all_scopes?(user_scopes, required_scopes) do + Enum.all?(required_scopes, fn scope -> scope in user_scopes end) + end + + @doc """ + Checks if the user can access a specific resource based on ownership. + + Admins can access any resource. Other users can only access their own resources. + """ + def can_access_resource?(user_roles, resource_user_id, current_user_id) do + cond do + "admin" in user_roles -> + true + + resource_user_id == current_user_id -> + true + + true -> + false + end + end + + defp handle_insufficient_role(conn, required_roles) do + conn + |> put_resp_content_type("application/json") + |> send_resp( + 403, + Jason.encode!(%{ + error: "Insufficient permissions", + required_roles: required_roles + }) + ) + |> halt() + end + + defp handle_insufficient_scope(conn, required_scopes) do + conn + |> put_resp_content_type("application/json") + |> send_resp( + 403, + Jason.encode!(%{ + error: "Insufficient permissions", + required_scopes: required_scopes + }) + ) + |> halt() + end + + defp handle_not_owner(conn) do + conn + |> put_resp_content_type("application/json") + |> send_resp( + 403, + Jason.encode!(%{error: "You do not have permission to access this resource"}) + ) + |> halt() + end +end diff --git a/lib/policy_service_web/plugs/claims_extractor.ex b/lib/policy_service_web/plugs/claims_extractor.ex new file mode 100644 index 0000000..3b36074 --- /dev/null +++ b/lib/policy_service_web/plugs/claims_extractor.ex @@ -0,0 +1,127 @@ +defmodule PolicyServiceWeb.Plugs.ClaimsExtractor do + @moduledoc """ + Extracts and normalizes Zitadel claims from validated JWT tokens. + + This module handles the extraction of organization ID, user ID, roles, + and scopes from Zitadel standard claims and normalizes them for use + throughout the application. + """ + + @doc """ + Extracts and normalizes claims from a validated OIDCC token. + + ## Parameters + - token: The validated OIDCC token struct + + ## Returns + A map containing normalized claims: + - org_id: Organization ID from Zitadel claims + - user_id: User ID from Zitadel claims + - roles: List of user roles + - scopes: List of granted scopes + """ + def extract_claims(token) do + claims = token.claims + + %{ + org_id: get_org_id(claims), + user_id: get_user_id(claims), + roles: get_roles(claims), + scopes: get_scopes(claims) + } + end + + @doc """ + Extracts organization ID from Zitadel claims. + + Uses the Zitadel-specific claim 'urn:zitadel:iam:user:resourceowner:id' + or falls back to the standard 'azp' (authorized party) claim. + """ + def get_org_id(claims) do + claims["urn:zitadel:iam:user:resourceowner:id"] || + claims["azp"] || + "default" + end + + @doc """ + Extracts user ID from Zitadel claims. + + Uses the standard 'sub' (subject) claim as the primary user identifier. + """ + def get_user_id(claims) do + claims["sub"] || + raise "Missing required user_id claim" + end + + @doc """ + Extracts roles from Zitadel claims. + + Zitadel provides roles in the 'urn:zitadel:iam:org:project:roles' claim + with a nested structure containing role keys and organization context. + """ + def get_roles(claims) do + project_roles = claims["urn:zitadel:iam:org:project:roles"] || [] + extract_roles_from_project_roles(project_roles) + end + + @doc """ + Extracts roles from Zitadel's nested project roles structure. + + The structure is typically: + [ + %{"role1" => %{"id" => "org1", "primaryDomain" => "domain1"}}, + %{"role2" => %{"id" => "org2", "primaryDomain" => "domain2"}} + ] + """ + def extract_roles_from_project_roles(project_roles) when is_list(project_roles) do + project_roles + |> Enum.flat_map(fn role_map -> + role_map + |> Map.keys() + |> Enum.reject(&(&1 == "id" || &1 == "primaryDomain")) + end) + |> Enum.map(&String.downcase/1) + |> Enum.uniq() + end + + def extract_roles_from_project_roles(_), do: [] + + @doc """ + Extracts scopes from Zitadel claims. + + Uses the standard 'scope' claim and splits it into a list. + """ + def get_scopes(claims) do + scope_string = claims["scope"] || "" + + scope_string + |> String.split(" ") + |> Enum.reject(&(&1 == "")) + end + + @doc """ + Checks if the given claims contain a specific role. + """ + def has_role?(claims, role) when is_binary(role) do + roles = get_roles(claims) + role_lower = String.downcase(role) + role_lower in roles + end + + @doc """ + Checks if the given claims contain a specific scope. + """ + def has_scope?(claims, scope) when is_binary(scope) do + scopes = get_scopes(claims) + scope in scopes + end + + @doc """ + Validates that all required claims are present. + """ + def validate_claims!(claims) do + _user_id = get_user_id(claims) + _org_id = get_org_id(claims) + :ok + end +end diff --git a/lib/policy_service_web/router.ex b/lib/policy_service_web/router.ex index c0d3ed3..3f481b8 100644 --- a/lib/policy_service_web/router.ex +++ b/lib/policy_service_web/router.ex @@ -8,6 +8,18 @@ defmodule PolicyServiceWeb.Router do plug OpenApiSpex.Plug.PutApiSpec, module: PolicyServiceWeb.ApiSpec end + pipeline :authenticated do + plug PolicyServiceWeb.Plugs.AuthenticationPlug, + provider: PolicyService.ZitadelProvider, + client_id: {__MODULE__, :get_zitadel_config, [:client_id]}, + client_secret: {__MODULE__, :get_zitadel_config, [:client_secret]}, + required_scopes: {__MODULE__, :get_zitadel_config, [:required_scopes]} + end + + pipeline :authorized do + plug PolicyServiceWeb.Plugs.AuthorizationPlug + end + get "/health", HealthController, :health get "/health/ready", HealthController, :ready @@ -17,6 +29,8 @@ defmodule PolicyServiceWeb.Router do get "/openapi", OpenApiSpex.Plug.RenderSpec, [] scope "/v1" do + pipe_through [:authenticated, :authorized] + get "/policies", PolicyController, :index get "/policies/:application_id", PolicyController, :show post "/policies", PolicyController, :create @@ -27,4 +41,8 @@ defmodule PolicyServiceWeb.Router do scope "/swaggerui" do get "/", OpenApiSpex.Plug.SwaggerUI, path: "/api/openapi" end + + def get_zitadel_config(key) do + Application.get_env(:policy_service, :zitadel)[key] + end end diff --git a/lib/policy_service_web/schemas/policy.ex b/lib/policy_service_web/schemas/policy.ex index 48fda36..2987678 100644 --- a/lib/policy_service_web/schemas/policy.ex +++ b/lib/policy_service_web/schemas/policy.ex @@ -230,8 +230,16 @@ defmodule PolicyServiceWeb.Schemas.Policy do coverage_amount: %Schema{type: :number, example: 100_000}, coverage_years: %Schema{type: :integer, example: 10}, smoker: %Schema{type: :boolean, example: false}, - medications: %Schema{type: :array, items: %Schema{type: :string}, example: ["Aspirin", "Lisinopril"]}, - surgeries: %Schema{type: :array, items: %Schema{type: :string}, example: ["Appendectomy, 2015"]}, + medications: %Schema{ + type: :array, + items: %Schema{type: :string}, + example: ["Aspirin", "Lisinopril"] + }, + surgeries: %Schema{ + type: :array, + items: %Schema{type: :string}, + example: ["Appendectomy, 2015"] + }, weight: %Schema{type: :number, example: 70}, height: %Schema{type: :number, example: 175} } diff --git a/mix.exs b/mix.exs index 9ec4b22..54f304a 100644 --- a/mix.exs +++ b/mix.exs @@ -62,7 +62,9 @@ defmodule PolicyService.MixProject do {:flop, "~> 0.26"}, {:req, "~> 0.5"}, {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}, - {:typedstruct, "~> 0.5"} + {:typedstruct, "~> 0.5"}, + {:oidcc, "~> 3.7"}, + {:oidcc_plug, "~> 0.4"} ] end diff --git a/mix.lock b/mix.lock index 0c97c22..a45fa35 100644 --- a/mix.lock +++ b/mix.lock @@ -23,10 +23,13 @@ "gen_stage": {:hex, :gen_stage, "1.3.2", "7c77e5d1e97de2c6c2f78f306f463bca64bf2f4c3cdd606affc0100b89743b7b", [:mix], [], "hexpm", "0ffae547fa777b3ed889a6b9e1e64566217413d018cabd825f786e843ffe63e7"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "jose": {:hex, :jose, "1.11.12", "06e62b467b61d3726cbc19e9b5489f7549c37993de846dfb3ee8259f9ed208b3", [:mix, :rebar3], [], "hexpm", "31e92b653e9210b696765cdd885437457de1add2a9011d92f8cf63e4641bab7b"}, "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "oidcc": {:hex, :oidcc, "3.7.2", "2047949832ca7984d6d9c218cc5f23e8096bf50ebb809124d3a01673ee2bfe12", [:mix, :rebar3], [{:igniter, "~> 0.6.3 or ~> 0.7.0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jose, "~> 1.11", [hex: :jose, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_registry, "~> 0.3.1", [hex: :telemetry_registry, repo: "hexpm", optional: false]}], "hexpm", "e3f1ed91509fdeb31ec8b9de4ecda0e80cb68b463a9f5b7a9ee1ee40e521e445"}, + "oidcc_plug": {:hex, :oidcc_plug, "0.4.0", "e31ed82f44c0a1685874f7a8574d3ce714603d398c449b8b0c55e89908623979", [:mix], [{:igniter, "~> 0.5.50 or ~> 0.6.0 or ~> 0.7.0", [hex: :igniter, repo: "hexpm", optional: true]}, {:oidcc, "~> 3.7", [hex: :oidcc, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "4d3d6da5f4b51bd9ffc03e4539c631503d459153e6ba31964316c87f4a310068"}, "open_api_spex": {:hex, :open_api_spex, "3.22.2", "0b3c4f572ee69cb6c936abf426b9d84d8eebd34960871fd77aead746f0d69cb0", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0 or ~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "0a4fc08472d75e9cfe96e0748c6b1565b3b4398f97bf43fcce41b41b6fd3fb33"}, "phoenix": {:hex, :phoenix, "1.8.3", "49ac5e485083cb1495a905e47eb554277bdd9c65ccb4fc5100306b350151aa95", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "36169f95cc2e155b78be93d9590acc3f462f1e5438db06e6248613f27c80caec"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.7.0", "75c4b9dfb3efdc42aec2bd5f8bccd978aca0651dbcbc7a3f362ea5d9d43153c6", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "1d75011e4254cb4ddf823e81823a9629559a1be93b4321a6a5f11a5306fbf4cc"}, diff --git a/ops/chart/values.yaml b/ops/chart/values.yaml index 7de6efb..d32eaad 100644 --- a/ops/chart/values.yaml +++ b/ops/chart/values.yaml @@ -72,6 +72,20 @@ controllers: secretKeyRef: name: '{{ include "bjw-s.common.lib.chart.names.fullname" $ }}-cluster-pg-app' key: uri + + # Zitadel Configuration + ZITADEL_ISSUER: + value: "https://zitadel.example.com" + ZITADEL_CLIENT_ID: + valueFrom: + secretKeyRef: + name: '{{ include "bjw-s.common.lib.chart.names.fullname" $ }}-apiapp-client-secret' + key: clientId + ZITADEL_CLIENT_SECRET: + valueFrom: + secretKeyRef: + name: '{{ include "bjw-s.common.lib.chart.names.fullname" $ }}-apiapp-client-secret' + key: clientSecret probes: liveness: enabled: true @@ -250,3 +264,16 @@ rawResources: schemas: - name: eventstore owner: policy_service + + apiapp: + enabled: true + apiVersion: zitadel.github.com/v1alpha1 + kind: APIApp + suffix: apiapp + spec: + spec: + projectRef: + name: seguros-dev + namespace: zitadel-resources-operator + apiAppName: policy-service + authMethodType: API_AUTH_METHOD_TYPE_BASIC