add authentication with zitadel
Some checks failed
Build and Publish / build-release (push) Failing after 1m49s
Some checks failed
Build and Publish / build-release (push) Failing after 1m49s
This commit is contained in:
@@ -40,6 +40,18 @@ cors_origin = System.get_env("CORS_ORIGIN", "*")
|
|||||||
config :cors_plug,
|
config :cors_plug,
|
||||||
origin: cors_origin
|
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
|
# ## Using releases
|
||||||
#
|
#
|
||||||
# If you use `mix release`, you need to explicitly enable the server
|
# If you use `mix release`, you need to explicitly enable the server
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
mixFodDeps = beamPackages.fetchMixDeps {
|
mixFodDeps = beamPackages.fetchMixDeps {
|
||||||
inherit pname version;
|
inherit pname version;
|
||||||
src = ./.;
|
src = ./.;
|
||||||
sha256 = "sha256-ZyrQiL5sOCC+V9S7rVGrCNEqm/TDZkjIx6Y+MZy7+6s=";
|
sha256 = "sha256-YqPo8102nqTd6cAxt6O2R+nLLa9UfRLza4qojxoMtqM=";
|
||||||
};
|
};
|
||||||
package = beamPackages.mixRelease {
|
package = beamPackages.mixRelease {
|
||||||
inherit pname version mixFodDeps;
|
inherit pname version mixFodDeps;
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ defmodule PolicyService.Aggregates.LifePolicyApplication do
|
|||||||
|
|
||||||
def validate_insured_object(_), do: {:error, :invalid_life_details}
|
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)
|
def validate_insured(%{"type" => "individual", "gender" => gender} = insured)
|
||||||
when is_binary(gender) and byte_size(gender) > 0 do
|
when is_binary(gender) and byte_size(gender) > 0 do
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ defmodule PolicyService.Application do
|
|||||||
|
|
||||||
use Application
|
use Application
|
||||||
|
|
||||||
|
defp get_zitadel_config(key) do
|
||||||
|
Application.get_env(:policy_service, :zitadel)[key]
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def start(_type, _args) do
|
def start(_type, _args) do
|
||||||
children = [
|
children = [
|
||||||
@@ -19,6 +23,11 @@ defmodule PolicyService.Application do
|
|||||||
PolicyService.Repo,
|
PolicyService.Repo,
|
||||||
{DNSCluster, query: Application.get_env(:policy_service, :dns_cluster_query) || :ignore},
|
{DNSCluster, query: Application.get_env(:policy_service, :dns_cluster_query) || :ignore},
|
||||||
{Phoenix.PubSub, name: PolicyService.PubSub, pool_size: 1},
|
{Phoenix.PubSub, name: PolicyService.PubSub, pool_size: 1},
|
||||||
|
{Oidcc.ProviderConfiguration.Worker,
|
||||||
|
%{
|
||||||
|
issuer: get_zitadel_config(:issuer),
|
||||||
|
name: PolicyService.ZitadelProvider
|
||||||
|
}},
|
||||||
PolicyServiceWeb.Endpoint
|
PolicyServiceWeb.Endpoint
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
defmodule PolicyServiceWeb.ApiSpec do
|
defmodule PolicyServiceWeb.ApiSpec do
|
||||||
alias OpenApiSpex.{OpenApi, Info, Server}
|
alias OpenApiSpex.{OpenApi, Info, Server, Components, SecurityScheme}
|
||||||
alias OpenApiSpex.{Info, OpenApi, Paths, Server}
|
alias OpenApiSpex.{Info, OpenApi, Paths, Server}
|
||||||
alias PolicyServiceWeb.{Endpoint, Router}
|
alias PolicyServiceWeb.{Endpoint, Router}
|
||||||
@behaviour OpenApi
|
@behaviour OpenApi
|
||||||
@@ -16,7 +16,18 @@ defmodule PolicyServiceWeb.ApiSpec do
|
|||||||
version: "1.0"
|
version: "1.0"
|
||||||
},
|
},
|
||||||
# Populate the paths from a phoenix router
|
# 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
|
# Discover request/response schemas from path specs
|
||||||
|> OpenApiSpex.resolve_schema_modules()
|
|> OpenApiSpex.resolve_schema_modules()
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ defmodule PolicyServiceWeb.PolicyController do
|
|||||||
)
|
)
|
||||||
|
|
||||||
def index(conn, params) 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
|
case PolicyQueries.list_by_org(org_id, params) do
|
||||||
{:ok, {policies, meta}} ->
|
{:ok, {policies, meta}} ->
|
||||||
@@ -63,7 +63,7 @@ defmodule PolicyServiceWeb.PolicyController do
|
|||||||
)
|
)
|
||||||
|
|
||||||
def show(conn, %{"application_id" => application_id}) 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
|
case PolicyQueries.get_by_application_id(org_id, application_id) do
|
||||||
{:ok, policy} ->
|
{:ok, policy} ->
|
||||||
@@ -89,8 +89,8 @@ defmodule PolicyServiceWeb.PolicyController do
|
|||||||
|
|
||||||
def create(conn, params) do
|
def create(conn, params) do
|
||||||
application_id = Ecto.UUID.generate()
|
application_id = Ecto.UUID.generate()
|
||||||
org_id = conn.assigns[:org_id] || "test"
|
org_id = conn.assigns[:org_id]
|
||||||
submitted_by = conn.assigns[:user_id] || "test"
|
submitted_by = conn.assigns[:user_id]
|
||||||
|
|
||||||
with {:ok, policy_type} <- parse_policy_type(params["policy_type"]),
|
with {:ok, policy_type} <- parse_policy_type(params["policy_type"]),
|
||||||
{:ok, insured} <- parse_insured(params["insured"]),
|
{:ok, insured} <- parse_insured(params["insured"]),
|
||||||
@@ -173,7 +173,7 @@ defmodule PolicyServiceWeb.PolicyController do
|
|||||||
)
|
)
|
||||||
|
|
||||||
def accept(conn, %{"application_id" => application_id} = params) 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
|
with {:ok, policy} <- PolicyQueries.get_by_application_id(org_id, application_id) do
|
||||||
command =
|
command =
|
||||||
|
|||||||
200
lib/policy_service_web/plugs/authentication_plug.ex
Normal file
200
lib/policy_service_web/plugs/authentication_plug.ex
Normal file
@@ -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
|
||||||
184
lib/policy_service_web/plugs/authorization_plug.ex
Normal file
184
lib/policy_service_web/plugs/authorization_plug.ex
Normal file
@@ -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
|
||||||
127
lib/policy_service_web/plugs/claims_extractor.ex
Normal file
127
lib/policy_service_web/plugs/claims_extractor.ex
Normal file
@@ -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
|
||||||
@@ -8,6 +8,18 @@ defmodule PolicyServiceWeb.Router do
|
|||||||
plug OpenApiSpex.Plug.PutApiSpec, module: PolicyServiceWeb.ApiSpec
|
plug OpenApiSpex.Plug.PutApiSpec, module: PolicyServiceWeb.ApiSpec
|
||||||
end
|
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", HealthController, :health
|
||||||
get "/health/ready", HealthController, :ready
|
get "/health/ready", HealthController, :ready
|
||||||
|
|
||||||
@@ -17,6 +29,8 @@ defmodule PolicyServiceWeb.Router do
|
|||||||
get "/openapi", OpenApiSpex.Plug.RenderSpec, []
|
get "/openapi", OpenApiSpex.Plug.RenderSpec, []
|
||||||
|
|
||||||
scope "/v1" do
|
scope "/v1" do
|
||||||
|
pipe_through [:authenticated, :authorized]
|
||||||
|
|
||||||
get "/policies", PolicyController, :index
|
get "/policies", PolicyController, :index
|
||||||
get "/policies/:application_id", PolicyController, :show
|
get "/policies/:application_id", PolicyController, :show
|
||||||
post "/policies", PolicyController, :create
|
post "/policies", PolicyController, :create
|
||||||
@@ -27,4 +41,8 @@ defmodule PolicyServiceWeb.Router do
|
|||||||
scope "/swaggerui" do
|
scope "/swaggerui" do
|
||||||
get "/", OpenApiSpex.Plug.SwaggerUI, path: "/api/openapi"
|
get "/", OpenApiSpex.Plug.SwaggerUI, path: "/api/openapi"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_zitadel_config(key) do
|
||||||
|
Application.get_env(:policy_service, :zitadel)[key]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -230,8 +230,16 @@ defmodule PolicyServiceWeb.Schemas.Policy do
|
|||||||
coverage_amount: %Schema{type: :number, example: 100_000},
|
coverage_amount: %Schema{type: :number, example: 100_000},
|
||||||
coverage_years: %Schema{type: :integer, example: 10},
|
coverage_years: %Schema{type: :integer, example: 10},
|
||||||
smoker: %Schema{type: :boolean, example: false},
|
smoker: %Schema{type: :boolean, example: false},
|
||||||
medications: %Schema{type: :array, items: %Schema{type: :string}, example: ["Aspirin", "Lisinopril"]},
|
medications: %Schema{
|
||||||
surgeries: %Schema{type: :array, items: %Schema{type: :string}, example: ["Appendectomy, 2015"]},
|
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},
|
weight: %Schema{type: :number, example: 70},
|
||||||
height: %Schema{type: :number, example: 175}
|
height: %Schema{type: :number, example: 175}
|
||||||
}
|
}
|
||||||
|
|||||||
4
mix.exs
4
mix.exs
@@ -62,7 +62,9 @@ defmodule PolicyService.MixProject do
|
|||||||
{:flop, "~> 0.26"},
|
{:flop, "~> 0.26"},
|
||||||
{:req, "~> 0.5"},
|
{:req, "~> 0.5"},
|
||||||
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
|
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
|
||||||
{:typedstruct, "~> 0.5"}
|
{:typedstruct, "~> 0.5"},
|
||||||
|
{:oidcc, "~> 3.7"},
|
||||||
|
{:oidcc_plug, "~> 0.4"}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
3
mix.lock
3
mix.lock
@@ -23,10 +23,13 @@
|
|||||||
"gen_stage": {:hex, :gen_stage, "1.3.2", "7c77e5d1e97de2c6c2f78f306f463bca64bf2f4c3cdd606affc0100b89743b7b", [:mix], [], "hexpm", "0ffae547fa777b3ed889a6b9e1e64566217413d018cabd825f786e843ffe63e7"},
|
"gen_stage": {:hex, :gen_stage, "1.3.2", "7c77e5d1e97de2c6c2f78f306f463bca64bf2f4c3cdd606affc0100b89743b7b", [:mix], [], "hexpm", "0ffae547fa777b3ed889a6b9e1e64566217413d018cabd825f786e843ffe63e7"},
|
||||||
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
|
"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"},
|
"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"},
|
"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"},
|
"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_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
|
||||||
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
|
"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"},
|
"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": {: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"},
|
"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"},
|
||||||
|
|||||||
@@ -72,6 +72,20 @@ controllers:
|
|||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: '{{ include "bjw-s.common.lib.chart.names.fullname" $ }}-cluster-pg-app'
|
name: '{{ include "bjw-s.common.lib.chart.names.fullname" $ }}-cluster-pg-app'
|
||||||
key: uri
|
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:
|
probes:
|
||||||
liveness:
|
liveness:
|
||||||
enabled: true
|
enabled: true
|
||||||
@@ -250,3 +264,16 @@ rawResources:
|
|||||||
schemas:
|
schemas:
|
||||||
- name: eventstore
|
- name: eventstore
|
||||||
owner: policy_service
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user