|
13 | 13 | # under the License. |
14 | 14 | from __future__ import annotations |
15 | 15 |
|
| 16 | +import re |
16 | 17 | from os import environ |
17 | 18 | from typing import TYPE_CHECKING, Awaitable, Callable, List, Optional, Union |
18 | 19 |
|
19 | 20 | from supertokens_python.normalised_url_path import NormalisedURLPath |
20 | | -from supertokens_python.recipe_module import APIHandled, RecipeModule |
| 21 | +from supertokens_python.recipe_module import APIHandled, RecipeModule, ApiIdWithTenantId |
21 | 22 |
|
22 | 23 | from .api import ( |
23 | 24 | api_key_protector, |
|
45 | 46 | from .exceptions import SuperTokensDashboardError |
46 | 47 | from .interfaces import APIInterface, APIOptions |
47 | 48 | from .recipe_implementation import RecipeImplementation |
| 49 | +from ..multitenancy.constants import DEFAULT_TENANT_ID |
48 | 50 |
|
49 | 51 | if TYPE_CHECKING: |
50 | 52 | from supertokens_python.framework.request import BaseRequest |
@@ -126,6 +128,7 @@ def get_apis_handled(self) -> List[APIHandled]: |
126 | 128 | async def handle_api_request( |
127 | 129 | self, |
128 | 130 | request_id: str, |
| 131 | + tenant_id: Optional[str], |
129 | 132 | request: BaseRequest, |
130 | 133 | path: NormalisedURLPath, |
131 | 134 | method: str, |
@@ -245,15 +248,56 @@ def reset(): |
245 | 248 |
|
246 | 249 | def return_api_id_if_can_handle_request( |
247 | 250 | self, path: NormalisedURLPath, method: str |
248 | | - ) -> Union[str, None]: |
| 251 | + ) -> Union[ApiIdWithTenantId, None]: |
249 | 252 | dashboard_bundle_path = self.app_info.api_base_path.append( |
250 | 253 | NormalisedURLPath(DASHBOARD_API) |
251 | 254 | ) |
252 | 255 |
|
253 | | - if is_api_path(path, self.app_info): |
254 | | - return get_api_if_matched(path, method) |
| 256 | + base_path_str = self.app_info.api_base_path.get_as_string_dangerous() |
| 257 | + path_str = path.get_as_string_dangerous() |
| 258 | + regex = rf"^{base_path_str}(?:/([a-zA-Z0-9-]+))?(/.*)$" |
| 259 | + # some examples against for above regex: |
| 260 | + # books => match = None |
| 261 | + # public/books => match = None |
| 262 | + # /books => match.group(1) = None, match.group(2) = /dashboard |
| 263 | + # /public/books => match.group(1) = 'public', match.group(2) = '/books' |
| 264 | + # /public/book/1 => match.group(1) = 'public', match.group(2) = '/book/1' |
| 265 | + |
| 266 | + match = re.match(regex, path_str) |
| 267 | + match_group_1 = match.group(1) if match is not None else None |
| 268 | + match_group_2 = match.group(2) if match is not None else None |
| 269 | + |
| 270 | + tenant_id: str = DEFAULT_TENANT_ID |
| 271 | + remaining_path: Optional[NormalisedURLPath] = None |
| 272 | + |
| 273 | + if ( |
| 274 | + match is not None |
| 275 | + and isinstance(match_group_1, str) |
| 276 | + and isinstance(match_group_2, str) |
| 277 | + ): |
| 278 | + tenant_id = match_group_1 |
| 279 | + remaining_path = NormalisedURLPath(match_group_2) |
| 280 | + |
| 281 | + if is_api_path(path, self.app_info.api_base_path) or ( |
| 282 | + remaining_path is not None |
| 283 | + and is_api_path( |
| 284 | + path, |
| 285 | + self.app_info.api_base_path.append(NormalisedURLPath(f"/{tenant_id}")), |
| 286 | + ) |
| 287 | + ): |
| 288 | + # check remainingPath first as path that contains tenantId might match as well |
| 289 | + # since getApiIdIfMatched uses endsWith to match |
| 290 | + if remaining_path is not None: |
| 291 | + id_ = get_api_if_matched(remaining_path, method) |
| 292 | + if id_ is not None: |
| 293 | + return ApiIdWithTenantId(id_, tenant_id) |
| 294 | + |
| 295 | + id_ = get_api_if_matched(path, method) |
| 296 | + if id_ is not None: |
| 297 | + return ApiIdWithTenantId(id_, DEFAULT_TENANT_ID) |
255 | 298 |
|
256 | 299 | if path.startswith(dashboard_bundle_path): |
257 | | - return DASHBOARD_API |
| 300 | + return ApiIdWithTenantId(DASHBOARD_API, DEFAULT_TENANT_ID) |
258 | 301 |
|
| 302 | + # tenantId is not supported for bundlePath, so not matching for it |
259 | 303 | return None |
0 commit comments