Initial commit

This commit is contained in:
Yaser
2026-08-13 19:56:46 +03:30
commit de1d57a67b
474 changed files with 43185 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
# frozen_string_literal: true
module API::V2
module Resource
# Addresses API
class Addresses < Grape::API
desc 'Documents related routes'
resource :addresses do
desc 'Upload a new address approval document for current user(this is what ui using now)',
success: { code: 201, message: 'New address approval document was uploaded' },
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 422, message: 'Validation errors' }
]
params do
requires :country,
type: String,
allow_blank: false,
desc: 'country name'
requires :address,
type: String,
allow_blank: false,
desc: 'address'
requires :upload,
desc: 'Array of Rack::Multipart::UploadedFile'
requires :city,
allow_blank: true,
desc: 'city name'
requires :postcode, type: String, desc: 'Any additional key: value pairs in json string format'
end
post do
identificator = SecureRandom.hex(16)
params[:upload].each do |file|
doc = current_user.documents.new(upload: file, identificator: identificator, doc_type: 'Poa', doc_category: 'front_side')
code_error!(doc.errors.details, 422) unless doc.save
end
status 201
rescue Excon::Error => e
Rails.logger.error e
error!('Connection error', 422)
end
end
end
end
end

View File

@@ -0,0 +1,159 @@
# frozen_string_literal: true
module API::V2
module Resource
# Responsible for CRUD for api keys
class APIKeys < Grape::API
helpers ::API::V2::NamedParams
helpers do
def skip_api_key_2fa?
ENV['BARONG_SKIP_API_KEY_2FA'] == 'true'
end
def otp_protected!
return if skip_api_key_2fa?
unless current_user.otp
error!({ errors: ['resource.api_key.2fa_disabled'] }, 400)
end
error!({ errors: ['resource.api_key.missing_totp'] }, 422) unless params[:totp_code].present?
return if TOTPService.validate?(current_user.uid, params[:totp_code])
error!({ errors: ['resource.api_key.invalid_totp'] }, 422)
end
end
resource :api_keys do
desc 'Create an api key',
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 422, message: 'Validation errors' }
],
success: Entities::APIKey
params do
requires :algorithm,
type: String,
allow_blank: false,
desc: 'API key algorithm'
optional :scope,
type: String,
allow_blank: false,
desc: 'Comma separated scopes'
optional :totp_code,
type: String,
allow_blank: false,
desc: 'Code from Google Authenticator'
end
post do
otp_protected!
declared_params = declared(params, include_missing: false)
.except(:totp_code)
.merge(scope: params[:scope]&.split(','))
.merge(secret: SecureRandom.hex(16))
api_key = current_user.api_keys.new(declared_params)
APIKey.transaction do
raise ActiveRecord::Rollback unless api_key.save
rescue Vault::VaultError
api_key.errors.add(:api_key, 'could_not_save_secret')
raise ActiveRecord::Rollback
end
if api_key.errors.any?
code_error!(api_key.errors.details, 422)
end
present api_key, with: Entities::APIKey
end
desc 'Updates an api key',
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 404, message: 'Record is not found' },
{ code: 422, message: 'Validation errors' }
],
success: Entities::APIKey
params do
requires :kid,
type: String,
allow_blank: false,
desc: 'API key kid'
optional :scope,
type: String,
allow_blank: false,
desc: 'Comma separated scopes'
optional :state,
type: String,
allow_blank: false,
desc: 'State of API Key. "active" state means key is active and can be used for auth'
optional :totp_code,
type: String,
allow_blank: false,
desc: 'Code from Google Authenticator'
end
patch ':kid' do
otp_protected!
declared_params = declared(params, include_missing: false)
.except(:totp_code)
.merge(scope: params[:scope]&.split(','))
api_key = current_user.api_keys.find_by!(kid: params[:kid])
unless api_key.update(declared_params)
code_error!(api_key.errors.details, 422)
end
present api_key, with: Entities::APIKey, except: [:secret]
end
desc 'Delete an api key',
success: { code: 204, message: 'Succefully deleted' },
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 404, message: 'Record is not found' }
]
params do
requires :kid,
type: String,
allow_blank: false,
desc: 'API key kid'
optional :totp_code,
type: String,
allow_blank: false,
desc: 'Code from Google Authenticator'
end
delete ':kid' do
otp_protected!
api_key = current_user.api_keys.find_by!(kid: params[:kid])
api_key.destroy
status 204
end
desc 'List all api keys for current account.',
failure: [
{ code: 400, message: 'Require 2FA and totp code' },
{ code: 401, message: 'Invalid bearer token' }
],
success: Entities::APIKey
params do
optional :ordering,
values: { value: -> (p){ %w[asc desc].include?(p) }, message: 'resource.api_key.invalid_ordering' },
default: 'asc',
desc: 'If set, returned values will be sorted in specific order, defaults to \'asc\'.'
optional :order_by,
values: { value: -> (p){ APIKey.new.attributes.keys.include?(p) }, message: 'resource.api_key.invalid_attribute' },
default: 'id',
desc: 'Name of the field, which result will be ordered by.'
use :pagination_filters
end
get do
current_user.api_keys.order(params[:order_by] => params[:ordering]).tap { |q| present paginate(q), with: Entities::APIKey, except: [:secret] }
end
end
end
end
end

View File

@@ -0,0 +1,36 @@
# frozen_string_literal: true
require_dependency 'barong/middleware/jwt_authenticator'
module API::V2
module Resource
class Base < Grape::API
use Barong::Middleware::JWTAuthenticator, \
pubkey: Rails.configuration.x.keystore.public_key
helpers API::V2::Resource::Utils
do_not_route_options!
mount Resource::Addresses
mount Resource::Users
mount Resource::Labels
mount Resource::Profiles
mount Resource::Documents
mount Resource::Phones
mount Resource::Otp
mount Resource::APIKeys
mount Resource::DataStorage
mount Resource::ServiceAccounts
add_swagger_documentation security_definitions: {
'BearerToken': {
description: 'Bearer Token authentication',
type: 'basic',
name: 'Authorization',
in: 'header'
}
}
end
end
end

View File

@@ -0,0 +1,39 @@
# frozen_string_literal: true
module API
module V2
module Resource
# data storage C functionality
class DataStorage < Grape::API
resource :data_storage do
desc 'Create data storage',
failure: [
{ code: 401, message: 'Invalid bearer token' },
{ code: 422, message: 'Validation errors' }
],
success: { code: 201, message: 'Data Storage was created' }
params do
requires :title,
type: String,
allow_blank: false,
desc: 'Storage title'
requires :data,
type: String,
allow_blank: false,
desc: 'Storage data'
end
post do
declared_params = declared(params)
data_storage = current_user.data_storages.new(declared_params)
code_error!(data_storage.errors.details, 422) unless data_storage.save
current_user.labels.create(key: data_storage.title, value: 'recorded', scope: 'private')
status 201
end
end
end
end
end
end

View File

@@ -0,0 +1,78 @@
# frozen_string_literal: true
module API::V2
module Resource
# Dpcuments API
class Documents < Grape::API
desc 'Documents related routes'
resource :documents do
desc 'Return current user documents list',
failure: [
{ code: 401, message: 'Invalid bearer token' }
],
success: Entities::Document
get do
present current_user.documents, with: Entities::Document, only: %i[upload doc_type doc_number doc_expire]
end
desc 'Upload a new document for current user',
success: { code: 201, message: 'Document is uploaded' },
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 422, message: 'Validation errors' }
]
params do
requires :doc_type,
type: String,
allow_blank: false,
desc: 'Document type'
requires :doc_number,
type: String,
allow_blank: false,
desc: 'Document number'
requires :upload,
desc: 'Array of Rack::Multipart::UploadedFile'
optional :doc_expire,
type: { value: Date, message: "resource.documents.expire_not_a_date" },
allow_blank: true,
desc: 'Document expiration date'
optional :doc_category,
type: String,
values: { value: -> (p){ %w[front_side back_side selfie].include?(p) }, message: 'resource.documents.doc_category' },
desc: 'Category of the submitted document - front/back/selfie etc.'
optional :identificator, type: String, desc: 'Identificator for documents to be supplied together'
optional :metadata, type: String, desc: 'Any additional key: value pairs in json string format'
end
post do
if Barong::App.config.required_docs_expire
error!({ errors: ['resource.documents.invalid_format'] }, 422) unless /\A\d{4}\-\d{2}\-\d{2}\z/.match?(params[:doc_expire].to_s)
error!({ errors: ['resource.documents.already_expired'] }, 422) if params[:doc_expire] < DateTime.now.to_date
end
unless current_user.documents.count <= Barong::App.config.doc_num_limit
error!({ errors: ['resource.documents.limit_reached'] }, 400)
end
unless current_user.documents.count + params[:upload].length <= Barong::App.config.doc_num_limit
error!({ errors: ['resource.documents.limit_will_be_reached'] }, 400)
end
params[:identificator] = SecureRandom.hex(16) unless params[:identificator]
params[:upload].each do |file|
doc = current_user.documents.new(params.except(:upload).merge(upload: file))
code_error!(doc.errors.details, 422) unless doc.save
end
status 201
rescue Excon::Error => e
Rails.logger.error e
error!('Connection error', 422)
end
end
end
end
end

View File

@@ -0,0 +1,127 @@
# frozen_string_literal: true
module API
module V2
module Resource
# Responsible for CRUD for labes
class Labels < Grape::API
resource :labels do
desc 'List all labels for current user.',
failure: [
{ code: 401, message: 'Invalid bearer token' }
],
success: Entities::Label
params do
optional :ordering,
type: String,
values: { value: %w(asc desc) },
default: 'asc',
desc: 'If set, returned labels sorted in specific order, default to "asc".'
end
get do
labels = current_user.labels.order(created_at: params[:ordering])
present labels, with: Entities::Label
end
desc 'Return a label by key.',
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 404, message: 'Record is not found' }
],
success: Entities::Label
params do
requires :key,
type: String,
allow_blank: false,
desc: 'Label key.'
end
route_param :key do
get do
label = current_user.labels.find_by!(key: params[:key])
present label, with: Entities::Label
end
end
desc "Create a label with 'public' scope.",
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 422, message: 'Validation errors' }
],
success: Entities::Label
params do
requires :key,
type: String,
allow_blank: false,
desc: 'Label key.'
requires :value,
type: String,
allow_blank: false,
desc: 'Label value.'
end
post do
label =
current_user.labels.new(
key: params[:key],
value: params[:value],
scope: 'public'
)
if label.save
present label, with: Entities::Label
else
code_error!(label.errors.details, 422)
end
end
desc "Update a label with 'public' scope.",
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 404, message: 'Record is not found' },
{ code: 422, message: 'Validation errors' }
],
success: Entities::Label
params do
requires :key,
type: String,
allow_blank: false,
desc: 'Label key.'
requires :value,
type: String,
allow_blank: false,
desc: 'Label value.'
end
patch ':key' do
label = current_user.labels.find_by!(key: params[:key])
return error!({ errors: ['resource.labels.private'] }, 400) if label.private?
label.update(value: params[:value])
present label, with: Entities::Label
end
desc "Delete a label with 'public' scope.",
success: { code: 204, message: 'Succefully deleted' },
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 404, message: 'Record is not found' }
]
params do
requires :key,
type: String,
allow_blank: false,
desc: 'Label key.'
end
delete ':key' do
label = current_user.labels.find_by!(key: params[:key])
return error!({ errors: ['resource.labels.private'] }, 400) if label.private?
label.destroy
status 204
end
end
end
end
end
end

220
app/api/v2/resource/otp.rb Normal file
View File

@@ -0,0 +1,220 @@
# frozen_string_literal: true
module API::V2
module Resource
# TOTP functionality API
class Otp < Grape::API
helpers do
def otp_error!(options = {})
options[:topic] = 'otp'
record_error!(options)
end
end
resource :otp do
desc 'Generate qr code for 2FA (first step for enabling)',
failure: [
{ code: 400, message: '2FA has been enabled for this account' },
{ code: 401, message: 'Invalid bearer token' }
],
success: { code: 200, message: 'QR code was generated' }
post '/generate_qrcode' do
if current_user.otp
otp_error!(reason: '2FA has been already enabled for this account', error_code: 400,
user: current_user.id, action: 'request QR code for 2FA', error_text: 'already_enabled')
end
if current_user.read_cache('enable_otp')
otp_error!(reason: '2FA has been already sent email for this account', error_code: 400,
user: current_user.id, action: 'request QR code for 2FA', error_text: 'already_sent')
end
activity_record(user: current_user.id, action: 'request QR code for 2FA', result: 'succeed', topic: 'otp')
TOTPService.create(current_user.uid, current_user.email)
end
desc 'enter google authenticator code and send authorization code by email(second step for enabling)',
failure: [
{ code: 400, message: '2FA has been enabled for this account or code is missing' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 422, message: 'Validation errors' }
],
success: { code: 200, message: '2FA was enabled' }
params do
requires :code,
type: String,
allow_blank: false,
desc: 'Code from Google Authenticator'
end
post '/enable' do
if current_user.otp
otp_error!(reason: '2FA has been already enabled for this account', error_code: 400,
user: current_user.id, action: 'enable 2FA', error_text: 'already_enabled')
end
unless TOTPService.validate?(current_user.uid, declared(params)[:code])
otp_error!(reason: 'OTP code is invalid', error_code: 422,
user: current_user.id, action: 'enable 2FA', error_text: 'invalid')
end
if current_user.read_cache('enable_otp')
otp_error!(reason: '2FA has been already sent email for this account', error_code: 400,
user: current_user.id, action: 'enable 2FA', error_text: 'already_enabled')
end
# unless current_user.update(otp: true)
# # FIXME active record validation
# otp_error!(reason: current_user.errors.full_messages.to_sentence, error_code: 422,
# user: current_user.id, action: 'enable 2FA')
# end
# current_user.labels.create(key: :otp, value: :enabled, scope: :private) unless current_user.labels.find_by(key: :otp, scope: :private)
# activity_record(user: current_user.id, action: 'enable 2FA', result: 'succeed', topic: 'otp')
publish_confirmation_code(current_user, Barong::App.config.domain, 'enable-otp')
current_user.write_cache('enable_otp', 'true', 120)
200
end
desc 'enter authorization key and enable 2fa(third step for enabling)',
failure: [
{ code: 400, message: '2FA has been enabled for this account or code is missing' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 422, message: 'Validation errors' }
],
success: { code: 200, message: '2FA was enabled' }
params do
requires :code,
type: String,
allow_blank: false,
desc: 'Code from email'
end
post '/enable_2fa' do
if current_user.otp
otp_error!(reason: '2FA has been already enabled for this account', error_code: 400,
user: current_user.id, action: 'enable 2FA', error_text: 'already_enabled')
end
unless current_user.read_cache("enable_otp")
otp_error!(reason: '2FA hasnt been enable', error_code: 400,
user: current_user.id, action: 'enable 2FA', error_text: 'doesnt get email')
end
unless TOTPServiceAction.new('enable-otp').validate?(current_user.uid, declared(params)[:code])
otp_error!(reason: 'OTP code is invalid', error_code: 422,
user: current_user.id, action: 'enable 2FA', error_text: 'invalid')
end
unless current_user.update(otp: true)
# FIXME active record validation
otp_error!(reason: current_user.errors.full_messages.to_sentence, error_code: 422,
user: current_user.id, action: 'enable 2FA')
end
current_user.labels.create(key: :otp, value: :enabled, scope: :private) unless current_user.labels.find_by(key: :otp, scope: :private)
activity_record(user: current_user.id, action: 'enable 2FA', result: 'succeed', topic: 'otp')
# publish_confirmation_code(current_user,Barong::App.config.domain, action)
current_user.delete_cache("enable_otp")
200
end
desc 'Disable 2FA request and send authorization code(first step disabling)',
failure: [
{ code: 400, message: '2FA has not been enabled for this account or code is missing' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 422, message: 'Validation errors' }
],
success: { code: 200, message: '2FA was disabled' }
params do
requires :code,
type: String,
allow_blank: false,
desc: 'Code from Google Authenticator'
end
post '/disable' do
unless current_user.otp
otp_error!(reason: '2FA has not been enabled for this account', error_code: 400,
user: current_user.id, action: 'disable 2FA', error_text: 'not_enabled')
end
unless TOTPService.validate?(current_user.uid, declared(params)[:code])
otp_error!(reason: 'OTP code is invalid', error_code: 422,
user: current_user.id, action: 'disable 2FA', error_text: 'invalid')
end
publish_confirmation_code(current_user, Barong::App.config.domain, 'disable-otp')
current_user.write_cache('disable_otp', 'true', 120)
status 200
end
desc 'enter authorization code that send by email (second step for disabling 2FA)',
failure: [
{ code: 400, message: '2FA has been enabled for this account or code is missing' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 422, message: 'Validation errors' }
],
success: { code: 200, message: '2FA was disabled' }
params do
requires :code,
type: String,
allow_blank: false,
desc: 'Code from Google Authenticator'
end
post '/disable_email' do
unless current_user.otp
otp_error!(reason: '2FA has not been enabled for this account', error_code: 400,
user: current_user.id, action: 'disable 2FA', error_text: 'not_enabled')
end
unless current_user.read_cache("disable_otp")
otp_error!(reason: '2FA hasnt been disable', error_code: 400,
user: current_user.id, action: 'enable 2FA', error_text: 'doesnt get email')
end
unless TOTPServiceAction.new('disable-otp').validate?(current_user.uid, declared(params)[:code])
otp_error!(reason: 'OTP code is invalid', error_code: 422,
user: current_user.id, action: 'enable 2FA', error_text: 'invalid')
end
unless current_user.update(otp: false)
otp_error!(reason: current_user.errors.full_messages.to_sentence, error_code: 422,
user: current_user.id, action: 'disable 2FA')
end
current_user.labels.find_by(key: :otp, scope: :private).delete if current_user.labels.find_by(key: :otp, scope: :private)
activity_record(user: current_user.id, action: 'disable 2FA', result: 'succeed', topic: 'otp')
status 200
end
desc 'Verify 2FA code(for checking 2fa)',
failure: [
{ code: 400, message: '2FA has not been enabled for this account or code is missing' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 422, message: 'Validation errors' }
],
success: { code: 200, message: '2FA was verified' }
params do
requires :code,
type: String,
allow_blank: false,
desc: 'Code from Google Authenticator'
end
post '/verify' do
unless current_user.otp
otp_error!(reason: '2FA has not been enabled for this account', error_code: 400,
user: current_user.id, action: 'verify 2FA code', error_text: 'already_enabled')
end
unless TOTPService.validate?(current_user.uid, declared(params)[:code])
otp_error!(reason: 'OTP code is invalid', error_code: 422,
user: current_user.id, action: 'verify 2FA code', error_text: 'invalid')
end
end
end
end
end
end

View File

@@ -0,0 +1,267 @@
# frozen_string_literal: true
module API::V2
module Resource
class Phones < Grape::API
rescue_from(Twilio::REST::RestError) do |error|
Rails.logger.error "Twilio Client Error: #{error.message}"
error!({ errors: [twilio_dictionary_error(error.code)] }, 422)
end
helpers do
def validate_phone!(phone_number, landline = nil)
phone_number = Phone.international(phone_number)
error!({ errors: ['resource.phone.invalid_num'] }, 400) \
unless Phone.valid?(phone_number)
error!({ errors: ['resource.phone.number_exist'] }, 400) \
if Phone.verified.find_by_number(phone_number) && landline.blank?
# check phone is landline
error!({ errors: ['resource.phone.number_not_landline'] }, 400) \
if landline.present? && phone_number[2] == '9'
end
end
desc 'mobile related routes'
resource :mobiles do
desc 'Returns list of user\'s phones',
failure: [
{ code: 401, message: 'Invalid bearer token' },
],
success: Entities::Phone
get do
present current_user.phones, with: Entities::Phone
end
desc 'Add new mobile',
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 404, message: 'Record is not found' },
{ code: 422, message: 'Validation errors' }
],
success: { code: 200, message: 'New phone was added' }
params do
requires :phone_number,
type: String,
allow_blank: false,
desc: 'Phone number with country code(in iran +98)'
optional :channel,
type: String,
default: 'sms',
values: { value: -> { Phone::TWILIO_CHANNELS }, message: 'resource.phone.invalid_channel'},
desc: 'The verification method to use(this param is for legacy structure must be sms)'
end
post do
error!({ errors: ['kyc_follow_steps'] }, 403) if current_user.kyc_info.dig(:step) < 2
error!({ errors: ['resource.phone.code_doesnt_expired'] }, 422) if current_user.read_cache('register-mobile')
declared_params = declared(params)
validate_phone!(declared_params[:phone_number])
phone_number = Phone.international(declared_params[:phone_number])
# label = current_user.labels.find_by(key: 'access_phone', value: 'verified')
# error!({ errors: ['resource.phone.exists'] }, 422) if label.present?
if current_user.mobile('pending').present?
current_user.mobile('pending').update(number: phone_number)
else
current_user.phones.create(number: phone_number, category: 'mobile')
end
# code_error!(phone.errors.details, 422) if phone.errors.any?
error!({ errors: ['resource.phone.cant_send_message'] }, 422) unless KaveNegarSmsService.send_confirmation(current_user)
status 200
end
desc 'Resend activation code for mobile sms',
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 404, message: 'Record is not found' },
{ code: 422, message: 'Validation errors' }
],
success: { code: 200, message: 'Activation code was resend' }
params do
requires :phone_number,
type: String,
allow_blank: false,
desc: 'Phone number with country code( in iran +98 )'
optional :channel,
type: String,
default: 'sms',
values: { value: -> { Phone::TWILIO_CHANNELS }, message: 'resource.phone.invalid_channel'},
desc: 'The verification method to use(this param is for legacy structure must be sms)'
end
# TODO you can delete this api and use above
post '/send_code' do
error!({ errors: ['resource.phone.code_doesnt_expired'] }, 422) if current_user.read_cache('register-mobile')
declared_params = declared(params)
validate_phone!(declared_params[:phone_number])
phone_number = Phone.international(declared_params[:phone_number])
phone = current_user.phones.find_by_number(phone_number)
error!({ errors: ['resource.phone.doesnt_exist'] }, 404) unless phone
KaveNegarSmsService.send_confirmation(current_user)
status 200
end
desc 'Verify a mobile(enter code and verify number)',
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 404, message: 'Record is not found' }
],
success: API::V2::Entities::UserWithFullInfo
params do
requires :phone_number,
type: String,
allow_blank: false,
desc: 'phone number with country code(in iran with +98)'
requires :verification_code,
type: String,
allow_blank: false,
desc: 'Verification code from sms(this param is for legacy structure must be sms)'
end
post '/verify' do
declared_params = declared(params)
validate_phone!(declared_params[:phone_number])
error!({ errors: ['resource.phone.doesnt_exist'] }, 404) unless current_user.mobile('pending')
unless TOTPServiceAction.new('register-mobile').validate?(current_user.uid, declared(params)[:verification_code])
error!({ errors: ['resource.phone.verification_invalid'] }, 400)
end
current_user.delete_cache('register-mobile')
pending_mobile = current_user.mobile('pending')
last_verified_mobile = current_user.mobile('verified')
if last_verified_mobile&.step == 'owner_phone'
if pending_mobile.check_owner_ship
last_verified_mobile.update(state: 'replaced')
pending_mobile.update(validated_at: Time.current, state: 'verified', step: 'owner_phone')
else
pending_mobile.update(state: 'rejected')
end
elsif last_verified_mobile&.step == 'access_phone'
last_verified_mobile.update(state: 'replaced')
pending_mobile.update(validated_at: Time.current, step: 'access_phone', state: 'verified')
else
pending_mobile.update(validated_at: Time.current, step: 'access_phone', state: 'verified')
end
present current_user, with: API::V2::Entities::UserWithFullInfo
end
end
desc 'landline related routes'
resource :phones do
desc 'Add new phone',
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 404, message: 'Record is not found' },
{ code: 422, message: 'Validation errors' }
],
success: { code: 200, message: 'New phone was added' }
params do
requires :phone_number,
type: String,
allow_blank: false,
desc: 'Phone number with country code(in iran +98)'
optional :channel,
type: String,
default: 'call',
values: { value: -> { Phone::TWILIO_CHANNELS }, message: 'resource.phone.invalid_channel'},
desc: 'The verification method to use'
end
post do
error!({ errors: ['resource.phone.code_doesnt_expired'] }, 422) if current_user.read_cache('register-phone')
error!({ errors: ['kyc_follow_steps'] }, 403) if current_user.kyc_info.dig(:step) < 6
declared_params = declared(params)
validate_phone!(declared_params[:phone_number], true)
phone_number = Phone.international(declared_params[:phone_number])
phone = current_user.phones.find_by(category: :landline, step: 'telephone', state: 'pending')
label = current_user.labels.find_by(key: 'telephone', value: 'verified')
# TODO can edit for updating feature
error!({ errors: ['resource.phone.exists'] }, 422) if label.present?
if phone.present?
phone.update(number: phone_number)
else
current_user.phones.create(number: phone_number, category: :landline, step: 'telephone')
end
# code_error!(phone.errors.details, 422) if phone.errors.any?
KaveNegarSmsService.send_call_confirmation(current_user)
end
desc 'Resend activation code in call',
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 404, message: 'Record is not found' },
{ code: 422, message: 'Validation errors' }
],
success: { code: 200, message: 'Activation code was resend' }
params do
requires :phone_number,
type: String,
allow_blank: false,
desc: 'Phone number with country code'
optional :channel,
type: String,
default: 'call',
values: { value: -> { Phone::TWILIO_CHANNELS }, message: 'resource.phone.invalid_channel'},
desc: 'The verification method to use'
end
# TODO you can delete this api and use above
post '/send_code' do
error!({ errors: ['resource.phone.code_doesnt_expired'] }, 422) if current_user.read_cache('register-phone')
declared_params = declared(params)
validate_phone!(declared_params[:phone_number], true)
phone_number = Phone.international(declared_params[:phone_number])
phone = current_user.phones.find_by_number(phone_number)
error!({ errors: ['resource.phone.doesnt_exist'] }, 404) unless phone
KaveNegarSmsService.send_call_confirmation(current_user)
end
desc 'Verify a phone (enter code and verify number)',
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 404, message: 'Record is not found' }
],
success: API::V2::Entities::UserWithFullInfo
params do
requires :phone_number,
type: String,
allow_blank: false,
desc: 'Phone number with country code(in iran with +98)'
requires :verification_code,
type: String,
allow_blank: false,
desc: 'Verification code from call'
end
post '/verify' do
declared_params = declared(params)
validate_phone!(declared_params[:phone_number], true)
# phone_number = Phone.international(declared_params[:phone_number])
phone = current_user.phones.find_by(step: 'telephone', category: 'landline')
error!({ errors: ['resource.phone.doesnt_exist'] }, 404) unless phone
error!({ errors: ['resource.phone.verification_invalid'] }, 404) unless TOTPServiceAction.new('register-phone').validate?(current_user.uid, declared(params)[:verification_code])
current_user.delete_cache('register-phone')
phone.update(validated_at: Time.current, state: 'verified')
present current_user, with: API::V2::Entities::UserWithFullInfo
end
end
end
end
end

View File

@@ -0,0 +1,255 @@
# frozen_string_literal: true
module API::V2
module Resource
# CR functionality over profiles table
class Profiles < Grape::API
helpers do
def profile_param_keys
%w[first_name last_name dob address
postcode city country metadata national_code].freeze
end
def treasury_param_keys
%w[title data kind].freeze
end
end
desc 'Profile related routes'
resource :profiles do
desc 'Return profiles of current resource owner',
failure: [
{ code: 401, message: 'Invalid bearer token' },
{ code: 404, message: 'User has no profile' }
],
success: API::V2::Entities::Profile
get '/me' do
present current_user.profiles, with: API::V2::Entities::Profile
end
# personal info - kyc level one step one
desc 'Create Level one - step one - personal info in profiles',
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 409, message: 'Profile already exists' },
{ code: 422, message: 'Validation errors' }
],
success: API::V2::Entities::Profile
params do
requires :first_name, type: String, desc: 'First Name'
requires :last_name, type: String, desc: 'Last Name'
requires :dob, type: Date, desc: 'Date of Birth'
requires :national_code, type: String, desc: 'national code'
requires :upload, desc: 'Array of Rack::Multipart::UploadedFile'
optional :confirm, type: Boolean, default: true, desc: 'Profile confirmation'
end
post do
error!({ errors: ['kyc_follow_steps'] }, 403) if current_user.kyc_info.dig(:step) < 1
declared_params = declared(params.slice(*profile_param_keys), include_missing: false)
declared_params.merge!(state: 'submitted')
# declared_params.merge!(state: 'submitted') if params['confirm']
ActiveRecord::Base.transaction do
profile = current_user.profiles.create(declared_params)
code_error!(profile.errors.details, 422) if profile.errors.any?
doc = current_user.documents.new(upload: params[:upload],
identificator: SecureRandom.hex(16),
doc_type: 'Identity card',
doc_category: 'front_side')
code_error!(doc.errors.details, 422) unless doc.save
present profile, with: API::V2::Entities::Profile
end
end
desc 'Update a profile for current_user',
failure: [
{ code: 401, message: 'Invalid bearer token' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 422, message: 'Validation errors' }
],
success: API::V2::Entities::Profile
params do
requires :first_name, type: String, desc: 'First Name'
requires :last_name, type: String, desc: 'Last Name'
requires :dob, type: Date, desc: 'Date of Birth'
requires :national_code, type: String, desc: 'national code'
requires :upload, desc: 'Array of Rack::Multipart::UploadedFile'
optional :confirm, type: Boolean, default: true, desc: 'Profile confirmation'
end
put do
target_profile = current_user.submitted_profile
error!({ errors: ['resource.profile.doesnt_exist_or_not_editable'] }, 404) if target_profile.nil?
declared_params = declared(params.slice(*profile_param_keys), include_missing: false)
# declared_params.merge!(state: 'submitted') if params['confirm']
declared_params.merge!(state: 'submitted')
ActiveRecord::Base.transaction do
code_error!(target_profile.errors.details, 422) unless target_profile.update(declared_params)
document = current_user.front_icard
document.update!(upload: params[:upload]) if document.present?
end
present target_profile, with: API::V2::Entities::Profile
end
# card bank info - kyc level one step three
resource :treasury do
# one request add iban just
desc 'Create Level one - step three - info bank ',
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 409, message: 'Bank info already exists' },
{ code: 422, message: 'Validation errors' }
],
success: API::V2::Entities::Profile
params do
requires :data, type: String, desc: 'treasury number'
requires :kind, type: String, desc: 'iban or card'
optional :title, type: String, desc: 'treasury name'
optional :confirm, type: Boolean, default: true, desc: 'Treasury confirmation'
end
post do
error!({ errors: ['resource.verified.profile.doesnt_exist'] }, 404) unless current_user.verified_profile.present?
declared_params = declared(params.slice(*treasury_param_keys), include_missing: false)
# declared_params.merge!(state: 'submitted') if params['confirm'].present?
declared_params.merge!(state: 'submitted')
error!({ errors: ['treasury.kind.not_exist'] }, 404) if Treasury.kinds.keys.exclude?(declared_params['kind'])
error!({ errors: ['kyc_follow_steps'] }, 403) if current_user.kyc_info.dig(:step) < 3 && declared_params['kind'].to_s == 'card'
error!({ errors: ['kyc_follow_steps'] }, 403) if current_user.kyc_info.dig(:step) < 4 && declared_params['kind'].to_s == 'iban'
treasury = current_user.treasuries.create(declared_params)
code_error!(treasury.errors.details, 422) if treasury.errors.any?
KYC.const_get(Barong::App.config.kyc_provider.capitalize, false)::TreasuryWorker.perform_async(current_user.verified_profile.id)
present treasury, with: API::V2::Entities::Treasury
end
desc 'Delete a treasury',
success: { code: 204, message: 'Successfully deleted' },
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 404, message: 'Record is not found' }
]
delete ':id' do
treasury = Treasury.find_by!(id: params[:id], user_id: current_user.id)
treasury.destroy
code_error!(treasury.errors.details, 422) if treasury.errors.any?
status 204
end
desc 'Return list of treasuries',
failure: [
{ code: 401, message: 'Invalid bearer token' },
{ code: 404, message: 'User has no treasuries' }
],
success: API::V2::Entities::Treasury
get '/list' do
present current_user.treasuries, with: API::V2::Entities::Treasury
end
end
resource :selfie do
# submit selfie - kyc level two step three
desc 'Create Level two - step three - selfie',
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 409, message: 'Selfie already exists' },
{ code: 422, message: 'Validation errors' }
],
success: API::V2::Entities::Profile
params do
requires :upload, desc: 'Array of Rack::Multipart::UploadedFile'
optional :confirm, type: Boolean, default: true, desc: 'Profile confirmation'
end
post do
error!({ errors: ['kyc_follow_steps'] }, 403) if current_user.kyc_info.dig(:step) < 7
target_profile = current_user.verified_profile
error!({ errors: ['resource.verified.profile.doesnt_exist'] }, 404) if target_profile.nil?
label = current_user.labels.find_by(key: 'selfie', value: 'verified')
error!({ errors: ['resource.verified.selfie.already_exist'] }, 404) if label.present?
doc = current_user.documents.find_by(doc_type: 'Selfie', state: 'pending')
if doc.present?
doc.update(upload: params[:upload])
else
doc = current_user.documents.new(upload: params[:upload],
identificator: SecureRandom.hex(16),
doc_type: 'Selfie',
doc_category: 'front_side')
code_error!(doc.errors.details, 422) unless doc.save
end
status 201
end
end
resource :address do
desc 'Adding Address',
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 404, message: 'needed data doesnt exist' },
{ code: 422, message: 'Validation errors' }
],
success: API::V2::Entities::Profile
params do
requires :city, type: String, desc: 'city name'
requires :province, type: String, desc: 'province name'
requires :address, type: String, desc: 'address'
requires :postcode, type: String, desc: 'post code'
requires :upload, desc: 'Array of Rack::Multipart::UploadedFile'
end
post do
error!({ errors: ['resource.address.already_exist'] }, 422) if current_user.documents.find_by(doc_type: 'Poa', state: 'verified').present?
error!({ errors: ['kyc_follow_steps'] }, 403) if current_user.kyc_info.dig(:step) < 5
declared_params = declared(params)
profile = current_user.verified_profile
# TODO change city and province to id instead of string
province = Province.find_by(name: declared_params['province'])
error!({ errors: ['resource.profile.province_doesnt_exist'] }, 404) if province.nil?
city = province.cities.find_by(name: declared_params['city'])
error!({ errors: ['resource.profile.province_doesnt_exist'] }, 404) if city.nil?
doc = current_user.documents.find_by(doc_type: 'Poa', state: 'pending')
# declared_params.merge!(state: 'submitted') if params['confirm']
ActiveRecord::Base.transaction do
profile.update(city: city, address: declared_params['address'], postcode: declared_params['postcode'])
code_error!(profile.errors.details, 422) if profile.errors.any?
if doc.present?
doc.update(upload: params[:upload])
else
doc = current_user.documents.new(upload: params[:upload],
identificator: SecureRandom.hex(16),
doc_type: 'Poa',
doc_category: 'front_side')
end
code_error!(doc.errors.details, 422) unless doc.save
present profile, with: API::V2::Entities::Profile
end
end
end
end
end
end
end

View File

@@ -0,0 +1,186 @@
# frozen_string_literal: true
module API::V2
module Resource
class ServiceAccounts < Grape::API
helpers ::API::V2::NamedParams
helpers do
def otp_protected!
unless current_user.otp
error!({ errors: ['resource.service_accounts.2fa_disabled'] }, 400)
end
error!({ errors: ['resource.service_accounts.missing_totp'] }, 422) unless params[:totp_code].present?
return if TOTPService.validate?(current_user.uid, params[:totp_code])
error!({ errors: ['resource.service_accounts.invalid_totp'] }, 422)
end
end
resource :service_accounts do
desc 'List all service accounts for current user.',
security: [{ "BearerToken": [] }],
failure: [
{ code: 400, message: 'Require 2FA and totp code' },
{ code: 401, message: 'Invalid bearer token' }
]
params do
end
get do
current_user.service_accounts
end
resource :api_keys do
desc 'List all api keys for specific service account.',
failure: [
{ code: 400, message: 'Require 2FA and totp code' },
{ code: 401, message: 'Invalid bearer token' }
],
success: Entities::APIKey
params do
optional :ordering,
values: { value: -> (p){ %w[asc desc].include?(p) }, message: 'resource.service_accounts.invalid_ordering' },
default: 'asc',
desc: 'If set, returned values will be sorted in specific order, defaults to \'asc\'.'
optional :order_by,
values: { value: -> (p){ APIKey.new.attributes.keys.include?(p) }, message: 'resource.service_accounts.invalid_attribute' },
default: 'id',
desc: 'Name of the field, which result will be ordered by.'
use :pagination_filters
requires :service_account_uid, type: { value: String, message: 'resource.service_account.non_string_service_account_uid' }
end
get do
target_service_account = current_user.service_accounts.find_by(uid: params[:service_account_uid])
error!({ errors: ['resource.service_account.doesnt_exist'] }, 404) if target_service_account.nil?
target_service_account.api_keys.order(params[:order_by] => params[:ordering]).tap { |q| present paginate(q), with: Entities::APIKey, except: [:secret] }
end
desc 'Create api key for specific service account.',
failure: [
{ code: 400, message: 'Require 2FA and totp code' },
{ code: 401, message: 'Invalid bearer token' }
],
success: Entities::APIKey
params do
requires :service_account_uid, type: { value: String, message: 'resource.service_account.non_string_service_account_uid' }
requires :algorithm,
type: String,
allow_blank: false,
desc: 'Service account algorithm'
optional :scope,
type: String,
allow_blank: false,
desc: 'Comma separated scopes'
requires :totp_code,
type: String,
message: 'resource.service_accounts.missing_totp',
allow_blank: false,
desc: 'Code from Google Authenticator'
end
post do
target_service_account = current_user.service_accounts.find_by(uid: params[:service_account_uid])
error!({ errors: ['resource.service_account.doesnt_exist'] }, 404) if target_service_account.nil?
otp_protected!
declared_params = declared(params, include_missing: false)
.except(:totp_code, :service_account_uid)
.merge(scope: params[:scope]&.split(','))
.merge(secret: SecureRandom.hex(16))
api_key = target_service_account.api_keys.new(declared_params)
APIKey.transaction do
raise ActiveRecord::Rollback unless api_key.save
rescue Vault::VaultError
api_key.errors.add(:api_key, 'could_not_save_secret')
raise ActiveRecord::Rollback
end
if api_key.errors.any?
code_error!(api_key.errors.details, 422)
end
present api_key, with: Entities::APIKey
end
desc 'Delete an api key for specific service account',
success: { code: 204, message: 'Succefully deleted' },
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 404, message: 'Record is not found' }
]
params do
requires :service_account_uid, type: { value: String, message: 'resource.service_account.non_string_service_account_uid' }
requires :kid,
type: String,
allow_blank: false,
desc: 'Service account kid'
requires :totp_code,
type: String,
allow_blank: false,
desc: 'Code from Google Authenticator'
end
delete ':kid' do
target_service_account = current_user.service_accounts.find_by(uid: params[:service_account_uid])
error!({ errors: ['resource.service_account.doesnt_exist'] }, 404) if target_service_account.nil?
otp_protected!
api_key = target_service_account.api_keys.find_by!(kid: params[:kid])
api_key.destroy
status 204
end
desc 'Updates an api key',
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 401, message: 'Invalid bearer token' },
{ code: 404, message: 'Record is not found' },
{ code: 422, message: 'Validation errors' }
],
success: Entities::APIKey
params do
requires :service_account_uid, type: { value: String, message: 'resource.service_account.non_string_service_account_uid' }
requires :kid,
type: String,
allow_blank: false,
desc: 'Service account kid'
optional :scope,
type: String,
allow_blank: false,
desc: 'Comma separated scopes'
optional :state,
type: String,
allow_blank: false,
desc: 'State of API Key. "active" state means key is active and can be used for auth'
requires :totp_code,
type: String,
allow_blank: false,
desc: 'Code from Google Authenticator'
end
put ':kid' do
target_service_account = current_user.service_accounts.find_by(uid: params[:service_account_uid])
error!({ errors: ['resource.service_account.doesnt_exist'] }, 404) if target_service_account.nil?
otp_protected!
declared_params = declared(params, include_missing: false)
.except(:totp_code).except(:service_account_uid)
.merge(scope: params[:scope]&.split(','))
api_key = target_service_account.api_keys.find_by!(kid: params[:kid])
unless api_key.update(declared_params)
code_error!(api_key.errors.details, 422)
end
present api_key, with: Entities::APIKey, except: [:secret]
end
end
end
end
end
end

View File

@@ -0,0 +1,210 @@
# frozen_string_literal: true
module API::V2
module Resource
class Users < Grape::API
helpers ::API::V2::NamedParams
helpers do
def password_error!(options = {})
options[:topic] = 'password'
record_error!(options)
end
def validate_topic!(topic)
unless %w[all session otp password account].include?(topic)
error!({ errors: ['resource.user.wrong_topic'] }, 422)
end
end
def verify_otp!
error!({ errors: ['resource.user.missing_otp_code'] }, 422) if params[:otp_code].nil?
error!({ errors: ['resource.user.empty_otp_code'] }, 422) if params[:otp_code].empty?
unless TOTPService.validate?(current_user.uid, params[:otp_code])
error!({ errors: ['resource.user.invalid_otp'] }, 422)
end
end
end
resource :users do
desc 'Returns current user',
success: API::V2::Entities::UserWithFullInfo
get '/me' do
present current_user, with: API::V2::Entities::UserWithFullInfo
end
desc 'Updates current user data field',
success: API::V2::Entities::UserWithFullInfo
params do
requires :data, type: String, allow_blank: false, desc: 'Any additional key: value pairs in json string format'
end
put '/me' do
code_error!(current_user.errors.details, 422) unless current_user.update(data: params[:data])
present current_user, with: API::V2::Entities::UserWithFullInfo
end
desc 'Blocks current user',
success: { code: 200, message: 'Current user was blocked' }
params do
requires :password, type: String, allow_blank: false, desc: 'Account password'
optional :otp_code, type: String, allow_blank: false, desc: 'Code from Google Authenticator'
end
delete '/me' do
error!({ errors: ['resource.user.invalid_password'] }, 422) unless password_valid?(params[:password])
verify_otp! if current_user.otp
current_user.labels.create(key: 'delete', value: 'by_user', scope: 'private')
EventAPI.notify(
'system.user.account.deleted',
record: { user: current_user.as_json_for_event_api }
)
status(200)
end
desc 'Returns user activity',
success: Entities::Activity
params do
requires :topic,
type: String,
allow_blank: { value: false, message: 'resource.user.empty_topic' },
desc: 'Topic of user activity. Allowed: [all, password, session, otp]'
optional :time_from,
type: { value: Integer, message: 'resource.user.non_integer_time_from' },
allow_blank: { value: false, message: 'resource.user.empty_time_from' },
desc: 'An integer represents the seconds elapsed since Unix epoch.'\
'If set, only activities created after the time will be returned.'
optional :time_to,
type: { value: Integer, message: 'resource.user.non_integer_time_to' },
allow_blank: { value: false, message: 'resource.user.empty_time_to' },
desc: 'An integer represents the seconds elapsed since Unix epoch.'\
'If set, only activities created before the time will be returned.'
optional :result,
type: { value: String, message: 'resource.user.non_string_result' },
allow_blank: { value: false, message: 'resource.user.empty_result' },
desc: 'Result of user activity. Allowed: [succeed, failed, denied]'
use :pagination_filters
end
get '/activity/:topic' do
validate_topic!(params[:topic])
data = current_user.activities.order('id DESC')
data = data.where(topic: params[:topic]) if params[:topic] != 'all'
data = data.tap { |q| q.where!('created_at >= ?', Time.at(params[:time_from])) if params[:time_from] }
.tap { |q| q.where!('created_at < ?', Time.at(params[:time_to])) if params[:time_to] }
.tap { |q| q.where!(result: params[:result]) if params[:result] }
error!({ errors: ['resource.user.no_activity'] }, 422) unless data.present?
present paginate(data), with: Entities::Activity
end
# change password
desc 'Sets new account password(change password)',
success: { code: 201, message: 'Code sent for confirm password' },
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 404, message: 'Record is not found' },
{ code: 422, message: 'Validation errors' }
]
params do
requires :old_password,
type: String,
allow_blank: false,
desc: 'Previous account password'
requires :new_password,
type: String,
allow_blank: false,
desc: 'User password'
requires :confirm_password,
type: String,
allow_blank: false,
desc: 'User password'
end
put '/password' do
# if user inters too many wrong password, we logout him
attempt = current_user.read_cache('attempt_change_password') || 0
# TODO: numeric must be config that set by admin
if attempt >= User::CHANGE_PASS_ATTEMPTS
activity_record(user: current_user.id, action: 'many_change_password_attempts', result: 'succeed', topic: 'session')
current_user.delete_cache('attempt_change_password')
request.session.destroy
password_error!(reason: 'Too many attempts', error_code: 422,
user: current_user.id, action: 'password change', error_text: 'many_change_password_attempts')
end
unless params[:new_password] == params[:confirm_password]
password_error!(reason: 'New passwords don\'t match',
error_code: 422, user: current_user.id, action: 'password change', error_text: 'doesnt_match')
end
unless PasswordStrengthChecker.validate!(params[:new_password]) == 'strong'
password_error!(reason: 'Previous password is not strong',
error_code: 400, user: current_user.id, action: 'password change', error_text: 'password_is_not_strong')
end
unless password_valid?(params[:old_password])
current_user.write_cache('attempt_change_password', attempt + 1, 86_400)
password_error!(reason: 'Previous password is not correct',
error_code: 400, user: current_user.id, action: 'password change', error_text: 'prev_pass_not_correct')
end
if params[:old_password] == params[:new_password]
password_error!(reason: 'New password cant be the same, as old one',
error_code: 400, user: current_user.id, action: 'password change', error_text: 'no_change_provided')
end
publish_confirmation_code(current_user, Barong::App.config.domain, 'change-password')
current_user.write_cache('change_password', params[:new_password], 300)
status 201
end
desc 'Confirm account password(enter authorization code)',
success: { code: 201, message: 'Changes password' },
failure: [
{ code: 400, message: 'Required params are empty' },
{ code: 404, message: 'Record is not found' },
{ code: 422, message: 'Validation errors' }
]
params do
requires :code,
type: String,
allow_blank: false,
desc: 'Totp Code from email'
end
post '/confirm_password' do
temp_password = current_user.read_cache('change_password')
error!({ errors: ['resource.password.email_expired'] }, 422) if temp_password.blank?
unless TOTPServiceAction.new('change-password').validate?(current_user.uid, declared(params)[:code])
error!({ errors: ['resource.password.confirm_code'] }, 422)
end
unless current_user.update(password: temp_password)
error_note = { reason: current_user.errors.full_messages.to_sentence }.to_json
activity_record(user: current_user.id, action: 'password change',
result: 'failed', topic: 'password', data: error_note)
code_error!(current_user.errors.details, 422)
end
activity_record(user: current_user.id, action: 'password change', result: 'succeed', topic: 'password')
EventAPI.notify('system.user.password.change',
record: {
user: current_user.as_json_for_event_api,
domain: Barong::App.config.domain
})
current_user.delete_cache('change_password')
status 201
end
end
end
end
end

View File

@@ -0,0 +1,64 @@
# frozen_string_literal: true
module API::V2
module Resource
module Utils
def current_user
if env[:current_payload].has_key?(:uid)
@_current_user ||= User.find_by!(uid: env[:current_payload][:uid])
else
raise(Peatio::Auth::Error, 'Middleware Error')
end
end
def record_error!(options = {})
options[:data] = { reason: options[:reason] }.to_json
options[:result] = 'failed'
activity_record(options.except(:reason, :error_code))
error!({ errors: ['resource.' + options[:topic] + '.' + options[:error_text]] }, options[:error_code])
end
def twilio_dictionary_error(code)
user_error = 'resource.phone.' + {
21_612 => 'num_not_reachable',
21_614 => 'num_not_valid',
21_618 => 'sms_content_invalid',
21_610 => 'unsubscribed_recipient'
}[code]
user_error || 'resource.phone.twilio_unexpected'
end
def activity_record(options = {})
params = {
category: 'user',
user_id: options[:user],
user_ip: remote_ip,
user_agent: request.env['HTTP_USER_AGENT'],
topic: options[:topic],
action: options[:action],
result: options[:result],
data: options[:data]
}
Activity.create(params)
end
def password_valid?(password)
true if current_user == current_user.try(:authenticate, password)
end
def publish_confirmation_code(user, domain, action)
totp = TOTPServiceAction.new(action)
totp.create(user.uid, user.email)
EventAPI.notify(
action,
record: {
user: user.as_json_for_event_api,
domain: domain,
code: totp.read_code(user.uid)
}
)
end
end
end
end