Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ISS-1111] Add reserved words check to profile registration #1239

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions app/assets/stylesheets/components/admin.scss
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,9 @@
padding: 0.375em 0.5em;
}
}
form.new_mass_invite {
padding: 0 1em;
form {
&.new_mass_invite, &.new_reserved_word {
padding: 0 1em;
}
}
}
38 changes: 38 additions & 0 deletions app/controllers/admin/reserved_words_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
module Admin
class ReservedWordsController < Admin::BaseController
before_action :find_reserved_word, only: %i[delete]

def index
@pagy, @reserved_words = pagy(ReservedWord.all)
end

def new
@reserved_word = ReservedWord.new
end

def create
@reserved_word = ReservedWord.new(reserved_word_params)

if @reserved_word.save
redirect_to admin_reserved_words_url
else
render :new
end
end

def delete
@reserved_word.destroy
redirect_to admin_reserved_words_url
end

private

def find_reserved_word
@reserved_word = ReservedWord.find(params[:id])
end

def reserved_word_params
params.require(:reserved_word).permit(:name)
end
end
end
18 changes: 6 additions & 12 deletions app/models/account_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,40 +29,34 @@ class AccountRequest < ApplicationRecord

validates :entity_type, inclusion: {
in: entity_types,
message: "We need to know what sort of thing you want to upload!"
message: :unselected_entity_type
}

validates :email,
on: :create,
format: {
with: URI::MailTo::EMAIL_REGEXP,
message: "should look like an email address."
message: :invalid_regex
}
validate :email_is_unique, on: :create

validates :login,
on: :create,
login_is_allowed: true,
format: {
with: /\A\w+\z/,
message: "should use only letters and numbers."
message: :must_be_alphanumeric
}
validate :login_is_unique, on: :create

validates :details, length: {
on: :create,
minimum: 40,
too_short: "must be at least 40 characters: Please link to your music/website or add more info!"
too_short: :details_too_short
}

def email_is_unique
if User.where(email: email).exists?
errors.add(:email, "You already have an account on alonetone!")
end
end

def login_is_unique
if User.where(login: login).exists?
errors.add(:login, "Sorry, login is taken. Be creative!")
errors.add(:email, :duplicate_account)
end
end

Expand Down
22 changes: 22 additions & 0 deletions app/models/reserved_word.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class ReservedWord < ApplicationRecord
validates :name, presence: true

def contains(search)
Regexp.new(name, Regexp::IGNORECASE).match(search)
end
end

# == Schema Information
#
# Table name: reserved_words
#
# id :bigint(8) not null, primary key
# name :string(255) not null
# details :text
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_reserved_words_on_name (name) UNIQUE
#
5 changes: 2 additions & 3 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,10 @@ class User < ApplicationRecord
message: "should use only letters and numbers."
},
length: { within: 3..100 },
uniqueness: {
case_sensitive: false,
login_is_allowed: {
if: :will_save_change_to_login?
}

validates :password,
confirmation: { if: :require_password? },
length: {
Expand Down
38 changes: 38 additions & 0 deletions app/validators/login_is_allowed_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
class LoginIsAllowedValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if !login_is_unique(value)
record.errors.add(attribute, :login_not_unique)
end

if !login_is_allowed(value)
record.errors.add(attribute, :login_not_allowed)
end
end

def login_is_allowed(login)
return !login_contains_reserved_words(login) && !login_contains_route_part(login)
end

def login_is_unique(login)
if User.where(login: login).exists?
return false
end

if AccountRequest.where(login: login).exists?
return false
end

true
end

def login_contains_reserved_words(login)
ReservedWord.find_each.any? { |rw| rw.contains(login) }
end

def login_contains_route_part(login)
Rails.application.routes.routes.any? {|route|
route = route.path.spec.to_s
route.split(/[^\w]/).reject(&:empty?).any? { |part| login == part }
}
end
end
1 change: 1 addition & 0 deletions app/views/admin/_navigation.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<%= navigation_item 'Not Spam', admin_comments_path({filter_by: {is_spam: false}}) %>
</ul>

<li><%= navigation_item "Reserved Words <span>#{ReservedWord.count}</span>", admin_reserved_words_path %></li>
<li><%= link_to "Secretz", "/secretz" %></li>
<li><%= link_to "Akismet Stats", "https://akismet.com/web/1.0/user-stats.php?blog=alonetone.com&api_key=#{Rails.configuration.alonetone.rakismet_key}"%></li>
<li><%= link_to "Postmark Stats", "https://account.postmarkapp.com/servers/2804035/streams" %></li>
Expand Down
14 changes: 14 additions & 0 deletions app/views/admin/reserved_words/_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<%= form_for([:admin, @reserved_word], builder: AdminFormBuilder) do |form| %>
<%= form.field :name do %>
<%= form.label_with_error :name %>
<%= form.text_field :name %>
<% end %>
<%= form.field :details do %>
<%= form.label_with_error :details %>
<%= form.text_field :details %>
<% end %>
<ul class="actions submit">
<li><%= form.submit 'Create reserved word' %></li>
<li><%= link_to 'Cancel', admin_reserved_words_path %></li>
</ul>
<% end %>
5 changes: 5 additions & 0 deletions app/views/admin/reserved_words/_reserved_word.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<tr>
<td><%= reserved_word.name %></div>
<td><%= l(reserved_word.created_at, format: :short) %></td>
<td><%= button_to "Delete Reserved Word", delete_admin_reserved_word_path(reserved_word), method: :put %></td>
</tr>
35 changes: 35 additions & 0 deletions app/views/admin/reserved_words/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<div id="admin_columns">
<div class="admin_column_left box">
<h2>Admin</h2>
<%= render partial: 'admin/navigation' %>
</div>
<div class="admin_column_right">
<%= button_to 'New reserved word', new_admin_reserved_word_path, method: :get %>

<div class="mini_paginator top_paginator">
<%== pagy_nav @pagy %>
</div>

<div class="box">
<h2>
Reserved words
</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Created</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<%= render @reserved_words %>
</tbody>
</table>
</div>

<div class="mini_paginator bottom_paginator">
<%== pagy_nav @pagy %>
</div>
</div>
</div>
13 changes: 13 additions & 0 deletions app/views/admin/reserved_words/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<div id="admin_columns">
<div class="admin_column_left box">
<h2>Admin</h2>
<%= render partial: 'admin/navigation' %>
</div>
<div class="admin_column_right">
<div class="admin_column_right_inner box">
<h2>Create a new reserved word</h2>

<%= render 'form' %>
</div>
</div>
</div>
20 changes: 20 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@ en:
attributes:
account_request:
entity_type: "What you do:"
errors:
models:
account_request:
attributes:
login:
login_not_allowed: Sorry, login is taken. be creative!
login_not_unique: Sorry, login is taken. be creative!
must_be_alphanumeric: should use only letters and numbers.
details:
details_too_short: "must be at least 40 characters: Please link to your music/website or add more info!"
email:
duplicate_account: You already have an account on alonetone!
invalid_regex: should look like an email address.
entity_type:
unselected_entity_type: We need to know what sort of thing you want to upload!
user:
attributes:
login:
login_not_allowed: not allowed.
login_not_unique: is already taken.

errors:
messages:
Expand Down
5 changes: 5 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
put :restore
end
end
resources :reserved_words do
member do
put :delete
end
end
resources :mass_invites, param: :token
end

Expand Down
12 changes: 12 additions & 0 deletions db/migrate/20240121160656_create_reserved_words.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class CreateReservedWords < ActiveRecord::Migration[7.0]
def change
create_table :reserved_words do |t|
t.string :name, null: false
t.text :details

t.timestamps
end

add_index :reserved_words, :name, unique: true
end
end
10 changes: 9 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.0].define(version: 2023_03_11_164131) do
ActiveRecord::Schema[7.0].define(version: 2024_01_21_160656) do
create_table "account_requests", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t|
t.string "email"
t.string "login"
Expand Down Expand Up @@ -263,6 +263,14 @@
t.index ["user_id"], name: "index_profiles_on_user_id"
end

create_table "reserved_words", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "name", null: false
t.text "details"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["name"], name: "index_reserved_words_on_name", unique: true
end

create_table "settings", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "user_id"
t.boolean "display_listen_count", default: true
Expand Down
9 changes: 9 additions & 0 deletions spec/fixtures/reserved_words.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
petaq:
name: petaQ
created_at: 2010-04-08 13:00:03
updated_at: 2020-04-08 13:00:03

trader:
name: "trader$"
created_at: 2010-04-08 13:00:03
updated_at: 2020-04-08 13:00:03
18 changes: 18 additions & 0 deletions spec/models/reserved_word_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require "rails_helper"

RSpec.describe ReservedWord, type: :model do
describe("#contains") do
it "performs exact matches" do
expect(reserved_words(:petaq).contains("petaQ")).to be_truthy
end

it "performs case-insensitive matches" do
expect(reserved_words(:petaq).contains("petaq")).to be_truthy
end

it "performs regular-expression matches" do
expect(reserved_words(:trader).contains("Andorian Trader")).to be_truthy
expect(reserved_words(:trader).contains("Andorian Traders")).to be_falsey
end
end
end
Loading