-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Spike One login implementation #10107
base: main
Are you sure you want to change the base?
Conversation
class CreateAccountRecoveryRequests < ActiveRecord::Migration[8.0] | ||
def change | ||
create_table :account_recovery_requests do |t| | ||
t.integer :code, null: false, index: { unique: true } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to encrypt this on the production PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, the AccountRecoveryRequest table should have multiple codes attached to it as we want to allow the user to use multiple codes, all with a arbitrary expiration time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Ha, I should have read the whole thing before I said you should encrypt the code...)
8caf1e9
to
2181c82
Compare
Previously you could not login if you already had a candidate account but no one_login_auth Now you can
And downcase and strip the account recovery email address
@@ -18,6 +18,8 @@ class Candidate < ApplicationRecord | |||
has_many :degree_qualifications, through: :application_forms | |||
has_many :application_choices, through: :application_forms | |||
has_many :application_references, through: :application_forms | |||
has_one :one_login_auth, dependent: :destroy | |||
has_one :account_recovery_request, dependent: :destroy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add specs for these to make sure we only have 1 of each at any time.
def call | ||
@valid_account_recovery_request = AccountRecoveryRequest.where(code:) | ||
.where('created_at >= ?', 1.hour.ago).first | ||
@old_candidate = Candidate.find_by(email_address: valid_account_recovery_request&.previous_account_email) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just thinking of the unlikely, but possible situation where there could be the same code generated for different users in the past hours. Should we loop through them to find the candidate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's a good point, I was hoping that this would always generate a unique code. I think this covers us against this edge case?
apply-for-teacher-training/app/models/account_recovery_request.rb
Lines 9 to 13 in cbdf703
def self.generate_code | |
code = SecureRandom.random_number(100_000..999_999) | |
AccountRecoveryRequest.generate_code while AccountRecoveryRequest.exists?(code:) | |
code | |
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! Thanks for pointing that out. I should read the whole thing before I comment!
That said, I'm not sure this will work, or work efficiently, if we are encrypting the codes. It's something to keep in mind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, good point. I haven't thought about encrypting the code when I wrote this method, I'll do some testing when I create the production PR 👍🏻
normalizes :previous_account_email, with: ->(email) { email.downcase.strip } | ||
|
||
def self.generate_code | ||
code = SecureRandom.random_number(100_000..999_999) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means we wouldn't have any codes that start with 0. If we want 6 random digits, rather than a number between 100_000
and 999_999
, we should do something like 6.times.collect { rand(0...9) }.join
instead -- and it would need to be a string rather that an integer if we want to persist the leading zeros. (also, in general, things with digits aren't numbers unless we are going to do maths to them, eg phone numbers and credit card numbers are strings rather than integers)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sure you will have already thought of this, but we'll also want these to be stored hashed_code
, using bcrypt
or something like that. I haven't done this in awhile, so I can't quite remember, but I remember it being pretty straight forward.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I agree. I'll change it to a true random 6 digit string as you suggested, thanks 👍🏻
@@ -46,6 +48,13 @@ shared: | |||
- withdrawn_or_declined_for_candidate_by_provider | |||
- structured_withdrawal_reasons | |||
- school_placement_auto_selected | |||
account_recovery_requests: | |||
- id | |||
- code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we put the code in the blocked
list? I suppose in practice it will be hashed, but might as well not send it at all as an extra precaution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we should
code: AccountRecoveryRequest.generate_code, | ||
) | ||
|
||
AccountRecoveryMailer.send_code( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only send emails to candidates that exist in our system
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So here is the thing, I did the account recovery thing and tried using my personal email address. And it sent me a code. But there is no candidate in this application with my personal email address. I will try it again and take a video.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this just sends emails to any valid email address. I'll keep this conversation open to remind myself to only send emails to candidates that exist in our system
We also need to allow our users to have access to their one login account, in the header of our pages https://www.sign-in.service.gov.uk/documentation/design-recommendations/let-users-navigate-sign-out |
Context
This is a POC, this PR won't be merged in main but a lot of the code here will be split out in different PRs once we get the green light from product.
This adds one login integration for the candidate path. The magic link implementation is not visible to the candidate.
The candidate will be redirected to one login test env where they can create an account and we will log them in apply. They can also recover their old account if their previous candidate email doesn't match the one login email.
Agreed technical flow(bottom diagram), this can change though
https://lucid.app/lucidspark/3647e5d9-5075-45d4-abd3-088ee4323fc9/edit?invitationId=inv_8027d1a6-8607-4cb9-a43e-7dc1a8975f38&page=0_0#
Changes proposed in this pull request
candidate.create_one_login_auth
which delets the old one and creates a new oneScreencast.2024-11-28.17.33.24.mp4
Guidance for review
Go on review app and try to login as a candidate.
Create a one login account and login.
Click on
Recover account
Add your email gov/dfe address with a +1 before the
@
so[email protected]
You should get an email with a unique code
Enter that code.
You should be logged in.
Try to break this, I have created 3 emails for our team members to recover, so +1,+2,+3
Try putting +100
Put the wrong or a bad email address
Create a new one login account and try to claim it after.
Go nuts
Things to check