push_subscription.rb 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. # frozen_string_literal: true
  2. # == Schema Information
  3. #
  4. # Table name: web_push_subscriptions
  5. #
  6. # id :bigint(8) not null, primary key
  7. # endpoint :string not null
  8. # key_p256dh :string not null
  9. # key_auth :string not null
  10. # data :json
  11. # created_at :datetime not null
  12. # updated_at :datetime not null
  13. # access_token_id :bigint(8)
  14. # user_id :bigint(8)
  15. #
  16. class Web::PushSubscription < ApplicationRecord
  17. belongs_to :user, optional: true
  18. belongs_to :access_token, class_name: 'Doorkeeper::AccessToken', optional: true
  19. has_one :session_activation, foreign_key: 'web_push_subscription_id', inverse_of: :web_push_subscription
  20. validates :endpoint, presence: true
  21. validates :key_p256dh, presence: true
  22. validates :key_auth, presence: true
  23. delegate :locale, to: :associated_user
  24. def encrypt(payload)
  25. Webpush::Encryption.encrypt(payload, key_p256dh, key_auth)
  26. end
  27. def audience
  28. @audience ||= Addressable::URI.parse(endpoint).normalized_site
  29. end
  30. def crypto_key_header
  31. p256ecdsa = vapid_key.public_key_for_push_header
  32. "p256ecdsa=#{p256ecdsa}"
  33. end
  34. def authorization_header
  35. jwt = JWT.encode({ aud: audience, exp: 24.hours.from_now.to_i, sub: "mailto:#{contact_email}" }, vapid_key.curve, 'ES256', typ: 'JWT')
  36. "WebPush #{jwt}"
  37. end
  38. def pushable?(notification)
  39. policy_allows_notification?(notification) && alert_enabled_for_notification_type?(notification)
  40. end
  41. def associated_user
  42. return @associated_user if defined?(@associated_user)
  43. @associated_user = begin
  44. if user_id.nil?
  45. session_activation.user
  46. else
  47. user
  48. end
  49. end
  50. end
  51. def associated_access_token
  52. return @associated_access_token if defined?(@associated_access_token)
  53. @associated_access_token = begin
  54. if access_token_id.nil?
  55. find_or_create_access_token.token
  56. else
  57. access_token.token
  58. end
  59. end
  60. end
  61. class << self
  62. def unsubscribe_for(application_id, resource_owner)
  63. access_token_ids = Doorkeeper::AccessToken.where(application_id: application_id, resource_owner_id: resource_owner.id, revoked_at: nil).pluck(:id)
  64. where(access_token_id: access_token_ids).delete_all
  65. end
  66. end
  67. private
  68. def find_or_create_access_token
  69. Doorkeeper::AccessToken.find_or_create_for(
  70. application: Doorkeeper::Application.find_by(superapp: true),
  71. resource_owner: user_id || session_activation.user_id,
  72. scopes: Doorkeeper::OAuth::Scopes.from_string('read write follow push'),
  73. expires_in: Doorkeeper.configuration.access_token_expires_in,
  74. use_refresh_token: Doorkeeper.configuration.refresh_token_enabled?
  75. )
  76. end
  77. def vapid_key
  78. @vapid_key ||= Webpush::VapidKey.from_keys(Rails.configuration.x.vapid_public_key, Rails.configuration.x.vapid_private_key)
  79. end
  80. def contact_email
  81. @contact_email ||= ::Setting.site_contact_email
  82. end
  83. def alert_enabled_for_notification_type?(notification)
  84. truthy?(data&.dig('alerts', notification.type.to_s))
  85. end
  86. def policy_allows_notification?(notification)
  87. case data&.dig('policy')
  88. when nil, 'all'
  89. true
  90. when 'none'
  91. false
  92. when 'followed'
  93. notification.account.following?(notification.from_account)
  94. when 'follower'
  95. notification.from_account.following?(notification.account)
  96. end
  97. end
  98. def truthy?(val)
  99. ActiveModel::Type::Boolean.new.cast(val)
  100. end
  101. end