mirror of
https://github.com/czhu12/canine.git
synced 2025-12-16 16:35:10 -06:00
remove local authentication -- allow one click passwordless auth
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
31
app/controllers/local/authentication_controller.rb
Normal file
31
app/controllers/local/authentication_controller.rb
Normal 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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(",")
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user