feat: sua added to group invitations
All checks were successful
ci / lint_and_test (push) Successful in 13s
All checks were successful
ci / lint_and_test (push) Successful in 13s
Issue: #23
This commit is contained in:
parent
7809df4c5a
commit
768a3881ef
4 changed files with 110 additions and 3 deletions
|
|
@ -16,9 +16,11 @@ Endpoints:
|
|||
- [GET](/iam/permissions/search): [root user]: Returns a list of permissions matching a filter(service|resource|action)
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, status
|
||||
from fastapi import APIRouter, status, BackgroundTasks
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from src.iam.exceptions import GroupNotFoundException
|
||||
from src.organisation.exceptions import OrgNotFoundException
|
||||
from src.service.exceptions import ServiceNotFoundException
|
||||
from src.exceptions import ConflictException
|
||||
from src.database import db_dependency
|
||||
|
|
@ -34,11 +36,12 @@ from src.user.models import User
|
|||
from src.user.dependencies import (
|
||||
user_model_body_dependency,
|
||||
user_model_query_dependency,
|
||||
user_model_claims_dependency,
|
||||
)
|
||||
from src.organisation.models import Organisation as Org
|
||||
from src.service.models import Service
|
||||
|
||||
from src.iam.service import service_key_dependency
|
||||
from src.iam.service import service_key_dependency, send_user_group_invitation
|
||||
from src.iam.models import (
|
||||
Permission as Perm,
|
||||
GroupPermissions as GPerms,
|
||||
|
|
@ -68,7 +71,10 @@ from src.iam.schemas import (
|
|||
IAMPostPermissionResponse,
|
||||
IAMGetPermissionsSearchRequest,
|
||||
IAMGetPermissionsSearchResponse,
|
||||
IAMPutGroupInvitationRequest,
|
||||
IAMPutGroupInvitationAcceptRequest,
|
||||
)
|
||||
from src.utils import decode_jwt
|
||||
|
||||
router = APIRouter(
|
||||
tags=["IAM"],
|
||||
|
|
@ -315,3 +321,68 @@ async def post_permissions(
|
|||
permission_models = permission_query.all()
|
||||
|
||||
return {"permissions": permission_models}
|
||||
|
||||
|
||||
@router.put(
|
||||
"/group/user/invitation",
|
||||
summary="Send an email invitation for non-org member to join a group",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
async def invitation(
|
||||
background_tasks: BackgroundTasks,
|
||||
org_model: org_model_root_claim_body_dependency,
|
||||
group_model: group_model_body_dependency,
|
||||
request_model: IAMPutGroupInvitationRequest,
|
||||
):
|
||||
org_id = org_model.id
|
||||
org_name = org_model.name
|
||||
user_email = request_model.user_email
|
||||
group_id = group_model.id
|
||||
group_name = group_model.name
|
||||
|
||||
background_tasks.add_task(
|
||||
send_user_group_invitation,
|
||||
org_id=org_id,
|
||||
org_name=org_name,
|
||||
user_email=user_email,
|
||||
group_id=group_id,
|
||||
group_name=group_name,
|
||||
)
|
||||
|
||||
return "Invitation sent"
|
||||
|
||||
|
||||
@router.put(
|
||||
"/group/user//invitation/accept",
|
||||
summary="Accept email invitation to join an org's group",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
async def accept_invitation(
|
||||
db: db_dependency,
|
||||
user_model: user_model_claims_dependency,
|
||||
request_model: IAMPutGroupInvitationAcceptRequest,
|
||||
):
|
||||
email_claims = await decode_jwt(request_model.jwt)
|
||||
claimed_email = email_claims["email"]
|
||||
|
||||
if user_model.email != claimed_email:
|
||||
raise UnauthorizedException("The logged in user and email do not match.")
|
||||
|
||||
org_model = db.get(Org, email_claims["org_id"])
|
||||
if org_model is None:
|
||||
raise OrgNotFoundException()
|
||||
|
||||
group_model = db.get(Group, email_claims["group_id"])
|
||||
if group_model is None:
|
||||
raise GroupNotFoundException()
|
||||
|
||||
if group_model not in org_model.group_rel:
|
||||
raise UnauthorizedException("Group and org do not match.")
|
||||
|
||||
if user_model in group_model.user_rel:
|
||||
raise ConflictException(message="User already in group.")
|
||||
|
||||
group_model.user_rel.append(user_model)
|
||||
db.commit()
|
||||
|
||||
return "Invitation accepted"
|
||||
|
|
|
|||
|
|
@ -108,3 +108,11 @@ class IAMGetPermissionsSearchRequest(OrgIDMixin):
|
|||
|
||||
class IAMGetPermissionsSearchResponse(CustomBaseModel):
|
||||
permissions: list[PermissionSchema]
|
||||
|
||||
|
||||
class IAMPutGroupInvitationRequest(OrgIDMixin, GroupIDMixin):
|
||||
user_email: EmailStr
|
||||
|
||||
|
||||
class IAMPutGroupInvitationAcceptRequest(CustomBaseModel):
|
||||
jwt: str
|
||||
|
|
|
|||
|
|
@ -6,11 +6,14 @@ Exports:
|
|||
"""
|
||||
|
||||
from typing import Annotated
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from src.service.models import Service
|
||||
from src.database import db_dependency
|
||||
from src.schemas import ResourceName
|
||||
from src.auth.exceptions import UnauthorizedException
|
||||
from src.utils import send_email, generate_jwt
|
||||
|
||||
|
||||
from fastapi import Request, Depends
|
||||
|
||||
|
|
@ -33,3 +36,28 @@ def valid_service_key(db: db_dependency, request: Request, rn: ResourceName) ->
|
|||
|
||||
|
||||
service_key_dependency = Annotated[bool, Depends(valid_service_key)]
|
||||
|
||||
|
||||
async def send_user_group_invitation(
|
||||
user_email: str, org_name: str, org_id: int, group_id: int, group_name: str
|
||||
):
|
||||
expiry_delta = timedelta(hours=24)
|
||||
expiry = datetime.now(timezone.utc) + expiry_delta
|
||||
claims = {
|
||||
"email": user_email,
|
||||
"org_id": org_id,
|
||||
"group_id": group_id,
|
||||
"group_name": group_name,
|
||||
"exp": expiry,
|
||||
"type": "group_invite",
|
||||
}
|
||||
|
||||
token = await generate_jwt(claims)
|
||||
subject = f"You have been invited to join a group of {org_name}"
|
||||
body = f"You have been invited to join {group_name}.\nClick the link to accept.\nfrontend.capture/send/to/endpoint/{token}"
|
||||
|
||||
await send_email(
|
||||
recipient=user_email,
|
||||
subject=subject,
|
||||
body=body,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ async def accept_invitation(
|
|||
request_model: UserPostInvitationAcceptRequest,
|
||||
):
|
||||
email_claims = await decode_jwt(request_model.jwt)
|
||||
claimed_email = email_claims["user_email"]
|
||||
claimed_email = email_claims["email"]
|
||||
|
||||
if user_model.email != claimed_email:
|
||||
raise UnauthorizedException("The logged in user and email do not match.")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue