diff --git a/app/controllers/organization_subscriptions_controller.rb b/app/controllers/organization_subscriptions_controller.rb
new file mode 100644
index 000000000..1ee93eb02
--- /dev/null
+++ b/app/controllers/organization_subscriptions_controller.rb
@@ -0,0 +1,34 @@
+class OrganizationSubscriptionsController < ApplicationController
+ AUTHENTICATION_TOKEN_EXPIRY_TIME = 2.weeks
+ before_action :validate_token
+ before_action :skip_authorization
+
+ def edit
+ end
+
+ def update
+ @user.update!(subscribed_organization_ids: params[:user][:subscribed_organization_ids])
+ flash[:notice] = 'Subscriptions Updated. Thanks!'
+ redirect_to root_url
+ end
+
+ private
+
+ def validate_token
+ @email_token = params[:token]
+ @user = User.find_by(email_authentication_token: @email_token)
+ if @user.nil?
+ redirect_to root_url
+ return
+ end
+
+ created_at = @user.email_authentication_created_at
+
+ if created_at < AUTHENTICATION_TOKEN_EXPIRY_TIME.ago
+ flash[:notice] = 'This link has expired.'\
+ ' Please log in to change your email preferences'
+ redirect_to root_url
+ return
+ end
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 43653c92f..7834d4af1 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -25,6 +25,7 @@ class User < ActiveRecord::Base
validates_presence_of :first_name, :last_name, :profile
validates_inclusion_of :time_zone, in: ActiveSupport::TimeZone.all.map(&:name), allow_blank: true
+ validates :email_authentication_token, length: { is: 32 }, allow_nil: true
def self.from_omniauth(omniauth)
authentication = Authentication.where(provider: omniauth['provider'], uid: omniauth['uid'].to_s).first
@@ -86,4 +87,12 @@ def event_role(event)
def event_checkiner?(event)
event_attendance(event)[:checkiner]
end
+
+ def generate_email_authentication_token!
+ new_token = SecureRandom.base64[0..15] + SecureRandom.base64[0..15]
+ self.email_authentication_token = new_token
+ self.email_authentication_created_at = Time.now
+ return generate_email_authentication_token! unless self.save
+ new_token
+ end
end
diff --git a/app/views/organization_subscriptions/edit.html.erb b/app/views/organization_subscriptions/edit.html.erb
new file mode 100644
index 000000000..b004da67e
--- /dev/null
+++ b/app/views/organization_subscriptions/edit.html.erb
@@ -0,0 +1,12 @@
+<%= simple_form_for(@user, :url => "/organization_subscriptions/#{@email_token}", :html => {:method => 'put'}) do |f| %>
+ <%= devise_error_messages! %>
+
+
+
+ <%= f.submit 'Update Email Preferences', class: 'btn btn-submit', data: {disable_with: 'Please wait...'} %>
+<% end %>
diff --git a/config/routes.rb b/config/routes.rb
index d9221e84a..c686285dd 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -109,6 +109,9 @@
get :raise_exception
end
+ get "/organization_subscriptions/:token" => "organization_subscriptions#edit"
+ put "/organization_subscriptions/:token" => "organization_subscriptions#update"
+
if Rails.env.development?
get "/style_guide" => "static_pages#style_guide"
end
diff --git a/db/migrate/20160820181944_add_email_authentication_token_to_user.rb b/db/migrate/20160820181944_add_email_authentication_token_to_user.rb
new file mode 100644
index 000000000..0adf96d11
--- /dev/null
+++ b/db/migrate/20160820181944_add_email_authentication_token_to_user.rb
@@ -0,0 +1,8 @@
+class AddEmailAuthenticationTokenToUser < ActiveRecord::Migration
+ def change
+ add_column :users, :email_authentication_token, :string
+ add_column :users, :email_authentication_created_at, :timestamp
+
+ add_index :users, :email_authentication_token, unique: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index b637a3267..c6c6d24de 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20160807055624) do
+ActiveRecord::Schema.define(version: 20160820181944) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
enable_extension "unaccent"
@@ -281,12 +281,12 @@
end
create_table "users", force: :cascade do |t|
- t.string "email", default: "", null: false
- t.string "encrypted_password", default: "", null: false
+ t.string "email", default: "", null: false
+ t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
- t.integer "sign_in_count", default: 0
+ t.integer "sign_in_count", default: 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
@@ -294,21 +294,24 @@
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.boolean "admin", default: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.boolean "admin", default: false
t.string "first_name"
t.string "last_name"
t.string "time_zone"
t.string "gender"
- t.boolean "allow_event_email", default: true
- t.boolean "publisher", default: false
- t.boolean "spammer", default: false
- t.integer "authentications_count", default: 0
+ t.boolean "allow_event_email", default: true
+ t.boolean "publisher", default: false
+ t.boolean "spammer", default: false
+ t.integer "authentications_count", default: 0
+ t.string "email_authentication_token"
+ t.datetime "email_authentication_created_at"
end
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
add_index "users", ["email"], name: "index_users_on_email", unique: true
+ add_index "users", ["email_authentication_token"], name: "index_users_on_email_authentication_token", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
add_foreign_key "authentications", "users"
diff --git a/spec/controllers/organization_subscriptions_controller_spec.rb b/spec/controllers/organization_subscriptions_controller_spec.rb
new file mode 100644
index 000000000..ac02ced87
--- /dev/null
+++ b/spec/controllers/organization_subscriptions_controller_spec.rb
@@ -0,0 +1,85 @@
+require 'rails_helper'
+
+describe OrganizationSubscriptionsController do
+ before { @authorization_token = 'andyandhaseebaregarbageteacherss' }
+
+
+ describe '#edit' do
+ subject { get :edit, token: @authorization_token }
+
+ context 'a user exists with the specified email_authorization_token' do
+ before do
+ @user = create(:user,
+ email_authentication_token: @authorization_token,
+ email_authentication_created_at: Time.now)
+ end
+
+ it 'renders the edit form' do
+ expect(subject).to render_template(:edit)
+ end
+
+ context 'the token was generated more than 2 weeks ago' do
+ before { @user.update! email_authentication_created_at: 3.weeks.ago }
+
+ it 'redirects to the root' do
+ expect(subject).to redirect_to(root_url)
+ expect(flash[:notice]).to match('link has expired.')
+ end
+ end
+ end
+
+ context 'a user does not exist with the specified email_authorization_token' do
+ it 'redirects to the root' do
+ expect(subject).to redirect_to(root_url)
+ end
+ end
+ end
+
+ describe '#update' do
+ subject do
+ put :update,
+ token: @authorization_token,
+ user: {
+ subscribed_organization_ids: @subscribed_organizations
+ }
+ end
+
+ context 'a user exists with the token' do
+ before do
+ @user = create(:user,
+ email_authentication_token: @authorization_token,
+ email_authentication_created_at: Time.now)
+
+ 3.times do
+ create(:organization)
+ end
+
+ @subscribed_organizations = Organization.all.pluck(:id).drop(1)
+ end
+
+ it 'updates the email preferences of the user, without logging in' do
+ subject
+ expect(@user.subscribed_organizations)
+ .to match_array Organization.where(id: @subscribed_organizations)
+ expect(flash[:notice]).to match(/thanks/i)
+ expect(controller.current_user).to be_nil
+ end
+
+ context 'the token was generated more than 2 weeks ago' do
+ before { @user.update! email_authentication_created_at: 3.weeks.ago }
+
+ it 'redirects to the root' do
+ expect {
+ expect(subject).to redirect_to(root_url)
+ }.not_to change { @user.reload.subscribed_organizations }
+ end
+ end
+ end
+
+ context 'a user does not exist with the token' do
+ it 'redirects to the root' do
+ expect(subject).to redirect_to(root_url)
+ end
+ end
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index bdb56cd69..d42f88efb 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -56,4 +56,24 @@
expect(@user.profile_path).to eq(Rails.application.routes.url_helpers.user_profile_path(@user))
end
end
+
+ describe '#generate_email_authentication_token' do
+ it 'generates a UID of length 32' do
+ @user.generate_email_authentication_token!
+ expect(@user.email_authentication_token.length).to eq 32
+ end
+
+ it 'sets the email_authentication_created_at' do
+ @user.generate_email_authentication_token!
+ expect(@user.email_authentication_created_at).to be > 1.minute.ago
+ end
+
+ it 'does not reuse the same token when generated again' do
+ @user.generate_email_authentication_token!
+ first_token = @user.email_authentication_token
+ @user.generate_email_authentication_token!
+ second_token = @user.email_authentication_token
+ expect(first_token).to_not eq second_token
+ end
+ end
end