Skip to content

Commit

Permalink
♻️ Is3318/refactoring websever.login plugin (2/3) (#3590)
Browse files Browse the repository at this point in the history
  • Loading branch information
pcrespov authored Dec 8, 2022
1 parent 0f04727 commit 5d22707
Show file tree
Hide file tree
Showing 46 changed files with 1,937 additions and 1,561 deletions.
2 changes: 1 addition & 1 deletion api/specs/webserver/openapi-auth.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ paths:
summary: user enters 2 Factor Authentication code when login in
tags:
- authentication
operationId: auth_validate_2fa_login
operationId: auth_login_2fa
requestBody:
content:
application/json:
Expand Down
2 changes: 1 addition & 1 deletion api/specs/webserver/openapi.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.0.0
info:
title: "osparc-simcore web API"
version: 0.12.0
version: 0.12.2
description: "API designed for the front-end app"
contact:
name: IT'IS Foundation
Expand Down
107 changes: 26 additions & 81 deletions api/specs/webserver/scripts/openapi_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,22 @@
from _common import Error, Log
from fastapi import FastAPI, status
from models_library.generics import Envelope
from pydantic import BaseModel, EmailStr, Field, SecretStr, confloat
from pydantic import BaseModel, Field, confloat
from simcore_service_webserver.login.api_keys_handlers import ApiKeyCreate, ApiKeyGet
from simcore_service_webserver.login.handlers import Login2faBody, LoginBody, LogoutBody
from simcore_service_webserver.login.handlers_change import (
ChangeEmailBody,
ChangePasswordBody,
ResetPasswordBody,
)
from simcore_service_webserver.login.handlers_confirmation import (
PhoneConfirmationBody,
ResetPasswordConfirmation,
)
from simcore_service_webserver.login.handlers_registration import (
RegisterBody,
RegisterPhoneBody,
)

app = FastAPI(redoc_url=None)

Expand All @@ -23,103 +37,53 @@
]


class RegistrationCreate(BaseModel):
email: EmailStr
password: SecretStr
confirm: Optional[SecretStr] = Field(None, description="Password confirmation")
invitation: Optional[str] = Field(None, description="Invitation code")

class Config:
schema_extra = {
"examples": [
{
"email": "[email protected]",
"password": "my secret",
"confirm": "my secret",
"invitation": "33c451d4-17b7-4e65-9880-694559b8ffc2",
}
]
}


@app.post(
"/auth/register",
response_model=Envelope[Log],
tags=TAGS,
operation_id="auth_register",
)
async def register(registration: RegistrationCreate):
async def register(registration: RegisterBody):
"""User registration"""


class Verify2FAPhone(BaseModel):
email: EmailStr
phone: str = Field(
..., description="Phone number E.164, needed on the deployments with 2FA"
)


@app.post(
"/auth/verify-phone-number",
response_model=Envelope[Log],
tags=TAGS,
operation_id="auth_verify_2fa_phone",
)
async def register_phone(registration: Verify2FAPhone):
async def register_phone(registration: RegisterPhoneBody):
"""user tries to verify phone number for 2 Factor Authentication when registering"""


class Validate2FAPhone(BaseModel):
email: str
phone: str = Field(
..., description="Phone number E.164, needed on the deployments with 2FA"
)
code: str


@app.post(
"/auth/validate-code-register",
response_model=Envelope[Log],
tags=TAGS,
operation_id="auth_validate_2fa_register",
)
async def phone_confirmation(confirmation: Validate2FAPhone):
async def phone_confirmation(confirmation: PhoneConfirmationBody):
"""user enters 2 Factor Authentication code when registering"""


class LoginForm(BaseModel):
email: Optional[str] = None
password: Optional[str] = None


class Login2FAForm(BaseModel):
email: str
code: str


class LogoutRequest(BaseModel):
client_session_id: Optional[str] = Field(
None, example="5ac57685-c40f-448f-8711-70be1936fd63"
)


@app.post(
"/auth/login",
response_model=Envelope[Log],
tags=TAGS,
operation_id="auth_login",
)
async def login(authentication: LoginForm):
async def login(authentication: LoginBody):
"""user logs in"""


@app.post(
"/auth/validate-code-login",
response_model=Envelope[Log],
tags=TAGS,
operation_id="auth_validate_2fa_login",
operation_id="auth_login_2fa",
)
async def login_2fa(authentication: Login2FAForm):
async def login_2fa(authentication: Login2faBody):
"""user enters 2 Factor Authentication code when login in"""


Expand All @@ -129,30 +93,21 @@ async def login_2fa(authentication: Login2FAForm):
tags=TAGS,
operation_id="auth_logout",
)
async def logout(data: LogoutRequest):
async def logout(data: LogoutBody):
"""user logout"""


class ResetPasswordRequest(BaseModel):
email: str


@app.post(
"/auth/reset-password",
response_model=Envelope[Log],
tags=TAGS,
operation_id="auth_reset_password",
responses={status.HTTP_503_SERVICE_UNAVAILABLE: {"model": Envelope[Error]}},
)
async def reset_password(data: ResetPasswordRequest):
async def reset_password(data: ResetPasswordBody):
"""a non logged-in user requests a password reset"""


class ResetPasswordForm(BaseModel):
password: str
confirm: str


@app.post(
"/auth/reset-password/{code}",
response_model=Envelope[Log],
Expand All @@ -165,14 +120,10 @@ class ResetPasswordForm(BaseModel):
}
},
)
async def reset_password_allowed(code: str, data: ResetPasswordForm):
async def reset_password_allowed(code: str, data: ResetPasswordConfirmation):
"""changes password using a token code without being logged in"""


class ChangeEmailForm(BaseModel):
email: str


@app.post(
"/auth/change-email",
response_model=Envelope[Log],
Expand All @@ -189,16 +140,10 @@ class ChangeEmailForm(BaseModel):
},
},
)
async def change_email(data: ChangeEmailForm):
async def change_email(data: ChangeEmailBody):
"""logged in user changes email"""


class ChangePasswordForm(BaseModel):
current: str
new: str
confirm: str


class PasswordCheckSchema(BaseModel):
strength: confloat(ge=0.0, le=1.0) = Field( # type: ignore
...,
Expand Down Expand Up @@ -230,7 +175,7 @@ class PasswordCheckSchema(BaseModel):
},
},
)
async def change_password(data: ChangePasswordForm):
async def change_password(data: ChangePasswordBody):
"""logged in user changes password"""


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from aiohttp import web
from aiohttp.test_utils import TestClient
from simcore_service_webserver.db_models import UserRole, UserStatus
from simcore_service_webserver.login._constants import MSG_LOGGED_IN
from simcore_service_webserver.login._registration import create_invitation_token
from simcore_service_webserver.login.settings import LoginOptions, get_plugin_options
from simcore_service_webserver.login.storage import AsyncpgStorage, get_plugin_storage
from yarl import URL

Expand Down Expand Up @@ -71,13 +71,12 @@ async def log_client_in(
# creates user directly in db
assert client.app
db: AsyncpgStorage = get_plugin_storage(client.app)
cfg: LoginOptions = get_plugin_options(client.app)

user = await create_fake_user(db, user_data)

# login
url = client.app.router["auth_login"].url_for()
r = await client.post(
reponse = await client.post(
str(url),
json={
"email": user["email"],
Expand All @@ -86,7 +85,7 @@ async def log_client_in(
)

if enable_check:
await assert_status(r, web.HTTPOk, cfg.MSG_LOGGED_IN)
await assert_status(reponse, web.HTTPOk, MSG_LOGGED_IN)

return user

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,14 @@ async def parse_request_body_as(
resource_name=request.rel_url.path,
use_error_v1=use_enveloped_error_v1,
):
try:
body = await request.json()
except json.decoder.JSONDecodeError as err:
raise web.HTTPBadRequest(reason=f"Invalid json in body: {err}")
if not request.can_read_body:
# requests w/o body e.g. when model-schema is fully optional
body = {}
else:
try:
body = await request.json()
except json.decoder.JSONDecodeError as err:
raise web.HTTPBadRequest(reason=f"Invalid json in body: {err}")

if hasattr(model_schema, "parse_obj"):
# NOTE: model_schema can be 'list[T]' or 'dict[T]' which raise TypeError
Expand Down
6 changes: 4 additions & 2 deletions services/storage/src/simcore_service_storage/s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,10 @@ def setup_s3(app: web.Application):

log.debug("Setting up %s ...", __name__)

app.cleanup_ctx.append(setup_s3_client)
app.cleanup_ctx.append(setup_s3_bucket)
if setup_s3_client not in app.cleanup_ctx:
app.cleanup_ctx.append(setup_s3_client)
if setup_s3_bucket not in app.cleanup_ctx:
app.cleanup_ctx.append(setup_s3_bucket)


def get_s3_client(app: web.Application) -> StorageS3Client:
Expand Down
2 changes: 1 addition & 1 deletion services/web/server/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.12.0
0.12.2
2 changes: 1 addition & 1 deletion services/web/server/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.12.0
current_version = 0.12.2
commit = True
message = services/webserver api version: {current_version} → {new_version}
tag = False
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from servicelib.aiohttp.application_keys import (
APP_CONFIG_KEY,
APP_DB_ENGINE_KEY,
APP_FIRE_AND_FORGET_TASKS_KEY,
APP_JSONSCHEMA_SPECS_KEY,
APP_OPENAPI_SPECS_KEY,
APP_SETTINGS_KEY,
Expand Down
Loading

0 comments on commit 5d22707

Please sign in to comment.