@ovok/core 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/dist/client/auth/methods/login.d.ts +2 -2
- package/dist/client/auth/methods/login.js +37 -0
- package/dist/client/auth/types/LoginResponse.d.ts +7 -0
- package/dist/client/auth/types/LoginResponse.js +2 -0
- package/dist/client/auth/types/Session.d.ts +2 -5
- package/dist/client/auth/types/StartLoginResponse.d.ts +8 -2
- package/dist/client/errors/rate-limit-error.js +25 -1
- package/dist/conformance/capability-requirements.js +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -90,3 +90,16 @@ await client.revokeSessions("other");
|
|
|
90
90
|
|
|
91
91
|
Pass `onUnauthenticated` in the normal Medplum client options to react when a request can no
|
|
92
92
|
longer refresh its session.
|
|
93
|
+
|
|
94
|
+
## Multi-factor authentication
|
|
95
|
+
|
|
96
|
+
When password login requires MFA, `login()` returns a verification step instead of an
|
|
97
|
+
`AuthResponse`. Submit the one-time code with `verify()` and use the returned `AuthResponse`:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const result = await client.login(loginBody);
|
|
101
|
+
|
|
102
|
+
if ("nextStep" in result && result.nextStep === "mfa") {
|
|
103
|
+
const authenticated = await result.verify(oneTimeCode);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { OvokClient } from "../../ovok-client";
|
|
2
|
-
import { AuthResponse } from "../types/AuthResponse";
|
|
3
2
|
import { LoginBody } from "../types/LoginBody";
|
|
4
|
-
|
|
3
|
+
import { LoginResponse } from "../types/LoginResponse";
|
|
4
|
+
export declare function login(this: OvokClient, body: LoginBody): Promise<LoginResponse>;
|
|
@@ -12,6 +12,43 @@ async function login(body) {
|
|
|
12
12
|
body,
|
|
13
13
|
codeChallenge,
|
|
14
14
|
});
|
|
15
|
+
if (startResponse.nextStep === "mfa") {
|
|
16
|
+
return {
|
|
17
|
+
nextStep: "mfa",
|
|
18
|
+
verify: async (code) => {
|
|
19
|
+
const tenantCode = body.type === "Practitioner" && !startResponse.profiles
|
|
20
|
+
? body.tenantCode
|
|
21
|
+
: (0, handle_tenant_code_1.handleTenantCode)({
|
|
22
|
+
profiles: startResponse.profiles,
|
|
23
|
+
tenantCode: body.tenantCode,
|
|
24
|
+
type: body.type,
|
|
25
|
+
});
|
|
26
|
+
const practitionerTenantCode = tenantCode;
|
|
27
|
+
if (body.type === "Practitioner" && !practitionerTenantCode) {
|
|
28
|
+
throw new Error("No tenant code found");
|
|
29
|
+
}
|
|
30
|
+
const mfaResponse = await this.post(`auth/tenant/${body.type}/login/mfa`, {
|
|
31
|
+
loginId: startResponse.loginId,
|
|
32
|
+
mfaToken: code,
|
|
33
|
+
}, "application/json");
|
|
34
|
+
if (body.type === "Practitioner") {
|
|
35
|
+
return (0, exchange_code_1.exchangeCode)({
|
|
36
|
+
client: this,
|
|
37
|
+
type: "Practitioner",
|
|
38
|
+
sessionCode: mfaResponse.sessionCode,
|
|
39
|
+
codeVerifier,
|
|
40
|
+
tenantCode: practitionerTenantCode,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return (0, exchange_code_1.exchangeCode)({
|
|
44
|
+
client: this,
|
|
45
|
+
type: "Patient",
|
|
46
|
+
sessionCode: mfaResponse.sessionCode,
|
|
47
|
+
codeVerifier,
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
15
52
|
const tenantCode = (0, handle_tenant_code_1.handleTenantCode)({
|
|
16
53
|
profiles: startResponse.profiles,
|
|
17
54
|
tenantCode: body.tenantCode,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { AuthResponse } from "./AuthResponse";
|
|
2
|
+
/** The second step returned when password login requires MFA verification. */
|
|
3
|
+
export type MfaLoginStep = {
|
|
4
|
+
nextStep: "mfa";
|
|
5
|
+
verify: (code: string) => Promise<AuthResponse>;
|
|
6
|
+
};
|
|
7
|
+
export type LoginResponse = AuthResponse | MfaLoginStep;
|
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
export interface OvokSession {
|
|
3
3
|
id: string;
|
|
4
4
|
authMethod?: string;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
os?: string;
|
|
8
|
-
createdAt?: string;
|
|
9
|
-
lastActiveAt?: string;
|
|
5
|
+
remoteAddress?: string;
|
|
6
|
+
lastUpdated?: string;
|
|
10
7
|
}
|
|
11
8
|
/** Session selector accepted by the session revocation endpoint. */
|
|
12
9
|
export type SessionRevokeOption = "current" | "other" | "all" | (string & {});
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { ContactPoint, HumanName, Patient, Practitioner, ProjectLink, Reference } from "@medplum/fhirtypes";
|
|
2
|
-
export
|
|
3
|
-
|
|
2
|
+
export type StartLoginResponse = TokenStartLoginResponse | MfaStartLoginResponse;
|
|
3
|
+
export interface TokenStartLoginResponse {
|
|
4
|
+
nextStep: "token";
|
|
4
5
|
sessionCode: string;
|
|
5
6
|
profiles?: ProfileElement[];
|
|
6
7
|
}
|
|
8
|
+
export interface MfaStartLoginResponse {
|
|
9
|
+
nextStep: "mfa";
|
|
10
|
+
loginId: string;
|
|
11
|
+
profiles?: ProfileElement[];
|
|
12
|
+
}
|
|
7
13
|
interface ProfileElement {
|
|
8
14
|
tenantCode: string;
|
|
9
15
|
project: Project;
|
|
@@ -4,6 +4,7 @@ exports.RateLimitError = void 0;
|
|
|
4
4
|
exports.isRateLimitError = isRateLimitError;
|
|
5
5
|
exports.mapRateLimitError = mapRateLimitError;
|
|
6
6
|
const core_1 = require("@medplum/core");
|
|
7
|
+
const RATE_LIMIT_RESET_EXTENSION_URL = "https://medplum.com/fhir/StructureDefinition/rate-limit-reset";
|
|
7
8
|
/** A typed error returned when the server asks the client to slow down. */
|
|
8
9
|
class RateLimitError extends Error {
|
|
9
10
|
constructor(retryAfterMs, options) {
|
|
@@ -31,10 +32,33 @@ function mapRateLimitError(error) {
|
|
|
31
32
|
return error;
|
|
32
33
|
}
|
|
33
34
|
if (error instanceof core_1.OperationOutcomeError && isRateLimitOutcome(error.outcome)) {
|
|
34
|
-
return new RateLimitError((
|
|
35
|
+
return new RateLimitError(rateLimitResetFromOutcome(error.outcome), { cause: error });
|
|
35
36
|
}
|
|
36
37
|
return error;
|
|
37
38
|
}
|
|
39
|
+
function rateLimitResetFromOutcome(outcome) {
|
|
40
|
+
var _a, _b, _c;
|
|
41
|
+
if (!outcome || typeof outcome !== "object") {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const record = outcome;
|
|
45
|
+
const extension = (_a = record.extension) === null || _a === void 0 ? void 0 : _a.find((candidate) => candidate.url === RATE_LIMIT_RESET_EXTENSION_URL);
|
|
46
|
+
if (typeof (extension === null || extension === void 0 ? void 0 : extension.valueUnsignedInt) === "number") {
|
|
47
|
+
return extension.valueUnsignedInt * 1000;
|
|
48
|
+
}
|
|
49
|
+
for (const issue of (_b = record.issue) !== null && _b !== void 0 ? _b : []) {
|
|
50
|
+
try {
|
|
51
|
+
const diagnostics = JSON.parse((_c = issue.diagnostics) !== null && _c !== void 0 ? _c : "");
|
|
52
|
+
if (typeof diagnostics.msBeforeNext === "number") {
|
|
53
|
+
return diagnostics.msBeforeNext;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (_d) {
|
|
57
|
+
// Diagnostics are optional and may be ordinary human-readable text.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
38
62
|
function isRateLimitOutcome(outcome) {
|
|
39
63
|
if (!(0, core_1.isOperationOutcome)(outcome)) {
|
|
40
64
|
return false;
|
|
@@ -48,7 +48,7 @@ exports.OVOK_CORE_REQUIREMENTS = {
|
|
|
48
48
|
description: "The FHIR surface a server must provide for the @ovok/core client library's own methods to function.",
|
|
49
49
|
software: {
|
|
50
50
|
name: "@ovok/core",
|
|
51
|
-
version: "0.3.
|
|
51
|
+
version: "0.3.2",
|
|
52
52
|
},
|
|
53
53
|
fhirVersion: "4.0.1",
|
|
54
54
|
format: ["json"],
|
package/dist/types/index.d.ts
CHANGED
package/dist/types/index.js
CHANGED
|
@@ -16,6 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
// Auth types
|
|
18
18
|
__exportStar(require("../client/auth/types/AuthResponse"), exports);
|
|
19
|
+
__exportStar(require("../client/auth/types/LoginResponse"), exports);
|
|
19
20
|
__exportStar(require("../client/auth/types/LoginBody"), exports);
|
|
20
21
|
__exportStar(require("../client/auth/types/RegisterBody"), exports);
|
|
21
22
|
__exportStar(require("../client/auth/types/ChangePasswordBody"), exports);
|