From 3cd1ddaf23ad99d86f0002ee57991f70fa315a59 Mon Sep 17 00:00:00 2001 From: Bartosz Date: Tue, 8 Sep 2026 12:05:19 +0200 Subject: [PATCH 1/2] Send completed password resets as $profile_reset on Risk --- README.md | 9 +++++---- .../users/password_resets_controller.rb | 17 +++++++++-------- .../users/password_resets/show.html.haml | 12 +++++++----- .../users/password_resets_controller_spec.rb | 19 ++++++++++--------- 4 files changed, 31 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 598823f..b4c84f5 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,11 @@ SDK (9.x). - **login** – successful logins are scored with the `risk` endpoint; failed logins are sent to `filter`. The returned verdict (`allow`, `challenge` or `deny`) drives whether the session is allowed. -- **logout, profile updates, custom events & password reset** – recorded with - the non-blocking `log` endpoint. The custom event is available from the - profile page, and Lists / Privacy / Password reset from the nav, once signed - in. +- **logout, profile updates & custom events** – recorded with the non-blocking + `log` endpoint. The custom event is available from the profile page, and + Lists / Privacy / Password reset from the nav, once signed in. +- **password reset** – `$profile_reset` to `risk` (completed reset, after the + user already passed the reset challenge). - **Lists API** – create a list and fetch all lists with `create_list` / `get_all_lists`. - **Privacy API** – honor GDPR/CCPA access and erasure requests with diff --git a/app/controllers/users/password_resets_controller.rb b/app/controllers/users/password_resets_controller.rb index abf8613..db96394 100644 --- a/app/controllers/users/password_resets_controller.rb +++ b/app/controllers/users/password_resets_controller.rb @@ -1,28 +1,29 @@ # frozen_string_literal: true module Users - # Demonstrates recording a password reset. We assume the user already passed - # the reset challenge (e.g. an emailed OTP) and record the outcome with the - # non-blocking `log` endpoint. The password is not actually changed. + # Demonstrates assessing a completed password reset. We assume the user + # already passed the reset challenge (e.g. an emailed OTP). The password is + # not actually changed. class PasswordResetsController < ApplicationController # Renders the form (and any result from a previous POST). def show; end # Reusing the current password counts as a failed reset; any other value is - # a successful one. Either way we only log the event to Castle. + # a successful one. def create status = current_user.valid_password?(params[:password].to_s) ? '$failed' : '$succeeded' payload = { - type: '$password_reset', + type: '$profile_reset', status: status, request_token: castle_request_token, user: { id: current_user.id.to_s, email: current_user.email } } - result = castle.log(**payload) - record_castle_result(endpoint: 'log', payload: payload, response: result) + payload[:changeset] = { password: { changed: true } } if status == '$succeeded' + result = castle.risk(**payload) + record_castle_result(endpoint: 'risk', payload: payload, response: result) rescue Castle::Error => e - record_castle_result(endpoint: 'log', payload: payload, error: e) + record_castle_result(endpoint: 'risk', payload: payload, error: e) ensure render :show end diff --git a/app/views/users/password_resets/show.html.haml b/app/views/users/password_resets/show.html.haml index 895da56..f4723e2 100644 --- a/app/views/users/password_resets/show.html.haml +++ b/app/views/users/password_resets/show.html.haml @@ -2,16 +2,18 @@ %h2{ class: 'text-[1.4rem]' } Password reset %p.lead - Records the password-reset event with the non-blocking - %code log - endpoint, which stores the event without returning a verdict. + Assesses a completed password reset with + %code $profile_reset + on + %code /risk + \. %p.text-muted{ class: 'text-[0.9rem]' } Assume the user already passed your reset challenge (e.g. an emailed OTP). Enter a value different from your current password to send - %code $password_reset / $succeeded + %code $profile_reset / $succeeded \, or your current password to send - %code $password_reset / $failed + %code $profile_reset / $failed \. (The password is not actually changed.) .card.mt-4{ class: 'max-w-[640px]' } diff --git a/spec/controllers/users/password_resets_controller_spec.rb b/spec/controllers/users/password_resets_controller_spec.rb index 9ce4c35..d32807d 100644 --- a/spec/controllers/users/password_resets_controller_spec.rb +++ b/spec/controllers/users/password_resets_controller_spec.rb @@ -31,18 +31,19 @@ with_user before do - allow(controller.castle).to receive(:log) + allow(controller.castle).to receive(:risk) post :create, params: { password: 'a-brand-new-password' } end it { expect(response).to render_template(:show) } - it 'logs $password_reset / $succeeded' do - expect(controller.castle).to have_received(:log).with( - type: '$password_reset', + it 'risks $profile_reset / $succeeded' do + expect(controller.castle).to have_received(:risk).with( + type: '$profile_reset', status: '$succeeded', request_token: nil, - user: { id: user.id.to_s, email: user.email } + user: { id: user.id.to_s, email: user.email }, + changeset: { password: { changed: true } } ) end end @@ -53,12 +54,12 @@ before do @request.env['devise.mapping'] = Devise.mappings[:user] sign_in user - allow(controller.castle).to receive(:log) + allow(controller.castle).to receive(:risk) post :create, params: { password: 'current-password-1' } end - it 'logs $password_reset / $failed' do - expect(controller.castle).to have_received(:log).with(hash_including(status: '$failed')) + it 'risks $profile_reset / $failed' do + expect(controller.castle).to have_received(:risk).with(hash_including(status: '$failed')) end end @@ -66,7 +67,7 @@ with_user before do - allow(controller.castle).to receive(:log).and_raise(Castle::Error) + allow(controller.castle).to receive(:risk).and_raise(Castle::Error) post :create, params: { password: 'a-brand-new-password' } end From b88f2fe71213819af3c6b937dd6cf2177fe22bdb Mon Sep 17 00:00:00 2001 From: Bartosz Date: Tue, 8 Sep 2026 12:17:32 +0200 Subject: [PATCH 2/2] Log completed password resets as $profile_reset --- README.md | 9 ++++----- .../users/password_resets_controller.rb | 15 +++++++-------- app/views/users/password_resets/show.html.haml | 8 +++----- .../users/password_resets_controller_spec.rb | 17 ++++++++--------- 4 files changed, 22 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index b4c84f5..598823f 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,10 @@ SDK (9.x). - **login** – successful logins are scored with the `risk` endpoint; failed logins are sent to `filter`. The returned verdict (`allow`, `challenge` or `deny`) drives whether the session is allowed. -- **logout, profile updates & custom events** – recorded with the non-blocking - `log` endpoint. The custom event is available from the profile page, and - Lists / Privacy / Password reset from the nav, once signed in. -- **password reset** – `$profile_reset` to `risk` (completed reset, after the - user already passed the reset challenge). +- **logout, profile updates, custom events & password reset** – recorded with + the non-blocking `log` endpoint. The custom event is available from the + profile page, and Lists / Privacy / Password reset from the nav, once signed + in. - **Lists API** – create a list and fetch all lists with `create_list` / `get_all_lists`. - **Privacy API** – honor GDPR/CCPA access and erasure requests with diff --git a/app/controllers/users/password_resets_controller.rb b/app/controllers/users/password_resets_controller.rb index db96394..df5c302 100644 --- a/app/controllers/users/password_resets_controller.rb +++ b/app/controllers/users/password_resets_controller.rb @@ -1,15 +1,15 @@ # frozen_string_literal: true module Users - # Demonstrates assessing a completed password reset. We assume the user - # already passed the reset challenge (e.g. an emailed OTP). The password is - # not actually changed. + # Demonstrates recording a password reset. We assume the user already passed + # the reset challenge (e.g. an emailed OTP) and record the outcome with the + # non-blocking `log` endpoint. The password is not actually changed. class PasswordResetsController < ApplicationController # Renders the form (and any result from a previous POST). def show; end # Reusing the current password counts as a failed reset; any other value is - # a successful one. + # a successful one. Either way we only log the event to Castle. def create status = current_user.valid_password?(params[:password].to_s) ? '$failed' : '$succeeded' @@ -19,11 +19,10 @@ def create request_token: castle_request_token, user: { id: current_user.id.to_s, email: current_user.email } } - payload[:changeset] = { password: { changed: true } } if status == '$succeeded' - result = castle.risk(**payload) - record_castle_result(endpoint: 'risk', payload: payload, response: result) + result = castle.log(**payload) + record_castle_result(endpoint: 'log', payload: payload, response: result) rescue Castle::Error => e - record_castle_result(endpoint: 'risk', payload: payload, error: e) + record_castle_result(endpoint: 'log', payload: payload, error: e) ensure render :show end diff --git a/app/views/users/password_resets/show.html.haml b/app/views/users/password_resets/show.html.haml index f4723e2..43ed078 100644 --- a/app/views/users/password_resets/show.html.haml +++ b/app/views/users/password_resets/show.html.haml @@ -2,11 +2,9 @@ %h2{ class: 'text-[1.4rem]' } Password reset %p.lead - Assesses a completed password reset with - %code $profile_reset - on - %code /risk - \. + Records the password-reset event with the non-blocking + %code log + endpoint, which stores the event without returning a verdict. %p.text-muted{ class: 'text-[0.9rem]' } Assume the user already passed your reset challenge (e.g. an emailed OTP). diff --git a/spec/controllers/users/password_resets_controller_spec.rb b/spec/controllers/users/password_resets_controller_spec.rb index d32807d..025f257 100644 --- a/spec/controllers/users/password_resets_controller_spec.rb +++ b/spec/controllers/users/password_resets_controller_spec.rb @@ -31,19 +31,18 @@ with_user before do - allow(controller.castle).to receive(:risk) + allow(controller.castle).to receive(:log) post :create, params: { password: 'a-brand-new-password' } end it { expect(response).to render_template(:show) } - it 'risks $profile_reset / $succeeded' do - expect(controller.castle).to have_received(:risk).with( + it 'logs $profile_reset / $succeeded' do + expect(controller.castle).to have_received(:log).with( type: '$profile_reset', status: '$succeeded', request_token: nil, - user: { id: user.id.to_s, email: user.email }, - changeset: { password: { changed: true } } + user: { id: user.id.to_s, email: user.email } ) end end @@ -54,12 +53,12 @@ before do @request.env['devise.mapping'] = Devise.mappings[:user] sign_in user - allow(controller.castle).to receive(:risk) + allow(controller.castle).to receive(:log) post :create, params: { password: 'current-password-1' } end - it 'risks $profile_reset / $failed' do - expect(controller.castle).to have_received(:risk).with(hash_including(status: '$failed')) + it 'logs $profile_reset / $failed' do + expect(controller.castle).to have_received(:log).with(hash_including(status: '$failed')) end end @@ -67,7 +66,7 @@ with_user before do - allow(controller.castle).to receive(:risk).and_raise(Castle::Error) + allow(controller.castle).to receive(:log).and_raise(Castle::Error) post :create, params: { password: 'a-brand-new-password' } end