Merge pull request #1232 from basecamp/flavorjones/local-authentication-step2
Standalone authentication: step 2, remove SignalId
This commit is contained in:
@@ -1,19 +1,3 @@
|
||||
GIT
|
||||
remote: https://github.com/basecamp/file_repository
|
||||
revision: 8c8d1594f3ec7e8c0938e6a7f7def521692344e4
|
||||
ref: 8c8d1594f3ec7e8c0938e6a7f7def521692344e4
|
||||
specs:
|
||||
file_repository (1.4.6)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/basecamp/portfolio
|
||||
revision: 86411a53d19a42ce44705a50ebe7c22828597991
|
||||
ref: 86411a53d19a42ce44705a50ebe7c22828597991
|
||||
specs:
|
||||
portfolio (5.2.0)
|
||||
activesupport
|
||||
file_repository (>= 1.4.4)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/basecamp/queenbee-plugin
|
||||
revision: eb01c697de1ad028afc65cc7d9b5345a7a8e849f
|
||||
@@ -24,16 +8,6 @@ GIT
|
||||
builder
|
||||
rexml
|
||||
|
||||
GIT
|
||||
remote: https://github.com/basecamp/queuety
|
||||
revision: bf7e2a552fb674533d3e092f06dbf9b5d1ebc753
|
||||
ref: bf7e2a552fb674533d3e092f06dbf9b5d1ebc753
|
||||
branch: rails4
|
||||
specs:
|
||||
queuety (4.0.2)
|
||||
activerecord
|
||||
service_concurrency_prevention
|
||||
|
||||
GIT
|
||||
remote: https://github.com/basecamp/rails-structured-logging
|
||||
revision: 76960cb5c15fc2b6b5f7542e05d7dcc031cef9e6
|
||||
@@ -42,31 +16,6 @@ GIT
|
||||
json
|
||||
rails (>= 6.0.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/basecamp/service_concurrency_prevention
|
||||
revision: 320da99431718615ed86f8723e8b9d3259c602a3
|
||||
ref: 320da99431718615ed86f8723e8b9d3259c602a3
|
||||
specs:
|
||||
service_concurrency_prevention (1.1.1)
|
||||
activesupport
|
||||
|
||||
GIT
|
||||
remote: https://github.com/basecamp/signal_id
|
||||
revision: d6c2a5ceed7bb57bf7dcdebdb439e7cd526fca09
|
||||
ref: d6c2a5ceed7bb57bf7dcdebdb439e7cd526fca09
|
||||
branch: rails4
|
||||
specs:
|
||||
signal_id (4.3.2)
|
||||
bcrypt-ruby (>= 3.0.0)
|
||||
google-id-token (>= 1.4.0)
|
||||
mocha
|
||||
multi_json
|
||||
portfolio (>= 4.0)
|
||||
pwned (~> 2.0)
|
||||
queuety (>= 3.0)
|
||||
rails (>= 4.0.2)
|
||||
rails-controller-testing
|
||||
|
||||
GIT
|
||||
remote: https://github.com/crmne/ruby_llm.git
|
||||
revision: 32611afc114c5f40696dd42f8a1eaee6b6b81330
|
||||
@@ -81,14 +30,6 @@ GIT
|
||||
marcel (~> 1.0)
|
||||
zeitwerk (~> 2)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/jeremy/mysql2.git
|
||||
revision: b5766f5d296745d6aed014c8c0b7d82ef149ade1
|
||||
ref: b5766f5d296745d6aed014c8c0b7d82ef149ade1
|
||||
branch: force_latin1_to_utf8
|
||||
specs:
|
||||
mysql2 (0.5.4.latin1utf8)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/rails/rails.git
|
||||
revision: d8795bd762ab2716b6e7fd07a403566b28159cdc
|
||||
@@ -206,7 +147,6 @@ PATH
|
||||
queenbee
|
||||
rails (>= 8.1.0.beta1)
|
||||
rails_structured_logging
|
||||
signal_id
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
@@ -251,8 +191,6 @@ GEM
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
base64 (0.3.0)
|
||||
bcrypt (3.1.20)
|
||||
bcrypt-ruby (3.1.5)
|
||||
bcrypt (>= 3.1.3)
|
||||
bcrypt_pbkdf (1.1.1)
|
||||
benchmark (0.4.1)
|
||||
bigdecimal (3.2.3)
|
||||
@@ -317,8 +255,6 @@ GEM
|
||||
addressable (>= 2.5.0)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
google-id-token (1.4.2)
|
||||
jwt (>= 1)
|
||||
hashdiff (1.2.1)
|
||||
i18n (1.14.7)
|
||||
concurrent-ruby (~> 1.0)
|
||||
@@ -391,7 +327,6 @@ GEM
|
||||
mocha (2.7.1)
|
||||
ruby2_keywords (>= 0.0.5)
|
||||
msgpack (1.8.0)
|
||||
multi_json (1.17.0)
|
||||
multipart-post (2.4.1)
|
||||
net-http (0.6.0)
|
||||
uri
|
||||
@@ -464,7 +399,6 @@ GEM
|
||||
public_suffix (6.0.2)
|
||||
puma (7.0.3)
|
||||
nio4r (~> 2.0)
|
||||
pwned (2.4.1)
|
||||
raabro (1.4.0)
|
||||
racc (1.8.1)
|
||||
rack (3.2.1)
|
||||
@@ -475,10 +409,6 @@ GEM
|
||||
rack (>= 1.3)
|
||||
rackup (2.2.1)
|
||||
rack (>= 3)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
actionview (>= 5.0.1.rc1)
|
||||
activesupport (>= 5.0.1.rc1)
|
||||
rails-dom-testing (2.3.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
@@ -670,7 +600,6 @@ DEPENDENCIES
|
||||
bundler-audit
|
||||
capybara
|
||||
debug
|
||||
file_repository (~> 1.4.5)!
|
||||
fizzy-saas!
|
||||
geared_pagination (~> 1.2)
|
||||
image_processing (~> 1.14)
|
||||
@@ -681,15 +610,12 @@ DEPENDENCIES
|
||||
lexxy
|
||||
mission_control-jobs
|
||||
mocha
|
||||
mysql2!
|
||||
net-http-persistent
|
||||
platform_agent
|
||||
portfolio (>= 4.6)!
|
||||
prometheus-client-mmap (~> 1.1)
|
||||
propshaft
|
||||
puma (>= 5.0)
|
||||
queenbee!
|
||||
queuety!
|
||||
rails!
|
||||
rails_structured_logging!
|
||||
redcarpet
|
||||
@@ -700,8 +626,6 @@ DEPENDENCIES
|
||||
selenium-webdriver
|
||||
sentry-rails
|
||||
sentry-ruby
|
||||
service_concurrency_prevention!
|
||||
signal_id!
|
||||
solid_cable (>= 3.0)
|
||||
solid_cache (~> 1.0)
|
||||
solid_queue (~> 1.1)
|
||||
|
||||
@@ -3,6 +3,21 @@ class Account < ApplicationRecord
|
||||
|
||||
has_many_attached :uploads
|
||||
|
||||
class << self
|
||||
def create_with_admin_user(tenant_id:, account_name:, owner_name:, owner_email:)
|
||||
account = create!(tenant_id:, name: account_name)
|
||||
|
||||
User.create!(
|
||||
name: owner_name,
|
||||
email_address: owner_email,
|
||||
role: "admin",
|
||||
password: SecureRandom.hex(16)
|
||||
)
|
||||
|
||||
account
|
||||
end
|
||||
end
|
||||
|
||||
def slug
|
||||
"/#{tenant}"
|
||||
end
|
||||
|
||||
@@ -15,9 +15,7 @@ load File.expand_path("../gems/fizzy-saas/bin/setup", __dir__) unless Bootstrap.
|
||||
|
||||
puts "\n== Preparing database =="
|
||||
if ARGV.include?("--reset")
|
||||
system! "bin/rails db:drop db:create db:schema:load db:prepare"
|
||||
system! "bin/rails db:fixtures:load" # load signal id fixtures before running fizzy seeds
|
||||
system! "bin/rails db:seed"
|
||||
system! "bin/rails db:drop db:create db:schema:load db:prepare db:seed"
|
||||
else
|
||||
system! "bin/rails db:prepare"
|
||||
end
|
||||
|
||||
@@ -11,7 +11,6 @@ CI.run do
|
||||
|
||||
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?
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class RemoveExternalUserIdFromUsers < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
remove_column :users, :external_user_id, :integer
|
||||
end
|
||||
end
|
||||
Generated
+1
-3
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.1].define(version: 2025_09_24_124737) do
|
||||
ActiveRecord::Schema[8.1].define(version: 2025_10_01_181713) do
|
||||
create_table "accesses", force: :cascade do |t|
|
||||
t.datetime "accessed_at"
|
||||
t.integer "collection_id", null: false
|
||||
@@ -451,13 +451,11 @@ ActiveRecord::Schema[8.1].define(version: 2025_09_24_124737) do
|
||||
t.boolean "active", default: true, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.string "email_address"
|
||||
t.integer "external_user_id"
|
||||
t.string "name", null: false
|
||||
t.string "password_digest"
|
||||
t.string "role", default: "member", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["email_address"], name: "index_users_on_email_address", unique: true
|
||||
t.index ["external_user_id"], name: "index_users_on_external_user_id", unique: true
|
||||
t.index ["role"], name: "index_users_on_role"
|
||||
end
|
||||
|
||||
|
||||
+1
-27
@@ -1290,16 +1290,6 @@ columns:
|
||||
default_function:
|
||||
collation:
|
||||
comment:
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
|
||||
auto_increment:
|
||||
name: external_user_id
|
||||
cast_type: *3
|
||||
sql_type_metadata: *4
|
||||
'null': true
|
||||
default:
|
||||
default_function:
|
||||
collation:
|
||||
comment:
|
||||
- *6
|
||||
- *10
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
|
||||
@@ -3098,22 +3088,6 @@ indexes:
|
||||
nulls_not_distinct:
|
||||
comment:
|
||||
valid: true
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
|
||||
table: users
|
||||
name: index_users_on_external_user_id
|
||||
unique: true
|
||||
columns:
|
||||
- external_user_id
|
||||
lengths: {}
|
||||
orders: {}
|
||||
opclasses: {}
|
||||
where:
|
||||
type:
|
||||
using:
|
||||
include:
|
||||
nulls_not_distinct:
|
||||
comment:
|
||||
valid: true
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
|
||||
table: users
|
||||
name: index_users_on_role
|
||||
@@ -3248,4 +3222,4 @@ indexes:
|
||||
comment:
|
||||
valid: true
|
||||
workflows: []
|
||||
version: 20250924124737
|
||||
version: 20251001181713
|
||||
|
||||
+7
-15
@@ -13,26 +13,18 @@ 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.oss_config
|
||||
tenant_id = ActiveRecord::FixtureSet.identify signal_account_name
|
||||
else
|
||||
signal_account = SignalId::Account.find_by_product_and_name!("fizzy", signal_account_name)
|
||||
tenant_id = signal_account.queenbee_id
|
||||
tenant_id = ActiveRecord::FixtureSet.identify signal_account_name
|
||||
end
|
||||
|
||||
ApplicationRecord.destroy_tenant tenant_id
|
||||
ApplicationRecord.create_tenant(tenant_id) do
|
||||
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",
|
||||
email_address: "david@37signals.com",
|
||||
password: "secret123456"
|
||||
)
|
||||
end
|
||||
else
|
||||
Account.create_with_admin_user(tenant_id: tenant_id)
|
||||
end
|
||||
account = Account.create_with_admin_user(
|
||||
tenant_id: tenant_id,
|
||||
account_name: signal_account_name,
|
||||
owner_name: "David Heinemeier Hansson",
|
||||
owner_email: "david@37signals.com",
|
||||
)
|
||||
account.setup_basic_template
|
||||
end
|
||||
|
||||
|
||||
@@ -2,11 +2,5 @@ source "https://rubygems.org"
|
||||
git_source(:bc) { |repo| "https://github.com/basecamp/#{repo}" }
|
||||
|
||||
# 37id and Queenbee integration
|
||||
gem "signal_id", bc: "signal_id", branch: "rails4", ref: "d6c2a5ceed7bb57bf7dcdebdb439e7cd526fca09"
|
||||
gem "mysql2", github: "jeremy/mysql2", branch: "force_latin1_to_utf8", ref: "b5766f5d296745d6aed014c8c0b7d82ef149ade1" # needed by signal_id
|
||||
gem "queuety", bc: "queuety", branch: "rails4", ref: "bf7e2a552fb674533d3e092f06dbf9b5d1ebc753" # needed by signal_id
|
||||
gem "service_concurrency_prevention", bc: "service_concurrency_prevention", ref: "320da99431718615ed86f8723e8b9d3259c602a3" # needed by queuety
|
||||
gem "portfolio", ">= 4.6", bc: "portfolio", ref: "86411a53d19a42ce44705a50ebe7c22828597991" # needed by signal_id
|
||||
gem "file_repository", "~> 1.4.5", bc: "file_repository", ref: "8c8d1594f3ec7e8c0938e6a7f7def521692344e4" # needed by portfolio
|
||||
gem "queenbee", bc: "queenbee-plugin", ref: "eb01c697de1ad028afc65cc7d9b5345a7a8e849f"
|
||||
gem "activeresource", require: "active_resource" # needed by queenbee
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
GIT
|
||||
remote: https://github.com/basecamp/file_repository
|
||||
revision: 8c8d1594f3ec7e8c0938e6a7f7def521692344e4
|
||||
ref: 8c8d1594f3ec7e8c0938e6a7f7def521692344e4
|
||||
specs:
|
||||
file_repository (1.4.6)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/basecamp/portfolio
|
||||
revision: 86411a53d19a42ce44705a50ebe7c22828597991
|
||||
ref: 86411a53d19a42ce44705a50ebe7c22828597991
|
||||
specs:
|
||||
portfolio (5.2.0)
|
||||
activesupport
|
||||
file_repository (>= 1.4.4)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/basecamp/queenbee-plugin
|
||||
revision: eb01c697de1ad028afc65cc7d9b5345a7a8e849f
|
||||
@@ -24,118 +8,19 @@ GIT
|
||||
builder
|
||||
rexml
|
||||
|
||||
GIT
|
||||
remote: https://github.com/basecamp/queuety
|
||||
revision: bf7e2a552fb674533d3e092f06dbf9b5d1ebc753
|
||||
ref: bf7e2a552fb674533d3e092f06dbf9b5d1ebc753
|
||||
branch: rails4
|
||||
specs:
|
||||
queuety (4.0.2)
|
||||
activerecord
|
||||
service_concurrency_prevention
|
||||
|
||||
GIT
|
||||
remote: https://github.com/basecamp/service_concurrency_prevention
|
||||
revision: 320da99431718615ed86f8723e8b9d3259c602a3
|
||||
ref: 320da99431718615ed86f8723e8b9d3259c602a3
|
||||
specs:
|
||||
service_concurrency_prevention (1.1.1)
|
||||
activesupport
|
||||
|
||||
GIT
|
||||
remote: https://github.com/basecamp/signal_id
|
||||
revision: d6c2a5ceed7bb57bf7dcdebdb439e7cd526fca09
|
||||
ref: d6c2a5ceed7bb57bf7dcdebdb439e7cd526fca09
|
||||
branch: rails4
|
||||
specs:
|
||||
signal_id (4.3.2)
|
||||
bcrypt-ruby (>= 3.0.0)
|
||||
google-id-token (>= 1.4.0)
|
||||
mocha
|
||||
multi_json
|
||||
portfolio (>= 4.0)
|
||||
pwned (~> 2.0)
|
||||
queuety (>= 3.0)
|
||||
rails (>= 4.0.2)
|
||||
rails-controller-testing
|
||||
|
||||
GIT
|
||||
remote: https://github.com/jeremy/mysql2.git
|
||||
revision: b5766f5d296745d6aed014c8c0b7d82ef149ade1
|
||||
ref: b5766f5d296745d6aed014c8c0b7d82ef149ade1
|
||||
branch: force_latin1_to_utf8
|
||||
specs:
|
||||
mysql2 (0.5.4.latin1utf8)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
zeitwerk (~> 2.6)
|
||||
actionmailbox (8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
activejob (= 8.0.2.1)
|
||||
activerecord (= 8.0.2.1)
|
||||
activestorage (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
mail (>= 2.8.0)
|
||||
actionmailer (8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
actionview (= 8.0.2.1)
|
||||
activejob (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
mail (>= 2.8.0)
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (8.0.2.1)
|
||||
actionview (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
nokogiri (>= 1.8.5)
|
||||
rack (>= 2.2.4)
|
||||
rack-session (>= 1.0.1)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
useragent (~> 0.16)
|
||||
actiontext (8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
activerecord (= 8.0.2.1)
|
||||
activestorage (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
activejob (8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
activemodel-serializers-xml (1.0.3)
|
||||
activemodel (>= 5.0.0.a)
|
||||
activesupport (>= 5.0.0.a)
|
||||
builder (~> 3.1)
|
||||
activerecord (8.0.2.1)
|
||||
activemodel (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
timeout (>= 0.4.0)
|
||||
activeresource (6.1.4)
|
||||
activemodel (>= 6.0)
|
||||
activemodel-serializers-xml (~> 1.0)
|
||||
activesupport (>= 6.0)
|
||||
activestorage (8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
activejob (= 8.0.2.1)
|
||||
activerecord (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
marcel (~> 1.0)
|
||||
activesupport (8.0.2.1)
|
||||
base64
|
||||
benchmark (>= 0.3)
|
||||
@@ -150,143 +35,21 @@ GEM
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
uri (>= 0.13.1)
|
||||
base64 (0.3.0)
|
||||
bcrypt (3.1.20)
|
||||
bcrypt-ruby (3.1.5)
|
||||
bcrypt (>= 3.1.3)
|
||||
benchmark (0.4.1)
|
||||
bigdecimal (3.2.3)
|
||||
builder (3.3.0)
|
||||
concurrent-ruby (1.3.5)
|
||||
connection_pool (2.5.4)
|
||||
crass (1.0.6)
|
||||
date (3.4.1)
|
||||
drb (2.2.3)
|
||||
erb (5.0.2)
|
||||
erubi (1.13.1)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
google-id-token (1.4.2)
|
||||
jwt (>= 1)
|
||||
i18n (1.14.7)
|
||||
concurrent-ruby (~> 1.0)
|
||||
io-console (0.8.1)
|
||||
irb (1.15.2)
|
||||
pp (>= 0.6.0)
|
||||
rdoc (>= 4.0.0)
|
||||
reline (>= 0.4.2)
|
||||
jwt (3.1.2)
|
||||
base64
|
||||
logger (1.7.0)
|
||||
loofah (2.24.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
mail (2.8.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
marcel (1.0.4)
|
||||
mini_mime (1.1.5)
|
||||
minitest (5.25.5)
|
||||
mocha (2.7.1)
|
||||
ruby2_keywords (>= 0.0.5)
|
||||
multi_json (1.17.0)
|
||||
net-imap (0.5.10)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
net-protocol
|
||||
net-protocol (0.2.2)
|
||||
timeout
|
||||
net-smtp (0.5.1)
|
||||
net-protocol
|
||||
nio4r (2.7.4)
|
||||
nokogiri (1.18.9-aarch64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-aarch64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-arm-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-arm-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-x86_64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-x86_64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-x86_64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
pp (0.6.2)
|
||||
prettyprint
|
||||
prettyprint (0.2.0)
|
||||
psych (5.2.6)
|
||||
date
|
||||
stringio
|
||||
pwned (2.4.1)
|
||||
racc (1.8.1)
|
||||
rack (3.2.1)
|
||||
rack-session (2.1.1)
|
||||
base64 (>= 0.1.0)
|
||||
rack (>= 3.0.0)
|
||||
rack-test (2.2.0)
|
||||
rack (>= 1.3)
|
||||
rackup (2.2.1)
|
||||
rack (>= 3)
|
||||
rails (8.0.2.1)
|
||||
actioncable (= 8.0.2.1)
|
||||
actionmailbox (= 8.0.2.1)
|
||||
actionmailer (= 8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
actiontext (= 8.0.2.1)
|
||||
actionview (= 8.0.2.1)
|
||||
activejob (= 8.0.2.1)
|
||||
activemodel (= 8.0.2.1)
|
||||
activerecord (= 8.0.2.1)
|
||||
activestorage (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 8.0.2.1)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
actionview (>= 5.0.1.rc1)
|
||||
activesupport (>= 5.0.1.rc1)
|
||||
rails-dom-testing (2.3.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.6.2)
|
||||
loofah (~> 2.21)
|
||||
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
||||
railties (8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
irb (~> 1.13)
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0, >= 1.2.2)
|
||||
zeitwerk (~> 2.6)
|
||||
rake (13.3.0)
|
||||
rdoc (6.14.2)
|
||||
erb
|
||||
psych (>= 4.0.0)
|
||||
reline (0.6.2)
|
||||
io-console (~> 0.5)
|
||||
rexml (3.4.4)
|
||||
ruby2_keywords (0.0.5)
|
||||
securerandom (0.4.1)
|
||||
stringio (3.1.7)
|
||||
thor (1.4.0)
|
||||
timeout (0.4.3)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uri (1.0.3)
|
||||
useragent (0.16.11)
|
||||
websocket-driver (0.8.0)
|
||||
base64
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
zeitwerk (2.7.3)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux-gnu
|
||||
@@ -300,13 +63,7 @@ PLATFORMS
|
||||
|
||||
DEPENDENCIES
|
||||
activeresource
|
||||
file_repository (~> 1.4.5)!
|
||||
mysql2!
|
||||
portfolio (>= 4.6)!
|
||||
queenbee!
|
||||
queuety!
|
||||
service_concurrency_prevention!
|
||||
signal_id!
|
||||
|
||||
BUNDLED WITH
|
||||
2.7.0
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
class Signup::AccountsController < Signup::BaseController
|
||||
before_action :reset_signup_storage
|
||||
rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to saas.new_signup_account_path, alert: "Try again later." }
|
||||
|
||||
def new
|
||||
@signup = Signup.new
|
||||
end
|
||||
|
||||
def create
|
||||
@signup = Signup.new(signup_params)
|
||||
|
||||
if @signup.recognized?
|
||||
store_signup
|
||||
redirect_to Launchpad.authentication_url(purpose: "signup", login_hint: @signup.email_address, redirect_uri: saas.signup_session_url), allow_other_host: true
|
||||
elsif @signup.process
|
||||
redirect_to_account(@signup.account)
|
||||
else
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def signup_params
|
||||
params.require(:signup).permit(*Signup::PERMITTED_KEYS)
|
||||
end
|
||||
|
||||
def store_signup
|
||||
@signup.to_h.each { |key, value| signup_storage[key.to_s] = value }
|
||||
end
|
||||
end
|
||||
@@ -1,36 +0,0 @@
|
||||
class Signup::BaseController < ApplicationController
|
||||
require_untenanted_access
|
||||
|
||||
# TODO: Remove this auth before launch.
|
||||
http_basic_authenticate_with(
|
||||
name: Rails.env.test? ? "testname" : Rails.application.credentials.account_signup_http_basic_auth.name,
|
||||
password: Rails.env.test? ? "testpassword" : Rails.application.credentials.account_signup_http_basic_auth.password
|
||||
)
|
||||
|
||||
attr_reader :authenticated_identity
|
||||
|
||||
private
|
||||
def signup_storage
|
||||
session[:signup] ||= {}
|
||||
end
|
||||
|
||||
def reset_signup_storage
|
||||
session.delete(:signup)
|
||||
end
|
||||
|
||||
def authenticated_identity=(identity)
|
||||
@authenticated_identity = identity
|
||||
signup_storage["identity_id"] = identity.try(:id)
|
||||
end
|
||||
|
||||
def set_authenticated_identity
|
||||
if identity_id = signup_storage["identity_id"]
|
||||
@authenticated_identity = SignalId::Identity.find_by(id: identity_id)
|
||||
end
|
||||
end
|
||||
|
||||
def redirect_to_account(account)
|
||||
redirect_to account.external_account.owner.remote_login_url(proceed_to: root_path),
|
||||
allow_other_host: true
|
||||
end
|
||||
end
|
||||
@@ -1,23 +0,0 @@
|
||||
class Signup::CompletionsController < Signup::BaseController
|
||||
before_action :set_authenticated_identity, only: :create
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
def create
|
||||
@signup = Signup.new(signup_params)
|
||||
@signup.signal_identity = authenticated_identity
|
||||
|
||||
if @signup.process
|
||||
reset_signup_storage
|
||||
redirect_to_account(@signup.account)
|
||||
else
|
||||
render plain: "Could not complete signup. This is probably a bug.", status: :unprocessable_entity, layout: false
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def signup_params
|
||||
signup_storage.slice(*Signup::PERMITTED_KEYS.map(&:to_s))
|
||||
end
|
||||
end
|
||||
@@ -1,9 +0,0 @@
|
||||
class Signup::SessionsController < Signup::BaseController
|
||||
def create
|
||||
if self.authenticated_identity = SignalId::Identity.authenticate(params.permit(:sig))
|
||||
redirect_to saas.new_signup_completion_url
|
||||
else
|
||||
render plain: "Authentication failed. This is probably a bug.", status: :unauthorized
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,30 +0,0 @@
|
||||
module Account::SignalAccount
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
prepended do
|
||||
# the tenant_id column is the SignalId::Account's queenbee_id
|
||||
belongs_to :external_account, class_name: "SignalId::Account", primary_key: :queenbee_id, foreign_key: :tenant_id, optional: true
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def find_by_queenbee_id(queenbee_id)
|
||||
find_by(tenant_id: queenbee_id)
|
||||
end
|
||||
|
||||
def create_with_admin_user(tenant_id:)
|
||||
new(tenant_id:).tap do |account|
|
||||
SignalId::Database.on_master do
|
||||
account.name = account.external_account.name
|
||||
account.save!
|
||||
|
||||
User.create!(
|
||||
name: account.external_account.owner.name,
|
||||
email_address: account.external_account.owner.email_address,
|
||||
role: "admin",
|
||||
password: SecureRandom.hex(16)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,24 +0,0 @@
|
||||
# lifted from bc3 app/models/launchpad.rb
|
||||
module Launchpad
|
||||
extend self
|
||||
|
||||
delegate :url, to: "SignalId.launchpad"
|
||||
|
||||
def login_url(product: false, account: nil, **params)
|
||||
url product_account_path("/signin", product: product, account: account), params
|
||||
end
|
||||
|
||||
def logout_url
|
||||
url "/signout"
|
||||
end
|
||||
|
||||
def authentication_url(**params)
|
||||
url "/authenticate", params.merge(product: :fizzy)
|
||||
end
|
||||
|
||||
def product_account_path(path = nil, product: false, account: nil)
|
||||
product_path = "/fizzy" if product || account
|
||||
account_path = "/#{account.external_account.id}" if account
|
||||
[ product_path, account_path, path ].compact.join
|
||||
end
|
||||
end
|
||||
@@ -1,159 +0,0 @@
|
||||
class Signup
|
||||
include ActiveModel::Model
|
||||
include ActiveModel::Attributes
|
||||
include ActiveModel::Validations
|
||||
|
||||
PERMITTED_KEYS = %i[ full_name email_address password company_name ]
|
||||
|
||||
# Input attribute
|
||||
attr_accessor :company_name
|
||||
|
||||
# Input attributes when not using an existing identity
|
||||
attr_accessor :full_name, :email_address, :password
|
||||
|
||||
# Input attribute when using an existing identity, output when creating new identity
|
||||
attr_accessor :signal_identity
|
||||
|
||||
# Output attributes
|
||||
attr_reader :queenbee_account, :signal_account, :account, :user
|
||||
|
||||
validate :validate_new_identity
|
||||
|
||||
def initialize(...)
|
||||
@full_name = nil
|
||||
@email_address = nil
|
||||
@password = nil
|
||||
@company_name = nil
|
||||
@signal_identity = nil
|
||||
@queenbee_account = nil
|
||||
@account = nil
|
||||
@user = nil
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def process(destroy_existing_tenant: false)
|
||||
return false unless valid?
|
||||
|
||||
create_signal_identity
|
||||
create_queenbee_account
|
||||
create_tenant(destroy_existing_tenant:)
|
||||
|
||||
true
|
||||
rescue => error
|
||||
destroy_tenant
|
||||
destroy_queenbee_account
|
||||
destroy_signal_identity
|
||||
|
||||
errors.add(:base, "An error occurred during signup: #{error.message}")
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def recognized?
|
||||
SignalId::Identity.find_by_email_address(email_address).present?
|
||||
end
|
||||
|
||||
def tenant_name
|
||||
@tenant_name ||= @queenbee_account.id.to_s
|
||||
end
|
||||
|
||||
def to_h
|
||||
{
|
||||
full_name: full_name,
|
||||
email_address: email_address,
|
||||
company_name: company_name
|
||||
}.compact_blank
|
||||
end
|
||||
|
||||
private
|
||||
def create_signal_identity
|
||||
unless signal_identity.present?
|
||||
SignalId::Database.on_master do
|
||||
@signal_identity = build_new_identity.tap(&:save!)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_queenbee_account
|
||||
@queenbee_account = Queenbee::Remote::Account.create!(queenbee_account_attributes)
|
||||
SignalId::Database.on_master do
|
||||
@signal_account = SignalId::Account.find_by(queenbee_id: @queenbee_account.id)
|
||||
end
|
||||
end
|
||||
|
||||
def create_tenant(destroy_existing_tenant: false, &block)
|
||||
ApplicationRecord.destroy_tenant(tenant_name) if destroy_existing_tenant
|
||||
ApplicationRecord.create_tenant(tenant_name) do
|
||||
@account = Account.create_with_admin_user(tenant_id: queenbee_account.id)
|
||||
@account.setup_basic_template
|
||||
@user = User.first
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_tenant
|
||||
if queenbee_account && ApplicationRecord.tenant_exist?(tenant_name)
|
||||
ApplicationRecord.destroy_tenant(tenant_name)
|
||||
@account = nil
|
||||
@user = nil
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_queenbee_account
|
||||
queenbee_account&.cancel
|
||||
@queenbee_account = nil
|
||||
end
|
||||
|
||||
def destroy_signal_identity
|
||||
SignalId::Database.on_master do
|
||||
signal_identity&.destroy
|
||||
end
|
||||
@signal_identity = nil
|
||||
end
|
||||
|
||||
def validate_new_identity
|
||||
unless signal_identity.present?
|
||||
build_new_identity.tap do |identity|
|
||||
unless identity.valid?
|
||||
identity.errors.each { |error| errors.add(error.attribute, error.message) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def build_new_identity
|
||||
SignalId::Identity.new do |identity|
|
||||
identity.name = full_name || email_address
|
||||
identity.email_address = email_address
|
||||
identity.username = email_address
|
||||
identity.password = password
|
||||
end
|
||||
end
|
||||
|
||||
def queenbee_account_attributes
|
||||
{
|
||||
skip_remote: true, # Fizzy creates its own local account
|
||||
product_name: "fizzy",
|
||||
name: queenbee_account_name,
|
||||
owner_identity_id: signal_identity.id,
|
||||
trial: false,
|
||||
subscription: subscription_attributes,
|
||||
remote_request: request_attributes
|
||||
}
|
||||
end
|
||||
|
||||
def subscription_attributes
|
||||
subscription = FreeV1Subscription
|
||||
{ name: subscription.to_param, price: subscription.price }
|
||||
end
|
||||
|
||||
def request_attributes
|
||||
{ remote_address: Current.ip_address, user_agent: Current.user_agent, referrer: Current.referrer }
|
||||
end
|
||||
|
||||
def queenbee_account_name
|
||||
name = company_name.presence || signal_identity.name
|
||||
name += " (Beta)" if Rails.env.beta?
|
||||
name
|
||||
end
|
||||
end
|
||||
@@ -1,3 +0,0 @@
|
||||
<%= auto_submit_form_with(method: :put, url: saas.session_launchpad_path) do |form| %>
|
||||
<%= form.hidden_field(:sig, value: @sig) %>
|
||||
<% end %>
|
||||
@@ -1,58 +0,0 @@
|
||||
<% @page_title = "Sign up for Fizzy" %>
|
||||
|
||||
<div class="panel shadow center margin-block-double <%= "shake" if flash[:alert] %>" style="--panel-size: 65ch;">
|
||||
<h1 class="txt-xx-large margin-block-end-double">Fizzy</h1>
|
||||
<h2 class="txt-large margin-block-end-double">Create your account</h2>
|
||||
|
||||
<%= form_with model: @signup, url: saas.signup_accounts_path, scope: "signup", class: "flex flex-column gap txt-large", data: { turbo: false } do |form| %>
|
||||
<div class="flex align-center gap">
|
||||
<label class="flex align-center gap input input--actor txt-large">
|
||||
<%= form.text_field :full_name, class: "input", autocomplete: "name", placeholder: "Your name", autofocus: true, required: true %>
|
||||
<%= icon_tag "person", class: "txt-large" %>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex align-center gap">
|
||||
<label class="flex align-center gap input input--actor txt-large">
|
||||
<%= form.email_field :email_address, class: "input", autocomplete: "username", placeholder: "Your email address", required: true %>
|
||||
<%= icon_tag "email", class: "txt-large" %>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex align-center gap">
|
||||
<label class="flex align-center gap input input--actor txt-large">
|
||||
<%= form.text_field :company_name, class: "input", autocomplete: "organization", placeholder: "Your organization's name", required: true %>
|
||||
<%= icon_tag "building", class: "txt-large" %>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex align-center gap">
|
||||
<label class="flex align-center gap input input--actor txt-large">
|
||||
<%= form.password_field :password, class: "input", autocomplete: "new-password", placeholder: "Password (at least 12 characters)", required: true, maxlength: 72, minlength: 12 %>
|
||||
<%= icon_tag "password", class: "txt-large" %>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<% if @signup.errors.any? %>
|
||||
<div class="alert alert--error">
|
||||
<ul class="margin-block-none">
|
||||
<% @signup.errors.full_messages.each do |message| %>
|
||||
<li><%= message %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<button type="submit" class="btn btn--reversed center">
|
||||
<%= icon_tag "arrow-right" %>
|
||||
<span>Create your account</span>
|
||||
</button>
|
||||
<% end %>
|
||||
|
||||
<footer class="margin-block-start-double txt-center">
|
||||
<p class="txt-small txt-muted">
|
||||
Already have an account?
|
||||
<%= link_to "Sign in", login_url, class: "decorated" %>
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<%= auto_submit_form_with url: saas.signup_completions_path, data: { turbo: false } %>
|
||||
@@ -1,10 +1,3 @@
|
||||
Fizzy::Saas::Engine.routes.draw do
|
||||
namespace :signup do
|
||||
get "/" => "accounts#new"
|
||||
resources :accounts, only: %i[ new create ]
|
||||
get "/session" => "sessions#create" # redirect from Launchpad after mid-signup authentication
|
||||
resources :completions, only: %i[ new create ]
|
||||
end
|
||||
|
||||
Queenbee.routes(self)
|
||||
end
|
||||
|
||||
@@ -22,7 +22,6 @@ Gem::Specification.new do |spec|
|
||||
end
|
||||
|
||||
spec.add_dependency "rails", ">= 8.1.0.beta1"
|
||||
spec.add_dependency "signal_id"
|
||||
spec.add_dependency "queenbee"
|
||||
spec.add_dependency "rails_structured_logging"
|
||||
end
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
require "signal_id"
|
||||
|
||||
module Fizzy
|
||||
module Saas
|
||||
class Engine < ::Rails::Engine
|
||||
# extend application models
|
||||
config.to_prepare do
|
||||
Account.prepend Account::SignalAccount
|
||||
end
|
||||
|
||||
# moved from config/initializers/queenbee.rb
|
||||
Queenbee.host_app = Fizzy
|
||||
|
||||
@@ -21,27 +14,6 @@ module Fizzy
|
||||
::Object.const_set const_name, Subscription.const_get(short_name, false)
|
||||
end
|
||||
end
|
||||
|
||||
# moved from config/initializers/signal_id.rb
|
||||
config.before_initialize do
|
||||
ENV["SIGNAL_ID_SECRET"] = Rails.application.credentials.signal_id_secret
|
||||
end
|
||||
|
||||
config.to_prepare do
|
||||
SignalId.product = "fizzy"
|
||||
|
||||
db_config = SignalId::Database.default_configuration
|
||||
SignalId::Database.load_configuration db_config
|
||||
SignalId::Database.enable_rw_splitting!
|
||||
|
||||
silence_warnings do
|
||||
SignalId::Account::Peer = Account
|
||||
end
|
||||
end
|
||||
|
||||
config.after_initialize do
|
||||
ActiveRecord.yaml_column_permitted_classes << SignalId::PersonName
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
require "test_helper"
|
||||
|
||||
class Signup::AccountsControllerTest < ActionDispatch::IntegrationTest
|
||||
test "new under a tenanted URL redirects to the root" do
|
||||
get saas.new_signup_account_url
|
||||
|
||||
assert_redirected_to root_url
|
||||
end
|
||||
|
||||
test "new under an untenanted URL is OK" do
|
||||
integration_session.default_url_options[:script_name] = "" # no tenant
|
||||
|
||||
get saas.new_signup_account_url, headers: auth_headers
|
||||
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "create with invalid params" do
|
||||
integration_session.default_url_options[:script_name] = "" # no tenant
|
||||
|
||||
post saas.signup_accounts_url,
|
||||
headers: auth_headers,
|
||||
params: { signup: { full_name: "Jim", email_address: "jim@example.com", password: "", company_name: "" } }
|
||||
|
||||
assert_response :unprocessable_entity
|
||||
assert_select "div.alert--error", text: /you need to choose a password/i
|
||||
end
|
||||
|
||||
test "create for a new identity" do
|
||||
integration_session.default_url_options[:script_name] = "" # no tenant
|
||||
|
||||
assert_difference -> { SignalId::Identity.count }, +1 do
|
||||
assert_difference -> { SignalId::Account.count }, +1 do
|
||||
post saas.signup_accounts_url, headers: auth_headers,
|
||||
params: {
|
||||
signup: {
|
||||
full_name: "Jim",
|
||||
email_address: "jim@example.com",
|
||||
password: SecureRandom.hex(12),
|
||||
company_name: "signup-accounts-controller-test-1"
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
signal_account = SignalId::Account.last
|
||||
assert_redirected_to(/#{signal_account.login_url}/)
|
||||
end
|
||||
|
||||
test "create for an existing identity" do
|
||||
integration_session.default_url_options[:script_name] = "" # no tenant
|
||||
|
||||
identity = signal_identities(:david)
|
||||
|
||||
post saas.signup_accounts_url, headers: auth_headers,
|
||||
params: { signup: { email_address: identity.email_address, company_name: "signup-accounts-controller-test-2" } }
|
||||
|
||||
assert_authentication_requested_for identity
|
||||
|
||||
assert_no_difference -> { SignalId::Identity.count } do
|
||||
assert_difference -> { SignalId::Account.count } do
|
||||
authenticate_via_launchpad_as(identity)
|
||||
assert_redirected_to_account
|
||||
assert_nil session[:signup]
|
||||
end
|
||||
end
|
||||
|
||||
signal_account = SignalId::Account.last
|
||||
ApplicationRecord.with_tenant(signal_account.queenbee_id) do
|
||||
assert_equal Account.last, signal_account.peer
|
||||
end
|
||||
end
|
||||
|
||||
test "actions require HTTP basic authentication while we're in internal-only mode" do
|
||||
integration_session.default_url_options[:script_name] = "" # no tenant
|
||||
|
||||
get saas.new_signup_account_url
|
||||
|
||||
assert_response :unauthorized
|
||||
end
|
||||
|
||||
private
|
||||
def auth_headers
|
||||
{ "HTTP_AUTHORIZATION" => ActionController::HttpAuthentication::Basic.encode_credentials("testname", "testpassword") }
|
||||
end
|
||||
|
||||
def assert_authentication_requested_for(identity)
|
||||
assert_response :redirect
|
||||
assert_match(/#{Regexp.escape(Launchpad.url("/authenticate", login_hint: identity.email_address))}.*&purpose=signup/, redirect_to_url)
|
||||
end
|
||||
|
||||
def authenticate_via_launchpad_as(identity)
|
||||
get saas.signup_session_url, headers: auth_headers, params: { sig: identity.perishable_signature }
|
||||
assert_equal identity.id, session[:signup]["identity_id"]
|
||||
assert_redirected_to saas.new_signup_completion_url
|
||||
post saas.signup_completions_url, headers: auth_headers
|
||||
end
|
||||
|
||||
def assert_redirected_to_account(signal_account = SignalId::Account.last)
|
||||
assert_response :redirect
|
||||
assert_match(/#{Regexp.escape(signal_account.url("/session/launchpad"))}.*&sig=/, redirect_to_url)
|
||||
end
|
||||
end
|
||||
@@ -1,41 +0,0 @@
|
||||
require "test_helper"
|
||||
|
||||
class Account::SignalAccountTest < ActiveSupport::TestCase
|
||||
# # TODO(MIKE): Queenbee client API compliance tests
|
||||
# include Queenbee::Testing::Client
|
||||
|
||||
setup do
|
||||
@account = accounts("37s")
|
||||
end
|
||||
|
||||
test "belongs to a signal_account via a shared queenbee_id" do
|
||||
assert_not_nil @account.tenant_id
|
||||
assert_equal @account.tenant_id, Account.new(external_account: @account.external_account).tenant_id
|
||||
assert_equal @account.external_account, Account.new(tenant_id: @account.tenant_id).external_account
|
||||
end
|
||||
|
||||
test "peering" do
|
||||
assert_equal @account, @account.external_account.peer
|
||||
end
|
||||
|
||||
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)
|
||||
|
||||
assert_not_nil account
|
||||
assert account.persisted?
|
||||
assert_equal 1, Account.count
|
||||
assert_equal signal_account.queenbee_id, account.tenant_id
|
||||
assert_equal signal_account.name, account.name
|
||||
assert_equal account, signal_account.peer
|
||||
|
||||
assert_equal 1, User.count
|
||||
User.first.tap do |user|
|
||||
assert_equal signal_account.owner.name, user.name
|
||||
assert_equal signal_account.owner.email_address, user.email_address
|
||||
assert_equal "admin", user.role
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,149 +0,0 @@
|
||||
require "test_helper"
|
||||
|
||||
class SignupTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@signup = Signup.new(
|
||||
email_address: "brian@example.com",
|
||||
full_name: "Brian Wilson",
|
||||
company_name: "Beach Boys",
|
||||
password: SecureRandom.hex(16)
|
||||
)
|
||||
end
|
||||
|
||||
test "#to_h allows persistence of the signup data" do
|
||||
actual = @signup.to_h
|
||||
expected = {
|
||||
email_address: @signup.email_address,
|
||||
full_name: @signup.full_name,
|
||||
company_name: @signup.company_name
|
||||
}
|
||||
assert_equal expected, actual
|
||||
end
|
||||
|
||||
test "#process creates all the necessary objects for a new identity" do
|
||||
Account.any_instance.expects(:setup_basic_template).once
|
||||
|
||||
assert @signup.process, @signup.errors.full_messages.to_sentence(words_connector: ". ")
|
||||
assert_empty @signup.errors
|
||||
|
||||
assert @signup.signal_identity
|
||||
assert @signup.signal_identity.persisted?
|
||||
|
||||
assert @signup.queenbee_account
|
||||
assert @signup.queenbee_account.id
|
||||
|
||||
assert @signup.signal_account
|
||||
assert @signup.signal_account.persisted?
|
||||
assert_equal @signup.signal_identity, @signup.signal_account.owner.identity
|
||||
assert_equal @signup.company_name, @signup.signal_account.name
|
||||
|
||||
assert @signup.account
|
||||
assert @signup.account.persisted?
|
||||
|
||||
assert @signup.user
|
||||
assert @signup.user.persisted?
|
||||
|
||||
assert_equal @signup.queenbee_account.id.to_s, @signup.tenant_name
|
||||
assert_includes ApplicationRecord.tenants, @signup.tenant_name
|
||||
end
|
||||
|
||||
test "the new account is named with company name if present" do
|
||||
assert_equal @signup.company_name, @signup.send(:queenbee_account_name)
|
||||
end
|
||||
|
||||
test "in beta, the new account is named distinctly" do
|
||||
Rails.stubs(:env).returns(stub(beta?: true))
|
||||
|
||||
assert_equal "#{@signup.company_name} (Beta)", @signup.send(:queenbee_account_name)
|
||||
end
|
||||
|
||||
test "#process creates all the necessary objects for an existing identity" do
|
||||
signal_identity = SignalId::Identity.find_by_email_address("david@37signals.com")
|
||||
|
||||
@signup = Signup.new(
|
||||
signal_identity: signal_identity,
|
||||
company_name: "Team LeMans",
|
||||
)
|
||||
|
||||
Account.any_instance.expects(:setup_basic_template).once
|
||||
|
||||
assert @signup.process, @signup.errors.full_messages.to_sentence(words_connector: ". ")
|
||||
assert_empty @signup.errors
|
||||
|
||||
assert @signup.signal_identity
|
||||
assert_equal signal_identity, @signup.signal_identity
|
||||
|
||||
assert @signup.queenbee_account
|
||||
assert @signup.queenbee_account.id
|
||||
|
||||
assert @signup.signal_account
|
||||
assert @signup.signal_account.persisted?
|
||||
assert_equal @signup.signal_identity, @signup.signal_account.owner.identity
|
||||
|
||||
assert @signup.account
|
||||
assert @signup.account.persisted?
|
||||
|
||||
assert @signup.user
|
||||
assert @signup.user.persisted?
|
||||
|
||||
assert_equal @signup.queenbee_account.id.to_s, @signup.tenant_name
|
||||
assert_includes ApplicationRecord.tenants, @signup.tenant_name
|
||||
end
|
||||
|
||||
test "#process does nothing if a validation error occurs creating identity" do
|
||||
@signup.password = ""
|
||||
|
||||
assert_not @signup.process
|
||||
assert_not_empty @signup.errors[:password]
|
||||
|
||||
assert_nil @signup.signal_identity
|
||||
assert_nil @signup.queenbee_account
|
||||
assert_nil @signup.account
|
||||
assert_nil @signup.user
|
||||
end
|
||||
|
||||
test "#process does nothing if a validation error occurs creating the queenbee account" do
|
||||
Queenbee::Remote::Account.stubs(:create!).raises(RuntimeError, "Invalid account data")
|
||||
|
||||
SignalId::Identity.any_instance.expects(:destroy).once
|
||||
|
||||
assert_not @signup.process
|
||||
assert_not_empty @signup.errors[:base]
|
||||
|
||||
assert_nil @signup.signal_identity
|
||||
assert_nil @signup.queenbee_account
|
||||
assert_nil @signup.account
|
||||
assert_nil @signup.user
|
||||
end
|
||||
|
||||
test "#process does nothing if a validation error occurs creating the tenant" do
|
||||
ApplicationRecord.stubs(:create_tenant).raises(RuntimeError, "Tenant already exists")
|
||||
|
||||
Queenbee::Remote::Account.any_instance.expects(:cancel).once
|
||||
SignalId::Identity.any_instance.expects(:destroy).once
|
||||
|
||||
assert_not @signup.process
|
||||
assert_not_empty @signup.errors[:base]
|
||||
|
||||
assert_nil @signup.signal_identity
|
||||
assert_nil @signup.queenbee_account
|
||||
assert_nil @signup.account
|
||||
assert_nil @signup.user
|
||||
end
|
||||
|
||||
test "#process does nothing if a validation error occurs creating the account" do
|
||||
Account.stubs(:create_with_admin_user).raises(RuntimeError, "Account creation failed")
|
||||
|
||||
ApplicationRecord.expects(:destroy_tenant).once
|
||||
Queenbee::Remote::Account.any_instance.expects(:cancel).once
|
||||
SignalId::Identity.any_instance.expects(:destroy).once
|
||||
|
||||
assert_not @signup.process
|
||||
assert_not_empty @signup.errors[:base]
|
||||
|
||||
assert_nil @signup.signal_identity
|
||||
assert_nil @signup.queenbee_account
|
||||
assert_nil @signup.account
|
||||
assert_nil @signup.user
|
||||
end
|
||||
end
|
||||
@@ -1,12 +1,5 @@
|
||||
require "signal_id/testing"
|
||||
require "queenbee/testing/mocks"
|
||||
|
||||
module ActiveSupport
|
||||
class TestCase
|
||||
include SignalId::Testing
|
||||
end
|
||||
end
|
||||
|
||||
Queenbee::Remote::Account.class_eval do
|
||||
# because we use the account ID as the tenant name, we need it to be unique in each test to avoid
|
||||
# parallelized tests clobbering each other.
|
||||
|
||||
@@ -13,27 +13,26 @@ unless Rails.env.local?
|
||||
end
|
||||
|
||||
identifier = SecureRandom.hex(4)
|
||||
signup = Signup.new(
|
||||
email_address: "dev-#{identifier}@example.com",
|
||||
full_name: "Developer #{identifier}",
|
||||
company_name: "Company #{identifier}",
|
||||
password: "secret123456"
|
||||
)
|
||||
tenant = ActiveRecord::FixtureSet.identify(identifier)
|
||||
|
||||
puts "Creating signal identity for #{signup.email_address}..."
|
||||
signup.send(:create_signal_identity)
|
||||
|
||||
puts "Creating queenbee account ..."
|
||||
signup.send(:create_queenbee_account)
|
||||
|
||||
path = ApplicationRecord.tenanted_root_config.database_path_for(signup.tenant_name)
|
||||
path = ApplicationRecord.tenanted_root_config.database_path_for(tenant)
|
||||
FileUtils.mkdir_p(File.dirname(path), verbose: true)
|
||||
FileUtils.cp original_dbfile, path, verbose: true
|
||||
|
||||
ActiveRecord::Tenanted::DatabaseTasks.migrate_all
|
||||
|
||||
ApplicationRecord.with_tenant(signup.tenant_name) do |tenant|
|
||||
Account.sole.update! external_account: signup.signal_account
|
||||
ApplicationRecord.with_tenant(tenant) do |tenant|
|
||||
Account.sole.destroy!
|
||||
|
||||
puts "\n\nLogin to http://launchpad.localhost:3011/ as #{signup.email_address} / #{signup.password}"
|
||||
Account.create_with_admin_user(
|
||||
tenant_id: tenant,
|
||||
account_name: "Company #{identifier}",
|
||||
owner_name: "Developer #{identifier}",
|
||||
owner_email: "dev-#{identifier}@example.com")
|
||||
|
||||
user = User.last
|
||||
user.update! password: "secret123456"
|
||||
|
||||
url = Rails.application.routes.url_helpers.root_url(Rails.application.config.action_controller.default_url_options.merge(script_name: Account.sole.slug))
|
||||
puts "\n\nLogin to #{url} as #{user.email_address} / #{user.password}"
|
||||
end
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/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,117 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require_relative "../config/environment"
|
||||
|
||||
ActiveRecord::Base.logger = Logger.new(File::NULL)
|
||||
|
||||
class BootstrapSignalId
|
||||
def initialize(dry_run: false)
|
||||
@dry_run = dry_run
|
||||
end
|
||||
|
||||
def run
|
||||
SignalId::Database.on_master do
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
puts "\n# tenant: #{tenant}"
|
||||
|
||||
next unless check_account_preconditions
|
||||
create_signal_id_account if Account.sole.queenbee_id.nil?
|
||||
|
||||
create_signal_id_users
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def check_account_preconditions
|
||||
unless Account.count == 1
|
||||
puts "There are #{Account.count} accounts, but exactly one is expected."
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def create_signal_id_account
|
||||
owner = SignalId::Identity.find_by_email_address!("kevin@37signals.com")
|
||||
print_identity("New owner is:", owner)
|
||||
|
||||
unless @dry_run
|
||||
qbattr = queenbee_account_attributes(owner)
|
||||
queenbee_account = Queenbee::Remote::Account.create!(qbattr)
|
||||
|
||||
signal_id_account = SignalId::Account.find_by!(queenbee_id: queenbee_account.id)
|
||||
signal_id_account.update_column :subdomain, ApplicationRecord.current_tenant
|
||||
|
||||
account = Account.sole
|
||||
account.queenbee_id = queenbee_account.id
|
||||
account.name = ApplicationRecord.current_tenant
|
||||
account.save!
|
||||
end
|
||||
end
|
||||
|
||||
def create_signal_id_users
|
||||
signal_account = Account.sole.signal_account
|
||||
|
||||
User.find_each do |user|
|
||||
if !user.system? && user.signal_user_id.nil?
|
||||
signal_identities = SignalId::Identity.where(email_address: user.email_address)
|
||||
if signal_identities.length > 1
|
||||
puts "Multiple identities found for #{user.email_address}:"
|
||||
signal_identities.each { |identity| print_identity(" - ", identity) }
|
||||
signal_identity = signal_identities.first
|
||||
elsif signal_identities.length == 1
|
||||
signal_identity = signal_identities.first
|
||||
print_identity("Identity for #{user.email_address}:", signal_identity)
|
||||
else
|
||||
puts "No identity found for #{user.name} (#{user.email_address})"
|
||||
signal_identity = nil
|
||||
end
|
||||
|
||||
if signal_identity
|
||||
unless @dry_run
|
||||
signal_user = SignalId::User.find_or_create_by!(identity: signal_identity, account: signal_account)
|
||||
|
||||
user.signal_user_id = signal_user.id
|
||||
user.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def print_identity(message = "Identity:", identity)
|
||||
pad = " " * message.length
|
||||
puts "#{message} #{identity.name} (#{identity.email_address})"
|
||||
puts "#{pad } ID: #{identity.id}"
|
||||
puts "#{pad } Username: #{identity.username}"
|
||||
end
|
||||
|
||||
def queenbee_account_attributes(signal_identity)
|
||||
{
|
||||
skip_remote: true, # Fizzy creates its own local account
|
||||
product_name: "fizzy",
|
||||
name: account_name,
|
||||
owner_identity_id: signal_identity.id,
|
||||
trial: false,
|
||||
subscription: subscription_attributes,
|
||||
remote_request: request_attributes
|
||||
}
|
||||
end
|
||||
|
||||
def subscription_attributes
|
||||
subscription = FreeV1Subscription
|
||||
{ name: subscription.to_param, price: subscription.price }
|
||||
end
|
||||
|
||||
def request_attributes
|
||||
{ user_agent: "script/bootstrap-signal-id.rb" }
|
||||
end
|
||||
|
||||
def account_name
|
||||
name = ApplicationRecord.current_tenant
|
||||
name += " (Beta)" if Rails.env.beta?
|
||||
name
|
||||
end
|
||||
end
|
||||
|
||||
BootstrapSignalId.new(dry_run: false).run
|
||||
@@ -5,4 +5,27 @@ class AccountTest < ActiveSupport::TestCase
|
||||
account = Account.sole
|
||||
assert_equal "/#{ApplicationRecord.current_tenant}", account.slug
|
||||
end
|
||||
|
||||
test ".create_with_admin_user creates a new local account" do
|
||||
ApplicationRecord.create_tenant("account-create-with-dependents") do
|
||||
account = Account.create_with_admin_user(
|
||||
tenant_id: ActiveRecord::FixtureSet.identify("account-create-with-admin-user-test"),
|
||||
account_name: "Account Create With Admin",
|
||||
owner_name: "David",
|
||||
owner_email: "david@37signals.com")
|
||||
|
||||
assert_not_nil account
|
||||
assert account.persisted?
|
||||
assert_equal 1, Account.count
|
||||
assert_equal ActiveRecord::FixtureSet.identify("account-create-with-admin-user-test"), account.tenant_id
|
||||
assert_equal "Account Create With Admin", account.name
|
||||
|
||||
assert_equal 1, User.count
|
||||
User.first.tap do |user|
|
||||
assert_equal "David", user.name
|
||||
assert_equal "david@37signals.com", user.email_address
|
||||
assert_equal "admin", user.role
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user