Merge pull request #859 from basecamp/flavorones/allow-local-auth
Allow local authentication
This commit is contained in:
@@ -7,6 +7,8 @@ module Authentication
|
||||
|
||||
before_action :require_authentication
|
||||
helper_method :authenticated?
|
||||
|
||||
include LoginHelper
|
||||
end
|
||||
|
||||
class_methods do
|
||||
@@ -53,11 +55,9 @@ module Authentication
|
||||
def request_authentication(untenanted: false)
|
||||
if ApplicationRecord.current_tenant.present?
|
||||
session[:return_to_after_authenticating] = request.url
|
||||
redirect_to Launchpad.login_url(product: true, account: Account.sole), allow_other_host: true
|
||||
else
|
||||
# Don't save the current untenanted URL, because it's just going to bounce back to Launchpad after login anyway.
|
||||
redirect_to Launchpad.login_url(product: true), allow_other_host: true
|
||||
end
|
||||
|
||||
redirect_to_login_url
|
||||
end
|
||||
|
||||
def after_authentication_url
|
||||
|
||||
@@ -1,6 +1,27 @@
|
||||
class SessionsController < ApplicationController
|
||||
before_action :require_local_auth, only: %i[ new create ]
|
||||
require_unauthenticated_access only: %i[ new create ]
|
||||
rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_session_path, alert: "Try again later." }
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
def create
|
||||
if user = User.active.authenticate_by(params.permit(:email_address, :password))
|
||||
start_new_session_for user
|
||||
redirect_to after_authentication_url
|
||||
else
|
||||
redirect_to new_session_path, alert: "Try another email address or password."
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
terminate_session
|
||||
redirect_to Launchpad.login_url, allow_other_host: true
|
||||
redirect_to_login_url
|
||||
end
|
||||
|
||||
private
|
||||
def require_local_auth
|
||||
head :forbidden unless Rails.application.config.x.local_authentication
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
class Signup::BaseController < ApplicationController
|
||||
require_untenanted_access
|
||||
before_action :redirect_if_local_auth
|
||||
|
||||
# TODO: Remove this auth before launch.
|
||||
http_basic_authenticate_with(
|
||||
@@ -33,4 +34,8 @@ class Signup::BaseController < ApplicationController
|
||||
redirect_to account.signal_account.owner.remote_login_url(proceed_to: root_path),
|
||||
allow_other_host: true
|
||||
end
|
||||
|
||||
def redirect_if_local_auth
|
||||
render plain: "Unauthorized", status: :unauthorized if Rails.application.config.x.local_authentication
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
module LoginHelper
|
||||
def login_url
|
||||
if ApplicationRecord.current_tenant
|
||||
if Rails.application.config.x.local_authentication
|
||||
new_session_path
|
||||
else
|
||||
Launchpad.login_url(product: true, account: Account.sole)
|
||||
end
|
||||
else
|
||||
Launchpad.login_url(product: true)
|
||||
end
|
||||
end
|
||||
|
||||
def redirect_to_login_url
|
||||
redirect_to login_url, allow_other_host: true
|
||||
end
|
||||
end
|
||||
@@ -2,7 +2,9 @@ module Account::SignalAccount
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
belongs_to :signal_account, class_name: "SignalId::Account", primary_key: :queenbee_id, foreign_key: :queenbee_id
|
||||
unless Rails.application.config.x.local_authentication
|
||||
belongs_to :signal_account, class_name: "SignalId::Account", primary_key: :queenbee_id, foreign_key: :queenbee_id, optional: true
|
||||
end
|
||||
end
|
||||
|
||||
class_methods do
|
||||
|
||||
@@ -2,6 +2,8 @@ module User::SignalUser
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
belongs_to :signal_user, dependent: :destroy, class_name: "SignalId::User", optional: true
|
||||
unless Rails.application.config.x.local_authentication
|
||||
belongs_to :signal_user, dependent: :destroy, class_name: "SignalId::User", optional: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<% @page_title = "Sign in" %>
|
||||
|
||||
<div class="panel shadow center margin-block-double <%= "shake" if flash[:alert] %>" style="--panel-size: 55ch;">
|
||||
<h1 class="txt-xx-large margin-block-end-double">Fizzy</h1>
|
||||
|
||||
<%= form_with url: session_path, class: "flex flex-column gap txt-large" do |form| %>
|
||||
<div class="flex align-center gap">
|
||||
<label class="flex align-center gap input input--actor">
|
||||
<%= form.email_field :email_address, required: true, class: "input full-width", autofocus: true, autocomplete: "username", placeholder: "Enter your email address" %>
|
||||
<%= icon_tag "email", class: "txt-large" %>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex align-center gap">
|
||||
<label class="flex align-center gap input input--actor">
|
||||
<%= form.password_field :password, required: true, class: "input full-width", autocomplete: "current-password", placeholder: "Enter your password", maxlength: 72 %>
|
||||
<%= icon_tag "password", class: "txt-large" %>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="log_in" class="btn btn--reversed center">
|
||||
<%= icon_tag "arrow-right" %>
|
||||
<span class="for-screen-reader">Sign in</span>
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% content_for :footer do %>
|
||||
<div class="txt-align-center center margin-block-double txt-subtle">Fizzy™ version 0</div>
|
||||
<% end %>
|
||||
@@ -52,7 +52,7 @@
|
||||
<footer class="margin-block-start-double txt-center">
|
||||
<p class="txt-small txt-muted">
|
||||
Already have an account?
|
||||
<%= link_to "Sign in", Launchpad.login_url(product: true), class: "decorated" %>
|
||||
<%= link_to "Sign in", login_url, class: "decorated" %>
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<nav class="header">
|
||||
<div class="header__actions header__actions--start"></div>
|
||||
<div class="header__actions header__actions--end">
|
||||
<%= link_to Launchpad.login_url(account: Account.sole), class: "btn", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<%= link_to login_url, class: "btn", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="for-screen-reader">Sign in instead</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -8,4 +8,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
|
||||
fi
|
||||
|
||||
exec ./bin/rails server -p 3006
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
require "signal_id"
|
||||
|
||||
Rails.application.config.x.local_authentication = ENV["LOCAL_AUTHENTICATION"].present?
|
||||
|
||||
ENV["SIGNAL_ID_SECRET"] = Rails.application.credentials.signal_id_secret
|
||||
|
||||
Rails.application.config.to_prepare do
|
||||
SignalId.product = "fizzy"
|
||||
|
||||
db_config = SignalId::Database.default_configuration
|
||||
if Rails.application.config.x.local_authentication
|
||||
db_config.each do |name, config|
|
||||
config["connect_timeout"] = 1
|
||||
end
|
||||
end
|
||||
SignalId::Database.load_configuration db_config
|
||||
SignalId::Database.enable_rw_splitting!
|
||||
|
||||
|
||||
+1
-1
@@ -89,7 +89,7 @@ Rails.application.routes.draw do
|
||||
get "join/:join_code", to: "users#new", as: :join
|
||||
post "join/:join_code", to: "users#create"
|
||||
|
||||
resource :session, only: :destroy do
|
||||
resource :session do
|
||||
scope module: "sessions" do
|
||||
resources :transfers, only: %i[ show update ]
|
||||
resource :launchpad, only: %i[ show update ], controller: "launchpad"
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
class RemovePasswordFromUsers < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
remove_column :users, :password, :string
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class AllowNullForAccountsQueenbeeId < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
change_column_null :accounts, :queenbee_id, true
|
||||
end
|
||||
end
|
||||
Generated
+2
-2
@@ -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_07_23_165724) do
|
||||
ActiveRecord::Schema[8.1].define(version: 2025_08_06_174718) do
|
||||
create_table "accesses", force: :cascade do |t|
|
||||
t.datetime "accessed_at"
|
||||
t.integer "collection_id", null: false
|
||||
@@ -28,7 +28,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_07_23_165724) do
|
||||
t.datetime "created_at", null: false
|
||||
t.string "join_code"
|
||||
t.string "name", null: false
|
||||
t.integer "queenbee_id", null: false
|
||||
t.integer "queenbee_id"
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["queenbee_id"], name: "index_accounts_on_queenbee_id", unique: true
|
||||
end
|
||||
|
||||
+2
-2
@@ -128,7 +128,7 @@ columns:
|
||||
name: queenbee_id
|
||||
cast_type: *3
|
||||
sql_type_metadata: *4
|
||||
'null': false
|
||||
'null': true
|
||||
default:
|
||||
default_function:
|
||||
collation:
|
||||
@@ -2703,4 +2703,4 @@ indexes:
|
||||
comment:
|
||||
valid: true
|
||||
workflows: []
|
||||
version: 20250723165724
|
||||
version: 20250806174718
|
||||
|
||||
@@ -15,6 +15,24 @@ class ControllerAuthenticationTest < ActionDispatch::IntegrationTest
|
||||
assert_redirected_to Launchpad.login_url(product: true, account: Account.sole)
|
||||
end
|
||||
|
||||
test "local auth, access without an account slug redirects to launchpad" do
|
||||
integration_session.default_url_options[:script_name] = "" # no tenant
|
||||
|
||||
with_local_auth do
|
||||
get cards_path
|
||||
end
|
||||
|
||||
assert_redirected_to Launchpad.login_url(product: true)
|
||||
end
|
||||
|
||||
test "local auth, access with an account slug but no session redirects to new session" do
|
||||
with_local_auth do
|
||||
get cards_path
|
||||
end
|
||||
|
||||
assert_redirected_to new_session_path
|
||||
end
|
||||
|
||||
test "access with an account slug and a session allows functional access" do
|
||||
sign_in_as :kevin
|
||||
|
||||
|
||||
@@ -3,8 +3,59 @@ require "test_helper"
|
||||
class SessionsControllerTest < ActionDispatch::IntegrationTest
|
||||
test "destroy" do
|
||||
sign_in_as :kevin
|
||||
|
||||
delete session_path
|
||||
assert_redirected_to Launchpad.login_url
|
||||
|
||||
assert_redirected_to Launchpad.login_url(account: Account.sole)
|
||||
assert_not cookies[:session_token].present?
|
||||
end
|
||||
|
||||
test "local auth, destroy" do
|
||||
sign_in_as :kevin
|
||||
|
||||
with_local_auth do
|
||||
delete session_path
|
||||
end
|
||||
|
||||
assert_redirected_to new_session_path
|
||||
assert_not cookies[:session_token].present?
|
||||
end
|
||||
|
||||
test "new" do
|
||||
get new_session_path
|
||||
|
||||
assert_response :forbidden
|
||||
end
|
||||
|
||||
test "local auth, new" do
|
||||
with_local_auth do
|
||||
get new_session_path
|
||||
end
|
||||
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "create" do
|
||||
post session_path, params: { email_address: "david@37signals.com", password: "secret123456" }
|
||||
|
||||
assert_response :forbidden
|
||||
end
|
||||
|
||||
test "local auth, create with valid credentials" do
|
||||
with_local_auth do
|
||||
post session_path, params: { email_address: "david@37signals.com", password: "secret123456" }
|
||||
end
|
||||
|
||||
assert_redirected_to root_path
|
||||
assert cookies[:session_token].present?
|
||||
end
|
||||
|
||||
test "local auth, create with invalid credentials" do
|
||||
with_local_auth do
|
||||
post session_path, params: { email_address: "david@37signals.com", password: "wrong" }
|
||||
end
|
||||
|
||||
assert_redirected_to new_session_path
|
||||
assert_not cookies[:session_token].present?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,6 +29,16 @@ module ActiveSupport
|
||||
include ActiveJob::TestHelper
|
||||
include SignalId::Testing
|
||||
include ActionTextTestHelper, CardTestHelper, ChangeTestHelper, SessionTestHelper
|
||||
|
||||
def with_local_auth
|
||||
begin
|
||||
old_local_auth = Rails.application.config.x.local_authentication
|
||||
Rails.application.config.x.local_authentication = true
|
||||
yield
|
||||
ensure
|
||||
Rails.application.config.x.local_authentication = old_local_auth
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user