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:
@@ -36,7 +36,10 @@ export class AlertManager {
|
||||
action: 'listPassportAlerts',
|
||||
}
|
||||
);
|
||||
const alerts = await this.listAlertsForUser(passportDevice.data.userId);
|
||||
const alerts = await this.listAlertsForUser(
|
||||
passportDevice.data.userId,
|
||||
!!requestArg.includeDismissed
|
||||
);
|
||||
return {
|
||||
alerts: alerts.map((alertArg) => ({ id: alertArg.id, data: alertArg.data })),
|
||||
};
|
||||
@@ -82,6 +85,25 @@ export class AlertManager {
|
||||
)
|
||||
);
|
||||
|
||||
this.typedRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_DismissPassportAlert>(
|
||||
'dismissPassportAlert',
|
||||
async (requestArg) => {
|
||||
const passportDevice = await this.receptionRef.passportManager.authenticatePassportDeviceRequest(
|
||||
requestArg,
|
||||
{
|
||||
action: 'dismissPassportAlert',
|
||||
signedFields: [`hint_id=${requestArg.hintId}`],
|
||||
}
|
||||
);
|
||||
await this.dismissAlert(passportDevice.data.userId, requestArg.hintId);
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.typedRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_UpsertAlertRule>(
|
||||
'upsertAlertRule',
|
||||
@@ -263,46 +285,81 @@ export class AlertManager {
|
||||
}
|
||||
|
||||
if (optionsArg.eventType === 'global_admin_access') {
|
||||
const fallbackRule = new AlertRule();
|
||||
fallbackRule.id = 'builtin-global-admin-access';
|
||||
fallbackRule.data = {
|
||||
return [this.createBuiltInRule('builtin-global-admin-access', {
|
||||
scope: 'global',
|
||||
organizationId: undefined,
|
||||
eventType: 'global_admin_access',
|
||||
minimumSeverity: 'high',
|
||||
recipientMode: 'global_admins',
|
||||
recipientUserIds: [],
|
||||
push: true,
|
||||
enabled: true,
|
||||
createdByUserId: 'system',
|
||||
createdAt: 0,
|
||||
updatedAt: 0,
|
||||
};
|
||||
return [fallbackRule];
|
||||
})];
|
||||
}
|
||||
|
||||
if (optionsArg.eventType === 'global_app_credentials_regenerated') {
|
||||
const fallbackRule = new AlertRule();
|
||||
fallbackRule.id = 'builtin-global-app-credentials-regenerated';
|
||||
fallbackRule.data = {
|
||||
return [this.createBuiltInRule('builtin-global-app-credentials-regenerated', {
|
||||
scope: 'global',
|
||||
organizationId: undefined,
|
||||
eventType: 'global_app_credentials_regenerated',
|
||||
minimumSeverity: 'critical',
|
||||
recipientMode: 'global_admins',
|
||||
recipientUserIds: [],
|
||||
push: true,
|
||||
enabled: true,
|
||||
createdByUserId: 'system',
|
||||
createdAt: 0,
|
||||
updatedAt: 0,
|
||||
})];
|
||||
}
|
||||
|
||||
if (optionsArg.organizationId) {
|
||||
const organizationFallbackMap: Record<
|
||||
string,
|
||||
{
|
||||
minimumSeverity: plugins.idpInterfaces.data.TAlertSeverity;
|
||||
}
|
||||
> = {
|
||||
org_app_connected: { minimumSeverity: 'medium' },
|
||||
org_app_disconnected: { minimumSeverity: 'medium' },
|
||||
org_invitation_created: { minimumSeverity: 'low' },
|
||||
org_invitation_resent: { minimumSeverity: 'low' },
|
||||
org_member_removed: { minimumSeverity: 'high' },
|
||||
org_member_roles_updated: { minimumSeverity: 'high' },
|
||||
org_ownership_transferred: { minimumSeverity: 'critical' },
|
||||
};
|
||||
return [fallbackRule];
|
||||
const fallbackConfig = organizationFallbackMap[optionsArg.eventType];
|
||||
if (fallbackConfig) {
|
||||
return [this.createBuiltInRule(`builtin-${optionsArg.eventType}`, {
|
||||
scope: 'organization',
|
||||
organizationId: optionsArg.organizationId,
|
||||
eventType: optionsArg.eventType,
|
||||
minimumSeverity: fallbackConfig.minimumSeverity,
|
||||
recipientMode: 'org_admins',
|
||||
})];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private createBuiltInRule(
|
||||
ruleIdArg: string,
|
||||
optionsArg: {
|
||||
scope: plugins.idpInterfaces.data.TAlertRuleScope;
|
||||
organizationId?: string;
|
||||
eventType: string;
|
||||
minimumSeverity: plugins.idpInterfaces.data.TAlertSeverity;
|
||||
recipientMode: plugins.idpInterfaces.data.TAlertRuleRecipientMode;
|
||||
}
|
||||
) {
|
||||
const fallbackRule = new AlertRule();
|
||||
fallbackRule.id = ruleIdArg;
|
||||
fallbackRule.data = {
|
||||
scope: optionsArg.scope,
|
||||
organizationId: optionsArg.organizationId,
|
||||
eventType: optionsArg.eventType,
|
||||
minimumSeverity: optionsArg.minimumSeverity,
|
||||
recipientMode: optionsArg.recipientMode,
|
||||
recipientUserIds: [],
|
||||
push: true,
|
||||
enabled: true,
|
||||
createdByUserId: 'system',
|
||||
createdAt: 0,
|
||||
updatedAt: 0,
|
||||
};
|
||||
return fallbackRule;
|
||||
}
|
||||
|
||||
public async createAlertsForEvent(optionsArg: {
|
||||
category: plugins.idpInterfaces.data.TAlertCategory;
|
||||
eventType: string;
|
||||
@@ -378,11 +435,13 @@ export class AlertManager {
|
||||
return createdAlerts;
|
||||
}
|
||||
|
||||
public async listAlertsForUser(userIdArg: string) {
|
||||
public async listAlertsForUser(userIdArg: string, includeDismissedArg = false) {
|
||||
const alerts = await this.CAlert.getInstances({
|
||||
'data.recipientUserId': userIdArg,
|
||||
});
|
||||
return alerts.sort((leftArg, rightArg) => rightArg.data.createdAt - leftArg.data.createdAt);
|
||||
return alerts
|
||||
.filter((alertArg) => includeDismissedArg || !alertArg.data.dismissedAt)
|
||||
.sort((leftArg, rightArg) => rightArg.data.createdAt - leftArg.data.createdAt);
|
||||
}
|
||||
|
||||
public async getAlertByHint(userIdArg: string, hintIdArg: string) {
|
||||
@@ -408,6 +467,25 @@ export class AlertManager {
|
||||
return alert;
|
||||
}
|
||||
|
||||
public async dismissAlert(userIdArg: string, hintIdArg: string) {
|
||||
const alert = await this.getAlertByHint(userIdArg, hintIdArg);
|
||||
if (!alert) {
|
||||
throw new plugins.typedrequest.TypedResponseError('Alert not found');
|
||||
}
|
||||
|
||||
alert.data.dismissedAt = Date.now();
|
||||
if (!alert.data.seenAt) {
|
||||
alert.data.seenAt = Date.now();
|
||||
}
|
||||
alert.data.notification = {
|
||||
...alert.data.notification,
|
||||
status: 'seen',
|
||||
seenAt: alert.data.notification.seenAt || Date.now(),
|
||||
};
|
||||
await alert.save();
|
||||
return alert;
|
||||
}
|
||||
|
||||
public async reDeliverPendingAlerts() {
|
||||
const alerts = await this.CAlert.getInstances({});
|
||||
for (const alert of alerts) {
|
||||
|
||||
Reference in New Issue
Block a user