Class: WebPush

Inherits:
Object
  • Object
show all
Defined in:
lib/web_push.rb,
lib/web_push/version.rb

Defined Under Namespace

Modules: Utils

Constant Summary collapse

GROUP_NAME =
'prime256v1'
DEFAULT_TTL =

4 weeks

60 * 60 * 24 * 7 * 4
DEFAULT_EXP =

1 day

60 * 60 * 24
VERSION =
"0.1.2"

Instance Method Summary collapse

Constructor Details

#initialize(subscription, ttl: DEFAULT_TTL, exp: DEFAULT_EXP) ⇒ WebPush

Returns a new instance of WebPush.



26
27
28
29
30
31
32
33
# File 'lib/web_push.rb', line 26

def initialize(subscription, ttl: DEFAULT_TTL, exp: DEFAULT_EXP)
  @subscription = subscription
  @endpoint = @subscription[:endpoint]
  @p256dh   = @subscription[:keys][:p256dh]
  @auth     = @subscription[:keys][:auth]
  @ttl = ttl
  @exp = exp
end

Instance Method Details

#encrypt(p256dh, auth, payload) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/web_push.rb', line 72

def encrypt(p256dh, auth, payload)
  user_public_key = urlsafe_decode64 p256dh
  user_auth = urlsafe_decode64 auth

  local_curve = Utils.generate_vapid_pkey
  user_public_key_point = OpenSSL::PKey::EC::Point.new(local_curve.group, OpenSSL::BN.new(user_public_key, 2))

  key = local_curve.dh_compute_key(user_public_key_point)
  server_public_key = local_curve.public_key.to_bn.to_s(2)

  params = {
    key: key,
    salt: SecureRandom.random_bytes(16),
    server_public_key: server_public_key,
    user_public_key: user_public_key,
    auth: user_auth
  }
  params[:cipher] = ECE.encrypt(payload, params)

  params
end

#generate_http_request(payload) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/web_push.rb', line 48

def generate_http_request(payload)
  {
    method: 'POST',
    headers: {
      'TTL' => @ttl.to_s,
      'Content-Type' => 'application/octet-stream',
      'Content-Encoding' => 'aesgcm',
    },
    body: nil,
    uri: URI(@endpoint),
  }.tap do |request|
    encrypted = encrypt(@p256dh, @auth, payload)
    request[:headers]['Content-Length'] = encrypted[:cipher].bytesize.to_s
    request[:headers]['Encryption'] = 'salt=' + urlsafe_encode64(encrypted[:salt])
    request[:headers]['Crypto-Key'] = 'dh=' + urlsafe_encode64(encrypted[:server_public_key])
    request[:body] = encrypted[:cipher]

    audience = request[:uri].scheme + "://" + request[:uri].host
    vapid_headers = generate_vapid_headers audience
    request[:headers]['Authorization'] = vapid_headers['Authorization']
    request[:headers]['Crypto-Key'] += ';' + vapid_headers['Crypto-Key']
  end
end

#generate_vapid_headers(audience, sub: @vapid_subject, exp: @exp) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
# File 'lib/web_push.rb', line 94

def generate_vapid_headers(audience, sub: @vapid_subject, exp: @exp)
  jwt = JWT.encode({
                     aud: audience,
                     exp: Time.now.to_i + exp,
                     sub: sub,
                   }, @vapid_pkey, 'ES256')
  {
    'Authorization' => 'WebPush ' + jwt,
    'Crypto-Key' => 'p256ecdsa=' + urlsafe_encode64(@vapid_public_key_bn),
  }
end

#generate_vapid_pkey(public_key, private_key) ⇒ Object



114
115
116
117
118
119
120
121
122
123
# File 'lib/web_push.rb', line 114

def generate_vapid_pkey(public_key, private_key)
  pvtbn = OpenSSL::BN.new(urlsafe_decode64(private_key), 2)
  pubbn = OpenSSL::BN.new(urlsafe_decode64(public_key), 2)

  pkey = Utils.generate_vapid_pkey
  pkey.private_key = pvtbn
  pkey.public_key = OpenSSL::PKey::EC::Point.new(pkey.group, pubbn)

  pkey
end

#send_notification(payload) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/web_push.rb', line 35

def send_notification(payload)
  params = generate_http_request(payload)

  uri = params[:uri]
  https = Net::HTTP.new uri.host, uri.port
  https.use_ssl = true

  request = Net::HTTP::Post.new uri.request_uri, params[:headers]
  request.body = params[:body]

  https.request request
end

#set_vapid_details(subject, public_key, private_key) ⇒ Object



106
107
108
109
110
111
112
# File 'lib/web_push.rb', line 106

def set_vapid_details(subject, public_key, private_key)
  @vapid_subject = subject
  @vapid_public_key = public_key
  @vapid_private_key = private_key
  @vapid_pkey = generate_vapid_pkey(@vapid_public_key, @vapid_private_key)
  @vapid_public_key_bn = @vapid_pkey.public_key.to_bn.to_s(2)
end

#urlsafe_decode64(bin) ⇒ Object



129
130
131
132
133
134
135
136
# File 'lib/web_push.rb', line 129

def urlsafe_decode64(bin)
  # take from ruby v2.3.3 base64.rb
  if !bin.end_with?("=") && bin.length % 4 != 0
    Base64.urlsafe_decode64 bin.ljust((bin.length + 3) & ~3, "=")
  else
    Base64.urlsafe_decode64 bin
  end
end

#urlsafe_encode64(bin) ⇒ Object



125
126
127
# File 'lib/web_push.rb', line 125

def urlsafe_encode64(bin)
  Base64.urlsafe_encode64(bin).delete('=')
end