diff --git a/lib/clerk.rb b/lib/clerk.rb index 27feeaa..f65725d 100644 --- a/lib/clerk.rb +++ b/lib/clerk.rb @@ -2,6 +2,7 @@ require_relative "clerk/version" require_relative "clerk/sdk" +require_relative "clerk/constants" module Clerk class << self diff --git a/lib/clerk/authenticatable.rb b/lib/clerk/authenticatable.rb index 4539b1e..c11db14 100644 --- a/lib/clerk/authenticatable.rb +++ b/lib/clerk/authenticatable.rb @@ -73,6 +73,16 @@ def clerk_user_signed_in? !!clerk_verified_session_claims end + def clerk_session_needs_reverification?(params=StepUp::PRESETS[:strict]) + !request.env['clerk'].is_user_reverified?(params) + end + + def clerk_render_reverification(missing_config=nil) + payload = request.env['clerk'].reverification_mismatch_payload(missing_config) + + render status: 403, json: payload + end + def clerk_sign_in_url ENV.fetch("CLERK_SIGN_IN_URL") end diff --git a/lib/clerk/constants.rb b/lib/clerk/constants.rb new file mode 100644 index 0000000..968b08a --- /dev/null +++ b/lib/clerk/constants.rb @@ -0,0 +1,10 @@ +module Clerk + module StepUp + PRESETS = { + very_strict: { after_minutes: 10, level: :multi_factor }, + strict: { after_minutes: 10, level: :second_factor }, + moderate: { after_minutes: 60, level: :second_factor }, + lax: { after_minutes: 1440, level: :second_factor } + } + end +end diff --git a/lib/clerk/rack_middleware_v2.rb b/lib/clerk/rack_middleware_v2.rb index 9216b0f..c229451 100644 --- a/lib/clerk/rack_middleware_v2.rb +++ b/lib/clerk/rack_middleware_v2.rb @@ -60,6 +60,48 @@ def org_permissions @session_claims["org_permissions"] end + # Returns true if the session needs to perform step up verification + def is_user_reverified?(params) + return false if session_claims.nil? + + fva = session_claims["fva"] + level = params[:level] + after_minutes = Integer(params[:after_minutes]) + + return false if fva.nil? || after_minutes.nil? || level.nil? + + factor1_age, factor2_age = fva + is_valid_factor1 = factor1_age != -1 && after_minutes > factor1_age + is_valid_factor2 = factor2_age != -1 && after_minutes > factor2_age + + case level + when :first_factor + is_valid_factor1 + when :second_factor + factor2_age == -1 ? is_valid_factor1 : is_valid_factor2 + when :multi_factor + factor2_age == -1 ? is_valid_factor1 : is_valid_factor1 && is_valid_factor2 + end + end + + def reverification_mismatch_payload(missing_config) + { + clerk_error: { + type: "forbidden", + reason: "reverification-mismatch", + metadata: { reverification: missing_config, } + } + } + end + + def reverification_response(missing_config=nil) + [ + 403, + { "Content-Type" => "application/json" }, + [reverification_mismatch_payload(missing_config).to_json], + ] + end + private def fetch_user(user_id)