app-platform 0.2.0__tar.gz
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.
- app_platform-0.2.0/PKG-INFO +164 -0
- app_platform-0.2.0/README.md +127 -0
- app_platform-0.2.0/app_platform/__init__.py +123 -0
- app_platform-0.2.0/app_platform/auth/__init__.py +21 -0
- app_platform-0.2.0/app_platform/auth/dependencies.py +28 -0
- app_platform-0.2.0/app_platform/auth/google.py +50 -0
- app_platform-0.2.0/app_platform/auth/protocols.py +50 -0
- app_platform-0.2.0/app_platform/auth/service.py +180 -0
- app_platform-0.2.0/app_platform/auth/stores.py +336 -0
- app_platform-0.2.0/app_platform/db/__init__.py +50 -0
- app_platform-0.2.0/app_platform/db/client_base.py +62 -0
- app_platform-0.2.0/app_platform/db/exceptions.py +257 -0
- app_platform-0.2.0/app_platform/db/migration.py +62 -0
- app_platform-0.2.0/app_platform/db/pool.py +99 -0
- app_platform-0.2.0/app_platform/db/session.py +67 -0
- app_platform-0.2.0/app_platform/gateway/__init__.py +6 -0
- app_platform-0.2.0/app_platform/gateway/models.py +27 -0
- app_platform-0.2.0/app_platform/gateway/proxy.py +287 -0
- app_platform-0.2.0/app_platform/gateway/session.py +108 -0
- app_platform-0.2.0/app_platform/logging/__init__.py +45 -0
- app_platform-0.2.0/app_platform/logging/core.py +665 -0
- app_platform-0.2.0/app_platform/logging/decorators.py +180 -0
- app_platform-0.2.0/app_platform/middleware/__init__.py +63 -0
- app_platform-0.2.0/app_platform/middleware/cors.py +28 -0
- app_platform-0.2.0/app_platform/middleware/error_handlers.py +126 -0
- app_platform-0.2.0/app_platform/middleware/rate_limiter.py +114 -0
- app_platform-0.2.0/app_platform/middleware/sessions.py +26 -0
- app_platform-0.2.0/app_platform/py.typed +1 -0
- app_platform-0.2.0/app_platform/pyproject.toml +49 -0
- app_platform-0.2.0/pyproject.toml +49 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: app-platform
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Generic web app infrastructure: PostgreSQL pooling, structured logging, rate limiting, FastAPI middleware, auth service, gateway proxy
|
|
5
|
+
Project-URL: Homepage, https://github.com/henrysouchien/app-platform
|
|
6
|
+
Project-URL: Repository, https://github.com/henrysouchien/app-platform
|
|
7
|
+
Author: Henry Chien
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Framework :: FastAPI
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Requires-Dist: psycopg2-binary>=2.9
|
|
17
|
+
Provides-Extra: all
|
|
18
|
+
Requires-Dist: fastapi>=0.100; extra == 'all'
|
|
19
|
+
Requires-Dist: google-auth>=2.0; extra == 'all'
|
|
20
|
+
Requires-Dist: httpx>=0.24; extra == 'all'
|
|
21
|
+
Requires-Dist: itsdangerous>=2.0; extra == 'all'
|
|
22
|
+
Requires-Dist: requests>=2.20; extra == 'all'
|
|
23
|
+
Requires-Dist: slowapi>=0.1.9; extra == 'all'
|
|
24
|
+
Requires-Dist: starlette; extra == 'all'
|
|
25
|
+
Provides-Extra: auth-google
|
|
26
|
+
Requires-Dist: google-auth>=2.0; extra == 'auth-google'
|
|
27
|
+
Requires-Dist: requests>=2.20; extra == 'auth-google'
|
|
28
|
+
Provides-Extra: fastapi
|
|
29
|
+
Requires-Dist: fastapi>=0.100; extra == 'fastapi'
|
|
30
|
+
Requires-Dist: itsdangerous>=2.0; extra == 'fastapi'
|
|
31
|
+
Requires-Dist: slowapi>=0.1.9; extra == 'fastapi'
|
|
32
|
+
Requires-Dist: starlette; extra == 'fastapi'
|
|
33
|
+
Provides-Extra: gateway
|
|
34
|
+
Requires-Dist: fastapi>=0.100; extra == 'gateway'
|
|
35
|
+
Requires-Dist: httpx>=0.24; extra == 'gateway'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# app-platform
|
|
39
|
+
|
|
40
|
+
Generic web app infrastructure for PostgreSQL pooling, structured logging, auth, middleware, and gateway proxying.
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install app-platform
|
|
46
|
+
pip install "app-platform[all]"
|
|
47
|
+
pip install "app-platform[fastapi]"
|
|
48
|
+
pip install "app-platform[auth-google]"
|
|
49
|
+
pip install "app-platform[gateway]"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Included subpackages
|
|
53
|
+
|
|
54
|
+
| Subpackage | Provides |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| `db` | PostgreSQL connection pooling, pooled session helpers, migrations, and database exception utilities |
|
|
57
|
+
| `logging` | Structured file and JSONL logging, context helpers, logger access, and decorators for timing/error instrumentation |
|
|
58
|
+
| `middleware` | FastAPI middleware configuration for rate limiting, sessions, CORS, and validation/error handling |
|
|
59
|
+
| `auth` | Pluggable auth service base with in-memory and PostgreSQL-backed user/session stores |
|
|
60
|
+
| `gateway` | FastAPI router factory for proxying chat and tool approval requests to an upstream gateway |
|
|
61
|
+
|
|
62
|
+
## Quick usage
|
|
63
|
+
|
|
64
|
+
### Database
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import os
|
|
68
|
+
|
|
69
|
+
from app_platform.db import PoolManager, get_db_session
|
|
70
|
+
|
|
71
|
+
os.environ["DATABASE_URL"] = "postgresql://postgres:postgres@localhost:5432/app"
|
|
72
|
+
|
|
73
|
+
pool_manager = PoolManager(min_connections=2, max_connections=10)
|
|
74
|
+
pool_manager.get_pool()
|
|
75
|
+
|
|
76
|
+
with get_db_session() as conn:
|
|
77
|
+
with conn.cursor() as cursor:
|
|
78
|
+
cursor.execute("SELECT 1 AS ok")
|
|
79
|
+
print(cursor.fetchone())
|
|
80
|
+
|
|
81
|
+
pool_manager.close()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Logging
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from app_platform.logging import (
|
|
88
|
+
LoggingManager,
|
|
89
|
+
configure_logging,
|
|
90
|
+
log_errors,
|
|
91
|
+
log_operation,
|
|
92
|
+
log_timing,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
logging_manager: LoggingManager = configure_logging(app_name="orders", log_dir="./logs")
|
|
96
|
+
logger = logging_manager.get_logger("service")
|
|
97
|
+
|
|
98
|
+
@log_errors()
|
|
99
|
+
@log_timing(threshold_s=0.25)
|
|
100
|
+
@log_operation("create_order")
|
|
101
|
+
def create_order(order_id: str) -> None:
|
|
102
|
+
logger.info("creating order %s", order_id)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
create_order("ord_123")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Auth
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from app_platform.auth import AuthServiceBase, InMemorySessionStore, InMemoryUserStore
|
|
112
|
+
|
|
113
|
+
users = {}
|
|
114
|
+
sessions = {}
|
|
115
|
+
|
|
116
|
+
auth = AuthServiceBase(
|
|
117
|
+
session_store=InMemorySessionStore(users_dict=users, sessions_dict=sessions),
|
|
118
|
+
user_store=InMemoryUserStore(users_dict=users),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
session_id = auth.create_user_session(
|
|
122
|
+
{
|
|
123
|
+
"google_user_id": "user-123",
|
|
124
|
+
"email": "user@example.com",
|
|
125
|
+
"name": "Example User",
|
|
126
|
+
}
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
print(auth.get_user_by_session(session_id))
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Gateway
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from fastapi import FastAPI
|
|
136
|
+
|
|
137
|
+
from app_platform.gateway import GatewayConfig, create_gateway_router
|
|
138
|
+
|
|
139
|
+
app = FastAPI()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def get_current_user():
|
|
143
|
+
return {"user_id": "user-123", "email": "user@example.com"}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
gateway_router = create_gateway_router(
|
|
147
|
+
GatewayConfig(
|
|
148
|
+
gateway_url="https://gateway.example.com",
|
|
149
|
+
api_key="gateway-api-key",
|
|
150
|
+
channel="web",
|
|
151
|
+
),
|
|
152
|
+
get_current_user=get_current_user,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
app.include_router(gateway_router, prefix="/gateway")
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Requirements
|
|
159
|
+
|
|
160
|
+
Python 3.11+
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
MIT
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# app-platform
|
|
2
|
+
|
|
3
|
+
Generic web app infrastructure for PostgreSQL pooling, structured logging, auth, middleware, and gateway proxying.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install app-platform
|
|
9
|
+
pip install "app-platform[all]"
|
|
10
|
+
pip install "app-platform[fastapi]"
|
|
11
|
+
pip install "app-platform[auth-google]"
|
|
12
|
+
pip install "app-platform[gateway]"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Included subpackages
|
|
16
|
+
|
|
17
|
+
| Subpackage | Provides |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| `db` | PostgreSQL connection pooling, pooled session helpers, migrations, and database exception utilities |
|
|
20
|
+
| `logging` | Structured file and JSONL logging, context helpers, logger access, and decorators for timing/error instrumentation |
|
|
21
|
+
| `middleware` | FastAPI middleware configuration for rate limiting, sessions, CORS, and validation/error handling |
|
|
22
|
+
| `auth` | Pluggable auth service base with in-memory and PostgreSQL-backed user/session stores |
|
|
23
|
+
| `gateway` | FastAPI router factory for proxying chat and tool approval requests to an upstream gateway |
|
|
24
|
+
|
|
25
|
+
## Quick usage
|
|
26
|
+
|
|
27
|
+
### Database
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import os
|
|
31
|
+
|
|
32
|
+
from app_platform.db import PoolManager, get_db_session
|
|
33
|
+
|
|
34
|
+
os.environ["DATABASE_URL"] = "postgresql://postgres:postgres@localhost:5432/app"
|
|
35
|
+
|
|
36
|
+
pool_manager = PoolManager(min_connections=2, max_connections=10)
|
|
37
|
+
pool_manager.get_pool()
|
|
38
|
+
|
|
39
|
+
with get_db_session() as conn:
|
|
40
|
+
with conn.cursor() as cursor:
|
|
41
|
+
cursor.execute("SELECT 1 AS ok")
|
|
42
|
+
print(cursor.fetchone())
|
|
43
|
+
|
|
44
|
+
pool_manager.close()
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Logging
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from app_platform.logging import (
|
|
51
|
+
LoggingManager,
|
|
52
|
+
configure_logging,
|
|
53
|
+
log_errors,
|
|
54
|
+
log_operation,
|
|
55
|
+
log_timing,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
logging_manager: LoggingManager = configure_logging(app_name="orders", log_dir="./logs")
|
|
59
|
+
logger = logging_manager.get_logger("service")
|
|
60
|
+
|
|
61
|
+
@log_errors()
|
|
62
|
+
@log_timing(threshold_s=0.25)
|
|
63
|
+
@log_operation("create_order")
|
|
64
|
+
def create_order(order_id: str) -> None:
|
|
65
|
+
logger.info("creating order %s", order_id)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
create_order("ord_123")
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Auth
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from app_platform.auth import AuthServiceBase, InMemorySessionStore, InMemoryUserStore
|
|
75
|
+
|
|
76
|
+
users = {}
|
|
77
|
+
sessions = {}
|
|
78
|
+
|
|
79
|
+
auth = AuthServiceBase(
|
|
80
|
+
session_store=InMemorySessionStore(users_dict=users, sessions_dict=sessions),
|
|
81
|
+
user_store=InMemoryUserStore(users_dict=users),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
session_id = auth.create_user_session(
|
|
85
|
+
{
|
|
86
|
+
"google_user_id": "user-123",
|
|
87
|
+
"email": "user@example.com",
|
|
88
|
+
"name": "Example User",
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
print(auth.get_user_by_session(session_id))
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Gateway
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from fastapi import FastAPI
|
|
99
|
+
|
|
100
|
+
from app_platform.gateway import GatewayConfig, create_gateway_router
|
|
101
|
+
|
|
102
|
+
app = FastAPI()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def get_current_user():
|
|
106
|
+
return {"user_id": "user-123", "email": "user@example.com"}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
gateway_router = create_gateway_router(
|
|
110
|
+
GatewayConfig(
|
|
111
|
+
gateway_url="https://gateway.example.com",
|
|
112
|
+
api_key="gateway-api-key",
|
|
113
|
+
channel="web",
|
|
114
|
+
),
|
|
115
|
+
get_current_user=get_current_user,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
app.include_router(gateway_router, prefix="/gateway")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Requirements
|
|
122
|
+
|
|
123
|
+
Python 3.11+
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Public app_platform exports for the extracted platform package."""
|
|
2
|
+
|
|
3
|
+
from .db import (
|
|
4
|
+
AuthenticationError,
|
|
5
|
+
ConnectionError,
|
|
6
|
+
DataConsistencyError,
|
|
7
|
+
DatabaseClientBase,
|
|
8
|
+
DatabaseError,
|
|
9
|
+
DatabasePermissionError,
|
|
10
|
+
MigrationError,
|
|
11
|
+
NotFoundError,
|
|
12
|
+
PoolExhaustionError,
|
|
13
|
+
PoolManager,
|
|
14
|
+
SchemaError,
|
|
15
|
+
SessionManager,
|
|
16
|
+
SessionNotFoundError,
|
|
17
|
+
TimeoutError,
|
|
18
|
+
TransactionError,
|
|
19
|
+
ValidationError,
|
|
20
|
+
get_db_session,
|
|
21
|
+
get_pool,
|
|
22
|
+
handle_database_error,
|
|
23
|
+
is_recoverable_error,
|
|
24
|
+
log_database_error,
|
|
25
|
+
run_migration,
|
|
26
|
+
run_migrations_dir,
|
|
27
|
+
)
|
|
28
|
+
from .logging import (
|
|
29
|
+
APP_LOG_FORMAT,
|
|
30
|
+
DEDUP_WINDOW_S,
|
|
31
|
+
JSON_LOG_FORMAT,
|
|
32
|
+
MAX_DEDUP_KEYS,
|
|
33
|
+
SLOW_OPERATION_THRESHOLD,
|
|
34
|
+
VERY_SLOW_OPERATION_THRESHOLD,
|
|
35
|
+
LoggingManager,
|
|
36
|
+
clear_log_context,
|
|
37
|
+
configure_logging,
|
|
38
|
+
get_logger,
|
|
39
|
+
get_logging_manager,
|
|
40
|
+
log_alert,
|
|
41
|
+
log_error,
|
|
42
|
+
log_errors,
|
|
43
|
+
log_event,
|
|
44
|
+
log_operation,
|
|
45
|
+
log_service_status,
|
|
46
|
+
log_slow_operation,
|
|
47
|
+
log_timing,
|
|
48
|
+
set_log_context,
|
|
49
|
+
)
|
|
50
|
+
from .middleware import (
|
|
51
|
+
ApiKeyRegistry,
|
|
52
|
+
DEFAULT_HEADERS,
|
|
53
|
+
DEFAULT_METHODS,
|
|
54
|
+
DEFAULT_SESSION_SECRET,
|
|
55
|
+
MiddlewareConfig,
|
|
56
|
+
RateLimitConfig,
|
|
57
|
+
add_rate_limit_handler,
|
|
58
|
+
add_validation_error_handler,
|
|
59
|
+
configure_cors,
|
|
60
|
+
configure_middleware,
|
|
61
|
+
configure_sessions,
|
|
62
|
+
create_limiter,
|
|
63
|
+
resolve_session_secret,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
__all__ = [
|
|
67
|
+
"APP_LOG_FORMAT",
|
|
68
|
+
"ApiKeyRegistry",
|
|
69
|
+
"AuthenticationError",
|
|
70
|
+
"ConnectionError",
|
|
71
|
+
"DEDUP_WINDOW_S",
|
|
72
|
+
"DEFAULT_HEADERS",
|
|
73
|
+
"DEFAULT_METHODS",
|
|
74
|
+
"DEFAULT_SESSION_SECRET",
|
|
75
|
+
"DataConsistencyError",
|
|
76
|
+
"DatabaseClientBase",
|
|
77
|
+
"DatabaseError",
|
|
78
|
+
"DatabasePermissionError",
|
|
79
|
+
"JSON_LOG_FORMAT",
|
|
80
|
+
"LoggingManager",
|
|
81
|
+
"MAX_DEDUP_KEYS",
|
|
82
|
+
"MigrationError",
|
|
83
|
+
"MiddlewareConfig",
|
|
84
|
+
"NotFoundError",
|
|
85
|
+
"PoolExhaustionError",
|
|
86
|
+
"PoolManager",
|
|
87
|
+
"RateLimitConfig",
|
|
88
|
+
"SLOW_OPERATION_THRESHOLD",
|
|
89
|
+
"SchemaError",
|
|
90
|
+
"SessionManager",
|
|
91
|
+
"SessionNotFoundError",
|
|
92
|
+
"TimeoutError",
|
|
93
|
+
"TransactionError",
|
|
94
|
+
"VERY_SLOW_OPERATION_THRESHOLD",
|
|
95
|
+
"ValidationError",
|
|
96
|
+
"clear_log_context",
|
|
97
|
+
"configure_logging",
|
|
98
|
+
"configure_cors",
|
|
99
|
+
"configure_middleware",
|
|
100
|
+
"configure_sessions",
|
|
101
|
+
"get_db_session",
|
|
102
|
+
"get_logger",
|
|
103
|
+
"get_logging_manager",
|
|
104
|
+
"get_pool",
|
|
105
|
+
"handle_database_error",
|
|
106
|
+
"is_recoverable_error",
|
|
107
|
+
"create_limiter",
|
|
108
|
+
"log_alert",
|
|
109
|
+
"log_error",
|
|
110
|
+
"log_database_error",
|
|
111
|
+
"log_errors",
|
|
112
|
+
"log_event",
|
|
113
|
+
"log_operation",
|
|
114
|
+
"log_service_status",
|
|
115
|
+
"log_slow_operation",
|
|
116
|
+
"log_timing",
|
|
117
|
+
"add_rate_limit_handler",
|
|
118
|
+
"add_validation_error_handler",
|
|
119
|
+
"resolve_session_secret",
|
|
120
|
+
"run_migration",
|
|
121
|
+
"run_migrations_dir",
|
|
122
|
+
"set_log_context",
|
|
123
|
+
]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Core exports for app_platform.auth."""
|
|
2
|
+
|
|
3
|
+
from .protocols import SessionStore, TokenVerifier, UserStore
|
|
4
|
+
from .service import AuthServiceBase
|
|
5
|
+
from .stores import (
|
|
6
|
+
InMemorySessionStore,
|
|
7
|
+
InMemoryUserStore,
|
|
8
|
+
PostgresSessionStore,
|
|
9
|
+
PostgresUserStore,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"AuthServiceBase",
|
|
14
|
+
"InMemorySessionStore",
|
|
15
|
+
"InMemoryUserStore",
|
|
16
|
+
"PostgresSessionStore",
|
|
17
|
+
"PostgresUserStore",
|
|
18
|
+
"SessionStore",
|
|
19
|
+
"TokenVerifier",
|
|
20
|
+
"UserStore",
|
|
21
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""FastAPI dependency helpers for app_platform.auth."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Callable
|
|
6
|
+
|
|
7
|
+
from fastapi import HTTPException, Request
|
|
8
|
+
|
|
9
|
+
from .service import AuthServiceBase
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def create_auth_dependency(
|
|
13
|
+
auth_service: AuthServiceBase,
|
|
14
|
+
cookie_name: str = "session_id",
|
|
15
|
+
) -> Callable[[Request], dict[str, Any]]:
|
|
16
|
+
"""Return a FastAPI dependency that resolves the current user from a cookie."""
|
|
17
|
+
|
|
18
|
+
def get_current_user(request: Request) -> dict[str, Any]:
|
|
19
|
+
session_id = request.cookies.get(cookie_name)
|
|
20
|
+
user = auth_service.get_user_by_session(session_id)
|
|
21
|
+
if not user:
|
|
22
|
+
raise HTTPException(status_code=401, detail="Authentication required")
|
|
23
|
+
return user
|
|
24
|
+
|
|
25
|
+
return get_current_user
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
__all__ = ["create_auth_dependency"]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Google OAuth token verification for app_platform.auth."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
from google.auth.transport import requests as google_requests
|
|
8
|
+
from google.oauth2 import id_token
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GoogleTokenVerifier:
|
|
12
|
+
"""TokenVerifier implementation backed by google-auth."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
client_id: Optional[str],
|
|
17
|
+
dev_mode: bool = False,
|
|
18
|
+
dev_user: Optional[Dict[str, Any]] = None,
|
|
19
|
+
):
|
|
20
|
+
self.client_id = client_id
|
|
21
|
+
self.dev_mode = dev_mode
|
|
22
|
+
self.dev_user = dev_user or {
|
|
23
|
+
"user_id": "dev_user_123",
|
|
24
|
+
"email": "dev@example.com",
|
|
25
|
+
"name": "Development User",
|
|
26
|
+
"google_user_id": "dev_google_123",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
def verify(self, token: str) -> tuple[Optional[Dict[str, Any]], Optional[str]]:
|
|
30
|
+
try:
|
|
31
|
+
if self.dev_mode or not self.client_id:
|
|
32
|
+
return dict(self.dev_user), None
|
|
33
|
+
|
|
34
|
+
id_info = id_token.verify_oauth2_token(
|
|
35
|
+
token,
|
|
36
|
+
google_requests.Request(),
|
|
37
|
+
self.client_id,
|
|
38
|
+
)
|
|
39
|
+
return {
|
|
40
|
+
"user_id": id_info["sub"],
|
|
41
|
+
"email": id_info["email"],
|
|
42
|
+
"name": id_info.get("name", ""),
|
|
43
|
+
"google_user_id": id_info["sub"],
|
|
44
|
+
}, None
|
|
45
|
+
|
|
46
|
+
except Exception as exc:
|
|
47
|
+
return None, f"Google token verification failed: {exc}"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
__all__ = ["GoogleTokenVerifier"]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Protocol contracts for app_platform.auth."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Any, Dict, Optional, Protocol, runtime_checkable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@runtime_checkable
|
|
10
|
+
class SessionStore(Protocol):
|
|
11
|
+
"""Backend contract for session persistence."""
|
|
12
|
+
|
|
13
|
+
def create_session(self, session_id: str, user_id: Any, expires_at: datetime) -> None:
|
|
14
|
+
"""Persist a new session."""
|
|
15
|
+
|
|
16
|
+
def get_session(self, session_id: str) -> Optional[Dict[str, Any]]:
|
|
17
|
+
"""Resolve a session id to the normalized auth payload."""
|
|
18
|
+
|
|
19
|
+
def delete_session(self, session_id: str) -> bool:
|
|
20
|
+
"""Delete a session if present."""
|
|
21
|
+
|
|
22
|
+
def cleanup_expired(self) -> int:
|
|
23
|
+
"""Delete expired sessions and return the cleanup count."""
|
|
24
|
+
|
|
25
|
+
def touch_session(self, session_id: str) -> None:
|
|
26
|
+
"""Update last-accessed metadata for a session."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@runtime_checkable
|
|
30
|
+
class UserStore(Protocol):
|
|
31
|
+
"""Backend contract for user persistence."""
|
|
32
|
+
|
|
33
|
+
def get_or_create_user(
|
|
34
|
+
self,
|
|
35
|
+
provider_user_id: str,
|
|
36
|
+
email: str,
|
|
37
|
+
name: str,
|
|
38
|
+
) -> tuple[Any, Dict[str, Any]]:
|
|
39
|
+
"""Return the resolved user id and normalized user payload."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@runtime_checkable
|
|
43
|
+
class TokenVerifier(Protocol):
|
|
44
|
+
"""Contract for OAuth/OIDC token verification."""
|
|
45
|
+
|
|
46
|
+
def verify(self, token: str) -> tuple[Optional[Dict[str, Any]], Optional[str]]:
|
|
47
|
+
"""Validate a provider token and return either user info or an error."""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
__all__ = ["SessionStore", "TokenVerifier", "UserStore"]
|