standard_id 0.27.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/standard_id/web/reset_password/start_controller.rb +17 -0
- data/app/controllers/standard_id/web/signup_controller.rb +7 -0
- data/app/controllers/standard_id/web/verify_email/confirm_controller.rb +9 -0
- data/app/controllers/standard_id/web/verify_phone/confirm_controller.rb +9 -0
- data/lib/generators/standard_id/install/templates/standard_id.rb +4 -1
- data/lib/standard_id/config/schema.rb +9 -0
- data/lib/standard_id/passwordless/base_strategy.rb +24 -0
- data/lib/standard_id/passwordless.rb +8 -0
- data/lib/standard_id/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c3aa81db933d1f506926c8c400e2bc57f60a7b29529f7bfa514e71b68fe028c1
|
|
4
|
+
data.tar.gz: 47a3be048ef63b02ee3819354dae0e4af75cb21a88d740e5b72e754c55471a2a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e4b6618506a9428d7a426ad4d095bd95fd2aa9c3f7b58e8727f20e8300d8dc3c6f622da1e07829961474c392d35a292775f4596b36e39c0961f66a28cca2db7a
|
|
7
|
+
data.tar.gz: 585a4b2d8c65e4d9de10eb8466d99c9323ea6279459edf21fe72705f829688cded440133faaa50317a01bcf4d2544ecf48b01749527287be73cc042eb833fe6a
|
|
@@ -7,6 +7,23 @@ module StandardId
|
|
|
7
7
|
|
|
8
8
|
layout "public"
|
|
9
9
|
|
|
10
|
+
# Rate limit reset-request generation by IP (10 per hour). The endpoint
|
|
11
|
+
# emails a reset token, so without a limit it's an email-flooding vector.
|
|
12
|
+
rate_limit to: StandardId.config.rate_limits.password_reset_start_per_ip,
|
|
13
|
+
within: 1.hour,
|
|
14
|
+
name: "reset-password-ip",
|
|
15
|
+
only: :create,
|
|
16
|
+
store: StandardId::RateLimitHandling::RATE_LIMIT_STORE
|
|
17
|
+
|
|
18
|
+
# Rate limit reset requests by email target (3 per 15 minutes) to blunt
|
|
19
|
+
# account-enumeration probing of individual addresses.
|
|
20
|
+
rate_limit to: StandardId.config.rate_limits.password_reset_start_per_target,
|
|
21
|
+
within: 15.minutes,
|
|
22
|
+
by: -> { "reset-password:#{params[:email].to_s.strip.downcase}" },
|
|
23
|
+
name: "reset-password-target",
|
|
24
|
+
only: :create,
|
|
25
|
+
store: StandardId::RateLimitHandling::RATE_LIMIT_STORE
|
|
26
|
+
|
|
10
27
|
skip_before_action :require_browser_session!, only: [:show, :create]
|
|
11
28
|
|
|
12
29
|
def show
|
|
@@ -9,6 +9,13 @@ module StandardId
|
|
|
9
9
|
|
|
10
10
|
layout "public"
|
|
11
11
|
|
|
12
|
+
# Throttle account-creation spam by IP (10 per hour).
|
|
13
|
+
rate_limit to: StandardId.config.rate_limits.signup_per_ip,
|
|
14
|
+
within: 1.hour,
|
|
15
|
+
name: "signup-ip",
|
|
16
|
+
only: :create,
|
|
17
|
+
store: StandardId::RateLimitHandling::RATE_LIMIT_STORE
|
|
18
|
+
|
|
12
19
|
skip_before_action :require_browser_session!, only: [:show, :create]
|
|
13
20
|
|
|
14
21
|
before_action :redirect_if_authenticated, only: [:show]
|
|
@@ -2,6 +2,15 @@ module StandardId
|
|
|
2
2
|
module Web
|
|
3
3
|
module VerifyEmail
|
|
4
4
|
class ConfirmController < BaseController
|
|
5
|
+
# Per-IP limit on code confirmation (20 per 15 minutes, shared with OTP
|
|
6
|
+
# verify). The per-challenge attempt cap alone doesn't stop distributed
|
|
7
|
+
# guessing across many challenges; both show and update probe a code.
|
|
8
|
+
rate_limit to: StandardId.config.rate_limits.otp_verify_per_ip,
|
|
9
|
+
within: 15.minutes,
|
|
10
|
+
name: "verify-email-confirm-ip",
|
|
11
|
+
only: [:show, :update],
|
|
12
|
+
store: StandardId::RateLimitHandling::RATE_LIMIT_STORE
|
|
13
|
+
|
|
5
14
|
before_action :prepare_code_challenge
|
|
6
15
|
|
|
7
16
|
def show
|
|
@@ -2,6 +2,15 @@ module StandardId
|
|
|
2
2
|
module Web
|
|
3
3
|
module VerifyPhone
|
|
4
4
|
class ConfirmController < BaseController
|
|
5
|
+
# Per-IP limit on code confirmation (20 per 15 minutes, shared with OTP
|
|
6
|
+
# verify). The per-challenge attempt cap alone doesn't stop distributed
|
|
7
|
+
# guessing across many challenges; both show and update probe a code.
|
|
8
|
+
rate_limit to: StandardId.config.rate_limits.otp_verify_per_ip,
|
|
9
|
+
within: 15.minutes,
|
|
10
|
+
name: "verify-phone-confirm-ip",
|
|
11
|
+
only: [:show, :update],
|
|
12
|
+
store: StandardId::RateLimitHandling::RATE_LIMIT_STORE
|
|
13
|
+
|
|
5
14
|
before_action :prepare_code_challenge
|
|
6
15
|
|
|
7
16
|
def show
|
|
@@ -153,7 +153,10 @@ StandardId.configure do |c|
|
|
|
153
153
|
# for backwards compatibility; new installs should set the newer key.
|
|
154
154
|
# c.passwordless.max_attempts = 3
|
|
155
155
|
|
|
156
|
-
# Default: 30 —
|
|
156
|
+
# Default: 30 — OTP-resend cooldown: minimum seconds before the same target
|
|
157
|
+
# can request another code. Enforced in the passwordless start path; a resend
|
|
158
|
+
# inside the window is rejected (the already-sent code stays valid). Set to 0
|
|
159
|
+
# to disable the cooldown.
|
|
157
160
|
# c.passwordless.retry_delay = 30
|
|
158
161
|
|
|
159
162
|
# Custom username validator; return nil/false to accept, or an error message
|
|
@@ -352,6 +352,15 @@ StandardId::ConfigSchema.define do
|
|
|
352
352
|
field :verification_start_per_target, type: :integer, default: 3 # per 15 minutes
|
|
353
353
|
field :verification_start_per_ip, type: :integer, default: 10 # per hour
|
|
354
354
|
|
|
355
|
+
# Password-reset request. The start endpoint emails a reset token, so an
|
|
356
|
+
# unthrottled endpoint is an email-flooding + account-enumeration vector.
|
|
357
|
+
# Mirrors the verification_start shape (per-IP hourly + per-target 15-min).
|
|
358
|
+
field :password_reset_start_per_ip, type: :integer, default: 10 # per hour
|
|
359
|
+
field :password_reset_start_per_target, type: :integer, default: 3 # per 15 minutes
|
|
360
|
+
|
|
361
|
+
# Password signup — throttle account-creation spam by IP.
|
|
362
|
+
field :signup_per_ip, type: :integer, default: 10 # per hour
|
|
363
|
+
|
|
355
364
|
# API equivalents
|
|
356
365
|
field :api_passwordless_start_per_ip, type: :integer, default: 10 # per hour
|
|
357
366
|
field :api_passwordless_start_per_target, type: :integer, default: 5 # per 15 minutes
|
|
@@ -23,6 +23,7 @@ module StandardId
|
|
|
23
23
|
|
|
24
24
|
validate_username!(username)
|
|
25
25
|
run_username_validator!(username)
|
|
26
|
+
enforce_retry_delay!(username)
|
|
26
27
|
emit_code_requested(username)
|
|
27
28
|
challenge = create_challenge!(
|
|
28
29
|
username,
|
|
@@ -43,6 +44,29 @@ module StandardId
|
|
|
43
44
|
|
|
44
45
|
protected
|
|
45
46
|
|
|
47
|
+
# Reject a resend that arrives within passwordless.retry_delay seconds of
|
|
48
|
+
# the most recent still-active code for this target. A soft cooldown that
|
|
49
|
+
# complements the coarser per-target rate limits: it does not consume the
|
|
50
|
+
# active challenge, so the code already sent remains usable. Raises
|
|
51
|
+
# InvalidRequestError (rescued by the web login + API OAuth error handlers).
|
|
52
|
+
def enforce_retry_delay!(username)
|
|
53
|
+
delay = StandardId::Passwordless.retry_delay
|
|
54
|
+
return if delay <= 0
|
|
55
|
+
|
|
56
|
+
last = StandardId::CodeChallenge.active
|
|
57
|
+
.where(realm: @realm, channel: connection_type, target: username)
|
|
58
|
+
.order(created_at: :desc)
|
|
59
|
+
.first
|
|
60
|
+
return if last.nil?
|
|
61
|
+
|
|
62
|
+
elapsed = Time.current - last.created_at
|
|
63
|
+
return if elapsed >= delay
|
|
64
|
+
|
|
65
|
+
wait = (delay - elapsed).ceil
|
|
66
|
+
raise StandardId::InvalidRequestError,
|
|
67
|
+
"Please wait #{wait} #{'second'.pluralize(wait)} before requesting another code"
|
|
68
|
+
end
|
|
69
|
+
|
|
46
70
|
def create_challenge!(username, code_length: nil, expires_in: nil, metadata: {})
|
|
47
71
|
ActiveRecord::Base.transaction do
|
|
48
72
|
invalidate_active_challenges!(username)
|
|
@@ -83,6 +83,14 @@ module StandardId
|
|
|
83
83
|
legacy = StandardId.config.passwordless.max_attempts.to_i
|
|
84
84
|
legacy.positive? ? legacy : 5
|
|
85
85
|
end
|
|
86
|
+
|
|
87
|
+
# Minimum seconds that must elapse between successive code requests for
|
|
88
|
+
# the same target (the OTP-resend cooldown). Reads passwordless.retry_delay;
|
|
89
|
+
# 0 or negative disables the cooldown. Enforced in the passwordless strategy
|
|
90
|
+
# start path (see BaseStrategy#enforce_retry_delay!).
|
|
91
|
+
def retry_delay
|
|
92
|
+
StandardId.config.passwordless.retry_delay.to_i
|
|
93
|
+
end
|
|
86
94
|
end
|
|
87
95
|
end
|
|
88
96
|
end
|
data/lib/standard_id/version.rb
CHANGED