Merge pull request #1216 from basecamp/flavorjones/local-authentication
Standalone authentication: step 1
This commit is contained in:
@@ -71,7 +71,7 @@ group :test do
|
||||
end
|
||||
|
||||
require_relative "lib/bootstrap"
|
||||
unless Bootstrap.local_authentication?
|
||||
unless Bootstrap.oss_config?
|
||||
eval_gemfile "gems/fizzy-saas/Gemfile"
|
||||
gem "fizzy-saas", path: "gems/fizzy-saas"
|
||||
end
|
||||
|
||||
@@ -37,7 +37,9 @@ module Authentication
|
||||
end
|
||||
|
||||
def require_tenant
|
||||
ApplicationRecord.current_tenant.present? || request_authentication
|
||||
unless ApplicationRecord.current_tenant.present?
|
||||
render "sessions/login_menu"
|
||||
end
|
||||
end
|
||||
|
||||
def require_authentication
|
||||
|
||||
@@ -4,7 +4,7 @@ class Account < ApplicationRecord
|
||||
has_many_attached :uploads
|
||||
|
||||
def slug
|
||||
"/#{tenant_id}"
|
||||
"/#{tenant}"
|
||||
end
|
||||
|
||||
def setup_basic_template
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
<%= tenanted_action_cable_meta_tag %>
|
||||
<%= yield :head %>
|
||||
|
||||
<link rel="manifest" href="<%= pwa_manifest_path(format: :json) %>">
|
||||
<% if ApplicationRecord.current_tenant %>
|
||||
<link rel="manifest" href="<%= pwa_manifest_path(format: :json) %>">
|
||||
<% end %>
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
<link rel="icon" href="/favicon.png" type="image/png">
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<% cache ApplicationRecord.tenants do %>
|
||||
<h2>We're migrating Fizzy authentication</h2>
|
||||
<p>
|
||||
While we migrate Fizzy away from Launchpad and 37id, please login at one of the following URLs:
|
||||
|
||||
<ul style="list-style-type: none;">
|
||||
<% ApplicationRecord.with_each_tenant do |tenant| %>
|
||||
<li><%= link_to Account.sole.name, root_url(script_name: Account.sole.slug) %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
Mike should have sent you your temporary password by now. If you haven't received it, please ping him!
|
||||
</p>
|
||||
<% end %>
|
||||
@@ -1,12 +1,10 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
echo "Login with david@37signals.com / secret123456 to:"
|
||||
bin/rails runner - <<EOF
|
||||
puts "Login with david@37signals.com / secret123456 to:"
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
next unless tenant =~ /\A\d+\z/
|
||||
if Account.sole.tenant_id
|
||||
puts " - #{Account.sole.name}: http://fizzy.localhost:3006/#{Account.sole.tenant_id}"
|
||||
end
|
||||
puts " - #{Account.sole.name}: http://fizzy.localhost:3006#{Account.sole.slug}"
|
||||
end
|
||||
EOF
|
||||
|
||||
@@ -14,8 +12,8 @@ if [ -f tmp/solid-queue.txt ]; then
|
||||
export SOLID_QUEUE_IN_PUMA=1
|
||||
fi
|
||||
|
||||
if [ -f tmp/local-auth.txt ]; then
|
||||
export LOCAL_AUTHENTICATION=1
|
||||
if [ -f tmp/oss-config.txt ]; then
|
||||
export OSS_CONFIG=1
|
||||
fi
|
||||
|
||||
exec ./bin/rails server -p 3006
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env ruby
|
||||
require_relative "../lib/bootstrap"
|
||||
if !Bootstrap.local_authentication?
|
||||
if !Bootstrap.oss_config?
|
||||
# default from rails/test_unit/runner.rb but adding the saas gem test files
|
||||
ENV["DEFAULT_TEST"] = "{gems/fizzy-saas/,}test/**/*_test.rb"
|
||||
ENV["DEFAULT_TEST_EXCLUDE"] = "{gems/fizzy-saas/,}test/{system,dummy,fixtures}/**/*_test.rb"
|
||||
|
||||
@@ -11,7 +11,7 @@ system("bundle config set --local auto_install true")
|
||||
system("bundle check") || system!("bundle install")
|
||||
|
||||
require_relative "../lib/bootstrap"
|
||||
load File.expand_path("../gems/fizzy-saas/bin/setup", __dir__) unless Bootstrap.local_authentication?
|
||||
load File.expand_path("../gems/fizzy-saas/bin/setup", __dir__) unless Bootstrap.oss_config?
|
||||
|
||||
puts "\n== Preparing database =="
|
||||
if ARGV.include?("--reset")
|
||||
|
||||
+4
-4
@@ -9,10 +9,10 @@ CI.run do
|
||||
step "Security: Importmap audit", "bin/importmap audit"
|
||||
step "Security: Brakeman audit", "bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error"
|
||||
|
||||
step "Tests: Rails with 37id auth", "bin/rails test"
|
||||
step "Tests: Rails with local auth", "LOCAL_AUTHENTICATION=1 bin/rails test"
|
||||
step "Tests: 37id", "bin/rails 37id:test:units"
|
||||
step "Tests: System", "bin/rails test:system"
|
||||
step "Tests: Rails: SaaS config", "bin/rails test"
|
||||
step "Tests: Rails: OSS config", "OSS_CONFIG=1 bin/rails test"
|
||||
step "Tests: 37id", "bin/rails 37id:test:units"
|
||||
step "Tests: System", "bin/rails test:system"
|
||||
|
||||
if success?
|
||||
step "Signoff: All systems go. Ready for merge and deploy.", "gh signoff"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
require "bootstrap"
|
||||
Rails.application.config.x.local_authentication = Bootstrap.local_authentication?
|
||||
Rails.application.config.x.oss_config = Bootstrap.oss_config?
|
||||
|
||||
+1
-1
@@ -221,7 +221,7 @@ Rails.application.routes.draw do
|
||||
|
||||
root "events#index"
|
||||
|
||||
unless Rails.application.config.x.local_authentication
|
||||
unless Rails.application.config.x.oss_config
|
||||
mount Fizzy::Saas::Engine, at: "/", as: "saas"
|
||||
end
|
||||
|
||||
|
||||
+11
-40
@@ -13,7 +13,7 @@ end
|
||||
def create_tenant(signal_account_name, bare: false)
|
||||
if bare
|
||||
tenant_id = Digest::SHA256.hexdigest(signal_account_name)[0..8].to_i(16)
|
||||
elsif Rails.application.config.x.local_authentication
|
||||
elsif Rails.application.config.x.oss_config
|
||||
tenant_id = ActiveRecord::FixtureSet.identify signal_account_name
|
||||
else
|
||||
signal_account = SignalId::Account.find_by_product_and_name!("fizzy", signal_account_name)
|
||||
@@ -22,7 +22,7 @@ def create_tenant(signal_account_name, bare: false)
|
||||
|
||||
ApplicationRecord.destroy_tenant tenant_id
|
||||
ApplicationRecord.create_tenant(tenant_id) do
|
||||
account = if bare || Rails.application.config.x.local_authentication
|
||||
account = if bare || Rails.application.config.x.oss_config
|
||||
Account.create(name: signal_account_name, tenant_id: tenant_id).tap do
|
||||
User.create!(
|
||||
name: "David Heinemeier Hansson",
|
||||
@@ -40,45 +40,16 @@ def create_tenant(signal_account_name, bare: false)
|
||||
end
|
||||
|
||||
def find_or_create_user(full_name, email_address)
|
||||
if Rails.application.config.x.local_authentication
|
||||
if user = User.find_by(email_address: email_address)
|
||||
user.password = "secret123456"
|
||||
user.save!
|
||||
user
|
||||
else
|
||||
User.create!(
|
||||
name: full_name,
|
||||
email_address: email_address,
|
||||
password: "secret123456"
|
||||
)
|
||||
end
|
||||
if user = User.find_by(email_address: email_address)
|
||||
user.password = "secret123456"
|
||||
user.save!
|
||||
user
|
||||
else
|
||||
SignalId::Database.on_master do
|
||||
unless signal_identity = SignalId::Identity.find_by_email_address(email_address)
|
||||
signal_identity = SignalId::Identity.create!(
|
||||
name: full_name,
|
||||
email_address: email_address,
|
||||
username: email_address,
|
||||
password: "secret123456"
|
||||
)
|
||||
end
|
||||
|
||||
signal_account = Account.sole.external_account
|
||||
signal_user = SignalId::User.find_or_create_by!(identity: signal_identity, account: signal_account)
|
||||
|
||||
if user = User.find_by(external_user_id: signal_user.id)
|
||||
user.password = "secret123456"
|
||||
user.save!
|
||||
user
|
||||
else
|
||||
User.create!(
|
||||
external_user_id: signal_user.id,
|
||||
name: signal_identity.name,
|
||||
email_address: signal_identity.email_address,
|
||||
password: "secret123456"
|
||||
)
|
||||
end
|
||||
end
|
||||
User.create!(
|
||||
name: full_name,
|
||||
email_address: email_address,
|
||||
password: "secret123456"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
module Sessions
|
||||
module SignalSessions
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :require_local_auth, only: %i[ new create ]
|
||||
end
|
||||
|
||||
private
|
||||
def require_local_auth
|
||||
head :forbidden
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,23 +0,0 @@
|
||||
class Sessions::LaunchpadController < ApplicationController
|
||||
require_unauthenticated_access
|
||||
|
||||
before_action :require_sig
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
def update
|
||||
user = Account.sole.external_account.authenticate(sig: @sig).try(:peer)
|
||||
if user.present? && user.active?
|
||||
start_new_session_for user
|
||||
redirect_to after_authentication_url
|
||||
else
|
||||
render plain: "Authentication failed. This is probably a bug.", status: :unauthorized
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def require_sig
|
||||
@sig = params.expect(:sig)
|
||||
end
|
||||
end
|
||||
@@ -1,15 +0,0 @@
|
||||
module LoginHelper
|
||||
module SignalLogin
|
||||
def login_url
|
||||
if ApplicationRecord.current_tenant
|
||||
Launchpad.login_url(product: true, account: Account.sole)
|
||||
else
|
||||
Launchpad.login_url(product: true)
|
||||
end
|
||||
end
|
||||
|
||||
def logout_url
|
||||
Launchpad.logout_url
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -20,9 +20,8 @@ module Account::SignalAccount
|
||||
User.create!(
|
||||
name: account.external_account.owner.name,
|
||||
email_address: account.external_account.owner.email_address,
|
||||
external_user_id: account.external_account.owner.id,
|
||||
role: "admin",
|
||||
password: SecureRandom.hex(36) # TODO: remove password column?
|
||||
password: SecureRandom.hex(16)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
module User::SignalUser
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
prepended do
|
||||
# the external_user_id is the SignalId::User id
|
||||
belongs_to :external_user, dependent: :destroy, class_name: "SignalId::User", optional: true
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def find_by_signal_user_id(signal_user_id)
|
||||
find_by(external_user_id: signal_user_id)
|
||||
end
|
||||
end
|
||||
|
||||
def deactivate
|
||||
super
|
||||
SignalId::Database.on_master { external_user&.destroy }
|
||||
end
|
||||
end
|
||||
@@ -1,10 +1,4 @@
|
||||
Fizzy::Saas::Engine.routes.draw do
|
||||
resource :session do
|
||||
scope module: "sessions" do
|
||||
resource :launchpad, only: %i[ show update ], controller: "launchpad"
|
||||
end
|
||||
end
|
||||
|
||||
namespace :signup do
|
||||
get "/" => "accounts#new"
|
||||
resources :accounts, only: %i[ new create ]
|
||||
|
||||
@@ -5,10 +5,7 @@ module Fizzy
|
||||
class Engine < ::Rails::Engine
|
||||
# extend application models
|
||||
config.to_prepare do
|
||||
User.prepend User::SignalUser
|
||||
Account.prepend Account::SignalAccount
|
||||
LoginHelper.prepend LoginHelper::SignalLogin
|
||||
SessionsController.include Sessions::SignalSessions
|
||||
end
|
||||
|
||||
# moved from config/initializers/queenbee.rb
|
||||
@@ -39,7 +36,6 @@ module Fizzy
|
||||
|
||||
silence_warnings do
|
||||
SignalId::Account::Peer = Account
|
||||
SignalId::User::Peer = User
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
require "test_helper"
|
||||
|
||||
class ControllerAuthenticationTest < ActionDispatch::IntegrationTest
|
||||
test "access without an account slug redirects to launchpad" do
|
||||
integration_session.default_url_options[:script_name] = "" # no tenant
|
||||
|
||||
get cards_path
|
||||
|
||||
assert_redirected_to Launchpad.login_url(product: true)
|
||||
end
|
||||
|
||||
test "access with an account slug but no session redirects to launchpad" do
|
||||
get cards_path
|
||||
|
||||
assert_redirected_to Launchpad.login_url(product: true, account: Account.sole)
|
||||
end
|
||||
end
|
||||
@@ -1,38 +0,0 @@
|
||||
require "test_helper"
|
||||
|
||||
class Sessions::LaunchpadControllerTest < ActionDispatch::IntegrationTest
|
||||
test "show renders when not signed in" do
|
||||
get saas.session_launchpad_path(params: { sig: "test-sig" })
|
||||
|
||||
assert_response :success
|
||||
|
||||
assert_select "form input#sig" do |node|
|
||||
assert_equal node.length, 1
|
||||
assert_equal node.first["value"], "test-sig"
|
||||
end
|
||||
end
|
||||
|
||||
test "create establishes a session when the sig is valid" do
|
||||
user = users(:david)
|
||||
|
||||
put saas.session_launchpad_path(params: { sig: user.external_user.perishable_signature })
|
||||
|
||||
assert_redirected_to root_url
|
||||
assert parsed_cookies.signed[:session_token]
|
||||
end
|
||||
|
||||
test "create checks user.active?" do
|
||||
user = users(:david)
|
||||
user.update! active: false
|
||||
|
||||
put saas.session_launchpad_path(params: { sig: user.external_user.perishable_signature })
|
||||
|
||||
assert_response :unauthorized
|
||||
end
|
||||
|
||||
test "returns 401 when the sig is invalid" do
|
||||
put saas.session_launchpad_path(params: { sig: "invalid" })
|
||||
|
||||
assert_response :unauthorized
|
||||
end
|
||||
end
|
||||
@@ -1,24 +0,0 @@
|
||||
require "test_helper"
|
||||
|
||||
class SessionsControllerTest < ActionDispatch::IntegrationTest
|
||||
test "destroy" do
|
||||
sign_in_as :kevin
|
||||
|
||||
delete session_path
|
||||
|
||||
assert_redirected_to Launchpad.logout_url
|
||||
assert_not cookies[:session_token].present?
|
||||
end
|
||||
|
||||
test "new" do
|
||||
get new_session_path
|
||||
|
||||
assert_response :forbidden
|
||||
end
|
||||
|
||||
test "create" do
|
||||
post session_path, params: { email_address: "david@37signals.com", password: "secret123456" }
|
||||
|
||||
assert_response :forbidden
|
||||
end
|
||||
end
|
||||
@@ -18,7 +18,7 @@ class Account::SignalAccountTest < ActiveSupport::TestCase
|
||||
assert_equal @account, @account.external_account.peer
|
||||
end
|
||||
|
||||
test ".create_with_admin_user creates a new local account and user peers" do
|
||||
test ".create_with_admin_user creates a new local account" do
|
||||
ApplicationRecord.create_tenant("account-create-with-dependents") do
|
||||
signal_account = signal_accounts(:honcho_fizzy)
|
||||
account = Account.create_with_admin_user(tenant_id: signal_account.queenbee_id)
|
||||
@@ -32,11 +32,9 @@ class Account::SignalAccountTest < ActiveSupport::TestCase
|
||||
|
||||
assert_equal 1, User.count
|
||||
User.first.tap do |user|
|
||||
assert signal_account.owner.name, user.name
|
||||
assert signal_account.owner.email_address, user.email_address
|
||||
assert signal_account.owner.id, user.external_user_id
|
||||
assert_equal signal_account.owner.name, user.name
|
||||
assert_equal signal_account.owner.email_address, user.email_address
|
||||
assert_equal "admin", user.role
|
||||
assert_equal user, signal_account.owner.peer
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -42,7 +42,6 @@ class SignupTest < ActiveSupport::TestCase
|
||||
|
||||
assert @signup.user
|
||||
assert @signup.user.persisted?
|
||||
assert_equal @signup.user.external_user, @signup.signal_account.owner
|
||||
|
||||
assert_equal @signup.queenbee_account.id.to_s, @signup.tenant_name
|
||||
assert_includes ApplicationRecord.tenants, @signup.tenant_name
|
||||
@@ -86,7 +85,6 @@ class SignupTest < ActiveSupport::TestCase
|
||||
|
||||
assert @signup.user
|
||||
assert @signup.user.persisted?
|
||||
assert_equal @signup.user.external_user, @signup.signal_account.owner
|
||||
|
||||
assert_equal @signup.queenbee_account.id.to_s, @signup.tenant_name
|
||||
assert_includes ApplicationRecord.tenants, @signup.tenant_name
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
require "test_helper"
|
||||
|
||||
class User::SignalUserTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@user = users(:david)
|
||||
end
|
||||
|
||||
test "belongs to a Signal::User" do
|
||||
assert_not_nil @user.external_user_id
|
||||
assert_equal signal_users("37s_fizzy_david"), @user.external_user
|
||||
end
|
||||
|
||||
test "peering" do
|
||||
assert_equal @user, @user.external_user.peer
|
||||
end
|
||||
|
||||
test "deactivate clears signal user" do
|
||||
users(:jz).deactivate
|
||||
|
||||
assert_nil users(:jz).reload.external_user
|
||||
end
|
||||
end
|
||||
@@ -4,10 +4,6 @@ require "queenbee/testing/mocks"
|
||||
module ActiveSupport
|
||||
class TestCase
|
||||
include SignalId::Testing
|
||||
|
||||
def saas_extension_sign_in_as(user)
|
||||
put saas.session_launchpad_path, params: { sig: user.external_user.perishable_signature }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
module Bootstrap
|
||||
def self.local_authentication?
|
||||
ENV.fetch("LOCAL_AUTHENTICATION", "") != "" || !File.directory?(File.expand_path("../gems/fizzy-saas", __dir__))
|
||||
def self.oss_config?
|
||||
ENV.fetch("OSS_CONFIG", "") != "" || !File.directory?(File.expand_path("../gems/fizzy-saas", __dir__))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
#
|
||||
# This script creates a new user that can be logged into via LOCAL_AUTHENTICATION, avoiding Launchpad/37id integration.
|
||||
# In order to login as this user, you must set the `LOCAL_AUTHENTICATION` environment variable when running the Rails server:
|
||||
#
|
||||
# LOCAL_AUTHENTICATION=1 bin/dev
|
||||
#
|
||||
|
||||
require_relative "../config/environment"
|
||||
|
||||
unless Rails.env.development?
|
||||
puts "ERROR: This script is intended to be run in development mode only."
|
||||
exit 1
|
||||
end
|
||||
|
||||
if ARGV.length < 2
|
||||
puts "Usage: #{$0} <email> <tenant>"
|
||||
exit 1
|
||||
end
|
||||
|
||||
email_address = ARGV[0]
|
||||
tenant = ARGV[1]
|
||||
|
||||
ApplicationRecord.with_tenant(tenant) do
|
||||
user = User.create!(
|
||||
name: email_address.split("@").first,
|
||||
email_address: email_address,
|
||||
password: "secret123456"
|
||||
)
|
||||
|
||||
puts "Created: "
|
||||
pp user
|
||||
end
|
||||
+14
-30
@@ -2,41 +2,25 @@
|
||||
|
||||
require_relative "../config/environment"
|
||||
|
||||
if ARGV.length < 2
|
||||
puts "Usage: #{$0} <email> <tenant>"
|
||||
if ARGV.length < 3
|
||||
puts "Usage: #{$0} <tenant> <email> <fullname>"
|
||||
exit 1
|
||||
end
|
||||
|
||||
email_address = ARGV[0]
|
||||
tenant = ARGV[1]
|
||||
tenant = ARGV.shift
|
||||
email_address = ARGV.shift
|
||||
name = ARGV.join(" ")
|
||||
|
||||
def confirm(noun)
|
||||
print "Is this the correct #{noun}? (y/n) "
|
||||
response = $stdin.gets.chomp.downcase
|
||||
exit 0 unless response == "y"
|
||||
puts
|
||||
end
|
||||
|
||||
signal_identity = SignalId::Identity.find_by!(email_address: email_address)
|
||||
pp signal_identity
|
||||
confirm "identity"
|
||||
|
||||
ApplicationRecord.with_tenant(tenant) do
|
||||
signal_account = Account.sole.external_account
|
||||
pp signal_account
|
||||
confirm "account"
|
||||
|
||||
SignalId::Database.on_master do
|
||||
signal_user = SignalId::User.create!(identity: signal_identity, account: signal_account)
|
||||
|
||||
user = User.create!(
|
||||
name: signal_user.name,
|
||||
email_address: signal_user.email_address,
|
||||
external_user_id: signal_user.id,
|
||||
password: SecureRandom.hex(36) # TODO: remove password column?
|
||||
)
|
||||
begin
|
||||
ApplicationRecord.with_tenant(tenant) do
|
||||
password = SecureRandom.hex(16)
|
||||
|
||||
user = User.create!(name:, email_address:, password:)
|
||||
puts "Created: "
|
||||
pp [ user, signal_user ]
|
||||
pp user
|
||||
|
||||
puts "Password is: #{password.inspect}"
|
||||
end
|
||||
rescue Exception => e
|
||||
puts "Failed with error: #{e.inspect}"
|
||||
end
|
||||
|
||||
@@ -34,7 +34,6 @@ ActiveRecord::Tenanted::DatabaseTasks.migrate_all
|
||||
|
||||
ApplicationRecord.with_tenant(signup.tenant_name) do |tenant|
|
||||
Account.sole.update! external_account: signup.signal_account
|
||||
User.first.update! external_user: signup.signal_account.owner
|
||||
|
||||
puts "\n\nLogin to http://launchpad.localhost:3011/ as #{signup.email_address} / #{signup.password}"
|
||||
end
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
#
|
||||
# set up a temporary password for all users
|
||||
# while we migrate away from Launchpad and 37id.
|
||||
#
|
||||
# the password is unguessable but generated based on the email address and a seed,
|
||||
# so each user will have the same password across all tenants.
|
||||
#
|
||||
require_relative "../../config/environment"
|
||||
|
||||
seed = ARGV[0] || "temporary-password-seed"
|
||||
puts "Using seed: #{seed.inspect}"
|
||||
|
||||
TENANTS = Hash.new
|
||||
USERS = Hash.new { |h, k| h[k] = Hash.new }
|
||||
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
TENANTS[tenant] = Account.sole.name
|
||||
|
||||
User.find_each do |user|
|
||||
putc "."
|
||||
next if user.system?
|
||||
|
||||
if user.external_user_id
|
||||
suser = SignalId::User.find_by_id(user.external_user_id)
|
||||
if suser && suser.email_address != user.email_address
|
||||
puts "\nWarning: fixing email address for user #{user.id} in tenant #{tenant}:"
|
||||
puts " local: #{user.email_address}"
|
||||
puts " signal: #{suser.email_address}"
|
||||
user.update! email_address: suser.email_address
|
||||
end
|
||||
end
|
||||
|
||||
password = Digest::SHA256.hexdigest("#{seed}-#{user.email_address}")[0..16]
|
||||
user.update! password: password
|
||||
|
||||
USERS[user.email_address][tenant] = password
|
||||
end
|
||||
end
|
||||
|
||||
puts
|
||||
|
||||
USERS.each do |email, hash|
|
||||
puts "\n#{email}:"
|
||||
puts " password: #{hash.first.last}"
|
||||
puts " fizzies:"
|
||||
hash.each do |tenant, _|
|
||||
url = Rails.application.routes.url_helpers.root_url(Rails.application.config.action_controller.default_url_options.merge(script_name: "/#{tenant}"))
|
||||
|
||||
puts " #{TENANTS[tenant]}: #{url}"
|
||||
end
|
||||
end
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require_relative "../config/environment"
|
||||
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
account = Account.sole
|
||||
signal_account = account.signal_account
|
||||
|
||||
signal_users = SignalId::User.where(account_id: signal_account.id)
|
||||
|
||||
signal_users.each do |signal_user|
|
||||
unless User.find_by(signal_user_id: signal_user.id)
|
||||
User.create!(
|
||||
name: signal_user.identity.name,
|
||||
email_address: signal_user.identity.email_address,
|
||||
signal_user_id: signal_user.id,
|
||||
password: SecureRandom.hex(36) # TODO: remove password column?
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,41 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
#
|
||||
# this is intended to copy a production database into beta, change the account id, and stitch the
|
||||
# user accounts together properly.
|
||||
#
|
||||
|
||||
require_relative "../config/environment"
|
||||
|
||||
ActiveRecord::Base.logger = Logger.new(File::NULL)
|
||||
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
puts "\n# tenant: #{tenant}"
|
||||
|
||||
signal_account = SignalId::Account.find_by!(queenbee_id: tenant)
|
||||
puts "Found signal account #{signal_account.inspect}"
|
||||
|
||||
account = Account.sole
|
||||
if account.tenant_id != tenant
|
||||
puts "setting account tenant_id to #{tenant}"
|
||||
account.update!(tenant_id: tenant, name: account.name + " (Beta)")
|
||||
end
|
||||
|
||||
User.find_each do |user|
|
||||
next if user.system? || user.external_user_id.nil?
|
||||
|
||||
signal_user = user.external_user
|
||||
next if signal_user.nil?
|
||||
next if signal_user.account == account.external_account
|
||||
|
||||
signal_identity = signal_user.identity
|
||||
pp signal_identity
|
||||
|
||||
SignalId::Database.on_master do
|
||||
signal_user = SignalId::User.find_or_create_by!(identity: signal_identity, account: signal_account)
|
||||
puts "Created signal user #{signal_user.inspect} for identity #{signal_identity.inspect}"
|
||||
user.external_user_id = signal_user.id
|
||||
user.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,20 +1,19 @@
|
||||
require "test_helper"
|
||||
|
||||
class ControllerAuthenticationTest < ActionDispatch::IntegrationTest
|
||||
if Rails.application.config.x.local_authentication
|
||||
test "access without an account slug redirects to new session" do
|
||||
integration_session.default_url_options[:script_name] = "" # no tenant
|
||||
test "access without an account slug redirects to new session" do
|
||||
integration_session.default_url_options[:script_name] = "" # no tenant
|
||||
|
||||
get cards_path
|
||||
get cards_path
|
||||
|
||||
assert_redirected_to new_session_path
|
||||
end
|
||||
assert_response :success
|
||||
assert_dom "h2", text: "We're migrating Fizzy authentication"
|
||||
end
|
||||
|
||||
test "access with an account slug but no session redirects to new session" do
|
||||
get cards_path
|
||||
test "access with an account slug but no session redirects to new session" do
|
||||
get cards_path
|
||||
|
||||
assert_redirected_to new_session_path
|
||||
end
|
||||
assert_redirected_to new_session_path
|
||||
end
|
||||
|
||||
test "access with an account slug and a session allows functional access" do
|
||||
|
||||
@@ -1,34 +1,32 @@
|
||||
require "test_helper"
|
||||
|
||||
class SessionsControllerTest < ActionDispatch::IntegrationTest
|
||||
if Rails.application.config.x.local_authentication
|
||||
test "destroy" do
|
||||
sign_in_as :kevin
|
||||
test "destroy" do
|
||||
sign_in_as :kevin
|
||||
|
||||
delete session_path
|
||||
delete session_path
|
||||
|
||||
assert_redirected_to new_session_path
|
||||
assert_not cookies[:session_token].present?
|
||||
end
|
||||
assert_redirected_to new_session_path
|
||||
assert_not cookies[:session_token].present?
|
||||
end
|
||||
|
||||
test "new" do
|
||||
get new_session_path
|
||||
test "new" do
|
||||
get new_session_path
|
||||
|
||||
assert_response :success
|
||||
end
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "create with valid credentials" do
|
||||
post session_path, params: { email_address: "david@37signals.com", password: "secret123456" }
|
||||
test "create with valid credentials" do
|
||||
post session_path, params: { email_address: "david@37signals.com", password: "secret123456" }
|
||||
|
||||
assert_redirected_to root_path
|
||||
assert cookies[:session_token].present?
|
||||
end
|
||||
assert_redirected_to root_path
|
||||
assert cookies[:session_token].present?
|
||||
end
|
||||
|
||||
test "create with invalid credentials" do
|
||||
post session_path, params: { email_address: "david@37signals.com", password: "wrong" }
|
||||
test "create with invalid credentials" do
|
||||
post session_path, params: { email_address: "david@37signals.com", password: "wrong" }
|
||||
|
||||
assert_redirected_to new_session_path
|
||||
assert_not cookies[:session_token].present?
|
||||
end
|
||||
assert_redirected_to new_session_path
|
||||
assert_not cookies[:session_token].present?
|
||||
end
|
||||
end
|
||||
|
||||
Vendored
-9
@@ -5,27 +5,18 @@ david:
|
||||
email_address: david@37signals.com
|
||||
password_digest: <%= digest %>
|
||||
role: member
|
||||
<% if User.reflect_on_association :external_user %>
|
||||
external_user: 37s_fizzy_david
|
||||
<% end %>
|
||||
|
||||
jz:
|
||||
name: JZ
|
||||
email_address: jz@37signals.com
|
||||
password_digest: <%= digest %>
|
||||
role: member
|
||||
<% if User.reflect_on_association :external_user %>
|
||||
external_user: 37s_fizzy_jzimdars
|
||||
<% end %>
|
||||
|
||||
kevin:
|
||||
name: Kevin
|
||||
email_address: kevin@37signals.com
|
||||
password_digest: <%= digest %>
|
||||
role: admin
|
||||
<% if User.reflect_on_association :external_user %>
|
||||
external_user: 37s_fizzy_kevin
|
||||
<% end %>
|
||||
|
||||
system:
|
||||
name: System
|
||||
|
||||
@@ -3,6 +3,6 @@ require "test_helper"
|
||||
class AccountTest < ActiveSupport::TestCase
|
||||
test "slug" do
|
||||
account = Account.sole
|
||||
assert_equal "/#{account.tenant_id}", account.slug
|
||||
assert_equal "/#{ApplicationRecord.current_tenant}", account.slug
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,30 +1,27 @@
|
||||
require "test_helper"
|
||||
|
||||
class PeriodHighlightTest < ActiveSupport::TestCase
|
||||
# Skipping when locally authenticating because the VCR cassettes would need to be re-recorded.
|
||||
unless Rails.application.config.x.local_authentication
|
||||
include VcrTestHelper
|
||||
include VcrTestHelper
|
||||
|
||||
setup do
|
||||
@user = users(:david)
|
||||
setup do
|
||||
@user = users(:david)
|
||||
end
|
||||
|
||||
test "generate period highlights" do
|
||||
period_highlights = assert_difference -> { PeriodHighlights.count }, 1 do
|
||||
PeriodHighlights.create_or_find_for(@user.collections, starts_at: 1.month.ago, duration: 2.months)
|
||||
end
|
||||
|
||||
test "generate period highlights" do
|
||||
period_highlights = assert_difference -> { PeriodHighlights.count }, 1 do
|
||||
PeriodHighlights.create_or_find_for(@user.collections, starts_at: 1.month.ago, duration: 2.months)
|
||||
end
|
||||
assert_match /logo/i, period_highlights.to_html
|
||||
end
|
||||
|
||||
assert_match /logo/i, period_highlights.to_html
|
||||
test "don't generate highlights for existing periods" do
|
||||
new_period_highlights = PeriodHighlights.create_or_find_for(@user.collections, starts_at: 1.month.ago, duration: 2.months)
|
||||
|
||||
existing_period_highlights = assert_no_difference -> { PeriodHighlights.count } do
|
||||
PeriodHighlights.create_or_find_for(@user.collections, starts_at: 1.month.ago, duration: 2.months)
|
||||
end
|
||||
|
||||
test "don't generate highlights for existing periods" do
|
||||
new_period_highlights = PeriodHighlights.create_or_find_for(@user.collections, starts_at: 1.month.ago, duration: 2.months)
|
||||
|
||||
existing_period_highlights = assert_no_difference -> { PeriodHighlights.count } do
|
||||
PeriodHighlights.create_or_find_for(@user.collections, starts_at: 1.month.ago, duration: 2.months)
|
||||
end
|
||||
|
||||
assert_equal new_period_highlights, existing_period_highlights
|
||||
end
|
||||
assert_equal new_period_highlights, existing_period_highlights
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,67 +1,64 @@
|
||||
require "test_helper"
|
||||
|
||||
class User::HighlightsTest < ActiveSupport::TestCase
|
||||
# Skipping when locally authenticating because the VCR cassettes would need to be re-recorded.
|
||||
unless Rails.application.config.x.local_authentication
|
||||
include VcrTestHelper
|
||||
include VcrTestHelper
|
||||
|
||||
setup do
|
||||
@user = users(:david)
|
||||
travel_to 1.week.ago + 2.days
|
||||
Current.session = sessions(:david)
|
||||
end
|
||||
setup do
|
||||
@user = users(:david)
|
||||
travel_to 1.week.ago + 2.days
|
||||
Current.session = sessions(:david)
|
||||
end
|
||||
|
||||
test "generate weekly highlights" do
|
||||
stub_const(PeriodHighlights::Period, :MIN_EVENTS_TO_BE_INTERESTING, 3) do
|
||||
period_highlights = assert_difference -> { PeriodHighlights.count }, 1 do
|
||||
@user.generate_weekly_highlights
|
||||
end
|
||||
|
||||
assert_match /logo/i, period_highlights.to_html
|
||||
end
|
||||
end
|
||||
|
||||
test "don't generate highlights for existing periods" do
|
||||
stub_const(PeriodHighlights::Period, :MIN_EVENTS_TO_BE_INTERESTING, 3) do
|
||||
new_period_highlights = @user.generate_weekly_highlights
|
||||
assert_not_nil new_period_highlights
|
||||
|
||||
existing_period_highlights = assert_no_difference -> { PeriodHighlights.count } do
|
||||
@user.generate_weekly_highlights
|
||||
end
|
||||
|
||||
assert_equal new_period_highlights, existing_period_highlights
|
||||
end
|
||||
end
|
||||
|
||||
test "periods respect user timezone for week boundaries" do
|
||||
@user.settings.update!(timezone_name: "America/New_York")
|
||||
|
||||
# Sunday Jan 7, 2024 at 2am EST (7am UTC) - this is Sunday in NYC
|
||||
sunday_in_nyc = Time.zone.parse("2024-01-07 07:00:00 UTC")
|
||||
|
||||
# Saturday Jan 6, 2024 at 11pm EST (Jan 7 4am UTC) - still Saturday in NYC but Sunday in UTC
|
||||
saturday_in_nyc = Time.zone.parse("2024-01-07 04:00:00 UTC")
|
||||
|
||||
# Event on Saturday evening in NYC (but Sunday in UTC)
|
||||
saturday_event = travel_to(saturday_in_nyc) { cards(:logo).track_event("Saturday event") }
|
||||
|
||||
# Events throughout the week starting Sunday in NYC
|
||||
7.times do |i|
|
||||
travel_to(sunday_in_nyc + i.days) { cards(:logo).track_event("Event #{i}") }
|
||||
test "generate weekly highlights" do
|
||||
stub_const(PeriodHighlights::Period, :MIN_EVENTS_TO_BE_INTERESTING, 3) do
|
||||
period_highlights = assert_difference -> { PeriodHighlights.count }, 1 do
|
||||
@user.generate_weekly_highlights
|
||||
end
|
||||
|
||||
wednesday = sunday_in_nyc + 3.days
|
||||
|
||||
# The period should start at Sunday in NYC timezone
|
||||
period_start = wednesday.in_time_zone("America/New_York").beginning_of_week(:sunday)
|
||||
period = PeriodHighlights::Period.new(@user.collections, starts_at: period_start, duration: 1.week)
|
||||
|
||||
# The Saturday event should NOT be included (it's in the previous week in NYC time)
|
||||
assert_not_includes period.events, saturday_event
|
||||
|
||||
# Should include 7 events from the current week
|
||||
assert_equal 7, period.events.count
|
||||
assert_match /logo/i, period_highlights.to_html
|
||||
end
|
||||
end
|
||||
|
||||
test "don't generate highlights for existing periods" do
|
||||
stub_const(PeriodHighlights::Period, :MIN_EVENTS_TO_BE_INTERESTING, 3) do
|
||||
new_period_highlights = @user.generate_weekly_highlights
|
||||
assert_not_nil new_period_highlights
|
||||
|
||||
existing_period_highlights = assert_no_difference -> { PeriodHighlights.count } do
|
||||
@user.generate_weekly_highlights
|
||||
end
|
||||
|
||||
assert_equal new_period_highlights, existing_period_highlights
|
||||
end
|
||||
end
|
||||
|
||||
test "periods respect user timezone for week boundaries" do
|
||||
@user.settings.update!(timezone_name: "America/New_York")
|
||||
|
||||
# Sunday Jan 7, 2024 at 2am EST (7am UTC) - this is Sunday in NYC
|
||||
sunday_in_nyc = Time.zone.parse("2024-01-07 07:00:00 UTC")
|
||||
|
||||
# Saturday Jan 6, 2024 at 11pm EST (Jan 7 4am UTC) - still Saturday in NYC but Sunday in UTC
|
||||
saturday_in_nyc = Time.zone.parse("2024-01-07 04:00:00 UTC")
|
||||
|
||||
# Event on Saturday evening in NYC (but Sunday in UTC)
|
||||
saturday_event = travel_to(saturday_in_nyc) { cards(:logo).track_event("Saturday event") }
|
||||
|
||||
# Events throughout the week starting Sunday in NYC
|
||||
7.times do |i|
|
||||
travel_to(sunday_in_nyc + i.days) { cards(:logo).track_event("Event #{i}") }
|
||||
end
|
||||
|
||||
wednesday = sunday_in_nyc + 3.days
|
||||
|
||||
# The period should start at Sunday in NYC timezone
|
||||
period_start = wednesday.in_time_zone("America/New_York").beginning_of_week(:sunday)
|
||||
period = PeriodHighlights::Period.new(@user.collections, starts_at: period_start, duration: 1.week)
|
||||
|
||||
# The Saturday event should NOT be included (it's in the previous week in NYC time)
|
||||
assert_not_includes period.events, saturday_event
|
||||
|
||||
# Should include 7 events from the current week
|
||||
assert_equal 7, period.events.count
|
||||
end
|
||||
end
|
||||
|
||||
+1
-1
@@ -65,6 +65,6 @@ RubyLLM.configure do |config|
|
||||
config.openai_api_key ||= "DUMMY-TEST-KEY" # Run tests with VCR without having to configure OpenAI API key locally.
|
||||
end
|
||||
|
||||
unless Rails.application.config.x.local_authentication
|
||||
unless Rails.application.config.x.oss_config
|
||||
load File.expand_path("../gems/fizzy-saas/test/test_helper.rb", __dir__)
|
||||
end
|
||||
|
||||
@@ -7,11 +7,7 @@ module SessionTestHelper
|
||||
cookies.delete :session_token
|
||||
user = users(user) unless user.is_a? User
|
||||
|
||||
if Rails.application.config.x.local_authentication
|
||||
post session_path, params: { email_address: user.email_address, password: "secret123456" }
|
||||
else
|
||||
saas_extension_sign_in_as(user)
|
||||
end
|
||||
post session_path, params: { email_address: user.email_address, password: "secret123456" }
|
||||
|
||||
cookie = cookies.get_cookie "session_token"
|
||||
assert_not_nil cookie, "Expected session_token cookie to be set after sign in"
|
||||
|
||||
Reference in New Issue
Block a user