Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/migrate dashboard #4393

Merged
merged 1 commit into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions app/controllers/api/nextv1/channels_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class Api::Nextv1::ChannelsController < Api::Nextv1::BaseController
include ChannelsHelper

before_action :authenticate_publisher!

before_action :setup_current_channel

attr_reader :current_channel

def destroy
success = DeletePublisherChannelJob.perform_now(current_channel.id)

if success
head :no_content
else
render(json: {errors: current_channel.errors}, status: 400)
end
end

private

def setup_current_channel
@current_channel = current_publisher.channels.find(params[:id])
rescue ActiveRecord::RecordNotFound
render(json: {}, status: 404)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
require "uri"
require "net/http"
require "json"
require "digest"
require "base64"

class Api::Nextv1::Connection::BitflyerConnectionsController < Api::Nextv1::Oauth2Controller
thypon marked this conversation as resolved.
Show resolved Hide resolved
def destroy
I18n.locale = :ja
bitflyer_connection = current_publisher.bitflyer_connection

# Destroy our database records
if bitflyer_connection.destroy
render(json: {}, status: 200)
else
render(json: {errors: I18n.t(
"publishers.gemini_connections.destroy.error",
errors: bitflyer_connection.errors.full_messages.join(", ")
)}, status: 417)
end
end

private

# 1.) Set required state for Oauth2 Implementation
# @debug is an optional flag that will return a json response from the callback
# Helpful for explicit debugging and introspection of access token request response values.
def set_controller_state
@klass = BitflyerConnection
@access_token_response = Oauth2::Responses::BitflyerAccessTokenResponse
end

# 2.) Bitflyer uses code exchange verification: https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
def code_challenge
Digest::SHA256.base64digest(code_verifier).chomp("=").tr("+", "-").tr("/", "_")
end

# One way encoded(Varies through time + unique to provider + random/varies through sesion)
def code_verifier
Digest::SHA256.base64digest(current_publisher.current_sign_in_at.to_s + current_publisher.id + current_publisher.session_salt.to_s)
end

# 3.) Generate auth_url using code_challange verification
def authorization_url
@_authorization_url ||= client.authorization_code_url(
state: @state,
scope: @klass.oauth2_config.scope,
code_challenge: code_challenge,
code_challenge_method: "S256"
)
end

# 4.) Make request using code_verifier
def access_token_request
client.access_token(params.require(:code), code_verifier: code_verifier)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class Api::Nextv1::Connection::GeminiConnectionsController < Api::Nextv1::Oauth2Controller
def destroy
gemini_connection = current_publisher.gemini_connection

# Destroy our database records
if gemini_connection.destroy
render(json: {}, status: 200)
else
render(json: {errors: I18n.t(
"publishers.gemini_connections.destroy.error",
errors: gemini_connection.errors.full_messages.join(", ")
)}, status: 417)
end
end

private

# 1.) Set required state for Oauth2 Implementation
# @debug is an optional flag that will return a json response from the callback
# Helpful for explicit debugging and introspection of access token request response values.
def set_controller_state
@klass = GeminiConnection
# @debug = true
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# NOTE: To test this locally you have to access the app from 127.0.0.1: Uphold does not allow localhost has a valid domain
# and when you redirect back to 127.0.0.1 all your relevant cookies are lost.
#
# When you login through the email url, you need to copy the url and replace localhost with 127.0.0.1
# and create your session with that domain.
#
# Took me a while to figure that out.

class Api::Nextv1::Connection::UpholdConnectionsController < Api::Nextv1::Oauth2Controller
include PublishersHelper

def show
publisher = current_publisher
render(json: {
uphold_status: publisher.uphold_connection&.uphold_status.to_s,
uphold_is_member: publisher.uphold_connection&.is_member? || false,
uphold_status_summary: uphold_status_summary(publisher),
uphold_status_description: uphold_status_description(publisher),
default_currency: publisher.uphold_connection&.default_currency,
uphold_username: publisher.uphold_connection&.uphold_details&.username
}, status: 200)
end

# TODO: Do we update connections???
# def update
# uphold_connection = current_publisher.uphold_connection
# return if uphold_connection.blank?

# send_emails = DateTime.now

# case params[:send_emails]
# when "forever"
# send_emails = UpholdConnection::FOREVER_DATE
# when "next_year"
# send_emails = 1.year.from_now
# end

# uphold_connection.update(send_emails: send_emails)
# end

# publishers/disconnect_uphold
def destroy
# You can't remove your connection if you've been banned/suspended.
# This is how we prevent you from reusing the connection.
if !current_publisher.authorized_to_act? #
head :unauthorized and return
end

current_publisher&.uphold_connection&.destroy
head :ok
end

private

# 1.) Set required state for Oauth2 Implementation
# @debug is an optional flag that will return a json response from the callback
# Helpful for explicit debugging and introspection of access token request response values.
def set_controller_state
@klass = UpholdConnection
@klass.strict_create = true # toggle various restrictions on creating wallets. Useful for debugging.
end
end
119 changes: 119 additions & 0 deletions app/controllers/api/nextv1/crypto_address_for_channels_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
require "eth"
require "rbnacl"
require "base58"

class Api::Nextv1::CryptoAddressForChannelsController < Api::Nextv1::BaseController
include PublishersHelper
include Eth

def index
current_channel = current_publisher.channels.find(params[:channel_id])
@crypto_addresses_for_channel = CryptoAddressForChannel.where(channel: current_channel)
render(json: @crypto_addresses_for_channel)
end

def generate_nonce
nonce = SecureRandom.uuid
Rails.cache.write(nonce, current_publisher.id)
render json: {nonce: nonce}
end

def create
signature = params[:transaction_signature]
account_address = params[:account_address]
chain = params[:chain]
message = params[:message]
current_channel = current_publisher.channels.find(params[:channel_id])
@errors = []

# check that the message is a valid nonce and delete after use
valid_message = if Rails.cache.read(message) == current_publisher.id
!!Rails.cache.delete(message)
else
@errors << "message is invalid"
false
end

# check to make sure the user owns the address
verified = valid_message &&
case chain
when "SOL"
verify_solana_address(signature, account_address, message)
when "ETH"
verify_ethereum_address(signature, account_address, message)
else
@errors << "address could not be verified"
false
end

# Create new crypto address, and remove any other addresses on the same chain for the channel
success = verified && replace_crypto_address_for_channel(account_address, chain, current_channel)

if success
render(json: {crypto_address_for_channel: success}, status: 201)
else
render(json: {errors: @errors}, status: 400)
end
end

def change_address
account_address = params[:address]
chain = params[:chain]
current_channel = current_publisher.channels.find(params[:channel_id])

success = replace_crypto_address_for_channel(account_address, chain, current_channel)

if success
render(json: {crypto_address_for_channel: success}, status: 200)
else
render(json: {errors: "address could not be updated"}, status: 400)
end
end

def destroy
chain = params[:chain]
current_channel = current_publisher.channels.find(params[:channel_id])

begin
CryptoAddressForChannel.where(chain: chain, channel: current_channel).first&.destroy!
render(json: {crypto_address_for_channel: true}, status: 200)
rescue => e
LogException.perform(e, publisher: current_publisher)
render(json: {errors: "address could not be deleted"}, status: 400)
end
end

def verify_solana_address(signature, address, message)
verify_key = RbNaCl::VerifyKey.new(Base58.base58_to_binary(address, :bitcoin))
verify_key.verify(Base58.base58_to_binary(signature, :bitcoin), message)
rescue => e
LogException.perform(e, publisher: current_publisher)
false
end

def verify_ethereum_address(signature, address, message)
signature_pubkey = Eth::Signature.personal_recover message, signature
signature_address = Eth::Util.public_key_to_address signature_pubkey
# Eth addresses are case insensitive
signature_address.address.downcase == address.downcase
rescue => e
LogException.perform(e, publisher: current_publisher)
false
end

def replace_crypto_address_for_channel(account_address, chain, channel)
ActiveRecord::Base.transaction do
crypto_address = CryptoAddress.where(publisher: current_publisher, address: account_address, chain: chain, verified: true).first_or_create!
existing_address = CryptoAddressForChannel.where(chain: chain, channel: channel)

if existing_address.length > 0
existing_address.first.destroy!
end

CryptoAddressForChannel.create!(chain: chain, crypto_address: crypto_address, channel: channel)
end
rescue => e
LogException.perform(e, publisher: current_publisher)
false
end
end
24 changes: 24 additions & 0 deletions app/controllers/api/nextv1/crypto_addresses_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class Api::Nextv1::CryptoAddressesController < Api::Nextv1::BaseController
include PublishersHelper

def index
@crypto_addresses = CryptoAddress.where(publisher_id: current_publisher.id)
render(json: @crypto_addresses)
end

def destroy
begin
@crypto_address = current_publisher.crypto_addresses.find(params[:id])
rescue ActiveRecord::RecordNotFound
return render json: {}, status: 404
end

begin
@crypto_address.destroy!
render(json: {crypto_address: true}, status: 200)
rescue => e
LogException.perform(e, publisher: current_publisher)
render(json: {errors: "address could not be deleted"}, status: 400)
end
end
end
79 changes: 79 additions & 0 deletions app/controllers/api/nextv1/home_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
class Api::Nextv1::HomeController < Api::Nextv1::BaseController
include PublishersHelper

def dashboard
publisher = current_publisher.as_json(only: [:id], methods: [:brave_payable?])
channels = current_publisher.channels.visible.as_json(only: [:details_type, :id, :verified, :verification_status, :verification_details],
methods: [:failed_verification_details, :failed_verification_call_to_action],
include: {
details: {only: [], methods: [:publication_title]}
})
wallet = PublisherWalletGetter.new(
publisher: current_publisher,
include_transactions: true
).perform
regions = Rewards::Parameters.new.fetch_allowed_regions

wallet_data = {
wallet: wallet,
uphold_connection: uphold_wallet,
gemini_connection: gemini_wallet,
bitflyer_connection: bitflyer_wallet,
allowed_regions: regions,
next_deposit_date: next_deposit_date
}

response_data = {
publisher: publisher,
channels: channels,
wallet_data: wallet_data
}

render(json: response_data.to_json, status: 200)
end

# TODO: figure out if we need the 'latest' endpoint
# Public: Requests the Publisher's latest transactions from Eyeshade
#
# Returns the latest settled payment in JSON
def latest
wallet = PublisherWalletGetter.new(
publisher: current_publisher,
include_transactions: true
).perform

render json: {lastSettlement: wallet.last_settlement_balance}
end

private

# Internal: Renders properties associated with an Uphold Wallet Connection
#
# Returns a hash
def uphold_wallet
current_publisher.uphold_connection.as_json(
only: [:default_currency, :uphold_id, :is_member, :oauth_refresh_failed, :payout_failed],
methods: [:can_create_uphold_cards?, :username, :uphold_status, :verify_url]
)
end

# Internal: Renders properties associated with the Gemini Wallet Connection
#
# Returns a hash
def gemini_wallet
current_publisher.gemini_connection.as_json(
only: [:default_currency, :display_name, :recipient_id, :oauth_refresh_failed, :recipient_id_status, :payout_failed],
methods: [:payable?, :verify_url, :valid_country?]
)
end

# Internal: Renders properties associated with the Bitflyer Wallet Connection
#
# Returns a hash
def bitflyer_wallet
current_publisher.bitflyer_connection.as_json(
only: [:default_currency, :display_name, :recipient_id, :oauth_refresh_failed, :payout_failed],
methods: [:payable?, :verify_url]
)
end
end
Loading
Loading