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,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