@prove-identity/prove-auth 2.8.2 → 2.9.0

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 (32) hide show
  1. package/README.md +8 -9
  2. package/build/bundle/release/prove-auth.js +1 -1
  3. package/build/lib/index.d.ts +2 -2
  4. package/build/lib/proveauth/authenticator-builder.d.ts +3 -0
  5. package/build/lib/proveauth/authenticator-builder.js +7 -3
  6. package/build/lib/proveauth/internal/auth-request.d.ts +1 -0
  7. package/build/lib/proveauth/internal/auth-response.d.ts +5 -1
  8. package/build/lib/proveauth/internal/auth-session.d.ts +3 -1
  9. package/build/lib/proveauth/internal/auth-session.js +50 -9
  10. package/build/lib/proveauth/internal/auth-status-actions.js +3 -3
  11. package/build/lib/proveauth/internal/device-passive-register-step.d.ts +1 -0
  12. package/build/lib/proveauth/internal/device-passive-register-step.js +55 -39
  13. package/build/lib/proveauth/internal/device-passive-silent-step.js +4 -0
  14. package/build/lib/proveauth/internal/device-passive-step.d.ts +12 -4
  15. package/build/lib/proveauth/internal/device-passive-step.js +172 -52
  16. package/build/lib/proveauth/internal/device-passive-stepup-step.d.ts +2 -1
  17. package/build/lib/proveauth/internal/device-passive-stepup-step.js +25 -3
  18. package/build/lib/proveauth/internal/device-passive-verify-step.d.ts +3 -2
  19. package/build/lib/proveauth/internal/device-passive-verify-step.js +29 -11
  20. package/build/lib/proveauth/internal/fido-options-error.d.ts +30 -0
  21. package/build/lib/proveauth/internal/fido-options-error.js +161 -0
  22. package/build/lib/proveauth/internal/main-authenticator.js +1 -1
  23. package/build/lib/proveauth/internal/mobile-instantlink-step.js +36 -29
  24. package/build/lib/proveauth/internal/mobile-otp-step.d.ts +3 -0
  25. package/build/lib/proveauth/internal/mobile-otp-step.js +115 -67
  26. package/build/lib/proveauth/internal/scan-message-step.js +1 -1
  27. package/build/lib/proveauth/internal/settings.js +1 -0
  28. package/build/lib/proveauth/internal/web-socket-close-reasons.d.ts +15 -0
  29. package/build/lib/proveauth/internal/web-socket-close-reasons.js +19 -0
  30. package/build/lib/proveauth/version.d.ts +1 -1
  31. package/build/lib/proveauth/version.js +1 -1
  32. package/package.json +1 -1
@@ -9,7 +9,8 @@ export interface AuthFailure {
9
9
  code: number;
10
10
  }
11
11
  export interface RegisterStartAuthResponseData {
12
- credCreateOptions: PublicKeyCredentialCreationOptions;
12
+ credCreateOptions?: PublicKeyCredentialCreationOptions;
13
+ credRequestOptions?: PublicKeyCredentialRequestOptions;
13
14
  }
14
15
  export interface RegisterStartAuthResponse extends AuthResponse {
15
16
  data: RegisterStartAuthResponseData;
@@ -17,6 +18,7 @@ export interface RegisterStartAuthResponse extends AuthResponse {
17
18
  export interface RegisterFinishAuthResponseData {
18
19
  deviceId: string;
19
20
  passkey: boolean;
21
+ scanMessage?: AuthMessage;
20
22
  }
21
23
  export interface RegisterFinishAuthResponse extends AuthResponse {
22
24
  data: RegisterFinishAuthResponseData;
@@ -30,6 +32,8 @@ export interface VerifyStartAuthResponse extends AuthResponse {
30
32
  }
31
33
  export interface VerifyFinishAuthResponseData {
32
34
  scanMessage?: AuthMessage;
35
+ deviceId?: string;
36
+ passkey?: boolean;
33
37
  }
34
38
  export interface VerifyFinishAuthResponse extends AuthResponse {
35
39
  data?: VerifyFinishAuthResponseData;
@@ -6,6 +6,7 @@ import AuthTokenClaims, { UserVerificationLevel } from './auth-token-claims';
6
6
  import { DeviceRegistration } from './device-auth';
7
7
  import Platform, { AuthSessionIntegration, MessageChannel, RequestSigner } from './platform';
8
8
  import Settings from './settings';
9
+ import { WebSocketCloseReasons } from './web-socket-close-reasons';
9
10
  export default class AuthSession implements AuthSessionIntegration {
10
11
  readonly platform: Platform;
11
12
  readonly authToken?: string;
@@ -26,7 +27,8 @@ export default class AuthSession implements AuthSessionIntegration {
26
27
  get next(): string;
27
28
  constructor(settings: Settings, platform: Platform, authToken?: string);
28
29
  fetchFromBackend(query: string, body: AuthRequest): Promise<AuthResponse>;
29
- createMessageChannel(query: string, onClose: () => void, onError: (message: string) => void, onMessage: (data: string) => void): MessageChannel;
30
+ static parseCloseEvent(event: CloseEvent): [WebSocketCloseReasons, number];
31
+ createMessageChannel(query: string, onClose: (reason: WebSocketCloseReasons, code: number) => void, onError: (message: string) => void, onMessage: (data: string) => void): MessageChannel;
30
32
  closeAllMessageChannels(): void;
31
33
  getDeviceRegistration(): Promise<DeviceRegistration | null>;
32
34
  embedFpResultToDeviceRegistration(registration: DeviceRegistration): Promise<DeviceRegistration>;
@@ -5,9 +5,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const logger_1 = require("../common/logger");
7
7
  const version_1 = require("../version");
8
- const auth_error_1 = __importDefault(require("./auth-error"));
9
8
  const auth_token_claims_1 = require("./auth-token-claims");
10
9
  const error_code_1 = __importDefault(require("./error-code"));
10
+ const web_socket_close_reasons_1 = require("./web-socket-close-reasons");
11
11
  class AuthSession {
12
12
  get namespace() {
13
13
  var _a;
@@ -91,6 +91,48 @@ class AuthSession {
91
91
  .catch(reject);
92
92
  });
93
93
  }
94
+ static parseCloseEvent(event) {
95
+ var reason = web_socket_close_reasons_1.WebSocketCloseReasons.UNKNOWN_REASON;
96
+ switch (event.code) {
97
+ case 1000:
98
+ reason = web_socket_close_reasons_1.WebSocketCloseReasons.NORMAL_CLOSURE;
99
+ break;
100
+ case 1001:
101
+ reason = web_socket_close_reasons_1.WebSocketCloseReasons.GOING_AWAY;
102
+ break;
103
+ case 1002:
104
+ reason = web_socket_close_reasons_1.WebSocketCloseReasons.PROTOCOL_ERROR;
105
+ break;
106
+ case 1003:
107
+ reason = web_socket_close_reasons_1.WebSocketCloseReasons.UNSUPPORTED_DATA;
108
+ break;
109
+ case 1005:
110
+ reason = web_socket_close_reasons_1.WebSocketCloseReasons.NO_STATUS_RECEIVED;
111
+ break;
112
+ case 1006:
113
+ reason = web_socket_close_reasons_1.WebSocketCloseReasons.ABNORMAL_CLOSURE;
114
+ break;
115
+ case 1007:
116
+ reason = web_socket_close_reasons_1.WebSocketCloseReasons.INVALID_FRAME_PAYLOAD_DATA;
117
+ break;
118
+ case 1008:
119
+ reason = web_socket_close_reasons_1.WebSocketCloseReasons.POLICY_VIOLATION;
120
+ break;
121
+ case 1009:
122
+ reason = web_socket_close_reasons_1.WebSocketCloseReasons.MESSAGE_TOO_BIG;
123
+ break;
124
+ case 1010:
125
+ reason = web_socket_close_reasons_1.WebSocketCloseReasons.MANDATORY_EXTENSION;
126
+ break;
127
+ case 1011:
128
+ reason = web_socket_close_reasons_1.WebSocketCloseReasons.INTERNAL_ERROR;
129
+ break;
130
+ case 1015:
131
+ reason = web_socket_close_reasons_1.WebSocketCloseReasons.TLS_HANDSHAKE;
132
+ break;
133
+ }
134
+ return [reason, event.code];
135
+ }
94
136
  createMessageChannel(query, onClose, onError, onMessage) {
95
137
  if (!this.authToken) {
96
138
  throw new Error('Authentication token is not initialized, cannot create MessageChannel');
@@ -113,18 +155,17 @@ class AuthSession {
113
155
  this.channels.delete(channel);
114
156
  }
115
157
  }, KEEP_ALIVE_INTERVAL);
116
- channel.addEventListener('close', (_) => {
117
- this.log.debug('Message channel is closed');
158
+ channel.addEventListener('close', (event) => {
159
+ var statusCode = event.code ? `with status code: ${event.code}` : 'without status code';
160
+ this.log.debug(`Message channel is closed ${statusCode}`);
118
161
  clearInterval(keepAlive);
119
- onClose();
120
162
  this.channels.delete(channel);
163
+ const [reason, code] = AuthSession.parseCloseEvent(event);
164
+ onClose(reason, code);
121
165
  });
122
166
  channel.addEventListener('error', (event) => {
123
- this.log.trace('Message channel encountered an error');
124
- this.log.trace(event);
125
- clearInterval(keepAlive);
126
- onError(auth_error_1.default.extractMessage(event));
127
- this.channels.delete(channel);
167
+ this.log.debug('Message channel encountered an error');
168
+ this.log.debug(event);
128
169
  });
129
170
  channel.addEventListener('message', (event) => {
130
171
  this.log.trace('Message channel received a message');
@@ -14,9 +14,9 @@ class AuthStatusActions {
14
14
  var gotResponse = false;
15
15
  this.log.trace('Waiting for auth status');
16
16
  return new Promise((resolve, reject) => {
17
- const channel = session.createMessageChannel('/v1/client/status?token=' + encodeURIComponent(session.authToken), () => {
17
+ const channel = session.createMessageChannel('/v1/client/status?token=' + encodeURIComponent(session.authToken), (reason, code) => {
18
18
  if (!gotResponse) {
19
- reject(new auth_error_1.default('Failed to receive secondary authentication status, no response'));
19
+ reject(new auth_error_1.default(`Failed to receive secondary authentication with status code ${code}. ${reason}`));
20
20
  }
21
21
  }, (errorMessage) => {
22
22
  gotResponse = true;
@@ -27,7 +27,7 @@ class AuthStatusActions {
27
27
  this.log.debug(('Secondary authentication status: ' + data));
28
28
  const response = JSON.parse(data);
29
29
  if (response.error) {
30
- reject(new auth_error_1.default(response.error.message, response.error.code, response.next));
30
+ reject(new auth_error_1.default(response.error.message, response.error.code, response.next, false));
31
31
  }
32
32
  else {
33
33
  session.lastStep = response.next;
@@ -5,6 +5,7 @@ export default class DevicePassiveRegisterStep implements AuthStep {
5
5
  private readonly log;
6
6
  readonly name = "device/passive/register";
7
7
  execute(session: AuthSession): Promise<string>;
8
+ static hasAssertionResponse(session: AuthSession): boolean;
8
9
  private finishRegistration;
9
10
  private getFido2Registration;
10
11
  }
@@ -7,6 +7,7 @@ const base64_1 = __importDefault(require("../common/base64"));
7
7
  const logger_1 = require("../common/logger");
8
8
  const auth_error_1 = __importDefault(require("./auth-error"));
9
9
  const error_code_1 = __importDefault(require("./error-code"));
10
+ const device_passive_verify_step_1 = __importDefault(require("./device-passive-verify-step"));
10
11
  class DevicePassiveRegisterStep {
11
12
  constructor() {
12
13
  this.log = logger_1.LoggerFactory.getLogger('device-passive-register-step');
@@ -14,46 +15,56 @@ class DevicePassiveRegisterStep {
14
15
  }
15
16
  execute(session) {
16
17
  return new Promise((resolve, reject) => {
17
- session
18
- .getDeviceRegistration()
19
- .then((devRegistration) => {
20
- if (devRegistration) {
21
- session
22
- .embedFpResultToDeviceRegistration(devRegistration)
23
- .then((devRegistration) => session.embedDarwiniumResultToDeviceRegistration(devRegistration))
24
- .then((devRegistration) => {
25
- this.finishRegistration(session, [this.getFido2Registration(session)], devRegistration.getSignals())
26
- .then(resolve)
27
- .catch(reject);
28
- });
29
- }
30
- else {
31
- session.platform.deviceAuth
32
- .createRegistration({
33
- namespace: session.namespace,
34
- endpoint: session.backendOrigin,
35
- })
36
- .then((devRegistration) => session.embedFpResultToDeviceRegistration(devRegistration))
37
- .then((devRegistration) => session.embedDarwiniumResultToDeviceRegistration(devRegistration))
38
- .then((devRegistration) => {
39
- devRegistration
40
- .getAuthRegistration(session.challenge)
41
- .then((authRegistration) => this.finishRegistration(session, [this.getFido2Registration(session), authRegistration], devRegistration.getSignals()))
42
- .then((next) => {
43
- devRegistration.deviceId = session.settings.deviceId;
44
- session.platform.deviceAuth
45
- .storeRegistration(devRegistration)
46
- .then(() => resolve(next))
18
+ if (DevicePassiveRegisterStep.hasAssertionResponse(session)) {
19
+ device_passive_verify_step_1.default.runFidoVerify(this.log, session).then(resolve).catch(reject);
20
+ }
21
+ else {
22
+ session
23
+ .getDeviceRegistration()
24
+ .then((devRegistration) => {
25
+ if (devRegistration) {
26
+ session
27
+ .embedFpResultToDeviceRegistration(devRegistration)
28
+ .then((devRegistration) => session.embedDarwiniumResultToDeviceRegistration(devRegistration))
29
+ .then((devRegistration) => {
30
+ this.finishRegistration(session, [this.getFido2Registration(session)], devRegistration.getSignals())
31
+ .then(resolve)
32
+ .catch(reject);
33
+ });
34
+ }
35
+ else {
36
+ session.platform.deviceAuth
37
+ .createRegistration({
38
+ namespace: session.namespace,
39
+ endpoint: session.backendOrigin,
40
+ })
41
+ .then((devRegistration) => session.embedFpResultToDeviceRegistration(devRegistration))
42
+ .then((devRegistration) => session.embedDarwiniumResultToDeviceRegistration(devRegistration))
43
+ .then((devRegistration) => {
44
+ devRegistration
45
+ .getAuthRegistration(session.challenge)
46
+ .then((authRegistration) => this.finishRegistration(session, [this.getFido2Registration(session), authRegistration], devRegistration.getSignals()))
47
+ .then((next) => {
48
+ devRegistration.deviceId = session.settings.deviceId;
49
+ session.platform.deviceAuth
50
+ .storeRegistration(devRegistration)
51
+ .then(() => resolve(next))
52
+ .catch(reject);
53
+ })
47
54
  .catch(reject);
48
55
  })
49
56
  .catch(reject);
50
- })
51
- .catch(reject);
52
- }
53
- })
54
- .catch(reject);
57
+ }
58
+ })
59
+ .catch(reject);
60
+ }
55
61
  });
56
62
  }
63
+ static hasAssertionResponse(session) {
64
+ const credential = session.credential;
65
+ const assertion = credential.response;
66
+ return assertion && 'authenticatorData' in assertion && 'signature' in assertion;
67
+ }
57
68
  finishRegistration(session, registrations, signals) {
58
69
  return new Promise((resolve, reject) => {
59
70
  session
@@ -65,13 +76,18 @@ class DevicePassiveRegisterStep {
65
76
  })
66
77
  .then((response) => {
67
78
  if (response.error) {
68
- reject(new auth_error_1.default(response.error.message, response.error.code, response.next));
79
+ reject(new auth_error_1.default(response.error.message, response.error.code, response.next, false));
69
80
  }
70
81
  else {
71
82
  const data = response.data;
72
- if (data && data.deviceId) {
73
- session.settings.deviceId = data.deviceId;
74
- session.settings.fidoPasskeyRegistered = true;
83
+ if (data) {
84
+ if (data.deviceId) {
85
+ session.settings.deviceId = data.deviceId;
86
+ session.settings.fidoPasskeyRegistered = true;
87
+ }
88
+ if (data.scanMessage) {
89
+ session.authMessage = data.scanMessage;
90
+ }
75
91
  resolve(response.next);
76
92
  }
77
93
  else {
@@ -87,6 +87,10 @@ class DevicePassiveSilentStep {
87
87
  session.settings.fidoPasskeyRegistered = deviceRegisterAuthResp.data.passkey;
88
88
  registration.deviceId = deviceId;
89
89
  this.log.debug('Device ID: ' + deviceId);
90
+ const data = response.data;
91
+ if (data && data.scanMessage) {
92
+ session.authMessage = data.scanMessage;
93
+ }
90
94
  session.platform.deviceAuth
91
95
  .storeRegistration(registration)
92
96
  .then(() => resolve(response.next))
@@ -1,18 +1,26 @@
1
1
  import AuthSession from './auth-session';
2
2
  import AuthStep from './auth-step';
3
- import { DeviceRole } from '../authenticator-builder';
3
+ import { DeviceRole, PasskeyAlreadyExistCallback } from '../authenticator-builder';
4
4
  import { AuthStatusActions } from './auth-status-actions';
5
+ import { Signals } from './auth-request';
5
6
  export declare class DevicePassiveActions extends AuthStatusActions {
6
7
  protected log: import("../common/logger").Logger;
7
8
  private readonly getDisplayName;
8
- protected constructor(getDisplayName?: () => string | null);
9
- protected register(session: AuthSession): Promise<string>;
9
+ private handler?;
10
+ static readonly NO_REQUEST_CREDS_FOUND = "Passkey has already been registered but found no CredentialRequestOptions in the fido/register/start response payload";
11
+ static readonly NO_CREDS_FOUND = "Neither credCreateOptions nor credRequestOptions are found in the fido/register/start response payload";
12
+ static readonly USER_NOT_ACCEPTING_RESPONSE = "User not accepting to continue by reusing the existing passkey with user response";
13
+ protected constructor(getDisplayName?: () => string | null, handler?: PasskeyAlreadyExistCallback);
14
+ protected register(session: AuthSession, signals?: Signals): Promise<string>;
10
15
  protected verify(session: AuthSession): Promise<string>;
16
+ private createCredentials;
17
+ private parseCredRequestOptions;
18
+ private getCredentials;
11
19
  }
12
20
  export default class DevicePassiveStep extends DevicePassiveActions implements AuthStep {
13
21
  static readonly NAME = "device/passive";
14
22
  readonly name = "device/passive";
15
23
  private readonly role;
16
- constructor(getDisplayName?: () => string | null, role?: DeviceRole);
24
+ constructor(getDisplayName?: () => string | null, handler?: PasskeyAlreadyExistCallback, role?: DeviceRole);
17
25
  execute(session: AuthSession): Promise<string>;
18
26
  }
@@ -11,57 +11,51 @@ const auth_token_claims_1 = require("./auth-token-claims");
11
11
  const auth_error_1 = __importDefault(require("./auth-error"));
12
12
  const authenticator_builder_1 = require("../authenticator-builder");
13
13
  const auth_status_actions_1 = require("./auth-status-actions");
14
+ const fido_options_error_1 = require("./fido-options-error");
15
+ const auth_response_status_1 = require("./auth-response-status");
14
16
  class DevicePassiveActions extends auth_status_actions_1.AuthStatusActions {
15
- constructor(getDisplayName) {
17
+ constructor(getDisplayName, handler) {
16
18
  super();
17
19
  this.log = logger_1.LoggerFactory.getLogger('device-passive-actions');
18
20
  this.getDisplayName = getDisplayName ? getDisplayName : () => null;
21
+ this.handler = handler;
19
22
  }
20
- register(session) {
21
- this.log.trace('Registering');
23
+ register(session, signals) {
24
+ this.log.trace('Registering Passkey');
22
25
  return new Promise((resolve, reject) => {
23
26
  const displayName = this.getDisplayName();
24
27
  session
25
28
  .fetchFromBackend('/v1/client/device/fido2/register/start', {
26
29
  displayName: displayName ? displayName : undefined,
30
+ signals: signals,
27
31
  })
28
32
  .then((response) => {
33
+ var _a, _b;
29
34
  if (response.error) {
30
- reject(new auth_error_1.default(response.error.message, response.error.code, response.next));
35
+ reject(new auth_error_1.default(response.error.message, response.error.code, response.next, false));
31
36
  }
32
37
  else {
33
- let options = response.data.credCreateOptions;
34
- options.challenge = base64_1.default.bufferDecode(options.challenge);
35
- options.user.id = base64_1.default.bufferDecode(options.user.id);
36
- if (displayName) {
37
- options.user.displayName = displayName;
38
+ const creationOptions = (_a = response.data) === null || _a === void 0 ? void 0 : _a.credCreateOptions;
39
+ const requestOptions = (_b = response.data) === null || _b === void 0 ? void 0 : _b.credRequestOptions;
40
+ if (creationOptions) {
41
+ this.createCredentials(session, displayName, creationOptions, response)
42
+ .then(resolve)
43
+ .catch(reject);
44
+ }
45
+ else if (requestOptions) {
46
+ this.getCredentials(session, requestOptions, response).then(resolve).catch(reject);
38
47
  }
39
- if (options.excludeCredentials) {
40
- options.excludeCredentials.forEach((i) => {
41
- i.id = base64_1.default.bufferDecode(i.id);
42
- });
48
+ else {
49
+ const error = new auth_error_1.default(DevicePassiveActions.NO_CREDS_FOUND);
50
+ reject(error);
43
51
  }
44
- session.platform.webauthn
45
- .createCredentials({
46
- publicKey: options,
47
- })
48
- .then((credential) => {
49
- if (!credential) {
50
- reject(new auth_error_1.default('Failed to create FIDO2 credentials'));
51
- }
52
- else {
53
- session.credential = credential;
54
- resolve(response.next);
55
- }
56
- })
57
- .catch(reject);
58
52
  }
59
53
  })
60
54
  .catch(reject);
61
55
  });
62
56
  }
63
57
  verify(session) {
64
- this.log.trace('Verifying');
58
+ this.log.trace('Verifying Passkey');
65
59
  return new Promise((resolve, reject) => {
66
60
  if (session.settings.deviceId) {
67
61
  session
@@ -69,46 +63,150 @@ class DevicePassiveActions extends auth_status_actions_1.AuthStatusActions {
69
63
  deviceId: session.settings.deviceId,
70
64
  })
71
65
  .then((response) => {
72
- var _a;
73
66
  if (response.error) {
74
- reject(new auth_error_1.default(response.error.message, response.error.code, response.next));
67
+ reject(new auth_error_1.default(response.error.message, response.error.code, response.next, false));
75
68
  }
76
69
  else {
77
- let options = response.data.credRequestOptions;
78
- options.challenge = base64_1.default.bufferDecode(options.challenge);
79
- if (options.allowCredentials) {
80
- (_a = options.allowCredentials) === null || _a === void 0 ? void 0 : _a.forEach((i) => {
81
- i.id = base64_1.default.bufferDecode(i.id);
82
- });
83
- }
84
- session.platform.webauthn
85
- .getCredentials({
86
- publicKey: options,
87
- })
88
- .then((credential) => {
89
- if (!credential) {
90
- reject(new Error('Failed to load FIDO2 credentials'));
70
+ const options = response.data.credRequestOptions;
71
+ this.getCredentials(session, options, response).then(resolve).catch(reject);
72
+ }
73
+ })
74
+ .catch(reject);
75
+ }
76
+ else {
77
+ reject(new auth_error_1.default('Failed to start verification, DeviceId is missing'));
78
+ }
79
+ });
80
+ }
81
+ createCredentials(session, displayName, options, response) {
82
+ this.log.trace('Trying create new FIDO credentials');
83
+ return new Promise((resolve, reject) => {
84
+ options.challenge = base64_1.default.bufferDecode(options.challenge);
85
+ options.user.id = base64_1.default.bufferDecode(options.user.id);
86
+ if (displayName) {
87
+ options.user.displayName = displayName;
88
+ }
89
+ if (options.excludeCredentials) {
90
+ options.excludeCredentials.forEach((i) => {
91
+ i.id = base64_1.default.bufferDecode(i.id);
92
+ });
93
+ }
94
+ if (options.authenticatorSelection) {
95
+ options.authenticatorSelection.residentKey = 'preferred';
96
+ options.authenticatorSelection.requireResidentKey = false;
97
+ }
98
+ else {
99
+ options.authenticatorSelection = {
100
+ residentKey: 'preferred',
101
+ requireResidentKey: false,
102
+ };
103
+ }
104
+ const fidoCreationOptions = {
105
+ publicKey: options,
106
+ };
107
+ session.platform.webauthn
108
+ .createCredentials(fidoCreationOptions)
109
+ .then((credential) => {
110
+ if (!credential) {
111
+ reject(new auth_error_1.default('Failed to create FIDO2 credentials'));
112
+ }
113
+ else {
114
+ session.credential = credential;
115
+ resolve(response.next);
116
+ }
117
+ })
118
+ .catch((error) => {
119
+ const fidoCreationError = fido_options_error_1.FidoOptionsError.identifyRegistrationError({
120
+ error: error,
121
+ options: fidoCreationOptions,
122
+ platform: session.platform,
123
+ });
124
+ if (fidoCreationError instanceof fido_options_error_1.FidoOptionsError &&
125
+ fidoCreationError.name === 'InvalidStateError') {
126
+ if (this.handler) {
127
+ this.log.trace('Passkey handler callback has been defined');
128
+ this.handler()
129
+ .then((userResponse) => {
130
+ if (userResponse === auth_response_status_1.AuthResponseStatus.Accept) {
131
+ this.parseCredRequestOptions(response, session).then(resolve).catch(reject);
91
132
  }
92
133
  else {
93
- session.credential = credential;
94
- resolve(response.next);
134
+ reject(new auth_error_1.default(`${DevicePassiveActions.USER_NOT_ACCEPTING_RESPONSE}: ${userResponse}`));
135
+ return;
95
136
  }
96
137
  })
97
138
  .catch(reject);
98
139
  }
99
- })
100
- .catch(reject);
140
+ else {
141
+ this.log.trace('Passkey handler callback is null, go ahead with passkey discoverable as default behavior');
142
+ this.parseCredRequestOptions(response, session).then(resolve).catch(reject);
143
+ }
144
+ }
145
+ else {
146
+ reject(fidoCreationError);
147
+ }
148
+ });
149
+ });
150
+ }
151
+ parseCredRequestOptions(response, session) {
152
+ return new Promise((resolve, reject) => {
153
+ const data = response.data;
154
+ if (data.credRequestOptions) {
155
+ const requestOptions = data.credRequestOptions;
156
+ this.getCredentials(session, requestOptions, response).then(resolve).catch(reject);
101
157
  }
102
158
  else {
103
- reject(new auth_error_1.default('Failed to start verification, DeviceId is missing'));
159
+ reject(new auth_error_1.default(DevicePassiveActions.NO_REQUEST_CREDS_FOUND));
160
+ }
161
+ });
162
+ }
163
+ getCredentials(session, options, response) {
164
+ this.log.trace('Trying get existing FIDO credentials');
165
+ return new Promise((resolve, reject) => {
166
+ var _a, _b;
167
+ if (((_a = options.allowCredentials) === null || _a === void 0 ? void 0 : _a.length) == 0) {
168
+ reject(new auth_error_1.default('AllowCredentials array should not be empty when call getCredentials()'));
169
+ }
170
+ else {
171
+ options.challenge = base64_1.default.bufferDecode(options.challenge);
172
+ if (options.allowCredentials) {
173
+ (_b = options.allowCredentials) === null || _b === void 0 ? void 0 : _b.forEach((i) => {
174
+ i.id = base64_1.default.bufferDecode(i.id);
175
+ });
176
+ }
177
+ const fidoRequestOptions = {
178
+ publicKey: options,
179
+ };
180
+ session.platform.webauthn
181
+ .getCredentials(fidoRequestOptions)
182
+ .then((credential) => {
183
+ if (!credential) {
184
+ reject(new auth_error_1.default('Failed to load FIDO2 credentials'));
185
+ }
186
+ else {
187
+ session.credential = credential;
188
+ resolve(response.next);
189
+ }
190
+ })
191
+ .catch((error) => {
192
+ const fidoAuthenticationError = fido_options_error_1.FidoOptionsError.identifyAuthenticationError({
193
+ error: error,
194
+ options: fidoRequestOptions,
195
+ platform: session.platform,
196
+ });
197
+ reject(fidoAuthenticationError);
198
+ });
104
199
  }
105
200
  });
106
201
  }
107
202
  }
203
+ DevicePassiveActions.NO_REQUEST_CREDS_FOUND = 'Passkey has already been registered but found no CredentialRequestOptions in the fido/register/start response payload';
204
+ DevicePassiveActions.NO_CREDS_FOUND = 'Neither credCreateOptions nor credRequestOptions are found in the fido/register/start response payload';
205
+ DevicePassiveActions.USER_NOT_ACCEPTING_RESPONSE = 'User not accepting to continue by reusing the existing passkey with user response';
108
206
  exports.DevicePassiveActions = DevicePassiveActions;
109
207
  class DevicePassiveStep extends DevicePassiveActions {
110
- constructor(getDisplayName, role) {
111
- super(getDisplayName);
208
+ constructor(getDisplayName, handler, role) {
209
+ super(getDisplayName, handler);
112
210
  this.name = DevicePassiveStep.NAME;
113
211
  this.role = role !== null && role !== void 0 ? role : authenticator_builder_1.DeviceRole.Primary;
114
212
  this.log = logger_1.LoggerFactory.getLogger('device-passive-step');
@@ -127,7 +225,29 @@ class DevicePassiveStep extends DevicePassiveActions {
127
225
  }
128
226
  return Promise.resolve(device_passive_silent_step_1.default.NAME);
129
227
  }
130
- return this.register(session);
228
+ return new Promise((resolve, reject) => {
229
+ session
230
+ .getFingerprintData()
231
+ .then((signal) => {
232
+ const signals = {
233
+ fingerprint: signal,
234
+ };
235
+ session.embedDarwiniumSignal(signals);
236
+ this.register(session, signals).then(resolve).catch(reject);
237
+ })
238
+ .catch((error) => {
239
+ const errorMsg = `Unexpected error happened during Fingerprint data collection: ${error.message}`;
240
+ this.log.warn(errorMsg);
241
+ this.log.warn(error);
242
+ const signals = {
243
+ fingerprint: {
244
+ error: errorMsg,
245
+ },
246
+ };
247
+ session.embedDarwiniumSignal(signals);
248
+ this.register(session, signals).then(resolve).catch(reject);
249
+ });
250
+ });
131
251
  }
132
252
  }
133
253
  DevicePassiveStep.NAME = 'device/passive';
@@ -1,9 +1,10 @@
1
+ import { PasskeyAlreadyExistCallback } from '../authenticator-builder';
1
2
  import AuthSession from './auth-session';
2
3
  import AuthStep from './auth-step';
3
4
  import { DevicePassiveActions } from './device-passive-step';
4
5
  export default class DevicePassiveStepupStep extends DevicePassiveActions implements AuthStep {
5
6
  static readonly NAME = "device/passive/stepup";
6
7
  readonly name = "device/passive/stepup";
7
- constructor(getDisplayName?: () => string | null);
8
+ constructor(getDisplayName?: () => string | null, handler?: PasskeyAlreadyExistCallback);
8
9
  execute(session: AuthSession): Promise<string>;
9
10
  }