Skip to content

Commit 63949c3

Browse files
Merge pull request #364 from supertokens/multitenancy-router
feat: Router changes to handle tenant id
2 parents 53b7710 + 177b31a commit 63949c3

File tree

24 files changed

+252
-60
lines changed

24 files changed

+252
-60
lines changed

supertokens_python/recipe/dashboard/recipe.py

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@
1313
# under the License.
1414
from __future__ import annotations
1515

16+
import re
1617
from os import environ
1718
from typing import TYPE_CHECKING, Awaitable, Callable, List, Optional, Union
1819

1920
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
2122

2223
from .api import (
2324
api_key_protector,
@@ -45,6 +46,7 @@
4546
from .exceptions import SuperTokensDashboardError
4647
from .interfaces import APIInterface, APIOptions
4748
from .recipe_implementation import RecipeImplementation
49+
from ..multitenancy.constants import DEFAULT_TENANT_ID
4850

4951
if TYPE_CHECKING:
5052
from supertokens_python.framework.request import BaseRequest
@@ -126,6 +128,7 @@ def get_apis_handled(self) -> List[APIHandled]:
126128
async def handle_api_request(
127129
self,
128130
request_id: str,
131+
tenant_id: Optional[str],
129132
request: BaseRequest,
130133
path: NormalisedURLPath,
131134
method: str,
@@ -245,15 +248,56 @@ def reset():
245248

246249
def return_api_id_if_can_handle_request(
247250
self, path: NormalisedURLPath, method: str
248-
) -> Union[str, None]:
251+
) -> Union[ApiIdWithTenantId, None]:
249252
dashboard_bundle_path = self.app_info.api_base_path.append(
250253
NormalisedURLPath(DASHBOARD_API)
251254
)
252255

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)
255298

256299
if path.startswith(dashboard_bundle_path):
257-
return DASHBOARD_API
300+
return ApiIdWithTenantId(DASHBOARD_API, DEFAULT_TENANT_ID)
258301

302+
# tenantId is not supported for bundlePath, so not matching for it
259303
return None

supertokens_python/recipe/dashboard/utils.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
if TYPE_CHECKING:
1919
from supertokens_python.framework.request import BaseRequest
20-
from ...supertokens import AppInfo
2120

2221
from supertokens_python.recipe.emailpassword import EmailPasswordRecipe
2322
from supertokens_python.recipe.emailpassword.asyncio import (
@@ -195,10 +194,8 @@ def validate_and_normalise_user_input(
195194
)
196195

197196

198-
def is_api_path(path: NormalisedURLPath, app_info: AppInfo) -> bool:
199-
dashboard_recipe_base_path = app_info.api_base_path.append(
200-
NormalisedURLPath(DASHBOARD_API)
201-
)
197+
def is_api_path(path: NormalisedURLPath, base_path: NormalisedURLPath) -> bool:
198+
dashboard_recipe_base_path = base_path.append(NormalisedURLPath(DASHBOARD_API))
202199

203200
if not path.startswith(dashboard_recipe_base_path):
204201
return False

supertokens_python/recipe/emailpassword/recipe.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from __future__ import annotations
1515

1616
from os import environ
17-
from typing import TYPE_CHECKING, Any, Dict, List, Union
17+
from typing import TYPE_CHECKING, Any, Dict, List, Union, Optional
1818

1919
from supertokens_python.ingredients.emaildelivery import EmailDeliveryIngredient
2020
from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig
@@ -170,6 +170,7 @@ def get_apis_handled(self) -> List[APIHandled]:
170170
async def handle_api_request(
171171
self,
172172
request_id: str,
173+
tenant_id: Optional[str],
173174
request: BaseRequest,
174175
path: NormalisedURLPath,
175176
method: str,

supertokens_python/recipe/emailverification/recipe.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ def get_apis_handled(self) -> List[APIHandled]:
165165
async def handle_api_request(
166166
self,
167167
request_id: str,
168+
tenant_id: Optional[str],
168169
request: BaseRequest,
169170
path: NormalisedURLPath,
170171
method: str,

supertokens_python/recipe/jwt/recipe.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from __future__ import annotations
1515

1616
from os import environ
17-
from typing import TYPE_CHECKING, List, Union
17+
from typing import TYPE_CHECKING, List, Union, Optional
1818

1919
from supertokens_python.querier import Querier
2020
from supertokens_python.recipe.jwt.api.implementation import APIImplementation
@@ -80,6 +80,7 @@ def get_apis_handled(self) -> List[APIHandled]:
8080
async def handle_api_request(
8181
self,
8282
request_id: str,
83+
tenant_id: Optional[str],
8384
request: BaseRequest,
8485
path: NormalisedURLPath,
8586
method: str,

supertokens_python/recipe/multitenancy/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
1414
LOGIN_METHODS = "/loginmethods"
15-
DEFAULT_TENANT_ID = "defaultTenantId"
15+
DEFAULT_TENANT_ID = "public"

supertokens_python/recipe/multitenancy/recipe.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ def get_apis_handled(self) -> List[APIHandled]:
122122
async def handle_api_request(
123123
self,
124124
request_id: str,
125+
tenant_id: Optional[str],
125126
request: BaseRequest,
126127
path: NormalisedURLPath,
127128
method: str,

supertokens_python/recipe/openid/recipe.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from __future__ import annotations
1515

1616
from os import environ
17-
from typing import TYPE_CHECKING, List, Union
17+
from typing import TYPE_CHECKING, List, Union, Optional
1818

1919
from supertokens_python.querier import Querier
2020
from supertokens_python.recipe.jwt import JWTRecipe
@@ -89,6 +89,7 @@ def get_apis_handled(self) -> List[APIHandled]:
8989
async def handle_api_request(
9090
self,
9191
request_id: str,
92+
tenant_id: Optional[str],
9293
request: BaseRequest,
9394
path: NormalisedURLPath,
9495
method: str,
@@ -107,7 +108,7 @@ async def handle_api_request(
107108
self.api_implementation, options
108109
)
109110
return await self.jwt_recipe.handle_api_request(
110-
request_id, request, path, method, response
111+
request_id, tenant_id, request, path, method, response
111112
)
112113

113114
async def handle_error(

supertokens_python/recipe/passwordless/recipe.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from __future__ import annotations
1515

1616
from os import environ
17-
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Union
17+
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Union, Optional
1818

1919
from supertokens_python.ingredients.emaildelivery import EmailDeliveryIngredient
2020
from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig
@@ -185,6 +185,7 @@ def get_apis_handled(self) -> List[APIHandled]:
185185
async def handle_api_request(
186186
self,
187187
request_id: str,
188+
tenant_id: Optional[str],
188189
request: BaseRequest,
189190
path: NormalisedURLPath,
190191
method: str,

supertokens_python/recipe/session/recipe.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ def get_apis_handled(self) -> List[APIHandled]:
187187
async def handle_api_request(
188188
self,
189189
request_id: str,
190+
tenant_id: Optional[str],
190191
request: BaseRequest,
191192
path: NormalisedURLPath,
192193
method: str,
@@ -215,7 +216,7 @@ async def handle_api_request(
215216
),
216217
)
217218
return await self.openid_recipe.handle_api_request(
218-
request_id, request, path, method, response
219+
request_id, tenant_id, request, path, method, response
219220
)
220221

221222
async def handle_error(

0 commit comments

Comments
 (0)