timeback-sdk 0.1.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.
- timeback_sdk-0.1.0/.gitignore +48 -0
- timeback_sdk-0.1.0/PKG-INFO +16 -0
- timeback_sdk-0.1.0/README.md +43 -0
- timeback_sdk-0.1.0/pyproject.toml +25 -0
- timeback_sdk-0.1.0/pytest.ini +6 -0
- timeback_sdk-0.1.0/src/timeback/__init__.py +135 -0
- timeback_sdk-0.1.0/src/timeback/py.typed +0 -0
- timeback_sdk-0.1.0/src/timeback/server/__init__.py +77 -0
- timeback_sdk-0.1.0/src/timeback/server/adapters/__init__.py +5 -0
- timeback_sdk-0.1.0/src/timeback/server/adapters/django.py +35 -0
- timeback_sdk-0.1.0/src/timeback/server/adapters/fastapi.py +170 -0
- timeback_sdk-0.1.0/src/timeback/server/handlers/__init__.py +24 -0
- timeback_sdk-0.1.0/src/timeback/server/handlers/activity.py +840 -0
- timeback_sdk-0.1.0/src/timeback/server/handlers/factory.py +79 -0
- timeback_sdk-0.1.0/src/timeback/server/handlers/identity.py +405 -0
- timeback_sdk-0.1.0/src/timeback/server/handlers/user.py +325 -0
- timeback_sdk-0.1.0/src/timeback/server/lib/__init__.py +29 -0
- timeback_sdk-0.1.0/src/timeback/server/lib/logger.py +59 -0
- timeback_sdk-0.1.0/src/timeback/server/lib/oidc.py +124 -0
- timeback_sdk-0.1.0/src/timeback/server/lib/resolve.py +412 -0
- timeback_sdk-0.1.0/src/timeback/server/lib/utils.py +70 -0
- timeback_sdk-0.1.0/src/timeback/server/timeback.py +461 -0
- timeback_sdk-0.1.0/src/timeback/server/types.py +567 -0
- timeback_sdk-0.1.0/src/timeback/shared/__init__.py +57 -0
- timeback_sdk-0.1.0/src/timeback/shared/constants.py +51 -0
- timeback_sdk-0.1.0/src/timeback/shared/types.py +285 -0
- timeback_sdk-0.1.0/tests/__init__.py +1 -0
- timeback_sdk-0.1.0/tests/test_activity_validation.py +311 -0
- timeback_sdk-0.1.0/tests/test_canonical_activity_url.py +242 -0
- timeback_sdk-0.1.0/tests/test_course_resolution.py +189 -0
- timeback_sdk-0.1.0/tests/test_identity_handler.py +219 -0
- timeback_sdk-0.1.0/tests/test_oidc.py +62 -0
- timeback_sdk-0.1.0/tests/test_resolve.py +163 -0
- timeback_sdk-0.1.0/tests/test_types.py +204 -0
- timeback_sdk-0.1.0/tests/test_user_handler.py +418 -0
- timeback_sdk-0.1.0/tests/test_utils.py +71 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib64/
|
|
14
|
+
parts/
|
|
15
|
+
sdist/
|
|
16
|
+
var/
|
|
17
|
+
wheels/
|
|
18
|
+
*.egg-info/
|
|
19
|
+
.installed.cfg
|
|
20
|
+
*.egg
|
|
21
|
+
|
|
22
|
+
# Virtual environments
|
|
23
|
+
.venv/
|
|
24
|
+
venv/
|
|
25
|
+
ENV/
|
|
26
|
+
|
|
27
|
+
# uv
|
|
28
|
+
uv.lock
|
|
29
|
+
|
|
30
|
+
# ruff
|
|
31
|
+
.ruff_cache/
|
|
32
|
+
|
|
33
|
+
# Testing
|
|
34
|
+
.pytest_cache/
|
|
35
|
+
.coverage
|
|
36
|
+
htmlcov/
|
|
37
|
+
.tox/
|
|
38
|
+
.nox/
|
|
39
|
+
|
|
40
|
+
# IDEs
|
|
41
|
+
.idea/
|
|
42
|
+
.vscode/
|
|
43
|
+
*.swp
|
|
44
|
+
*.swo
|
|
45
|
+
|
|
46
|
+
# OS
|
|
47
|
+
.DS_Store
|
|
48
|
+
Thumbs.db
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: timeback-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Timeback SDK for Python - adapters for FastAPI, Django, and more
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: httpx>=0.27.0
|
|
7
|
+
Requires-Dist: starlette>=0.35.0
|
|
8
|
+
Requires-Dist: timeback-core
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
11
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
12
|
+
Requires-Dist: ruff>=0.8.0; extra == 'dev'
|
|
13
|
+
Provides-Extra: django
|
|
14
|
+
Requires-Dist: django>=4.0; extra == 'django'
|
|
15
|
+
Provides-Extra: fastapi
|
|
16
|
+
Requires-Dist: fastapi>=0.100.0; extra == 'fastapi'
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Timeback SDK
|
|
2
|
+
|
|
3
|
+
Server-side SDK for integrating Timeback into Python web applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# With FastAPI
|
|
9
|
+
pip install timeback[fastapi]
|
|
10
|
+
|
|
11
|
+
# With Django
|
|
12
|
+
pip install timeback[django]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## FastAPI
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from fastapi import FastAPI
|
|
19
|
+
from timeback.fastapi import create_timeback_router
|
|
20
|
+
|
|
21
|
+
app = FastAPI()
|
|
22
|
+
|
|
23
|
+
timeback_router = create_timeback_router(
|
|
24
|
+
env="staging",
|
|
25
|
+
client_id="...",
|
|
26
|
+
client_secret="...",
|
|
27
|
+
identity={
|
|
28
|
+
"mode": "sso",
|
|
29
|
+
"client_id": "...",
|
|
30
|
+
"client_secret": "...",
|
|
31
|
+
"get_user": lambda req: get_session_user(req),
|
|
32
|
+
"on_callback_success": lambda ctx: handle_sso_success(ctx),
|
|
33
|
+
},
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
app.include_router(timeback_router, prefix="/api/timeback")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Django
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
# Coming soon
|
|
43
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "timeback-sdk"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Timeback SDK for Python - adapters for FastAPI, Django, and more"
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"timeback-core",
|
|
8
|
+
"httpx>=0.27.0",
|
|
9
|
+
"starlette>=0.35.0",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[project.optional-dependencies]
|
|
13
|
+
fastapi = ["fastapi>=0.100.0"]
|
|
14
|
+
django = ["django>=4.0"]
|
|
15
|
+
dev = ["pytest>=8.0", "pytest-asyncio>=0.23", "ruff>=0.8.0"]
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["hatchling"]
|
|
19
|
+
build-backend = "hatchling.build"
|
|
20
|
+
|
|
21
|
+
[tool.hatch.build.targets.wheel]
|
|
22
|
+
packages = ["src/timeback"]
|
|
23
|
+
|
|
24
|
+
[tool.uv.sources]
|
|
25
|
+
timeback-core = { workspace = true }
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Timeback SDK for Python.
|
|
3
|
+
|
|
4
|
+
Server-side SDK for integrating Timeback into Python web applications.
|
|
5
|
+
|
|
6
|
+
Example (FastAPI with SSO - Full SDK):
|
|
7
|
+
```python
|
|
8
|
+
from fastapi import FastAPI
|
|
9
|
+
from timeback import ApiCredentials, SsoIdentityConfig
|
|
10
|
+
from timeback.server import create_server, to_fastapi_router
|
|
11
|
+
|
|
12
|
+
async def create_app():
|
|
13
|
+
timeback = await create_server(TimebackConfig(
|
|
14
|
+
env="staging",
|
|
15
|
+
api=ApiCredentials(
|
|
16
|
+
client_id=os.environ["TIMEBACK_API_CLIENT_ID"],
|
|
17
|
+
client_secret=os.environ["TIMEBACK_API_CLIENT_SECRET"],
|
|
18
|
+
),
|
|
19
|
+
identity=SsoIdentityConfig(
|
|
20
|
+
mode="sso",
|
|
21
|
+
client_id=os.environ["COGNITO_CLIENT_ID"],
|
|
22
|
+
client_secret=os.environ["COGNITO_CLIENT_SECRET"],
|
|
23
|
+
get_user=get_session_user, # returns { id: timebackId, email }
|
|
24
|
+
on_callback_success=handle_sso_success,
|
|
25
|
+
),
|
|
26
|
+
))
|
|
27
|
+
|
|
28
|
+
app = FastAPI()
|
|
29
|
+
app.include_router(to_fastapi_router(timeback), prefix="/api/timeback")
|
|
30
|
+
return app
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Example (FastAPI with custom auth):
|
|
34
|
+
```python
|
|
35
|
+
from timeback import ApiCredentials, CustomIdentityConfig
|
|
36
|
+
from timeback.server import create_server, to_fastapi_router
|
|
37
|
+
|
|
38
|
+
timeback = await create_server(TimebackConfig(
|
|
39
|
+
env="staging",
|
|
40
|
+
api=ApiCredentials(client_id="...", client_secret="..."),
|
|
41
|
+
identity=CustomIdentityConfig(
|
|
42
|
+
mode="custom",
|
|
43
|
+
get_email=lambda req: get_session(req).email, # returns email string
|
|
44
|
+
),
|
|
45
|
+
))
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Example (FastAPI with SSO - Identity-Only):
|
|
49
|
+
```python
|
|
50
|
+
from fastapi import FastAPI
|
|
51
|
+
from timeback import IdentityOnlyConfig, IdentityOnlySsoConfig
|
|
52
|
+
from timeback.server import create_identity_only_server, to_fastapi_router
|
|
53
|
+
|
|
54
|
+
timeback = create_identity_only_server(IdentityOnlyConfig(
|
|
55
|
+
env="staging",
|
|
56
|
+
identity=IdentityOnlySsoConfig(
|
|
57
|
+
mode="sso",
|
|
58
|
+
client_id=os.environ["COGNITO_CLIENT_ID"],
|
|
59
|
+
client_secret=os.environ["COGNITO_CLIENT_SECRET"],
|
|
60
|
+
on_callback_success=lambda ctx: ctx.redirect("/dashboard"),
|
|
61
|
+
),
|
|
62
|
+
))
|
|
63
|
+
|
|
64
|
+
app = FastAPI()
|
|
65
|
+
app.include_router(to_fastapi_router(timeback), prefix="/api/auth")
|
|
66
|
+
```
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
from .server.types import (
|
|
70
|
+
ApiCredentials,
|
|
71
|
+
BuildStateContext,
|
|
72
|
+
CallbackErrorContext,
|
|
73
|
+
CallbackSuccessContext,
|
|
74
|
+
CustomIdentityConfig,
|
|
75
|
+
IdentityConfig,
|
|
76
|
+
IdentityOnlyCallbackSuccessContext,
|
|
77
|
+
IdentityOnlyConfig,
|
|
78
|
+
IdentityOnlySsoConfig,
|
|
79
|
+
IdpData,
|
|
80
|
+
OIDCTokens,
|
|
81
|
+
OIDCUserInfo,
|
|
82
|
+
SsoIdentityConfig,
|
|
83
|
+
TimebackConfig,
|
|
84
|
+
)
|
|
85
|
+
from .shared.types import (
|
|
86
|
+
ActivityCourseRef,
|
|
87
|
+
ActivityEndPayload,
|
|
88
|
+
ActivityMetrics,
|
|
89
|
+
ActivityParams,
|
|
90
|
+
ActivityResponse,
|
|
91
|
+
CourseCodeRef,
|
|
92
|
+
Environment,
|
|
93
|
+
IdentityClaims,
|
|
94
|
+
SubjectGradeCourseRef,
|
|
95
|
+
TimebackAuthUser,
|
|
96
|
+
TimebackIdentity,
|
|
97
|
+
TimebackProfile,
|
|
98
|
+
TimebackSessionUser,
|
|
99
|
+
TimebackUser,
|
|
100
|
+
TimebackUserResolutionErrorCode,
|
|
101
|
+
is_subject_grade_course_ref,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
__all__ = [
|
|
105
|
+
"ActivityCourseRef",
|
|
106
|
+
"ActivityEndPayload",
|
|
107
|
+
"ActivityMetrics",
|
|
108
|
+
"ActivityParams",
|
|
109
|
+
"ActivityResponse",
|
|
110
|
+
"ApiCredentials",
|
|
111
|
+
"BuildStateContext",
|
|
112
|
+
"CallbackErrorContext",
|
|
113
|
+
"CallbackSuccessContext",
|
|
114
|
+
"CourseCodeRef",
|
|
115
|
+
"CustomIdentityConfig",
|
|
116
|
+
"Environment",
|
|
117
|
+
"IdentityClaims",
|
|
118
|
+
"IdentityConfig",
|
|
119
|
+
"IdentityOnlyCallbackSuccessContext",
|
|
120
|
+
"IdentityOnlyConfig",
|
|
121
|
+
"IdentityOnlySsoConfig",
|
|
122
|
+
"IdpData",
|
|
123
|
+
"OIDCTokens",
|
|
124
|
+
"OIDCUserInfo",
|
|
125
|
+
"SsoIdentityConfig",
|
|
126
|
+
"SubjectGradeCourseRef",
|
|
127
|
+
"TimebackAuthUser",
|
|
128
|
+
"TimebackConfig",
|
|
129
|
+
"TimebackIdentity",
|
|
130
|
+
"TimebackProfile",
|
|
131
|
+
"TimebackSessionUser",
|
|
132
|
+
"TimebackUser",
|
|
133
|
+
"TimebackUserResolutionErrorCode",
|
|
134
|
+
"is_subject_grade_course_ref",
|
|
135
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Timeback server SDK."""
|
|
2
|
+
|
|
3
|
+
from .adapters.fastapi import to_fastapi_router
|
|
4
|
+
from .handlers import IdentityOnlyHandlers, InvalidSensorUrlError, MissingSyncedCourseIdError
|
|
5
|
+
from .lib.resolve import (
|
|
6
|
+
ActivityCourseResolutionError,
|
|
7
|
+
TimebackUserResolutionError,
|
|
8
|
+
lookup_timeback_id_by_email,
|
|
9
|
+
resolve_activity_course,
|
|
10
|
+
resolve_status_for_user_resolution_error,
|
|
11
|
+
resolve_timeback_user_by_email,
|
|
12
|
+
)
|
|
13
|
+
from .timeback import (
|
|
14
|
+
AppConfig,
|
|
15
|
+
ConfigValidationError,
|
|
16
|
+
CourseEnvOverrides,
|
|
17
|
+
CourseOverrides,
|
|
18
|
+
IdentityOnlyInstance,
|
|
19
|
+
TimebackInstance,
|
|
20
|
+
create_identity_only_server,
|
|
21
|
+
create_server,
|
|
22
|
+
)
|
|
23
|
+
from .types import (
|
|
24
|
+
ActivityBeforeSendData,
|
|
25
|
+
ApiCredentials,
|
|
26
|
+
BuildStateContext,
|
|
27
|
+
CallbackErrorContext,
|
|
28
|
+
CallbackSuccessContext,
|
|
29
|
+
CustomIdentityConfig,
|
|
30
|
+
IdentityConfig,
|
|
31
|
+
IdentityOnlyCallbackSuccessContext,
|
|
32
|
+
IdentityOnlyConfig,
|
|
33
|
+
IdentityOnlySsoConfig,
|
|
34
|
+
IdpData,
|
|
35
|
+
OIDCTokens,
|
|
36
|
+
OIDCUserInfo,
|
|
37
|
+
SsoIdentityConfig,
|
|
38
|
+
TimebackConfig,
|
|
39
|
+
TimebackHooks,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"ActivityBeforeSendData",
|
|
44
|
+
"ActivityCourseResolutionError",
|
|
45
|
+
"ApiCredentials",
|
|
46
|
+
"AppConfig",
|
|
47
|
+
"BuildStateContext",
|
|
48
|
+
"CallbackErrorContext",
|
|
49
|
+
"CallbackSuccessContext",
|
|
50
|
+
"ConfigValidationError",
|
|
51
|
+
"CourseEnvOverrides",
|
|
52
|
+
"CourseOverrides",
|
|
53
|
+
"CustomIdentityConfig",
|
|
54
|
+
"IdentityConfig",
|
|
55
|
+
"IdentityOnlyCallbackSuccessContext",
|
|
56
|
+
"IdentityOnlyConfig",
|
|
57
|
+
"IdentityOnlyHandlers",
|
|
58
|
+
"IdentityOnlyInstance",
|
|
59
|
+
"IdentityOnlySsoConfig",
|
|
60
|
+
"IdpData",
|
|
61
|
+
"InvalidSensorUrlError",
|
|
62
|
+
"MissingSyncedCourseIdError",
|
|
63
|
+
"OIDCTokens",
|
|
64
|
+
"OIDCUserInfo",
|
|
65
|
+
"SsoIdentityConfig",
|
|
66
|
+
"TimebackConfig",
|
|
67
|
+
"TimebackHooks",
|
|
68
|
+
"TimebackInstance",
|
|
69
|
+
"TimebackUserResolutionError",
|
|
70
|
+
"create_identity_only_server",
|
|
71
|
+
"create_server",
|
|
72
|
+
"lookup_timeback_id_by_email",
|
|
73
|
+
"resolve_activity_course",
|
|
74
|
+
"resolve_status_for_user_resolution_error",
|
|
75
|
+
"resolve_timeback_user_by_email",
|
|
76
|
+
"to_fastapi_router",
|
|
77
|
+
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django adapter for Timeback SDK.
|
|
3
|
+
|
|
4
|
+
Provides URL patterns and views for Django integration.
|
|
5
|
+
|
|
6
|
+
Status: Not yet implemented. Coming soon.
|
|
7
|
+
|
|
8
|
+
Example (future API):
|
|
9
|
+
```python
|
|
10
|
+
# urls.py
|
|
11
|
+
from django.urls import path, include
|
|
12
|
+
from timeback.django import get_timeback_urls
|
|
13
|
+
|
|
14
|
+
urlpatterns = [
|
|
15
|
+
path("api/timeback/", include(get_timeback_urls(
|
|
16
|
+
env="staging",
|
|
17
|
+
api={"client_id": "...", "client_secret": "..."},
|
|
18
|
+
identity={"mode": "custom", "get_user": get_request_user},
|
|
19
|
+
))),
|
|
20
|
+
]
|
|
21
|
+
```
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
# Django adapter implementation will be added when there's demand.
|
|
25
|
+
# Key considerations:
|
|
26
|
+
# - Django uses sync views by default (async views available in 3.1+)
|
|
27
|
+
# - Django has its own Request/Response objects (not Starlette)
|
|
28
|
+
# - Session handling is built-in via django.contrib.sessions
|
|
29
|
+
# - Authentication is built-in via django.contrib.auth
|
|
30
|
+
#
|
|
31
|
+
# Implementation approach:
|
|
32
|
+
# 1. Create view functions that wrap the core handlers
|
|
33
|
+
# 2. Convert Django HttpRequest to a common format
|
|
34
|
+
# 3. Convert handler responses back to Django HttpResponse
|
|
35
|
+
# 4. Provide both sync (using async_to_sync) and async views
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI Adapter
|
|
3
|
+
|
|
4
|
+
Adapts Timeback handlers for FastAPI.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, cast, overload
|
|
10
|
+
|
|
11
|
+
from fastapi import APIRouter, Request
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from starlette.responses import Response
|
|
15
|
+
|
|
16
|
+
from ..handlers.factory import Handlers
|
|
17
|
+
from ..timeback import IdentityOnlyInstance, TimebackInstance
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Type for flexible input (full or identity-only instance)
|
|
21
|
+
AnyTimebackInstance = "TimebackInstance | IdentityOnlyInstance"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@overload
|
|
25
|
+
def to_fastapi_router(
|
|
26
|
+
timeback: TimebackInstance,
|
|
27
|
+
*,
|
|
28
|
+
callback_path: str | None = None,
|
|
29
|
+
) -> APIRouter: ...
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@overload
|
|
33
|
+
def to_fastapi_router(
|
|
34
|
+
timeback: IdentityOnlyInstance,
|
|
35
|
+
*,
|
|
36
|
+
callback_path: str | None = None,
|
|
37
|
+
) -> APIRouter: ...
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def to_fastapi_router(
|
|
41
|
+
timeback: TimebackInstance | IdentityOnlyInstance,
|
|
42
|
+
*,
|
|
43
|
+
callback_path: str | None = None,
|
|
44
|
+
) -> APIRouter:
|
|
45
|
+
"""
|
|
46
|
+
Convert Timeback instance to FastAPI router.
|
|
47
|
+
|
|
48
|
+
Supports both full SDK instances (from create_server()) and identity-only
|
|
49
|
+
instances (from create_identity_only_server()).
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
timeback: Timeback instance (full or identity-only)
|
|
53
|
+
callback_path: Custom callback path for OAuth redirects. If your IdP has
|
|
54
|
+
a pre-registered callback URL that differs from the SDK default
|
|
55
|
+
(/identity/callback), specify the path here. The path should be
|
|
56
|
+
relative to the router mount point (e.g., "/auth/callback").
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
FastAPI APIRouter with Timeback endpoints
|
|
60
|
+
|
|
61
|
+
Example (Full SDK):
|
|
62
|
+
```python
|
|
63
|
+
from timeback.server import create_server, TimebackConfig, SsoIdentityConfig
|
|
64
|
+
from timeback.server.adapters.fastapi import to_fastapi_router
|
|
65
|
+
|
|
66
|
+
timeback = await create_server(TimebackConfig(
|
|
67
|
+
env="staging",
|
|
68
|
+
api=ApiCredentials(client_id="...", client_secret="..."),
|
|
69
|
+
identity=SsoIdentityConfig(
|
|
70
|
+
mode="sso",
|
|
71
|
+
client_id="...",
|
|
72
|
+
client_secret="...",
|
|
73
|
+
get_user=get_session_user,
|
|
74
|
+
on_callback_success=lambda ctx: ctx.redirect("/"),
|
|
75
|
+
),
|
|
76
|
+
))
|
|
77
|
+
|
|
78
|
+
app = FastAPI()
|
|
79
|
+
app.include_router(to_fastapi_router(timeback), prefix="/api/timeback")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Example (Identity-Only):
|
|
83
|
+
```python
|
|
84
|
+
from timeback.server import (
|
|
85
|
+
create_identity_only_server,
|
|
86
|
+
IdentityOnlyConfig,
|
|
87
|
+
IdentityOnlySsoConfig,
|
|
88
|
+
)
|
|
89
|
+
from timeback.server.adapters.fastapi import to_fastapi_router
|
|
90
|
+
|
|
91
|
+
timeback = create_identity_only_server(IdentityOnlyConfig(
|
|
92
|
+
env="staging",
|
|
93
|
+
identity=IdentityOnlySsoConfig(
|
|
94
|
+
mode="sso",
|
|
95
|
+
client_id="...",
|
|
96
|
+
client_secret="...",
|
|
97
|
+
on_callback_success=lambda ctx: ctx.redirect("/dashboard"),
|
|
98
|
+
),
|
|
99
|
+
))
|
|
100
|
+
|
|
101
|
+
app = FastAPI()
|
|
102
|
+
app.include_router(to_fastapi_router(timeback), prefix="/api/auth")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Example (Custom Callback Path):
|
|
106
|
+
```python
|
|
107
|
+
# When your IdP requires a specific callback URL like:
|
|
108
|
+
# https://example.com/api/auth/sso/callback/timeback
|
|
109
|
+
|
|
110
|
+
timeback = create_identity_only_server(IdentityOnlyConfig(
|
|
111
|
+
env="staging",
|
|
112
|
+
identity=IdentityOnlySsoConfig(
|
|
113
|
+
mode="sso",
|
|
114
|
+
client_id="...",
|
|
115
|
+
client_secret="...",
|
|
116
|
+
redirect_uri="https://example.com/api/auth/sso/callback/timeback",
|
|
117
|
+
on_callback_success=lambda ctx: ctx.redirect("/dashboard"),
|
|
118
|
+
),
|
|
119
|
+
))
|
|
120
|
+
|
|
121
|
+
app = FastAPI()
|
|
122
|
+
app.include_router(
|
|
123
|
+
to_fastapi_router(timeback, callback_path="/sso/callback/timeback"),
|
|
124
|
+
prefix="/api/auth",
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
"""
|
|
128
|
+
router = APIRouter()
|
|
129
|
+
handle = timeback.handle
|
|
130
|
+
|
|
131
|
+
# Determine if this is a full instance or identity-only
|
|
132
|
+
is_full_instance = hasattr(handle, "activity") and hasattr(handle, "user")
|
|
133
|
+
|
|
134
|
+
# Register identity routes
|
|
135
|
+
@router.get("/identity/signin")
|
|
136
|
+
async def identity_signin(request: Request) -> Response:
|
|
137
|
+
return await handle.identity.sign_in(request)
|
|
138
|
+
|
|
139
|
+
# Default callback path
|
|
140
|
+
@router.get("/identity/callback")
|
|
141
|
+
async def identity_callback(request: Request) -> Response:
|
|
142
|
+
return await handle.identity.callback(request)
|
|
143
|
+
|
|
144
|
+
# Custom callback path (if provided and different from default)
|
|
145
|
+
if callback_path and callback_path != "/identity/callback":
|
|
146
|
+
# Normalize path to ensure it starts with /
|
|
147
|
+
normalized_path = callback_path if callback_path.startswith("/") else f"/{callback_path}"
|
|
148
|
+
|
|
149
|
+
@router.get(normalized_path)
|
|
150
|
+
async def identity_callback_custom(request: Request) -> Response:
|
|
151
|
+
return await handle.identity.callback(request)
|
|
152
|
+
|
|
153
|
+
@router.get("/identity/signout")
|
|
154
|
+
async def identity_signout(_request: Request) -> Response:
|
|
155
|
+
return handle.identity.sign_out()
|
|
156
|
+
|
|
157
|
+
# Register full SDK routes only for full instances
|
|
158
|
+
if is_full_instance:
|
|
159
|
+
# Cast to Handlers since we've confirmed it has activity and user
|
|
160
|
+
full_handle = cast("Handlers", handle)
|
|
161
|
+
|
|
162
|
+
@router.post("/activity", response_model=None)
|
|
163
|
+
async def submit_activity(request: Request) -> Response:
|
|
164
|
+
return await full_handle.activity(request)
|
|
165
|
+
|
|
166
|
+
@router.get("/user/me")
|
|
167
|
+
async def user_me(request: Request) -> Response:
|
|
168
|
+
return await full_handle.user.me(request)
|
|
169
|
+
|
|
170
|
+
return router
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""SDK request handlers."""
|
|
2
|
+
|
|
3
|
+
from .activity import InvalidSensorUrlError, MissingSyncedCourseIdError, create_activity_handler
|
|
4
|
+
from .factory import Handlers, create_handlers
|
|
5
|
+
from .identity import (
|
|
6
|
+
IdentityHandlers,
|
|
7
|
+
IdentityOnlyHandlers,
|
|
8
|
+
create_identity_handlers,
|
|
9
|
+
create_identity_only_handlers,
|
|
10
|
+
)
|
|
11
|
+
from .user import create_user_handler
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"Handlers",
|
|
15
|
+
"IdentityHandlers",
|
|
16
|
+
"IdentityOnlyHandlers",
|
|
17
|
+
"InvalidSensorUrlError",
|
|
18
|
+
"MissingSyncedCourseIdError",
|
|
19
|
+
"create_activity_handler",
|
|
20
|
+
"create_handlers",
|
|
21
|
+
"create_identity_handlers",
|
|
22
|
+
"create_identity_only_handlers",
|
|
23
|
+
"create_user_handler",
|
|
24
|
+
]
|