Skip to content

Commit 653b60a

Browse files
authored
♻️ webserver: Refactor login domain for CSR compliance and future confirmation logic (#7417)
1 parent d8b3828 commit 653b60a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+897
-517
lines changed

api/specs/web-server/_auth.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,29 @@
1515
from models_library.rest_error import EnvelopedError, Log
1616
from pydantic import BaseModel, Field, confloat
1717
from simcore_service_webserver._meta import API_VTAG
18-
from simcore_service_webserver.login._2fa_handlers import Resend2faBody
19-
from simcore_service_webserver.login._auth_handlers import (
18+
from simcore_service_webserver.login._controller.rest.auth import (
2019
LoginBody,
2120
LoginNextPage,
2221
LoginTwoFactorAuthBody,
2322
LogoutBody,
2423
)
25-
from simcore_service_webserver.login.handlers_change import (
24+
from simcore_service_webserver.login._controller.rest.change import (
2625
ChangeEmailBody,
2726
ChangePasswordBody,
2827
ResetPasswordBody,
2928
)
30-
from simcore_service_webserver.login.handlers_confirmation import (
29+
from simcore_service_webserver.login._controller.rest.confirmation import (
3130
PhoneConfirmationBody,
3231
ResetPasswordConfirmation,
3332
)
34-
from simcore_service_webserver.login.handlers_registration import (
33+
from simcore_service_webserver.login._controller.rest.registration import (
3534
InvitationCheck,
3635
InvitationInfo,
3736
RegisterBody,
3837
RegisterPhoneBody,
3938
RegisterPhoneNextPage,
4039
)
40+
from simcore_service_webserver.login._controller.rest.twofa import Resend2faBody
4141

4242
router = APIRouter(prefix=f"/{API_VTAG}", tags=["auth"])
4343

packages/pytest-simcore/src/pytest_simcore/helpers/webserver_login.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111
from simcore_service_webserver.db.models import UserRole, UserStatus
1212
from simcore_service_webserver.groups.api import auto_add_user_to_product_group
1313
from simcore_service_webserver.login._constants import MSG_LOGGED_IN
14-
from simcore_service_webserver.login._registration import create_invitation_token
15-
from simcore_service_webserver.login.storage import AsyncpgStorage, get_plugin_storage
14+
from simcore_service_webserver.login._invitations_service import create_invitation_token
15+
from simcore_service_webserver.login._login_repository_legacy import (
16+
AsyncpgStorage,
17+
get_plugin_storage,
18+
)
1619
from simcore_service_webserver.products.products_service import list_products
1720
from simcore_service_webserver.security.api import clean_auth_policy_cache
1821
from yarl import URL

services/web/server/src/simcore_service_webserver/garbage_collector/_tasks_users.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Scheduled tasks addressing users
2+
Scheduled tasks addressing users
33
44
"""
55

@@ -14,7 +14,7 @@
1414
from tenacity.before_sleep import before_sleep_log
1515
from tenacity.wait import wait_exponential
1616

17-
from ..login.utils import notify_user_logout
17+
from ..login import login_service
1818
from ..security.api import clean_auth_policy_cache
1919
from ..users.api import update_expired_users
2020

@@ -39,7 +39,7 @@ async def notify_user_logout_all_sessions(
3939
get_log_record_extra(user_id=user_id),
4040
):
4141
try:
42-
await notify_user_logout(app, user_id, client_session_id=None)
42+
await login_service.notify_user_logout(app, user_id, client_session_id=None)
4343
except Exception: # pylint: disable=broad-except
4444
_logger.warning(
4545
"Ignored error while notifying logout for %s",

services/web/server/src/simcore_service_webserver/login/_auth_api.py renamed to services/web/server/src/simcore_service_webserver/login/_auth_service.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
from ..groups.api import is_user_by_email_in_group
1111
from ..products.models import Product
1212
from ..security.api import check_password, encrypt_password
13+
from . import _login_service
1314
from ._constants import MSG_UNKNOWN_EMAIL, MSG_WRONG_PASSWORD
14-
from .storage import AsyncpgStorage, get_plugin_storage
15-
from .utils import validate_user_status
15+
from ._login_repository_legacy import AsyncpgStorage, get_plugin_storage
1616

1717

1818
async def get_user_by_email(app: web.Application, *, email: str) -> dict[str, Any]:
@@ -55,7 +55,7 @@ async def check_authorized_user_credentials_or_raise(
5555
reason=MSG_UNKNOWN_EMAIL, content_type=MIMETYPE_APPLICATION_JSON
5656
)
5757

58-
validate_user_status(user=user, support_email=product.support_email)
58+
_login_service.validate_user_status(user=user, support_email=product.support_email)
5959

6060
if not check_password(password, user["password_hash"]):
6161
raise web.HTTPUnauthorized(

services/web/server/src/simcore_service_webserver/login/_confirmation.py renamed to services/web/server/src/simcore_service_webserver/login/_confirmation_service.py

Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,48 @@
1414
from models_library.users import UserID
1515
from yarl import URL
1616

17+
from ._login_repository_legacy import (
18+
ActionLiteralStr,
19+
AsyncpgStorage,
20+
ConfirmationTokenDict,
21+
)
1722
from .settings import LoginOptions
18-
from .storage import ActionLiteralStr, AsyncpgStorage, ConfirmationTokenDict
1923

20-
log = logging.getLogger(__name__)
24+
_logger = logging.getLogger(__name__)
2125

2226

23-
async def validate_confirmation_code(
24-
code: str, db: AsyncpgStorage, cfg: LoginOptions
25-
) -> ConfirmationTokenDict | None:
26-
"""
27-
Returns None if validation fails
28-
"""
29-
assert not code.startswith("***"), "forgot .get_secret_value()??" # nosec
27+
async def get_or_create_confirmation_without_data(
28+
cfg: LoginOptions,
29+
db: AsyncpgStorage,
30+
user_id: UserID,
31+
action: ActionLiteralStr,
32+
) -> ConfirmationTokenDict:
3033

3134
confirmation: ConfirmationTokenDict | None = await db.get_confirmation(
32-
{"code": code}
35+
{"user": {"id": user_id}, "action": action}
3336
)
34-
if confirmation and is_confirmation_expired(cfg, confirmation):
37+
38+
if confirmation is not None and is_confirmation_expired(cfg, confirmation):
3539
await db.delete_confirmation(confirmation)
36-
log.warning(
40+
_logger.warning(
3741
"Used expired token [%s]. Deleted from confirmations table.",
3842
confirmation,
3943
)
40-
return None
44+
confirmation = None
45+
46+
if confirmation is None:
47+
confirmation = await db.create_confirmation(user_id, action=action)
48+
4149
return confirmation
4250

4351

52+
def get_expiration_date(
53+
cfg: LoginOptions, confirmation: ConfirmationTokenDict
54+
) -> datetime:
55+
lifetime = cfg.get_confirmation_lifetime(confirmation["action"])
56+
return confirmation["created_at"] + lifetime
57+
58+
4459
def _url_for_confirmation(app: web.Application, code: str) -> URL:
4560
# NOTE: this is in a query parameter, and can contain ? for example.
4661
safe_code = quote(code, safe="")
@@ -54,39 +69,28 @@ def make_confirmation_link(
5469
return f"{request.scheme}://{request.host}{link}"
5570

5671

57-
def get_expiration_date(
58-
cfg: LoginOptions, confirmation: ConfirmationTokenDict
59-
) -> datetime:
72+
def is_confirmation_expired(cfg: LoginOptions, confirmation: ConfirmationTokenDict):
73+
age = datetime.utcnow() - confirmation["created_at"]
6074
lifetime = cfg.get_confirmation_lifetime(confirmation["action"])
61-
return confirmation["created_at"] + lifetime
75+
return age > lifetime
6276

6377

64-
async def get_or_create_confirmation(
65-
cfg: LoginOptions,
66-
db: AsyncpgStorage,
67-
user_id: UserID,
68-
action: ActionLiteralStr,
69-
) -> ConfirmationTokenDict:
78+
async def validate_confirmation_code(
79+
code: str, db: AsyncpgStorage, cfg: LoginOptions
80+
) -> ConfirmationTokenDict | None:
81+
"""
82+
Returns None if validation fails
83+
"""
84+
assert not code.startswith("***"), "forgot .get_secret_value()??" # nosec
7085

7186
confirmation: ConfirmationTokenDict | None = await db.get_confirmation(
72-
{"user": {"id": user_id}, "action": action}
87+
{"code": code}
7388
)
74-
75-
if confirmation is not None and is_confirmation_expired(cfg, confirmation):
89+
if confirmation and is_confirmation_expired(cfg, confirmation):
7690
await db.delete_confirmation(confirmation)
77-
log.warning(
91+
_logger.warning(
7892
"Used expired token [%s]. Deleted from confirmations table.",
7993
confirmation,
8094
)
81-
confirmation = None
82-
83-
if confirmation is None:
84-
confirmation = await db.create_confirmation(user_id, action=action)
85-
95+
return None
8696
return confirmation
87-
88-
89-
def is_confirmation_expired(cfg: LoginOptions, confirmation: ConfirmationTokenDict):
90-
age = datetime.utcnow() - confirmation["created_at"]
91-
lifetime = cfg.get_confirmation_lifetime(confirmation["action"])
92-
return age > lifetime

services/web/server/src/simcore_service_webserver/login/_controller/__init__.py

Whitespace-only changes.

services/web/server/src/simcore_service_webserver/login/_controller/rest/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)