Skip to content

Access decisions

Every call the tenant’s active policies decide returns a Decided<T> value, never an exception. A Decided<T> is always one of three decisions: allow, deny or challenge.

Outcome and Decided<T>

Outcome is 'allow', 'deny' or 'challenge'. Decided<T> is the union of Allowed<T>, Denied and Challenged<T>, tagged by outcome.

type Outcome = 'allow' | 'deny' | 'challenge';
type Decided<T = {}> = Allowed<T> | Denied | Challenged<T>;

Every Decided<T> carries an EventId, the access event the Seald Healthcare Cloud assigned. The Seald Healthcare Cloud records every decision: allow, deny and challenge. The record of the request and its decision is complete whatever the decision.

Allowed<T>

On allow, the value is what you asked for, with the outcome and the event id.

type Allowed<T> = { outcome: 'allow'; eventId: EventId } & T;

Denied

On deny, you get a fixed reason code and the words a person should see. You never get a policy or a rule.

interface Denied {
outcome: 'deny';
eventId: EventId;
reason: DenyReason;
text: string;
}

The fixed list of DenyReason values is on Deny reasons.

Challenged<T>

On challenge, the tenant wants a fresh multi-factor sign-in before it decides. Call stepUp(). It runs the sign-in adapter and returns a second Decided<T> for the same request, with its own event id.

interface Challenged<T> {
outcome: 'challenge';
eventId: EventId;
stepUp(): Promise<Decided<T>>;
}

A stepUp() can itself resolve to another challenge. Handle it recursively, as the allowed helper below does. This covers every case without special handling for a second round.

SealdHealthcareError

A decision is never an error. An error is a fault or a state your application must handle:

  • the device is locked
  • no session is open
  • the device was revoked
  • the release is below the version floor
  • the Seald Healthcare Cloud is unreachable
  • a stored object or a device certificate (card) failed its check

SealdHealthcareError carries one fixed code and a retryable flag. retryable says whether the same call may succeed later with no change on your side.

interface SealdHealthcareError extends Error {
code: ErrorCode;
retryable: boolean;
}

The full list of codes is on Errors.

A decision, not an error

A Decided<T> and a SealdHealthcareError answer different questions. A Decided<T> says whether the tenant’s active policies let this person do this thing, on this device, now. It always gives a meaningful answer. A SealdHealthcareError says that the SDK could not ask. For example, the device is locked, the network is gone or the release is too old. Handle a Decided<T> by outcome. Handle a SealdHealthcareError by code.

The allowed helper

Most application code needs only the value on allow. The allowed helper reduces a Decided<T> to that value. It follows a challenge until it resolves and shows the reason on a deny.

async function allowed<T>(decided: Decided<T>): Promise<Allowed<T> | undefined> {
if (decided.outcome === 'challenge') return allowed(await decided.stepUp());
if (decided.outcome === 'deny') { showReason(decided.reason, decided.text); return undefined; }
return decided;
}

The guides use allowed to keep examples short. You can handle the three decisions by hand instead. Quickstart does this once, to show what allowed does.

Next