Created
July 12, 2023 12:13
-
-
Save cintrzyk/98988598d99f463f33c7555d9ca0177f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'dry/monads' | |
require 'dry/matcher/result_matcher' | |
module BaseTransaction | |
include Dry::Monads[:result, :try, :do] | |
include Dry::Matcher.for(:call, with: Dry::Matcher::ResultMatcher) | |
def call | |
run | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# SAMPLE TRANSACTION | |
module Admins | |
module OTP | |
class EnableTransaction | |
extend Dry::Initializer | |
include BaseTransaction | |
option :admin | |
option :password | |
option :otp_attempt | |
option :headers, default: proc { {} } | |
def run | |
yield check_if_otp_enabled | |
yield check_otp_attempt_presence | |
yield check_otp_secret_presence | |
yield verify_password | |
enable_otp! | |
Success() | |
end | |
private | |
def check_if_otp_enabled | |
if admin.otp_enabled? | |
Failure(:otp_already_enabled) | |
else | |
Success() | |
end | |
end | |
def check_otp_attempt_presence | |
if otp_attempt.present? | |
Success() | |
else | |
Failure(:otp_attempt_empty) | |
end | |
end | |
def check_otp_secret_presence | |
if admin.otp_secret.present? | |
Success() | |
else | |
Failure(:otp_secret_empty) | |
end | |
end | |
def verify_password | |
# Fallback to devise for legacy auth | |
if admin.try(:authenticate, password) || admin.try(:valid_password?, password) | |
Success() | |
else | |
Failure(:invalid_password) | |
end | |
end | |
def enable_otp! | |
admin.update!(otp_enabled: true) | |
end | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# TRANSACTION SPEC; "fail_with" is a custom matcher | |
RSpec.describe Admins::OTP::EnableTransaction do | |
subject(:transaction_call) { transaction.call } | |
let(:transaction) { described_class.new(**input) } | |
let(:input) do | |
{ | |
admin: admin, | |
password: password, | |
otp_attempt: otp_attempt, | |
headers: headers | |
} | |
end | |
let(:admin) { create(:admin, otp_secret: ROTP::Base32.random, password: 'password') } | |
let(:password) { 'password' } | |
let(:otp_attempt) { ROTP::TOTP.new(admin.otp_secret).now } | |
let(:headers) { { 'key' => 'value' } } | |
around do |example| | |
freeze_time { example.run } | |
end | |
it 'succeeds' do | |
expect(transaction_call).to be_success | |
end | |
it 'enables mfa' do | |
expect { transaction_call }.to change { admin.reload.otp_enabled }.to(true) | |
end | |
context 'when otp is enabled' do | |
let(:admin) { create(:admin, :with_mfa, password: 'password') } | |
it 'fails with otp_already_enabled' do | |
expect(transaction).to fail_with(:otp_already_enabled) | |
end | |
end | |
context 'when otp_attempt is not present' do | |
let(:otp_attempt) { nil } | |
it 'fails with otp_attempt_empty' do | |
expect(transaction).to fail_with(:otp_attempt_empty) | |
end | |
end | |
context 'when otp_secret is not present' do | |
let(:otp_attempt) { 'asd' } | |
let(:admin) { create(:admin, password: 'password') } | |
it 'fails with otp_secret_empty' do | |
expect(transaction).to fail_with(:otp_secret_empty) | |
end | |
end | |
context 'when password is incorrect' do | |
let(:password) { 'invalid' } | |
it 'fails with invalid_password' do | |
expect(transaction).to fail_with(:invalid_password) | |
end | |
end | |
context 'when otp_attempt is incorrect' do | |
let(:otp_attempt) { 'invalid' } | |
it 'fails with invalid_otp_attempt' do | |
expect(transaction).to fail_with(:invalid_otp_attempt) | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# SAMPLE TRANSACTION USAGE IN API | |
post do | |
transaction.call do |m| | |
m.success do | |
status :no_content | |
end | |
m.failure :otp_already_enabled do | |
handle_error!(error: :otp_already_enabled, code: :bad_request) | |
end | |
m.failure :otp_secret_empty do | |
handle_error!(error: :otp_secret_empty, code: :bad_request) | |
end | |
m.failure :invalid_password do | |
handle_error!(error: :invalid_password, code: :bad_request) | |
end | |
m.failure(&method(:error_500!)) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment