Class MFAStore

java.lang.Object
com.iizix.server.user.mfa.MFAStore

public final class MFAStore extends Object
The database of the second factor of the users (SignIn L-6; D-24 of the SignIn survey): the tables IZS.USER_MFA and IZS.USER_MFA_CODES of createMFATables.sql beside this class, and the statements on them. Nothing here knows what a code is: the rules are UserMFA's. The devices that are trusted not to be asked again have no table here: they are rows of the device tokens (DeviceTokenStore, CC-LOGIN-L5-DEVICE-TOKEN; CC-SIGNIN-MERGE-L5-L6-L8).

The tables are created here, when they are not there (ready(Connection)), as the hub and the Push Notification engine create theirs: createBaseTables.sql runs in a database that has no tables at all, so a table added to it never reaches a database that exists. A database in which they cannot be created (no right to create a table) has no second factor: nobody can enrol, and nobody is enrolled.

Two statements carry a rule, so that it holds for every server that shares the database and for two requests at the same moment: acceptStep(Connection, long, long, long) moves the last accepted time step forward only, and tells whether it did, and useCode(Connection, long, int, long) marks a recovery code used only while it is not, and tells whether it did.

Every method takes the connection of its caller and commits nothing: the caller does.

Author:
Christopher Mindus
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static final record 
    A recovery code, as stored.
    static final record 
    The second factor of a user, as stored.
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final String
    The table of the recovery codes, and the table probed to tell whether the script ran to its end: the last one it creates.
    static final String
    The schema of the tables.
    static final String
    The name of the SQL script beside this class.
    static final String
    The table of the second factors.
  • Method Summary

    Modifier and Type
    Method
    Description
    static boolean
    acceptStep(Connection conn, long uid, long step, long now)
    Accepts the code of a time step: the last accepted step moves forward to it, the failures are forgotten.
    static long
    codeLastUsed(Connection conn, long uid)
    Gets the time the recovery code of a user that was used last was used.
    static long
    codesCreated(Connection conn, long uid)
    Gets the time the recovery codes of a user were made.
    static void
    countFailure(Connection conn, long uid, long now)
    Counts an invalid code of a user.
    static boolean
    exists(Connection conn, long uid)
    Checks if a user has a second factor.
    static void
    forgetFailures(Connection conn, long uid, long now)
    Forgets the invalid codes of a user after an accepted recovery code.
    static String
    Gets the class-loader resource name of the SQL script.
    static void
    Inserts the second factor of a user, who has none.
    read(Connection conn, long uid)
    Reads the second factor of a user.
    static boolean
    Makes the tables ready: they exist, or they are created now.
    static boolean
    remove(Connection conn, long uid)
    Removes the second factor of a user and every recovery code.
    static boolean
    Replaces the second factor of a user, who has one, keeping its recovery codes: an authenticator app enrolled by a user whose second factor was a passkey, and the passkey that is left when the app is removed (CC-FIX-SIGNIN-2FA-CHOICE).
    static void
    replaceCodes(Connection conn, long uid, String[] hashes, long now)
    Replaces the recovery codes of a user: every code there is goes, used or not.
    static boolean
    setMethod(Connection conn, long uid, String method)
    Changes the method of the second factor of a user, and nothing else of it - its secret, its steps, its count of invalid codes and its recovery codes stay: an app beside a passkey the user chose, and the app when that passkey is gone (CC-FIX-SIGNIN-2FA-CHOICE).
    unusedCodes(Connection conn, long uid)
    Reads the recovery codes of a user that are not used.
    static boolean
    useCode(Connection conn, long uid, int seq, long now)
    Uses a recovery code: it is marked used, only while it is not.
  • Field Details

  • Method Details

    • getScriptResource

      public static String getScriptResource()
      Gets the class-loader resource name of the SQL script.
      Returns:
      The resource name, com/iizix/server/user/mfa/createMFATables.sql.
    • ready

      public static boolean ready(Connection conn) throws SQLException
      Makes the tables ready: they exist, or they are created now. The connection is committed when they are created.

      An error of the database is not "no tables" (CC-FIX-DERBY-ATTACH, TP-D-8): before, every SQLException of the probe read as "the tables are absent", so hasSecondFactor answered "none" on a transient failure and a password alone signed in a user who has a factor under an optional policy. The probe now tells the two apart, and an error is thrown to the caller, every one of which fails closed.

      Parameters:
      conn - The connection.
      Returns:
      true when the tables exist, false when they do not and cannot be created (logged): the database has no second factor then.
      Throws:
      SQLException - When the database cannot tell whether the tables exist: whether a user has a second factor is then not known.
    • read

      public static MFAStore.Row read(Connection conn, long uid) throws SQLException
      Reads the second factor of a user.
      Parameters:
      conn - The connection.
      uid - The user ID.
      Returns:
      The row, null when the user has none.
      Throws:
      SQLException - For SQL errors.
    • exists

      public static boolean exists(Connection conn, long uid) throws SQLException
      Checks if a user has a second factor.
      Parameters:
      conn - The connection.
      uid - The user ID.
      Returns:
      true when the user has one.
      Throws:
      SQLException - For SQL errors.
    • insert

      public static void insert(Connection conn, MFAStore.Row row) throws SQLException
      Inserts the second factor of a user, who has none.
      Parameters:
      conn - The connection.
      row - The row; its times and counts of failure are not written, they start at zero.
      Throws:
      SQLException - For SQL errors, among them a user who has a second factor already.
    • replace

      public static boolean replace(Connection conn, MFAStore.Row row) throws SQLException
      Replaces the second factor of a user, who has one, keeping its recovery codes: an authenticator app enrolled by a user whose second factor was a passkey, and the passkey that is left when the app is removed (CC-FIX-SIGNIN-2FA-CHOICE).
      Parameters:
      conn - The connection.
      row - The row; its counts of failure are not written, they start at zero.
      Returns:
      true when the user had a second factor, and it is replaced.
      Throws:
      SQLException - For SQL errors.
    • setMethod

      public static boolean setMethod(Connection conn, long uid, String method) throws SQLException
      Changes the method of the second factor of a user, and nothing else of it - its secret, its steps, its count of invalid codes and its recovery codes stay: an app beside a passkey the user chose, and the app when that passkey is gone (CC-FIX-SIGNIN-2FA-CHOICE).
      Parameters:
      conn - The connection.
      uid - The user ID.
      method - The method.
      Returns:
      true when the user had a second factor, and its method is changed.
      Throws:
      SQLException - For SQL errors.
    • acceptStep

      public static boolean acceptStep(Connection conn, long uid, long step, long now) throws SQLException
      Accepts the code of a time step: the last accepted step moves forward to it, the failures are forgotten. It moves forward only - the statement changes no row whose last accepted step is this one or a later one - which is what refuses a code that was accepted before, on this server or on another, and one of two requests that carry the same code.
      Parameters:
      conn - The connection.
      uid - The user ID.
      step - The time step of the code.
      now - The time, milliseconds since the epoch.
      Returns:
      true when the step was accepted, false when the code is not new: its step, or a later one, was accepted before (or the user has no second factor).
      Throws:
      SQLException - For SQL errors.
    • countFailure

      public static void countFailure(Connection conn, long uid, long now) throws SQLException
      Counts an invalid code of a user.
      Parameters:
      conn - The connection.
      uid - The user ID.
      now - The time, milliseconds since the epoch.
      Throws:
      SQLException - For SQL errors.
    • forgetFailures

      public static void forgetFailures(Connection conn, long uid, long now) throws SQLException
      Forgets the invalid codes of a user after an accepted recovery code.
      Parameters:
      conn - The connection.
      uid - The user ID.
      now - The time, milliseconds since the epoch.
      Throws:
      SQLException - For SQL errors.
    • remove

      public static boolean remove(Connection conn, long uid) throws SQLException
      Removes the second factor of a user and every recovery code.
      Parameters:
      conn - The connection.
      uid - The user ID.
      Returns:
      true when the user had a second factor.
      Throws:
      SQLException - For SQL errors.
    • replaceCodes

      public static void replaceCodes(Connection conn, long uid, String[] hashes, long now) throws SQLException
      Replaces the recovery codes of a user: every code there is goes, used or not.
      Parameters:
      conn - The connection.
      uid - The user ID.
      hashes - The hashes of the new codes, in their order.
      now - The time, milliseconds since the epoch.
      Throws:
      SQLException - For SQL errors.
    • codesCreated

      public static long codesCreated(Connection conn, long uid) throws SQLException
      Gets the time the recovery codes of a user were made.
      Parameters:
      conn - The connection.
      uid - The user ID.
      Returns:
      The time, milliseconds since the epoch, zero when the user has none.
      Throws:
      SQLException - For SQL errors.
    • codeLastUsed

      public static long codeLastUsed(Connection conn, long uid) throws SQLException
      Gets the time the recovery code of a user that was used last was used.
      Parameters:
      conn - The connection.
      uid - The user ID.
      Returns:
      The time, milliseconds since the epoch, zero when none was used.
      Throws:
      SQLException - For SQL errors.
    • unusedCodes

      public static List<MFAStore.Code> unusedCodes(Connection conn, long uid) throws SQLException
      Reads the recovery codes of a user that are not used.
      Parameters:
      conn - The connection.
      uid - The user ID.
      Returns:
      The codes in their order, empty when none is left.
      Throws:
      SQLException - For SQL errors.
    • useCode

      public static boolean useCode(Connection conn, long uid, int seq, long now) throws SQLException
      Uses a recovery code: it is marked used, only while it is not.
      Parameters:
      conn - The connection.
      uid - The user ID.
      seq - The number of the code.
      now - The time, milliseconds since the epoch.
      Returns:
      true when the code is used by this call, false when it was used before.
      Throws:
      SQLException - For SQL errors.