-
Notifications
You must be signed in to change notification settings - Fork 9
/
user_token.rb
55 lines (40 loc) · 1.23 KB
/
user_token.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# frozen_string_literal: true
class UserToken < ApplicationRecord
SHORT_LENGTH = 8
LONG_LENGTH = 32
LONG_MASKED = "X" * LONG_LENGTH
VALUE_SEPARATOR = "_"
belongs_to :user
attribute :long, :string
before_validation :refresh, on: :create
validates :long, presence: true, length: {is: LONG_LENGTH}
validates :short, presence: true, length: {is: SHORT_LENGTH}
def self.parse_value(arg)
arg.split(VALUE_SEPARATOR)
end
def self.salt_parts(short:)
a, b, c, d, e, f, g, h = short.chars
["#{h}#{c}", "#{e}#{g}", "#{b}#{d}", "#{f}#{a}"]
end
def self.secret_value(short:, long:)
salt1, salt2, salt3, salt4 = salt_parts(short:)
"#{salt2}-#{salt3}.:#{long}:.#{salt4}_#{salt1}"
end
def self.checksum(...)
Digest::SHA256.hexdigest(secret_value(...))
end
def value
"#{short}#{VALUE_SEPARATOR}#{long || LONG_MASKED}"
end
def refresh(secure_random: SecureRandom)
self.short = secure_random.base58(SHORT_LENGTH)
self.long = secure_random.base58(LONG_LENGTH)
self.checksum = self.class.checksum(short:, long:)
end
def refresh!(...)
attempts ||= 1
refresh(...).then { save! }.then { self }
rescue ActiveRecord::RecordNotUnique
retry if (attempts += 1) <= 3
end
end