mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 01:31:39 -05:00
Remove patreon OAuth integration
This commit is contained in:
parent
fde478e2a4
commit
a4dcd0387f
12 changed files with 6 additions and 483 deletions
|
|
@ -28,14 +28,6 @@ class SettingsController < ApplicationController
|
||||||
redirect_back(fallback_location: root_path)
|
redirect_back(fallback_location: root_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
def disconnect_patreon
|
|
||||||
if current_user.disconnect_patreon!
|
|
||||||
redirect_to settings_path, notice: 'Patreon account disconnected successfully'
|
|
||||||
else
|
|
||||||
redirect_to settings_path, alert: 'Unable to disconnect Patreon account'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def settings_params
|
def settings_params
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,6 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||||
handle_auth('OpenID Connect')
|
handle_auth('OpenID Connect')
|
||||||
end
|
end
|
||||||
|
|
||||||
def patreon
|
|
||||||
handle_auth('Patreon')
|
|
||||||
end
|
|
||||||
|
|
||||||
def failure
|
def failure
|
||||||
error_type = request.env['omniauth.error.type']
|
error_type = request.env['omniauth.error.type']
|
||||||
error = request.env['omniauth.error']
|
error = request.env['omniauth.error']
|
||||||
|
|
|
||||||
|
|
@ -156,10 +156,7 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength
|
||||||
user = User.find_by(provider: provider, uid: uid)
|
user = User.find_by(provider: provider, uid: uid)
|
||||||
|
|
||||||
if user
|
if user
|
||||||
# Update tokens for existing user
|
# User found by provider/uid
|
||||||
if provider == 'patreon'
|
|
||||||
user.update_patreon_tokens(access_token)
|
|
||||||
end
|
|
||||||
return user
|
return user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -169,9 +166,6 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength
|
||||||
if user
|
if user
|
||||||
# Update provider and uid for existing user (first-time linking)
|
# Update provider and uid for existing user (first-time linking)
|
||||||
user.update(provider: provider, uid: uid)
|
user.update(provider: provider, uid: uid)
|
||||||
if provider == 'patreon'
|
|
||||||
user.update_patreon_tokens(access_token)
|
|
||||||
end
|
|
||||||
return user
|
return user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -183,55 +177,9 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength
|
||||||
uid: uid
|
uid: uid
|
||||||
)
|
)
|
||||||
|
|
||||||
if provider == 'patreon'
|
|
||||||
user.update_patreon_tokens(access_token)
|
|
||||||
end
|
|
||||||
|
|
||||||
user
|
user
|
||||||
end
|
end
|
||||||
|
|
||||||
def patreon_connected?
|
|
||||||
provider == 'patreon' && uid.present?
|
|
||||||
end
|
|
||||||
|
|
||||||
def disconnect_patreon!
|
|
||||||
return false unless patreon_connected?
|
|
||||||
|
|
||||||
update(
|
|
||||||
provider: nil,
|
|
||||||
uid: nil,
|
|
||||||
patreon_access_token: nil,
|
|
||||||
patreon_refresh_token: nil,
|
|
||||||
patreon_token_expires_at: nil
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_patreon_tokens(auth)
|
|
||||||
credentials = auth.credentials
|
|
||||||
update(
|
|
||||||
patreon_access_token: credentials.token,
|
|
||||||
patreon_refresh_token: credentials.refresh_token,
|
|
||||||
patreon_token_expires_at: Time.at(credentials.expires_at)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Check if user is a patron of a specific creator
|
|
||||||
# @param creator_id [String] The Patreon creator ID to check
|
|
||||||
# @return [Boolean] true if user is an active patron
|
|
||||||
def patron_of?(creator_id)
|
|
||||||
return false unless patreon_connected?
|
|
||||||
|
|
||||||
Patreon::PatronChecker.new(self).patron_of?(creator_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get all campaigns the user is supporting
|
|
||||||
# @return [Array<Hash>] Array of campaign data
|
|
||||||
def patreon_memberships
|
|
||||||
return [] unless patreon_connected?
|
|
||||||
|
|
||||||
Patreon::PatronChecker.new(self).memberships
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def create_api_key
|
def create_api_key
|
||||||
|
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Patreon
|
|
||||||
# Service to check Patreon patron status
|
|
||||||
class PatronChecker
|
|
||||||
attr_reader :user
|
|
||||||
|
|
||||||
def initialize(user)
|
|
||||||
@user = user
|
|
||||||
end
|
|
||||||
|
|
||||||
# Check if user is a patron of a specific creator
|
|
||||||
# @param creator_id [String] The Patreon creator ID
|
|
||||||
# @return [Boolean] true if user is an active patron
|
|
||||||
def patron_of?(creator_id)
|
|
||||||
memberships.any? do |membership|
|
|
||||||
membership.dig('relationships', 'campaign', 'data', 'id') == creator_id.to_s &&
|
|
||||||
membership.dig('attributes', 'patron_status') == 'active_patron'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get all active memberships
|
|
||||||
# @return [Array<Hash>] Array of membership data with campaign info
|
|
||||||
def memberships
|
|
||||||
@memberships ||= fetch_memberships
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get detailed membership info for a specific creator
|
|
||||||
# @param creator_id [String] The Patreon creator ID
|
|
||||||
# @return [Hash, nil] Membership details or nil if not a patron
|
|
||||||
def membership_for(creator_id)
|
|
||||||
memberships.find do |membership|
|
|
||||||
membership.dig('relationships', 'campaign', 'data', 'id') == creator_id.to_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def fetch_memberships
|
|
||||||
return [] unless valid_token?
|
|
||||||
|
|
||||||
response = make_api_request
|
|
||||||
return [] unless response
|
|
||||||
|
|
||||||
extract_memberships(response)
|
|
||||||
rescue StandardError => e
|
|
||||||
Rails.logger.error("Failed to fetch Patreon memberships: #{e.message}")
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
|
|
||||||
def valid_token?
|
|
||||||
return false if user.patreon_access_token.blank?
|
|
||||||
|
|
||||||
# Check if token is expired
|
|
||||||
if user.patreon_token_expires_at && user.patreon_token_expires_at < Time.current
|
|
||||||
refresh_token!
|
|
||||||
end
|
|
||||||
|
|
||||||
user.patreon_access_token.present?
|
|
||||||
end
|
|
||||||
|
|
||||||
def refresh_token!
|
|
||||||
return false if user.patreon_refresh_token.blank?
|
|
||||||
|
|
||||||
conn = Faraday.new(url: 'https://www.patreon.com') do |f|
|
|
||||||
f.request :url_encoded
|
|
||||||
f.response :json
|
|
||||||
f.adapter Faraday.default_adapter
|
|
||||||
end
|
|
||||||
|
|
||||||
response = conn.post('/api/oauth2/token') do |req|
|
|
||||||
req.body = {
|
|
||||||
grant_type: 'refresh_token',
|
|
||||||
refresh_token: user.patreon_refresh_token,
|
|
||||||
client_id: ENV['PATREON_CLIENT_ID'],
|
|
||||||
client_secret: ENV['PATREON_CLIENT_SECRET']
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
if response.success?
|
|
||||||
data = response.body
|
|
||||||
user.update(
|
|
||||||
patreon_access_token: data['access_token'],
|
|
||||||
patreon_refresh_token: data['refresh_token'],
|
|
||||||
patreon_token_expires_at: Time.current + data['expires_in'].seconds
|
|
||||||
)
|
|
||||||
true
|
|
||||||
else
|
|
||||||
Rails.logger.error("Failed to refresh Patreon token: #{response.body}")
|
|
||||||
false
|
|
||||||
end
|
|
||||||
rescue StandardError => e
|
|
||||||
Rails.logger.error("Error refreshing Patreon token: #{e.message}")
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def make_api_request
|
|
||||||
conn = Faraday.new(url: 'https://www.patreon.com') do |f|
|
|
||||||
f.request :url_encoded
|
|
||||||
f.response :json
|
|
||||||
f.adapter Faraday.default_adapter
|
|
||||||
end
|
|
||||||
|
|
||||||
response = conn.get('/api/oauth2/v2/identity') do |req|
|
|
||||||
req.headers['Authorization'] = "Bearer #{user.patreon_access_token}"
|
|
||||||
req.params = {
|
|
||||||
include: 'memberships,memberships.campaign',
|
|
||||||
'fields[member]' => 'patron_status,pledge_relationship_start',
|
|
||||||
'fields[campaign]' => 'vanity,url'
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
response.success? ? response.body : nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def extract_memberships(response)
|
|
||||||
return [] unless response['included']
|
|
||||||
|
|
||||||
memberships = response['included'].select { |item| item['type'] == 'member' }
|
|
||||||
campaigns = response['included'].select { |item| item['type'] == 'campaign' }
|
|
||||||
|
|
||||||
# Enrich memberships with campaign data
|
|
||||||
memberships.map do |membership|
|
|
||||||
campaign_id = membership.dig('relationships', 'campaign', 'data', 'id')
|
|
||||||
campaign = campaigns.find { |c| c['id'] == campaign_id }
|
|
||||||
|
|
||||||
membership.merge('campaign' => campaign) if campaign
|
|
||||||
end.compact
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -78,35 +78,8 @@
|
||||||
</svg>Connected Accounts
|
</svg>Connected Accounts
|
||||||
</h2>
|
</h2>
|
||||||
<div class="bg-base-100 p-5 rounded-lg shadow-sm space-y-4">
|
<div class="bg-base-100 p-5 rounded-lg shadow-sm space-y-4">
|
||||||
<div class="flex items-center justify-between p-4 border border-base-300 rounded-lg">
|
|
||||||
<div class="flex items-center space-x-3">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" class="text-[#FF424D]">
|
|
||||||
<path d="M0 .48v23.04h24V.48zm22.89 21.93H1.11V1.61h21.78zm-3.2-6.15c-1.05 0-2.08-.28-2.99-.81.88 1.99 2.83 3.37 5.08 3.37 3.09 0 5.59-2.5 5.59-5.59 0-3.09-2.5-5.59-5.59-5.59-2.25 0-4.2 1.33-5.08 3.25.9-.48 1.93-.73 3-.73 3.56 0 6.44 2.88 6.44 6.44s-2.88 6.44-6.44 6.44z"/>
|
|
||||||
</svg>
|
|
||||||
<div>
|
|
||||||
<h3 class="font-semibold">Patreon</h3>
|
|
||||||
<p class="text-sm text-base-content/70">
|
<p class="text-sm text-base-content/70">
|
||||||
<% if current_user.patreon_connected? %>
|
OAuth providers configured: <%= OMNIAUTH_PROVIDERS.map(&:to_s).join(', ').presence || 'None' %>
|
||||||
Connected
|
|
||||||
<% else %>
|
|
||||||
Not connected
|
|
||||||
<% end %>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% if current_user.patreon_connected? %>
|
|
||||||
<%= button_to "Disconnect", disconnect_patreon_settings_path,
|
|
||||||
method: :delete,
|
|
||||||
class: "btn btn-sm btn-outline btn-error",
|
|
||||||
form: { data: { turbo_confirm: "Are you sure you want to disconnect your Patreon account?" } } %>
|
|
||||||
<% else %>
|
|
||||||
<%= link_to "Connect", user_patreon_omniauth_authorize_path,
|
|
||||||
class: "btn btn-sm btn-primary",
|
|
||||||
method: :post %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<p class="text-sm text-base-content/70 mt-2">
|
|
||||||
Connect your Patreon account to support the development of Dawarich and get access to exclusive features.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -38,16 +38,15 @@ METRICS_PASSWORD = ENV.fetch('METRICS_PASSWORD', 'prometheus')
|
||||||
# /Prometheus metrics
|
# /Prometheus metrics
|
||||||
|
|
||||||
# Configure OAuth providers based on environment
|
# Configure OAuth providers based on environment
|
||||||
# Self-hosted: only OpenID Connect, Cloud: only GitHub, Google, and Patreon
|
# Self-hosted: only OpenID Connect, Cloud: only GitHub and Google
|
||||||
OMNIAUTH_PROVIDERS =
|
OMNIAUTH_PROVIDERS =
|
||||||
if SELF_HOSTED
|
if SELF_HOSTED
|
||||||
# Self-hosted: only OpenID Connect
|
# Self-hosted: only OpenID Connect
|
||||||
ENV['OIDC_CLIENT_ID'].present? ? %i[openid_connect] : []
|
ENV['OIDC_CLIENT_ID'].present? ? %i[openid_connect] : []
|
||||||
else
|
else
|
||||||
# Cloud: only GitHub, Google, and Patreon
|
# Cloud: only GitHub and Google
|
||||||
providers = []
|
providers = []
|
||||||
providers << :github if ENV['GITHUB_OAUTH_CLIENT_ID'].present?
|
providers << :github if ENV['GITHUB_OAUTH_CLIENT_ID'].present?
|
||||||
providers << :google_oauth2 if ENV['GOOGLE_OAUTH_CLIENT_ID'].present?
|
providers << :google_oauth2 if ENV['GOOGLE_OAUTH_CLIENT_ID'].present?
|
||||||
providers << :patreon if ENV['PATREON_CLIENT_ID'].present?
|
|
||||||
providers
|
providers
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -278,12 +278,6 @@ Devise.setup do |config|
|
||||||
scope: 'userinfo.email,userinfo.profile'
|
scope: 'userinfo.email,userinfo.profile'
|
||||||
Rails.logger.info 'OAuth: Google configured'
|
Rails.logger.info 'OAuth: Google configured'
|
||||||
end
|
end
|
||||||
|
|
||||||
if ENV['PATREON_CLIENT_ID'].present? && ENV['PATREON_CLIENT_SECRET'].present?
|
|
||||||
config.omniauth :patreon, ENV['PATREON_CLIENT_ID'], ENV['PATREON_CLIENT_SECRET'],
|
|
||||||
scope: 'identity identity[email]'
|
|
||||||
Rails.logger.info 'OAuth: Patreon configured'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Self-hosted version: only OpenID Connect (when env vars present)
|
# Self-hosted version: only OpenID Connect (when env vars present)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# Load custom OmniAuth strategies
|
# Load custom OmniAuth strategies
|
||||||
require Rails.root.join('lib', 'omniauth', 'strategies', 'patreon')
|
# (none currently needed)
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,6 @@ Rails.application.routes.draw do
|
||||||
patch 'settings', to: 'settings#update'
|
patch 'settings', to: 'settings#update'
|
||||||
get 'settings/theme', to: 'settings#theme'
|
get 'settings/theme', to: 'settings#theme'
|
||||||
post 'settings/generate_api_key', to: 'settings#generate_api_key', as: :generate_api_key
|
post 'settings/generate_api_key', to: 'settings#generate_api_key', as: :generate_api_key
|
||||||
delete 'settings/disconnect_patreon', to: 'settings#disconnect_patreon', as: :disconnect_patreon_settings
|
|
||||||
|
|
||||||
resources :imports
|
resources :imports
|
||||||
resources :visits, only: %i[index update]
|
resources :visits, only: %i[index update]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
class AddPatreonTokenToUsers < ActiveRecord::Migration[8.0]
|
|
||||||
def change
|
|
||||||
add_column :users, :patreon_access_token, :text
|
|
||||||
add_column :users, :patreon_refresh_token, :text
|
|
||||||
add_column :users, :patreon_token_expires_at, :datetime
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,194 +0,0 @@
|
||||||
# Patreon Integration
|
|
||||||
|
|
||||||
Dawarich Cloud includes Patreon OAuth integration that allows users to connect their Patreon accounts. This enables checking if users are patrons of specific creators.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- **OAuth Authentication**: Users can connect their Patreon accounts via OAuth 2.0
|
|
||||||
- **Patron Status Checking**: Check if a user is an active patron of specific creators
|
|
||||||
- **Membership Data**: Access detailed information about user's Patreon memberships
|
|
||||||
- **Token Management**: Automatic token refresh to maintain API access
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
### Environment Variables
|
|
||||||
|
|
||||||
Configure the following environment variables for Dawarich Cloud:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
PATREON_CLIENT_ID=your_patreon_client_id
|
|
||||||
PATREON_CLIENT_SECRET=your_patreon_client_secret
|
|
||||||
```
|
|
||||||
|
|
||||||
### Getting Patreon OAuth Credentials
|
|
||||||
|
|
||||||
1. Go to [Patreon Developer Portal](https://www.patreon.com/portal/registration/register-clients)
|
|
||||||
2. Create a new OAuth client
|
|
||||||
3. Set the redirect URI to: `https://your-domain.com/users/auth/patreon/callback`
|
|
||||||
4. Copy the Client ID and Client Secret
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### User Connection
|
|
||||||
|
|
||||||
Users can connect their Patreon account in the account settings:
|
|
||||||
|
|
||||||
1. Navigate to Settings
|
|
||||||
2. Find the "Connected Accounts" section
|
|
||||||
3. Click "Connect" next to Patreon
|
|
||||||
4. Authorize the application on Patreon
|
|
||||||
5. Get redirected back to Dawarich
|
|
||||||
|
|
||||||
### Checking Patron Status
|
|
||||||
|
|
||||||
#### Check if User is a Patron of Specific Creator
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
# Get Dawarich creator's Patreon ID (find it in your Patreon campaign URL)
|
|
||||||
dawarich_creator_id = 'your_creator_id'
|
|
||||||
|
|
||||||
# Check if current user is a patron
|
|
||||||
if current_user.patron_of?(dawarich_creator_id)
|
|
||||||
# User is an active patron!
|
|
||||||
# Grant special features, show badge, etc.
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Get All Memberships
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
# Get all campaigns the user is supporting
|
|
||||||
memberships = current_user.patreon_memberships
|
|
||||||
|
|
||||||
memberships.each do |membership|
|
|
||||||
campaign = membership['campaign']
|
|
||||||
|
|
||||||
puts "Supporting: #{campaign['attributes']['vanity']}"
|
|
||||||
puts "URL: #{campaign['attributes']['url']}"
|
|
||||||
puts "Status: #{membership['attributes']['patron_status']}"
|
|
||||||
puts "Since: #{membership['attributes']['pledge_relationship_start']}"
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Get Specific Membership Details
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
creator_id = 'your_creator_id'
|
|
||||||
|
|
||||||
membership = Patreon::PatronChecker.new(current_user).membership_for(creator_id)
|
|
||||||
|
|
||||||
if membership
|
|
||||||
# User is a patron
|
|
||||||
status = membership.dig('attributes', 'patron_status')
|
|
||||||
started_at = membership.dig('attributes', 'pledge_relationship_start')
|
|
||||||
|
|
||||||
# Access campaign details
|
|
||||||
campaign = membership['campaign']
|
|
||||||
campaign_name = campaign.dig('attributes', 'vanity')
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### Patron Status Values
|
|
||||||
|
|
||||||
The `patron_status` field can have the following values:
|
|
||||||
|
|
||||||
- `active_patron` - Currently an active patron
|
|
||||||
- `declined_patron` - Payment declined
|
|
||||||
- `former_patron` - Was a patron but not anymore
|
|
||||||
|
|
||||||
### Example: Show Patron Badge
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
# In a view or helper
|
|
||||||
def show_patron_badge?(user)
|
|
||||||
dawarich_creator_id = ENV['DAWARICH_PATREON_CREATOR_ID']
|
|
||||||
return false unless dawarich_creator_id.present?
|
|
||||||
|
|
||||||
user.patron_of?(dawarich_creator_id)
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
```erb
|
|
||||||
<!-- In a view -->
|
|
||||||
<% if show_patron_badge?(current_user) %>
|
|
||||||
<span class="badge badge-primary">
|
|
||||||
❤️ Patreon Supporter
|
|
||||||
</span>
|
|
||||||
<% end %>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example: Grant Premium Features
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
class User < ApplicationRecord
|
|
||||||
def premium_access?
|
|
||||||
return true if admin?
|
|
||||||
return true if active_subscription? # existing subscription logic
|
|
||||||
|
|
||||||
# Check Patreon support
|
|
||||||
dawarich_creator_id = ENV['DAWARICH_PATREON_CREATOR_ID']
|
|
||||||
return false unless dawarich_creator_id
|
|
||||||
|
|
||||||
patron_of?(dawarich_creator_id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## Token Management
|
|
||||||
|
|
||||||
The integration automatically handles token refresh:
|
|
||||||
|
|
||||||
- Access tokens are stored securely in the database
|
|
||||||
- Tokens are automatically refreshed when expired
|
|
||||||
- If refresh fails, the user needs to reconnect their account
|
|
||||||
|
|
||||||
## Disconnecting Patreon
|
|
||||||
|
|
||||||
Users can disconnect their Patreon account at any time:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
current_user.disconnect_patreon!
|
|
||||||
```
|
|
||||||
|
|
||||||
This will:
|
|
||||||
- Remove the provider/uid linkage
|
|
||||||
- Clear all stored tokens
|
|
||||||
- Revoke API access
|
|
||||||
|
|
||||||
## Security Considerations
|
|
||||||
|
|
||||||
- Access tokens are stored in the database (consider encrypting at rest)
|
|
||||||
- Tokens are automatically refreshed to maintain access
|
|
||||||
- API requests are made server-side only
|
|
||||||
- Users can revoke access at any time from their Patreon settings
|
|
||||||
|
|
||||||
## API Rate Limits
|
|
||||||
|
|
||||||
Patreon API has rate limits. The service handles this by:
|
|
||||||
- Caching membership data when possible
|
|
||||||
- Using efficient API queries
|
|
||||||
- Handling API errors gracefully
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Token Expired Errors
|
|
||||||
|
|
||||||
If users see authentication errors:
|
|
||||||
1. Ask them to disconnect and reconnect their Patreon account
|
|
||||||
2. Check that the refresh token is still valid
|
|
||||||
3. Verify environment variables are set correctly
|
|
||||||
|
|
||||||
### Creator ID Not Found
|
|
||||||
|
|
||||||
To find a Patreon creator ID:
|
|
||||||
1. Go to the creator's Patreon page
|
|
||||||
2. Use the Patreon API: `GET https://www.patreon.com/api/oauth2/v2/campaigns`
|
|
||||||
3. The campaign ID is the creator ID you need
|
|
||||||
|
|
||||||
## Future Enhancements
|
|
||||||
|
|
||||||
Potential future features:
|
|
||||||
- Tier-based access (check pledge amount)
|
|
||||||
- Lifetime pledge amount tracking
|
|
||||||
- Patron anniversary badges
|
|
||||||
- Direct campaign data caching
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'omniauth-oauth2'
|
|
||||||
|
|
||||||
module OmniAuth
|
|
||||||
module Strategies
|
|
||||||
# OmniAuth strategy for Patreon OAuth2
|
|
||||||
class Patreon < OmniAuth::Strategies::OAuth2
|
|
||||||
option :name, 'patreon'
|
|
||||||
|
|
||||||
option :client_options,
|
|
||||||
site: 'https://www.patreon.com',
|
|
||||||
authorize_url: 'https://www.patreon.com/oauth2/authorize',
|
|
||||||
token_url: 'https://www.patreon.com/api/oauth2/token'
|
|
||||||
|
|
||||||
option :authorize_params,
|
|
||||||
scope: 'identity identity[email]'
|
|
||||||
|
|
||||||
uid { raw_info['data']['id'] }
|
|
||||||
|
|
||||||
info do
|
|
||||||
{
|
|
||||||
email: raw_info.dig('data', 'attributes', 'email'),
|
|
||||||
name: raw_info.dig('data', 'attributes', 'full_name'),
|
|
||||||
first_name: raw_info.dig('data', 'attributes', 'first_name'),
|
|
||||||
last_name: raw_info.dig('data', 'attributes', 'last_name'),
|
|
||||||
image: raw_info.dig('data', 'attributes', 'image_url')
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
extra do
|
|
||||||
{
|
|
||||||
raw_info: raw_info
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def raw_info
|
|
||||||
@raw_info ||= access_token.get('/api/oauth2/v2/identity?include=memberships&fields[user]=email,first_name,full_name,last_name,image_url').parsed
|
|
||||||
end
|
|
||||||
|
|
||||||
def callback_url
|
|
||||||
full_host + callback_path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
Loading…
Reference in a new issue