wafaHell 1.0.0__py3-none-any.whl → 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
wafaHell/utils.py CHANGED
@@ -1,16 +1,16 @@
1
+ from .model import WafLog, Blocked, Whitelist, AdminUser, get_session
2
+ from .globals import waf_cache
1
3
  from datetime import datetime, timedelta, timezone
2
4
  import secrets
5
+ import socket
3
6
  import string
4
7
  import time
8
+ import tomllib
5
9
  from werkzeug.security import generate_password_hash
6
10
  from sqlalchemy.orm import Session
7
11
  from sqlalchemy import text, func, case
8
- from model import WafLog, Blocked
9
- from model import AdminUser
10
12
  from functools import wraps
11
13
  from flask import session, redirect, url_for, request
12
- from model import get_session
13
- from globals import waf_cache
14
14
  import geoip2.database
15
15
  import os
16
16
 
@@ -43,170 +43,197 @@ class Admin:
43
43
  def admin(fn):
44
44
  @wraps(fn)
45
45
  def wrapper(*args, **kwargs):
46
- print(f"DEBUG: Session logged_in status: {session.get('logged_in')}") # Adicione isso
47
46
  if not session.get("logged_in"):
48
- print("DEBUG: Redirecting to login...")
49
47
  return redirect(url_for("login", next=request.path))
50
48
  return fn(*args, **kwargs)
51
49
  return wrapper
52
50
 
53
51
  class Dashboard:
54
52
  def __init__(self):
55
- self.db_session = get_session()
56
53
  self.geo_db_path = os.path.join(os.path.dirname(__file__), 'GeoLite2-Country.mmdb')
57
54
 
58
55
  def dashboard_setup(self):
59
56
  json = {}
57
+
58
+ def get_waf_version():
59
+ base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
60
+ toml_path = os.path.join(base_path, "pyproject.toml")
61
+
62
+ try:
63
+ with open(toml_path, "rb") as f:
64
+ data = tomllib.load(f)
65
+ return data["project"]["version"]
66
+ except FileNotFoundError:
67
+ return "v.0.0.0"
68
+
60
69
  def get_server_info():
61
70
  server_time = datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z')
62
- node_id = "WAF-01"
71
+ node_id = socket.gethostname()
63
72
  avg_latency = waf_cache.get('latency_avg', default=0.0)
64
73
  system_status = "critical" if avg_latency > 500 else "degraded" if avg_latency > 200 else "healthy"
65
74
  return {
66
75
  "server_time": server_time,
67
76
  "node_id": node_id,
68
77
  "average_latency_ms": round(float(avg_latency), 2),
69
- "system_status": system_status
78
+ "system_status": system_status,
79
+ "version": get_waf_version()
70
80
  }
71
81
 
72
82
  def get_kpis():
73
83
  now = datetime.now(timezone.utc)
74
84
  last_24h = now - timedelta(hours=24)
75
85
  prev_24h = now - timedelta(hours=48)
76
-
77
- # --- TOTAIS DE HOJE ---
78
- total_today = self.db_session.query(func.count(WafLog.id)).filter(WafLog.timestamp >= last_24h).scalar() or 0
79
- blocked_today = self.db_session.query(func.count(WafLog.id)).filter(
80
- WafLog.timestamp >= last_24h,
81
- WafLog.attack_type != 'INFO'
82
- ).scalar() or 0
83
-
84
- # --- TOTAIS DE ONTEM (Para Tendência) ---
85
- total_yesterday = self.db_session.query(func.count(WafLog.id)).filter(
86
- WafLog.timestamp >= prev_24h,
87
- WafLog.timestamp < last_24h
88
- ).scalar() or 0
89
-
90
- blocked_yesterday = self.db_session.query(func.count(WafLog.id)).filter(
91
- WafLog.timestamp >= prev_24h,
92
- WafLog.timestamp < last_24h,
93
- WafLog.attack_type != 'INFO'
94
- ).scalar() or 0
95
-
96
- # --- CÁLCULO DE TENDÊNCIA (%) ---
97
- def calc_trend(current, previous):
98
- if previous == 0:
99
- return 100.0 if current > 0 else 0.0
100
- return round(((current - previous) / previous * 100), 1)
101
-
102
- trend_total = calc_trend(total_today, total_yesterday)
103
- trend_attacks = calc_trend(blocked_today, blocked_yesterday)
104
-
105
- # --- INFOS COMPLEMENTARES ---
106
- from globals import waf_cache
107
- # Latência e RPS vindos do Cache Global
108
- last_second_timestamp = int(time.time()) - 1
109
- rps_key = f"rps_{last_second_timestamp}"
110
-
111
- # 2. Lê o valor real do cache para esse segundo específico
112
- current_rps = waf_cache.get(rps_key, default=0)
113
-
114
- # 3. Lógica do Pico (Peak)
115
- # Aqui continuamos usando uma chave fixa 'rps_peak_hour' para guardar o recorde
116
- peak_rps = waf_cache.get('rps_peak_hour', default=0)
117
-
118
- if current_rps > peak_rps:
119
- peak_rps = current_rps
120
- # Atualiza o recorde no cache por 1 hora
121
- waf_cache.set('rps_peak_hour', peak_rps, expire=3600)
86
+ session = get_session()
122
87
 
123
- # Dados da Blacklist
124
- total_blacklist = self.db_session.query(func.count(Blocked.id)).scalar() or 0
125
- # Como blocked_at é String formatada no seu modelo, comparamos com o horário
126
- added_today = self.db_session.query(func.count(Blocked.id)).filter(
127
- Blocked.blocked_until >= now # Um IP "adicionado hoje" é tecnicamente um ainda bloqueado
128
- ).scalar() or 0
129
-
130
- return {
131
- "total_requests_24h": {
132
- "value": total_today,
133
- "trend_percent": trend_total
134
- },
135
- "attacks_mitigated_24h": {
136
- "value": blocked_today,
137
- "trend_percent": trend_attacks # Adicionado aqui
138
- },
139
- "throughput": {
140
- "current_req_per_sec": current_rps,
141
- "peak_last_hour": peak_rps
142
- },
143
- "blacklist_count": {
144
- "total_ips": total_blacklist,
145
- "added_today": added_today
88
+ try:
89
+ # --- TOTAIS DE HOJE ---
90
+ total_today = session.query(func.count(WafLog.id)).filter(WafLog.timestamp >= last_24h).scalar() or 0
91
+ blocked_today = session.query(func.count(WafLog.id)).filter(
92
+ WafLog.timestamp >= last_24h,
93
+ WafLog.attack_type != 'INFO'
94
+ ).scalar() or 0
95
+
96
+ # --- TOTAIS DE ONTEM (Para Tendência) ---
97
+ total_yesterday = session.query(func.count(WafLog.id)).filter(
98
+ WafLog.timestamp >= prev_24h,
99
+ WafLog.timestamp < last_24h
100
+ ).scalar() or 0
101
+
102
+ blocked_yesterday = session.query(func.count(WafLog.id)).filter(
103
+ WafLog.timestamp >= prev_24h,
104
+ WafLog.timestamp < last_24h,
105
+ WafLog.attack_type != 'INFO'
106
+ ).scalar() or 0
107
+
108
+ # --- CÁLCULO DE TENDÊNCIA (%) ---
109
+ def calc_trend(current, previous):
110
+ if previous == 0:
111
+ return 100.0 if current > 0 else 0.0
112
+ return round(((current - previous) / previous * 100), 1)
113
+
114
+ trend_total = calc_trend(total_today, total_yesterday)
115
+ trend_attacks = calc_trend(blocked_today, blocked_yesterday)
116
+
117
+ # --- INFOS COMPLEMENTARES ---
118
+ from globals import waf_cache
119
+ # Latência e RPS vindos do Cache Global
120
+ last_second_timestamp = int(time.time()) - 1
121
+ rps_key = f"rps_{last_second_timestamp}"
122
+
123
+ # 2. Lê o valor real do cache para esse segundo específico
124
+ current_rps = waf_cache.get(rps_key, default=0)
125
+
126
+ # 3. Lógica do Pico (Peak)
127
+ # Aqui continuamos usando uma chave fixa 'rps_peak_hour' para guardar o recorde
128
+ peak_rps = waf_cache.get('rps_peak_hour', default=0)
129
+
130
+ if current_rps > peak_rps:
131
+ peak_rps = current_rps
132
+ # Atualiza o recorde no cache por 1 hora
133
+ waf_cache.set('rps_peak_hour', peak_rps, expire=3600)
134
+
135
+ # Dados da Blacklist
136
+ total_blacklist = session.query(func.count(Blocked.id)).scalar() or 0
137
+ # Como blocked_at é String formatada no seu modelo, comparamos com o horário
138
+ added_today = session.query(func.count(Blocked.id)).filter(
139
+ Blocked.blocked_until >= now # Um IP "adicionado hoje" é tecnicamente um ainda bloqueado
140
+ ).scalar() or 0
141
+
142
+ return {
143
+ "total_requests_24h": {
144
+ "value": total_today,
145
+ "trend_percent": trend_total
146
+ },
147
+ "attacks_mitigated_24h": {
148
+ "value": blocked_today,
149
+ "trend_percent": trend_attacks # Adicionado aqui
150
+ },
151
+ "throughput": {
152
+ "current_req_per_sec": current_rps,
153
+ "peak_last_hour": peak_rps
154
+ },
155
+ "blacklist_count": {
156
+ "total_ips": total_blacklist,
157
+ "added_today": added_today
158
+ }
146
159
  }
147
- }
148
-
160
+
161
+ except Exception as e:
162
+ print(f"Erro no Dashboard: {e}")
163
+ finally:
164
+ session.close()
165
+
149
166
  def get_traffic_chart():
150
167
  now = datetime.now(timezone.utc)
151
168
  start_time = now - timedelta(minutes=40)
152
-
153
- # Query para agrupar por minuto
154
- # No SQLite usamos strftime, no Postgres/MySQL seria date_format ou similar
155
- query = self.db_session.query(
156
- func.strftime('%H:%M', WafLog.timestamp).label('minute'),
157
- func.count(WafLog.id).label('total'),
158
- func.sum(case({WafLog.attack_type == 'INFO': 1}, else_=0)).label('legit'),
159
- func.sum(case({WafLog.attack_type != 'INFO': 1}, else_=0)).label('attacks')
160
- ).filter(WafLog.timestamp >= start_time)\
161
- .group_by('minute')\
162
- .order_by('minute').all()
163
-
164
- labels = []
165
- series_legit = []
166
- series_attacks = []
167
-
168
- # Preenche os arrays para o gráfico
169
- for row in query:
170
- labels.append(row.minute)
171
- series_legit.append(row.legit or 0)
172
- series_attacks.append(row.attacks or 0)
173
-
174
- # Caso não existam dados nos últimos 40 min, retorna arrays vazios para não quebrar o front
175
- return {
176
- "labels": labels,
177
- "series_legit": series_legit,
178
- "series_attacks": series_attacks
179
- }
169
+ session = get_session()
170
+ try:
171
+ # Query para agrupar por minuto
172
+ # No SQLite usamos strftime, no Postgres/MySQL seria date_format ou similar
173
+ query = session.query(
174
+ func.strftime('%H:%M', WafLog.timestamp).label('minute'),
175
+ func.count(WafLog.id).label('total'),
176
+ func.sum(case({WafLog.attack_type == 'INFO': 1}, else_=0)).label('legit'),
177
+ func.sum(case({WafLog.attack_type != 'INFO': 1}, else_=0)).label('attacks')
178
+ ).filter(WafLog.timestamp >= start_time)\
179
+ .group_by('minute')\
180
+ .order_by('minute').all()
181
+
182
+ labels = []
183
+ series_legit = []
184
+ series_attacks = []
185
+
186
+ # Preenche os arrays para o gráfico
187
+ for row in query:
188
+ labels.append(row.minute)
189
+ series_legit.append(row.legit or 0)
190
+ series_attacks.append(row.attacks or 0)
191
+
192
+ # Caso não existam dados nos últimos 40 min, retorna arrays vazios para não quebrar o front
193
+ return {
194
+ "labels": labels,
195
+ "series_legit": series_legit,
196
+ "series_attacks": series_attacks
197
+ }
198
+ except Exception as e:
199
+ print(f"Erro no Dashboard: {e}")
200
+ finally:
201
+ session.close()
180
202
 
181
203
  def get_distribution_vectors():
182
204
  now = datetime.now(timezone.utc)
183
205
  last_24h = now - timedelta(hours=24)
184
-
185
- # 1. Buscamos a contagem agrupada por tipo de ataque
186
- # Filtramos para não incluir tráfego legítimo (INFO)
187
- query = self.db_session.query(
188
- WafLog.attack_type,
189
- func.count(WafLog.id).label('count')
190
- ).filter(
191
- WafLog.timestamp >= last_24h,
192
- WafLog.attack_type != 'INFO'
193
- ).group_by(WafLog.attack_type).all()
194
-
195
- # 2. Calculamos o total de ataques para obter a porcentagem
196
- total_attacks = sum(row.count for row in query)
197
-
198
- distribution = []
199
-
200
- for row in query:
201
- percentage = round((row.count / total_attacks * 100), 1) if total_attacks > 0 else 0
202
- distribution.append({
203
- "label": row.attack_type,
204
- "count": row.count,
205
- "percentage": percentage
206
- })
207
-
208
- # Caso não haja ataques, retornamos uma lista vazia ou um placeholder
209
- return distribution
206
+ session = get_session()
207
+ try:
208
+ # 1. Buscamos a contagem agrupada por tipo de ataque
209
+ # Filtramos para não incluir tráfego legítimo (INFO)
210
+ query = session.query(
211
+ WafLog.attack_type,
212
+ func.count(WafLog.id).label('count')
213
+ ).filter(
214
+ WafLog.timestamp >= last_24h,
215
+ WafLog.attack_type != 'INFO'
216
+ ).group_by(WafLog.attack_type).all()
217
+
218
+ # 2. Calculamos o total de ataques para obter a porcentagem
219
+ total_attacks = sum(row.count for row in query)
220
+
221
+ distribution = []
222
+
223
+ for row in query:
224
+ percentage = round((row.count / total_attacks * 100), 1) if total_attacks > 0 else 0
225
+ distribution.append({
226
+ "label": row.attack_type,
227
+ "count": row.count,
228
+ "percentage": percentage
229
+ })
230
+
231
+ # Caso não haja ataques, retornamos uma lista vazia ou um placeholder
232
+ return distribution
233
+ except Exception as e:
234
+ print(f"Erro no Dashboard: {e}")
235
+ finally:
236
+ session.close()
210
237
 
211
238
  def get_top_geo():
212
239
  def resolve_ip(ip):
@@ -224,75 +251,85 @@ class Dashboard:
224
251
 
225
252
  now = datetime.now(timezone.utc)
226
253
  last_24h = now - timedelta(hours=24)
227
-
254
+ session = get_session()
228
255
  # 1. Busca todos os ataques agrupados por IP
229
- query = self.db_session.query(
230
- WafLog.ip,
231
- func.count(WafLog.id).label('count')
232
- ).filter(
233
- WafLog.timestamp >= last_24h,
234
- WafLog.attack_type != 'INFO'
235
- ).group_by(WafLog.ip).all()
236
-
237
- geo_stats = {}
238
- total_attacks = 0
239
-
240
- # 2. Processa cada IP real usando o GeoIP2
241
- for row in query:
242
- code, name = resolve_ip(row.ip)
243
-
244
- if code not in geo_stats:
245
- geo_stats[code] = {"name": name, "count": 0}
246
-
247
- geo_stats[code]["count"] += row.count
248
- total_attacks += row.count
249
-
250
- # 3. Formata o Top 5
251
- top_geo = []
252
- sorted_geo = sorted(geo_stats.items(), key=lambda x: x[1]['count'], reverse=True)[:5]
253
-
254
- for code, data in sorted_geo:
255
- percentage = round((data["count"] / total_attacks * 100), 1) if total_attacks > 0 else 0
256
- top_geo.append({
257
- "country_code": code,
258
- "country_name": data["name"],
259
- "count": data["count"],
260
- "percentage": percentage
261
- })
262
-
263
- return top_geo
256
+ try:
257
+ query = session.query(
258
+ WafLog.ip,
259
+ func.count(WafLog.id).label('count')
260
+ ).filter(
261
+ WafLog.timestamp >= last_24h,
262
+ WafLog.attack_type != 'INFO'
263
+ ).group_by(WafLog.ip).all()
264
+
265
+ geo_stats = {}
266
+ total_attacks = 0
267
+
268
+ # 2. Processa cada IP real usando o GeoIP2
269
+ for row in query:
270
+ code, name = resolve_ip(row.ip)
271
+
272
+ if code not in geo_stats:
273
+ geo_stats[code] = {"name": name, "count": 0}
274
+
275
+ geo_stats[code]["count"] += row.count
276
+ total_attacks += row.count
277
+
278
+ # 3. Formata o Top 5
279
+ top_geo = []
280
+ sorted_geo = sorted(geo_stats.items(), key=lambda x: x[1]['count'], reverse=True)[:5]
281
+
282
+ for code, data in sorted_geo:
283
+ percentage = round((data["count"] / total_attacks * 100), 1) if total_attacks > 0 else 0
284
+ top_geo.append({
285
+ "country_code": code,
286
+ "country_name": data["name"],
287
+ "count": data["count"],
288
+ "percentage": percentage
289
+ })
290
+
291
+ return top_geo
292
+ except Exception as e:
293
+ print(f"Erro no Dashboard: {e}")
294
+ finally:
295
+ session.close()
264
296
 
265
297
  def get_top_offenders():
266
298
  now = datetime.now(timezone.utc)
267
299
  last_24h = now - timedelta(hours=24)
268
-
300
+ session = get_session()
269
301
  # 1. Agrupamos por IP e contamos os hits e os tipos de ataques diferentes
270
302
  # Ignoramos tráfego INFO
271
- query = self.db_session.query(
272
- WafLog.ip,
273
- func.count(WafLog.id).label('hits_count'),
274
- func.count(func.distinct(WafLog.attack_type)).label('unique_vectors')
275
- ).filter(
276
- WafLog.timestamp >= last_24h,
277
- WafLog.attack_type != 'INFO'
278
- ).group_by(WafLog.ip).order_by(text('hits_count DESC')).limit(5).all()
279
-
280
- offenders = []
281
- for row in query:
282
- # 2. Lógica de Risk Score (0 a 100)
283
- # Baseada em volume e diversidade de ataques
284
- # Ex: Cada vetor único vale 20 pontos + 1 ponto para cada 50 hits (até o teto de 100)
285
- vector_points = row.unique_vectors * 20
286
- hit_points = row.hits_count // 50
287
- risk_score = min(100, vector_points + hit_points)
288
-
289
- offenders.append({
290
- "ip": row.ip,
291
- "risk_score": int(risk_score),
292
- "hits_count": row.hits_count
293
- })
294
-
295
- return offenders
303
+ try:
304
+ query = session.query(
305
+ WafLog.ip,
306
+ func.count(WafLog.id).label('hits_count'),
307
+ func.count(func.distinct(WafLog.attack_type)).label('unique_vectors')
308
+ ).filter(
309
+ WafLog.timestamp >= last_24h,
310
+ WafLog.attack_type != 'INFO'
311
+ ).group_by(WafLog.ip).order_by(text('hits_count DESC')).limit(5).all()
312
+
313
+ offenders = []
314
+ for row in query:
315
+ # 2. Lógica de Risk Score (0 a 100)
316
+ # Baseada em volume e diversidade de ataques
317
+ # Ex: Cada vetor único vale 20 pontos + 1 ponto para cada 50 hits (até o teto de 100)
318
+ vector_points = row.unique_vectors * 20
319
+ hit_points = row.hits_count // 50
320
+ risk_score = min(100, vector_points + hit_points)
321
+
322
+ offenders.append({
323
+ "ip": row.ip,
324
+ "risk_score": int(risk_score),
325
+ "hits_count": row.hits_count
326
+ })
327
+
328
+ return offenders
329
+ except Exception as e:
330
+ print(f"Erro no Dashboard: {e}")
331
+ finally:
332
+ session.close()
296
333
 
297
334
  try:
298
335
  json['meta'] = get_server_info()
@@ -307,6 +344,73 @@ class Dashboard:
307
344
  except Exception as e:
308
345
  print(f"Erro no Dashboard: {e}")
309
346
  return {"error": str(e)}
310
-
311
- finally:
312
- self.db_session.close()
347
+
348
+ def seed_default_whitelist():
349
+ """
350
+ Popula a Whitelist com IPs essenciais (Localhost, DNS públicos, etc)
351
+ Executar na inicialização do app.
352
+ """
353
+ # Lista de IPs Padrão (Adicione aqui IPs unitários que confia)
354
+ DEFAULT_IPS = [
355
+ # --- Localhost / Loopback (Essencial) ---
356
+ "127.0.0.1",
357
+ "::1",
358
+
359
+ # Redes Privadas (As gigantes)
360
+ "10.0.0.0/8", # 16 milhões de IPs
361
+ "172.16.0.0/12", # 1 milhão de IPs
362
+ "192.168.0.0/16", # 65 mil IPs
363
+
364
+ # --- DNS Públicos (Google) ---
365
+ "8.8.8.8",
366
+ "8.8.4.4",
367
+
368
+ # --- DNS Públicos (Cloudflare) ---
369
+ "1.1.1.1",
370
+ "1.0.0.1",
371
+
372
+ # --- DNS Públicos (OpenDNS) ---
373
+ "208.67.222.222",
374
+ "208.67.220.220",
375
+
376
+ # --- DNS Públicos (Quad9) ---
377
+ "9.9.9.9",
378
+ "149.112.112.112"
379
+ ]
380
+
381
+ session = get_session()
382
+ try:
383
+ # 1. Descobre o que já existe no banco para não duplicar
384
+ existing_query = session.query(Whitelist.ip).filter(Whitelist.ip.in_(DEFAULT_IPS)).all()
385
+ existing_ips = {row.ip for row in existing_query}
386
+
387
+ # 2. Filtra apenas os novos
388
+ ips_to_insert = set(DEFAULT_IPS) - existing_ips
389
+
390
+ if not ips_to_insert:
391
+ print(" * [WafaHell] Whitelist padrão já está atualizada.")
392
+ return
393
+
394
+ # 3. Prepara Bulk Insert e Cache
395
+ now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
396
+ bulk_data = []
397
+
398
+ for ip in ips_to_insert:
399
+ bulk_data.append({
400
+ "ip": ip,
401
+ "added_at": now_str
402
+ })
403
+ # Já coloca no cache para funcionar imediatamente
404
+ waf_cache.set(f"whitelist_{ip}", True, expire=3600)
405
+
406
+ # 4. Grava no Banco
407
+ session.bulk_insert_mappings(Whitelist, bulk_data)
408
+ session.commit()
409
+
410
+ print(f" * [WafaHell] Seed Whitelist: {len(ips_to_insert)} IPs padrão adicionados.")
411
+
412
+ except Exception as e:
413
+ session.rollback()
414
+ print(f"Erro ao semear whitelist: {e}")
415
+ finally:
416
+ session.close()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wafaHell
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: Middleware WAF to Flask
5
5
  Author-email: Yago Martins <yagomartins30@gmail.com>
6
6
  License: MIT License
@@ -0,0 +1,15 @@
1
+ wafaHell/__init__.py,sha256=bdafr3LRK7_frr-V6umEJZUqt8RMrOiLJy72DSQfs2o,60
2
+ wafaHell/app.py,sha256=oZCqS6rwvm3vU1pc56yYiNc_Mh4DOI52H00qnVovt-o,986
3
+ wafaHell/globals.py,sha256=keIe0YCNsb5si0yUhUjd6nlk1s0YgfH5G7lH5-05Zws,160
4
+ wafaHell/logger.py,sha256=_h4ceGM2UYnEQ5SY7nbYtR0rZprCOC0rN1IehtkkM_o,5734
5
+ wafaHell/middleware.py,sha256=DlSGA-1YHaHG-iQ2lBIWJIuzQnxkRITBPMSQogM8hdA,19625
6
+ wafaHell/mock.py,sha256=EgfSCKYV3rUTerKoEyFQPSf47wrOo-E0yqe7JpQUe3w,2105
7
+ wafaHell/model.py,sha256=o3wrIp8H17vlIB1tW28ApYedXtJLIGTbtrB7zKjzbtE,2919
8
+ wafaHell/panel.py,sha256=mq_vUg4LWLaFEWLvXpuDB3_QUw46WHhw3ruKb6hc_Sc,15926
9
+ wafaHell/rateLimiter.py,sha256=p4IDxha-ZrRPROFf8Fa1gRTbbUTQdbD9TgBTmyMR2hQ,664
10
+ wafaHell/utils.py,sha256=nQk1A4J1OeuvDnX5lj8pIE8IyUF5f5wbtXt0XSdsF7A,16810
11
+ wafahell-1.1.0.dist-info/licenses/LICENSE,sha256=6bv9v4HamenV3rqm3mhaGOecwGFrgxtVTW7JPfFDmeY,1086
12
+ wafahell-1.1.0.dist-info/METADATA,sha256=8fF9VBaal86TMs5EwmntQeKPA7wY9bNbo-9VPvEWBEw,2087
13
+ wafahell-1.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
14
+ wafahell-1.1.0.dist-info/top_level.txt,sha256=VGBo2g3pOeTH2qIXfZDJCSblJgijemMHUHmI0bBgrls,9
15
+ wafahell-1.1.0.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- wafaHell/__init__.py,sha256=bdafr3LRK7_frr-V6umEJZUqt8RMrOiLJy72DSQfs2o,60
2
- wafaHell/app.py,sha256=mXUCyOVrbU2Cdugh-UIDSQf-6zqvT2aazZtJQp_KKJI,980
3
- wafaHell/globals.py,sha256=keIe0YCNsb5si0yUhUjd6nlk1s0YgfH5G7lH5-05Zws,160
4
- wafaHell/logger.py,sha256=keT-Iw6g9w1dtrk0GhldxhgKtmNUVClgFGkSkAWXS5Y,5686
5
- wafaHell/middleware.py,sha256=cse81wj9ocUV11MPfrmvleD5nYq6asjDx6dqXToIcIs,14348
6
- wafaHell/mock.py,sha256=EgfSCKYV3rUTerKoEyFQPSf47wrOo-E0yqe7JpQUe3w,2105
7
- wafaHell/model.py,sha256=OB4sEucMSgO-DBLgLhqh0oBx5fZRug23MDnRc8vS5w4,2501
8
- wafaHell/panel.py,sha256=NpbvgXy5Regm4dQDoMapr-otW27TvC7Q-vdPrZDD-5k,7465
9
- wafaHell/rateLimiter.py,sha256=p4IDxha-ZrRPROFf8Fa1gRTbbUTQdbD9TgBTmyMR2hQ,664
10
- wafaHell/utils.py,sha256=v9NXo94AQiHjjCOgC38vwrlLoQOCZqp_qOjZVz00NEI,12750
11
- wafahell-1.0.0.dist-info/licenses/LICENSE,sha256=6bv9v4HamenV3rqm3mhaGOecwGFrgxtVTW7JPfFDmeY,1086
12
- wafahell-1.0.0.dist-info/METADATA,sha256=2EA_4vTwwoXimX_etFdZo_x_Z2_qijnbSjuIHfwE10Q,2087
13
- wafahell-1.0.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
14
- wafahell-1.0.0.dist-info/top_level.txt,sha256=VGBo2g3pOeTH2qIXfZDJCSblJgijemMHUHmI0bBgrls,9
15
- wafahell-1.0.0.dist-info/RECORD,,