push_subscription.rb 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  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, dependent: nil
  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 = if user_id.nil?
  44. session_activation.user
  45. else
  46. user
  47. end
  48. end
  49. def associated_access_token
  50. return @associated_access_token if defined?(@associated_access_token)
  51. @associated_access_token = if access_token_id.nil?
  52. find_or_create_access_token.token
  53. else
  54. access_token.token
  55. end
  56. end
  57. class << self
  58. def unsubscribe_for(application_id, resource_owner)
  59. access_token_ids = Doorkeeper::AccessToken.where(application_id: application_id, resource_owner_id: resource_owner.id, revoked_at: nil).pluck(:id)
  60. where(access_token_id: access_token_ids).delete_all
  61. end
  62. end
  63. private
  64. def find_or_create_access_token
  65. Doorkeeper::AccessToken.find_or_create_for(
  66. application: Doorkeeper::Application.find_by(superapp: true),
  67. resource_owner: user_id || session_activation.user_id,
  68. scopes: Doorkeeper::OAuth::Scopes.from_string('read write follow push'),
  69. expires_in: Doorkeeper.configuration.access_token_expires_in,
  70. use_refresh_token: Doorkeeper.configuration.refresh_token_enabled?
  71. )
  72. end
  73. def vapid_key
  74. @vapid_key ||= Webpush::VapidKey.from_keys(Rails.configuration.x.vapid_public_key, Rails.configuration.x.vapid_private_key)
  75. end
  76. def contact_email
  77. @contact_email ||= ::Setting.site_contact_email
  78. end
  79. def alert_enabled_for_notification_type?(notification)
  80. truthy?(data&.dig('alerts', notification.type.to_s))
  81. end
  82. def policy_allows_notification?(notification)
  83. case data&.dig('policy')
  84. when nil, 'all'
  85. true
  86. when 'none'
  87. false
  88. when 'followed'
  89. notification.account.following?(notification.from_account)
  90. when 'follower'
  91. notification.from_account.following?(notification.account)
  92. end
  93. end
  94. def truthy?(val)
  95. ActiveModel::Type::Boolean.new.cast(val)
  96. end
  97. end