Add FA Cookies management functionality
- Introduced methods for managing FurAffinity cookies in the GlobalStatesController, including `fa_cookies`, `edit_fa_cookies`, and `update_fa_cookies`. - Added a new policy for managing FA cookies, restricting access to admin users. - Created views for displaying and editing FA cookies, enhancing user interaction. - Updated routes to include paths for FA cookies management. - Added comprehensive tests for the new functionality in the GlobalStatesController spec.
This commit is contained in:
@@ -2,6 +2,12 @@ class GlobalStatesController < ApplicationController
|
||||
before_action :set_global_state, only: %i[edit update destroy]
|
||||
after_action :verify_authorized
|
||||
|
||||
FA_COOKIE_KEYS = %w[
|
||||
furaffinity-cookie-a
|
||||
furaffinity-cookie-b
|
||||
furaffinity-cookie-oaid
|
||||
].freeze
|
||||
|
||||
def index
|
||||
authorize GlobalState
|
||||
@global_states = policy_scope(GlobalState).order(:key)
|
||||
@@ -44,6 +50,50 @@ class GlobalStatesController < ApplicationController
|
||||
notice: "Global state was successfully deleted."
|
||||
end
|
||||
|
||||
def fa_cookies
|
||||
authorize GlobalState
|
||||
@fa_cookies =
|
||||
FA_COOKIE_KEYS.map do |key|
|
||||
GlobalState.find_by(key: key) ||
|
||||
GlobalState.new(key: key, value_type: :string)
|
||||
end
|
||||
end
|
||||
|
||||
def edit_fa_cookies
|
||||
authorize GlobalState
|
||||
@fa_cookies =
|
||||
FA_COOKIE_KEYS.map do |key|
|
||||
GlobalState.find_by(key: key) ||
|
||||
GlobalState.new(key: key, value_type: :string)
|
||||
end
|
||||
end
|
||||
|
||||
def update_fa_cookies
|
||||
authorize GlobalState
|
||||
|
||||
begin
|
||||
ActiveRecord::Base.transaction do
|
||||
fa_cookies_params.each do |key, value|
|
||||
state = GlobalState.find_or_initialize_by(key: key)
|
||||
state.value = value
|
||||
state.value_type = :string
|
||||
state.save!
|
||||
end
|
||||
end
|
||||
|
||||
redirect_to fa_cookies_global_states_path,
|
||||
notice: "FA cookies were successfully updated."
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
@fa_cookies =
|
||||
FA_COOKIE_KEYS.map do |key|
|
||||
GlobalState.find_by(key: key) ||
|
||||
GlobalState.new(key: key, value_type: :string)
|
||||
end
|
||||
flash.now[:alert] = "Error updating FA cookies: #{e.message}"
|
||||
render :edit_fa_cookies, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_global_state
|
||||
@@ -53,4 +103,8 @@ class GlobalStatesController < ApplicationController
|
||||
def global_state_params
|
||||
params.require(:global_state).permit(:key, :value, :value_type)
|
||||
end
|
||||
|
||||
def fa_cookies_params
|
||||
params.require(:fa_cookies).permit(*FA_COOKIE_KEYS)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,6 +27,18 @@ class GlobalStatePolicy < ApplicationPolicy
|
||||
update?
|
||||
end
|
||||
|
||||
def fa_cookies?
|
||||
user.admin?
|
||||
end
|
||||
|
||||
def edit_fa_cookies?
|
||||
user.admin?
|
||||
end
|
||||
|
||||
def update_fa_cookies?
|
||||
user.admin?
|
||||
end
|
||||
|
||||
class Scope < Scope
|
||||
def resolve
|
||||
user.admin? ? scope.all : scope.none
|
||||
|
||||
62
app/views/global_states/edit_fa_cookies.html.erb
Normal file
62
app/views/global_states/edit_fa_cookies.html.erb
Normal file
@@ -0,0 +1,62 @@
|
||||
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-2xl font-semibold text-slate-900">
|
||||
Edit FurAffinity Cookies
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-slate-700">
|
||||
Update the cookie values used for FurAffinity authentication.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
|
||||
<%= link_to "Back to Cookies",
|
||||
fa_cookies_global_states_path,
|
||||
class:
|
||||
"bg-slate-500 hover:bg-slate-700 text-white font-bold py-2 px-4 rounded" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 overflow-hidden bg-white shadow sm:rounded-lg">
|
||||
<div class="p-3 sm:p-4">
|
||||
<%= form_tag fa_cookies_global_states_path, method: :patch do %>
|
||||
<table class="min-w-full divide-y divide-slate-300">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="pb-2 text-left text-sm font-semibold text-slate-900">
|
||||
Cookie
|
||||
</th>
|
||||
<th class="pb-2 text-left text-sm font-semibold text-slate-900">
|
||||
Value
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-200">
|
||||
<% @fa_cookies.each do |cookie| %>
|
||||
<tr>
|
||||
<td class="py-2 pr-4 text-sm font-medium text-slate-900">
|
||||
<%= cookie.key.sub("furaffinity-cookie-", "").upcase %>
|
||||
</td>
|
||||
<td class="py-2 pr-4">
|
||||
<%= text_field_tag "fa_cookies[#{cookie.key}]",
|
||||
cookie.value,
|
||||
class:
|
||||
"block w-full rounded-md border-slate-300 shadow-sm focus:border-sky-500 focus:ring-sky-500 sm:text-sm" %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="mt-4 flex justify-end space-x-3">
|
||||
<%= link_to "Cancel",
|
||||
fa_cookies_global_states_path,
|
||||
class:
|
||||
"rounded-md border border-slate-300 bg-white py-2 px-4 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2" %>
|
||||
<%= submit_tag "Save",
|
||||
class:
|
||||
"inline-flex justify-center rounded-md border border-transparent bg-sky-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-sky-700 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
49
app/views/global_states/fa_cookies.html.erb
Normal file
49
app/views/global_states/fa_cookies.html.erb
Normal file
@@ -0,0 +1,49 @@
|
||||
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-2xl font-semibold text-slate-900">FurAffinity Cookies</h1>
|
||||
<p class="mt-2 text-sm text-slate-700">
|
||||
Manage cookies for FurAffinity authentication.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 space-x-4 sm:ml-16 sm:mt-0 sm:flex-none">
|
||||
<%= link_to "Back to Global States",
|
||||
global_states_path,
|
||||
class:
|
||||
"bg-slate-500 hover:bg-slate-700 text-white font-bold py-2 px-4 rounded mr-3" %>
|
||||
<%= link_to "Edit Cookies",
|
||||
fa_cookies_edit_global_states_path,
|
||||
class:
|
||||
"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 overflow-hidden bg-white shadow sm:rounded-lg">
|
||||
<div class="p-3 sm:p-4">
|
||||
<table class="min-w-full divide-y divide-slate-300">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="pb-2 text-left text-sm font-semibold text-slate-900">
|
||||
Cookie
|
||||
</th>
|
||||
<th class="pb-2 text-left text-sm font-semibold text-slate-900">
|
||||
Value
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-200">
|
||||
<% @fa_cookies.each do |cookie| %>
|
||||
<tr>
|
||||
<td class="py-2 pr-4 text-sm font-medium text-slate-900">
|
||||
<%= cookie.key.sub("furaffinity-cookie-", "").upcase %>
|
||||
</td>
|
||||
<td class="py-2 pr-4 text-sm text-slate-500">
|
||||
<%= cookie.value.present? ? cookie.value : "(not set)" %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3,111 +3,88 @@
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-2xl font-semibold text-slate-900">Global States</h1>
|
||||
<p class="mt-2 text-sm text-slate-700">
|
||||
A list of all global states in the application.
|
||||
A list of all global states in the system.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
|
||||
<%= link_to new_global_state_path,
|
||||
<div class="mt-4 space-x-4 sm:ml-16 sm:mt-0 sm:flex-none">
|
||||
<%= link_to "Manage FA Cookies",
|
||||
fa_cookies_global_states_path,
|
||||
class:
|
||||
"inline-flex items-center justify-center rounded-md border border-transparent bg-sky-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-sky-700 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 sm:w-auto" do %>
|
||||
<i class="fas fa-plus mr-2"></i>
|
||||
New Global State
|
||||
<% end %>
|
||||
"bg-slate-500 hover:bg-slate-700 text-white font-bold py-2 px-4 rounded" %>
|
||||
<%= link_to "New Global State",
|
||||
new_global_state_path,
|
||||
class:
|
||||
"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 flex flex-col">
|
||||
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||
<div class="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8">
|
||||
<div
|
||||
class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg"
|
||||
>
|
||||
<table class="min-w-full divide-y divide-slate-300">
|
||||
<thead class="bg-slate-50">
|
||||
<tr>
|
||||
<th
|
||||
scope="col"
|
||||
class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-slate-900 sm:pl-6"
|
||||
<div class="mt-6 overflow-hidden bg-white shadow sm:rounded-lg">
|
||||
<div class="p-3 sm:p-4">
|
||||
<table class="min-w-full divide-y divide-slate-300">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="pb-2 text-left text-sm font-semibold text-slate-900">
|
||||
Key
|
||||
</th>
|
||||
<th class="pb-2 text-left text-sm font-semibold text-slate-900">
|
||||
Value Type
|
||||
</th>
|
||||
<th class="pb-2 text-left text-sm font-semibold text-slate-900">
|
||||
Value
|
||||
</th>
|
||||
<th class="relative pb-2"><span class="sr-only">Actions</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-200">
|
||||
<% @global_states.each do |global_state| %>
|
||||
<tr>
|
||||
<td class="py-2 pr-4 text-sm font-medium text-slate-900">
|
||||
<%= global_state.key %>
|
||||
</td>
|
||||
<td class="py-2 pr-4 text-sm">
|
||||
<% pill_color =
|
||||
case global_state.value_type
|
||||
when "string"
|
||||
"bg-sky-100 text-sky-700"
|
||||
when "counter"
|
||||
"bg-emerald-100 text-emerald-700"
|
||||
when "duration"
|
||||
"bg-purple-100 text-purple-700"
|
||||
when "password"
|
||||
"bg-rose-100 text-rose-700"
|
||||
end %>
|
||||
<span
|
||||
class="<%= pill_color %> inline-flex items-center rounded-full px-2.5 py-0.5 font-medium"
|
||||
>
|
||||
Key
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-slate-900"
|
||||
>
|
||||
Type
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-slate-900"
|
||||
>
|
||||
Value
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-slate-900"
|
||||
>
|
||||
Last Updated
|
||||
</th>
|
||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
||||
<span class="sr-only">Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-200 bg-white">
|
||||
<% @global_states.each do |global_state| %>
|
||||
<tr>
|
||||
<td
|
||||
class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-slate-900 sm:pl-6"
|
||||
>
|
||||
<%= global_state.key %>
|
||||
</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-slate-500"
|
||||
>
|
||||
<span
|
||||
class="<%= if global_state.value_type_password?
|
||||
"bg-red-100 text-red-800"
|
||||
else
|
||||
"bg-sky-100 text-sky-800"
|
||||
end %> inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium"
|
||||
>
|
||||
<%= global_state.value_type.titleize %>
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-slate-500"
|
||||
>
|
||||
<%= global_state.display_value %>
|
||||
</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-slate-500"
|
||||
>
|
||||
<%= time_ago_in_words(global_state.updated_at) %> ago
|
||||
</td>
|
||||
<td
|
||||
class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6"
|
||||
>
|
||||
<%= link_to "Edit",
|
||||
edit_global_state_path(global_state),
|
||||
class: "text-sky-600 hover:text-sky-900 mr-3" %>
|
||||
<%= button_to "Delete",
|
||||
global_state_path(global_state),
|
||||
method: :delete,
|
||||
class: "text-red-600 hover:text-red-900 inline",
|
||||
form: {
|
||||
class: "inline",
|
||||
data: {
|
||||
turbo_confirm: "Are you sure?",
|
||||
},
|
||||
} %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<%= global_state.value_type.humanize %>
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-2 pr-4 text-sm text-slate-500">
|
||||
<%= global_state.display_value %>
|
||||
</td>
|
||||
<td class="py-2 text-right text-sm font-medium">
|
||||
<% if policy(global_state).edit? %>
|
||||
<%= link_to "Edit",
|
||||
edit_global_state_path(global_state),
|
||||
class: "text-blue-600 hover:text-blue-900 mr-3" %>
|
||||
<% end %>
|
||||
<% if policy(global_state).destroy? %>
|
||||
<%= button_to "Delete",
|
||||
global_state_path(global_state),
|
||||
method: :delete,
|
||||
class: "text-red-600 hover:text-red-900 inline",
|
||||
form: {
|
||||
class: "inline",
|
||||
data: {
|
||||
turbo_confirm: "Are you sure?",
|
||||
},
|
||||
} %>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -89,5 +89,11 @@ Rails.application.routes.draw do
|
||||
end
|
||||
end
|
||||
|
||||
resources :global_states, path: "state"
|
||||
resources :global_states, path: "state" do
|
||||
collection do
|
||||
get "fa-cookies", to: "global_states#fa_cookies"
|
||||
get "fa-cookies/edit", to: "global_states#edit_fa_cookies"
|
||||
patch "fa-cookies", to: "global_states#update_fa_cookies"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -103,4 +103,151 @@ RSpec.describe GlobalStatesController, type: :controller do
|
||||
expect(response).to redirect_to(global_states_path)
|
||||
end
|
||||
end
|
||||
|
||||
describe "FA Cookies Management" do
|
||||
let(:fa_cookie_keys) do
|
||||
%w[furaffinity-cookie-a furaffinity-cookie-b furaffinity-cookie-oaid]
|
||||
end
|
||||
|
||||
describe "GET #fa_cookies" do
|
||||
context "when no FA cookie states exist" do
|
||||
before { GlobalState.delete_all }
|
||||
|
||||
it "returns a success response" do
|
||||
get :fa_cookies
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it "assigns new GlobalState objects for all required cookies" do
|
||||
get :fa_cookies
|
||||
assigned_cookies = assigns(:fa_cookies)
|
||||
|
||||
expect(assigned_cookies.size).to eq(3)
|
||||
expect(assigned_cookies).to all(be_new_record)
|
||||
expect(assigned_cookies.map(&:key)).to match_array(fa_cookie_keys)
|
||||
expect(assigned_cookies.map(&:value_type).uniq).to eq(["string"])
|
||||
end
|
||||
end
|
||||
|
||||
context "when FA cookie states exist" do
|
||||
it "assigns all FA cookie states" do
|
||||
cookie_states =
|
||||
fa_cookie_keys.map do |key|
|
||||
create(
|
||||
:global_state,
|
||||
key: key,
|
||||
value: "test",
|
||||
value_type: :string,
|
||||
)
|
||||
end
|
||||
|
||||
get :fa_cookies
|
||||
expect(assigns(:fa_cookies)).to match_array(cookie_states)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET #edit_fa_cookies" do
|
||||
context "when no FA cookie states exist" do
|
||||
before { GlobalState.delete_all }
|
||||
|
||||
it "returns a success response" do
|
||||
get :edit_fa_cookies
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it "assigns new GlobalState objects for all required cookies" do
|
||||
get :edit_fa_cookies
|
||||
assigned_cookies = assigns(:fa_cookies)
|
||||
|
||||
expect(assigned_cookies.size).to eq(3)
|
||||
expect(assigned_cookies).to all(be_new_record)
|
||||
expect(assigned_cookies.map(&:key)).to match_array(fa_cookie_keys)
|
||||
expect(assigned_cookies.map(&:value_type).uniq).to eq(["string"])
|
||||
end
|
||||
end
|
||||
|
||||
context "when FA cookie states exist" do
|
||||
it "assigns all FA cookie states" do
|
||||
cookie_states =
|
||||
fa_cookie_keys.map do |key|
|
||||
create(
|
||||
:global_state,
|
||||
key: key,
|
||||
value: "test",
|
||||
value_type: :string,
|
||||
)
|
||||
end
|
||||
|
||||
get :edit_fa_cookies
|
||||
expect(assigns(:fa_cookies)).to match_array(cookie_states)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "PATCH #update_fa_cookies" do
|
||||
context "when no FA cookie states exist" do
|
||||
before { GlobalState.delete_all }
|
||||
|
||||
let(:new_values) { fa_cookie_keys.index_with { |_key| "new_value" } }
|
||||
|
||||
it "creates all cookie states" do
|
||||
expect {
|
||||
patch :update_fa_cookies, params: { fa_cookies: new_values }
|
||||
}.to change(GlobalState, :count).by(3)
|
||||
|
||||
fa_cookie_keys.each do |key|
|
||||
state = GlobalState.find_by(key: key)
|
||||
expect(state).to be_present
|
||||
expect(state.value).to eq("new_value")
|
||||
expect(state.value_type).to eq("string")
|
||||
end
|
||||
end
|
||||
|
||||
it "redirects to the fa_cookies page" do
|
||||
patch :update_fa_cookies, params: { fa_cookies: new_values }
|
||||
expect(response).to redirect_to(fa_cookies_global_states_path)
|
||||
end
|
||||
end
|
||||
|
||||
context "when FA cookie states exist" do
|
||||
let(:new_values) { fa_cookie_keys.index_with { |_key| "new_value" } }
|
||||
|
||||
it "updates existing records" do
|
||||
existing_states =
|
||||
fa_cookie_keys.map do |key|
|
||||
create(
|
||||
:global_state,
|
||||
key: key,
|
||||
value: "old_value",
|
||||
value_type: :string,
|
||||
)
|
||||
end
|
||||
|
||||
patch :update_fa_cookies, params: { fa_cookies: new_values }
|
||||
|
||||
existing_states.each do |state|
|
||||
expect(state.reload.value).to eq("new_value")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with invalid params" do
|
||||
let(:invalid_values) { { "furaffinity-cookie-a" => "" } }
|
||||
|
||||
it "returns unprocessable entity status" do
|
||||
patch :update_fa_cookies, params: { fa_cookies: invalid_values }
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
|
||||
it "assigns @fa_cookies with all required keys" do
|
||||
patch :update_fa_cookies, params: { fa_cookies: invalid_values }
|
||||
assigned_cookies = assigns(:fa_cookies)
|
||||
|
||||
expect(assigned_cookies).to be_present
|
||||
expect(assigned_cookies.map(&:key)).to match_array(fa_cookie_keys)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user