Interface IUserAuthenticationSecondFactor

All Known Subinterfaces:
IUserAuthenticationUI

public interface IUserAuthenticationSecondFactor
The second step of a sign-in, the part of IUserAuthenticationUI for it (SignIn L-6; the SignIn flow design, sections 3.5, 5 and 7): the sign-in policy of the application, the sign-in that waits for its second factor, the verification codes of an authenticator app (TOTP, RFC 6238), the recovery codes that stand in for a lost app, and the devices that are trusted not to be asked again - which are the devices of IUserAuthenticationUI.getSignedInDevices(IClientSessionGyro): the trust is kept with the device's token (CC-SIGNIN-MERGE-L5-L6-L8).

The sign-in that waits. When a second factor is due - the account has one, or the policy requires one - the first factor signs nobody in: IUserAuthenticationUI.performInternalLogin(IClientSessionGyro, String, String) answers SigninResult.SUCCESS_SECOND_FACTOR_REQUIRED, records nothing in the session, and the server holds the user for this session, for 5 minutes. getSignInMethods(IClientSessionGyro) then tells what the user has. verifySecondFactor(IClientSessionGyro, String, String, int) completes the sign-in with a code, or with a passkey of the user in place of one (CC-LOGIN-PASSKEY-SECOND-FACTOR); when the policy requires a second factor of a user who has none, beginTotpEnrolment(IClientSessionGyro) and confirmTotpEnrolment(IClientSessionGyro, String) do. cancelPendingSignIn(IClientSessionGyro) ends it. While the sign-in waits the session has no user: every other call of IUserAuthenticationUI sees the session it saw before the first factor. Where the policy requires a passkey on the client and the user has none, the second factor does not complete the sign-in: it waits on, for the registration of a passkey (SigninResult.SUCCESS_PASSKEY_ENROLMENT_REQUIRED, IUserAuthenticationPasskeys).

The enrolment. beginTotpEnrolment(IClientSessionGyro) answers with the QR code and the setup key; nothing is saved until confirmTotpEnrolment(IClientSessionGyro, String) has the first code the app shows, so an app that was never enrolled correctly locks nobody out. The confirmation answers with the recovery codes, which are shown once. A user who is signed in changes how the user signs in - enrols, removes, gets new recovery codes - only after a recent sign-in: the one recent sign-in of the interface (IUserAuthenticationPasskeys.isRecentlyAuthenticated(IClientSessionGyro), 5 minutes), which the password (IUserAuthenticationPasskeys.reauthenticate(IClientSessionGyro, String)) and a passkey confirm; otherwise the answer is SigninResult.RECENT_SIGNIN_REQUIRED. The removal and new recovery codes need more: a session that verified a second factor - with a code when it signed in, or with a passkey, which is accepted with user verification only and is the proof of a second factor whether it signed the user in or confirmed the user (IUserAuthenticationPasskeys.finishPasskeyAssertion(IClientSessionGyro, String)). A session that was asked for none - the one of a trusted device, of a device that stays signed in - is answered SigninResult.SECOND_FACTOR_REQUIRED, however often the password confirms it: the user confirms with a passkey, or signs in with the second step.

A code is accepted once. Invalid codes are limited per account, per client source and per waiting sign-in. No method of this interface logs or returns a secret, a code or a recovery code, but for the two that hand the user the setup key and the recovery codes, once.

Author:
Christopher Mindus
  • Method Details

    • getSignInPolicy

      SignInPolicy getSignInPolicy(IClientSessionGyro clientGyro)
      Gets the sign-in policy of the application of this session: the settings of the server environment with what the application overrides, and with the methods of a second factor narrowed to the ones this server can do.
      Parameters:
      clientGyro - The Client Session Gyro that is the cause of this request.
      Returns:
      The policy, never null.
    • getSignInMethods

      SignInMethods getSignInMethods(IClientSessionGyro clientGyro)
      Gets the ways the user signs in: of the user whose sign-in waits in this session, otherwise of the user this session has signed in.
      Parameters:
      clientGyro - The Client Session Gyro that is the cause of this request.
      Returns:
      The methods, SignInMethods.NONE when the session has neither.
    • verifySecondFactor

      SigninResult verifySecondFactor(IClientSessionGyro clientGyro, String method, String code, int trustDays)
      Completes the sign-in that waits in this session with its second factor: the code is verified, and the user is signed in and recorded in the session exactly as IUserAuthenticationUI.performInternalLogin(IClientSessionGyro, String, String) records a user without one.

      A verification code is accepted once: the code of a time step that was accepted before, or of an earlier one, is refused. A recovery code is accepted once.

      A passkey in place of a code (CC-LOGIN-PASSKEY-SECOND-FACTOR). A user who has a passkey answers with it: getSignInMethods(IClientSessionGyro) then lists the method SignInPolicy.METHOD_PASSKEY, the first, IUserAuthenticationPasskeys.startPasskeyAssertion(IClientSessionGyro, boolean) returns the options that name the passkeys of the user, and this method takes the answer of the client as its code. The passkey is accepted with user verification only, is the proof of a second factor of the session, of the method passkey, and trusts the device as a verification code does. One answer is looked at per sign-in that waits: a passkey that is refused ends it, and the user begins again with the first factor. A passkey that was registered through an account recovery is no second factor for 24 hours: it is not listed and not accepted.

      Parameters:
      clientGyro - The Client Session Gyro that is the cause of this sign-in.
      method - The method the code is of, SignInPolicy.METHOD_TOTP, SignInPolicy.METHOD_RECOVERY or SignInPolicy.METHOD_PASSKEY.
      code - The code as typed; whitespace is ignored, and so are the dashes and the letter case of a recovery code. For a passkey, the JSON text of the client's AuthenticationResponseJSON.
      trustDays - The days this device is not to be asked for the second factor again ("Do not ask again on this device"), zero for none; at most what the policy allows. A recovery code trusts no device. The trust is kept with the device's token (IUserAuthenticationUI.trustDeviceForMFA(IClientSessionGyro, boolean)), and ends at every change of the password.
      Returns:
      SUCCESS or SUCCESS_CHANGE_PASSWORD when the user is signed in; SUCCESS_PASSKEY_ENROLMENT_REQUIRED when the code was accepted and the sign-in waits on, for the registration of a passkey the policy requires; PENDING_EXPIRED when no sign-in waits (none was begun, 5 minutes have passed, or too many invalid codes or a refused passkey ended it); INVALID_CODE; INVALID_PASSKEY for a passkey that is refused for any reason; PASSKEY_REQUEST_EXPIRED when there is no request of startPasskeyAssertion to answer - the sign-in waits on; TOO_MANY_CODES when the account waits after invalid codes; TOO_MANY_INVALID_LOGINS_RETRY_LATER when the client source is held back; SECOND_FACTOR_METHOD_NOT_ALLOWED; SECOND_FACTOR_NOT_ENROLLED when the user has no second factor to verify (one is enrolled first); or a server, application or system result.
      Throws:
      NullPointerException - If method is null.
    • beginTotpEnrolment

      TotpEnrolment beginTotpEnrolment(IClientSessionGyro clientGyro) throws UserException
      Begins the enrolment of an authenticator app: for the user whose sign-in waits in this session, otherwise for the user this session has signed in, after a recent sign-in. Nothing is saved: the enrolment is held for this session until confirmTotpEnrolment(IClientSessionGyro, String) confirms it, for at most 10 minutes, and a sign-in that waits is kept waiting as long. A second call for the same user, while the enrolment waits, shows the SAME enrolment again - the same secret and QR code, waiting another 10 minutes - so that going back and forth on the set-up page mints no new secret silently (SIGNIN-TOTP-ENROL-RESTART-SILENT, CC-FIX-SIGNIN-TOTP-ENROL); restartTotpEnrolment(IClientSessionGyro) is the one way to a new secret.
      Parameters:
      clientGyro - The Client Session Gyro that is the cause of this request.
      Returns:
      What the user needs to enrol the app: the QR code, the link and the setup key.
      Throws:
      UserException - With RECENT_SIGNIN_REQUIRED when the session has signed its user in too long ago, or has none; SECOND_FACTOR_ALREADY_ENROLLED; SECOND_FACTOR_METHOD_NOT_ALLOWED when the policy has no authenticator app or no second factor at all; the result of a client source that is held back; or a server, application or system result.
    • confirmTotpEnrolment

      String[] confirmTotpEnrolment(IClientSessionGyro clientGyro, String code) throws UserException
      Confirms the enrolment this session began, with the first code the authenticator app shows. The secret is saved now, sealed with the server's own key, and the account has a second factor from this moment on. When the sign-in waited for this enrolment, the user is signed in and recorded in the session now - unless the sign-in waits on, for the registration of a passkey the policy requires (IUserAuthenticationUI.getPendingSignIn(IClientSessionGyro) tells so). The code that confirmed is spent. The trust of every device of the user for MFA ends: no device was trusted by a code of this second factor.
      Parameters:
      clientGyro - The Client Session Gyro that is the cause of this request.
      code - The verification code the app shows; whitespace is ignored.
      Returns:
      The recovery codes of the account, each good for one sign-in. They are returned here once and cannot be read again: only a hash of each is kept.
      Throws:
      UserException - With SECOND_FACTOR_NOT_ENROLLED when no enrolment of this session waits; INVALID_CODE (the enrolment keeps waiting; getCodeClockOffset(IClientSessionGyro) then names the drift of a clock the code would have been right for, when there is one); TOO_MANY_CODES for the fifth invalid code, which drops the enrolment - the user starts over (restartTotpEnrolment(IClientSessionGyro); CC-FIX-SIGNIN-TOTP-ENROL); the result of a client source that is held back; or a server, application or system result.
    • restartTotpEnrolment

      default TotpEnrolment restartTotpEnrolment(IClientSessionGyro clientGyro) throws UserException
      Begins the enrolment of an authenticator app ANEW ("Start over" on the set-up page, CC-FIX-SIGNIN-TOTP-ENROL): the enrolment this session held is dropped, whatever beginTotpEnrolment(IClientSessionGyro) showed before, and a new secret is made; the entry an authenticator app made of the old QR code is dead from now on. The rest - the user it is for, what is held and for how long, the refusals - as beginTotpEnrolment(IClientSessionGyro).

      The default begins as beginTotpEnrolment(IClientSessionGyro) does: an implementation that keeps no enrolment to show again has nothing to drop.

      Parameters:
      clientGyro - The Client Session Gyro that is the cause of this request.
      Returns:
      What the user needs to enrol the app: the QR code, the link and the setup key.
      Throws:
      UserException - As beginTotpEnrolment(IClientSessionGyro).
    • getCodeClockOffset

      default int getCodeClockOffset(IClientSessionGyro clientGyro)
      Gets the drift of a clock the last refused verification code of this session would have been right for (CC-FIX-SIGNIN-TOTP-ENROL, item 9): after verifySecondFactor(IClientSessionGyro, String, String, int) or confirmTotpEnrolment(IClientSessionGyro, String) answered INVALID_CODE for a code that matches a time step outside the verifier's window but within 5 minutes of this server's clock. Nothing was accepted; the screen names the drift, so that the user checks the clock of the phone - or the server's - instead of guessing.

      The default names none.

      Parameters:
      clientGyro - The Client Session Gyro that is the cause of this request.
      Returns:
      The seconds the clock of the code is off this server's: positive when ahead, negative when behind; zero when the last code of the session was accepted, was plainly wrong, or there was none.
    • enrolPasskeySecondFactor

      default String[] enrolPasskeySecondFactor(IClientSessionGyro clientGyro) throws UserException
      Makes a passkey the second factor of the user (CC-FIX-SIGNIN-2FA-CHOICE): the user chose a passkey on the choice of a second factor, and this session has registered one for the user a short time ago (IUserAuthenticationPasskeys.finishPasskeyRegistration(IClientSessionGyro, String, String)) - for the user whose sign-in waits for the enrolment of a second factor, or for the user it has signed in, after a recent sign-in. A passkey is accepted with user verification only: it is a second factor as an authenticator app is (ruling M-11). The account has a second factor from this moment on; the recovery codes are made with its first one, and the trust of every device of the user for MFA ends, as confirmTotpEnrolment(IClientSessionGyro, String) does. When the sign-in waited for this enrolment, the user is signed in and recorded in the session now, with the proof SignInPolicy.METHOD_PASSKEY.

      The default answers SECOND_FACTOR_METHOD_NOT_ALLOWED: an implementation without passkeys as a second factor.

      Parameters:
      clientGyro - The Client Session Gyro that is the cause of this request.
      Returns:
      The recovery codes of the account, returned here once; empty when the account had a second factor already, whose recovery codes it keeps.
      Throws:
      UserException - With SECOND_FACTOR_NOT_ENROLLED when this session registered no passkey of the user a short time ago; RECENT_SIGNIN_REQUIRED; SECOND_FACTOR_METHOD_NOT_ALLOWED when the policy has no passkey or no second factor at all; the result of a client source that is held back; or a server, application or system result.
    • setPreferredSecondFactor

      default boolean setPreferredSecondFactor(IClientSessionGyro clientGyro, String method)
      Sets the second factor a sign-in of the user this session has signed in asks for first ("Ask me first for", CC-FIX-SIGNIN-2FA-CHOICE): getSignInMethods(IClientSessionGyro) then lists it first. The sign-in of the session must be recent.

      The default sets nothing.

      Parameters:
      clientGyro - The Client Session Gyro that is the cause of this request.
      method - The method, SignInPolicy.METHOD_PASSKEY or SignInPolicy.METHOD_TOTP, one the user has; null for none: the strongest the user has is asked for first.
      Returns:
      true when it is set.
    • regenerateRecoveryCodes

      String[] regenerateRecoveryCodes(IClientSessionGyro clientGyro) throws UserException
      Replaces the recovery codes of the user this session has signed in: every code made before is dead, used or not. The sign-in of the session must be recent, and the session must have verified a second factor of the user: a code, or a passkey.
      Parameters:
      clientGyro - The Client Session Gyro that is the cause of this request.
      Returns:
      The new recovery codes, returned here once.
      Throws:
      UserException - With RECENT_SIGNIN_REQUIRED; SECOND_FACTOR_NOT_ENROLLED; SECOND_FACTOR_REQUIRED when the sign-in of the session verified no second factor; or a server, application or system result.
    • removeSecondFactor

      SigninResult removeSecondFactor(IClientSessionGyro clientGyro, String method)
      Removes a second factor of the user this session has signed in: for the authenticator app the secret, every recovery code and the trust of every device. The sign-in of the session must be recent, and the session must have verified a second factor of the user: a code, or a passkey.
      Parameters:
      clientGyro - The Client Session Gyro that is the cause of this request.
      method - The method to remove, SignInPolicy.METHOD_TOTP.
      Returns:
      SUCCESS when it is removed; RECENT_SIGNIN_REQUIRED; SECOND_FACTOR_NOT_ENROLLED; SECOND_FACTOR_REQUIRED when the sign-in of the session verified no second factor; SECOND_FACTOR_METHOD_NOT_ALLOWED for a method this server cannot do; or a server, application or system result.
      Throws:
      NullPointerException - If method is null.
    • cancelPendingSignIn

      void cancelPendingSignIn(IClientSessionGyro clientGyro)
      Ends the sign-in that waits in this session, whatever it waits for - its second factor, its passkey, the enrolment of either - and the enrolment that waits: the user goes back to the start. Nothing happens when nothing waits.
      Parameters:
      clientGyro - The Client Session Gyro that is the cause of this request.
    • getSecondFactorProof

      MFAProof getSecondFactorProof(IClientSessionGyro clientGyro)
      Gets the proof that this session's sign-in verified a second factor: the MFA proof of HUB-DESIGN 7.4 (ruling RB-11), which a session transfer carries.
      Parameters:
      clientGyro - The Client Session Gyro that is the cause of this request.
      Returns:
      The proof, MFAProof.NONE when the sign-in verified none - among them the sign-in of a trusted device, which was asked for none. A passkey gives the proof of the method MFAProof.METHOD_PASSKEY. A proof that is no longer fresh is returned as it is; it is the sender that does not send it.