Add phase 6 PAN login invitation and client identity
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Form, Request
|
||||
from fastapi.responses import RedirectResponse
|
||||
from sqlalchemy import select
|
||||
from app.core.db.common import CommonSessionLocal
|
||||
from app.core.security.csrf import get_or_create_csrf_token, validate_csrf
|
||||
from app.core.security.session_auth import get_current_user
|
||||
from app.core.templating import templates
|
||||
from app.modules.clients.models import Client
|
||||
from app.modules.core.rbac.models import Permission, RolePermission, UserRole
|
||||
from .models import ClientIdentityAuditLog, ClientPortalInvitation
|
||||
from .service import ensure_identity, get_identity, issue_client_invitation, update_login_email
|
||||
router=APIRouter(prefix="/client-identity", tags=["client-identity"])
|
||||
|
||||
def _has(db,user,code):
|
||||
return db.execute(select(Permission.id).join(RolePermission,RolePermission.permission_id==Permission.id).join(UserRole,UserRole.role_id==RolePermission.role_id).where(UserRole.user_id==user.id,Permission.code==code,Permission.is_active.is_(True))).first() is not None
|
||||
|
||||
def _load(db,user,client_id):
|
||||
c=db.get(Client,client_id)
|
||||
if not c or c.tenant_id!=user.tenant_id: return None
|
||||
if user.branch_id and c.branch_id!=user.branch_id and not _has(db,user,"clients.view_all_branches"): return None
|
||||
return c
|
||||
|
||||
@router.get("/clients/{client_id}")
|
||||
def identity_page(request:Request,client_id:int):
|
||||
db=CommonSessionLocal()
|
||||
try:
|
||||
user=get_current_user(request,db=db)
|
||||
if not user:return RedirectResponse('/login',303)
|
||||
if not _has(db,user,'clients.view'):return RedirectResponse('/clients?error=Access+denied',303)
|
||||
client=_load(db,user,client_id)
|
||||
if not client:return RedirectResponse('/clients?error=Client+not+found',303)
|
||||
identity=get_identity(db,client.id)
|
||||
invites=[]; audits=[]
|
||||
if identity:
|
||||
invites=list(db.execute(select(ClientPortalInvitation).where(ClientPortalInvitation.identity_id==identity.id).order_by(ClientPortalInvitation.id.desc())).scalars().all())
|
||||
audits=list(db.execute(select(ClientIdentityAuditLog).where(ClientIdentityAuditLog.identity_id==identity.id).order_by(ClientIdentityAuditLog.id.desc()).limit(30)).scalars().all())
|
||||
return templates.TemplateResponse('client_identity/detail.html',{'request':request,'current_user':user,'csrf_token':get_or_create_csrf_token(request),'client':client,'identity':identity,'invites':invites,'audits':audits,'can_manage':_has(db,user,'clients.edit'),'invite_url':request.session.pop('client_invite_url',None),'flash':request.session.pop('client_identity_flash',None)})
|
||||
finally:db.close()
|
||||
|
||||
@router.post("/clients/{client_id}/create")
|
||||
def create_identity(request:Request,client_id:int,email:str=Form(''),csrf_token:str=Form(...)):
|
||||
validate_csrf(request,csrf_token);db=CommonSessionLocal()
|
||||
try:
|
||||
user=get_current_user(request,db=db)
|
||||
if not user or not _has(db,user,'clients.edit'):return RedirectResponse('/clients?error=Access+denied',303)
|
||||
client=_load(db,user,client_id)
|
||||
if not client:return RedirectResponse('/clients?error=Client+not+found',303)
|
||||
try: ensure_identity(db,client=client,actor_user_id=user.id,requested_email=email or None);request.session['client_identity_flash']='Client portal identity created.'
|
||||
except Exception as exc: request.session['client_identity_flash']=str(exc)
|
||||
return RedirectResponse(f'/client-identity/clients/{client_id}',303)
|
||||
finally:db.close()
|
||||
|
||||
@router.post("/clients/{client_id}/email")
|
||||
def change_email(request:Request,client_id:int,email:str=Form(...),csrf_token:str=Form(...)):
|
||||
validate_csrf(request,csrf_token);db=CommonSessionLocal()
|
||||
try:
|
||||
user=get_current_user(request,db=db)
|
||||
if not user or not _has(db,user,'clients.edit'):return RedirectResponse('/clients?error=Access+denied',303)
|
||||
client=_load(db,user,client_id); identity=get_identity(db,client_id) if client else None
|
||||
try:
|
||||
if not identity: raise ValueError('Create the PAN identity first.')
|
||||
update_login_email(db,identity=identity,email=email,actor_user_id=user.id);request.session['client_identity_flash']='Client portal email updated.'
|
||||
except Exception as exc: request.session['client_identity_flash']=str(exc)
|
||||
return RedirectResponse(f'/client-identity/clients/{client_id}',303)
|
||||
finally:db.close()
|
||||
|
||||
@router.post("/clients/{client_id}/invite")
|
||||
def invite(request:Request,client_id:int,send_email:str=Form('1'),csrf_token:str=Form(...)):
|
||||
validate_csrf(request,csrf_token);db=CommonSessionLocal()
|
||||
try:
|
||||
user=get_current_user(request,db=db)
|
||||
if not user or not _has(db,user,'clients.edit'):return RedirectResponse('/clients?error=Access+denied',303)
|
||||
client=_load(db,user,client_id); identity=get_identity(db,client_id) if client else None
|
||||
try:
|
||||
if not identity: identity=ensure_identity(db,client=client,actor_user_id=user.id)
|
||||
token,row=issue_client_invitation(db,identity=identity,actor_user_id=user.id,send_email=send_email=='1')
|
||||
request.session['client_invite_url']=f"{str(request.base_url).rstrip('/')}/invite/accept?token={token}"
|
||||
request.session['client_identity_flash']='Invitation issued. Copy the link now; the token is not stored in plain text.'
|
||||
except Exception as exc: request.session['client_identity_flash']=str(exc)
|
||||
return RedirectResponse(f'/client-identity/clients/{client_id}',303)
|
||||
finally:db.close()
|
||||
Reference in New Issue
Block a user