feat(reception): add passport device authentication flows and alert delivery management
This commit is contained in:
@@ -3,6 +3,9 @@ export type TActivityAction =
|
||||
| 'logout'
|
||||
| 'session_created'
|
||||
| 'session_revoked'
|
||||
| 'passport_device_enrolled'
|
||||
| 'passport_device_revoked'
|
||||
| 'passport_challenge_approved'
|
||||
| 'org_created'
|
||||
| 'org_joined'
|
||||
| 'org_left'
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
export type TAlertSeverity = 'low' | 'medium' | 'high' | 'critical';
|
||||
|
||||
export type TAlertStatus = 'pending' | 'seen' | 'dismissed';
|
||||
|
||||
export type TAlertCategory = 'security' | 'admin' | 'system';
|
||||
|
||||
export type TAlertNotificationStatus = 'pending' | 'sent' | 'failed' | 'seen';
|
||||
|
||||
export interface IAlert {
|
||||
id: string;
|
||||
data: {
|
||||
recipientUserId: string;
|
||||
organizationId?: string;
|
||||
category: TAlertCategory;
|
||||
eventType: string;
|
||||
severity: TAlertSeverity;
|
||||
title: string;
|
||||
body: string;
|
||||
actorUserId?: string;
|
||||
relatedEntityId?: string;
|
||||
relatedEntityType?: string;
|
||||
notification: {
|
||||
hintId: string;
|
||||
status: TAlertNotificationStatus;
|
||||
attemptCount: number;
|
||||
createdAt: number;
|
||||
deliveredAt?: number | null;
|
||||
seenAt?: number | null;
|
||||
lastError?: string | null;
|
||||
};
|
||||
createdAt: number;
|
||||
seenAt?: number | null;
|
||||
dismissedAt?: number | null;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { TAlertSeverity } from './alert.js';
|
||||
|
||||
export type TAlertRuleScope = 'global' | 'organization';
|
||||
|
||||
export type TAlertRuleRecipientMode = 'global_admins' | 'org_admins' | 'specific_users';
|
||||
|
||||
export interface IAlertRule {
|
||||
id: string;
|
||||
data: {
|
||||
scope: TAlertRuleScope;
|
||||
organizationId?: string;
|
||||
eventType: string;
|
||||
minimumSeverity: TAlertSeverity;
|
||||
recipientMode: TAlertRuleRecipientMode;
|
||||
recipientUserIds?: string[];
|
||||
push: boolean;
|
||||
enabled: boolean;
|
||||
createdByUserId: string;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
export * from './abusewindow.js';
|
||||
export * from './activity.js';
|
||||
export * from './alert.js';
|
||||
export * from './alertrule.js';
|
||||
export * from './app.js';
|
||||
export * from './emailactiontoken.js';
|
||||
export * from './oidc.js';
|
||||
@@ -10,6 +12,9 @@ export * from './jwt.js';
|
||||
export * from './loginsession.js';
|
||||
export * from './organization.js';
|
||||
export * from './paddlecheckoutdata.js';
|
||||
export * from './passportchallenge.js';
|
||||
export * from './passportdevice.js';
|
||||
export * from './passportnonce.js';
|
||||
export * from './registrationsession.js';
|
||||
export * from './role.js';
|
||||
export * from './user.js';
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { IPassportCapabilities } from './passportdevice.js';
|
||||
|
||||
export type TPassportChallengeType =
|
||||
| 'device_enrollment'
|
||||
| 'authentication'
|
||||
| 'step_up'
|
||||
| 'physical_access';
|
||||
|
||||
export type TPassportChallengeStatus = 'pending' | 'approved' | 'expired' | 'rejected';
|
||||
|
||||
export type TPassportChallengeDeliveryStatus = 'pending' | 'sent' | 'failed' | 'seen';
|
||||
|
||||
export type TPassportSignatureFormat = 'raw' | 'der';
|
||||
|
||||
export interface IPassportLocationEvidence {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
accuracyMeters: number;
|
||||
capturedAt: number;
|
||||
}
|
||||
|
||||
export interface IPassportNfcEvidence {
|
||||
tagId?: string;
|
||||
readerId?: string;
|
||||
}
|
||||
|
||||
export interface IPassportChallenge {
|
||||
id: string;
|
||||
data: {
|
||||
userId: string;
|
||||
deviceId?: string | null;
|
||||
type: TPassportChallengeType;
|
||||
status: TPassportChallengeStatus;
|
||||
tokenHash?: string | null;
|
||||
challenge: string;
|
||||
metadata: {
|
||||
originHost?: string;
|
||||
audience?: string;
|
||||
notificationTitle?: string;
|
||||
deviceLabel?: string;
|
||||
requireLocation: boolean;
|
||||
requireNfc: boolean;
|
||||
requestedCapabilities?: Partial<IPassportCapabilities>;
|
||||
};
|
||||
evidence?: {
|
||||
signatureFormat?: TPassportSignatureFormat;
|
||||
location?: IPassportLocationEvidence;
|
||||
nfc?: IPassportNfcEvidence;
|
||||
};
|
||||
notification?: {
|
||||
hintId: string;
|
||||
status: TPassportChallengeDeliveryStatus;
|
||||
attemptCount: number;
|
||||
createdAt: number;
|
||||
deliveredAt?: number | null;
|
||||
seenAt?: number | null;
|
||||
lastError?: string | null;
|
||||
};
|
||||
createdAt: number;
|
||||
expiresAt: number;
|
||||
completedAt?: number | null;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
export type TPassportDevicePlatform =
|
||||
| 'ios'
|
||||
| 'ipados'
|
||||
| 'macos'
|
||||
| 'watchos'
|
||||
| 'android'
|
||||
| 'web'
|
||||
| 'unknown';
|
||||
|
||||
export type TPassportDeviceStatus = 'active' | 'revoked';
|
||||
|
||||
export type TPassportPushProvider = 'apns';
|
||||
|
||||
export type TPassportPushEnvironment = 'development' | 'production';
|
||||
|
||||
export interface IPassportCapabilities {
|
||||
gps: boolean;
|
||||
nfc: boolean;
|
||||
push: boolean;
|
||||
}
|
||||
|
||||
export interface IPassportDevice {
|
||||
id: string;
|
||||
data: {
|
||||
userId: string;
|
||||
label: string;
|
||||
platform: TPassportDevicePlatform;
|
||||
status: TPassportDeviceStatus;
|
||||
publicKeyAlgorithm: 'p256';
|
||||
publicKeyX963Base64: string;
|
||||
capabilities: IPassportCapabilities;
|
||||
pushRegistration?: {
|
||||
provider: TPassportPushProvider;
|
||||
token: string;
|
||||
topic: string;
|
||||
environment: TPassportPushEnvironment;
|
||||
registeredAt: number;
|
||||
lastDeliveredAt?: number;
|
||||
lastError?: string;
|
||||
};
|
||||
appVersion?: string;
|
||||
createdAt: number;
|
||||
lastSeenAt?: number;
|
||||
lastChallengeAt?: number;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface IPassportNonce {
|
||||
id: string;
|
||||
data: {
|
||||
deviceId: string;
|
||||
nonceHash: string;
|
||||
createdAt: number;
|
||||
expiresAt: number;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as data from '../data/index.js';
|
||||
import type { IPassportDeviceSignedRequest } from './passport.js';
|
||||
|
||||
export interface IReq_ListPassportAlerts
|
||||
extends plugins.typedRequestInterfaces.implementsTR<
|
||||
plugins.typedRequestInterfaces.ITypedRequest,
|
||||
IReq_ListPassportAlerts
|
||||
> {
|
||||
method: 'listPassportAlerts';
|
||||
request: IPassportDeviceSignedRequest;
|
||||
response: {
|
||||
alerts: data.IAlert[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface IReq_GetPassportAlertByHint
|
||||
extends plugins.typedRequestInterfaces.implementsTR<
|
||||
plugins.typedRequestInterfaces.ITypedRequest,
|
||||
IReq_GetPassportAlertByHint
|
||||
> {
|
||||
method: 'getPassportAlertByHint';
|
||||
request: IPassportDeviceSignedRequest & {
|
||||
hintId: string;
|
||||
};
|
||||
response: {
|
||||
alert?: data.IAlert;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IReq_MarkPassportAlertSeen
|
||||
extends plugins.typedRequestInterfaces.implementsTR<
|
||||
plugins.typedRequestInterfaces.ITypedRequest,
|
||||
IReq_MarkPassportAlertSeen
|
||||
> {
|
||||
method: 'markPassportAlertSeen';
|
||||
request: IPassportDeviceSignedRequest & {
|
||||
hintId: string;
|
||||
};
|
||||
response: {
|
||||
success: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IReq_UpsertAlertRule
|
||||
extends plugins.typedRequestInterfaces.implementsTR<
|
||||
plugins.typedRequestInterfaces.ITypedRequest,
|
||||
IReq_UpsertAlertRule
|
||||
> {
|
||||
method: 'upsertAlertRule';
|
||||
request: {
|
||||
jwt: string;
|
||||
ruleId?: string;
|
||||
scope: data.TAlertRuleScope;
|
||||
organizationId?: string;
|
||||
eventType: string;
|
||||
minimumSeverity: data.TAlertSeverity;
|
||||
recipientMode: data.TAlertRuleRecipientMode;
|
||||
recipientUserIds?: string[];
|
||||
push: boolean;
|
||||
enabled: boolean;
|
||||
};
|
||||
response: {
|
||||
rule: data.IAlertRule;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IReq_GetAlertRules
|
||||
extends plugins.typedRequestInterfaces.implementsTR<
|
||||
plugins.typedRequestInterfaces.ITypedRequest,
|
||||
IReq_GetAlertRules
|
||||
> {
|
||||
method: 'getAlertRules';
|
||||
request: {
|
||||
jwt: string;
|
||||
scope?: data.TAlertRuleScope;
|
||||
organizationId?: string;
|
||||
};
|
||||
response: {
|
||||
rules: data.IAlertRule[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface IReq_DeleteAlertRule
|
||||
extends plugins.typedRequestInterfaces.implementsTR<
|
||||
plugins.typedRequestInterfaces.ITypedRequest,
|
||||
IReq_DeleteAlertRule
|
||||
> {
|
||||
method: 'deleteAlertRule';
|
||||
request: {
|
||||
jwt: string;
|
||||
ruleId: string;
|
||||
};
|
||||
response: {
|
||||
success: boolean;
|
||||
};
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
export * from './admin.js';
|
||||
export * from './apitoken.js';
|
||||
export * from './alert.js';
|
||||
export * from './app.js';
|
||||
export * from './authorization.js';
|
||||
export * from './billingplan.js';
|
||||
export * from './jwt.js';
|
||||
export * from './login.js';
|
||||
export * from './organization.js';
|
||||
export * from './passport.js';
|
||||
export * from './plan.js';
|
||||
export * from './registration.js';
|
||||
export * from './user.js';
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as data from '../data/index.js';
|
||||
|
||||
export interface IPassportDeviceSignedRequest {
|
||||
deviceId: string;
|
||||
timestamp: number;
|
||||
nonce: string;
|
||||
signatureBase64: string;
|
||||
signatureFormat?: data.TPassportSignatureFormat;
|
||||
}
|
||||
|
||||
export interface IReq_CreatePassportEnrollmentChallenge
|
||||
extends plugins.typedRequestInterfaces.implementsTR<
|
||||
plugins.typedRequestInterfaces.ITypedRequest,
|
||||
IReq_CreatePassportEnrollmentChallenge
|
||||
> {
|
||||
method: 'createPassportEnrollmentChallenge';
|
||||
request: {
|
||||
jwt: string;
|
||||
deviceLabel: string;
|
||||
platform: data.TPassportDevicePlatform;
|
||||
appVersion?: string;
|
||||
capabilities?: Partial<data.IPassportCapabilities>;
|
||||
};
|
||||
response: {
|
||||
challengeId: string;
|
||||
pairingToken: string;
|
||||
pairingPayload: string;
|
||||
signingPayload: string;
|
||||
expiresAt: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IReq_CompletePassportEnrollment
|
||||
extends plugins.typedRequestInterfaces.implementsTR<
|
||||
plugins.typedRequestInterfaces.ITypedRequest,
|
||||
IReq_CompletePassportEnrollment
|
||||
> {
|
||||
method: 'completePassportEnrollment';
|
||||
request: {
|
||||
pairingToken: string;
|
||||
deviceLabel: string;
|
||||
platform: data.TPassportDevicePlatform;
|
||||
publicKeyX963Base64: string;
|
||||
signatureBase64: string;
|
||||
signatureFormat?: data.TPassportSignatureFormat;
|
||||
appVersion?: string;
|
||||
capabilities?: Partial<data.IPassportCapabilities>;
|
||||
};
|
||||
response: {
|
||||
device: data.IPassportDevice;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IReq_GetPassportDevices
|
||||
extends plugins.typedRequestInterfaces.implementsTR<
|
||||
plugins.typedRequestInterfaces.ITypedRequest,
|
||||
IReq_GetPassportDevices
|
||||
> {
|
||||
method: 'getPassportDevices';
|
||||
request: {
|
||||
jwt: string;
|
||||
};
|
||||
response: {
|
||||
devices: data.IPassportDevice[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface IReq_RevokePassportDevice
|
||||
extends plugins.typedRequestInterfaces.implementsTR<
|
||||
plugins.typedRequestInterfaces.ITypedRequest,
|
||||
IReq_RevokePassportDevice
|
||||
> {
|
||||
method: 'revokePassportDevice';
|
||||
request: {
|
||||
jwt: string;
|
||||
deviceId: string;
|
||||
};
|
||||
response: {
|
||||
success: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IReq_CreatePassportChallenge
|
||||
extends plugins.typedRequestInterfaces.implementsTR<
|
||||
plugins.typedRequestInterfaces.ITypedRequest,
|
||||
IReq_CreatePassportChallenge
|
||||
> {
|
||||
method: 'createPassportChallenge';
|
||||
request: {
|
||||
jwt: string;
|
||||
type?: Exclude<data.TPassportChallengeType, 'device_enrollment'>;
|
||||
preferredDeviceId?: string;
|
||||
audience?: string;
|
||||
notificationTitle?: string;
|
||||
requireLocation?: boolean;
|
||||
requireNfc?: boolean;
|
||||
};
|
||||
response: {
|
||||
challengeId: string;
|
||||
challenge: string;
|
||||
signingPayload: string;
|
||||
deviceId: string;
|
||||
expiresAt: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IReq_ApprovePassportChallenge
|
||||
extends plugins.typedRequestInterfaces.implementsTR<
|
||||
plugins.typedRequestInterfaces.ITypedRequest,
|
||||
IReq_ApprovePassportChallenge
|
||||
> {
|
||||
method: 'approvePassportChallenge';
|
||||
request: {
|
||||
challengeId: string;
|
||||
deviceId: string;
|
||||
signatureBase64: string;
|
||||
signatureFormat?: data.TPassportSignatureFormat;
|
||||
location?: data.IPassportLocationEvidence;
|
||||
nfc?: data.IPassportNfcEvidence;
|
||||
};
|
||||
response: {
|
||||
success: boolean;
|
||||
challenge: data.IPassportChallenge;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IReq_RegisterPassportPushToken
|
||||
extends plugins.typedRequestInterfaces.implementsTR<
|
||||
plugins.typedRequestInterfaces.ITypedRequest,
|
||||
IReq_RegisterPassportPushToken
|
||||
> {
|
||||
method: 'registerPassportPushToken';
|
||||
request: IPassportDeviceSignedRequest & {
|
||||
provider: data.TPassportPushProvider;
|
||||
token: string;
|
||||
topic: string;
|
||||
environment: data.TPassportPushEnvironment;
|
||||
};
|
||||
response: {
|
||||
success: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IReq_ListPendingPassportChallenges
|
||||
extends plugins.typedRequestInterfaces.implementsTR<
|
||||
plugins.typedRequestInterfaces.ITypedRequest,
|
||||
IReq_ListPendingPassportChallenges
|
||||
> {
|
||||
method: 'listPendingPassportChallenges';
|
||||
request: IPassportDeviceSignedRequest;
|
||||
response: {
|
||||
challenges: data.IPassportChallenge[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface IReq_GetPassportChallengeByHint
|
||||
extends plugins.typedRequestInterfaces.implementsTR<
|
||||
plugins.typedRequestInterfaces.ITypedRequest,
|
||||
IReq_GetPassportChallengeByHint
|
||||
> {
|
||||
method: 'getPassportChallengeByHint';
|
||||
request: IPassportDeviceSignedRequest & {
|
||||
hintId: string;
|
||||
};
|
||||
response: {
|
||||
challenge?: data.IPassportChallenge;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IReq_MarkPassportChallengeSeen
|
||||
extends plugins.typedRequestInterfaces.implementsTR<
|
||||
plugins.typedRequestInterfaces.ITypedRequest,
|
||||
IReq_MarkPassportChallengeSeen
|
||||
> {
|
||||
method: 'markPassportChallengeSeen';
|
||||
request: IPassportDeviceSignedRequest & {
|
||||
hintId: string;
|
||||
};
|
||||
response: {
|
||||
success: boolean;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user