@prove-identity/prove-auth 2.4.5 → 2.7.1

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.
Files changed (37) hide show
  1. package/README.md +35 -0
  2. package/build/lib/index.d.ts +3 -1
  3. package/build/lib/index.js +3 -1
  4. package/build/lib/proveauth/authenticator-builder.d.ts +12 -2
  5. package/build/lib/proveauth/authenticator-builder.js +114 -7
  6. package/build/lib/proveauth/device-context-options.d.ts +19 -0
  7. package/build/lib/proveauth/device-context-options.js +19 -0
  8. package/build/lib/proveauth/internal/auth-request.d.ts +12 -0
  9. package/build/lib/proveauth/internal/auth-response.d.ts +1 -0
  10. package/build/lib/proveauth/internal/auth-session.d.ts +12 -8
  11. package/build/lib/proveauth/internal/auth-session.js +94 -20
  12. package/build/lib/proveauth/internal/auth-token-claims.d.ts +4 -0
  13. package/build/lib/proveauth/internal/device-auth.d.ts +4 -1
  14. package/build/lib/proveauth/internal/device-passive-register-step.js +10 -9
  15. package/build/lib/proveauth/internal/device-passive-silent-step.d.ts +5 -3
  16. package/build/lib/proveauth/internal/device-passive-silent-step.js +30 -11
  17. package/build/lib/proveauth/internal/device-passive-verify-step.d.ts +2 -0
  18. package/build/lib/proveauth/internal/device-passive-verify-step.js +18 -0
  19. package/build/lib/proveauth/internal/device-universal-redirect-steps.js +2 -2
  20. package/build/lib/proveauth/internal/main-authenticator.d.ts +2 -3
  21. package/build/lib/proveauth/internal/main-authenticator.js +3 -10
  22. package/build/lib/proveauth/internal/mobile-instantlink-step.js +6 -4
  23. package/build/lib/proveauth/internal/platform.d.ts +5 -1
  24. package/build/lib/proveauth/internal/report-error-step.d.ts +2 -2
  25. package/build/lib/proveauth/internal/report-error-step.js +5 -5
  26. package/build/lib/proveauth/internal/settings.d.ts +1 -0
  27. package/build/lib/proveauth/internal/settings.js +1 -0
  28. package/build/lib/proveauth/internal/web-device-auth.d.ts +4 -1
  29. package/build/lib/proveauth/internal/web-device-auth.js +6 -0
  30. package/build/lib/proveauth/internal/web-platform.d.ts +7 -1
  31. package/build/lib/proveauth/internal/web-platform.js +11 -2
  32. package/build/lib/proveauth/user-consent-step.d.ts +7 -0
  33. package/build/lib/proveauth/user-consent-step.js +2 -0
  34. package/build/lib/proveauth/version.d.ts +2 -2
  35. package/build/lib/proveauth/version.js +2 -2
  36. package/package.json +9 -1
  37. package/build/bundle/release/prove-auth.js +0 -1
package/README.md CHANGED
@@ -15,3 +15,38 @@ This SDK provides JavaScript client API to the Prove's authentication platform P
15
15
  - Build the sdk first, then build the sample code:
16
16
  - Build SDK web bundle - `npm run clean && npm run build && npm run bundle-dev`
17
17
  - Build and start sample code - `cd samples/basic; npm run build && npm run serve`
18
+
19
+ ## Running Unit Tests
20
+
21
+ This project provides several options for running unit tests. Follow these instructions to execute the tests:
22
+
23
+ ### Standard Test Run
24
+
25
+ To run the standard test suite:
26
+
27
+ ```
28
+ npm test
29
+ ```
30
+
31
+ This command will execute all unit tests and display the results in your default web browser.
32
+
33
+ ### Running Tests with Node
34
+
35
+ If you need to run tests in a Node.js environment:
36
+
37
+ ```
38
+ npm run test-with-node
39
+ ```
40
+
41
+ This command will execute all unit tests and display the results in your terminal.
42
+
43
+ ### Using the Test Runner
44
+
45
+ To run integration tests using Test Runner:
46
+
47
+ npm run test-runner
48
+
49
+ This command uses Test Runner client to execute integration tests enabled for the Web SDK.
50
+ Execution of integration tests depends on the Test Runner server components, which are part of
51
+ (Garfield backend service)[https://gitlab.com/prove-identity/prove-auth/garfield], and must be
52
+ started in advance using `make test-localhost-up` command in that repo.
@@ -1,5 +1,6 @@
1
1
  import { VERSION } from './proveauth/version';
2
2
  import AuthenticatorBuilder, { AuthMessageHandler, DeviceRole, MobileAuthImplementation } from './proveauth/authenticator-builder';
3
+ import DeviceContextOptions, { BuildConfig } from './proveauth/device-context-options';
3
4
  import AuthMessage from './proveauth/internal/auth-message';
4
5
  import { AuthResponseStatus } from './proveauth/internal/auth-response-status';
5
6
  import { LoggerFactory, LogWriter, LogLevel, Logger } from './proveauth/common/logger';
@@ -9,4 +10,5 @@ import Authenticator from './proveauth/authenticator';
9
10
  import { PhoneValidationError } from './proveauth/internal/phone-number-input';
10
11
  import { OtpError, OtpStartStep, OtpStartInput, OtpStartStepFn, OtpFinishStep, OtpFinishInput, OtpFinishStepFn, OtpFinishResult, OtpFinishResultType } from './proveauth/otp';
11
12
  import { InstantLinkStartInput, InstantLinkStartStep, InstantLinkStartStepFn } from './proveauth/instantlink';
12
- export { VERSION, AuthFinishStep, AuthFinishStepFn, AuthFinishStepInput, Authenticator, AuthenticatorBuilder, AuthMessage, AuthMessageHandler, AuthResponseStatus, CancelablePromise, DeviceRole, InstantLinkStartStep, InstantLinkStartInput, InstantLinkStartStepFn, Logger, LoggerFactory, LogLevel, LogWriter, MobileAuthImplementation, OtpFinishStep, OtpFinishInput, OtpFinishResult, OtpFinishStepFn, OtpFinishResultType, OtpStartStep, OtpStartInput, OtpStartStepFn, OtpError, PhoneValidationError, };
13
+ import UserConsentStep, { UserConsentOutput, UserConsentStepFn } from './proveauth/user-consent-step';
14
+ export { VERSION, AuthFinishStep, AuthFinishStepFn, AuthFinishStepInput, Authenticator, AuthenticatorBuilder, AuthMessage, AuthMessageHandler, AuthResponseStatus, CancelablePromise, DeviceRole, InstantLinkStartStep, InstantLinkStartInput, InstantLinkStartStepFn, Logger, LoggerFactory, LogLevel, LogWriter, MobileAuthImplementation, OtpFinishStep, OtpFinishInput, OtpFinishResult, OtpFinishStepFn, OtpFinishResultType, OtpStartStep, OtpStartInput, OtpStartStepFn, OtpError, PhoneValidationError, BuildConfig, DeviceContextOptions, UserConsentStep, UserConsentStepFn, UserConsentOutput, };
@@ -26,13 +26,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
26
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.PhoneValidationError = exports.OtpError = exports.OtpFinishResultType = exports.MobileAuthImplementation = exports.LogLevel = exports.LoggerFactory = exports.DeviceRole = exports.CancelablePromise = exports.AuthResponseStatus = exports.AuthenticatorBuilder = exports.VERSION = void 0;
29
+ exports.BuildConfig = exports.PhoneValidationError = exports.OtpError = exports.OtpFinishResultType = exports.MobileAuthImplementation = exports.LogLevel = exports.LoggerFactory = exports.DeviceRole = exports.CancelablePromise = exports.AuthResponseStatus = exports.AuthenticatorBuilder = exports.VERSION = void 0;
30
30
  const version_1 = require("./proveauth/version");
31
31
  Object.defineProperty(exports, "VERSION", { enumerable: true, get: function () { return version_1.VERSION; } });
32
32
  const authenticator_builder_1 = __importStar(require("./proveauth/authenticator-builder"));
33
33
  exports.AuthenticatorBuilder = authenticator_builder_1.default;
34
34
  Object.defineProperty(exports, "DeviceRole", { enumerable: true, get: function () { return authenticator_builder_1.DeviceRole; } });
35
35
  Object.defineProperty(exports, "MobileAuthImplementation", { enumerable: true, get: function () { return authenticator_builder_1.MobileAuthImplementation; } });
36
+ const device_context_options_1 = require("./proveauth/device-context-options");
37
+ Object.defineProperty(exports, "BuildConfig", { enumerable: true, get: function () { return device_context_options_1.BuildConfig; } });
36
38
  const auth_response_status_1 = require("./proveauth/internal/auth-response-status");
37
39
  Object.defineProperty(exports, "AuthResponseStatus", { enumerable: true, get: function () { return auth_response_status_1.AuthResponseStatus; } });
38
40
  const logger_1 = require("./proveauth/common/logger");
@@ -5,6 +5,9 @@ import { AuthResponseStatus } from './internal/auth-response-status';
5
5
  import Platform from './internal/platform';
6
6
  import { OtpFinishStep, OtpFinishStepFn, OtpStartStep, OtpStartStepFn } from './otp';
7
7
  import { InstantLinkStartStep, InstantLinkStartStepFn } from './instantlink';
8
+ import type { Region } from '@fingerprintjs/fingerprintjs-pro';
9
+ import DeviceContextOptions, { BuildConfig } from './device-context-options';
10
+ import UserConsentStep, { UserConsentStepFn } from './user-consent-step';
8
11
  export type AuthMessageHandler = (message: AuthMessage) => Promise<AuthResponseStatus>;
9
12
  export declare enum DeviceRole {
10
13
  Primary = 0,
@@ -26,9 +29,12 @@ export default class AuthenticatorBuilder {
26
29
  private otpStartStep?;
27
30
  private otpFinishStep?;
28
31
  private instantLinkStartStep?;
29
- private forUPK;
30
- private instantLinkTestMode;
32
+ private upkEnabled;
33
+ private userConsentStep?;
34
+ private readonly log;
35
+ private deviceContextOptions?;
31
36
  constructor();
37
+ withDeviceContext(options: DeviceContextOptions): AuthenticatorBuilder;
32
38
  withAuthFinishStep(step: AuthFinishStep | AuthFinishStepFn): AuthenticatorBuilder;
33
39
  withDisplayName(displayName: string | (() => string | null) | null): AuthenticatorBuilder;
34
40
  withAuthMessageHandler(handler: AuthMessageHandler): AuthenticatorBuilder;
@@ -40,5 +46,9 @@ export default class AuthenticatorBuilder {
40
46
  withOtpFallback(startStep: OtpStartStep | OtpStartStepFn, finishStep: OtpFinishStep | OtpFinishStepFn): AuthenticatorBuilder;
41
47
  withInstantLinkFallback(startStep: InstantLinkStartStep | InstantLinkStartStepFn): AuthenticatorBuilder;
42
48
  withUPKEnabled(): this;
49
+ withUniversalProveKey(step?: UserConsentStep | UserConsentStepFn): this;
50
+ getUrlsByBuildConfig(buildConfig?: BuildConfig): [string | undefined, string | undefined];
51
+ getRegionByBuildConfig(buildConfig?: BuildConfig): Region;
52
+ private getFpPromiseInstanceFromOptions;
43
53
  build(): Authenticator;
44
54
  }
@@ -18,6 +18,9 @@ const device_passive_stepup_step_1 = __importDefault(require("./internal/device-
18
18
  const device_universal_step_1 = __importDefault(require("./internal/device-universal-step"));
19
19
  const device_universal_redirect_steps_1 = require("./internal/device-universal-redirect-steps");
20
20
  const main_authenticator_1 = __importDefault(require("./internal/main-authenticator"));
21
+ const logger_1 = require("./common/logger");
22
+ const settings_1 = __importDefault(require("./internal/settings"));
23
+ const device_context_options_1 = require("./device-context-options");
21
24
  var DeviceRole;
22
25
  (function (DeviceRole) {
23
26
  DeviceRole[DeviceRole["Primary"] = 0] = "Primary";
@@ -32,13 +35,17 @@ class AuthenticatorBuilder {
32
35
  constructor() {
33
36
  this.role = DeviceRole.Primary;
34
37
  this.mobileAuthImplementation = MobileAuthImplementation.Fetch;
35
- this.forUPK = false;
36
- this.instantLinkTestMode = false;
38
+ this.upkEnabled = false;
39
+ this.log = logger_1.LoggerFactory.getLogger('authenticator-builder');
37
40
  if (typeof window !== 'undefined') {
38
41
  this.storage = window.localStorage;
39
42
  this.platform = new web_platform_1.WebPlatform();
40
43
  }
41
44
  }
45
+ withDeviceContext(options) {
46
+ this.deviceContextOptions = options;
47
+ return this;
48
+ }
42
49
  withAuthFinishStep(step) {
43
50
  if (typeof step === 'function') {
44
51
  this.authFinishStep = {
@@ -103,18 +110,118 @@ class AuthenticatorBuilder {
103
110
  return this;
104
111
  }
105
112
  withUPKEnabled() {
106
- this.forUPK = true;
113
+ return this.withUniversalProveKey();
114
+ }
115
+ withUniversalProveKey(step) {
116
+ if (!step) {
117
+ this.userConsentStep = { execute: () => Promise.resolve({ consentGranted: true }) };
118
+ }
119
+ else if (typeof step === 'function') {
120
+ this.userConsentStep = { execute: step };
121
+ }
122
+ else {
123
+ this.userConsentStep = step;
124
+ }
125
+ this.upkEnabled = this.userConsentStep != null;
107
126
  return this;
108
127
  }
128
+ getUrlsByBuildConfig(buildConfig) {
129
+ switch (buildConfig) {
130
+ case device_context_options_1.BuildConfig.US_PROD:
131
+ return [
132
+ device_context_options_1.ProveAuthProxyScriptUrl.DEFAULT_US_PROD_SCRIPT_URL.toString(),
133
+ device_context_options_1.ProveAuthProxyEndpoint.DEFAULT_US_PROD_ENDPOINT.toString(),
134
+ ];
135
+ case device_context_options_1.BuildConfig.US_UAT:
136
+ return [
137
+ device_context_options_1.ProveAuthProxyScriptUrl.DEFAULT_US_UAT_SCRIPT_URL.toString(),
138
+ device_context_options_1.ProveAuthProxyEndpoint.DEFAULT_US_UAT_ENDPOINT.toString(),
139
+ ];
140
+ case device_context_options_1.BuildConfig.DEV:
141
+ this.log.debug("Recommended for Prove's internal testing only, BuildConfig.DEV might need custom endpoint URL and custom script URL values to bypass ad blockers");
142
+ return [undefined, undefined];
143
+ default:
144
+ this.log.warn('Unknown BuildConfig value: ' + buildConfig);
145
+ return [undefined, undefined];
146
+ }
147
+ }
148
+ getRegionByBuildConfig(buildConfig) {
149
+ var region;
150
+ switch (buildConfig) {
151
+ case device_context_options_1.BuildConfig.DEV:
152
+ case device_context_options_1.BuildConfig.US_PROD:
153
+ case device_context_options_1.BuildConfig.US_UAT:
154
+ region = 'us';
155
+ break;
156
+ default:
157
+ this.log.warn('Unknown BuildConfig value, set Region to default value: us');
158
+ region = 'us';
159
+ break;
160
+ }
161
+ return region;
162
+ }
163
+ getFpPromiseInstanceFromOptions(options) {
164
+ try {
165
+ const FingerprintJS = require('@fingerprintjs/fingerprintjs-pro');
166
+ if (!FingerprintJS) {
167
+ this.log.debug('fingerprintjs package is not installed or failed to load');
168
+ }
169
+ else if (!options) {
170
+ this.log.warn('Prove Key Persistence feature is not enabled');
171
+ }
172
+ else {
173
+ let region = this.getRegionByBuildConfig(options.buildConfig);
174
+ let [scriptUrl, endpointUrl] = this.getUrlsByBuildConfig(options.buildConfig);
175
+ if (options.customScriptUrl && options.customEndpointUrl) {
176
+ scriptUrl = options.customScriptUrl;
177
+ endpointUrl = options.customEndpointUrl;
178
+ }
179
+ const scriptUrlPattern = scriptUrl
180
+ ? [
181
+ `${scriptUrl}?apiKey=<apiKey>&version=<version>&loaderVersion=<loaderVersion>`,
182
+ FingerprintJS.defaultScriptUrlPattern,
183
+ ]
184
+ : [FingerprintJS.defaultScriptUrlPattern];
185
+ const endpoint = endpointUrl
186
+ ? [`${endpointUrl}?region=${region}`, FingerprintJS.defaultEndpoint]
187
+ : [FingerprintJS.defaultEndpoint];
188
+ const fpPromise = FingerprintJS.load({
189
+ apiKey: options.publicApiKey,
190
+ endpoint: endpoint,
191
+ scriptUrlPattern: scriptUrlPattern,
192
+ region: region,
193
+ });
194
+ const status = fpPromise ? 'successfully' : 'unsuccessfully with null instance';
195
+ this.log.trace(`Instantiating FingerprintJS ${status}`);
196
+ return fpPromise;
197
+ }
198
+ }
199
+ catch (error) {
200
+ this.log.trace('FingerprintJS is not installed or failed to load', error);
201
+ }
202
+ }
109
203
  build() {
204
+ var _a;
205
+ if (!this.platform) {
206
+ throw new Error('Implementation of Platform is required');
207
+ }
208
+ if (!this.storage) {
209
+ throw new Error('Implementation of Storage is required');
210
+ }
211
+ const fpPromise = this.getFpPromiseInstanceFromOptions(this.deviceContextOptions);
212
+ if (fpPromise) {
213
+ (_a = this.platform) === null || _a === void 0 ? void 0 : _a.setFpPromise(fpPromise);
214
+ }
215
+ const settings = new settings_1.default(this.storage);
216
+ settings.upkEnabled = this.upkEnabled;
110
217
  if (this.role === DeviceRole.Primary) {
111
- return new main_authenticator_1.default(this.platform, this.storage, this.authFinishStep, [
112
- new device_universal_step_1.default(this.forUPK),
218
+ return new main_authenticator_1.default(this.platform, settings, this.authFinishStep, [
219
+ new device_universal_step_1.default(this.upkEnabled),
113
220
  new device_universal_redirect_steps_1.DeviceUniversalRedirectExchangeStep(),
114
221
  new device_universal_redirect_steps_1.DeviceUniversalRedirectFinishStep(),
115
222
  new device_passive_step_1.default(this.getDisplayName, this.role),
116
223
  new device_passive_stepup_step_1.default(this.getDisplayName),
117
- new device_passive_silent_step_1.default(this.forUPK),
224
+ new device_passive_silent_step_1.default(this.upkEnabled, this.userConsentStep),
118
225
  new device_passive_register_step_1.default(),
119
226
  new device_passive_verify_step_1.default(),
120
227
  new mobile_instant_step_1.default(this.mobileAuthImplementation, this.getDeviceIp),
@@ -125,7 +232,7 @@ class AuthenticatorBuilder {
125
232
  ]);
126
233
  }
127
234
  else {
128
- return new main_authenticator_1.default(this.platform, this.storage, this.authFinishStep, [
235
+ return new main_authenticator_1.default(this.platform, settings, this.authFinishStep, [
129
236
  new device_passive_step_1.default(this.getDisplayName, this.role),
130
237
  new mobile_instant_step_1.default(this.mobileAuthImplementation, this.getDeviceIp),
131
238
  new mobile_instantlink_step_1.default(this.instantLinkStartStep, this.getDeviceIp),
@@ -0,0 +1,19 @@
1
+ export declare enum ProveAuthProxyScriptUrl {
2
+ DEFAULT_US_UAT_SCRIPT_URL = "https://upk.uat.prove-auth.proveapis.com/vFqZceQyx8/uqLttozA7q",
3
+ DEFAULT_US_PROD_SCRIPT_URL = "https://upk.prove-auth.proveapis.com/vf82rhgDRK/r4VnwuwPUd"
4
+ }
5
+ export declare enum ProveAuthProxyEndpoint {
6
+ DEFAULT_US_UAT_ENDPOINT = "https://upk.uat.prove-auth.proveapis.com/vFqZceQyx8/bt9xhGAgQw",
7
+ DEFAULT_US_PROD_ENDPOINT = "https://upk.prove-auth.proveapis.com/vf82rhgDRK/ePaZsNne4X"
8
+ }
9
+ export declare enum BuildConfig {
10
+ DEV = "DEV",
11
+ US_UAT = "US_UAT",
12
+ US_PROD = "US_PROD"
13
+ }
14
+ export default interface DeviceContextOptions {
15
+ readonly buildConfig: BuildConfig;
16
+ readonly publicApiKey: string;
17
+ readonly customScriptUrl?: string;
18
+ readonly customEndpointUrl?: string;
19
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BuildConfig = exports.ProveAuthProxyEndpoint = exports.ProveAuthProxyScriptUrl = void 0;
4
+ var ProveAuthProxyScriptUrl;
5
+ (function (ProveAuthProxyScriptUrl) {
6
+ ProveAuthProxyScriptUrl["DEFAULT_US_UAT_SCRIPT_URL"] = "https://upk.uat.prove-auth.proveapis.com/vFqZceQyx8/uqLttozA7q";
7
+ ProveAuthProxyScriptUrl["DEFAULT_US_PROD_SCRIPT_URL"] = "https://upk.prove-auth.proveapis.com/vf82rhgDRK/r4VnwuwPUd";
8
+ })(ProveAuthProxyScriptUrl = exports.ProveAuthProxyScriptUrl || (exports.ProveAuthProxyScriptUrl = {}));
9
+ var ProveAuthProxyEndpoint;
10
+ (function (ProveAuthProxyEndpoint) {
11
+ ProveAuthProxyEndpoint["DEFAULT_US_UAT_ENDPOINT"] = "https://upk.uat.prove-auth.proveapis.com/vFqZceQyx8/bt9xhGAgQw";
12
+ ProveAuthProxyEndpoint["DEFAULT_US_PROD_ENDPOINT"] = "https://upk.prove-auth.proveapis.com/vf82rhgDRK/ePaZsNne4X";
13
+ })(ProveAuthProxyEndpoint = exports.ProveAuthProxyEndpoint || (exports.ProveAuthProxyEndpoint = {}));
14
+ var BuildConfig;
15
+ (function (BuildConfig) {
16
+ BuildConfig["DEV"] = "DEV";
17
+ BuildConfig["US_UAT"] = "US_UAT";
18
+ BuildConfig["US_PROD"] = "US_PROD";
19
+ })(BuildConfig = exports.BuildConfig || (exports.BuildConfig = {}));
@@ -32,6 +32,7 @@ export interface V1ClientDeviceFido2RegisterFinish {
32
32
  deviceName: string;
33
33
  deviceCapabilities: string[];
34
34
  registrations: AuthRegistration[];
35
+ signals?: Signals;
35
36
  }
36
37
  export interface V1ClientDeviceFido2VerifyStart {
37
38
  deviceId: string;
@@ -50,16 +51,19 @@ export interface WebAuthnAssertion {
50
51
  }
51
52
  export interface V1ClientDeviceFido2VerifyFinish {
52
53
  webAuthnAssertion: WebAuthnAssertion;
54
+ signals?: Signals;
53
55
  }
54
56
  export interface V1ClientDevicePassiveRegister {
55
57
  deviceName: string;
56
58
  deviceCapabilities: string[];
57
59
  registrations: PassiveRegistration[];
60
+ signals?: Signals;
58
61
  }
59
62
  export interface V1ClientDevicePassiveVerify {
60
63
  deviceId: string;
61
64
  keyId: string;
62
65
  signature: string;
66
+ signals?: Signals;
63
67
  }
64
68
  export interface V1ClientUserResponse {
65
69
  response: AuthResponseStatus;
@@ -88,3 +92,11 @@ export interface V1ClientOtpStart {
88
92
  export interface V1ClientOtpFinish {
89
93
  otp: string;
90
94
  }
95
+ export interface Signals {
96
+ fingerprint?: Signal;
97
+ darwinium?: Signal;
98
+ }
99
+ export interface Signal {
100
+ results?: string;
101
+ error?: string;
102
+ }
@@ -16,6 +16,7 @@ export interface RegisterStartAuthResponse extends AuthResponse {
16
16
  }
17
17
  export interface RegisterFinishAuthResponseData {
18
18
  deviceId: string;
19
+ passkey: boolean;
19
20
  }
20
21
  export interface RegisterFinishAuthResponse extends AuthResponse {
21
22
  data: RegisterFinishAuthResponseData;
@@ -1,6 +1,6 @@
1
1
  /// <reference types="webappsec-credential-management" />
2
2
  import AuthMessage from './auth-message';
3
- import { AuthRequest } from './auth-request';
3
+ import { AuthRequest, Signal } from './auth-request';
4
4
  import AuthResponse from './auth-response';
5
5
  import AuthTokenClaims, { UserVerificationLevel } from './auth-token-claims';
6
6
  import { DeviceRegistration } from './device-auth';
@@ -13,20 +13,24 @@ export default class AuthSession implements AuthSessionIntegration {
13
13
  readonly settings: Settings;
14
14
  readonly requestSigner: RequestSigner;
15
15
  readonly channels: Set<MessageChannel>;
16
- lastStep: string | null;
17
- credential: CredentialType | null;
18
- authMessage: AuthMessage | null;
19
- uvLevel: UserVerificationLevel | null;
20
- backendUrlOverride: string | null;
16
+ private readonly log;
17
+ lastStep?: string;
18
+ credential?: CredentialType;
19
+ authMessage?: AuthMessage;
20
+ uvLevel?: UserVerificationLevel;
21
+ backendOriginOverride?: string;
21
22
  get namespace(): string;
22
- get backendUrl(): string;
23
+ get backendOrigin(): string;
23
24
  get authId(): string;
24
25
  get challenge(): string;
25
26
  get next(): string;
26
27
  constructor(settings: Settings, platform: Platform, authToken?: string);
27
28
  fetchFromBackend(query: string, body: AuthRequest): Promise<AuthResponse>;
28
- createMessageChannel(endpointPath: string, onClose: () => void, onError: (message: string) => void, onMessage: (data: string) => void): MessageChannel;
29
+ createMessageChannel(query: string, onClose: () => void, onError: (message: string) => void, onMessage: (data: string) => void): MessageChannel;
29
30
  closeAllMessageChannels(): void;
30
31
  getDeviceRegistration(): Promise<DeviceRegistration | null>;
32
+ embedFpResultToDeviceRegistration(registration: DeviceRegistration): Promise<DeviceRegistration>;
33
+ getFingerprintData(): Promise<Signal | undefined>;
34
+ shouldCollectFP(): boolean;
31
35
  private parseJwt;
32
36
  }
@@ -12,9 +12,9 @@ class AuthSession {
12
12
  var _a;
13
13
  return ((_a = this.claims) === null || _a === void 0 ? void 0 : _a.auth.ans) || this.settings.namespace;
14
14
  }
15
- get backendUrl() {
15
+ get backendOrigin() {
16
16
  var _a;
17
- return ((_a = this.claims) === null || _a === void 0 ? void 0 : _a.auth.endp) || this.backendUrlOverride;
17
+ return this.backendOriginOverride || ((_a = this.claims) === null || _a === void 0 ? void 0 : _a.auth.endp);
18
18
  }
19
19
  get authId() {
20
20
  var _a;
@@ -31,11 +31,7 @@ class AuthSession {
31
31
  constructor(settings, platform, authToken) {
32
32
  var _a, _b;
33
33
  this.channels = new Set();
34
- this.lastStep = null;
35
- this.credential = null;
36
- this.authMessage = null;
37
- this.uvLevel = null;
38
- this.backendUrlOverride = null;
34
+ this.log = logger_1.LoggerFactory.getLogger('auth-session');
39
35
  this.platform = platform;
40
36
  this.authToken = authToken;
41
37
  this.settings = settings;
@@ -73,7 +69,7 @@ class AuthSession {
73
69
  headers.set('PA-Signature', signature.signature);
74
70
  }
75
71
  this.platform
76
- .fetch(this.backendUrl + query, {
72
+ .fetch(this.backendOrigin + query, {
77
73
  mode: 'cors',
78
74
  method: method,
79
75
  headers: headers,
@@ -94,41 +90,56 @@ class AuthSession {
94
90
  .catch(reject);
95
91
  });
96
92
  }
97
- createMessageChannel(endpointPath, onClose, onError, onMessage) {
93
+ createMessageChannel(query, onClose, onError, onMessage) {
98
94
  if (!this.authToken) {
99
95
  throw new Error('Authentication token is not initialized, cannot create MessageChannel');
100
96
  }
101
97
  const KEEP_ALIVE_INTERVAL = 30000;
102
- const endpoint = this.backendUrl.replace(/^http/, 'ws');
103
- const channel = this.platform.createMessageChannel(endpoint + endpointPath);
104
- const log = logger_1.LoggerFactory.getLogger('web-message-channel');
98
+ const endpoint = this.backendOrigin.replace(/^http/, 'ws');
99
+ const channel = this.platform.createMessageChannel(endpoint + query);
100
+ var pongReceived = true;
105
101
  const keepAlive = setInterval(() => {
106
- log.trace('Sending keep-alive message');
107
- channel.send('');
108
- }, KEEP_ALIVE_INTERVAL);
109
- channel.addEventListener('close', (_) => {
110
- if (keepAlive) {
102
+ if (pongReceived) {
103
+ this.log.trace('Sending ping message');
104
+ pongReceived = false;
105
+ channel.send('ping');
106
+ }
107
+ else {
108
+ this.log.error('Failed to receive ping response in time, closing the channel');
111
109
  clearInterval(keepAlive);
110
+ channel.close();
111
+ onError('Channel communication stalled');
112
+ this.channels.delete(channel);
112
113
  }
114
+ }, KEEP_ALIVE_INTERVAL);
115
+ channel.addEventListener('close', (_) => {
116
+ clearInterval(keepAlive);
113
117
  onClose();
114
118
  this.channels.delete(channel);
115
119
  });
116
120
  channel.addEventListener('error', (event) => {
121
+ clearInterval(keepAlive);
117
122
  if ('message' in event) {
118
123
  onError(event['message']);
119
124
  }
120
125
  else {
121
126
  onError(event.toString());
122
127
  }
128
+ this.channels.delete(channel);
123
129
  });
124
130
  channel.addEventListener('message', (event) => {
125
131
  if ('origin' in event && event['origin'] !== endpoint) {
126
132
  onError('Unexpected origin');
127
133
  }
128
134
  else {
129
- var data = event.data;
135
+ const data = event.data;
130
136
  if (data && typeof data === 'string') {
131
- onMessage(data);
137
+ if (data === 'pong') {
138
+ pongReceived = true;
139
+ }
140
+ else {
141
+ onMessage(data);
142
+ }
132
143
  }
133
144
  else {
134
145
  onMessage(event.toString());
@@ -150,13 +161,76 @@ class AuthSession {
150
161
  .getRegistration(this.namespace)
151
162
  .then((registration) => {
152
163
  if (registration) {
153
- this.backendUrlOverride = registration === null || registration === void 0 ? void 0 : registration.endpoint;
164
+ if (!this.backendOriginOverride) {
165
+ this.backendOriginOverride = registration.endpoint;
166
+ this.log.debug('backend URL overridden with ' + this.backendOriginOverride);
167
+ }
168
+ else {
169
+ this.log.debug('Not overriding backend URL since it has been already set');
170
+ }
154
171
  }
155
172
  resolve(registration);
156
173
  })
157
174
  .catch(reject);
158
175
  });
159
176
  }
177
+ embedFpResultToDeviceRegistration(registration) {
178
+ return new Promise((resolve) => {
179
+ this.getFingerprintData()
180
+ .then((result) => {
181
+ if (result) {
182
+ registration.setFpSignal(result);
183
+ }
184
+ resolve(registration);
185
+ })
186
+ .catch((error) => {
187
+ const errorMsg = `Unexpected error happened during Fingerprint data collection: ${error.toString}`;
188
+ this.log.warn(errorMsg);
189
+ registration.setFpSignal({ error: errorMsg });
190
+ });
191
+ });
192
+ }
193
+ getFingerprintData() {
194
+ return new Promise((resolve) => {
195
+ var fpPromise = this.platform.getFpPromise();
196
+ if (!this.shouldCollectFP()) {
197
+ this.log.debug('Fingerprint is not enabled from AuthToken');
198
+ resolve(undefined);
199
+ }
200
+ else if (!fpPromise) {
201
+ const msg = 'Found null instance of Fingerprint, check if your input API key is valid';
202
+ this.log.warn(msg);
203
+ resolve({ error: msg });
204
+ }
205
+ else {
206
+ fpPromise
207
+ .then((fp) => fp.get())
208
+ .then((result) => {
209
+ if (result.sealedResult) {
210
+ this.log.debug(`FP result: ${result.sealedResult}`);
211
+ resolve({ results: result.sealedResult });
212
+ }
213
+ else {
214
+ const msg = 'Cannot found sealed result in Fingerprint returned payload';
215
+ this.log.warn(msg);
216
+ resolve({ error: msg });
217
+ }
218
+ })
219
+ .catch((error) => {
220
+ const msg = `Error in collecting Fingerprint data: ${error.toString}`;
221
+ this.log.warn(msg);
222
+ resolve({ error: msg });
223
+ });
224
+ }
225
+ });
226
+ }
227
+ shouldCollectFP() {
228
+ var _a, _b, _c;
229
+ if ((_c = (_b = (_a = this.claims) === null || _a === void 0 ? void 0 : _a.auth.subs.dev) === null || _b === void 0 ? void 0 : _b.sgnls) === null || _c === void 0 ? void 0 : _c.fpt) {
230
+ return true;
231
+ }
232
+ return false;
233
+ }
160
234
  parseJwt(token) {
161
235
  return JSON.parse(atob(token.split('.')[1]));
162
236
  }
@@ -27,8 +27,12 @@ export interface Authenticators {
27
27
  otp?: OtpAuthenticator;
28
28
  unvsl?: UniversalAuthenticator;
29
29
  }
30
+ export interface Signals {
31
+ fpt?: boolean;
32
+ }
30
33
  export interface DeviceAuthSubjectClaim {
31
34
  auths: Authenticators;
35
+ sgnls?: Signals;
32
36
  }
33
37
  export interface MobileAuthSubjectClaim {
34
38
  auths: Authenticators;
@@ -1,4 +1,4 @@
1
- import { AuthRegistration } from './auth-request';
1
+ import { AuthRegistration, Signal, Signals } from './auth-request';
2
2
  export interface DeviceRegistrationOptions {
3
3
  namespace: string;
4
4
  endpoint: string;
@@ -9,9 +9,12 @@ export interface DeviceRegistration {
9
9
  readonly algorithm: string;
10
10
  readonly endpoint: string;
11
11
  deviceId: string | null;
12
+ fingerprint?: Signal;
12
13
  sign: (data: string) => Promise<string>;
13
14
  getPublicKey: () => Promise<string>;
14
15
  getAuthRegistration: (challenge: string) => Promise<AuthRegistration>;
16
+ setFpSignal: (fpSignal: Signal) => void;
17
+ getSignals: () => Signals | undefined;
15
18
  }
16
19
  export default interface DeviceAuth {
17
20
  createRegistration: (options: DeviceRegistrationOptions) => Promise<DeviceRegistration>;
@@ -18,23 +18,23 @@ class DevicePassiveRegisterStep {
18
18
  .getDeviceRegistration()
19
19
  .then((devRegistration) => {
20
20
  if (devRegistration) {
21
- this.finishRegistration(session, [this.getFido2Registration(session)])
22
- .then(resolve)
23
- .catch(reject);
21
+ session.embedFpResultToDeviceRegistration(devRegistration).then((devRegistration) => {
22
+ this.finishRegistration(session, [this.getFido2Registration(session)], devRegistration.getSignals())
23
+ .then(resolve)
24
+ .catch(reject);
25
+ });
24
26
  }
25
27
  else {
26
28
  session.platform.deviceAuth
27
29
  .createRegistration({
28
30
  namespace: session.namespace,
29
- endpoint: session.backendUrl,
31
+ endpoint: session.backendOrigin,
30
32
  })
33
+ .then((devRegistration) => session.embedFpResultToDeviceRegistration(devRegistration))
31
34
  .then((devRegistration) => {
32
35
  devRegistration
33
36
  .getAuthRegistration(session.challenge)
34
- .then((authRegistration) => this.finishRegistration(session, [
35
- this.getFido2Registration(session),
36
- authRegistration,
37
- ]))
37
+ .then((authRegistration) => this.finishRegistration(session, [this.getFido2Registration(session), authRegistration], devRegistration.getSignals()))
38
38
  .then((next) => {
39
39
  devRegistration.deviceId = session.settings.deviceId;
40
40
  session.platform.deviceAuth
@@ -50,13 +50,14 @@ class DevicePassiveRegisterStep {
50
50
  .catch(reject);
51
51
  });
52
52
  }
53
- finishRegistration(session, registrations) {
53
+ finishRegistration(session, registrations, signals) {
54
54
  return new Promise((resolve, reject) => {
55
55
  session
56
56
  .fetchFromBackend('/v1/client/device/fido2/register/finish', {
57
57
  deviceName: session.platform.getPlatformName(),
58
58
  deviceCapabilities: session.platform.getDeviceCapabilities(),
59
59
  registrations: registrations,
60
+ signals: signals,
60
61
  })
61
62
  .then((response) => {
62
63
  if (response.error) {