Skip to content

Commit 1218f7e

Browse files
committed
add user subsystem
1 parent 351ba55 commit 1218f7e

56 files changed

Lines changed: 1534 additions & 6 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ gem "tailwindcss-rails"
2020
gem "jbuilder"
2121

2222
# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
23-
# gem "bcrypt", "~> 3.1.7"
23+
gem "bcrypt", "~> 3.1.7"
2424

2525
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
2626
gem "tzinfo-data", platforms: %i[ windows jruby ]

Gemfile.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ GEM
9393
metrics (~> 0.12)
9494
traces (~> 0.18)
9595
base64 (0.3.0)
96+
bcrypt (3.1.21)
9697
bcrypt_pbkdf (1.1.2)
9798
bigdecimal (4.0.1)
9899
bindex (0.8.1)
@@ -503,6 +504,7 @@ PLATFORMS
503504
x86_64-linux-musl
504505

505506
DEPENDENCIES
507+
bcrypt (~> 3.1.7)
506508
bootsnap
507509
brakeman
508510
bundler-audit
@@ -546,6 +548,7 @@ CHECKSUMS
546548
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
547549
async (2.36.0) sha256=090623f4c65706664355c9efa6c7bfb86771a513e65cd681c51cb27747530550
548550
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
551+
bcrypt (3.1.21) sha256=5964613d750a42c7ee5dc61f7b9336fb6caca429ba4ac9f2011609946e4a2dcf
549552
bcrypt_pbkdf (1.1.2) sha256=c2414c23ce66869b3eb9f643d6a3374d8322dfb5078125c82792304c10b94cf6
550553
bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
551554
bindex (0.8.1) sha256=7b1ecc9dc539ed8bccfc8cb4d2732046227b09d6f37582ff12e50a5047ceb17e
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module ApplicationCable
2+
class Connection < ActionCable::Connection::Base
3+
identified_by :current_user
4+
5+
def connect
6+
set_current_user || reject_unauthorized_connection
7+
end
8+
9+
private
10+
def set_current_user
11+
if session = Session.find_by(id: cookies.signed[:session_id])
12+
self.current_user = session.user
13+
end
14+
end
15+
end
16+
end
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module Admin
2+
class BaseController < ApplicationController
3+
before_action :require_content_admin_or_above
4+
5+
layout "admin"
6+
end
7+
end
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module Admin
2+
class DashboardController < BaseController
3+
def show
4+
@user_count = User.count
5+
@admin_count = User.where(role: [ :content_admin, :site_admin ]).count
6+
end
7+
end
8+
end
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
module Admin
2+
class InvitationsController < BaseController
3+
before_action :require_site_admin, only: [ :new, :create, :destroy ]
4+
5+
def index
6+
@pending_invitations = Invitation.pending.includes(:invited_by).order(created_at: :desc)
7+
@accepted_invitations = Invitation.accepted.includes(:invited_by, :accepted_by).order(accepted_at: :desc)
8+
end
9+
10+
def new
11+
@invitation = Invitation.new(role: :content_admin)
12+
end
13+
14+
def create
15+
@invitation = Invitation.new(invitation_params)
16+
@invitation.invited_by = Current.session.user
17+
18+
if @invitation.save
19+
redirect_to admin_invitations_path, notice: "Invitation created."
20+
else
21+
render :new, status: :unprocessable_entity
22+
end
23+
end
24+
25+
def destroy
26+
@invitation = Invitation.find(params[:id])
27+
@invitation.destroy
28+
redirect_to admin_invitations_path, notice: "Invitation revoked."
29+
end
30+
31+
private
32+
33+
def invitation_params
34+
params.expect(invitation: [ :role ])
35+
end
36+
end
37+
end
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module Admin
2+
class UsersController < BaseController
3+
before_action :require_site_admin
4+
5+
def index
6+
@users = User.order(created_at: :desc)
7+
end
8+
end
9+
end

app/controllers/application_controller.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
class ApplicationController < ActionController::Base
2+
include Authentication
3+
include Authorization
4+
25
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
36
allow_browser versions: :modern
47

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
module Authentication
2+
extend ActiveSupport::Concern
3+
4+
included do
5+
before_action :require_authentication
6+
helper_method :authenticated?
7+
end
8+
9+
class_methods do
10+
def allow_unauthenticated_access(**options)
11+
skip_before_action :require_authentication, **options
12+
end
13+
end
14+
15+
private
16+
def authenticated?
17+
resume_session
18+
end
19+
20+
def require_authentication
21+
resume_session || request_authentication
22+
end
23+
24+
def resume_session
25+
Current.session ||= find_session_by_cookie
26+
end
27+
28+
def find_session_by_cookie
29+
Session.find_by(id: cookies.signed[:session_id]) if cookies.signed[:session_id]
30+
end
31+
32+
def request_authentication
33+
session[:return_to_after_authenticating] = request.url
34+
redirect_to new_session_path
35+
end
36+
37+
def after_authentication_url
38+
session.delete(:return_to_after_authenticating) || root_url
39+
end
40+
41+
def start_new_session_for(user)
42+
user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|
43+
Current.session = session
44+
cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax }
45+
end
46+
end
47+
48+
def terminate_session
49+
Current.session.destroy
50+
cookies.delete(:session_id)
51+
end
52+
end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module Authorization
2+
extend ActiveSupport::Concern
3+
4+
private
5+
6+
def require_site_admin
7+
unless Current.session&.user&.site_admin?
8+
redirect_to root_path, alert: "You are not authorized to access this page."
9+
end
10+
end
11+
12+
def require_content_admin_or_above
13+
unless Current.session&.user&.admin?
14+
redirect_to root_path, alert: "You are not authorized to access this page."
15+
end
16+
end
17+
end

0 commit comments

Comments
 (0)