Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .github/workflows/ci_ephemeral_authorization_handler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ jobs:
npm-
- run: bundle exec rake test_app
name: Create test app
- run: |
rm -f ./spec/decidim_dummy_app/app/services/dummy_signature_handler.rb
rm -f ./spec/decidim_dummy_app/app/services/dummy_sms_mobile_phone_validator.rb
name: Remove Initiative-dependent dummy files
- run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots
name: Create the screenshots folder
- uses: nanasess/setup-chromedriver@v2
Expand Down
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ ruby RUBY_VERSION
gem "decidim", "~> 0.31.2"
gem "decidim-ephemeral_authorization_handler", path: "."

gem "puma", ">= 6.3.1"
gem "bootsnap", "~> 1.4"
gem "puma", ">= 6.3.1"

group :development, :test do
gem "byebug", "~> 11.0", platform: :mri
Expand Down
5 changes: 5 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ PATH
remote: .
specs:
decidim-ephemeral_authorization_handler (1.0.0)
countries (~> 5.1, >= 5.1.2)
decidim-core (~> 0.31)

GEM
Expand Down Expand Up @@ -140,6 +141,8 @@ GEM
commonmarker (0.23.12)
concurrent-ruby (1.3.6)
connection_pool (2.5.5)
countries (5.7.2)
unaccent (~> 0.3)
crack (1.0.1)
bigdecimal
rexml
Expand Down Expand Up @@ -506,6 +509,7 @@ GEM
rails (>= 3.2.0)
io-console (0.8.2)
irb (1.17.0)
nokogiri (~> 1.11)
pp (>= 0.6.0)
prism (>= 1.3.0)
rdoc (>= 4.0.0)
Expand Down Expand Up @@ -906,6 +910,7 @@ GEM
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
uber (0.1.0)
unaccent (0.4.0)
unicode-display_width (3.2.0)
unicode-emoji (~> 4.1)
unicode-emoji (4.2.0)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

module Decidim
module EphemeralAuthorizationHandler
module Verification
class BaseVerification < Decidim::Command
def initialize(user)
@user = user
end

protected

def verification_code
@verification_code ||= generate_code
end

def generate_code
code = SecureRandom.random_number(10**auth_code_length).to_s
add_zeros(code)
end

def auth_code_length
::Decidim::EphemeralAuthorizationHandler.auth_code_length
end

def add_zeros(code)
return code if code.length == auth_code_length

("0" * (auth_code_length - code.length)) + code
end

def expires_at
@expires_at ||= Time.zone.now + Decidim::EphemeralAuthorizationHandler.code_ttl
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# frozen_string_literal: true

module Decidim
module EphemeralAuthorizationHandler
module Verification
class SendSmsVerification < BaseVerification
def initialize(form, user)
@form = form
@user = user
end

def call
result = send_sms_verification!

if result
broadcast(:ok, verification_code, expires_at, phone_number)
else
broadcast(:invalid)
end
end

private

attr_reader :form, :user

delegate :current_organization, to: :form

def send_sms_verification!
gateway.deliver_code

gateway.code
rescue StandardError => e
Rails.logger.error e.message

false
end

def gateway
@gateway ||=
if Decidim.config.sms_gateway_service == "Decidim::Sms::Twilio::Gateway"
Decidim.config.sms_gateway_service.constantize.new(phone_number, verification_code, organization: current_organization)
elsif Decidim.config.sms_gateway_service.nil?
Decidim::Verifications::Sms::ExampleGateway.new(phone_number, verification_code)
else
Decidim.config.sms_gateway_service.constantize.new(phone_number, verification_code)
end
end

def phone_with_country_code(country_code, phone_number)
PhoneNumberFormatter.new(phone_number:, iso_country_code: country_code).format
end

def phone_number
@phone_number ||= phone_with_country_code(form.phone_country, form.phone_number)
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

module Decidim
module EphemeralAuthorizationHandler
module Verification
module EphemeralAuthorizationHandlerConcern
extend ActiveSupport::Concern

included do
layout "decidim/application"

def init_sessions!(options = {})
session[:auth_attempt] = options
end

def auth_session
(session[:auth_attempt].presence || {}).with_indifferent_access
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# frozen_string_literal: true

module Decidim
module EphemeralAuthorizationHandler
module Verification
class AuthorizationsController < ::Decidim::ApplicationController
include FormFactory
include EphemeralAuthorizationHandlerConcern
include Decidim::Verifications::Renewable

helper Decidim::EphemeralAuthorizationHandler::Verification::ApplicationHelper

def new
return redirect_to decidim_ephemeral_authorization_handler.sms_authorization_path if user_signed_in?

action = current_user.extended_data.dig("onboarding", "action")
if requested_component.permissions[action]["authorization_handlers"].keys.include?("ephemeral_authorization_handler")
redirect_to decidim_ephemeral_authorization_handler.sms_authorization_path
else
redirect_to decidim_verifications.onboarding_pending_authorizations_path
end
end

def renew
redirect_to decidim_ephemeral_authorization_handler.sms_authorization_path
end

def sms
@form = form(Decidim::EphemeralAuthorizationHandler::Verification::SmsCodeForm).instance
end

def verify_sms_code
@form = form(::Decidim::EphemeralAuthorizationHandler::Verification::VerificationCodeForm).instance
end

def verify
form = form(Decidim::EphemeralAuthorizationHandler::Verification::SmsCodeForm).from_params(params)

SendSmsVerification.call(form, current_user) do
on(:ok) do |result, expires_at, formatted_phone_number|
init_sessions!({ code: result, expires_at: expires_at, formatted_phone_number:, phone_number: params["sms_code"]["phone_number"],
phone_country: params["sms_code"]["phone_country"], strategy: :sms })
flash[:notice] = I18n.t("success", scope: "decidim.ephemeral_authorization_handler.sms", phone_number: phone_number)
redirect_to decidim_ephemeral_authorization_handler.verify_sms_code_authorization_path
end

on(:invalid) do |_error_code|
flash.now[:alert] = I18n.t("error", scope: "decidim.ephemeral_authorization_handler.sms")
redirect_to decidim_ephemeral_authorization_handler.root_path
end
end
end

def edit; end

def verify_submitted_code
return redirect_to decidim_ephemeral_authorization_handler.new_authorization_path if auth_session.blank?

if auth_session[:code] == params[:verification_code][:verification]
# if there is a duplicated authorization associated
# to an ephemeral user and the current user is also ephemeral
# the session is transferred to the user with the existing authorization
if transferable_user?
handler.user = authorization.user
Authorization.create_or_update_from(handler)
handler.user.update(last_sign_in_at: Time.current, deleted_at: nil)
sign_out(current_user)
sign_in(handler.user)
redirect_to handler.user.extended_data.dig("onboarding", "redirect_path")
elsif transferable_handler?
# if there is an existing authorization that can be transferred
handler.user = current_user
transfer = proceed_transfer(authorization, handler)
handle_transfer(transfer)
else
# create Authorization
new_authorization = Decidim::Authorization.find_or_initialize_by(
user: current_user,
name: "ephemeral_authorization_handler"
)
new_authorization.attributes = {
granted_at: Time.current,
unique_id: auth_session[:formatted_phone_number],
metadata: { phone_number: auth_session[:formatted_phone_number] },
verification_metadata: {},
verification_attachment: nil
}
new_authorization.save!
init_sessions!
flash[:notice] = I18n.t("success", scope: "decidim.ephemeral_authorization_handler.verification.authorizations.verify_sms_code")
# redirect for ephemeral user or signed_in user
redirect_to current_user.extended_data.dig("onboarding", "redirect_path") || decidim_verifications.authorizations_path
end
else
# invalid code
flash[:alert] = I18n.t("error", scope: "decidim.ephemeral_authorization_handler.verification.authorizations.verify_sms_code")
redirect_to decidim_ephemeral_authorization_handler.verify_sms_code_authorization_path
end
end

def transferable_user?
authorization.present? && [authorization.user, current_user].all?(&:ephemeral?)
end

def transferable_handler?
authorization.present? && (authorization.user.deleted? || authorization.user.ephemeral?)
end

def proceed_transfer(authorization, handler)
authorization.transfer!(handler)
rescue Decidim::AuthorizationTransfer::DisabledError
Decidim::Verifications::AuthorizeUser.register_conflict
redirect_to decidim_ephemeral_authorization_handler.verify_sms_code_authorization_path
end

def handle_transfer(transfer)
if transfer
message = t("authorizations.create.success", scope: "decidim.verifications")
if transfer.records.any?
flash[:html_safe] = true
message = <<~HTML
<p>#{CGI.escapeHTML(message)}</p>
<p>#{CGI.escapeHTML(t("authorizations.create.transferred", scope: "decidim.verifications"))}</p>
#{transfer.presenter.records_list_html}
HTML
end

flash[:notice] = message
redirect_to transfer.user.extended_data["onboarding"]["redirect_path"]
else
flash[:alert] = I18n.t("error", scope: "decidim.ephemeral_authorization_handler.verification.authorizations.verify_sms_code")
redirect_to decidim_ephemeral_authorization_handler.verify_sms_code_authorization_path
end
end

def authorization
Decidim::Authorization.find_by(
user: Decidim::User.where.not(id: current_user.id).where(organization: current_user.organization),
name: "ephemeral_authorization_handler",
unique_id: auth_session[:formatted_phone_number]
)
end

private

def handler
@handler ||= ::EphemeralAuthorizationHandler.new(phone_number: auth_session[:phone_number], phone_country: auth_session[:phone_country])
end

def requested_component
path = current_user.extended_data.dig("onboarding", "redirect_path")
id = URI.parse(path).path.match(%r{/f/(\d+)/})&.captures&.first
Decidim::Component.find(id)
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module Decidim
module EphemeralAuthorizationHandler
module Verification
class SmsCodeForm < Form
attribute :phone_number, Integer
attribute :phone_country, String

validates :phone_country, presence: true
validates :phone_number, numericality: { greater_than: 0 }, presence: true
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module Decidim
module EphemeralAuthorizationHandler
module Verification
class VerificationCodeForm < Form
attribute :verification, String
attribute :current_locale, String
attribute :organization, Decidim::Organization

validates :verification, presence: true
end
end
end
end

This file was deleted.

Loading
Loading