jac-scale 0.1.1__tar.gz → 0.1.2__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.
- {jac_scale-0.1.1 → jac_scale-0.1.2}/PKG-INFO +9 -2
- {jac_scale-0.1.1 → jac_scale-0.1.2}/README.md +7 -0
- jac_scale-0.1.2/jac_scale/google_sso_provider.jac +85 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/impl/serve.impl.jac +12 -225
- jac_scale-0.1.2/jac_scale/impl/user_manager.impl.jac +349 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/plugin.jac +8 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/serve.jac +0 -12
- jac_scale-0.1.2/jac_scale/sso_provider.jac +72 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/targets/kubernetes/kubernetes_target.jac +7 -2
- jac_scale-0.1.2/jac_scale/tests/test_hooks.py +39 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/tests/test_sso.py +273 -284
- jac_scale-0.1.2/jac_scale/user_manager.jac +49 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale.egg-info/PKG-INFO +9 -2
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale.egg-info/SOURCES.txt +5 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale.egg-info/requires.txt +1 -1
- {jac_scale-0.1.1 → jac_scale-0.1.2}/pyproject.toml +2 -2
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/__init__.py +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/abstractions/config/app_config.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/abstractions/config/base_config.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/abstractions/database_provider.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/abstractions/deployment_target.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/abstractions/image_registry.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/abstractions/logger.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/abstractions/models/deployment_result.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/abstractions/models/resource_status.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/config_loader.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/context.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/factories/database_factory.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/factories/deployment_factory.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/factories/registry_factory.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/factories/utility_factory.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/impl/config_loader.impl.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/impl/context.impl.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/impl/memory_hierarchy.main.impl.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/impl/memory_hierarchy.mongo.impl.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/impl/memory_hierarchy.redis.impl.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/jserver/__init__.py +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/jserver/impl/jfast_api.impl.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/jserver/impl/jserver.impl.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/jserver/jfast_api.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/jserver/jserver.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/memory_hierarchy.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/plugin_config.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/providers/database/kubernetes_mongo.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/providers/database/kubernetes_redis.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/providers/registry/dockerhub.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/targets/kubernetes/kubernetes_config.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/targets/kubernetes/utils/kubernetes_utils.impl.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/targets/kubernetes/utils/kubernetes_utils.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/tests/__init__.py +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/tests/conftest.py +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/tests/fixtures/test_api.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/tests/fixtures/todo_app.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/tests/test_abstractions.py +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/tests/test_deploy_k8s.py +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/tests/test_examples.py +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/tests/test_factories.py +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/tests/test_file_upload.py +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/tests/test_k8s_utils.py +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/tests/test_memory_hierarchy.py +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/tests/test_serve.py +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/utilities/loggers/standard_logger.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale/utils.jac +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale.egg-info/dependency_links.txt +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale.egg-info/entry_points.txt +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/jac_scale.egg-info/top_level.txt +0 -0
- {jac_scale-0.1.1 → jac_scale-0.1.2}/setup.cfg +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jac-scale
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Author-email: Jason Mars <jason@mars.ninja>
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
|
-
Requires-Dist: jaclang>=0.9.
|
|
7
|
+
Requires-Dist: jaclang>=0.9.11
|
|
8
8
|
Requires-Dist: python-dotenv<2.0.0,>=1.2.1
|
|
9
9
|
Requires-Dist: docker<8.0.0,>=7.1.0
|
|
10
10
|
Requires-Dist: kubernetes<35.0.0,>=34.1.0
|
|
@@ -43,6 +43,13 @@ Requires-Dist: python-multipart<1.0.0,>=0.0.21
|
|
|
43
43
|
|
|
44
44
|
Whether you're developing locally with `jac start` or deploying to production with `jac start --scale`, you get the same powerful features with the flexibility to choose your deployment strategy.
|
|
45
45
|
|
|
46
|
+
### 4. Single Sign-On (SSO) Support
|
|
47
|
+
|
|
48
|
+
- **Google SSO**: Built-in support for Google Sign-In out of the box
|
|
49
|
+
- **Extensible Architecture**: Easily add other providers (GitHub, Microsoft, etc.)
|
|
50
|
+
- **Secure Authentication**: Integrated with JWT for secure session management
|
|
51
|
+
- **User Management**: Automatic account creation and linking
|
|
52
|
+
|
|
46
53
|
## Prerequisites
|
|
47
54
|
|
|
48
55
|
- kubenetes(K8s) installed
|
|
@@ -25,6 +25,13 @@
|
|
|
25
25
|
|
|
26
26
|
Whether you're developing locally with `jac start` or deploying to production with `jac start --scale`, you get the same powerful features with the flexibility to choose your deployment strategy.
|
|
27
27
|
|
|
28
|
+
### 4. Single Sign-On (SSO) Support
|
|
29
|
+
|
|
30
|
+
- **Google SSO**: Built-in support for Google Sign-In out of the box
|
|
31
|
+
- **Extensible Architecture**: Easily add other providers (GitHub, Microsoft, etc.)
|
|
32
|
+
- **Secure Authentication**: Integrated with JWT for secure session management
|
|
33
|
+
- **User Management**: Automatic account creation and linking
|
|
34
|
+
|
|
28
35
|
## Prerequisites
|
|
29
36
|
|
|
30
37
|
- kubenetes(K8s) installed
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Google SSO Provider implementation for jac-scale.
|
|
2
|
+
|
|
3
|
+
This module implements the SSOProvider interface for Google OAuth authentication.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import from fastapi { Request, Response }
|
|
7
|
+
import from fastapi_sso.sso.google { GoogleSSO }
|
|
8
|
+
import from jac_scale.sso_provider { SSOProvider, SSOUserInfo }
|
|
9
|
+
|
|
10
|
+
"""Google OAuth SSO Provider.
|
|
11
|
+
|
|
12
|
+
This class wraps the fastapi_sso GoogleSSO implementation to conform to
|
|
13
|
+
our SSOProvider interface, enabling consistent handling across all SSO vendors.
|
|
14
|
+
"""
|
|
15
|
+
obj GoogleSSOProvider(SSOProvider) {
|
|
16
|
+
has client_id: str,
|
|
17
|
+
client_secret: str,
|
|
18
|
+
redirect_uri: str,
|
|
19
|
+
allow_insecure_http: bool = True,
|
|
20
|
+
_google_sso: (GoogleSSO | None) = None;
|
|
21
|
+
|
|
22
|
+
"""Initialize the Google SSO provider."""
|
|
23
|
+
def postinit -> None {
|
|
24
|
+
self._google_sso = GoogleSSO(
|
|
25
|
+
client_id=self.client_id,
|
|
26
|
+
client_secret=self.client_secret,
|
|
27
|
+
redirect_uri=self.redirect_uri,
|
|
28
|
+
allow_insecure_http=self.allow_insecure_http
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
"""Initiate Google OAuth authentication flow.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
operation: The operation type ('login' or 'register')
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
RedirectResponse to Google's OAuth authorization page
|
|
39
|
+
"""
|
|
40
|
+
async def initiate_auth(operation: str) -> Response {
|
|
41
|
+
if not self._google_sso {
|
|
42
|
+
raise RuntimeError("GoogleSSO not initialized") ;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
with self._google_sso {
|
|
46
|
+
return await self._google_sso.get_login_redirect();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
"""Handle the OAuth callback from Google.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
request: The FastAPI request object containing OAuth callback data
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
SSOUserInfo: Standardized user information from Google
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
Exception: If authentication fails or user info cannot be retrieved
|
|
60
|
+
"""
|
|
61
|
+
async def handle_callback(request: Request) -> SSOUserInfo {
|
|
62
|
+
if not self._google_sso {
|
|
63
|
+
raise RuntimeError("GoogleSSO not initialized") ;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
with self._google_sso {
|
|
67
|
+
user_info = await self._google_sso.verify_and_process(request);
|
|
68
|
+
return SSOUserInfo(
|
|
69
|
+
email=user_info.email,
|
|
70
|
+
external_id=user_info.id,
|
|
71
|
+
platform=self.get_platform_name(),
|
|
72
|
+
display_name=user_info?.display_name
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
"""Get the platform identifier.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
str: 'google'
|
|
81
|
+
"""
|
|
82
|
+
def get_platform_name -> str {
|
|
83
|
+
return "google";
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -558,7 +558,7 @@ impl JacAPIServer.create_function_callback(
|
|
|
558
558
|
) {
|
|
559
559
|
token = authorization[7:];
|
|
560
560
|
}
|
|
561
|
-
username = self.validate_jwt_token(token) if token else None;
|
|
561
|
+
username = self.user_manager.validate_jwt_token(token) if token else None;
|
|
562
562
|
if not username {
|
|
563
563
|
return TransportResponse.fail(
|
|
564
564
|
code='UNAUTHORIZED',
|
|
@@ -685,7 +685,7 @@ impl JacAPIServer.create_walker_callback(
|
|
|
685
685
|
) {
|
|
686
686
|
token = authorization[7:];
|
|
687
687
|
}
|
|
688
|
-
username = self.validate_jwt_token(token) if token else None;
|
|
688
|
+
username = self.user_manager.validate_jwt_token(token) if token else None;
|
|
689
689
|
if not username {
|
|
690
690
|
return TransportResponse.fail(
|
|
691
691
|
code='UNAUTHORIZED',
|
|
@@ -783,7 +783,7 @@ impl JacAPIServer.refresh_token(token: (str | None) = None) -> TransportResponse
|
|
|
783
783
|
if token.startswith('Bearer ') {
|
|
784
784
|
token = token[7:];
|
|
785
785
|
}
|
|
786
|
-
new_token = self.refresh_jwt_token(token);
|
|
786
|
+
new_token = self.user_manager.refresh_jwt_token(token);
|
|
787
787
|
if (not new_token) {
|
|
788
788
|
return TransportResponse.fail(
|
|
789
789
|
code='UNAUTHORIZED',
|
|
@@ -809,7 +809,7 @@ impl JacAPIServer.create_user(username: str, password: str) -> TransportResponse
|
|
|
809
809
|
meta=Meta(extra={'http_status': 400})
|
|
810
810
|
);
|
|
811
811
|
}
|
|
812
|
-
res['token'] = self.create_jwt_token(username);
|
|
812
|
+
res['token'] = self.user_manager.create_jwt_token(username);
|
|
813
813
|
return TransportResponse.success(
|
|
814
814
|
data=res, meta=Meta(extra={'http_status': 201})
|
|
815
815
|
);
|
|
@@ -837,7 +837,7 @@ impl JacAPIServer.update_username(
|
|
|
837
837
|
) {
|
|
838
838
|
token = Authorization[7:];
|
|
839
839
|
}
|
|
840
|
-
token_username = self.validate_jwt_token(token) if token else None;
|
|
840
|
+
token_username = self.user_manager.validate_jwt_token(token) if token else None;
|
|
841
841
|
if not token_username {
|
|
842
842
|
return TransportResponse.fail(
|
|
843
843
|
code='UNAUTHORIZED',
|
|
@@ -869,7 +869,7 @@ impl JacAPIServer.update_username(
|
|
|
869
869
|
);
|
|
870
870
|
}
|
|
871
871
|
# Generate new JWT token with updated username
|
|
872
|
-
result['token'] = self.create_jwt_token(new_username);
|
|
872
|
+
result['token'] = self.user_manager.create_jwt_token(new_username);
|
|
873
873
|
return TransportResponse.success(
|
|
874
874
|
data=result, meta=Meta(extra={'http_status': 200})
|
|
875
875
|
);
|
|
@@ -891,7 +891,7 @@ impl JacAPIServer.update_password(
|
|
|
891
891
|
) {
|
|
892
892
|
token = Authorization[7:];
|
|
893
893
|
}
|
|
894
|
-
token_username = self.validate_jwt_token(token) if token else None;
|
|
894
|
+
token_username = self.user_manager.validate_jwt_token(token) if token else None;
|
|
895
895
|
if not token_username {
|
|
896
896
|
return TransportResponse.fail(
|
|
897
897
|
code='UNAUTHORIZED',
|
|
@@ -1066,7 +1066,7 @@ impl JacAPIServer.login(username: str, password: str) -> TransportResponse {
|
|
|
1066
1066
|
meta=Meta(extra={'http_status': 401})
|
|
1067
1067
|
);
|
|
1068
1068
|
}
|
|
1069
|
-
result['token'] = self.create_jwt_token(username);
|
|
1069
|
+
result['token'] = self.user_manager.create_jwt_token(username);
|
|
1070
1070
|
return TransportResponse.success(
|
|
1071
1071
|
data=dict[(str, JsonValue)](result), meta=Meta(extra={'http_status': 200})
|
|
1072
1072
|
);
|
|
@@ -1109,219 +1109,6 @@ impl JacAPIServer.postinit -> None {
|
|
|
1109
1109
|
}
|
|
1110
1110
|
self.server.app.add_middleware(CustomHeadersMiddleware);
|
|
1111
1111
|
}
|
|
1112
|
-
self.SUPPORTED_PLATFORMS: dict = {};
|
|
1113
|
-
# Load SSO config fresh (not from cached global) to support env var overrides at runtime
|
|
1114
|
-
sso_config = get_scale_config().get_sso_config();
|
|
1115
|
-
for platform in Platforms {
|
|
1116
|
-
key = platform.lower();
|
|
1117
|
-
platform_config = sso_config.get(key, {});
|
|
1118
|
-
|
|
1119
|
-
client_id = platform_config.get('client_id', '');
|
|
1120
|
-
client_secret = platform_config.get('client_secret', '');
|
|
1121
|
-
|
|
1122
|
-
if not client_id or not client_secret {
|
|
1123
|
-
continue;
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
self.SUPPORTED_PLATFORMS[platform.value] = {
|
|
1127
|
-
"client_id": client_id,
|
|
1128
|
-
"client_secret": client_secret
|
|
1129
|
-
};
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
impl JacAPIServer.refresh_jwt_token(token: str) -> (str | None) {
|
|
1134
|
-
try {
|
|
1135
|
-
decoded = jwt.decode(
|
|
1136
|
-
token, JWT_SECRET, algorithms=[JWT_ALGORITHM], options={"verify_exp": True}
|
|
1137
|
-
);
|
|
1138
|
-
username = decoded.get('username');
|
|
1139
|
-
|
|
1140
|
-
if not username {
|
|
1141
|
-
return None;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
return JacAPIServer.create_jwt_token(username);
|
|
1145
|
-
} except Exception {
|
|
1146
|
-
return None;
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
impl JacAPIServer.validate_jwt_token(token: str) -> (str | None) {
|
|
1151
|
-
try {
|
|
1152
|
-
decoded = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM]);
|
|
1153
|
-
return decoded['username'];
|
|
1154
|
-
} except Exception {
|
|
1155
|
-
return None;
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
impl JacAPIServer.create_jwt_token(username: str) -> str {
|
|
1160
|
-
now = datetime.now(UTC);
|
|
1161
|
-
payload: dict[(str, Any)] = {
|
|
1162
|
-
'username': username,
|
|
1163
|
-
'exp': (now + timedelta(days=JWT_EXP_DELTA_DAYS)),
|
|
1164
|
-
'iat': now.timestamp()
|
|
1165
|
-
};
|
|
1166
|
-
return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM);
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
impl JacAPIServer.get_sso(platform: str, operation: str) -> (GoogleSSO | None) {
|
|
1170
|
-
if (platform not in self.SUPPORTED_PLATFORMS) {
|
|
1171
|
-
return None;
|
|
1172
|
-
}
|
|
1173
|
-
credentials = self.SUPPORTED_PLATFORMS[platform];
|
|
1174
|
-
redirect_uri = f"{SSO_HOST}/{platform}/{operation}/callback";
|
|
1175
|
-
if (platform == Platforms.GOOGLE.value) {
|
|
1176
|
-
return GoogleSSO(
|
|
1177
|
-
client_id=credentials['client_id'],
|
|
1178
|
-
client_secret=credentials['client_secret'],
|
|
1179
|
-
redirect_uri=redirect_uri,
|
|
1180
|
-
allow_insecure_http=True
|
|
1181
|
-
);
|
|
1182
|
-
}
|
|
1183
|
-
return None;
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
impl JacAPIServer.sso_initiate(
|
|
1187
|
-
platform: str, operation: str
|
|
1188
|
-
) -> (Response | TransportResponse) {
|
|
1189
|
-
import from jaclang.runtimelib.transport { TransportResponse, Meta }
|
|
1190
|
-
if (platform not in [p.value for p in Platforms]) {
|
|
1191
|
-
return TransportResponse.fail(
|
|
1192
|
-
code='INVALID_PLATFORM',
|
|
1193
|
-
message=f"Invalid platform '{platform}'. Supported platforms: {', '.join(
|
|
1194
|
-
[p.value for p in Platforms]
|
|
1195
|
-
)}",
|
|
1196
|
-
meta=Meta(extra={'http_status': 400})
|
|
1197
|
-
);
|
|
1198
|
-
}
|
|
1199
|
-
if (platform not in self.SUPPORTED_PLATFORMS) {
|
|
1200
|
-
return TransportResponse.fail(
|
|
1201
|
-
code='SSO_NOT_CONFIGURED',
|
|
1202
|
-
message=f"SSO for platform '{platform}' is not configured. Please set SSO_{platform.upper()}_CLIENT_ID and SSO_{platform.upper()}_CLIENT_SECRET environment variables.",
|
|
1203
|
-
meta=Meta(extra={'http_status': 501})
|
|
1204
|
-
);
|
|
1205
|
-
}
|
|
1206
|
-
if (operation not in [o.value for o in Operations]) {
|
|
1207
|
-
return TransportResponse.fail(
|
|
1208
|
-
code='INVALID_OPERATION',
|
|
1209
|
-
message=f"Invalid operation '{operation}'. Must be 'login' or 'register'",
|
|
1210
|
-
meta=Meta(extra={'http_status': 400})
|
|
1211
|
-
);
|
|
1212
|
-
}
|
|
1213
|
-
sso = self.get_sso(platform, operation);
|
|
1214
|
-
if not sso {
|
|
1215
|
-
return TransportResponse.fail(
|
|
1216
|
-
code='SSO_INIT_FAILED',
|
|
1217
|
-
message=f"Failed to initialize SSO for platform '{platform}'",
|
|
1218
|
-
meta=Meta(extra={'http_status': 500})
|
|
1219
|
-
);
|
|
1220
|
-
}
|
|
1221
|
-
with sso {
|
|
1222
|
-
return await sso.get_login_redirect();
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
impl JacAPIServer.sso_callback(
|
|
1227
|
-
request: Request, platform: str, operation: str
|
|
1228
|
-
) -> TransportResponse {
|
|
1229
|
-
import from jaclang.runtimelib.transport { TransportResponse, Meta }
|
|
1230
|
-
if (platform not in [p.value for p in Platforms]) {
|
|
1231
|
-
return TransportResponse.fail(
|
|
1232
|
-
code='INVALID_PLATFORM',
|
|
1233
|
-
message=f"Invalid platform '{platform}'. Supported platforms: {', '.join(
|
|
1234
|
-
[p.value for p in Platforms]
|
|
1235
|
-
)}",
|
|
1236
|
-
meta=Meta(extra={'http_status': 400})
|
|
1237
|
-
);
|
|
1238
|
-
}
|
|
1239
|
-
if (platform not in self.SUPPORTED_PLATFORMS) {
|
|
1240
|
-
return TransportResponse.fail(
|
|
1241
|
-
code='SSO_NOT_CONFIGURED',
|
|
1242
|
-
message=f"SSO for platform '{platform}' is not configured. Please set SSO_{platform.upper()}_CLIENT_ID and SSO_{platform.upper()}_CLIENT_SECRET environment variables.",
|
|
1243
|
-
meta=Meta(extra={'http_status': 501})
|
|
1244
|
-
);
|
|
1245
|
-
}
|
|
1246
|
-
if (operation not in [o.value for o in Operations]) {
|
|
1247
|
-
return TransportResponse.fail(
|
|
1248
|
-
code='INVALID_OPERATION',
|
|
1249
|
-
message=f"Invalid operation '{operation}'. Must be 'login' or 'register'",
|
|
1250
|
-
meta=Meta(extra={'http_status': 400})
|
|
1251
|
-
);
|
|
1252
|
-
}
|
|
1253
|
-
sso = self.get_sso(platform, operation);
|
|
1254
|
-
if not sso {
|
|
1255
|
-
return TransportResponse.fail(
|
|
1256
|
-
code='SSO_INIT_FAILED',
|
|
1257
|
-
message=f"Failed to initialize SSO for platform '{platform}'",
|
|
1258
|
-
meta=Meta(extra={'http_status': 500})
|
|
1259
|
-
);
|
|
1260
|
-
}
|
|
1261
|
-
try {
|
|
1262
|
-
with sso {
|
|
1263
|
-
user_info = await sso.verify_and_process(request);
|
|
1264
|
-
email = user_info.email;
|
|
1265
|
-
if not email {
|
|
1266
|
-
return TransportResponse.fail(
|
|
1267
|
-
code='EMAIL_MISSING',
|
|
1268
|
-
message=f"Email not provided by {platform}",
|
|
1269
|
-
meta=Meta(extra={'http_status': 400})
|
|
1270
|
-
);
|
|
1271
|
-
}
|
|
1272
|
-
if (operation == Operations.LOGIN.value) {
|
|
1273
|
-
user = self.user_manager.get_user(email);
|
|
1274
|
-
if not user {
|
|
1275
|
-
return TransportResponse.fail(
|
|
1276
|
-
code='USER_NOT_FOUND',
|
|
1277
|
-
message='User not found. Please register first.',
|
|
1278
|
-
meta=Meta(extra={'http_status': 404})
|
|
1279
|
-
);
|
|
1280
|
-
}
|
|
1281
|
-
token = self.create_jwt_token(email);
|
|
1282
|
-
return TransportResponse.success(
|
|
1283
|
-
data={
|
|
1284
|
-
'message': 'Login successful',
|
|
1285
|
-
'email': email,
|
|
1286
|
-
'token': token,
|
|
1287
|
-
'platform': platform,
|
|
1288
|
-
'user': dict[(str, JsonValue)](user)
|
|
1289
|
-
},
|
|
1290
|
-
meta=Meta(extra={'http_status': 200})
|
|
1291
|
-
);
|
|
1292
|
-
} elif (operation == Operations.REGISTER.value) {
|
|
1293
|
-
existing_user = self.user_manager.get_user(email);
|
|
1294
|
-
if existing_user {
|
|
1295
|
-
return TransportResponse.fail(
|
|
1296
|
-
code='USER_EXISTS',
|
|
1297
|
-
message='User already exists. Please login instead.',
|
|
1298
|
-
meta=Meta(extra={'http_status': 400})
|
|
1299
|
-
);
|
|
1300
|
-
}
|
|
1301
|
-
random_password = generate_random_password();
|
|
1302
|
-
result = self.user_manager.create_user(email, random_password);
|
|
1303
|
-
if ('error' in result) {
|
|
1304
|
-
return TransportResponse.fail(
|
|
1305
|
-
code='USER_CREATION_FAILED',
|
|
1306
|
-
message=result.get('error', 'User creation failed'),
|
|
1307
|
-
meta=Meta(extra={'http_status': 400})
|
|
1308
|
-
);
|
|
1309
|
-
}
|
|
1310
|
-
token = self.create_jwt_token(email);
|
|
1311
|
-
result['token'] = token;
|
|
1312
|
-
result['platform'] = platform;
|
|
1313
|
-
return TransportResponse.success(
|
|
1314
|
-
data=result, meta=Meta(extra={'http_status': 201})
|
|
1315
|
-
);
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
} except Exception as e {
|
|
1319
|
-
return TransportResponse.fail(
|
|
1320
|
-
code='AUTHENTICATION_FAILED',
|
|
1321
|
-
message=f"Authentication failed: {str(e)}",
|
|
1322
|
-
meta=Meta(extra={'http_status': 500})
|
|
1323
|
-
);
|
|
1324
|
-
}
|
|
1325
1112
|
}
|
|
1326
1113
|
|
|
1327
1114
|
impl JacAPIServer.register_sso_endpoints -> None {
|
|
@@ -1329,7 +1116,7 @@ impl JacAPIServer.register_sso_endpoints -> None {
|
|
|
1329
1116
|
JEndPoint(
|
|
1330
1117
|
method=HTTPMethod.GET,
|
|
1331
1118
|
path='/sso/{platform}/{operation}',
|
|
1332
|
-
callback=self.sso_initiate,
|
|
1119
|
+
callback=self.user_manager.sso_initiate,
|
|
1333
1120
|
parameters=[
|
|
1334
1121
|
APIParameter(
|
|
1335
1122
|
name='platform',
|
|
@@ -1358,7 +1145,7 @@ impl JacAPIServer.register_sso_endpoints -> None {
|
|
|
1358
1145
|
JEndPoint(
|
|
1359
1146
|
method=HTTPMethod.GET,
|
|
1360
1147
|
path='/sso/{platform}/{operation}/callback',
|
|
1361
|
-
callback=self.sso_callback,
|
|
1148
|
+
callback=self.user_manager.sso_callback,
|
|
1362
1149
|
parameters=[
|
|
1363
1150
|
APIParameter(
|
|
1364
1151
|
name='platform',
|
|
@@ -1455,7 +1242,7 @@ impl JacAPIServer.register_dynamic_walker_endpoint -> None {
|
|
|
1455
1242
|
) {
|
|
1456
1243
|
token = Authorization[7:];
|
|
1457
1244
|
}
|
|
1458
|
-
username = self.validate_jwt_token(token) if token else None;
|
|
1245
|
+
username = self.user_manager.validate_jwt_token(token) if token else None;
|
|
1459
1246
|
if not username {
|
|
1460
1247
|
return TransportResponse.fail(
|
|
1461
1248
|
code='UNAUTHORIZED',
|
|
@@ -1604,7 +1391,7 @@ impl JacAPIServer.register_dynamic_function_endpoint -> None {
|
|
|
1604
1391
|
) {
|
|
1605
1392
|
token = Authorization[7:];
|
|
1606
1393
|
}
|
|
1607
|
-
username = self.validate_jwt_token(token) if token else None;
|
|
1394
|
+
username = self.user_manager.validate_jwt_token(token) if token else None;
|
|
1608
1395
|
if not username {
|
|
1609
1396
|
return TransportResponse.fail(
|
|
1610
1397
|
code='UNAUTHORIZED',
|