Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions app-rails/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ To run natively:
1. `make start-native`
1. Then visit http://localhost:3100

#### Local Authentication

You can circumvent typical login and log in using any email and password if you add
```
AUTH_ADAPTER=mock
```

to your .env

#### IDE tips

<details>
Expand Down
55 changes: 36 additions & 19 deletions app-rails/app/adapters/auth/mock_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,38 @@ def self.provider_name
@@provider_name
end

def initiate_auth(email, password)
if email.include?("unconfirmed")
raise Auth::Errors::UserNotConfirmed.new
elsif password == "wrong"
raise Auth::Errors::InvalidCredentials.new
elsif email.include?("mfa")
return {
"challenge_name": "SOFTWARE_TOKEN_MFA",
"session": "mock-session"
}
end

existing_user = User.find_by(email: email)

uid = if existing_user
existing_user.uid
else
@uid_generator.call
end

{
uid: uid,
provider: "mock",
token: generate_token(email)
}
end

def generate_token(email)
# Return a dummy token — apps expecting a token can grab this
JWT.encode({ user_id: email, exp: 24.hours.from_now.to_i }, "mock_secret_key")
end

def create_account(email, password)
if email.include?("UsernameExists")
raise Auth::Errors::UsernameExists.new
Expand All @@ -22,6 +54,7 @@ def create_account(email, password)
end

def change_email(uid, new_email)
# No-op
end

def forgot_password(email)
Expand All @@ -40,24 +73,6 @@ def confirm_forgot_password(email, code, password)
end
end

def initiate_auth(email, password)
if email.include?("unconfirmed")
raise Auth::Errors::UserNotConfirmed.new
elsif password == "wrong"
raise Auth::Errors::InvalidCredentials.new
elsif email.include?("mfa")
return {
"challenge_name": "SOFTWARE_TOKEN_MFA",
"session": "mock-session"
}
end

{
uid: @uid_generator.call,
provider: "mock"
}
end

def associate_software_token(access_token)
"mock-secret"
end
Expand All @@ -69,6 +84,7 @@ def verify_software_token(code, access_token)
end

def disable_software_token(uid)
# No-op
end

def respond_to_auth_challenge(code, challenge)
Expand All @@ -78,7 +94,8 @@ def respond_to_auth_challenge(code, challenge)

{
uid: @uid_generator.call,
provider: "mock"
provider: "mock",
token: generate_token("challenge-user@example.com")
}
end

Expand Down
2 changes: 1 addition & 1 deletion app-rails/app/controllers/users/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def auth_service

def new_session_params
# If :users_new_session_form is renamed, make sure to also update it in
# cognito_authenticatable.rb otherwise login will not work.
# auth_service_authenticatable.rb otherwise login will not work.
params.require(:users_new_session_form).permit(:email, :password, :spam_trap)
end

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# This adds a :cognito_authenticatable accessor for use in the `devise` portion of a user model
# This adds a :auth_service_authenticatable accessor for use in the `devise` portion of a user model
# Heavily inspired by https://www.endpointdev.com/blog/2023/01/using-devise-for-authentication-without-database-stored-accounts/
module Devise
module Models
module CognitoAuthenticatable
module AuthServiceAuthenticatable
extend ActiveSupport::Concern

module ClassMethods
Expand Down
2 changes: 1 addition & 1 deletion app-rails/app/models/user.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class User < ApplicationRecord
devise :cognito_authenticatable, :timeoutable
devise :auth_service_authenticatable, :timeoutable
attr_accessor :access_token

# == Enums ========================================================
Expand Down
21 changes: 19 additions & 2 deletions app-rails/app/services/auth_service.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
# frozen_string_literal: true

class AuthService
def initialize(auth_adapter = Auth::CognitoAdapter.new)
@auth_adapter = auth_adapter
def initialize(auth_adapter = nil)
@auth_adapter = auth_adapter || default_adapter
end

def default_adapter
auth_service = ENV["AUTH_ADAPTER"]

if Rails.env.test?
auth service = "mock"
end

case auth_service
when "cognito"
Auth::CognitoAdapter.new
when "mock"
Auth::MockAdapter.new
else
raise "Unsupported AUTH_SERVICE: #{auth_service}"
end
end

# Send a confirmation code that's required to change the user's password
Expand Down
2 changes: 1 addition & 1 deletion app-rails/config/initializers/devise.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Many of these configuration options can be set straight in your model.
Devise.setup do |config|
# ==> Support AWS Cognito login
Devise.add_module :cognito_authenticatable, controller: :sessions, route: :session
Devise.add_module :auth_service_authenticatable, controller: :sessions, route: :session

# ==> Configuration for :timeoutable
# The time you want to timeout the user session without activity. After this
Expand Down
1 change: 1 addition & 0 deletions app-rails/local.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ AWS_DEFAULT_REGION=<FILL ME IN>
COGNITO_USER_POOL_ID=<FILL ME IN>
COGNITO_CLIENT_ID=<FILL ME IN>
COGNITO_CLIENT_SECRET=<FILL ME IN>
AUTH_ADAPTER=<FILL ME IN>

############################
# Database
Expand Down
Loading