remove local authentication -- allow one click passwordless auth

This commit is contained in:
Chris
2025-09-20 23:48:41 -04:00
parent e5af5eb67c
commit e94d512258
11 changed files with 71 additions and 167 deletions

View File

@@ -1,20 +0,0 @@
class Local::CreateDefaultUser
BASE_DOMAIN = "oncanine.run"
extend LightService::Action
promises :user, :account
executed do |context|
ActiveRecord::Base.transaction do
context.user = User.first || User.new(
email: "#{ENV["CANINE_USERNAME"].presence || SecureRandom.uuid}@#{BASE_DOMAIN}",
password: ENV["CANINE_PASSWORD"].presence || "password",
password_confirmation: ENV["CANINE_PASSWORD"].presence || "password",
admin: true
)
context.user.save!
context.account = context.user.accounts.first || Account.create!(name: "Default", owner: context.user)
context.account.account_users.find_or_create_by!(user: context.user)
end
end
end

View File

@@ -8,11 +8,7 @@ class ApplicationController < ActionController::Base
skip_before_action :verify_authenticity_token
before_action :configure_permitted_parameters, if: :devise_controller?
# if Rails.application.config.local_mode
# include Local::Authentication
# else
before_action :authenticate_user!
# end
layout :determine_layout

View File

@@ -0,0 +1,31 @@
class Local::AuthenticationController < ApplicationController
skip_before_action :authenticate_user!
before_action :load_account_from_slug
before_action :check_if_passwordless_allowed
def login
if @account.stack_manager.present?
redirect_to account_sign_in_path(@account.slug)
elsif @account.users.count > 1
redirect_to account_sign_in_path(@account.slug)
else
user = @account.owner
sign_in(user)
session[:account_id] = @account.id
flash[:notice] = "Logged in successfully"
redirect_to root_path
end
end
private
def check_if_passwordless_allowed
unless Rails.application.config.local_mode_passwordless
redirect_to new_user_session_path
end
end
def load_account_from_slug
@account = Account.friendly.find(params[:id])
end
end

View File

@@ -1,42 +0,0 @@
module Local::Authentication
extend ActiveSupport::Concern
included do
before_action :authenticate_user!
before_action :set_github_token_if_not_exists, if: :current_user
end
def set_github_token_if_not_exists
if current_user.providers.empty?
# Redirect to github
redirect_to github_token_path
end
end
def current_user
@current_user
end
def current_account
@current_account
end
def authenticate_user!
if User.count.zero?
Local::CreateDefaultUser.execute
end
if ENV["CANINE_USERNAME"].presence && ENV["CANINE_PASSWORD"].presence
authenticate_or_request_with_http_basic do |username, password|
@current_user = User.find_by!(email: "#{username}@#{Local::CreateDefaultUser::BASE_DOMAIN}")
@current_account = @current_user.accounts.first
end
else
@current_user = User.first
@current_account = @current_user.accounts.first
end
rescue StandardError => e
Rails.logger.error "Error authenticating user: #{e.message}"
# Logout http basic auth
request_http_basic_authentication
end
end

View File

@@ -48,14 +48,14 @@
<%= f.submit "Create Account", class: "btn btn-primary w-full" %>
</div>
<% end %>
<div class="flex items-center my-4">
<div class="flex-grow border-t border-gray-600"></div>
<span class="mx-4 text-gray-500">OR</span>
<div class="flex-grow border-t border-gray-600"></div>
</div>
<%= render "devise/shared/links" %>
<% unless Rails.application.config.local_mode %>
<div class="flex items-center my-4">
<div class="flex-grow border-t border-gray-600"></div>
<span class="mx-4 text-gray-500">OR</span>
<div class="flex-grow border-t border-gray-600"></div>
</div>
<%= render "devise/shared/links" %>
<% end %>
</div>
</div>
</div>

View File

@@ -1,4 +1,9 @@
<%= link_to account_sign_in_url(account), class: "block group" do %>
<% redirect_url = if Rails.application.config.local_mode && Rails.application.config.local_mode_passwordless
login_local_authentication_url(account.slug)
else
account_sign_in_url(account)
end %>
<%= link_to redirect_url, class: "block group" do %>
<div class="bg-base-200 p-5 rounded-xl hover:bg-base-300 transition-all duration-300 hover:shadow-2xl hover:shadow-white/10 border border-base-300 hover:border-white/30 transform hover:scale-[1.02] hover:-translate-y-1">
<div class="flex items-center gap-4">
<div class="relative">

View File

@@ -41,13 +41,15 @@
<%= f.submit "Sign in", class: "btn btn-primary w-full" %>
</div>
<% end %>
<% unless Rails.application.config.local_mode %>
<div class="flex items-center my-4">
<div class="flex-grow border-t border-gray-600"></div>
<span class="mx-4 text-gray-500">OR</span>
<div class="flex-grow border-t border-gray-600"></div>
</div>
<%= render "devise/shared/links" %>
<div class="flex items-center my-4">
<div class="flex-grow border-t border-gray-600"></div>
<span class="mx-4 text-gray-500">OR</span>
<div class="flex-grow border-t border-gray-600"></div>
</div>
<%= render "devise/shared/links" %>
<% end %>
</div>
</div>
</div>

View File

@@ -15,6 +15,7 @@ module Canine
end
config.local_mode = config.boot_mode == "local"
config.local_mode_passwordless = ENV.fetch("LOCAL_MODE_PASSWORDLESS", "false") == "true"
config.cloud_mode = config.boot_mode == "cloud"
config.cluster_mode = config.boot_mode == "cluster"
config.onboarding_methods = ENV.fetch("ONBOARDING_METHODS", "").split(",")

View File

@@ -140,17 +140,16 @@ Rails.application.routes.draw do
# Public marketing homepage
if Rails.application.config.local_mode
namespace :local do
resources :authentication do
member do
get :login
end
end
resources :onboarding, only: [ :index, :create ] do
collection do
post :verify_url
end
end
resource :portainer, only: [ :show, :update ] do
resources :sessions, only: [ :new, :create ], controller: 'portainer/sessions'
collection do
get :github_oauth
end
end
end
if Rails.application.config.onboarding_methods.any?
root to: "local/onboarding#index"

View File

@@ -1,3 +1,10 @@
x-shared-env: &shared-env
DATABASE_URL: postgres://postgres:password@postgres:5432
BOOT_MODE: local
SECRET_KEY_BASE: a38fcb39d60f9d146d2a0053a25024b9
APP_HOST: http://localhost:${PORT:-3000}
LOCAL_MODE_PASSWORDLESS: true
services:
postgres:
image: postgres:16
@@ -24,14 +31,9 @@ services:
- "${PORT:-3000}:${PORT:-3000}"
- "3200:3200"
environment:
- DATABASE_URL=postgres://postgres:password@postgres:5432
- PORT=${PORT:-3000}
- CANINE_USERNAME=${CANINE_USERNAME}
- CANINE_PASSWORD=${CANINE_PASSWORD}
- BOOT_MODE=local
# This docker file is only expected to be used in the laptop deployment, so we can hardcode the secret key base
- SECRET_KEY_BASE=a38fcb39d60f9d146d2a0053a25024b9
- APP_HOST=http://localhost:${PORT:-3000}
<<: *shared-env
PORT: ${PORT:-3000}
ACCOUNT_SIGN_IN_ONLY: "true"
volumes:
- ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock
@@ -43,13 +45,8 @@ services:
depends_on:
- postgres
environment:
- DATABASE_URL=postgres://postgres:password@postgres:5432
- CANINE_USERNAME=${CANINE_USERNAME}
- CANINE_PASSWORD=${CANINE_PASSWORD}
- LOCAL_MODE=true
- BOOT_MODE=local
- SECRET_KEY_BASE=a38fcb39d60f9d146d2a0053a25024b9
- APP_HOST=http://localhost:${PORT:-3000}
<<: *shared-env
LOCAL_MODE: "true"
volumes:
- ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock

View File

@@ -1,65 +0,0 @@
require 'rails_helper'
RSpec.describe Local::CreateDefaultUser do
let(:base_domain) { "oncanine.run" }
describe '.execute' do
subject { described_class.execute }
context 'when no users exist' do
it 'creates a new user with default credentials' do
expect { subject }.to change { User.count }.by(1)
user = subject.user
expect(user.email).to match(/^[a-f0-9-]+@#{base_domain}$/)
expect(user).to be_admin
expect(user.valid_password?("password")).to be true
end
it 'creates a default account' do
expect { subject }.to change { Account.count }.by(1)
account = subject.account
expect(account.name).to eq("Default")
expect(account.owner).to eq(subject.user)
end
it 'creates an account user association' do
expect { subject }.to change { AccountUser.count }.by(1)
account_user = subject.account.account_users.first
expect(account_user.user).to eq(subject.user)
end
end
context 'when environment variables are set' do
before do
allow(ENV).to receive(:[]).with("CANINE_USERNAME").and_return("testuser")
allow(ENV).to receive(:[]).with("CANINE_PASSWORD").and_return("testpass")
end
it 'uses environment variables for user creation' do
subject
user = subject.user
expect(user.email).to eq("testuser@#{base_domain}")
expect(user.valid_password?("testpass")).to be true
end
end
context 'when a user already exists' do
let!(:existing_user) { create(:user) }
let!(:existing_account) { create(:account, owner: existing_user) }
it 'returns the existing user' do
expect { subject }.not_to change { User.count }
expect(subject.user).to eq(existing_user)
end
it 'returns the existing account' do
expect { subject }.not_to change { Account.count }
expect(subject.account).to eq(existing_user.accounts.first)
end
end
end
end