diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e746ab7..956f744 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -34,7 +34,12 @@ class ApplicationController < ActionController::Base end 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." end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000..247f92a --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -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 diff --git a/app/views/entries/index.html.erb b/app/views/entries/index.html.erb index bf829e4..2a18105 100644 --- a/app/views/entries/index.html.erb +++ b/app/views/entries/index.html.erb @@ -23,7 +23,7 @@
-
+
<%= form_with url: entries_path, method: :get, data: { turbo_stream: true } do |form| %> diff --git a/app/views/layouts/admin.html.erb b/app/views/layouts/admin.html.erb index a326e50..e2a5597 100644 --- a/app/views/layouts/admin.html.erb +++ b/app/views/layouts/admin.html.erb @@ -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 "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" %> - <%= 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" %>
diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb new file mode 100644 index 0000000..6d63994 --- /dev/null +++ b/app/views/sessions/new.html.erb @@ -0,0 +1,66 @@ +<% content_for :title, "Sign In" %> + +
+
+
+
+ <%= link_to root_path, class: "flex items-center gap-2" do %> + Sanasto + Wiki + <% end %> +
+
+
+ +
+
+
+
+

Sign in

+

Enter your credentials to continue

+
+ + <% if flash[:alert] %> + + <% end %> + + <%= form_with url: login_path, method: :post, local: true, class: "space-y-5" do |form| %> +
+ <%= 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" %> +
+ +
+ <%= 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" %> +
+ +
+ <%= 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" %> +
+ <% end %> + +
+ <%= link_to root_path, class: "text-sm text-slate-600 hover:text-indigo-600 transition inline-flex items-center gap-1" do %> + + + + Back to Wiki + <% end %> +
+
+
+
+
diff --git a/docs/TODO.md b/docs/TODO.md index 6db798a..cd9383c 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -2,21 +2,25 @@ ## Authentication & Authorization -- [ ] **Authentication system** - - [ ] Sessions controller and views (login/logout) +- [x] **Authentication system** + - [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 - [ ] Rate limiting on login attempts - [ ] Session management (remember me, session timeout) - [ ] **Invitation system** - - [ ] Invitations controller and mailer - - [ ] Invitation token generation and validation - - [ ] Registration via invitation link - - [ ] Token expiry (14 days) -- [ ] **Authorization & roles** - - [ ] Role-based access control middleware - - [ ] Contributor permissions enforcement - - [ ] Reviewer permissions enforcement - - [ ] Admin permissions enforcement + - [x] Invitations controller (create, list, cancel) + - [x] Invitation token generation + - [ ] Registration via invitation link (acceptance flow) + - [ ] Token expiry validation (14 days) + - [ ] Invitation mailer +- [x] **Authorization & roles** + - [x] Role-based access control middleware (Admin::BaseController) + - [x] Admin permissions enforcement + - [ ] Contributor permissions enforcement (for entry editing) + - [ ] Reviewer permissions enforcement (for review queue) ## Core Features @@ -122,6 +126,11 @@ ## 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] **Mismatched enum syntax** in models - [x] **Replace hardcoded LANGUAGE_COLUMNS** with dynamic query