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]
|
return [Archive.from_dict(res) for res in results]
|
||||||
|
|
||||||
async def get_archive_for_user_by_id(
|
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"""
|
"""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)
|
res = await self.archives.find_one(query)
|
||||||
return Archive.from_dict(res)
|
return Archive.from_dict(res)
|
||||||
|
|
||||||
@ -184,7 +184,7 @@ class ArchiveOps:
|
|||||||
|
|
||||||
async def handle_new_user_invite(self, invite_token: str, user: User):
|
async def handle_new_user_invite(self, invite_token: str, user: User):
|
||||||
"""Handle invite from a new 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.add_user_by_invite(new_user_invite, user)
|
||||||
await self.invites.remove_invite(invite_token)
|
await self.invites.remove_invite(invite_token)
|
||||||
return True
|
return True
|
||||||
|
@ -22,12 +22,14 @@ class UserRole(IntEnum):
|
|||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
class InvitePending(BaseModel):
|
class InvitePending(BaseMongoModel):
|
||||||
"""Pending Request to join"""
|
"""An invite for a new user, with an email and invite token as id"""
|
||||||
|
|
||||||
created: datetime
|
created: datetime
|
||||||
|
inviterEmail: str
|
||||||
aid: Optional[str]
|
aid: Optional[str]
|
||||||
role: Optional[UserRole] = UserRole.VIEWER
|
role: Optional[UserRole] = UserRole.VIEWER
|
||||||
|
email: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@ -44,13 +46,6 @@ class InviteToArchiveRequest(InviteRequest):
|
|||||||
role: UserRole
|
role: UserRole
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
class NewUserInvite(InvitePending, BaseMongoModel):
|
|
||||||
"""An invite for a new user, with an email and invite token as id"""
|
|
||||||
|
|
||||||
email: str
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
class InviteOps:
|
class InviteOps:
|
||||||
""" invite users (optionally to an archive), send emails and delete invites """
|
""" invite users (optionally to an archive), send emails and delete invites """
|
||||||
@ -61,8 +56,7 @@ class InviteOps:
|
|||||||
|
|
||||||
async def add_new_user_invite(
|
async def add_new_user_invite(
|
||||||
self,
|
self,
|
||||||
new_user_invite: NewUserInvite,
|
new_user_invite: InvitePending,
|
||||||
inviter_email: str,
|
|
||||||
archive_name: Optional[str],
|
archive_name: Optional[str],
|
||||||
headers: Optional[dict],
|
headers: Optional[dict],
|
||||||
):
|
):
|
||||||
@ -78,23 +72,21 @@ class InviteOps:
|
|||||||
|
|
||||||
self.email.send_new_user_invite(
|
self.email.send_new_user_invite(
|
||||||
new_user_invite.email,
|
new_user_invite.email,
|
||||||
inviter_email,
|
new_user_invite.inviterEmail,
|
||||||
archive_name,
|
archive_name,
|
||||||
new_user_invite.id,
|
new_user_invite.id,
|
||||||
headers,
|
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"""
|
""" Retrieve a valid invite data from db, or throw if invalid"""
|
||||||
invite_data = await self.invites.find_one({"_id": invite_token})
|
invite_data = await self.invites.find_one({"_id": invite_token})
|
||||||
if not invite_data:
|
if not invite_data:
|
||||||
print("NO DATA", flush=True)
|
|
||||||
raise HTTPException(status_code=400, detail="Invalid Invite Code")
|
raise HTTPException(status_code=400, detail="Invalid Invite Code")
|
||||||
|
|
||||||
new_user_invite = NewUserInvite.from_dict(invite_data)
|
new_user_invite = InvitePending.from_dict(invite_data)
|
||||||
print(new_user_invite, flush=True)
|
|
||||||
|
|
||||||
if user.email != new_user_invite.email:
|
if email != new_user_invite.email:
|
||||||
raise HTTPException(status_code=400, detail="Invalid Invite Code")
|
raise HTTPException(status_code=400, detail="Invalid Invite Code")
|
||||||
|
|
||||||
return new_user_invite
|
return new_user_invite
|
||||||
@ -133,19 +125,19 @@ class InviteOps:
|
|||||||
archive_name = archive.name
|
archive_name = archive.name
|
||||||
|
|
||||||
invite_pending = InvitePending(
|
invite_pending = InvitePending(
|
||||||
|
id=invite_code,
|
||||||
aid=aid,
|
aid=aid,
|
||||||
created=datetime.utcnow(),
|
created=datetime.utcnow(),
|
||||||
role=invite.role if hasattr(invite, "role") else None,
|
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)
|
other_user = await user_manager.user_db.get_by_email(invite.email)
|
||||||
|
|
||||||
if not other_user:
|
if not other_user:
|
||||||
await self.add_new_user_invite(
|
await self.add_new_user_invite(
|
||||||
NewUserInvite(
|
invite_pending,
|
||||||
id=invite_code, email=invite.email, **invite_pending.dict()
|
|
||||||
),
|
|
||||||
user.email,
|
|
||||||
archive_name,
|
archive_name,
|
||||||
headers,
|
headers,
|
||||||
)
|
)
|
||||||
@ -162,6 +154,8 @@ class InviteOps:
|
|||||||
status_code=400, detail="User already a member of this archive."
|
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
|
other_user.invites[invite_code] = invite_pending
|
||||||
|
|
||||||
await user_manager.user_db.update(other_user)
|
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")
|
raise HTTPException(status_code=400, detail="Invite Token Required")
|
||||||
|
|
||||||
if user.inviteToken and not await self.invites.get_valid_invite(
|
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")
|
raise HTTPException(status_code=400, detail="Invalid Invite Token")
|
||||||
|
|
||||||
@ -153,7 +153,13 @@ class UserManager(BaseUserManager[UserCreate, UserDB]):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
res = await self.create(
|
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(f"Super user {email} created", flush=True)
|
||||||
print(res, flush=True)
|
print(res, flush=True)
|
||||||
@ -207,7 +213,6 @@ class UserManager(BaseUserManager[UserCreate, UserDB]):
|
|||||||
user.email, token, request and request.headers
|
user.email, token, request and request.headers
|
||||||
)
|
)
|
||||||
|
|
||||||
###pylint: disable=no-self-use, unused-argument
|
|
||||||
async def on_after_request_verify(
|
async def on_after_request_verify(
|
||||||
self, user: UserDB, token: str, request: Optional[Request] = None
|
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)
|
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):
|
def init_user_manager(mdb, emailsender, invites):
|
||||||
@ -299,6 +317,24 @@ def init_users_api(app, user_manager):
|
|||||||
|
|
||||||
return {"invited": "new_user"}
|
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"])
|
app.include_router(users_router, prefix="/users", tags=["users"])
|
||||||
|
|
||||||
asyncio.create_task(user_manager.create_super_user())
|
asyncio.create_task(user_manager.create_super_user())
|
||||||
|
Loading…
Reference in New Issue
Block a user