Initial commit
This commit is contained in:
110
app/api/v2/management/api_keys.rb
Normal file
110
app/api/v2/management/api_keys.rb
Normal file
@@ -0,0 +1,110 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API::V2
|
||||
module Management
|
||||
class APIKeys < Grape::API
|
||||
resource :api_keys do
|
||||
desc 'Create an api key for service account' do
|
||||
@settings[:scope] = :write_apikeys
|
||||
success API::V2::Entities::APIKey
|
||||
end
|
||||
params do
|
||||
requires :algorithm,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'API key algorithm'
|
||||
requires :uid,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'User UID or Service Account UID'
|
||||
optional :scopes,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'Comma separated scopes'
|
||||
end
|
||||
post do
|
||||
if params[:uid].start_with?(Barong::App.config.uid_prefix)
|
||||
error!({ error: 'disabled_management_endpoint' }, 422) unless Barong::App.config.mgn_api_keys_user
|
||||
|
||||
key_holder = User.find_by(uid: params[:uid])
|
||||
error!({ error: 'user_doesnt_exist' }, 422) unless key_holder
|
||||
elsif params[:uid].start_with?(ServiceAccount::UID_PREFIX)
|
||||
error!({ error: 'disabled_management_endpoint' }, 422) unless Barong::App.config.mgn_api_keys_sa
|
||||
|
||||
key_holder = ServiceAccount.find_by(uid: params[:uid])
|
||||
error!({ error: 'service_account_doesnt_exist' }, 422) unless key_holder
|
||||
else
|
||||
error!({ error: 'uid_prefix_doesnt_exist'}, 422)
|
||||
end
|
||||
|
||||
declared_params = declared(params, include_missing: false)
|
||||
.except(:uid, :sa_uid, :scopes)
|
||||
.merge(scope: params[:scopes]&.split(','))
|
||||
.merge(secret: SecureRandom.hex(16))
|
||||
|
||||
api_key = key_holder.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
|
||||
|
||||
code_error!(api_key.errors.details, 422) if api_key.errors.any?
|
||||
|
||||
present api_key, with: API::V2::Entities::APIKey
|
||||
end
|
||||
|
||||
desc 'Updates an api key for service account' do
|
||||
@settings[:scope] = :write_apikeys
|
||||
success API::V2::Entities::APIKey
|
||||
end
|
||||
params do
|
||||
requires :kid,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'API key kid'
|
||||
requires :uid,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'Service Account UID'
|
||||
optional :scopes,
|
||||
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'
|
||||
end
|
||||
post '/update' do
|
||||
if params[:uid].start_with?(Barong::App.config.uid_prefix)
|
||||
error!({ error: 'disabled_management_endpoint' }, 422) unless Barong::App.config.mgn_api_keys_user
|
||||
|
||||
key_holder = User.find_by(uid: params[:uid])
|
||||
error!({ error: 'user_doesnt_exist' }, 422) unless key_holder
|
||||
elsif params[:uid].start_with?(ServiceAccount::UID_PREFIX)
|
||||
error!({ error: 'disabled_management_endpoint' }, 422) unless Barong::App.config.mgn_api_keys_sa
|
||||
|
||||
key_holder = ServiceAccount.find_by(uid: params[:uid])
|
||||
error!({ error: 'service_account_doesnt_exist' }, 422) unless key_holder
|
||||
else
|
||||
error!({ error: 'uid_prefix_doesnt_exist'}, 422)
|
||||
end
|
||||
|
||||
declared_params = declared(params, include_missing: false)
|
||||
.except(:uid, :scopes)
|
||||
.merge(scope: params[:scopes]&.split(','))
|
||||
|
||||
api_key = key_holder.api_keys.find_by(kid: params[:kid])
|
||||
error!({ error: 'api_key_doesnt_exist' }, 422) unless api_key
|
||||
|
||||
code_error!(api_key.errors.details, 422) unless api_key.update(declared_params)
|
||||
|
||||
present api_key, with: API::V2::Entities::APIKey, except: [:secret]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
58
app/api/v2/management/base.rb
Normal file
58
app/api/v2/management/base.rb
Normal file
@@ -0,0 +1,58 @@
|
||||
module API::V2
|
||||
module Management
|
||||
class Base < Grape::API
|
||||
PREFIX = '/management'
|
||||
|
||||
do_not_route_options!
|
||||
|
||||
rescue_from(API::V2::Management::Exceptions::Base) { |e| error!(e.message, e.status, e.headers) }
|
||||
rescue_from(Grape::Exceptions::ValidationErrors) { |e| error!(e.message, 422) }
|
||||
rescue_from(ActiveRecord::RecordNotFound) { error!('Record is not found', 404) }
|
||||
|
||||
# Known Vault Error from TOTPService.with_human_error
|
||||
rescue_from(TOTPService::Error) do |error|
|
||||
error!(error.message, 422)
|
||||
end
|
||||
|
||||
use API::V2::Management::JWTAuthenticationMiddleware
|
||||
mount API::V2::Management::Labels
|
||||
mount API::V2::Management::Users
|
||||
mount API::V2::Management::Profiles
|
||||
mount API::V2::Management::Phones
|
||||
mount API::V2::Management::Tools
|
||||
mount API::V2::Management::Otp
|
||||
mount API::V2::Management::Documents
|
||||
mount API::V2::Management::ServiceAccounts
|
||||
mount API::V2::Management::APIKeys
|
||||
|
||||
add_swagger_documentation base_path: File.join(API::Base::PREFIX, API::V2::Base::API_VERSION, 'barong', PREFIX),
|
||||
info: {
|
||||
title: 'Barong',
|
||||
description: 'Management API for barong OAuth server'
|
||||
},
|
||||
mount_path: '/swagger',
|
||||
security_definitions: {
|
||||
'SecurityScope': {
|
||||
description: 'JWT should have signature keychains',
|
||||
type: 'basic',
|
||||
name: 'Authorization'
|
||||
}
|
||||
},
|
||||
models: [
|
||||
API::V2::Entities::Label,
|
||||
API::V2::Entities::APIKey,
|
||||
API::V2::Entities::UserWithFullInfo,
|
||||
API::V2::Entities::User,
|
||||
API::V2::Management::Entities::Profile,
|
||||
API::V2::Management::Entities::Phone,
|
||||
API::V2::Management::Entities::Document,
|
||||
API::V2::Management::Entities::UserWithProfile,
|
||||
API::V2::Management::Entities::UserWithKYC,
|
||||
API::V2::Management::Entities::APIKey,
|
||||
],
|
||||
api_version: API::V2::Base::API_VERSION,
|
||||
doc_version: Barong::Application::GIT_TAG,
|
||||
add_base_path: true
|
||||
end
|
||||
end
|
||||
end
|
||||
70
app/api/v2/management/documents.rb
Normal file
70
app/api/v2/management/documents.rb
Normal file
@@ -0,0 +1,70 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API::V2
|
||||
module Management
|
||||
# Documents server-to-server API
|
||||
class Documents < Grape::API
|
||||
desc 'Documents related routes'
|
||||
resource :documents do
|
||||
helpers do
|
||||
def parse_file_data(upload, name, ext)
|
||||
decoded_file = Base64.strict_decode64(upload)
|
||||
file = Tempfile.new([name, ext])
|
||||
file.binmode
|
||||
file.write decoded_file
|
||||
|
||||
return file
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Push documents to barong DB' do
|
||||
@settings[:scope] = :write_documents
|
||||
end
|
||||
params do
|
||||
requires :uid, type: String, allow_blank: false, desc: 'User uid'
|
||||
requires :doc_type,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'Document type'
|
||||
requires :doc_number,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'Document number'
|
||||
requires :filename,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'Document name'
|
||||
requires :file_ext,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'Document file extension'
|
||||
requires :upload,
|
||||
type: String,
|
||||
desc: 'Base64 encoded document'
|
||||
optional :doc_expire,
|
||||
type: { value: Date, message: 'management.documents.expire_not_a_date' },
|
||||
allow_blank: true,
|
||||
desc: 'Document expiration date'
|
||||
optional :update_labels,
|
||||
type: { value: Boolean, message: 'management.documents.update_labels_invalid' },
|
||||
default: true,
|
||||
desc: 'If set to false, user label will not be created/updated'
|
||||
optional :metadata,
|
||||
type: String,
|
||||
desc: 'Any additional key: value pairs in json string format'
|
||||
end
|
||||
post do
|
||||
user = User.find_by(uid: params[:uid])
|
||||
error!(errors: ['user doesnt exist']) unless user
|
||||
|
||||
file = parse_file_data(params[:upload], params[:filename], params[:file_ext])
|
||||
|
||||
doc = user.documents.new(declared(params).except(:upload, :uid, :filename, :file_ext).merge(upload: file))
|
||||
error!(doc.errors.full_messages.to_sentence, 422) unless doc.save
|
||||
|
||||
status 201
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
12
app/api/v2/management/entities/document.rb
Normal file
12
app/api/v2/management/entities/document.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API::V2::Management
|
||||
module Entities
|
||||
class Document < API::V2::Entities::Document
|
||||
expose :doc_number,
|
||||
documentation: {
|
||||
type: 'String', desc: 'Document number: AB123123 type'
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
13
app/api/v2/management/entities/phone.rb
Normal file
13
app/api/v2/management/entities/phone.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API::V2::Management
|
||||
module Entities
|
||||
class Phone < API::V2::Entities::Phone
|
||||
expose :number,
|
||||
documentation: {
|
||||
type: 'String',
|
||||
desc: 'Phone Number'
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
19
app/api/v2/management/entities/profile.rb
Normal file
19
app/api/v2/management/entities/profile.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API::V2::Management
|
||||
module Entities
|
||||
class Profile < API::V2::Entities::Profile
|
||||
expose :last_name,
|
||||
documentation: {
|
||||
type: 'String',
|
||||
desc: 'Last name'
|
||||
}
|
||||
|
||||
expose :dob,
|
||||
documentation: {
|
||||
type: 'Date',
|
||||
desc: 'Birth date'
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
11
app/api/v2/management/entities/user_with_kyc.rb
Normal file
11
app/api/v2/management/entities/user_with_kyc.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API::V2::Management
|
||||
module Entities
|
||||
class UserWithKYC < API::V2::Entities::UserWithKYC
|
||||
expose :profiles, using: Entities::Profile
|
||||
expose :phones, using: Entities::Phone
|
||||
expose :documents, using: Entities::Document
|
||||
end
|
||||
end
|
||||
end
|
||||
9
app/api/v2/management/entities/user_with_profile.rb
Normal file
9
app/api/v2/management/entities/user_with_profile.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API::V2::Management
|
||||
module Entities
|
||||
class UserWithProfile < API::V2::Entities::UserWithProfile
|
||||
expose :profiles, using: Entities::Profile
|
||||
end
|
||||
end
|
||||
end
|
||||
13
app/api/v2/management/exceptions/authentication.rb
Normal file
13
app/api/v2/management/exceptions/authentication.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API::V2
|
||||
module Management
|
||||
module Exceptions
|
||||
class Authentication < Base
|
||||
def status
|
||||
@options.fetch(:status, 401)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
22
app/api/v2/management/exceptions/base.rb
Normal file
22
app/api/v2/management/exceptions/base.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API::V2
|
||||
module Management
|
||||
module Exceptions
|
||||
class Base < StandardError
|
||||
def initialize(message:, **options)
|
||||
@options = options
|
||||
super(message)
|
||||
end
|
||||
|
||||
def headers
|
||||
@options.fetch(:headers, {})
|
||||
end
|
||||
|
||||
def status
|
||||
@options.fetch(:status)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
93
app/api/v2/management/jwt_authentication_middleware.rb
Normal file
93
app/api/v2/management/jwt_authentication_middleware.rb
Normal file
@@ -0,0 +1,93 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'stringio'
|
||||
|
||||
module API::V2
|
||||
module Management
|
||||
class JWTAuthenticationMiddleware < Grape::Middleware::Base
|
||||
extend Memoist
|
||||
mattr_accessor :security_configuration
|
||||
|
||||
def before
|
||||
return if request.path == '/api/v2/management/swagger'
|
||||
|
||||
check_request_method!
|
||||
check_query_parameters!
|
||||
check_content_type!
|
||||
payload = check_jwt!(jwt)
|
||||
|
||||
env['rack.input'] = StringIO.new(payload.fetch(:data, {}).to_json)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def request
|
||||
Grape::Request.new(env)
|
||||
end
|
||||
memoize :request
|
||||
|
||||
def jwt
|
||||
JSON.parse(request.body.read)
|
||||
rescue StandardError => e
|
||||
raise Exceptions::Authentication, \
|
||||
message: 'Couldn\'t parse JWT.',
|
||||
debug_message: e.inspect,
|
||||
status: 400
|
||||
end
|
||||
memoize :jwt
|
||||
|
||||
def check_request_method!
|
||||
return if request.post? || request.put?
|
||||
|
||||
raise Exceptions::Authentication, \
|
||||
message: 'Only POST and PUT verbs are allowed.',
|
||||
status: 405
|
||||
end
|
||||
|
||||
def check_query_parameters!
|
||||
return if request.GET.empty?
|
||||
|
||||
raise Exceptions::Authentication, \
|
||||
message: 'Query parameters are not allowed.',
|
||||
status: 400
|
||||
end
|
||||
|
||||
def check_content_type!
|
||||
return if request.content_type == 'application/json'
|
||||
|
||||
raise Exceptions::Authentication, \
|
||||
message: 'Only JSON body is accepted.',
|
||||
status: 400
|
||||
end
|
||||
|
||||
def check_jwt!(jwt)
|
||||
begin
|
||||
scope = security_configuration.fetch(:scopes).fetch(security_scope)
|
||||
keychain = security_configuration
|
||||
.fetch(:keychain)
|
||||
.slice(*scope.fetch(:permitted_signers))
|
||||
.each_with_object({}) { |(k, v), memo| memo[k] = v.fetch(:value) }
|
||||
result = JWT::Multisig.verify_jwt(jwt, keychain, security_configuration.fetch(:jwt, {}))
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "ManagementAPI check_jwt error: #{e.inspect}"
|
||||
raise Exceptions::Authentication, \
|
||||
message: 'Failed to verify JWT.',
|
||||
debug_message: e.inspect,
|
||||
status: 401
|
||||
end
|
||||
|
||||
unless (scope.fetch(:mandatory_signers) - result[:verified]).empty?
|
||||
raise Exceptions::Authentication, \
|
||||
message: 'Not enough signatures for the action.',
|
||||
status: 401
|
||||
end
|
||||
|
||||
result[:payload]
|
||||
end
|
||||
|
||||
def security_scope
|
||||
request.env['api.endpoint'].options.fetch(:route_options).fetch(:scope)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
132
app/api/v2/management/labels.rb
Normal file
132
app/api/v2/management/labels.rb
Normal file
@@ -0,0 +1,132 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API::V2
|
||||
module Management
|
||||
# Labels-related API
|
||||
class Labels < Grape::API
|
||||
helpers ::API::V2::NamedParams
|
||||
|
||||
helpers do
|
||||
def user
|
||||
@user ||= User.find_by!(uid: params[:user_uid])
|
||||
end
|
||||
|
||||
def permitted_search_params(params)
|
||||
params.slice(:key, :value, :from, :to, :range, :scope)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Label related routes'
|
||||
resource :labels do
|
||||
desc 'Get all labels assigned to users' do
|
||||
@settings[:scope] = :read_users
|
||||
success API::V2::Entities::User
|
||||
end
|
||||
params do
|
||||
requires :key, type: String, allow_blank: false, desc: 'Label key.'
|
||||
optional :value, type: String, allow_blank: false, desc: 'Label value.'
|
||||
optional :scope, type: String, allow_blank: false, desc: 'Label scope.'
|
||||
optional :extended,
|
||||
type: { value: Boolean, message: 'Non boolean extended value' },
|
||||
default: false,
|
||||
desc: 'When true endpoint returns full information about users'
|
||||
optional :range,
|
||||
type: String,
|
||||
values: { value: ->(p) { %w[created updated].include?(p) }, message: 'Invalid range' },
|
||||
default: 'created'
|
||||
|
||||
use :pagination_filters
|
||||
end
|
||||
post '/filter/users' do
|
||||
entity = params[:extended] ? API::V2::Entities::UserWithProfile : API::V2::Entities::User
|
||||
users = API::V2::Queries::UserWithLabelFilter.new(User.all).call(permitted_search_params(params))
|
||||
|
||||
present paginate(users), with: entity
|
||||
|
||||
status 200
|
||||
end
|
||||
|
||||
desc 'Get user collection filtered on label attributes' do
|
||||
@settings[:scope] = :read_labels
|
||||
success API::V2::Entities::AdminLabelView
|
||||
end
|
||||
params do
|
||||
requires :user_uid, type: String, allow_blank: false, desc: 'User uid'
|
||||
end
|
||||
post '/list' do
|
||||
present user.labels, with: API::V2::Entities::AdminLabelView
|
||||
end
|
||||
|
||||
desc "Create a label with 'private' scope and assigns to users" do
|
||||
@settings[:scope] = :write_labels
|
||||
success API::V2::Entities::Label
|
||||
end
|
||||
params do
|
||||
requires :user_uid, type: String, allow_blank: false, desc: 'User uid'
|
||||
requires :key, type: String, allow_blank: false, desc: 'Label key.'
|
||||
requires :value, type: String, allow_blank: false, desc: 'Label value.'
|
||||
optional :description, type: String, allow_blank: false, desc: 'Label desc.'
|
||||
end
|
||||
post do
|
||||
label = user.labels.create(key: params[:key],
|
||||
value: params[:value],
|
||||
description: params[:description],
|
||||
scope: 'private')
|
||||
if label.errors.any?
|
||||
error!(label.errors.as_json(full_messages: true), 422)
|
||||
end
|
||||
|
||||
present label, with: API::V2::Entities::Label
|
||||
end
|
||||
|
||||
desc "Update a label with 'private' scope" do
|
||||
@settings[:scope] = :write_labels
|
||||
success API::V2::Entities::Label
|
||||
end
|
||||
params do
|
||||
requires :user_uid, type: String, allow_blank: false, desc: 'User uid'
|
||||
requires :key, type: String, allow_blank: false, desc: 'Label key.'
|
||||
requires :value, type: String, allow_blank: false, desc: 'Label value.'
|
||||
optional :description, type: String, allow_blank: false, desc: 'Label desc.'
|
||||
optional :replace, type: Boolean, default: true, desc: 'When true label will be created if not exist'
|
||||
end
|
||||
put do
|
||||
label = user.labels.find_by(key: params[:key], scope: 'private')
|
||||
|
||||
if label.nil?
|
||||
if params[:replace]
|
||||
label = Label.create(
|
||||
user_id: user.id,
|
||||
key: params[:key],
|
||||
value: params[:value],
|
||||
description: params[:description],
|
||||
scope: params[:scope] || 'private'
|
||||
)
|
||||
else
|
||||
error!({ error: 'label doesnt exist' }, 404)
|
||||
end
|
||||
else
|
||||
label.update({ value: params[:value], description: params[:description] }.compact)
|
||||
end
|
||||
|
||||
error!(label.errors.as_json(full_messages: true), 422) if label.errors.any?
|
||||
|
||||
present label, with: API::V2::Entities::Label
|
||||
end
|
||||
|
||||
desc "Delete a label with 'private' scope" do
|
||||
@settings[:scope] = :write_labels
|
||||
end
|
||||
params do
|
||||
requires :user_uid, type: String, allow_blank: false, desc: 'User uid'
|
||||
requires :key, type: String, allow_blank: false, desc: 'Label key.'
|
||||
end
|
||||
post '/delete' do
|
||||
user.labels.find_by!(key: params[:key], scope: 'private').destroy
|
||||
|
||||
status 204
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
40
app/api/v2/management/otp.rb
Normal file
40
app/api/v2/management/otp.rb
Normal file
@@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Otp < Grape::API
|
||||
helpers do
|
||||
def sign_request(jwt)
|
||||
JWT::Multisig.add_jws(jwt, :barong, Barong::App.config.keystore.private_key, 'RS256')
|
||||
rescue StandardError => e
|
||||
error!("JWT is invalid by the reason \"#{e.message}\"", 422)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'OTP related routes'
|
||||
resource :otp do
|
||||
desc 'Sign request with barong signature' do
|
||||
@settings[:scope] = :otp_sign
|
||||
end
|
||||
params do
|
||||
requires :user_uid, type: String, allow_blank: false, desc: 'Account UID'
|
||||
requires :otp_code, type: String, allow_blank: false, desc: 'Code from Google Authenticator'
|
||||
requires :jwt, type: Hash, allow_blank: false, desc: 'RFC 7516 jwt with applogic signature'
|
||||
end
|
||||
post '/sign' do
|
||||
declared_params = declared(params)
|
||||
user = User.active.find_by!(uid: declared_params[:user_uid])
|
||||
error!('Account has not enabled 2FA', 422) unless user.otp
|
||||
|
||||
unless TOTPService.validate?(user.uid, declared_params[:otp_code])
|
||||
error!('OTP code is invalid', 422)
|
||||
end
|
||||
|
||||
sign_request(declared_params[:jwt])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
74
app/api/v2/management/phones.rb
Normal file
74
app/api/v2/management/phones.rb
Normal file
@@ -0,0 +1,74 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API::V2
|
||||
module Management
|
||||
class Phones < Grape::API
|
||||
helpers do
|
||||
def validate_phone!(phone_number)
|
||||
error!('management.phone.invalid_num', 400) unless Phone.valid?(phone_number)
|
||||
error!('management.phone.number_exist', 400) if Phone.verified.find_by_number(phone_number)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Phones related routes'
|
||||
resource :phones do
|
||||
|
||||
desc 'Get user phone numbers' do
|
||||
@settings[:scope] = :read_phones
|
||||
success API::V2::Management::Entities::Phone
|
||||
end
|
||||
params do
|
||||
requires :uid, type: String, desc: 'User uid', allow_blank: false
|
||||
end
|
||||
post '/get' do
|
||||
user = User.find_by(uid: params[:uid])
|
||||
error!('user.doesnt_exist', 422) unless user
|
||||
|
||||
present user.phones, with: API::V2::Management::Entities::Phone
|
||||
end
|
||||
|
||||
desc 'Create phone number for user' do
|
||||
@settings[:scope] = :write_phones
|
||||
success API::V2::Management::Entities::Phone
|
||||
end
|
||||
params do
|
||||
requires :uid, type: String, desc: 'User uid', allow_blank: false
|
||||
requires :number, type: String, desc: 'User phone number', allow_blank: false
|
||||
end
|
||||
post do
|
||||
user = User.find_by(uid: params[:uid])
|
||||
error!('user.doesnt_exist', 422) unless user
|
||||
|
||||
phone_number = Phone.international(params[:number])
|
||||
validate_phone!(phone_number)
|
||||
|
||||
error!('management.phone.exists', 400) if user.phones.find_by_number(phone_number)
|
||||
|
||||
phone = user.phones.create(number: params[:number], validated_at: Time.now)
|
||||
error!(phone.errors.full_messages, 422) if phone.errors.any?
|
||||
|
||||
present phone, with: API::V2::Management::Entities::Phone
|
||||
end
|
||||
|
||||
desc 'Delete phone number for user' do
|
||||
@settings[:scope] = :write_phones
|
||||
success API::V2::Management::Entities::Phone
|
||||
end
|
||||
params do
|
||||
requires :uid, type: String, desc: 'User uid', allow_blank: false
|
||||
requires :number, type: String, desc: 'User phone number', allow_blank: false
|
||||
end
|
||||
post '/delete' do
|
||||
user = User.find_by(uid: params[:uid])
|
||||
error!('user.doesnt_exist', 422) unless user
|
||||
|
||||
phone_number = Phone.international(params[:number])
|
||||
phone = user.phones.find_by_number(phone_number) if phone_number.present?
|
||||
error!('management.phone.doesnt_exists', 422) unless phone
|
||||
|
||||
present phone.destroy, with: API::V2::Management::Entities::Phone
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
47
app/api/v2/management/profiles.rb
Normal file
47
app/api/v2/management/profiles.rb
Normal file
@@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API::V2
|
||||
module Management
|
||||
# Profiles server-to-server API
|
||||
class Profiles < Grape::API
|
||||
desc 'Profiles related routes'
|
||||
resource :profiles do
|
||||
helpers do
|
||||
def profile_param_keys
|
||||
%w[first_name last_name dob address
|
||||
postcode city country state metadata].freeze
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Imports a profile for user' do
|
||||
@settings[:scope] = :write_users
|
||||
success API::V2::Management::Entities::UserWithProfile
|
||||
end
|
||||
|
||||
params do
|
||||
requires :uid, type: String, desc: 'User Uid', allow_blank: false
|
||||
optional :first_name, type: String, desc: 'First Name', allow_blank: false
|
||||
optional :last_name, type: String, desc: 'Last Name', allow_blank: false
|
||||
optional :dob, type: Date, desc: 'Birth date', allow_blank: false
|
||||
optional :address, type: String, desc: 'Address', allow_blank: false
|
||||
optional :postcode, type: String, desc: 'Postcode', allow_blank: false
|
||||
optional :city, type: String, desc: 'City', allow_blank: false
|
||||
optional :country, type: String, desc: 'Country', allow_blank: false
|
||||
optional :state, type: String, desc: 'State', allow_blank: false
|
||||
optional :metadata, type: String, desc: 'Metadata', allow_blank: false
|
||||
end
|
||||
|
||||
post do
|
||||
user = User.find_by(uid: params[:uid])
|
||||
error! 'user.doesnt_exist', 422 unless user
|
||||
|
||||
profile_params = params.slice(*profile_param_keys)
|
||||
profile = Profile.new(profile_params.merge(user_id: user.id))
|
||||
error!(profile.errors.full_messages, 422) unless profile.save
|
||||
|
||||
present user, with: API::V2::Management::Entities::UserWithProfile
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
113
app/api/v2/management/service_accounts.rb
Normal file
113
app/api/v2/management/service_accounts.rb
Normal file
@@ -0,0 +1,113 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API::V2
|
||||
module Management
|
||||
# ServiceAccounts server-to-server API
|
||||
class ServiceAccounts < Grape::API
|
||||
helpers ::API::V2::NamedParams
|
||||
|
||||
desc 'ServiceAccounts related routes'
|
||||
resource :service_accounts do
|
||||
|
||||
desc 'Get specific service_account information' do
|
||||
@settings[:scope] = :read_service_accounts
|
||||
success API::V2::Entities::ServiceAccounts
|
||||
end
|
||||
params do
|
||||
optional :uid, type: String, allow_blank: false, desc: 'service_account uid'
|
||||
optional :email, type: String, allow_blank: false, desc: 'service_account email'
|
||||
exactly_one_of :uid, :email
|
||||
end
|
||||
post '/get' do
|
||||
declared_params = declared(params, include_missing: false)
|
||||
|
||||
service_acc = ServiceAccount.find_by!(declared_params)
|
||||
error!('Service account doesnt exist', 422) unless service_acc
|
||||
|
||||
present service_acc, with: API::V2::Entities::ServiceAccounts
|
||||
status 200
|
||||
end
|
||||
|
||||
desc 'Get service_accounts as a paginated collection' do
|
||||
@settings[:scope] = :read_service_accounts
|
||||
success API::V2::Entities::ServiceAccounts
|
||||
end
|
||||
params do
|
||||
use :pagination_filters
|
||||
optional :owner_uid, type: String, allow_blank: false, desc: 'owner uid'
|
||||
optional :owner_email, type: String, allow_blank: false, desc: 'owner email'
|
||||
end
|
||||
post '/list' do
|
||||
owner = User.find_by(uid: params[:owner_uid]) || User.find_by(email: params[:owner_email]) if params[:owner_uid] || params[:owner_email]
|
||||
service_accs = owner ? owner.service_accounts : ServiceAccount.all
|
||||
|
||||
service_accs.tap { |q| present paginate(q), with: API::V2::Entities::ServiceAccounts }
|
||||
status 200
|
||||
end
|
||||
|
||||
desc 'Create service_account' do
|
||||
@settings[:scope] = :write_service_accounts
|
||||
success API::V2::Entities::ServiceAccounts
|
||||
end
|
||||
params do
|
||||
requires :service_account_role, type: String, allow_blank: false, desc: 'service_account role'
|
||||
optional :owner_uid, type: String, allow_blank: false, desc: 'owner uid'
|
||||
optional :service_account_uid, type: String, allow_blank: false, desc: 'service_account uid'
|
||||
optional :service_account_email, type: String, allow_blank: false, desc: 'service_account email'
|
||||
end
|
||||
|
||||
post '/create' do
|
||||
owner = User.find_by(uid: params[:owner_uid])
|
||||
error!('User doesnt exist', 422) unless owner
|
||||
|
||||
s_params = { email: params[:service_account_email], uid: params[:service_account_uid], role: params[:service_account_role] }.compact
|
||||
service_acc = ServiceAccount.new(s_params.merge(user: owner))
|
||||
error!(service_acc.errors.full_messages, 422) unless service_acc.save
|
||||
|
||||
present service_acc, with: API::V2::Entities::ServiceAccounts
|
||||
status 201
|
||||
end
|
||||
|
||||
desc 'Update service_account' do
|
||||
@settings[:scope] = :write_service_accounts
|
||||
success API::V2::Entities::ServiceAccounts
|
||||
end
|
||||
params do
|
||||
requires :uid, type: String, allow_blank: false, desc: 'service_account uid'
|
||||
optional :owner_uid, type: String, allow_blank: false, desc: 'service_account owner uid'
|
||||
end
|
||||
post '/update' do
|
||||
service_acc = ServiceAccount.find_by(uid: params[:uid])
|
||||
error!('Service account doesnt exist', 422) unless service_acc
|
||||
|
||||
owner = User.find_by(uid: params[:owner_uid])
|
||||
s_params = { owner_id: owner&.id }.compact
|
||||
code_error!(service_acc.errors.details, 422) unless service_acc.update(s_params)
|
||||
|
||||
present service_acc, with: API::V2::Entities::ServiceAccounts
|
||||
end
|
||||
|
||||
desc 'Delete specific service_account' do
|
||||
@settings[:scope] = :write_service_accounts
|
||||
success API::V2::Entities::ServiceAccounts
|
||||
end
|
||||
params do
|
||||
requires :uid, type: String, allow_blank: false, desc: 'service_account uid'
|
||||
end
|
||||
post '/delete' do
|
||||
declared_params = declared(params, include_missing: false)
|
||||
|
||||
service_acc = ServiceAccount.find_by!(declared_params)
|
||||
error!('Service account doesnt exist', 422) unless service_acc
|
||||
|
||||
unless service_acc.update(state: 'disabled')
|
||||
code_error!(service_acc.errors.details, 422)
|
||||
end
|
||||
|
||||
present service_acc, with: API::V2::Entities::ServiceAccounts
|
||||
status 200
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
15
app/api/v2/management/tools.rb
Normal file
15
app/api/v2/management/tools.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API::V2
|
||||
module Management
|
||||
class Tools < Grape::API
|
||||
desc 'Returns server time in seconds since Unix epoch.' do
|
||||
@settings[:scope] = :tools
|
||||
end
|
||||
post '/timestamp' do
|
||||
body timestamp: Time.now.to_i
|
||||
status 200
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
168
app/api/v2/management/users.rb
Normal file
168
app/api/v2/management/users.rb
Normal file
@@ -0,0 +1,168 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API::V2
|
||||
module Management
|
||||
class Users < Grape::API
|
||||
helpers ::API::V2::NamedParams
|
||||
helpers do
|
||||
def profile_param_keys
|
||||
%w[first_name last_name dob address
|
||||
postcode city country state].freeze
|
||||
end
|
||||
|
||||
def create_user(user_params)
|
||||
user = User.new(user_params)
|
||||
user.send :assign_uid
|
||||
user.save(validate: false)
|
||||
error!(user.errors.full_messages3, 422) unless user.persisted?
|
||||
user
|
||||
end
|
||||
|
||||
def all_profile_fields?(params)
|
||||
profile_param_keys.all? { |key| params[key].present? }
|
||||
end
|
||||
|
||||
def create_phone(user:, number:)
|
||||
return if number.blank?
|
||||
|
||||
phone = user.phones.create(number: number)
|
||||
error!(phone.errors.full_messages, 422) unless phone.persisted?
|
||||
phone.update(validated_at: Time.current)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Users related routes'
|
||||
resource :users do
|
||||
desc 'Get users and profile information' do
|
||||
@settings[:scope] = :read_users
|
||||
success API::V2::Management::Entities::UserWithKYC
|
||||
end
|
||||
params do
|
||||
optional :uid, type: String, allow_blank: false, desc: 'User uid'
|
||||
optional :email, type: String, allow_blank: false, desc: 'User email'
|
||||
optional :phone_num, type: String, allow_blank: false, desc: 'User phone number'
|
||||
exactly_one_of :uid, :email, :phone_num
|
||||
end
|
||||
post '/get' do
|
||||
declared_params = declared(params, include_missing: false)
|
||||
|
||||
if declared_params.key?(:phone_num)
|
||||
user = Phone.find_by_number!(declared_params[:phone_num]).user
|
||||
present user, with: API::V2::Management::Entities::UserWithKYC
|
||||
return status 201
|
||||
end
|
||||
|
||||
user = User.find_by!(declared_params)
|
||||
present user, with: API::V2::Management::Entities::UserWithKYC
|
||||
end
|
||||
|
||||
desc 'Returns array of users as collection',
|
||||
security: [{ "BearerToken": [] }],
|
||||
failure: [
|
||||
{ code: 401, message: 'Invalid bearer token' }
|
||||
] do
|
||||
@settings[:scope] = :read_users
|
||||
success API::V2::Entities::User
|
||||
end
|
||||
params do
|
||||
optional :extended,
|
||||
type: { value: Boolean, message: 'Non boolean extended value' },
|
||||
default: false,
|
||||
desc: 'When true endpoint returns full information about users'
|
||||
optional :range,
|
||||
type: String,
|
||||
values: { value: -> (p){ %w[created updated].include?(p) }, message: 'Non positive page' },
|
||||
default: 'created'
|
||||
use :timeperiod_filters
|
||||
use :pagination_filters
|
||||
end
|
||||
post '/list' do
|
||||
entity = params[:extended] ? API::V2::Management::Entities::UserWithProfile : API::V2::Entities::User
|
||||
users = API::V2::Queries::UserFilter.new(User.all).call(params)
|
||||
users.tap { |q| present paginate(q), with: entity }
|
||||
status 200
|
||||
end
|
||||
|
||||
desc 'Creates new user' do
|
||||
@settings[:scope] = :write_users
|
||||
success API::V2::Management::Entities::UserWithProfile
|
||||
end
|
||||
params do
|
||||
requires :email, type: String, desc: 'User Email', allow_blank: false
|
||||
requires :password, type: String, desc: 'User Password', allow_blank: false
|
||||
optional :referral_uid, type: String, desc: 'Referral uid', allow_blank: false
|
||||
end
|
||||
post do
|
||||
referral = User.find_by_uid(params[:referral_uid]).id if params[:referral_uid]
|
||||
|
||||
user = User.create({ email: params[:email], password: params[:password], referral_id: referral }.compact )
|
||||
|
||||
error!(user.errors.full_messages, 422) unless user.persisted?
|
||||
present user, with: API::V2::Management::Entities::UserWithProfile
|
||||
end
|
||||
|
||||
desc 'Updates role and data fields of existing user' do
|
||||
@settings[:scope] = :write_users
|
||||
success API::V2::Management::Entities::UserWithProfile
|
||||
end
|
||||
params do
|
||||
requires :uid, type: String, desc: 'User Uid', allow_blank: false
|
||||
optional :role, type: String, desc: 'User Role', allow_blank: false
|
||||
optional :data, type: String, desc: 'Any additional key:value pairs in json format', allow_blank: false
|
||||
at_least_one_of :role, :data
|
||||
end
|
||||
post '/update' do
|
||||
user = User.find_by_uid(params[:uid])
|
||||
error! 'user.doesnt_exist', 422 unless user
|
||||
|
||||
u_params = { data: params[:data], role: params[:role] }.compact
|
||||
error!(user.errors.full_messages, 422) unless user.update(u_params)
|
||||
|
||||
present user, with: API::V2::Management::Entities::UserWithProfile
|
||||
end
|
||||
|
||||
desc 'Imports an existing user' do
|
||||
@settings[:scope] = :write_users
|
||||
success API::V2::Management::Entities::UserWithProfile
|
||||
end
|
||||
params do
|
||||
requires :email, type: String,
|
||||
desc: 'User Email',
|
||||
allow_blank: false
|
||||
requires :password_digest, type: String,
|
||||
desc: 'User Password Hash',
|
||||
allow_blank: false
|
||||
optional :referral_uid, type: String, desc: 'Referral uid', allow_blank: false
|
||||
optional :phone, type: String, allow_blank: false, desc: 'Phone'
|
||||
optional :first_name, type: String, allow_blank: false, desc: 'First Name'
|
||||
optional :last_name, type: String, allow_blank: false, desc: 'Last Name'
|
||||
optional :dob, type: Date, desc: 'Birth date', allow_blank: false
|
||||
optional :address, type: String, allow_blank: false, desc: 'Address'
|
||||
optional :postcode, type: String, allow_blank: false, desc: 'Postcode'
|
||||
optional :city, type: String, allow_blank: false, desc: 'City'
|
||||
optional :country, type: String, allow_blank: false, desc: 'Country'
|
||||
optional :state, type: String, allow_blank: false, desc: 'State'
|
||||
end
|
||||
post '/import' do
|
||||
if User.find_by(email: params[:email]).present?
|
||||
error! 'User already exists by this email', 422
|
||||
end
|
||||
|
||||
referral = User.find_by_uid(params[:referral_uid]).id if params[:referral_uid]
|
||||
user = create_user({
|
||||
email: params[:email],
|
||||
password_digest: params[:password_digest],
|
||||
referral_id: referral
|
||||
}.compact)
|
||||
create_phone(user: user, number: params[:phone])
|
||||
|
||||
profile_params = params.slice(*profile_param_keys)
|
||||
profile = Profile.new(profile_params.merge(user_id: user.id))
|
||||
error!(profile.errors.full_messages, 422) unless profile.save
|
||||
|
||||
present user, with: API::V2::Management::Entities::UserWithProfile
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user