Module: Groovestack::Auth::Passwordless::TOtpTokenizer
- Defined in:
- lib/groovestack/auth/passwordless/t_otp_tokenizer.rb
Class Method Summary collapse
-
.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).
-
.encode(resource, _extra: nil, _expires_at: nil) ⇒ Object
Generates the token for the current time window.
-
.token_validity_seconds ⇒ Object
Validity period (in seconds).
-
.totp_for(resource, digits: 4, interval: token_validity_seconds) ⇒ Object
Generate a TOTP instance for the resource.
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.
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.passwordless_expire_old_tokens_on_sign_in && resource.current_sign_in_at.present? # support login / logout / login again within same token validity period verify_opts[:after] = (resource.current_sign_in_at - 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_seconds ⇒ Object
Validity period (in seconds)
13 14 15 |
# File 'lib/groovestack/auth/passwordless/t_otp_tokenizer.rb', line 13 def token_validity_seconds Devise.passwordless_login_within.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.current_sign_in_at.to_i}" if resource.current_sign_in_at.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 |