Skip to content

Commit 96941b4

Browse files
committed
feat: support auditing
1 parent bb2227e commit 96941b4

File tree

27 files changed

+2550
-93
lines changed

27 files changed

+2550
-93
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
auditLog:
2+
type: object
3+
description: Audit log entry
4+
properties:
5+
id:
6+
type: string
7+
description: Audit log ID
8+
user_id:
9+
type: string
10+
nullable: true
11+
description: User ID who performed the action
12+
username:
13+
type: string
14+
nullable: true
15+
description: Username for display
16+
resource_type:
17+
type: string
18+
enum: [collection, document, bot, chat, message, api_key, llm_provider, llm_provider_model, model_service_provider, user, config]
19+
nullable: true
20+
description: Type of resource
21+
resource_id:
22+
type: string
23+
nullable: true
24+
description: ID of the resource (extracted at query time)
25+
api_name:
26+
type: string
27+
description: API operation name
28+
http_method:
29+
type: string
30+
description: HTTP method (POST, PUT, DELETE)
31+
path:
32+
type: string
33+
description: API path
34+
status_code:
35+
type: integer
36+
nullable: true
37+
description: HTTP status code
38+
start_time:
39+
type: integer
40+
format: int64
41+
description: Request start time (milliseconds since epoch)
42+
end_time:
43+
type: integer
44+
format: int64
45+
nullable: true
46+
description: Request end time (milliseconds since epoch)
47+
duration_ms:
48+
type: integer
49+
nullable: true
50+
description: Request duration in milliseconds (calculated)
51+
request_data:
52+
type: string
53+
nullable: true
54+
description: Request data (JSON string)
55+
response_data:
56+
type: string
57+
nullable: true
58+
description: Response data (JSON string)
59+
error_message:
60+
type: string
61+
nullable: true
62+
description: Error message if failed
63+
ip_address:
64+
type: string
65+
nullable: true
66+
description: Client IP address
67+
user_agent:
68+
type: string
69+
nullable: true
70+
description: User agent string
71+
request_id:
72+
type: string
73+
description: Request ID for tracking
74+
created:
75+
type: string
76+
format: date-time
77+
description: Created timestamp
78+
79+
auditLogList:
80+
type: object
81+
description: List of audit logs
82+
properties:
83+
items:
84+
type: array
85+
description: Audit log entries
86+
items:
87+
$ref: '#/auditLog'
88+
89+

aperag/api/openapi.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ paths:
8888
/prompt-templates:
8989
$ref: './paths/prompt_templates.yaml#/promptTemplates'
9090

91+
# audit
92+
/audit-logs:
93+
$ref: './paths/audit.yaml#/audit_logs'
94+
/audit-logs/{audit_id}:
95+
$ref: './paths/audit.yaml#/audit_log_detail'
96+
9197
# users
9298
/invite:
9399
$ref: './paths/auth.yaml#/invite'

aperag/api/paths/audit.yaml

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
audit_logs:
2+
get:
3+
tags:
4+
- audit
5+
summary: List audit logs
6+
description: List audit logs with filtering options
7+
operationId: list_audit_logs
8+
parameters:
9+
- name: user_id
10+
in: query
11+
required: false
12+
schema:
13+
type: string
14+
description: Filter by user ID
15+
- name: username
16+
in: query
17+
required: false
18+
schema:
19+
type: string
20+
description: Filter by username
21+
- name: resource_type
22+
in: query
23+
required: false
24+
schema:
25+
type: string
26+
enum: [collection, document, bot, chat, message, api_key, llm_provider, llm_provider_model, model_service_provider, user, config]
27+
description: Filter by resource type
28+
- name: resource_id
29+
in: query
30+
required: false
31+
schema:
32+
type: string
33+
description: Filter by resource ID
34+
- name: api_name
35+
in: query
36+
required: false
37+
schema:
38+
type: string
39+
description: Filter by API name
40+
- name: http_method
41+
in: query
42+
required: false
43+
schema:
44+
type: string
45+
enum: [POST, PUT, DELETE]
46+
description: Filter by HTTP method
47+
- name: status_code
48+
in: query
49+
required: false
50+
schema:
51+
type: integer
52+
description: Filter by status code
53+
- name: start_date
54+
in: query
55+
required: false
56+
schema:
57+
type: string
58+
format: date-time
59+
description: Filter by start date
60+
- name: end_date
61+
in: query
62+
required: false
63+
schema:
64+
type: string
65+
format: date-time
66+
description: Filter by end date
67+
- name: limit
68+
in: query
69+
required: false
70+
schema:
71+
type: integer
72+
maximum: 5000
73+
default: 1000
74+
description: Maximum number of records
75+
responses:
76+
"200":
77+
description: Audit logs retrieved successfully
78+
content:
79+
application/json:
80+
schema:
81+
$ref: "../components/schemas/audit.yaml#/auditLogList"
82+
"403":
83+
description: Admin access required
84+
85+
audit_log_detail:
86+
get:
87+
tags:
88+
- audit
89+
summary: Get audit log detail
90+
description: Get a specific audit log by ID
91+
operationId: get_audit_log
92+
parameters:
93+
- name: audit_id
94+
in: path
95+
required: true
96+
schema:
97+
type: string
98+
description: Audit log ID
99+
responses:
100+
"200":
101+
description: Audit log retrieved successfully
102+
content:
103+
application/json:
104+
schema:
105+
$ref: "../components/schemas/audit.yaml#/auditLog"
106+
"403":
107+
description: Admin access required
108+
"404":
109+
description: Audit log not found
110+
111+

aperag/app.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
from aperag.exception_handlers import register_exception_handlers
1818
from aperag.llm.litellm_track import register_opik_llm_track
19+
from aperag.middleware.audit_middleware import AuditMiddleware
1920
from aperag.views.api_key import router as api_key_router
21+
from aperag.views.audit import router as audit_router
2022
from aperag.views.auth import router as auth_router
2123
from aperag.views.chat_completion import router as chat_completion_router
2224
from aperag.views.config import router as config_router
@@ -35,9 +37,13 @@
3537

3638
register_opik_llm_track()
3739

40+
# Add audit middleware - should be added before other middlewares/routers
41+
app.add_middleware(AuditMiddleware, enabled=True)
42+
3843
app.include_router(auth_router, prefix="/api/v1")
3944
app.include_router(main_router, prefix="/api/v1")
4045
app.include_router(api_key_router, prefix="/api/v1")
46+
app.include_router(audit_router, prefix="/api/v1") # Add audit router
4147
app.include_router(flow_router, prefix="/api/v1")
4248
app.include_router(llm_router, prefix="/api/v1")
4349
app.include_router(chat_completion_router, prefix="/v1")

aperag/db/models.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
Text,
3030
UniqueConstraint,
3131
select,
32+
Index,
33+
Float,
34+
func,
3235
)
3336
from sqlalchemy import Enum as SQLEnum
3437
from sqlalchemy.ext.declarative import declarative_base
@@ -746,3 +749,59 @@ def update_spec(self, desired_state: IndexDesiredState = None, created_by: str =
746749
self.created_by = created_by
747750
self.version += 1
748751
self.gmt_updated = utc_now()
752+
753+
754+
class AuditResource(str, Enum):
755+
"""Audit resource types"""
756+
COLLECTION = "collection"
757+
DOCUMENT = "document"
758+
BOT = "bot"
759+
CHAT = "chat"
760+
MESSAGE = "message"
761+
API_KEY = "api_key"
762+
LLM_PROVIDER = "llm_provider"
763+
LLM_PROVIDER_MODEL = "llm_provider_model"
764+
MODEL_SERVICE_PROVIDER = "model_service_provider"
765+
USER = "user"
766+
CONFIG = "config"
767+
768+
769+
class AuditLog(Base):
770+
"""Audit log model to track all system operations"""
771+
772+
__tablename__ = "audit_log"
773+
774+
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
775+
user_id = Column(String(36), nullable=True, comment="User ID")
776+
username = Column(String(255), nullable=True, comment="Username")
777+
resource_type = Column(Enum(AuditResource), nullable=True, comment="Resource type")
778+
resource_id = Column(String(255), nullable=True, comment="Resource ID (extracted at query time)")
779+
api_name = Column(String(255), nullable=False, comment="API operation name")
780+
http_method = Column(String(10), nullable=False, comment="HTTP method (POST, PUT, DELETE)")
781+
path = Column(String(512), nullable=False, comment="API path")
782+
status_code = Column(Integer, nullable=True, comment="HTTP status code")
783+
request_data = Column(Text, nullable=True, comment="Request data (JSON)")
784+
response_data = Column(Text, nullable=True, comment="Response data (JSON)")
785+
error_message = Column(Text, nullable=True, comment="Error message if failed")
786+
ip_address = Column(String(45), nullable=True, comment="Client IP address")
787+
user_agent = Column(String(500), nullable=True, comment="User agent string")
788+
request_id = Column(String(255), nullable=False, comment="Request ID for tracking")
789+
start_time = Column(BigInteger, nullable=False, comment="Request start time (milliseconds since epoch)")
790+
end_time = Column(BigInteger, nullable=True, comment="Request end time (milliseconds since epoch)")
791+
gmt_created = Column(DateTime(timezone=True), nullable=False, default=func.now(), comment="Created time")
792+
793+
# Index for better query performance
794+
__table_args__ = (
795+
Index("idx_audit_user_id", "user_id"),
796+
Index("idx_audit_resource_type", "resource_type"),
797+
Index("idx_audit_api_name", "api_name"),
798+
Index("idx_audit_http_method", "http_method"),
799+
Index("idx_audit_status_code", "status_code"),
800+
Index("idx_audit_gmt_created", "gmt_created"),
801+
Index("idx_audit_resource_id", "resource_id"),
802+
Index("idx_audit_request_id", "request_id"),
803+
Index("idx_audit_start_time", "start_time"),
804+
)
805+
806+
def __repr__(self):
807+
return f"<AuditLog(id={self.id}, user={self.username}, api={self.api_name}, method={self.http_method}, status={self.status_code})>"

0 commit comments

Comments
 (0)