cover/Elixir.DaProductApp.Partners.ApiKeyOperation.html

1 defmodule DaProductApp.Partners.ApiKeyOperation do
2 @moduledoc """
3 Schema for tracking API key operations and audit trail.
4
5 This tracks when API keys are:
6 - Generated
7 - Rotated
8 - Suspended
9 - Revoked
10 - Used for authentication
11
12 Provides audit logging for security compliance.
13 """
14 use Ecto.Schema
15 import Ecto.Changeset
16
17 @primary_key {:id, :binary_id, autogenerate: true}
18 @foreign_key_type :binary_id
19
20
:-(
schema "api_key_operations" do
21 belongs_to :partner, DaProductApp.Partners.Partner
22
23 field :operation_type, :string # "GENERATED", "ROTATED", "SUSPENDED", "REVOKED", "AUTHENTICATED"
24 field :api_key_hash, :string # Hash of the API key (for audit without storing actual key)
25 field :ip_address, :string # IP address where operation occurred
26 field :user_agent, :string # User agent for API requests
27 field :success, :boolean # Whether operation was successful
28 field :failure_reason, :string # Reason for failure if unsuccessful
29 field :metadata, :map # Additional context (rate limit status, etc.)
30
31 timestamps(type: :utc_datetime)
32 end
33
34 @doc """
35 Changeset for creating API key operation log entries
36 """
37 def changeset(operation, attrs) do
38 operation
39 |> cast(attrs, [
40 :partner_id, :operation_type, :api_key_hash, :ip_address,
41 :user_agent, :success, :failure_reason, :metadata
42 ])
43 |> validate_required([:partner_id, :operation_type, :success])
44 |> validate_inclusion(:operation_type, [
45 "GENERATED", "ROTATED", "SUSPENDED", "REVOKED", "AUTHENTICATED"
46 ])
47
:-(
|> foreign_key_constraint(:partner_id)
48 end
49
50 @doc """
51 Create an operation log entry for successful authentication
52 """
53
:-(
def log_authentication(partner_id, api_key, ip_address, user_agent \\ nil) do
54 %__MODULE__{}
55
:-(
|> changeset(%{
56 partner_id: partner_id,
57 operation_type: "AUTHENTICATED",
58 api_key_hash: hash_api_key(api_key),
59 ip_address: ip_address,
60 user_agent: user_agent,
61 success: true,
62 metadata: %{timestamp: DateTime.utc_now()}
63 })
64 end
65
66 @doc """
67 Create an operation log entry for failed authentication
68 """
69
:-(
def log_failed_authentication(api_key, ip_address, reason, user_agent \\ nil) do
70 %__MODULE__{}
71
:-(
|> changeset(%{
72 operation_type: "AUTHENTICATED",
73 api_key_hash: hash_api_key(api_key),
74 ip_address: ip_address,
75 user_agent: user_agent,
76 success: false,
77 failure_reason: reason,
78 metadata: %{timestamp: DateTime.utc_now()}
79 })
80 end
81
82 @doc """
83 Create an operation log entry for API key generation
84 """
85
:-(
def log_key_generation(partner_id, api_key, ip_address \\ nil) do
86 %__MODULE__{}
87
:-(
|> changeset(%{
88 partner_id: partner_id,
89 operation_type: "GENERATED",
90 api_key_hash: hash_api_key(api_key),
91 ip_address: ip_address,
92 success: true,
93 metadata: %{
94 timestamp: DateTime.utc_now(),
95 expires_at: DateTime.add(DateTime.utc_now(), 365, :day)
96 }
97 })
98 end
99
100 @doc """
101 Create an operation log entry for API key rotation
102 """
103
:-(
def log_key_rotation(partner_id, old_api_key, new_api_key, ip_address \\ nil) do
104 %__MODULE__{}
105
:-(
|> changeset(%{
106 partner_id: partner_id,
107 operation_type: "ROTATED",
108 api_key_hash: hash_api_key(new_api_key),
109 ip_address: ip_address,
110 success: true,
111 metadata: %{
112 timestamp: DateTime.utc_now(),
113 old_key_hash: hash_api_key(old_api_key),
114 new_key_hash: hash_api_key(new_api_key)
115 }
116 })
117 end
118
119 # Private helper to create a hash of the API key for audit purposes
120 defp hash_api_key(api_key) when is_binary(api_key) do
121
:-(
:crypto.hash(:sha256, api_key) |> Base.encode16()
122 end
123
:-(
defp hash_api_key(_), do: nil
124 end
Line Hits Source