async def process_join(conn: AsyncIOMotorClient, invitation_token: str, user_current: UserTokenWrapper): token_db: TokenDB = await get_token(conn, invitation_token) if token_db.used_at: raise StarletteHTTPException(status_code=HTTP_403_FORBIDDEN, detail="Invitation token already used") group_db: GroupDB group_db_id: str group_db, group_db_id = await get_group_by_id(conn, token_db.group_id, get_id=True) group_update: GroupUpdate = GroupUpdate(member=UserBase( **user_current.dict())) if token_db.subject == TokenSubject.GROUP_INVITE_CO_OWNER: group_update: GroupUpdate = GroupUpdate(co_owner=UserBase( **user_current.dict())) group_id_wrapper: GroupIdWrapper = GroupIdWrapper(**group_db.dict(), id=str(group_db_id)) group_db_updated: GroupDB group_db_id_updated: ObjectId group_db_updated, group_db_id_updated = await update_group( conn, group_id_wrapper, group_update) await update_token( conn, TokenUpdate(token=token_db.token, used_at=datetime.utcnow())) return GroupResponse(group=GroupIdWrapper(**group_db_updated.dict(), id=str(group_db_id_updated)))
async def register( background_tasks: BackgroundTasks, user_create: UserCreate = Body(..., embed=True), conn: AsyncIOMotorClient = Depends(get_database), smtp_conn: FastMail = Depends(get_smtp), ) -> UserResponse: await check_availability_username_and_email(conn, user_create.email, user_create.username) async with await conn.start_session( ) as session, session.start_transaction(): user_db: UserDB = await create_user(conn, user_create) token: str = await TokenUtils.wrap_user_db_data_into_token( user_db, subject=TokenSubject.ACCESS) token_activation_expires_delta = timedelta( minutes=settings.ACTIVATE_TOKEN_EXPIRE_MINUTES) # 7 days token_activation: str = await TokenUtils.wrap_user_db_data_into_token( user_db, subject=TokenSubject.ACTIVATE, token_expires_delta=token_activation_expires_delta, ) action_link: str = f"{settings.FRONTEND_DNS}{settings.FRONTEND_ACTIVATION_PATH}?token={token_activation}" await background_send_new_account_email(smtp_conn, background_tasks, user_db.email, action_link) return UserResponse( user=UserTokenWrapper(**user_db.dict(), token=token))
async def get_user_from_invitation( conn: AsyncIOMotorClient = Depends(get_database), token: str = Depends(get_invitation_token), ) -> UserTokenWrapper: try: payload: dict = decode(token, str(settings.SECRET_KEY), algorithms=[settings.ALGORITHM]) if not payload.get("subject") in ( TokenSubject.GROUP_INVITE_CO_OWNER, TokenSubject.GROUP_INVITE_MEMBER, TokenSubject.USER_INVITE, ): raise StarletteHTTPException( status_code=HTTP_403_FORBIDDEN, detail="This is not an invitation token") token_data: TokenPayload = TokenPayload(**payload) except PyJWTError: raise StarletteHTTPException(status_code=HTTP_403_FORBIDDEN, detail="Could not validate invitation") user_db: UserDB = await get_user_by_email(conn, token_data.user_email_invited) if not user_db: raise StarletteHTTPException(status_code=HTTP_404_NOT_FOUND, detail="This user doesn't exist") return UserTokenWrapper(**user_db.dict(), token=token)
async def join_via_invitation( user_invitation: UserTokenWrapper = Depends(get_user_from_invitation), user_current: UserTokenWrapper = Depends(get_current_user), conn: AsyncIOMotorClient = Depends(get_database), ) -> UserResponse: # TODO: Create a user_friends entity in database and link them. # TODO: Create gamification to encourage users to become part of the community token_db: TokenDB = await get_token(conn, user_invitation.token) if not token_db.used_at: if user_current.email == token_db.user_email_invited: await update_token( conn, TokenUpdate(token=token_db.token, used_at=datetime.utcnow()) ) return UserResponse(user=UserTokenWrapper(**user_current.dict())) raise StarletteHTTPException( status_code=HTTP_403_FORBIDDEN, detail="This user was not invited" ) raise StarletteHTTPException( status_code=HTTP_403_FORBIDDEN, detail="Invitation token already used" )
async def group_by_id( group_id: str, user_current: UserTokenWrapper = Depends(get_current_user), conn: AsyncIOMotorClient = Depends(get_database), ) -> GroupResponse: group_db: GroupDB = await get_group_by_id(conn, group_id) if group_db.user_in_group(UserBase(**user_current.dict())): return GroupResponse( group=GroupIdWrapper(**group_db.dict(), id=group_id)) raise StarletteHTTPException(status_code=HTTP_403_FORBIDDEN, detail="User is not in the group")
async def login( user_login: UserLogin = Body(..., embed=True), conn: AsyncIOMotorClient = Depends(get_database), ) -> UserResponse: user_db: UserDB = await get_user_by_email(conn, user_login.email, raise_bad_request=True) if not user_db.check_password(user_login.password): raise StarletteHTTPException(status_code=HTTP_400_BAD_REQUEST, detail="Invalid credentials") token: str = await TokenUtils.wrap_user_db_data_into_token( user_db, subject=TokenSubject.ACCESS) return UserResponse(user=UserTokenWrapper(**user_db.dict(), token=token))
async def create( group_create: GroupCreate = Body(..., embed=True), user_current: UserTokenWrapper = Depends(get_current_user), conn: AsyncIOMotorClient = Depends(get_database), ) -> GroupResponse: async with await conn.start_session( ) as session, session.start_transaction(): group_db: GroupDB group_db_id: ObjectId group_db, group_db_id = await create_group( conn, group_create, UserBase(**user_current.dict())) return GroupResponse( group=GroupIdWrapper(**group_db.dict(), id=str(group_db_id)))
async def groups( user_current: UserTokenWrapper = Depends(get_current_user), conn: AsyncIOMotorClient = Depends(get_database), ) -> GroupsResponse: groups_db: List[Tuple[GroupDB, ObjectId]] = await get_groups_by_user( conn, UserBase(**user_current.dict())) groups_id_wrapper: List[GroupIdWrapper] = [] for group_db, group_db_id in groups_db: groups_id_wrapper.append( GroupIdWrapper(**group_db.dict(), id=str(group_db_id))) return GroupsResponse(groups=groups_id_wrapper)
async def update_current( user_update: UserUpdate = Body(..., embed=True), user_current: UserTokenWrapper = Depends(get_current_user), conn: AsyncIOMotorClient = Depends(get_database), ) -> UserResponse: user_update.username = ( None if user_update.username == user_current.username else user_update.username ) user_update.email = ( None if user_update.email == user_current.email else user_update.email ) await check_availability_username_and_email( conn, user_update.email, user_update.username ) user_db: UserDB = await update_user(conn, user_current, user_update) return UserResponse(user=UserTokenWrapper(**user_db.dict()))
async def leave( group_id: str, user_current: UserTokenWrapper = Depends(get_current_user), conn: AsyncIOMotorClient = Depends(get_database), ) -> GenericResponse: group_db: GroupDB = await get_group_by_id(conn, group_id) user_base: UserBase = UserBase(**user_current.dict()) if group_db.user_in_group(user_base): group_id_wrapper: GroupIdWrapper = GroupIdWrapper(**group_db.dict(), id=str(group_id)) await leave_group(conn, group_id_wrapper, user_base) return GenericResponse( status=GenericStatus.COMPLETED, message="User left the group", ) raise StarletteHTTPException(status_code=HTTP_403_FORBIDDEN, detail="User is not in the group")
async def _delete_inactive_users(): conn = await get_database() tokens_db: List[TokenDB] = await get_tokens_by_subject_and_lt_datetime( conn, subject=TokenSubject.ACTIVATE, needle_datetime=datetime.utcnow(), used=False, ) for token_db in tokens_db: user_db: UserDB = await get_user_by_email(conn, token_db.email) await delete_user(conn, UserTokenWrapper(**user_db.dict())) tokens_db_current_email: List[TokenDB] = await get_tokens_by_email( conn, token_db.email) for token_db_current_email in tokens_db_current_email: await update_token( conn, TokenUpdate(token=token_db_current_email.token, deleted=True))
async def get_current_user( conn: AsyncIOMotorClient = Depends(get_database), token: str = Depends(get_token), ) -> UserTokenWrapper: try: payload: dict = decode(token, str(settings.SECRET_KEY), algorithms=[settings.ALGORITHM]) token_data: TokenPayload = TokenPayload(**payload) except PyJWTError: raise StarletteHTTPException(status_code=HTTP_403_FORBIDDEN, detail="Could not validate credentials") user_db: UserDB = await get_user_by_email(conn, token_data.email) if not user_db: raise StarletteHTTPException(status_code=HTTP_404_NOT_FOUND, detail="This user doesn't exist") return UserTokenWrapper(**user_db.dict(), token=token)
async def kick( group_kick: GroupKick = Body(..., embed=True), user_current: UserTokenWrapper = Depends(get_current_user), conn: AsyncIOMotorClient = Depends(get_database), ) -> GenericResponse: group_db: GroupDB = await get_group_by_id(conn, group_kick.id) user_base: UserBase = UserBase(**user_current.dict()) if group_db.user_in_group(user_base): user_base_is_owner: bool = group_db.user_is_owner(user_base) user_base_is_co_owner: bool = group_db.user_is_co_owner(user_base) if user_base_is_owner or user_base_is_co_owner: user_kick_db: UserDB = await get_user_by_email( conn, group_kick.email) user_kick: UserBase = UserBase(**user_kick_db.dict()) if group_db.user_in_group(user_kick): if group_db.user_is_owner(user_kick): raise StarletteHTTPException( status_code=HTTP_403_FORBIDDEN, detail="Owner of the group can't be kicked", ) if group_db.user_is_co_owner( user_kick) and user_base_is_co_owner: raise StarletteHTTPException( status_code=HTTP_403_FORBIDDEN, detail="Co-owner is not allowed to kick other co-owner", ) group_id_wrapper: GroupIdWrapper = GroupIdWrapper( **group_db.dict(), id=str(group_kick.id)) await leave_group(conn, group_id_wrapper, user_kick) return GenericResponse( status=GenericStatus.COMPLETED, message="User kick out of the group", ) raise StarletteHTTPException(status_code=HTTP_403_FORBIDDEN, detail="User is not in the group") raise StarletteHTTPException( status_code=HTTP_403_FORBIDDEN, detail="User is not allowed to kick other members", ) raise StarletteHTTPException( status_code=HTTP_403_FORBIDDEN, detail="Current user is not part of the group")
async def activate( user_current: UserTokenWrapper = Depends(get_current_user), conn: AsyncIOMotorClient = Depends(get_database), ) -> UserResponse: token_db: TokenDB = await get_token(conn, user_current.token) if token_db.subject == TokenSubject.ACTIVATE: if not token_db.used_at: user_db: UserDB = await update_user( conn, user_current, UserUpdate(is_active=True) ) await update_token( conn, TokenUpdate(token=token_db.token, used_at=datetime.utcnow()) ) return UserResponse(user=UserTokenWrapper(**user_db.dict())) raise StarletteHTTPException( status_code=HTTP_403_FORBIDDEN, detail="Token has expired" ) raise StarletteHTTPException( status_code=HTTP_403_FORBIDDEN, detail="Invalid activation" )
async def change_password( user_current: UserTokenWrapper = Depends(get_current_user), password: AnyStr = Body(..., embed=True), conn: AsyncIOMotorClient = Depends(get_database), ) -> UserResponse: token_db: TokenDB = await get_token(conn, user_current.token) if token_db.subject == TokenSubject.RECOVER: if not token_db.used_at: user_db: UserDB = await update_user( conn, user_current, UserUpdate(password=password) ) await update_token( conn, TokenUpdate(token=token_db.token, used_at=datetime.utcnow()) ) return UserResponse(user=UserTokenWrapper(**user_db.dict())) raise StarletteHTTPException( status_code=HTTP_403_FORBIDDEN, detail="Token has expired" ) raise StarletteHTTPException( status_code=HTTP_403_FORBIDDEN, detail="Invalid recovery" )
async def invite_qrcode( user_current: UserTokenWrapper = Depends(get_current_user), group_invite: GroupInvite = Body(..., embed=True), conn: AsyncIOMotorClient = Depends(get_database), ) -> GroupInviteQRCodeResponse: group_db: GroupDB = await get_group_by_id(conn, group_invite.group_id) user_host: UserBase = UserBase(**user_current.dict()) if group_db.user_is_owner(user_host) or group_db.user_is_co_owner( user_host): if group_invite.role == GroupRole.OWNER: raise StarletteHTTPException(status_code=HTTP_403_FORBIDDEN, detail="Owner role is unique") if group_invite.role == GroupRole.CO_OWNER: if group_db.user_is_co_owner(user_host): raise StarletteHTTPException( status_code=HTTP_403_FORBIDDEN, detail="User is not allowed to invite another co-owner", ) return GroupInviteQRCodeResponse( invite_link=await process_invitation_qrcode( group_invite, user_host, TokenSubject.GROUP_INVITE_CO_OWNER, settings.GROUP_INVITE_CO_OWNER_TOKEN_EXPIRE_MINUTES, )) if group_invite.role == GroupRole.MEMBER: return GroupInviteQRCodeResponse( invite_link=await process_invitation_qrcode( group_invite, user_host, TokenSubject.GROUP_INVITE_MEMBER, settings.GROUP_INVITE_MEMBER_TOKEN_EXPIRE_MINUTES, )) raise StarletteHTTPException(status_code=HTTP_403_FORBIDDEN, detail="User is not allowed to invite")
async def invite( background_tasks: BackgroundTasks, group_invite: GroupInvite = Body(..., embed=True), user_current: UserTokenWrapper = Depends(get_current_user), conn: AsyncIOMotorClient = Depends(get_database), smtp_conn: FastMail = Depends(get_smtp), ) -> GenericResponse: group_db: GroupDB = await get_group_by_id(conn, group_invite.group_id) user_host: UserBase = UserBase(**user_current.dict()) if group_db.user_is_owner(user_host) or group_db.user_is_co_owner( user_host): if group_invite.role == GroupRole.OWNER: raise StarletteHTTPException(status_code=HTTP_403_FORBIDDEN, detail="Owner role is unique") # This is a mock that will be replace if there is a user with the desired email # TODO: FRONTEND will take care to register/login a user before joining a group user_invited: UserDB = UserDB(email=group_invite.email, first_name="", last_name="", username="") try: user_invited: UserDB = await get_user_by_email( conn, group_invite.email) except StarletteHTTPException as exc: if not (exc.status_code == 404 and exc.detail == "This user doesn't exist"): raise exc # There will be no problem with mocked user_invited, because you can't be part # of any group if you are not registered. if group_db.user_in_group(user_invited): raise StarletteHTTPException(status_code=HTTP_403_FORBIDDEN, detail="User already in group") if group_invite.role == GroupRole.CO_OWNER: if group_db.user_is_co_owner(user_host): raise StarletteHTTPException( status_code=HTTP_403_FORBIDDEN, detail="User is not allowed to invite another co-owner", ) await process_invitation( background_tasks, smtp_conn, group_db, group_invite, user_host, user_invited, TokenSubject.GROUP_INVITE_CO_OWNER, settings.GROUP_INVITE_CO_OWNER_TOKEN_EXPIRE_MINUTES, ) if group_invite.role == GroupRole.MEMBER: await process_invitation( background_tasks, smtp_conn, group_db, group_invite, user_host, user_invited, TokenSubject.GROUP_INVITE_MEMBER, settings.GROUP_INVITE_MEMBER_TOKEN_EXPIRE_MINUTES, ) return GenericResponse( status=GenericStatus.RUNNING, message="Group invite email has been processed", ) raise StarletteHTTPException(status_code=HTTP_403_FORBIDDEN, detail="User is not allowed to invite")