Support Invite Info APIs (#82)
* backend: support exposing info about a particular invite, fixes part of #35 new apis are: - GET /users/invite/{token}?email={email} - no auth needed, get invite to new user - GET /users/me/invite/{token} - with auth, to get invite to join an archive for an existing user * get archive.name as well if invite is adding to an archive * first camelCase typo
This commit is contained in:
parent
b9622df9e6
commit
c561fe3af4
@ -158,10 +158,10 @@ class ArchiveOps:
|
||||
return [Archive.from_dict(res) for res in results]
|
||||
|
||||
async def get_archive_for_user_by_id(
|
||||
self, uid: str, user: User, role: UserRole = UserRole.VIEWER
|
||||
self, aid: str, user: User, role: UserRole = UserRole.VIEWER
|
||||
):
|
||||
"""Get an archive for user by unique id"""
|
||||
query = {f"users.{user.id}": {"$gte": role.value}, "_id": uid}
|
||||
query = {f"users.{user.id}": {"$gte": role.value}, "_id": aid}
|
||||
res = await self.archives.find_one(query)
|
||||
return Archive.from_dict(res)
|
||||
|
||||
@ -184,7 +184,7 @@ class ArchiveOps:
|
||||
|
||||
async def handle_new_user_invite(self, invite_token: str, user: User):
|
||||
"""Handle invite from a new user"""
|
||||
new_user_invite = await self.invites.get_valid_invite(invite_token, user)
|
||||
new_user_invite = await self.invites.get_valid_invite(invite_token, user.email)
|
||||
await self.add_user_by_invite(new_user_invite, user)
|
||||
await self.invites.remove_invite(invite_token)
|
||||
return True
|
||||
|
@ -22,12 +22,14 @@ class UserRole(IntEnum):
|
||||
|
||||
|
||||
# ============================================================================
|
||||
class InvitePending(BaseModel):
|
||||
"""Pending Request to join"""
|
||||
class InvitePending(BaseMongoModel):
|
||||
"""An invite for a new user, with an email and invite token as id"""
|
||||
|
||||
created: datetime
|
||||
inviterEmail: str
|
||||
aid: Optional[str]
|
||||
role: Optional[UserRole] = UserRole.VIEWER
|
||||
email: Optional[str]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@ -44,13 +46,6 @@ class InviteToArchiveRequest(InviteRequest):
|
||||
role: UserRole
|
||||
|
||||
|
||||
# ============================================================================
|
||||
class NewUserInvite(InvitePending, BaseMongoModel):
|
||||
"""An invite for a new user, with an email and invite token as id"""
|
||||
|
||||
email: str
|
||||
|
||||
|
||||
# ============================================================================
|
||||
class InviteOps:
|
||||
""" invite users (optionally to an archive), send emails and delete invites """
|
||||
@ -61,8 +56,7 @@ class InviteOps:
|
||||
|
||||
async def add_new_user_invite(
|
||||
self,
|
||||
new_user_invite: NewUserInvite,
|
||||
inviter_email: str,
|
||||
new_user_invite: InvitePending,
|
||||
archive_name: Optional[str],
|
||||
headers: Optional[dict],
|
||||
):
|
||||
@ -78,23 +72,21 @@ class InviteOps:
|
||||
|
||||
self.email.send_new_user_invite(
|
||||
new_user_invite.email,
|
||||
inviter_email,
|
||||
new_user_invite.inviterEmail,
|
||||
archive_name,
|
||||
new_user_invite.id,
|
||||
headers,
|
||||
)
|
||||
|
||||
async def get_valid_invite(self, invite_token: str, user):
|
||||
async def get_valid_invite(self, invite_token: str, email):
|
||||
""" Retrieve a valid invite data from db, or throw if invalid"""
|
||||
invite_data = await self.invites.find_one({"_id": invite_token})
|
||||
if not invite_data:
|
||||
print("NO DATA", flush=True)
|
||||
raise HTTPException(status_code=400, detail="Invalid Invite Code")
|
||||
|
||||
new_user_invite = NewUserInvite.from_dict(invite_data)
|
||||
print(new_user_invite, flush=True)
|
||||
new_user_invite = InvitePending.from_dict(invite_data)
|
||||
|
||||
if user.email != new_user_invite.email:
|
||||
if email != new_user_invite.email:
|
||||
raise HTTPException(status_code=400, detail="Invalid Invite Code")
|
||||
|
||||
return new_user_invite
|
||||
@ -133,19 +125,19 @@ class InviteOps:
|
||||
archive_name = archive.name
|
||||
|
||||
invite_pending = InvitePending(
|
||||
id=invite_code,
|
||||
aid=aid,
|
||||
created=datetime.utcnow(),
|
||||
role=invite.role if hasattr(invite, "role") else None,
|
||||
email=invite.email,
|
||||
inviterEmail=user.email
|
||||
)
|
||||
|
||||
other_user = await user_manager.user_db.get_by_email(invite.email)
|
||||
|
||||
if not other_user:
|
||||
await self.add_new_user_invite(
|
||||
NewUserInvite(
|
||||
id=invite_code, email=invite.email, **invite_pending.dict()
|
||||
),
|
||||
user.email,
|
||||
invite_pending,
|
||||
archive_name,
|
||||
headers,
|
||||
)
|
||||
@ -162,6 +154,8 @@ class InviteOps:
|
||||
status_code=400, detail="User already a member of this archive."
|
||||
)
|
||||
|
||||
# no need to store our own email as adding invite to user
|
||||
invite_pending.email = None
|
||||
other_user.invites[invite_code] = invite_pending
|
||||
|
||||
await user_manager.user_db.update(other_user)
|
||||
|
@ -124,7 +124,7 @@ class UserManager(BaseUserManager[UserCreate, UserDB]):
|
||||
raise HTTPException(status_code=400, detail="Invite Token Required")
|
||||
|
||||
if user.inviteToken and not await self.invites.get_valid_invite(
|
||||
user.inviteToken, user
|
||||
user.inviteToken, user.email
|
||||
):
|
||||
raise HTTPException(status_code=400, detail="Invalid Invite Token")
|
||||
|
||||
@ -153,7 +153,13 @@ class UserManager(BaseUserManager[UserCreate, UserDB]):
|
||||
|
||||
try:
|
||||
res = await self.create(
|
||||
UserCreate(email=email, password=password, is_superuser=True, newArchive=False, is_verified=True)
|
||||
UserCreate(
|
||||
email=email,
|
||||
password=password,
|
||||
is_superuser=True,
|
||||
newArchive=False,
|
||||
is_verified=True,
|
||||
)
|
||||
)
|
||||
print(f"Super user {email} created", flush=True)
|
||||
print(res, flush=True)
|
||||
@ -207,7 +213,6 @@ class UserManager(BaseUserManager[UserCreate, UserDB]):
|
||||
user.email, token, request and request.headers
|
||||
)
|
||||
|
||||
###pylint: disable=no-self-use, unused-argument
|
||||
async def on_after_request_verify(
|
||||
self, user: UserDB, token: str, request: Optional[Request] = None
|
||||
):
|
||||
@ -215,6 +220,19 @@ class UserManager(BaseUserManager[UserCreate, UserDB]):
|
||||
|
||||
self.email.send_user_validation(user.email, token, request and request.headers)
|
||||
|
||||
async def format_invite(self, invite):
|
||||
""" format an InvitePending to return via api, resolve name of inviter """
|
||||
inviter = await self.get_by_email(invite.inviterEmail)
|
||||
result = invite.serialize()
|
||||
result["inviterName"] = inviter.name
|
||||
if invite.aid:
|
||||
archive = await self.archive_ops.get_archive_for_user_by_id(invite.aid, inviter)
|
||||
result["archiveName"] = archive.name
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
|
||||
# ============================================================================
|
||||
def init_user_manager(mdb, emailsender, invites):
|
||||
@ -299,6 +317,24 @@ def init_users_api(app, user_manager):
|
||||
|
||||
return {"invited": "new_user"}
|
||||
|
||||
@users_router.get("/invite/{token}", tags=["invites"])
|
||||
async def get_invite_info(token: str, email: str):
|
||||
invite = await user_manager.invites.get_valid_invite(token, email)
|
||||
return await user_manager.format_invite(invite)
|
||||
|
||||
@users_router.get("/me/invite/{token}", tags=["invites"])
|
||||
async def get_existing_user_invite_info(token: str,
|
||||
user: User = Depends(current_active_user)):
|
||||
|
||||
try:
|
||||
invite = user.invites[token]
|
||||
except:
|
||||
# pylint: disable=raise-missing-from
|
||||
raise HTTPException(status_code=400, detail="Invalid Invite Code")
|
||||
|
||||
return await user_manager.format_invite(invite)
|
||||
|
||||
|
||||
app.include_router(users_router, prefix="/users", tags=["users"])
|
||||
|
||||
asyncio.create_task(user_manager.create_super_user())
|
||||
|
Loading…
Reference in New Issue
Block a user