Initial commit
This commit is contained in:
210
app/api/v2/resource/users.rb
Normal file
210
app/api/v2/resource/users.rb
Normal 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
|
||||
Reference in New Issue
Block a user