Class: Admin::DeviceAuthorization

Inherits:
ApplicationRecord show all
Defined in:
app/models/admin/device_authorization.rb

Defined Under Namespace

Classes: TokenError

Constant Summary collapse

EXPIRES_IN =
10.minutes

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.digest(device_code) ⇒ Object



47
48
49
# File 'app/models/admin/device_authorization.rb', line 47

def self.digest(device_code)
  Digest::SHA256.hexdigest(device_code)
end

.generate_user_codeObject



51
52
53
# File 'app/models/admin/device_authorization.rb', line 51

def self.generate_user_code
  "#{SecureRandom.alphanumeric(4).upcase}-#{SecureRandom.alphanumeric(4).upcase}"
end

.issue!(requested_ip:, user_agent:) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'app/models/admin/device_authorization.rb', line 33

def self.issue!(requested_ip:, user_agent:)
  device_code = SecureRandom.urlsafe_base64(32)

  device_authorization = create!(
    device_code_digest: digest(device_code),
    user_code:          generate_user_code,
    request_expires_at: EXPIRES_IN.from_now,
    requested_ip:,
    user_agent:,
  )

  [device_authorization, device_code]
end

.issue_access_token!(device_code:, token_expires_in: 12.hours) ⇒ Object

Raises:



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'app/models/admin/device_authorization.rb', line 55

def self.issue_access_token!(device_code:, token_expires_in: 12.hours)
  device_authorization = find_by(device_code_digest: digest(device_code.to_s))
  raise TokenError.new("invalid_grant") unless device_authorization

  device_authorization.with_lock do
    device_authorization.reload

    if (error = device_authorization.token_error)
      raise TokenError.new(error)
    end

    access_token = device_authorization.generate_token_for(:api_access)
    device_authorization.consume!(token_expires_in:)

    {
      access_token:,
      token_type:   "Bearer",
      expires_in:   token_expires_in.to_i,
    }
  end
end

Instance Method Details

#actionable?Boolean

Returns:

  • (Boolean)


115
116
117
# File 'app/models/admin/device_authorization.rb', line 115

def actionable?
  pending? && !expired?
end

#approve!(admin_user:) ⇒ Object



100
101
102
103
104
105
106
# File 'app/models/admin/device_authorization.rb', line 100

def approve!(admin_user:)
  update!(
    status:      "approved",
    approved_at: Time.current,
    admin_user:,
  )
end

#consume!(token_expires_in:) ⇒ Object



92
93
94
95
96
97
98
# File 'app/models/admin/device_authorization.rb', line 92

def consume!(token_expires_in:)
  update!(
    status:           "consumed",
    consumed_at:      Time.current,
    token_expires_at: token_expires_in.from_now,
  )
end

#deny!(admin_user:) ⇒ Object



108
109
110
111
112
113
# File 'app/models/admin/device_authorization.rb', line 108

def deny!(admin_user:)
  update!(
    status:     "denied",
    admin_user:,
  )
end

#expired?Boolean

Returns:

  • (Boolean)


77
78
79
# File 'app/models/admin/device_authorization.rb', line 77

def expired?
  request_expires_at <= Time.current
end

#issuable?Boolean

Returns:

  • (Boolean)


81
82
83
# File 'app/models/admin/device_authorization.rb', line 81

def issuable?
  approved? && !expired?
end

#token_errorObject



85
86
87
88
89
90
# File 'app/models/admin/device_authorization.rb', line 85

def token_error
  return "authorization_pending" if pending?
  return "access_denied" if denied?

  "invalid_grant" if consumed? || expired?
end