Class DeviceService

java.lang.Object
sk.iway.iwcm.users.devices.DeviceService

@Service public class DeviceService extends Object
Persists browser recognition, device confirmation and blocking for individual user accounts.

Each account and browser-token hash identify one device record, which also holds its latest new-device notice. Recognition depends on the last successful login, independently of whether the owner has confirmed the device. Confirmation resolves the notice; blocking requires an email-code challenge before that browser can complete another login. Expiration of the notice alone does not remove the device or its block.

AdminDeviceService handles authentication, cookies, proof generation, email delivery and session termination. This service receives account IDs and already hashed tokens or proofs, applies the persistence rules and audits state changes. Repository failures are exposed as IllegalStateException so callers can distinguish unavailable storage from a rejected proof.

  • Constructor Summary

    Constructors
    Constructor
    Description
    DeviceService(DeviceRepository deviceRepository)
     
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    cleanup(long knownSinceCutoff)
    Removes unblocked devices whose last successful login is at or before the retention cutoff.
    void
    completeLocation(int userId, long deviceId, long loginTime, String location)
    Adds a location obtained after login without replacing a location already stored for that login.
    confirm(int userId, long id, String hash, long now, boolean code)
    Confirms an unblocked device using a proof delivered to the account owner's email.
    confirmAfterSecondFactor(int userId, long id, long now)
    Confirms a device based on a second factor already verified by the login flow.
    findActive(int userId, long now)
    Finds recent device notices that still need the account owner's attention on the dashboard.
    findByIds(int userId, Set<Long> ids)
    Loads device states in one query so session listings can show current confirmation information.
    findByTokenHash(int userId, String tokenHash)
    Resolves a browser cookie to its account-owned device record without changing device state.
    org.springframework.data.domain.Page<DeviceEntity>
    findDevices(int userId, org.springframework.data.domain.Pageable pageable)
    Lists retained device records for the account's device-management view.
    findEvent(int userId, long id)
    Loads an owned device for a notice detail or security action, regardless of its notice age.
    boolean
    issueConfirmation(int userId, long id, String hash, long now, boolean code)
    Stores a confirmation proof before the caller sends its secret to the account owner's email.
    boolean
    issueUnblockCode(int userId, long id, String hash, long now)
    Stores an email code for the pending login challenge of a blocked browser.
    recordLogin(int userId, String tokenHash, long now, long knownSinceCutoff, String browserName, String browserVersion, String operatingSystem, String ipAddress)
    Records a successful login when no location is available.
    recordLogin(int userId, String tokenHash, long now, long knownSinceCutoff, String browserName, String browserVersion, String operatingSystem, String ipAddress, String location)
    Records the latest successful login and creates a notice when browser recognition has expired.
    report(int userId, long id, long now)
    Marks an owned browser as blocked after the account owner reports an unrecognized login.
    unblock(int userId, long id, String hash, long now)
    Removes a browser's block after its pending login email code is accepted.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Constructor Details

  • Method Details

    • recordLogin

      public DeviceEntity recordLogin(int userId, String tokenHash, long now, long knownSinceCutoff, String browserName, String browserVersion, String operatingSystem, String ipAddress)
      Records a successful login when no location is available.

      Uses the same recognition rules as the location-aware overload and stores null as the latest location, including when a previous login had a known location.

      Parameters:
      userId - account that completed authentication
      tokenHash - hash of the persistent browser-recognition token
      now - successful login time in epoch milliseconds
      knownSinceCutoff - last-login cutoff in epoch milliseconds; older or equal values require re-detection
      browserName - browser name to retain when the device is detected
      browserVersion - browser version to retain when the device is detected
      operatingSystem - operating system to retain when the device is detected
      ipAddress - IP address of this login
      Returns:
      the newly detected or re-detected device, or null for an already recognized device
      Throws:
      IllegalStateException - if the device is blocked or the operation fails
    • recordLogin

      public DeviceEntity recordLogin(int userId, String tokenHash, long now, long knownSinceCutoff, String browserName, String browserVersion, String operatingSystem, String ipAddress, String location)
      Records the latest successful login and creates a notice when browser recognition has expired.

      A device is recognized when its stored lastSeen is strictly newer than the cutoff; confirmation is not required for recognition. Every accepted login refreshes the time, IP and location. A recognized device keeps its original browser/OS snapshot and confirmation state.

      A missing device is inserted; an expired device reuses its existing record. Both cases start a new notice, capture the browser/OS snapshot and clear confirmation and outstanding email proofs. Returning that record tells the caller to send a notification. Blocked devices must first pass the separate unblock flow and cannot be refreshed here.

      Parameters:
      userId - account that completed authentication
      tokenHash - hash of the persistent browser-recognition token
      now - successful login time in epoch milliseconds
      knownSinceCutoff - last-login cutoff in epoch milliseconds; older or equal values require re-detection
      browserName - browser name to retain when the device is detected
      browserVersion - browser version to retain when the device is detected
      operatingSystem - operating system to retain when the device is detected
      ipAddress - IP address of this login
      location - advisory location of this login, or null when unavailable
      Returns:
      the newly detected or re-detected device, or null for an already recognized device
      Throws:
      IllegalStateException - if the device is blocked or the operation fails
    • completeLocation

      public void completeLocation(int userId, long deviceId, long loginTime, String location)
      Adds a location obtained after login without replacing a location already stored for that login.

      The lookup must still show the same login timestamp and a missing location; otherwise the result is ignored. This check avoids applying a delayed result to a newer login. Adding the location does not refresh recognition, create another notice or send another notification.

      Parameters:
      userId - account that owns the device
      deviceId - device associated with the completed login
      loginTime - original successful login time in epoch milliseconds
      location - advisory location resolved for that login
    • findActive

      public List<DeviceEntity> findActive(int userId, long now)
      Finds recent device notices that still need the account owner's attention on the dashboard.

      Only unconfirmed, unblocked devices detected less than seven days ago are included. Repeated recognized logins update lastSeen, but do not extend this notice period.

      Parameters:
      userId - account whose notices are requested
      now - current time in epoch milliseconds, used to calculate the notice cutoff
      Returns:
      unresolved devices ordered by detection time descending and ID ascending
    • findEvent

      public DeviceEntity findEvent(int userId, long id)
      Loads an owned device for a notice detail or security action, regardless of its notice age.

      Unlike findActive(int, long), this also returns confirmed, blocked and notice-expired devices. Callers must check whether their intended action is allowed for the returned state.

      Parameters:
      userId - account that must own the device
      id - device ID referenced by the notice or action
      Returns:
      the current device record, or null when missing or owned by another account
    • findByTokenHash

      public DeviceEntity findByTokenHash(int userId, String tokenHash)
      Resolves a browser cookie to its account-owned device record without changing device state.

      Used both to check blocking before login and to associate a recognized device with the completed session. A matching record alone does not establish that recognition is still valid.

      Parameters:
      userId - account to which the browser token belongs
      tokenHash - hash of the browser-recognition token, not the raw cookie value
      Returns:
      the matching device, or null when the account has no record for this token
    • findByIds

      public List<DeviceEntity> findByIds(int userId, Set<Long> ids)
      Loads device states in one query so session listings can show current confirmation information.
      Parameters:
      userId - account whose devices may be returned
      ids - device IDs attached to the account's sessions; an empty set avoids a database query
      Returns:
      matching owned devices, omitting missing and foreign IDs without guaranteeing order
    • findDevices

      public org.springframework.data.domain.Page<DeviceEntity> findDevices(int userId, org.springframework.data.domain.Pageable pageable)
      Lists retained device records for the account's device-management view.

      Includes confirmed, blocked and notice-expired devices, even without an active session. Pagination and ordering come from the caller rather than the active-notice rules.

      Parameters:
      userId - account whose devices are listed
      pageable - requested page, size and sort order
      Returns:
      a page of owned device records with the total matching count
    • issueConfirmation

      public boolean issueConfirmation(int userId, long id, String hash, long now, boolean code)
      Stores a confirmation proof before the caller sends its secret to the account owner's email.

      Only unconfirmed, unblocked devices qualify. Links last 24 hours; codes last ten minutes and use the resend and attempt limits described by issueProof(int, long, String, long, boolean, boolean). Issuing one proof type preserves the other, allowing an existing email link to remain usable.

      Parameters:
      userId - account that must own the device
      id - device to confirm
      hash - hash of the new email token or code, prepared by the caller
      now - issue time in epoch milliseconds
      code - true for a numeric email code, false for an email-link token
      Returns:
      true when stored, or false when the device state or code limits reject issuance
    • issueUnblockCode

      public boolean issueUnblockCode(int userId, long id, String hash, long now)
      Stores an email code for the pending login challenge of a blocked browser.

      The caller binds the code hash to the pending login and sends the email only after issuance succeeds. This reuses the device's code fields and limits; it does not remove the block.

      Parameters:
      userId - account that must own the blocked device
      id - device awaiting unblock verification
      hash - hash of the session-bound unblock code, prepared by the caller
      now - issue time in epoch milliseconds
      Returns:
      true when stored, or false when the device state or code limits reject issuance
    • confirm

      public DeviceEntity confirm(int userId, long id, String hash, long now, boolean code)
      Confirms an unblocked device using a proof delivered to the account owner's email.

      Delegates proof validation and code-attempt accounting to the shared verification flow. Successful confirmation clears both proof types and resolves the notice without refreshing the last-login timestamp. This action cannot remove a device block.

      Parameters:
      userId - account that must own the device
      id - device to confirm
      hash - hash of the submitted email proof, prepared by the caller
      now - verification time in epoch milliseconds
      code - true for a numeric email code, false for an email-link token
      Returns:
      the confirmed device, or null when the device or proof cannot be accepted
    • unblock

      public DeviceEntity unblock(int userId, long id, String hash, long now)
      Removes a browser's block after its pending login email code is accepted.

      Also confirms the device and refreshes lastSeen, preventing immediate re-detection after a long block. The caller remains responsible for completing the pending authentication.

      Parameters:
      userId - account that must own the blocked device
      id - device awaiting unblock verification
      hash - hash of the submitted session-bound unblock code
      now - verification time in epoch milliseconds
      Returns:
      the unblocked, confirmed device, or null when the device or code cannot be accepted
    • confirmAfterSecondFactor

      public DeviceEntity confirmAfterSecondFactor(int userId, long id, long now)
      Confirms a device based on a second factor already verified by the login flow.

      The caller must establish that the factor succeeded; this method does not validate it. A newly confirmed device has both email proofs revoked and the transition audited. An already confirmed device is returned unchanged. Blocked devices still require their email-code challenge.

      Parameters:
      userId - account whose second factor was verified
      id - device used for the completed login
      now - confirmation time in epoch milliseconds
      Returns:
      the confirmed device, or null when missing, foreign or blocked
    • report

      public DeviceEntity report(int userId, long id, long now)
      Marks an owned browser as blocked after the account owner reports an unrecognized login.

      Clears confirmation and both email proofs, removing the device from active notices while retaining its record for future login checks. Repeated reports preserve the original block time and produce no additional audit entry. Session termination is handled by AdminDeviceService.

      Parameters:
      userId - account reporting its device
      id - device to block
      now - report time in epoch milliseconds
      Returns:
      the blocked device, including an already blocked record, or null when missing or foreign
    • cleanup

      public void cleanup(long knownSinceCutoff)
      Removes unblocked devices whose last successful login is at or before the retention cutoff.

      Called by DeviceCleanup; deleting a device also removes its notice and outstanding proofs. Blocked records are retained regardless of age so cleanup cannot silently lift a block.

      Parameters:
      knownSinceCutoff - latest expired last-login time in epoch milliseconds, inclusive