cover/Elixir.DaProductApp.Partners.ApiKeyService.html

1 defmodule DaProductApp.Partners.ApiKeyService do
2 @moduledoc """
3 Service module for advanced API key management operations.
4
5 Provides additional functionality beyond the basic Partners context:
6 - Bulk API key operations
7 - Advanced security checks
8 - API key analytics and monitoring
9 - Audit trail management
10 """
11
12 alias DaProductApp.Partners
13 alias DaProductApp.Partners.{Partner, ApiKeyOperation}
14 alias DaProductApp.Repo
15
16 import Ecto.Query
17
18 @doc """
19 Generate API keys for multiple partners in bulk
20 """
21
:-(
def bulk_generate_api_keys(partner_ids, expires_in_days \\ 365) when is_list(partner_ids) do
22
:-(
results =
23 partner_ids
24 |> Enum.map(fn partner_id ->
25
:-(
case Partners.get_partner(partner_id) do
26
:-(
nil -> {:error, partner_id, :partner_not_found}
27 partner ->
28
:-(
case Partners.generate_api_key(partner, expires_in_days) do
29
:-(
{:ok, updated_partner} -> {:ok, partner_id, updated_partner}
30
:-(
{:error, changeset} -> {:error, partner_id, changeset}
31 end
32 end
33 end)
34
35
:-(
successful = Enum.filter(results, fn {status, _, _} -> status == :ok end)
36
:-(
failed = Enum.filter(results, fn {status, _, _} -> status == :error end)
37
38
:-(
%{
39 successful: successful,
40 failed: failed,
41 total_processed: length(results),
42 success_count: length(successful),
43 failure_count: length(failed)
44 }
45 end
46
47 @doc """
48 Rotate API keys for partners that are expiring soon
49 """
50
:-(
def rotate_expiring_keys(days_before_expiry \\ 30) do
51
:-(
expiry_threshold = DateTime.add(DateTime.utc_now(), days_before_expiry, :day)
52
53
:-(
partners_to_rotate =
54 from(p in Partner,
55 where: p.status == "ACTIVE",
56 where: not is_nil(p.api_key_expires_at),
57 where: p.api_key_expires_at <= ^expiry_threshold
58 )
59 |> Repo.all()
60
61
:-(
results =
62 partners_to_rotate
63 |> Enum.map(fn partner ->
64
:-(
case Partners.rotate_api_key(partner) do
65 {:ok, updated_partner} ->
66 # Log the rotation
67
:-(
log_operation(ApiKeyOperation.log_key_rotation(
68
:-(
partner.id,
69
:-(
partner.api_key,
70
:-(
updated_partner.api_key
71 ))
72
:-(
{:ok, partner.id, updated_partner}
73 {:error, changeset} ->
74
:-(
{:error, partner.id, changeset}
75 end
76 end)
77
78
:-(
%{
79 partners_processed: length(partners_to_rotate),
80 results: results
81 }
82 end
83
84 @doc """
85 Get API key usage statistics for a partner
86 """
87
:-(
def get_api_key_stats(partner_id, days_back \\ 30) do
88
:-(
start_date = DateTime.add(DateTime.utc_now(), -days_back, :day)
89
90
:-(
operations =
91 from(op in ApiKeyOperation,
92 where: op.partner_id == ^partner_id,
93 where: op.inserted_at >= ^start_date,
94 order_by: [desc: :inserted_at]
95 )
96 |> Repo.all()
97
98
:-(
auth_operations = Enum.filter(operations, &(&1.operation_type == "AUTHENTICATED"))
99
:-(
successful_auths = Enum.filter(auth_operations, &(&1.success == true))
100
:-(
failed_auths = Enum.filter(auth_operations, &(&1.success == false))
101
102
:-(
%{
103 total_operations: length(operations),
104 authentication_attempts: length(auth_operations),
105 successful_authentications: length(successful_auths),
106 failed_authentications: length(failed_auths),
107 success_rate: calculate_success_rate(successful_auths, auth_operations),
108 last_used: get_last_successful_auth(successful_auths),
109 failure_reasons: get_failure_reasons(failed_auths),
110 daily_usage: group_by_day(successful_auths)
111 }
112 end
113
114 @doc """
115 Check if an API key shows suspicious activity
116 """
117
:-(
def detect_suspicious_activity(partner_id, hours_back \\ 24) do
118
:-(
start_time = DateTime.add(DateTime.utc_now(), -hours_back, :hour)
119
120
:-(
operations =
121 from(op in ApiKeyOperation,
122 where: op.partner_id == ^partner_id,
123 where: op.operation_type == "AUTHENTICATED",
124 where: op.inserted_at >= ^start_time
125 )
126 |> Repo.all()
127
128
:-(
failed_attempts = Enum.filter(operations, &(&1.success == false))
129
:-(
successful_attempts = Enum.filter(operations, &(&1.success == true))
130
131
:-(
unique_ips = operations |> Enum.map(&(&1.ip_address)) |> Enum.uniq() |> length()
132
:-(
failed_count = length(failed_attempts)
133
:-(
total_attempts = length(operations)
134
135
:-(
suspicious_indicators = []
136
137 # High failure rate
138
:-(
suspicious_indicators =
139
:-(
if failed_count > 0 and (failed_count / total_attempts) > 0.5 do
140 ["high_failure_rate" | suspicious_indicators]
141 else
142
:-(
suspicious_indicators
143 end
144
145 # Multiple IPs
146
:-(
suspicious_indicators =
147
:-(
if unique_ips > 3 do
148 ["multiple_ips" | suspicious_indicators]
149 else
150
:-(
suspicious_indicators
151 end
152
153 # High frequency (more than 1000 requests per hour)
154
:-(
suspicious_indicators =
155
:-(
if total_attempts > 1000 do
156 ["high_frequency" | suspicious_indicators]
157 else
158
:-(
suspicious_indicators
159 end
160
161
:-(
%{
162 is_suspicious: length(suspicious_indicators) > 0,
163 indicators: suspicious_indicators,
164 failed_attempts: failed_count,
165 total_attempts: total_attempts,
166 unique_ips: unique_ips,
167
:-(
failure_rate: if(total_attempts > 0, do: failed_count / total_attempts, else: 0)
168 }
169 end
170
171 @doc """
172 Get audit trail for a specific API key operation
173 """
174
:-(
def get_audit_trail(partner_id, operation_type \\ nil, limit \\ 100) do
175
:-(
query =
176 from(op in ApiKeyOperation,
177 where: op.partner_id == ^partner_id,
178 order_by: [desc: :inserted_at],
179 limit: ^limit
180 )
181
182
:-(
query =
183 if operation_type do
184
:-(
from(op in query, where: op.operation_type == ^operation_type)
185 else
186
:-(
query
187 end
188
189
:-(
Repo.all(query)
190 end
191
192 # Private helper functions
193
194 defp log_operation(changeset) do
195
:-(
case Repo.insert(changeset) do
196
:-(
{:ok, operation} -> operation
197
:-(
{:error, _} -> nil # Log silently, don't fail the main operation
198 end
199 end
200
201 defp calculate_success_rate(successful, total) when length(total) > 0 do
202
:-(
(length(successful) / length(total)) * 100
203 end
204
:-(
defp calculate_success_rate(_, _), do: 0
205
206
:-(
defp get_last_successful_auth([]), do: nil
207 defp get_last_successful_auth(auths) do
208 auths
209
:-(
|> Enum.max_by(&(&1.inserted_at))
210
:-(
|> Map.get(:inserted_at)
211 end
212
213 defp get_failure_reasons(failed_auths) do
214 failed_auths
215
:-(
|> Enum.map(&(&1.failure_reason))
216
:-(
|> Enum.filter(&(&1 != nil))
217
:-(
|> Enum.frequencies()
218 end
219
220 defp group_by_day(operations) do
221 operations
222 |> Enum.group_by(fn op ->
223
:-(
op.inserted_at |> DateTime.to_date()
224 end)
225
:-(
|> Enum.map(fn {date, ops} -> {date, length(ops)} end)
226
:-(
|> Enum.sort_by(fn {date, _} -> date end)
227 end
228 end
Line Hits Source