Skip to content
Merged
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
5 changes: 5 additions & 0 deletions app/controllers/admin_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class AdminController < ApplicationController
include Authentication
end
1 change: 0 additions & 1 deletion app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
class ApplicationController < ActionController::Base
include Authentication
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
allow_browser versions: :modern

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/classroom_rosters_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class ClassroomRostersController < ApplicationController
include StudentAuthentication
allow_unauthenticated_access

def show
@classroom = Classroom.includes(:students).find_by!(uuid: params.expect(:uuid))
end
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/classrooms_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class ClassroomsController < ApplicationController
class ClassroomsController < AdminController
before_action :set_classroom, only: %i[ edit update ]

# GET /classrooms/1/edit
Expand Down
52 changes: 52 additions & 0 deletions app/controllers/concerns/student_authentication.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
module StudentAuthentication
extend ActiveSupport::Concern

included do
before_action :require_student_authentication
helper_method :student_authenticated?
end

class_methods do
def allow_unauthenticated_access(**options)
skip_before_action :require_student_authentication, **options
end
end

private
def student_authenticated?
resume_student_session
end

def require_student_authentication
resume_student_session || request_student_authentication
end

def resume_student_session
Current.student_session ||= find_student_session_by_cookie
end

def find_student_session_by_cookie
StudentSession.find_by(id: cookies.signed[:student_session_id]) if cookies.signed[:student_session_id]
end

def request_student_authentication
session[:return_to_after_authenticating] = request.url
redirect_to new_student_session_path
end

def after_student_authentication_url
session.delete(:return_to_after_authenticating) || student_homes_url
end

def start_new_student_session_for(student)
student.sessions.create!.tap do |session|
Current.student_session = session
cookies.signed.permanent[:student_session_id] = { value: session.id, httponly: true, same_site: :lax }
end
end

def terminate_student_session
Current.student_session.destroy
cookies.delete(:student_session_id)
end
end
2 changes: 1 addition & 1 deletion app/controllers/passwords_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class PasswordsController < ApplicationController
class PasswordsController < AdminController
allow_unauthenticated_access
before_action :set_user_by_token, only: %i[ edit update ]
rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_password_path, alert: "Try again later." }
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/schools_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class SchoolsController < ApplicationController
class SchoolsController < AdminController
before_action :set_school, only: %i[ show edit update destroy ]

# GET /schools or /schools.json
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class SessionsController < ApplicationController
class SessionsController < AdminController
allow_unauthenticated_access only: %i[ new create ]
rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_session_path, alert: "Try again later." }

Expand Down
7 changes: 7 additions & 0 deletions app/controllers/student_homes_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class StudentHomesController < ApplicationController
include StudentAuthentication

def index
@student = Current.student
end
end
30 changes: 30 additions & 0 deletions app/controllers/student_sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
class StudentSessionsController < ApplicationController
include StudentAuthentication
allow_unauthenticated_access only: %i[ create new ]
before_action :set_student, only: %i[ create ]

def new
end

def create
if @student
start_new_student_session_for(@student)
redirect_to after_student_authentication_url
else
redirect_to new_student_session_path, alert: "Invalid classroom or student ID."
end
end

def destroy
terminate_student_session
redirect_to new_student_session_path, status: :see_other
end

private

def set_student
classroom = Classroom.find_by(uuid: params.expect(:classroom_uuid))
return unless classroom
@student = classroom.students.find_by(id: params.expect(:student_id))
end
end
2 changes: 1 addition & 1 deletion app/controllers/students_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class StudentsController < ApplicationController
class StudentsController < AdminController
before_action :set_school, only: %i[ index new create ]
before_action :set_student, only: %i[ show edit update destroy ]

Expand Down
4 changes: 3 additions & 1 deletion app/models/current.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class Current < ActiveSupport::CurrentAttributes
attribute :session
attribute :session, :student_session
delegate :user, to: :session, allow_nil: true
attribute :student_session
delegate :student, to: :student_session, allow_nil: true
end
1 change: 1 addition & 0 deletions app/models/student.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class Student < ApplicationRecord
belongs_to :school
belongs_to :classroom
has_many :sessions, class_name: "StudentSession", dependent: :destroy

enum :gender, %i[male female other].index_by(&:itself)

Expand Down
3 changes: 3 additions & 0 deletions app/models/student_session.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class StudentSession < ApplicationRecord
belongs_to :student
end
12 changes: 7 additions & 5 deletions app/views/classroom_rosters/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
</header>
<ul class="list bg-base-100 rounded-box shadow-md">
<% @classroom.students.sort_by(&:full_name).each do |student| %>
<%= link_to "#" do %>
<li class="list-row link link-hover">
<span class="text-2xl">
<li class="list-row link link-hover text-2xl">
<%= form_with(url: student_session_path) do |form| %>
<%= form.hidden_field :student_id, value: student.id %>
<%= form.hidden_field :classroom_uuid, value: @classroom.uuid %>
<%= form.button type: :submit, class: "w-full cursor-pointer" do %>
<%= student.full_name %>
</span>
</li>
<% end %>
<% end %>
</li>
<% end %>
</ul>
11 changes: 11 additions & 0 deletions app/views/student_homes/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<section class="py-10 flex items-center justify-center">
<div class="card card-border bg-base-100 shadow-md w-96">
<div class="card-body">
<h1 class="card-title">Welcome to EndsideOut!</h1>
<p>You are logged in as <strong><%= @student.full_name %></strong></p>
<div class="card-actions justify-end">
<%= button_to "Logout", student_session_path, method: :delete, class: "btn btn-primary" %>
</div>
</div>
</div>
</section>
8 changes: 8 additions & 0 deletions app/views/student_sessions/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<section class="py-10 flex items-center justify-center">
<div class="card card-border bg-base-100 shadow-md w-96">
<div class="card-body">
<h1 class="card-title">Welcome to EndsideOut</h1>
<p>If you are a student looking to log in, you need to get a special link from your teacher or facilitator.</p>
</div>
</div>
</section>
3 changes: 2 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
Rails.application.routes.draw do
get "classroom_rosters/show"
resource :session
resources :passwords, param: :token
resource :student_session, only: %i[new create destroy]
resources :student_homes, only: %i[index]
resources :classroom_rosters, only: %i[show], param: :uuid
scope :admin do
resources :schools do
Expand Down
9 changes: 9 additions & 0 deletions db/migrate/20260429230254_create_student_sessions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class CreateStudentSessions < ActiveRecord::Migration[8.1]
def change
create_table :student_sessions do |t|
t.belongs_to :student, null: false, foreign_key: true

t.timestamps
end
end
end
10 changes: 9 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions test/controllers/student_home_controller_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require "test_helper"

class StudentHomeControllerTest < ActionDispatch::IntegrationTest
test "should get index" do
student_sign_in_as(students(:ada))

get student_homes_path
assert_response :success
end

test "should redirect to new session if not signed in" do
get student_homes_path
assert_redirected_to new_student_session_path
end
end
42 changes: 42 additions & 0 deletions test/controllers/student_sessions_controller_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
require "test_helper"

class StudentSessionsControllerTest < ActionDispatch::IntegrationTest
setup { @student = students(:ada) }

test "new" do
get new_student_session_path
assert_response :success
end

test "create with valid classroom" do
post student_session_path, params: { student_id: @student.id, classroom_uuid: @student.classroom.uuid }

assert_redirected_to student_homes_path
assert cookies[:student_session_id]
end

test "create with invalid uuid" do
post student_session_path, params: { student_id: @student.id, classroom_uuid: "wrong" }

assert_redirected_to new_student_session_path
assert_nil cookies[:student_session_id]
end

test "create with wrong classroom uuid" do
other_classroom = classrooms(:two)
assert_not @student.classroom_id == other_classroom.id
post student_session_path, params: { student_id: @student.id, classroom_uuid: other_classroom.uuid }

assert_redirected_to new_student_session_path
assert_nil cookies[:student_session_id]
end

test "destroy" do
student_sign_in_as(@student)

delete student_session_path

assert_redirected_to new_student_session_path
assert_empty cookies[:student_session_id]
end
end
7 changes: 7 additions & 0 deletions test/models/student_session_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require "test_helper"

class StudentSessionTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end
2 changes: 2 additions & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
require_relative "../config/environment"
require "rails/test_help"
require_relative "test_helpers/session_test_helper"
require_relative "test_helpers/student_session_test_helper"


module ActiveSupport
class TestCase
Expand Down
19 changes: 19 additions & 0 deletions test/test_helpers/student_session_test_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module StudentSessionTestHelper
def student_sign_in_as(student)
Current.student_session = student.sessions.create!

ActionDispatch::TestRequest.create.cookie_jar.tap do |cookie_jar|
cookie_jar.signed[:student_session_id] = Current.student_session.id
cookies["student_session_id"] = cookie_jar[:student_session_id]
end
end

def student_sign_out
Current.student_session&.destroy!
cookies.delete("student_session_id")
end
end

ActiveSupport.on_load(:action_dispatch_integration_test) do
include StudentSessionTestHelper
end