mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 01:01:39 -05:00
Restrict to some functionality access for inactive users
This commit is contained in:
parent
d6cec7725d
commit
6fac14675b
26 changed files with 255 additions and 6 deletions
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
class SettingsController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
|
||||
before_action :authenticate_active_user!, only: %i[update]
|
||||
def index; end
|
||||
|
||||
def update
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}, '')
|
||||
|
|
|
|||
7
db/migrate/20250219195822_add_status_to_users.rb
Normal file
7
db/migrate/20250219195822_add_status_to_users.rb
Normal 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
7
db/schema.rb
generated
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ FactoryBot.define do
|
|||
"user#{n}@example.com"
|
||||
end
|
||||
|
||||
status { :active }
|
||||
|
||||
password { SecureRandom.hex(8) }
|
||||
|
||||
settings do
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue