add office-aware passport policies and alert lifecycle

Enforce geofenced location evidence for passport challenges and extend admin alerting so mobile devices can review, dismiss, and act on real org and security events.
This commit is contained in:
2026-04-20 13:21:28 +00:00
parent a1a684ee81
commit e9eb9b4172
11 changed files with 548 additions and 37 deletions
+103 -6
View File
@@ -14,6 +14,36 @@ export class UserInvitationManager {
public CUserInvitation = plugins.smartdata.setDefaultManagerForDoc(this, UserInvitation);
private async emitOrganizationAlert(optionsArg: {
organizationId: string;
eventType: string;
severity: plugins.idpInterfaces.data.TAlertSeverity;
title: string;
body: string;
actorUserId: string;
relatedEntityId?: string;
relatedEntityType?: string;
}) {
await this.receptionRef.alertManager.createAlertsForEvent({
category: 'admin',
organizationId: optionsArg.organizationId,
eventType: optionsArg.eventType,
severity: optionsArg.severity,
title: optionsArg.title,
body: optionsArg.body,
actorUserId: optionsArg.actorUserId,
relatedEntityId: optionsArg.relatedEntityId,
relatedEntityType: optionsArg.relatedEntityType,
});
}
private async getOrganizationName(organizationIdArg: string) {
const organization = await this.receptionRef.organizationmanager.COrganization.getInstance({
id: organizationIdArg,
});
return organization?.data.name || 'this organization';
}
constructor(receptionRefArg: Reception) {
this.receptionRef = receptionRefArg;
this.receptionRef.typedrouter.addTypedRouter(this.typedrouter);
@@ -85,11 +115,24 @@ export class UserInvitationManager {
isNew = true;
}
// Send invitation email
await this.sendInvitationEmail(invitation, requestArg.organizationId);
// Send invitation email
await this.sendInvitationEmail(invitation, requestArg.organizationId);
return {
success: true,
await this.emitOrganizationAlert({
organizationId: requestArg.organizationId,
eventType: 'org_invitation_created',
severity: 'low',
title: 'Organization invitation created',
body: `${user.data.email} invited ${email} to ${await this.getOrganizationName(
requestArg.organizationId
)}.`,
actorUserId: user.id,
relatedEntityId: invitation.id,
relatedEntityType: 'invitation',
});
return {
success: true,
invitation: await invitation.createSavableObject(),
isNew,
};
@@ -189,6 +232,17 @@ export class UserInvitationManager {
await invitation.regenerateToken();
await this.sendInvitationEmail(invitation, requestArg.organizationId);
await this.emitOrganizationAlert({
organizationId: requestArg.organizationId,
eventType: 'org_invitation_resent',
severity: 'low',
title: 'Organization invitation resent',
body: `${user.data.email} resent an invitation to ${invitation.data.email}.`,
actorUserId: user.id,
relatedEntityId: invitation.id,
relatedEntityType: 'invitation',
});
return { success: true, message: 'Invitation resent.' };
}
)
@@ -231,10 +285,12 @@ export class UserInvitationManager {
await role.delete();
// Remove org from user's connectedOrgs
const memberUser = await this.receptionRef.userManager.CUser.getInstance({
const removedUser = await this.receptionRef.userManager.CUser.getInstance({
id: requestArg.userId,
});
// Remove org from user's connectedOrgs
const memberUser = removedUser;
if (memberUser && memberUser.data.connectedOrgs) {
memberUser.data.connectedOrgs = memberUser.data.connectedOrgs.filter(
orgId => orgId !== requestArg.organizationId
@@ -242,6 +298,19 @@ export class UserInvitationManager {
await memberUser.save();
}
await this.emitOrganizationAlert({
organizationId: requestArg.organizationId,
eventType: 'org_member_removed',
severity: 'high',
title: 'Organization member removed',
body: `${user.data.email} removed ${removedUser?.data?.email || requestArg.userId} from ${await this.getOrganizationName(
requestArg.organizationId
)}.`,
actorUserId: user.id,
relatedEntityId: requestArg.userId,
relatedEntityType: 'user',
});
return { success: true };
}
)
@@ -283,6 +352,20 @@ export class UserInvitationManager {
role.data.roles = requestArg.roles;
await role.save();
const updatedUser = await this.receptionRef.userManager.CUser.getInstance({
id: requestArg.userId,
});
await this.emitOrganizationAlert({
organizationId: requestArg.organizationId,
eventType: 'org_member_roles_updated',
severity: 'high',
title: 'Organization member roles updated',
body: `${user.data.email} changed roles for ${updatedUser?.data?.email || requestArg.userId} to ${requestArg.roles.join(', ')}.`,
actorUserId: user.id,
relatedEntityId: requestArg.userId,
relatedEntityType: 'user',
});
return { success: true, role: await role.createSavableObject() };
}
)
@@ -332,6 +415,20 @@ export class UserInvitationManager {
}
await currentUserRole.save();
const newOwner = await this.receptionRef.userManager.CUser.getInstance({
id: requestArg.newOwnerId,
});
await this.emitOrganizationAlert({
organizationId: requestArg.organizationId,
eventType: 'org_ownership_transferred',
severity: 'critical',
title: 'Organization ownership transferred',
body: `${user.data.email} transferred ownership to ${newOwner?.data?.email || requestArg.newOwnerId}.`,
actorUserId: user.id,
relatedEntityId: requestArg.newOwnerId,
relatedEntityType: 'user',
});
return { success: true };
}
)