Skip to content

Commit 772ef54

Browse files
authored
🎨 web-api: enhances reset-password workflow (ITISFoundation#7336)
1 parent 45ae7a5 commit 772ef54

File tree

22 files changed

+523
-313
lines changed

22 files changed

+523
-313
lines changed

‎api/specs/web-server/_auth.py‎

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@
4747
operation_id="request_product_account",
4848
status_code=status.HTTP_204_NO_CONTENT,
4949
)
50-
async def request_product_account(_body: AccountRequestInfo):
51-
...
50+
async def request_product_account(_body: AccountRequestInfo): ...
5251

5352

5453
@router.post(
@@ -75,8 +74,7 @@ async def register(_body: RegisterBody):
7574
status_code=status.HTTP_200_OK,
7675
responses={status.HTTP_409_CONFLICT: {"model": EnvelopedError}},
7776
)
78-
async def unregister_account(_body: UnregisterCheck):
79-
...
77+
async def unregister_account(_body: UnregisterCheck): ...
8078

8179

8280
@router.post(
@@ -171,26 +169,24 @@ async def check_auth():
171169
@router.post(
172170
"/auth/reset-password",
173171
response_model=Envelope[Log],
174-
operation_id="auth_reset_password",
172+
operation_id="initiate_reset_password",
175173
responses={status.HTTP_503_SERVICE_UNAVAILABLE: {"model": EnvelopedError}},
176174
)
177-
async def reset_password(_body: ResetPasswordBody):
178-
"""a non logged-in user requests a password reset"""
175+
async def initiate_reset_password(_body: ResetPasswordBody): ...
179176

180177

181178
@router.post(
182179
"/auth/reset-password/{code}",
183180
response_model=Envelope[Log],
184-
operation_id="auth_reset_password_allowed",
181+
operation_id="complete_reset_password",
185182
responses={
186183
status.HTTP_401_UNAUTHORIZED: {
187184
"model": EnvelopedError,
188-
"description": "unauthorized reset due to invalid token code",
185+
"description": "Invalid token code",
189186
}
190187
},
191188
)
192-
async def reset_password_allowed(code: str, _body: ResetPasswordConfirmation):
193-
"""changes password using a token code without being logged in"""
189+
async def complete_reset_password(code: str, _body: ResetPasswordConfirmation): ...
194190

195191

196192
@router.post(
@@ -268,5 +264,4 @@ async def email_confirmation(code: str):
268264
status_code=status.HTTP_200_OK,
269265
responses={status.HTTP_200_OK: {"content": {"image/png": {}}}},
270266
)
271-
async def request_captcha():
272-
...
267+
async def request_captcha(): ...

‎packages/models-library/src/models_library/rest_error.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from dataclasses import dataclass
22
from typing import Annotated
33

4+
from common_library.basic_types import DEFAULT_FACTORY
45
from models_library.generics import Envelope
56
from pydantic import BaseModel, ConfigDict, Field
67

@@ -81,11 +82,11 @@ class ErrorGet(BaseModel):
8182
errors: Annotated[
8283
list[ErrorItemType],
8384
Field(deprecated=True, default_factory=list, json_schema_extra={"default": []}),
84-
]
85+
] = DEFAULT_FACTORY
8586
logs: Annotated[
8687
list[LogMessageType],
8788
Field(deprecated=True, default_factory=list, json_schema_extra={"default": []}),
88-
]
89+
] = DEFAULT_FACTORY
8990

9091
model_config = ConfigDict(
9192
populate_by_name=True,

‎services/static-webserver/client/source/class/osparc/data/Resources.js‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1490,7 +1490,10 @@ qx.Class.define("osparc.data.Resources", {
14901490
}
14911491

14921492
if ([404, 503].includes(status)) {
1493-
message += "<br>Please try again later and/or contact support";
1493+
// NOTE: a temporary solution to avoid duplicate information
1494+
if (!message.includes("contact") && !message.includes("try")) {
1495+
message += "<br>Please try again later and/or contact support";
1496+
}
14941497
}
14951498
const err = Error(message ? message : `Error while trying to fetch ${endpoint} ${resource}`);
14961499
if (status) {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.61.0
1+
0.61.1

‎services/web/server/setup.cfg‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.61.0
2+
current_version = 0.61.1
33
commit = True
44
message = services/webserver api version: {current_version} → {new_version}
55
tag = False

‎services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml‎

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ openapi: 3.1.0
22
info:
33
title: simcore-service-webserver
44
description: Main service with an interface (http-API & websockets) to the web front-end
5-
version: 0.61.0
5+
version: 0.61.1
66
servers:
77
- url: ''
88
description: webserver
@@ -253,9 +253,8 @@ paths:
253253
post:
254254
tags:
255255
- auth
256-
summary: Reset Password
257-
description: a non logged-in user requests a password reset
258-
operationId: auth_reset_password
256+
summary: Initiate Reset Password
257+
operationId: initiate_reset_password
259258
requestBody:
260259
content:
261260
application/json:
@@ -279,9 +278,8 @@ paths:
279278
post:
280279
tags:
281280
- auth
282-
summary: Reset Password Allowed
283-
description: changes password using a token code without being logged in
284-
operationId: auth_reset_password_allowed
281+
summary: Complete Reset Password
282+
operationId: complete_reset_password
285283
parameters:
286284
- name: code
287285
in: path
@@ -303,7 +301,7 @@ paths:
303301
schema:
304302
$ref: '#/components/schemas/Envelope_Log_'
305303
'401':
306-
description: unauthorized reset due to invalid token code
304+
description: Invalid token code
307305
content:
308306
application/json:
309307
schema:
@@ -14225,6 +14223,7 @@ components:
1422514223
properties:
1422614224
email:
1422714225
type: string
14226+
format: email
1422814227
title: Email
1422914228
additionalProperties: false
1423014229
type: object
@@ -15404,7 +15403,6 @@ components:
1540415403
- change_email_email.jinja2
1540515404
- new_2fa_code.jinja2
1540615405
- registration_email.jinja2
15407-
- reset_password_email_failed.jinja2
1540815406
- reset_password_email.jinja2
1540915407
- service_submission.jinja2
1541015408
title: Template Name

‎services/web/server/src/simcore_service_webserver/email/_handlers.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ class TestEmail(BaseModel):
3131
"change_email_email.jinja2",
3232
"new_2fa_code.jinja2",
3333
"registration_email.jinja2",
34-
"reset_password_email_failed.jinja2",
3534
"reset_password_email.jinja2",
3635
"service_submission.jinja2",
3736
] = "registration_email.jinja2"

‎services/web/server/src/simcore_service_webserver/login/_confirmation.py‎

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
1-
""" Confirmation codes/tokens tools
1+
"""Confirmation codes/tokens tools
22
3-
Codes are inserted in confirmation tables and they are associated to a user and an action
4-
Used to validate some action (e.g. register, invitation, etc)
5-
Codes can be used one time
6-
Codes have expiration date (duration time is configurable)
3+
Codes are inserted in confirmation tables and they are associated to a user and an action
4+
Used to validate some action (e.g. register, invitation, etc)
5+
Codes can be used one time
6+
Codes have expiration date (duration time is configurable)
77
"""
88

99
import logging
1010
from datetime import datetime
1111
from urllib.parse import quote
1212

1313
from aiohttp import web
14+
from models_library.users import UserID
1415
from yarl import URL
1516

16-
from ..db.models import ConfirmationAction
1717
from .settings import LoginOptions
18-
from .storage import AsyncpgStorage, ConfirmationTokenDict
18+
from .storage import ActionLiteralStr, AsyncpgStorage, ConfirmationTokenDict
1919

2020
log = logging.getLogger(__name__)
2121

@@ -61,22 +61,29 @@ def get_expiration_date(
6161
return confirmation["created_at"] + lifetime
6262

6363

64-
async def is_confirmation_allowed(
65-
cfg: LoginOptions, db: AsyncpgStorage, user, action: ConfirmationAction
66-
):
64+
async def get_or_create_confirmation(
65+
cfg: LoginOptions,
66+
db: AsyncpgStorage,
67+
user_id: UserID,
68+
action: ActionLiteralStr,
69+
) -> ConfirmationTokenDict:
70+
6771
confirmation: ConfirmationTokenDict | None = await db.get_confirmation(
68-
{"user": user, "action": action}
72+
{"user": {"id": user_id}, "action": action}
6973
)
70-
if not confirmation:
71-
return True
72-
if is_confirmation_expired(cfg, confirmation):
74+
75+
if confirmation is not None and is_confirmation_expired(cfg, confirmation):
7376
await db.delete_confirmation(confirmation)
7477
log.warning(
7578
"Used expired token [%s]. Deleted from confirmations table.",
7679
confirmation,
7780
)
78-
return True
79-
return False
81+
confirmation = None
82+
83+
if confirmation is None:
84+
confirmation = await db.create_confirmation(user_id, action=action)
85+
86+
return confirmation
8087

8188

8289
def is_confirmation_expired(cfg: LoginOptions, confirmation: ConfirmationTokenDict):

0 commit comments

Comments
 (0)