async def change_username(self, id: int, username: str) -> None: new_username = self._validate_user_model(UserInChangeUsername, { "username": username }).username item = await self._repo.get(id) old_username = item.get("username") if old_username == new_username: raise HTTPException(400, detail=get_error_message( "username change same", self._language)) existing_user = await self._repo.get_by_username(new_username) if existing_user is not None: raise HTTPException(400, detail=get_error_message( "existing username", self._language)) logger.info( f"change_username id={id} old_username={old_username} new_username={new_username}" ) await self._repo.change_username(id, new_username) logger.info(f"change_username id={id} success") return None
async def register(self, data: dict) -> Dict[str, str]: """POST /register Args: data: email, username, password1, password2. Returns: Access and refresh tokens. Raises: HTTPException: 400 - validation error. """ if not self._debug: captcha = data.get("captcha") if not await validate_captcha(captcha, self._recaptcha_secret): raise HTTPException(400, detail=get_error_message( "captcha", self._language)) # try: # user = UserInRegister(**data) # except ValidationError as e: # message = get_error_message(e.errors()[0].get("msg"), self._language) # raise HTTPException(400, detail=message) user = self._validate_user_model(UserInRegister, data) if await self._email_exists(user.email): raise HTTPException(400, detail=get_error_message( "existing email", self._language)) if await self._username_exists(user.username): raise HTTPException(400, detail=get_error_message( "existing username", self._language)) new_user = UserInCreate(**user.dict(), password=get_password_hash( user.password1)).dict() try: validate_email(new_user.get("email"), timeout=5) except EmailNotValidError: raise HTTPException(400, detail=get_error_message( "try another email", self._language)) new_user_id = await self._repo.create(new_user) asyncio.create_task( self._request_email_confirmation(new_user.get("email"))) payload = UserPayload(id=new_user_id, username=user.username).dict() return self._auth_backend.create_tokens(payload)
async def forgot_password(self, data: dict, ip: str) -> None: """POST /forgot_password Only for accounts with password. Args: data: {email: "*****@*****.**"} ip: ip from request Returns: None Raises: HTTPException: 400 - validation or timeout. 404 - email not found. """ try: email = UserInForgotPassword(**data).email except ValidationError: raise HTTPException(400, detail=get_error_message( "validation", self._language)) item = await self._repo.get_by_email(email) if item is None: raise HTTPException(404, detail=get_error_message( "email not found", self._language)) if item.get("password") is None: raise HTTPException(406) id = item.get("id") if not await self._repo.is_password_reset_available(id): raise HTTPException(400, detail=get_error_message( "reset before", self._language)) logger.info(f"forgot_password ip={ip} email={email}") token = create_random_string() token_hash = hash_string(token) await self._repo.set_password_reset_token(id, token_hash) # if not self._debug: # TODO email_client = self._create_email_client() await email_client.send_forgot_password_email(email, token) return None
def _validate_user_model(self, model, data: dict): try: user = model(**data) return user except ValidationError as e: # for error in e.errors(): msg = e.errors()[0].get("msg") raise HTTPException(400, detail=get_error_message(msg, self._language))
async def password_set(self, data: dict) -> None: item = await self._repo.get(self._user.id) if item.get("provider") is not None and item.get("password") is None: user_model = self._validate_user_model(UserInSetPassword, data) password_hash = get_password_hash(user_model.password1) await self._repo.set_password(self._user.id, password_hash) return None else: raise HTTPException( 400, get_error_message("password already exists", self._language))
async def password_change(self, data: dict) -> None: user_model = self._validate_user_model(UserInChangePassword, data) item = await self._repo.get(self._user.id) if not verify_password(user_model.old_password, item.get("password")): raise HTTPException(400, detail=get_error_message( "password invalid", self._language)) password_hash = get_password_hash(user_model.password1) await self._repo.set_password(self._user.id, password_hash) return None
async def login(self, data: dict, ip: str) -> Dict[str, str]: """POST /login Args: data: login, password. ip: for bruteforce check. Returns: Access and refresh tokens. Raises: HTTPException: 400 - validation error or ban. 404 - user doesn't exist. 429 - bruteforce attempt. """ try: user = UserInLogin(**data) except ValidationError: raise HTTPException(400) if await self._is_bruteforce(ip, user.login): raise HTTPException(429, detail="Too many requests") item = await self._repo.get_by_login(user.login) if item is None: raise HTTPException(404) if not item.get("active"): raise HTTPException(400, detail=get_error_message( "ban", self._language)) if not verify_password(user.password, item.get("password")): raise HTTPException(401) await self._update_last_login(item.get("id")) payload = UserPayload(**item).dict() tokens = self._auth_backend.create_tokens(payload) return tokens