add sessions for admin access
This commit is contained in:
@@ -34,7 +34,12 @@ class ApplicationController < ActionController::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def require_admin
|
def require_admin
|
||||||
unless admin?
|
unless logged_in?
|
||||||
|
redirect_to login_path, alert: "You must be logged in to access this page."
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
unless current_user.admin?
|
||||||
redirect_to root_path, alert: "You must be an administrator to access this page."
|
redirect_to root_path, alert: "You must be an administrator to access this page."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
class SessionsController < ApplicationController
|
||||||
|
def new
|
||||||
|
# Redirect to admin if already logged in
|
||||||
|
if logged_in?
|
||||||
|
redirect_to admin? ? admin_root_path : root_path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
user = User.find_by(email: params[:email]&.downcase&.strip)
|
||||||
|
|
||||||
|
if user&.authenticate(params[:password])
|
||||||
|
# Check if user has accepted invitation
|
||||||
|
unless user.invitation_accepted_at.present?
|
||||||
|
flash.now[:alert] = "Your account is pending. Please use your invitation link to complete registration."
|
||||||
|
render :new, status: :unprocessable_entity
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
session[:user_id] = user.id
|
||||||
|
redirect_to admin? ? admin_root_path : root_path, notice: "Welcome back, #{user.name}!"
|
||||||
|
else
|
||||||
|
flash.now[:alert] = "Invalid email or password."
|
||||||
|
render :new, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
session[:user_id] = nil
|
||||||
|
redirect_to root_path, notice: "You have been logged out."
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
<div class="flex-1 flex flex-col">
|
<div class="flex-1 flex flex-col">
|
||||||
<section class="bg-white border-b border-slate-200">
|
<section class="bg-white border-b border-slate-200">
|
||||||
<div class="max-w-7xl mx-auto px-4 pb-6 space-y-4">
|
<div class="max-w-7xl mx-auto px-4 pb-1 space-y-4">
|
||||||
<%= form_with url: entries_path,
|
<%= form_with url: entries_path,
|
||||||
method: :get,
|
method: :get,
|
||||||
data: { turbo_stream: true } do |form| %>
|
data: { turbo_stream: true } do |form| %>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<%= link_to "Users", admin_users_path, class: "text-sm font-medium text-slate-600 hover:text-indigo-600 transition" %>
|
<%= link_to "Users", admin_users_path, class: "text-sm font-medium text-slate-600 hover:text-indigo-600 transition" %>
|
||||||
<%= link_to "Invitations", admin_invitations_path, class: "text-sm font-medium text-slate-600 hover:text-indigo-600 transition" %>
|
<%= link_to "Invitations", admin_invitations_path, class: "text-sm font-medium text-slate-600 hover:text-indigo-600 transition" %>
|
||||||
<%= link_to "Back to Site", root_path, class: "text-sm font-medium text-slate-600 hover:text-indigo-600 transition" %>
|
<%= link_to "Back to Site", root_path, class: "text-sm font-medium text-slate-600 hover:text-indigo-600 transition" %>
|
||||||
<%= button_to "Log Out", logout_path, method: :delete, class: "bg-indigo-600 text-white px-4 py-2 rounded-lg text-sm font-semibold hover:bg-indigo-700 transition" %>
|
<%= button_to "Log Out", logout_path, method: :delete, form: { data: { turbo: false }, style: "display: inline-block;" }, class: "bg-indigo-600 text-white px-4 py-2 rounded-lg text-sm font-semibold hover:bg-indigo-700 transition cursor-pointer" %>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<% content_for :title, "Sign In" %>
|
||||||
|
|
||||||
|
<div class="min-h-screen flex flex-col">
|
||||||
|
<header class="bg-white border-b border-slate-200">
|
||||||
|
<div class="max-w-7xl mx-auto px-4">
|
||||||
|
<div class="h-16 flex items-center">
|
||||||
|
<%= link_to root_path, class: "flex items-center gap-2" do %>
|
||||||
|
<span class="text-xl font-bold tracking-tight text-indigo-600">Sanasto</span>
|
||||||
|
<span class="text-xl font-light text-slate-400">Wiki</span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="flex-1 flex items-center justify-center px-4 py-12 bg-slate-50">
|
||||||
|
<div class="w-full max-w-md">
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-8">
|
||||||
|
<div class="mb-8">
|
||||||
|
<h1 class="text-2xl font-bold text-slate-900 mb-2">Sign in</h1>
|
||||||
|
<p class="text-sm text-slate-600">Enter your credentials to continue</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% if flash[:alert] %>
|
||||||
|
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg mb-6" role="alert">
|
||||||
|
<%= flash[:alert] %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= form_with url: login_path, method: :post, local: true, class: "space-y-5" do |form| %>
|
||||||
|
<div>
|
||||||
|
<%= form.label :email, "Email", class: "block text-sm font-medium text-slate-700 mb-2" %>
|
||||||
|
<%= form.email_field :email,
|
||||||
|
autofocus: true,
|
||||||
|
autocomplete: "email",
|
||||||
|
required: true,
|
||||||
|
placeholder: "you@example.com",
|
||||||
|
class: "block w-full px-4 py-3 bg-white border border-slate-200 rounded-lg shadow-sm focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<%= form.label :password, "Password", class: "block text-sm font-medium text-slate-700 mb-2" %>
|
||||||
|
<%= form.password_field :password,
|
||||||
|
autocomplete: "current-password",
|
||||||
|
required: true,
|
||||||
|
placeholder: "••••••••••••",
|
||||||
|
class: "block w-full px-4 py-3 bg-white border border-slate-200 rounded-lg shadow-sm focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pt-2">
|
||||||
|
<%= form.submit "Sign In",
|
||||||
|
class: "w-full bg-indigo-600 text-white px-4 py-3 rounded-lg text-sm font-semibold hover:bg-indigo-700 transition cursor-pointer" %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<div class="mt-6 text-center">
|
||||||
|
<%= link_to root_path, class: "text-sm text-slate-600 hover:text-indigo-600 transition inline-flex items-center gap-1" do %>
|
||||||
|
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||||||
|
</svg>
|
||||||
|
Back to Wiki
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
+20
-11
@@ -2,21 +2,25 @@
|
|||||||
|
|
||||||
## Authentication & Authorization
|
## Authentication & Authorization
|
||||||
|
|
||||||
- [ ] **Authentication system**
|
- [x] **Authentication system**
|
||||||
- [ ] Sessions controller and views (login/logout)
|
- [x] Sessions controller and views (login/logout)
|
||||||
|
- [x] Email/password authentication with session management
|
||||||
|
- [x] Login redirects (admin vs regular users)
|
||||||
|
- [x] Logout functionality
|
||||||
- [ ] Password reset flow
|
- [ ] Password reset flow
|
||||||
- [ ] Rate limiting on login attempts
|
- [ ] Rate limiting on login attempts
|
||||||
- [ ] Session management (remember me, session timeout)
|
- [ ] Session management (remember me, session timeout)
|
||||||
- [ ] **Invitation system**
|
- [ ] **Invitation system**
|
||||||
- [ ] Invitations controller and mailer
|
- [x] Invitations controller (create, list, cancel)
|
||||||
- [ ] Invitation token generation and validation
|
- [x] Invitation token generation
|
||||||
- [ ] Registration via invitation link
|
- [ ] Registration via invitation link (acceptance flow)
|
||||||
- [ ] Token expiry (14 days)
|
- [ ] Token expiry validation (14 days)
|
||||||
- [ ] **Authorization & roles**
|
- [ ] Invitation mailer
|
||||||
- [ ] Role-based access control middleware
|
- [x] **Authorization & roles**
|
||||||
- [ ] Contributor permissions enforcement
|
- [x] Role-based access control middleware (Admin::BaseController)
|
||||||
- [ ] Reviewer permissions enforcement
|
- [x] Admin permissions enforcement
|
||||||
- [ ] Admin permissions enforcement
|
- [ ] Contributor permissions enforcement (for entry editing)
|
||||||
|
- [ ] Reviewer permissions enforcement (for review queue)
|
||||||
|
|
||||||
## Core Features
|
## Core Features
|
||||||
|
|
||||||
@@ -122,6 +126,11 @@
|
|||||||
|
|
||||||
## Completed
|
## Completed
|
||||||
|
|
||||||
|
- [x] **Authentication system** (login/logout with session management)
|
||||||
|
- [x] **Admin layout design** updated to match entries page style
|
||||||
|
- [x] **Dynamic navigation** (Admin button for logged-in admins, Sign In for guests)
|
||||||
|
- [x] **Authorization middleware** (Admin::BaseController with role checks)
|
||||||
|
- [x] **Invitation token generation** (secure token creation for new users)
|
||||||
- [x] **Search input loses focus on filter change**
|
- [x] **Search input loses focus on filter change**
|
||||||
- [x] **Mismatched enum syntax** in models
|
- [x] **Mismatched enum syntax** in models
|
||||||
- [x] **Replace hardcoded LANGUAGE_COLUMNS** with dynamic query
|
- [x] **Replace hardcoded LANGUAGE_COLUMNS** with dynamic query
|
||||||
|
|||||||
Reference in New Issue
Block a user