Require valid invitation to start sign-up process
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Basti 2020-12-02 19:20:01 +01:00
parent 69b99711e5
commit 7aadb5cb51
Signed by untrusted user: basti
GPG Key ID: 9F88009D31D99C72
15 changed files with 201 additions and 3 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -0,0 +1,2 @@
module SignupHelper
end

View File

@ -36,4 +36,8 @@ class User < ApplicationRecord
false
end
end
def address
"#{self.cn}@#{self.ou}"
end
end

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<title>Sign up | Kosmos Accounts</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<meta name="viewport" content="width=device-width, initial-scale=1">
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<div id="wrapper">
<header>
<h1>
<span class ="project-name">Kosmos</span>
<span class ="site-name">Sign Up</span>
<!-- <span class="beta"><span class="bolt">⚡</span> beta</span> -->
</h1>
<% if user_signed_in? %>
<p class="current-user">
Signed in as <strong><%= current_user.cn %>@kosmos.org</strong>.
<%= link_to "Log out", destroy_user_session_path, method: :delete %>
</p>
<% end %>
</header>
<% flash.each do |type, msg| %>
<div class="flash-msg <%= type %>">
<p><%= msg %></p>
</div>
<% end %>
<main>
<%= yield %>
</main>
</div>
</body>
</html>

View File

@ -1,2 +1,2 @@
<h2>Access forbidden</h2>
<p>Not with those shoes, buddy.</p>
<p>Sorry, you're not allowed to access this page.</p>

View File

@ -0,0 +1,12 @@
<h2>Welcome</h2>
<p>
Hey there! You were invited to sign up for a Kosmos account by
<strong><%= @invited_by_name %></strong>.
</p>
<p>
This invitation can only be used once, and sign-up is currently only possible
by invitation. Seems like you have good friends!
</p>
<p>
<%= link_to "Get started", signup_path, class: "next-step" %>
</p>

View File

@ -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'

View File

@ -0,0 +1,7 @@
FactoryBot.define do
factory :invitation do
id { 1 }
token { "abcdef123456" }
user
end
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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