Restrict to some functionality access for inactive users

This commit is contained in:
Eugene Burmakin 2025-02-19 21:23:11 +01:00
parent d6cec7725d
commit 6fac14675b
26 changed files with 255 additions and 6 deletions

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
class Api::V1::Overland::BatchesController < ApiController
before_action :authenticate_active_api_user!, only: %i[create]
def create
Overland::BatchCreatingJob.perform_later(batch_params, current_api_user.id)

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
class Api::V1::Owntracks::PointsController < ApiController
before_action :authenticate_active_api_user!, only: %i[create]
def create
Owntracks::PointCreatingJob.perform_later(point_params, current_api_user.id)

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
class Api::V1::PointsController < ApiController
before_action :authenticate_active_api_user!, only: %i[create update destroy]
def index
start_at = params[:start_at]&.to_datetime&.to_i
end_at = params[:end_at]&.to_datetime&.to_i || Time.zone.now.to_i

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
class Api::V1::SettingsController < ApiController
before_action :authenticate_active_api_user!, only: %i[update]
def index
render json: {
settings: current_api_user.settings,

View file

@ -12,6 +12,12 @@ class ApiController < ApplicationController
true
end
def authenticate_active_api_user!
render json: { error: 'User is not active' }, status: :unauthorized unless current_api_user&.active?
true
end
def current_api_user
@current_api_user ||= User.find_by(api_key:)
end

View file

@ -25,6 +25,12 @@ class ApplicationController < ActionController::Base
redirect_to root_path, notice: 'You are not authorized to perform this action.', status: :see_other
end
def authenticate_active_user!
return if current_user&.active?
redirect_to root_path, notice: 'Your account is not active.', status: :see_other
end
private
def set_self_hosted_status

View file

@ -2,6 +2,7 @@
class ImportsController < ApplicationController
before_action :authenticate_user!
before_action :authenticate_active_user!, only: %i[new create]
before_action :set_import, only: %i[show destroy]
def index

View file

@ -2,7 +2,7 @@
class SettingsController < ApplicationController
before_action :authenticate_user!
before_action :authenticate_active_user!, only: %i[update]
def index; end
def update

View file

@ -2,6 +2,7 @@
class StatsController < ApplicationController
before_action :authenticate_user!
before_action :authenticate_active_user!, only: %i[update update_all]
def index
@stats = current_user.stats.group_by(&:year).sort.reverse

View file

@ -2,6 +2,7 @@
class TripsController < ApplicationController
before_action :authenticate_user!
before_action :authenticate_active_user!, only: %i[new create]
before_action :set_trip, only: %i[show edit update destroy]
before_action :set_coordinates, only: %i[show edit]

View file

@ -8,6 +8,7 @@ class VisitSuggestingJob < ApplicationJob
users = user_ids.any? ? User.where(id: user_ids) : User.all
users.find_each do |user|
next unless user.active?
next if user.tracked_points.empty?
Visits::Suggest.new(user, start_at:, end_at:).call

View file

@ -24,6 +24,8 @@ class User < ApplicationRecord
attribute :admin, :boolean, default: false
enum :status, { inactive: 0, active: 1 }
def safe_settings
Users::SafeSettings.new(settings)
end
@ -104,6 +106,10 @@ class User < ApplicationRecord
save
end
def activate
update(state: :active) if DawarichSettings.self_hosted?
end
def sanitize_input
settings['immich_url']&.gsub!(%r{/+\z}, '')
settings['photoprism_url']&.gsub!(%r{/+\z}, '')

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddStatusToUsers < ActiveRecord::Migration[8.0]
def change
add_column :users, :status, :integer, default: 0
end
end

7
db/schema.rb generated
View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_01_23_151657) do
ActiveRecord::Schema[8.0].define(version: 2025_02_19_195822) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
enable_extension "postgis"
@ -201,7 +201,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_01_23_151657) do
t.bigint "user_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.geometry "path", limit: {:srid=>3857, :type=>"line_string"}
t.geometry "path", limit: {srid: 3857, type: "line_string"}
t.index ["user_id"], name: "index_trips_on_user_id"
end
@ -215,13 +215,14 @@ ActiveRecord::Schema[8.0].define(version: 2025_01_23_151657) do
t.datetime "updated_at", null: false
t.string "api_key", default: "", null: false
t.string "theme", default: "dark", null: false
t.jsonb "settings", default: {"fog_of_war_meters"=>"100", "meters_between_routes"=>"1000", "minutes_between_routes"=>"60"}
t.jsonb "settings", default: {"fog_of_war_meters" => "100", "meters_between_routes" => "1000", "minutes_between_routes" => "60"}
t.boolean "admin", default: false
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.integer "status", default: 0
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end

View file

@ -6,6 +6,8 @@ FactoryBot.define do
"user#{n}@example.com"
end
status { :active }
password { SecureRandom.hex(8) }
settings do

View file

@ -30,5 +30,17 @@ RSpec.describe VisitSuggestingJob, type: :job do
expect(Visits::Suggest).to have_received(:new)
end
end
context 'when user is inactive' do
before do
users.first.update(status: :inactive)
end
it 'does not suggest visits' do
subject
expect(Visits::Suggest).not_to have_received(:new)
end
end
end
end

View file

@ -16,6 +16,10 @@ RSpec.describe User, type: :model do
it { is_expected.to have_many(:trips).dependent(:destroy) }
end
describe 'enums' do
it { is_expected.to define_enum_for(:status).with_values(inactive: 0, active: 1) }
end
describe 'callbacks' do
describe '#create_api_key' do
let(:user) { create(:user) }
@ -24,6 +28,30 @@ RSpec.describe User, type: :model do
expect(user.api_key).to be_present
end
end
describe '#activate' do
let(:user) { create(:user) }
context 'when self-hosted' do
before do
allow(DawarichSettings).to receive(:self_hosted?).and_return(true)
end
it 'activates user' do
expect { user.send(:activate) }.to change(user, :status).to('active')
end
end
context 'when not self-hosted' do
before do
allow(DawarichSettings).to receive(:self_hosted?).and_return(false)
end
it 'does not activate user' do
expect { user.send(:activate) }.to_not change(user, :status)
end
end
end
end
describe 'methods' do

View file

@ -31,6 +31,18 @@ RSpec.describe 'Api::V1::Overland::Batches', type: :request do
post "/api/v1/overland/batches?api_key=#{user.api_key}", params: params
end.to have_enqueued_job(Overland::BatchCreatingJob)
end
context 'when user is inactive' do
before do
user.update(status: :inactive)
end
it 'returns http unauthorized' do
post "/api/v1/overland/batches?api_key=#{user.api_key}", params: params
expect(response).to have_http_status(:unauthorized)
end
end
end
end
end

View file

@ -31,6 +31,18 @@ RSpec.describe 'Api::V1::Owntracks::Points', type: :request do
end.to have_enqueued_job(Owntracks::PointCreatingJob)
end
end
context 'when user is inactive' do
before do
user.update(status: :inactive)
end
it 'returns http unauthorized' do
post api_v1_owntracks_points_path(api_key: user.api_key), params: params
expect(response).to have_http_status(:unauthorized)
end
end
end
end
end

View file

@ -119,4 +119,66 @@ RSpec.describe 'Api::V1::Points', type: :request do
end
end
end
describe 'POST /create' do
it 'returns a successful response' do
post "/api/v1/points?api_key=#{user.api_key}", params: { point: { latitude: 1.0, longitude: 1.0 } }
expect(response).to have_http_status(:success)
end
context 'when user is inactive' do
before do
user.update(status: :inactive)
end
it 'returns an unauthorized response' do
post "/api/v1/points?api_key=#{user.api_key}", params: { point: { latitude: 1.0, longitude: 1.0 } }
expect(response).to have_http_status(:unauthorized)
end
end
end
describe 'PUT /update' do
it 'returns a successful response' do
put "/api/v1/points/#{points.first.id}?api_key=#{user.api_key}",
params: { point: { latitude: 1.0, longitude: 1.1 } }
expect(response).to have_http_status(:success)
end
context 'when user is inactive' do
before do
user.update(status: :inactive)
end
it 'returns an unauthorized response' do
put "/api/v1/points/#{points.first.id}?api_key=#{user.api_key}",
params: { point: { latitude: 1.0, longitude: 1.1 } }
expect(response).to have_http_status(:unauthorized)
end
end
end
describe 'DELETE /destroy' do
it 'returns a successful response' do
delete "/api/v1/points/#{points.first.id}?api_key=#{user.api_key}"
expect(response).to have_http_status(:success)
end
context 'when user is inactive' do
before do
user.update(status: :inactive)
end
it 'returns an unauthorized response' do
delete "/api/v1/points/#{points.first.id}?api_key=#{user.api_key}"
expect(response).to have_http_status(:unauthorized)
end
end
end
end

View file

@ -25,6 +25,18 @@ RSpec.describe 'Api::V1::Settings', type: :request do
expect(response.parsed_body['settings']['route_opacity'].to_f).to eq(0.3)
end
context 'when user is inactive' do
before do
user.update(status: :inactive)
end
it 'returns http unauthorized' do
patch "/api/v1/settings?api_key=#{api_key}", params: { settings: { route_opacity: 0.3 } }
expect(response).to have_http_status(:unauthorized)
end
end
end
context 'with invalid request' do

View file

@ -82,6 +82,19 @@ RSpec.describe 'Settings', type: :request do
expect(user.reload.settings).to eq(params[:settings])
end
context 'when user is inactive' do
before do
user.update(status: :inactive)
end
it 'redirects to the root path' do
patch '/settings', params: params
expect(response).to redirect_to(root_path)
expect(flash[:notice]).to eq('Your account is not active.')
end
end
end
describe 'GET /settings/users' do

View file

@ -69,6 +69,19 @@ RSpec.describe '/stats', type: :request do
end
end
end
context 'when user is inactive' do
before do
user.update(status: :inactive)
end
it 'returns an unauthorized response' do
put update_year_month_stats_url(year: '2024', month: '1')
expect(response).to redirect_to(root_path)
expect(flash[:notice]).to eq('Your account is not active.')
end
end
end
describe 'PUT /update_all' do
@ -83,6 +96,19 @@ RSpec.describe '/stats', type: :request do
expect(Stats::CalculatingJob).to have_been_enqueued.with(user.id, 2024, 2)
expect(Stats::CalculatingJob).to_not have_been_enqueued.with(user.id, 2024, 3)
end
context 'when user is inactive' do
before do
user.update(status: :inactive)
end
it 'returns an unauthorized response' do
put update_all_stats_url
expect(response).to redirect_to(root_path)
expect(flash[:notice]).to eq('Your account is not active.')
end
end
end
end
end

View file

@ -53,6 +53,19 @@ RSpec.describe '/trips', type: :request do
expect(response).to be_successful
end
context 'when user is inactive' do
before do
user.update(status: :inactive)
end
it 'redirects to the root path' do
get new_trip_url
expect(response).to redirect_to(root_path)
expect(flash[:notice]).to eq('Your account is not active.')
end
end
end
describe 'GET /edit' do
@ -77,6 +90,19 @@ RSpec.describe '/trips', type: :request do
post trips_url, params: { trip: valid_attributes }
expect(response).to redirect_to(trip_url(Trip.last))
end
context 'when user is inactive' do
before do
user.update(status: :inactive)
end
it 'redirects to the root path' do
post trips_url, params: { trip: valid_attributes }
expect(response).to redirect_to(root_path)
expect(flash[:notice]).to eq('Your account is not active.')
end
end
end
context 'with invalid parameters' do

View file

@ -78,6 +78,9 @@ describe 'Points API', type: :request do
coordinates: [-122.40530871, 37.74430413]
},
properties: {
battery_state: 'full',
battery_level: 0.7,
wifi: 'dawarich_home',
timestamp: '2025-01-17T21:03:01Z',
horizontal_accuracy: 5,
vertical_accuracy: -1,
@ -92,7 +95,7 @@ describe 'Points API', type: :request do
}
]
}
tags 'Batches'
tags 'Points'
consumes 'application/json'
parameter name: :locations, in: :body, schema: {
type: :object,

View file

@ -828,7 +828,7 @@ paths:
post:
summary: Creates a batch of points
tags:
- Batches
- Points
parameters:
- name: api_key
in: query
@ -918,6 +918,9 @@ paths:
- -122.40530871
- 37.74430413
properties:
battery_state: full
battery_level: 0.7
wifi: dawarich_home
timestamp: '2025-01-17T21:03:01Z'
horizontal_accuracy: 5
vertical_accuracy: -1