diff --git a/Gemfile b/Gemfile index 22020cab..a4ddb07c 100644 --- a/Gemfile +++ b/Gemfile @@ -30,7 +30,7 @@ gem 'jbuilder', '~> 2.5' # Use Redis adapter to run Action Cable in production # gem 'redis', '~> 3.0' # Use ActiveModel has_secure_password -# gem 'bcrypt', '~> 3.1.7' +gem 'bcrypt', '~> 3.1.7' # Authentication. gem 'omniauth' diff --git a/Gemfile.lock b/Gemfile.lock index 293528cb..ccf587c6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -39,6 +39,7 @@ GEM minitest (~> 5.1) tzinfo (~> 1.1) arel (7.1.4) + bcrypt (3.1.11) bigbluebutton-api-ruby (1.6.0) xml-simple (~> 1.1) bindex (0.5.0) @@ -201,6 +202,7 @@ PLATFORMS ruby DEPENDENCIES + bcrypt (~> 3.1.7) bigbluebutton-api-ruby bootstrap-sass (= 3.3.0.0) bootstrap-social-rails (~> 4.12) diff --git a/app/controllers/meetings_controller.rb b/app/controllers/meetings_controller.rb index 3efc3137..312288f4 100644 --- a/app/controllers/meetings_controller.rb +++ b/app/controllers/meetings_controller.rb @@ -86,7 +86,8 @@ class MeetingsController < ApplicationController private def meeting_params(room) - params.require(:meeting).permit(:name).merge!(room_id: room.id) + params.require(:meeting).permit(:name).merge!(room: room) + #params.require(:meeting).permit(:name).merge!(room_id: room.id) end def default_meeting_options diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index ab0161dc..c602edbf 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -6,17 +6,35 @@ class SessionsController < ApplicationController # GET /logout def destroy - logout + logout if current_user + end + + # POST /login + def create + user = User.find_by(email: session_params[:email]) + if user && user.authenticate(session_params[:password]) + login(user) + else + # Login unsuccessful, display error message. + + render :new + end end # GET/POST /auth/:provider/callback - def create + def omniauth_session user = User.from_omniauth(request.env['omniauth.auth']) login(user) end # POST /auth/failure def fail + redirect_to root_path + end + private + + def session_params + params.require(:session).permit(:email, :password) end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 1c6eb415..98ae769e 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,2 +1,23 @@ class UsersController < ApplicationController + + # GET /signup + def new + @user = User.new + end + + # POST /signup + def create + user = User.new(user_params) + if user.save + login(user) + else + render :new + end + end + + private + + def user_params + params.require(:user).permit(:name, :email, :password, :password_confirmation) + end end \ No newline at end of file diff --git a/app/helpers/big_blue_helper.rb b/app/helpers/big_blue_helper.rb deleted file mode 100644 index e86c786d..00000000 --- a/app/helpers/big_blue_helper.rb +++ /dev/null @@ -1,149 +0,0 @@ -module BigBlueHelper - - META_LISTED = "gl-listed" - META_TOKEN = "gl-token" - - def bbb_endpoint - Rails.configuration.bigbluebutton_endpoint - end - - def bbb_secret - Rails.configuration.bigbluebutton_secret - end - - def bbb - @bbb ||= BigBlueButton::BigBlueButtonApi.new(bbb_endpoint + "api", bbb_secret, "0.8") - end - - # Generates a BigBlueButton meeting id from a meeting token. - def bbb_meeting_id(id) - Digest::SHA1.hexdigest(Rails.application.secrets[:secret_key_base] + id).to_s - end - - # Generates a random password for a meeting. - def random_password(length) - o = ([('a'..'z'), ('A'..'Z')].map do |i| i.to_a end).flatten - ((0...length).map do o[rand(o.length)] end).join - end - - # Checks if a meeting is running on the BigBlueButton server. - def meeting_is_running?(id) - begin - bbb.get_meeting_info(id, nil) - return true - rescue BigBlueButton::BigBlueButtonException => exc - return false - end - end - - def start_meeting(options) - meeting_id = bbb_meeting_id(name) - - # Need to create the meeting on the BigBlueButton server. - create_options = { - record: options[:meeting_recorded].to_s, - #logoutURL: options[:meeting_logout_url] || request.base_url, - moderatorPW: random_password(12), - attendeePW: random_password(12), - moderatorOnlyMessage: options[:moderator_message], - "meta_#{BigBlueHelper::META_LISTED}": false, - "meta_#{BigBlueHelper::META_TOKEN}": name - } - - #meeting_options.merge!( - #{ "meta_room-id": options[:room_owner], - # "meta_meeting-name": options[:meeting_name]} - #) if options[:room_owner] - - # Send the create request. - begin - bbb.create_meeting(name, meeting_id, create_options) - rescue BigBlueButton::BigBlueButtonException => exc - puts "BigBlueButton failed on create: #{exc.key}: #{exc.message}" - end - - # Get the meeting info. - #bbb_meeting_info = bbb.get_meeting_info(meeting_id, nil) - - meeting_id - end - - # Generates a URL to join a BigBlueButton session. - def join_url(meeting_id, username, options = {}) - options[:meeting_recorded] ||= false - options[:user_is_moderator] ||= false - options[:wait_for_moderator] ||= false - options[:meeting_logout_url] ||= nil - options[:meeting_name] ||= name - options[:room_owner] ||= nil - options[:moderator_message] ||= '' - - return call_invalid_res if !bbb - - # Get the meeting info. - meeting_info = bbb.get_meeting_info(meeting_id, nil) - - # Determine the password to use when joining. - password = if options[:user_is_moderator] - meeting_info[:moderatorPW] - else - meeting_info[:attendeePW] - end - - # Generate the join URL. - bbb.join_meeting_url(meeting_id, username, password) - end - - # Generates a URL to join a BigBlueButton session. - def join_url_old(meeting_token, full_name, options={}) - options[:meeting_recorded] ||= false - options[:user_is_moderator] ||= false - options[:wait_for_moderator] ||= false - options[:meeting_logout_url] ||= nil - options[:meeting_name] ||= meeting_token - options[:room_owner] ||= nil - options[:moderator_message] ||= '' - - return call_invalid_res if !bbb - - meeting_id = bbb_meeting_id(meeting_token) - - unless meeting_is_running?(meeting_id) - # Need to create the meeting on the BigBlueButton server. - create_options = { - record: options[:meeting_recorded].to_s, - logoutURL: options[:meeting_logout_url] || request.base_url, - moderatorPW: random_password(12), - attendeePW: random_password(12), - moderatorOnlyMessage: options[:moderator_message], - "meta_#{BigBlueHelper::META_LISTED}": false, - "meta_#{BigBlueHelper::META_TOKEN}": meeting_token - } - - #meeting_options.merge!( - #{ "meta_room-id": options[:room_owner], - # "meta_meeting-name": options[:meeting_name]} - #) if options[:room_owner] - - # Send the create request. - begin - bbb.create_meeting(options[:meeting_name], meeting_id, create_options) - rescue BigBlueButton::BigBlueButtonException => exc - puts "BigBlueButton failed on create: #{exc.key}: #{exc.message}" - end - - # Get the meeting info. - bbb_meeting_info = bbb.get_meeting_info(meeting_id, nil) - end - - # Determine the password to use when joining. - password = if options[:user_is_moderator] - bbb_meeting_info[:moderatorPW] - else - bbb_meeting_info[:attendeePW] - end - - # Generate the join URL. - bbb.join_meeting_url(meeting_id, full_name, password) - end -end \ No newline at end of file diff --git a/app/models/meeting.rb b/app/models/meeting.rb index 20728330..6e3112e0 100644 --- a/app/models/meeting.rb +++ b/app/models/meeting.rb @@ -2,6 +2,8 @@ class Meeting < ApplicationRecord before_create :generate_meeting_id + validates :name, presence: true + belongs_to :room # Creates a meeting on the BigBlueButton server. @@ -11,9 +13,7 @@ class Meeting < ApplicationRecord logoutURL: options[:meeting_logout_url] || '', moderatorPW: random_password(12), attendeePW: random_password(12), - moderatorOnlyMessage: options[:moderator_message], - "meta_#{BigBlueHelper::META_LISTED}": false, - "meta_#{BigBlueHelper::META_TOKEN}": name + moderatorOnlyMessage: options[:moderator_message] } #meeting_options.merge!( diff --git a/app/models/room.rb b/app/models/room.rb index 8f61c204..f27a4d35 100644 --- a/app/models/room.rb +++ b/app/models/room.rb @@ -11,7 +11,10 @@ class Room < ApplicationRecord private + # Generates a uid for the room. def set_uid - self.uid = Digest::SHA1.hexdigest(user.uid + user.provider + user.username)[0..12] + digest = user.id.to_s + user.provider + user.username + digest += user.uid unless user.uid.nil? + self.uid = Digest::SHA1.hexdigest(digest)[0..12] end end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb index 97c737a9..febb7f62 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,7 +1,23 @@ class User < ApplicationRecord + after_create :initialize_room + before_save { email.downcase! } + has_one :room + validates :name, length: { maximum: 24 }, presence: true + validates :username, presence: true + validates :provider, presence: true + validates :email, length: { maximum: 60 }, presence: true, + uniqueness: { case_sensitive: false }, + format: {with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i } + + validates :password, length: { minimum: 6 }, allow_nil: true + + # We don't want to run the validations because they require a user + # to have a password. Users who authenticate via omniauth won't. + has_secure_password(validations: false) + class << self # Generates a user from omniauth. @@ -10,10 +26,6 @@ class User < ApplicationRecord user.name = send("#{auth['provider']}_name", auth) user.username = send("#{auth['provider']}_username", auth) user.email = send("#{auth['provider']}_email", auth) - #user.token = auth['credentials']['token'] - - # Create a room for the user if they don't have one. - user.room = Room.create unless user.room user.save! user @@ -48,4 +60,11 @@ class User < ApplicationRecord end + private + + # Initializes a room for the user. + def initialize_room + self.room = Room.new + self.save! + end end diff --git a/app/views/rooms/index.html.erb b/app/views/rooms/index.html.erb index 22002561..8ddb1fe1 100644 --- a/app/views/rooms/index.html.erb +++ b/app/views/rooms/index.html.erb @@ -19,4 +19,4 @@ <% end %>
-<%= link_to 'Logout', user_logout_path %> \ No newline at end of file +<%= link_to 'Logout', logout_path %> \ No newline at end of file diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb index 369999a5..7b867457 100644 --- a/app/views/sessions/new.html.erb +++ b/app/views/sessions/new.html.erb @@ -24,6 +24,21 @@ <% end %> <% end %> +

or...

+ + <%= form_for(:session, url: login_path) do |f| %> +
+ <%= f.label :email, "Email Address" %> + <%= f.text_field :email %> +
+
+ <%= f.label :password %> + <%= f.password_field :password %> +
+
+ <%= f.submit "Login", class: "btn white-text light-green" %> + <%= link_to "Don't have an account? Sign up!", signup_path %> + <% end %> <% end %> diff --git a/app/views/shared/_signup.html.erb b/app/views/shared/_signup.html.erb index 4dc4a501..b10a25f4 100644 --- a/app/views/shared/_signup.html.erb +++ b/app/views/shared/_signup.html.erb @@ -6,12 +6,12 @@ <% else %> <%= link_to(t('return_to_room'), room_path(current_user)) %> <% end %> -
<%= link_to t('logout'), user_logout_url %>
+
<%= link_to t('logout'), logout_url %>
<% else %>
<%= t('login_description') %> -
<%= link_to t('login'), user_login_url %>
+
<%= link_to t('login'), login_url %>
<% end %> \ No newline at end of file diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb new file mode 100644 index 00000000..e69de29b diff --git a/config/routes.rb b/config/routes.rb index 97764e55..cf61d8e2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -9,10 +9,20 @@ Rails.application.routes.draw do end end - get '/login', to: 'sessions#new', as: :user_login - get '/logout', to: 'sessions#destroy', as: :user_logout + get '/signup', to: 'users#new' + post '/signup', to: 'users#create' - match '/auth/:provider/callback', to: 'sessions#create', via: [:get, :post] + # Login to Greenlight. + get '/login', to: 'sessions#new' + + # Handles login of :greenlight provider account. + post '/login', to: 'sessions#create', as: :create_session + + # Log the user out of the session. + get '/logout', to: 'sessions#destroy' + + # Handles Omniauth authentication. + match '/auth/:provider/callback', to: 'sessions#omniauth_session', via: [:get, :post], as: :omniauth_session get '/auth/failure', to: 'sessions#fail' root to: 'main#index' diff --git a/db/migrate/20180504131648_create_users.rb b/db/migrate/20180504131648_create_users.rb index 0aeefa5e..e03d7742 100644 --- a/db/migrate/20180504131648_create_users.rb +++ b/db/migrate/20180504131648_create_users.rb @@ -1,11 +1,12 @@ class CreateUsers < ActiveRecord::Migration[5.0] def change create_table :users do |t| - t.string :provider, null: false - t.string :uid, null: false + t.string :provider + t.string :uid t.string :name t.string :username t.string :email + t.string :password_digest, index: { unique: true } t.timestamps end diff --git a/db/migrate/20180504131713_create_meetings.rb b/db/migrate/20180504131713_create_meetings.rb index 416f3b0e..6acccce5 100644 --- a/db/migrate/20180504131713_create_meetings.rb +++ b/db/migrate/20180504131713_create_meetings.rb @@ -2,7 +2,7 @@ class CreateMeetings < ActiveRecord::Migration[5.0] def change create_table :meetings do |t| t.belongs_to :room, index: true - t.string :name, null: false + t.string :name, index: true t.string :uid, index: true t.timestamps diff --git a/db/schema.rb b/db/schema.rb index 0a80dd18..2fb5b1f1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -14,10 +14,11 @@ ActiveRecord::Schema.define(version: 20180504131713) do create_table "meetings", force: :cascade do |t| t.integer "room_id" - t.string "name", null: false + t.string "name" t.string "uid" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.index ["name"], name: "index_meetings_on_name" t.index ["room_id"], name: "index_meetings_on_room_id" t.index ["uid"], name: "index_meetings_on_uid" end @@ -32,13 +33,15 @@ ActiveRecord::Schema.define(version: 20180504131713) do end create_table "users", force: :cascade do |t| - t.string "provider", null: false - t.string "uid", null: false + t.string "provider" + t.string "uid" t.string "name" t.string "username" t.string "email" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.string "password_digest" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["password_digest"], name: "index_users_on_password_digest", unique: true end end diff --git a/db/seeds.rb b/db/seeds.rb index 1beea2ac..a50361c9 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -5,3 +5,13 @@ # # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) # Character.create(name: 'Luke', movie: movies.first) + +User.create( + provider: "greenlight", + name: "Test User", + uid: "someuid", + username: "testuser", + email: "test@user.com", + password: "test", + password_confirmation: "test", +) \ No newline at end of file diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb index 6135ce6a..20386d58 100644 --- a/test/controllers/sessions_controller_test.rb +++ b/test/controllers/sessions_controller_test.rb @@ -1,7 +1,63 @@ require 'test_helper' class SessionsControllerTest < ActionDispatch::IntegrationTest - # test "the truth" do - # assert true - # end + + def setup + @user = User.new( + name: "Example User", + username: "Username", + provider: "greenlight", + email: "user@example.com", + password: "example", + password_confirmation: "example" + ) + + @user.save! + end + + test 'can signin with greenlight account.' do + post create_session_path, params: {session: {email: @user.email, password: @user.password}} + + assert_redirected_to room_path(@user.room.uid) + assert @user.id, session[:user_id] + end + + test 'can signup for greenlight account.' do + + end + + test 'can signup/login with omniauth.' do + email = "omniauth@test.com" + uid = "123456789" + + OmniAuth.config.add_mock(:twitter, { + provider: "twitter", + uid: uid, + info: { + email: email, + name: "Omni User", + nickname: "username" + } + }) + + get "/auth/twitter" + + request.env['omniauth.auth'] = OmniAuth.config.mock_auth[:twitter] + get omniauth_session_path(provider: "twitter") + + user = User.find_by(email: email, uid: uid) + + assert_not_nil user + assert_redirected_to room_path(user.room.uid) + assert @user.id, session[:user_id] + end + + test 'can logout.' do + post create_session_path, params: {session: {email: @user.email, password: @user.password}} + assert @user.id, session[:user_id] + + get logout_path + assert_not_equal @user.id, session[:user_id] + end + end diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb index 6c3da770..3a4935ff 100644 --- a/test/controllers/users_controller_test.rb +++ b/test/controllers/users_controller_test.rb @@ -1,7 +1,5 @@ require 'test_helper' class UsersControllerTest < ActionDispatch::IntegrationTest - # test "the truth" do - # assert true - # end + end diff --git a/test/models/meeting_test.rb b/test/models/meeting_test.rb index 063e6b91..b67e0fc7 100644 --- a/test/models/meeting_test.rb +++ b/test/models/meeting_test.rb @@ -1,7 +1,32 @@ require 'test_helper' class MeetingTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end + + def setup + @user = User.new( + name: "Example User", + username: "Username", + provider: "greenlight", + email: "user@example.com", + password: "example", + password_confirmation: "example" + ) + + @room = Room.new(user: @user) + + @meeting = Meeting.new( + name: "Test Meeting", + room: @room + ) + end + + test "name should be present." do + @meeting.name = nil + assert_not @meeting.valid? + end + + test "should set uid on creation." do + @meeting.save + assert @meeting.uid + end end diff --git a/test/models/room_test.rb b/test/models/room_test.rb index 2f0b89f4..f671024d 100644 --- a/test/models/room_test.rb +++ b/test/models/room_test.rb @@ -1,7 +1,39 @@ require 'test_helper' class RoomTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end + + def setup + @user = User.new( + name: "Example User", + username: "Username", + provider: "greenlight", + email: "user@example.com", + password: "example", + password_confirmation: "example" + ) + + @room = Room.new( + user: @user + ) + end + + test "#owned_by? should identify correct owner." do + assert @room.owned_by?(@user) + end + + test "#owned_by? should identify incorrect owner." do + diff_user = User.new( + name: "Different User", + username: "Diffname", + provider: "greenlight", + email: "diff@example.com", + ) + + assert_not @room.owned_by?(diff_user) + end + + test "should set uid on creation." do + @room.save + assert @room.uid + end end diff --git a/test/models/user_test.rb b/test/models/user_test.rb index 82f61e01..5a612194 100644 --- a/test/models/user_test.rb +++ b/test/models/user_test.rb @@ -1,7 +1,120 @@ require 'test_helper' class UserTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end + + def setup + @user = User.new( + name: "Example User", + username: "Username", + provider: "greenlight", + email: "user@example.com", + password: "example", + password_confirmation: "example" + ) + end + + test "should be valid." do + assert @user.valid? + end + + test "name should be present." do + @user.name = nil + assert_not @user.valid? + end + + test "email should be present." do + @user.email = nil + assert_not @user.valid? + end + + test "username should be present." do + @user.username = nil + assert_not @user.valid? + end + + test "provider should be present." do + @user.provider = nil + assert_not @user.valid? + end + + test "should allow nil uid." do + @user.uid = nil + assert @user.valid? + end + + test "should allow nil password." do + @user.password = @user.password_confirmation = nil + assert @user.valid? + end + + test "should create user from omniauth" do + auth = { + "uid" => "123456789", + "provider" => "twitter", + "info" => { + "name" => "Test Name", + "nickname" => "username", + "email" => "test@example.com" + } + } + + assert_difference 'User.count' do + User.from_omniauth(auth) + end + + user = User.find_by(uid: auth["uid"], provider: auth["provider"]) + + assert user.username, auth["info"]["nickname"] + assert user.name, auth["info"]["name"] + end + + test "email addresses should be saved as lower-case." do + mixed_case = "ExAmPlE@eXaMpLe.CoM" + @user.email = mixed_case + @user.save + assert_equal mixed_case.downcase, @user.email + end + + test "email validation should reject invalid addresses." do + invalid_addresses = %w[user@example,com user_at_foo.org user.name@example. foo@bar_baz.com foo@bar+baz.com] + invalid_addresses.each do |invalid_address| + @user.email = invalid_address + assert_not @user.valid?, "#{invalid_address.inspect} should be invalid." + end + end + + test "email should be unique." do + duplicate_user = @user.dup + duplicate_user.email = @user.email.upcase + @user.save + assert_not duplicate_user.valid? + end + + test "name should not be too long." do + @user.name = "a" * 25 + assert_not @user.valid? + end + + test "email should not be too long." do + @user.email = "a" * 50 + "@example.com" + assert_not @user.valid? + end + + test "password should have a minimum length." do + @user.password = @user.password_confirmation = "a" * 5 + assert_not @user.valid? + end + + test "should authenticate on valid password." do + assert_not_equal @user.authenticate('example'), false + end + + test "should not authenticate on invalid password." do + assert_not @user.authenticate('incorrect') + end + + test "should create room when saved." do + @user.save + assert @user.room + end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 92e39b2d..344a2be8 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -6,5 +6,5 @@ class ActiveSupport::TestCase # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. fixtures :all - # Add more helper methods to be used by all tests here... -end + OmniAuth.config.test_mode = true +end \ No newline at end of file