~singpolyma/sgx-jmp

e2b5bdfa8a5676f565895807ce106f6f2b42a202 — Stephen Paul Weber 1 year, 8 months ago 9a30233
Block repeated invite code tries by customer id

So it's not as trivial to brute-force the space and find an open one. Limit is
10 tries per hour.
2 files changed, 94 insertions(+), 3 deletions(-)

M lib/registration.rb
M test/test_registration.rb
M lib/registration.rb => lib/registration.rb +19 -2
@@ 313,16 313,33 @@ class Registration

			def write
				COMMAND_MANAGER.write(@reply).then do |iq|
					verify(iq.form.field("code")&.value&.to_s).then {
					guard_too_many_tries.then {
						verify(iq.form.field("code")&.value&.to_s)
					}.then {
						Finish.new(iq, @customer, @tel)
					}.catch_only(Invalid) { |e|
						InviteCode.new(iq, @customer, @tel, error: e.message)
						invalid_code(iq, e)
					}.then(&:write)
				end
			end

		protected

			def guard_too_many_tries
				REDIS.get("jmp_invite_tries-#{@customer.customer_id}").then do |t|
					raise Invalid, "Too many wrong attempts" if t > 10
				end
			end

			def invalid_code(iq, e)
				EMPromise.all([
					REDIS.incr("jmp_invite_tries-#{@customer.customer_id}").then do
						REDIS.expire("jmp_invite_tries-#{@customer.customer_id}", 60 * 60)
					end,
					InviteCode.new(iq, @customer, @tel, error: e.message)
				]).then(&:last)
			end

			def customer_id
				@customer.customer_id
			end

M test/test_registration.rb => test/test_registration.rb +75 -1
@@ 342,6 342,8 @@ class RegistrationTest < Minitest::Test
		class InviteCodeTest < Minitest::Test
			Registration::Payment::InviteCode::DB =
				Minitest::Mock.new
			Registration::Payment::InviteCode::REDIS =
				Minitest::Mock.new
			Registration::Payment::InviteCode::COMMAND_MANAGER =
				Minitest::Mock.new
			Registration::Payment::InviteCode::Finish =


@@ 349,6 351,11 @@ class RegistrationTest < Minitest::Test

			def test_write
				customer = Customer.new("test", plan_name: "test_usd")
				Registration::Payment::InviteCode::REDIS.expect(
					:get,
					EMPromise.resolve(0),
					["jmp_invite_tries-test"]
				)
				Registration::Payment::InviteCode::COMMAND_MANAGER.expect(
					:write,
					EMPromise.resolve(


@@ 380,12 387,18 @@ class RegistrationTest < Minitest::Test
				).write.sync
				Registration::Payment::InviteCode::COMMAND_MANAGER.verify
				Registration::Payment::InviteCode::DB.verify
				Registration::Payment::InviteCode::REDIS.verify
				Registration::Payment::InviteCode::Finish.verify
			end
			em :test_write

			def test_write_bad_code
				customer = Customer.new("test", plan_name: "test_usd")
				Registration::Payment::InviteCode::REDIS.expect(
					:get,
					EMPromise.resolve(0),
					["jmp_invite_tries-test"]
				)
				Registration::Payment::InviteCode::COMMAND_MANAGER.expect(
					:write,
					EMPromise.resolve(


@@ 401,6 414,16 @@ class RegistrationTest < Minitest::Test
				Registration::Payment::InviteCode::DB.expect(:transaction, []) do
					raise Registration::Payment::InviteCode::Invalid, "wut"
				end
				Registration::Payment::InviteCode::REDIS.expect(
					:incr,
					EMPromise.resolve(nil),
					["jmp_invite_tries-test"]
				)
				Registration::Payment::InviteCode::REDIS.expect(
					:expire,
					EMPromise.resolve(nil),
					["jmp_invite_tries-test", 60 * 60]
				)
				Registration::Payment::InviteCode::COMMAND_MANAGER.expect(
					:write,
					EMPromise.reject(Promise::Error.new),


@@ 420,9 443,60 @@ class RegistrationTest < Minitest::Test
				end
				Registration::Payment::InviteCode::COMMAND_MANAGER.verify
				Registration::Payment::InviteCode::DB.verify
				Registration::Payment::InviteCode::Finish.verify
				Registration::Payment::InviteCode::REDIS.verify
			end
			em :test_write_bad_code

			def test_write_bad_code_over_limit
				customer = Customer.new("test", plan_name: "test_usd")
				Registration::Payment::InviteCode::REDIS.expect(
					:get,
					EMPromise.resolve(11),
					["jmp_invite_tries-test"]
				)
				Registration::Payment::InviteCode::COMMAND_MANAGER.expect(
					:write,
					EMPromise.resolve(
						Blather::Stanza::Iq::Command.new.tap { |iq|
							iq.form.fields = [{ var: "code", value: "abc" }]
						}
					),
					[Matching.new do |reply|
						assert_equal :form, reply.form.type
						assert_nil reply.form.instructions
					end]
				)
				Registration::Payment::InviteCode::REDIS.expect(
					:incr,
					EMPromise.resolve(nil),
					["jmp_invite_tries-test"]
				)
				Registration::Payment::InviteCode::REDIS.expect(
					:expire,
					EMPromise.resolve(nil),
					["jmp_invite_tries-test", 60 * 60]
				)
				Registration::Payment::InviteCode::COMMAND_MANAGER.expect(
					:write,
					EMPromise.reject(Promise::Error.new),
					[Matching.new do |reply|
						assert_equal :form, reply.form.type
						assert_equal "Too many wrong attempts", reply.form.instructions
					end]
				)
				iq = Blather::Stanza::Iq::Command.new
				iq.from = "test@example.com"
				assert_raises Promise::Error do
					Registration::Payment::InviteCode.new(
						iq,
						customer,
						"+15555550000"
					).write.sync
				end
				Registration::Payment::InviteCode::COMMAND_MANAGER.verify
				Registration::Payment::InviteCode::REDIS.verify
			end
			em :test_write_bad_code_over_limit
		end
	end