Module: Groovestack::Auth::Passwordless::TOtpTokenizer

Defined in:
lib/groovestack/auth/passwordless/t_otp_tokenizer.rb

Class Method Summary collapse

Class Method Details

.decode(code, email, resource_class, _as_of: nil, _expire_duration: nil) ⇒ Object

Verifies that the provided token is valid (i.e. within the current time window).

Since the token is time‐dependent, expiration is enforced by the TOTP algorithm.

Raises:

  • (::Devise::Passwordless::InvalidTokenError)


43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/groovestack/auth/passwordless/t_otp_tokenizer.rb', line 43

def decode(code, email, resource_class, _as_of: nil, _expire_duration: nil)
  raise ::Devise::Passwordless::InvalidTokenError if code.blank?

  # For TOTP, we look up the resource and then verify the code.
  # Note: How you locate the resource is up to your application.
  # For example, if your code is combined with an email or user id in the URL, you would look it up there.
  #
  resource = resource_class.find_by(email: email)

  raise 'Resource not found' if resource.blank?

  # # Remove the hyphen to get the plain digits.
  # otp = otp_from_formatted_code(code)
  otp = code

  verify_opts = {}
  # by default, ROTP is fixed window interval. This code enables a sliding window
  now = Time.zone.now
  verify_opts[:at] = now
  verify_opts[:drift_behind] = token_validity_seconds / 2 # (now.sec % token_validity_seconds) - 1
  if resource_class. && resource..present?
    # support login / logout / login again within same token validity period
    verify_opts[:after] = (resource. - token_validity_seconds).to_i
  end
  # end sliding window logic
  decrypted_data = totp_for(resource).verify(otp, **verify_opts)

  raise ::Devise::Passwordless::InvalidOrExpiredTokenError if decrypted_data.blank?

  [resource, { data: decrypted_data }]
end

.encode(resource, _extra: nil, _expires_at: nil) ⇒ Object

Generates the token for the current time window.



33
34
35
36
37
38
# File 'lib/groovestack/auth/passwordless/t_otp_tokenizer.rb', line 33

def encode(resource, _extra: nil, _expires_at: nil)
  # otp = totp_for(resource).now

  # otp_as_formatted_code(otp)
  totp_for(resource).now
end

.token_validity_secondsObject

Validity period (in seconds)



13
14
15
# File 'lib/groovestack/auth/passwordless/t_otp_tokenizer.rb', line 13

def token_validity_seconds
  Devise..seconds
end

.totp_for(resource, digits: 4, interval: token_validity_seconds) ⇒ Object

Generate a TOTP instance for the resource.

We derive a per‑resource secret from a combination of the resource’s id and Rails’ secret.



20
21
22
23
24
25
26
27
28
29
30
# File 'lib/groovestack/auth/passwordless/t_otp_tokenizer.rb', line 20

def totp_for(resource, digits: 4, interval: token_validity_seconds)
  # add last sign in at to prevent same otp being generated multiple times
  identifier = resource.id
  identifier += ":#{resource..to_i}" if resource..present?

  raw_secret = OpenSSL::HMAC.digest('sha1', Rails.application.secret_key_base, identifier)
  base32_secret = ROTP::Base32.encode raw_secret

  # Create a TOTP object configured for 4 digits and the given time interval.
  ROTP::TOTP.new(base32_secret, digits: digits, interval: interval)
end