diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index dd4cf1c..c672886 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -9,6 +9,12 @@ class ApplicationController < ActionController::Base end end + def require_user_signed_out + if user_signed_in? + redirect_to root_path and return + end + end + def authorize_admin http_status :forbidden unless current_user.is_admin? end diff --git a/app/controllers/invitations_controller.rb b/app/controllers/invitations_controller.rb index 7809a07..46bd2b1 100644 --- a/app/controllers/invitations_controller.rb +++ b/app/controllers/invitations_controller.rb @@ -1,11 +1,26 @@ class InvitationsController < ApplicationController - before_action :require_user_signed_in + before_action :require_user_signed_in, except: ["show"] + before_action :require_user_signed_out, only: ["show"] + + layout "signup", only: ["show"] # GET /invitations def index @invitations = current_user.invitations end + # GET /invitations/a-random-invitation-token + def show + token = session[:invitation_token] = params[:id] + + if Invitation.where(token: token, used_at: nil).exists? + redirect_to signup_path and return + else + flash.now[:alert] = "This invitation either doesn't exist or has already been used." + http_status :unauthorized + end + end + # POST /invitations def create @invitation = Invitation.new(user: current_user) diff --git a/app/controllers/signup_controller.rb b/app/controllers/signup_controller.rb new file mode 100644 index 0000000..21dd7b3 --- /dev/null +++ b/app/controllers/signup_controller.rb @@ -0,0 +1,33 @@ +class SignupController < ApplicationController + before_action :require_user_signed_out + before_action :require_invitation + before_action :set_invitation + + layout "signup" + + def index + @invited_by_name = @invitation.user.address + end + + private + + def require_invitation + if session[:invitation_token].blank? + flash.now[:alert] = "You need an invitation to sign up for an account." + http_status :unauthorized + elsif !valid_invitation?(session[:invitation_token]) + flash.now[:alert] = "This invitation either doesn't exist or has already been used." + http_status :unauthorized + end + + @invitation = Invitation.find_by(token: session[:invitation_token]) + end + + def valid_invitation?(token) + Invitation.where(token: session[:invitation_token], used_at: nil).exists? + end + + def set_invitation + @invitation = Invitation.find_by(token: session[:invitation_token]) + end +end diff --git a/app/helpers/signup_helper.rb b/app/helpers/signup_helper.rb new file mode 100644 index 0000000..f954dda --- /dev/null +++ b/app/helpers/signup_helper.rb @@ -0,0 +1,2 @@ +module SignupHelper +end diff --git a/app/models/user.rb b/app/models/user.rb index bc28e90..e6f80f0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -36,4 +36,8 @@ class User < ApplicationRecord false end end + + def address + "#{self.cn}@#{self.ou}" + end end diff --git a/app/views/layouts/signup.html.erb b/app/views/layouts/signup.html.erb new file mode 100644 index 0000000..05fbf73 --- /dev/null +++ b/app/views/layouts/signup.html.erb @@ -0,0 +1,41 @@ + + +
++ Signed in as <%= current_user.cn %>@kosmos.org. + <%= link_to "Log out", destroy_user_session_path, method: :delete %> +
+ <% end %> +<%= msg %>
+Not with those shoes, buddy.
+Sorry, you're not allowed to access this page.
diff --git a/app/views/shared/status_unauthorized.html.erb b/app/views/shared/status_unauthorized.html.erb new file mode 100644 index 0000000..e69de29 diff --git a/app/views/signup/index.html.erb b/app/views/signup/index.html.erb new file mode 100644 index 0000000..f3782fa --- /dev/null +++ b/app/views/signup/index.html.erb @@ -0,0 +1,12 @@ ++ Hey there! You were invited to sign up for a Kosmos account by + <%= @invited_by_name %>. +
++ This invitation can only be used once, and sign-up is currently only possible + by invitation. Seems like you have good friends! +
++ <%= link_to "Get started", signup_path, class: "next-step" %> +
diff --git a/config/routes.rb b/config/routes.rb index 8b08a0f..f6aa90c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,10 +4,12 @@ Rails.application.routes.draw do get 'welcome', to: 'welcome#index' get 'check_your_email', to: 'welcome#check_your_email' + get 'signup', to: 'signup#index' + get 'settings', to: 'settings#index' post 'settings_reset_password', to: 'settings#reset_password' - resources :invitations, only: ['index', 'create', 'destroy'] + resources :invitations, only: ['index', 'show', 'create', 'destroy'] namespace :admin do root to: 'dashboard#index' diff --git a/spec/factories/invitations.rb b/spec/factories/invitations.rb new file mode 100644 index 0000000..621ac80 --- /dev/null +++ b/spec/factories/invitations.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :invitation do + id { 1 } + token { "abcdef123456" } + user + end +end diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb new file mode 100644 index 0000000..e6ea504 --- /dev/null +++ b/spec/features/signup_spec.rb @@ -0,0 +1,39 @@ +require "rails_helper" + +RSpec.describe "Signup", type: :feature do + let(:user) { create :user } + + before do + @unused_invitation = Invitation.create(user: user) + @used_invitation = Invitation.create(user: user) + @used_invitation.update_attribute :used_at, DateTime.now - 1.day + end + + scenario "Follow link for non-existing invitation" do + visit invitation_url(id: "123") + + within ".flash-msg.alert" do + expect(page).to have_content("doesn't exist") + end + end + + scenario "Follow link for used invitation" do + visit invitation_url(id: @used_invitation.token) + + within ".flash-msg.alert" do + expect(page).to have_content("has already been used") + end + end + + scenario "Follow link for unused invitation" do + visit invitation_url(id: @unused_invitation.token) + + expect(current_url).to eq(signup_url) + expect(page).to have_content("Welcome") + end + + scenario "Successful signup" do + visit invitation_url(id: @unused_invitation.token) + click_link "Get started" + end +end diff --git a/spec/helpers/signup_helper_spec.rb b/spec/helpers/signup_helper_spec.rb new file mode 100644 index 0000000..0ed5adc --- /dev/null +++ b/spec/helpers/signup_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the SignupHelper. For example: +# +# describe SignupHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe SignupHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 3bc4941..ed135d9 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -20,4 +20,12 @@ RSpec.describe User, type: :model do expect(user.is_admin?).to be false end end + + describe "#address" do + let(:user) { build :user, cn: "jimmy", ou: "kosmos.org" } + + it "returns the user address" do + expect(user.address).to eq("jimmy@kosmos.org") + end + end end diff --git a/spec/requests/signup_request_spec.rb b/spec/requests/signup_request_spec.rb new file mode 100644 index 0000000..a782f48 --- /dev/null +++ b/spec/requests/signup_request_spec.rb @@ -0,0 +1,14 @@ +require 'rails_helper' + +RSpec.describe "Signups", type: :request do + + describe "GET /index" do + context "without invitation" do + it "returns http unauthorized" do + get "/signup" + expect(response).to have_http_status(:unauthorized) + end + end + end + +end