feat: ensure signup verification [UPM-14] (#13858)
Previously, signing up gave immediate access to the app. Now, unconfirmed users are redirected to a verification page where they can resend the confirmation email. - After signup, the user is routed to `/auth/verify-email` instead of the dashboard - After login, unconfirmed users are redirected to the verification page - The dashboard route guard catches unconfirmed users and redirects them - `active_for_authentication?` is removed from the sessions controller so unconfirmed users can authenticate — the frontend gates access instead - If the user visits the verification page after already confirming, they're automatically redirected to the dashboard - No session is issued until the user is verified <details><summary>Demo</summary> <p> #### Fresh Signup https://github.com/user-attachments/assets/abb735e5-7c8e-44a2-801c-96d9e4823e51 #### Google Fresh Signup https://github.com/user-attachments/assets/ab9e389a-a604-4a9d-b492-219e6d94ee3f #### Create new account from Dashboard https://github.com/user-attachments/assets/c456690d-1946-4e0b-834b-ad8efcea8369 </p> </details> --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
@@ -26,8 +26,8 @@ RSpec.describe 'Accounts API', type: :request do
|
||||
|
||||
expect(AccountBuilder).to have_received(:new).with(params.except(:password).merge(user_password: params[:password]))
|
||||
expect(account_builder).to have_received(:perform)
|
||||
expect(response.headers.keys).to include('access-token', 'token-type', 'client', 'expiry', 'uid')
|
||||
expect(response.body).to include('en')
|
||||
expect(response.headers.keys).not_to include('access-token', 'token-type', 'client', 'expiry', 'uid')
|
||||
expect(response.parsed_body['email']).to eq(email)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -46,8 +46,8 @@ RSpec.describe 'Accounts API', type: :request do
|
||||
as: :json
|
||||
|
||||
expect(ChatwootCaptcha).to have_received(:new).with('123')
|
||||
expect(response.headers.keys).to include('access-token', 'token-type', 'client', 'expiry', 'uid')
|
||||
expect(response.body).to include('en')
|
||||
expect(response.headers.keys).not_to include('access-token', 'token-type', 'client', 'expiry', 'uid')
|
||||
expect(response.parsed_body['email']).to eq(email)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -68,6 +68,23 @@ RSpec.describe 'Accounts API', type: :request do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an authenticated user creates a second account' do
|
||||
let(:existing_user) { create(:user, password: 'Password1!') }
|
||||
|
||||
it 'returns the full response with account_id' do
|
||||
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do
|
||||
post api_v1_accounts_url,
|
||||
params: { account_name: 'Second Account', email: existing_user.email,
|
||||
user_full_name: existing_user.name, password: 'Password1!' },
|
||||
headers: existing_user.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(response.parsed_body.dig('data', 'account_id')).to be_present
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ENABLE_ACCOUNT_SIGNUP env variable is set to false' do
|
||||
it 'responds 404 on requests' do
|
||||
params = { account_name: 'test', email: email, user_full_name: user_full_name }
|
||||
@@ -105,7 +122,17 @@ RSpec.describe 'Accounts API', type: :request do
|
||||
end
|
||||
|
||||
context 'when ENABLE_ACCOUNT_SIGNUP env variable is set to api_only' do
|
||||
it 'does not respond 404 on requests' do
|
||||
before do
|
||||
GlobalConfig.clear_cache
|
||||
InstallationConfig.where(name: 'ENABLE_ACCOUNT_SIGNUP').delete_all
|
||||
end
|
||||
|
||||
after do
|
||||
InstallationConfig.where(name: 'ENABLE_ACCOUNT_SIGNUP').delete_all
|
||||
GlobalConfig.clear_cache
|
||||
end
|
||||
|
||||
it 'returns auth headers and full response for api_only signup' do
|
||||
params = { account_name: 'test', email: email, user_full_name: user_full_name, password: 'Password1!' }
|
||||
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'api_only' do
|
||||
post api_v1_accounts_url,
|
||||
@@ -113,6 +140,21 @@ RSpec.describe 'Accounts API', type: :request do
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(response.headers.keys).to include('access-token', 'token-type', 'client', 'expiry', 'uid')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when CW_API_ONLY_SERVER is true' do
|
||||
it 'returns auth headers and full response' do
|
||||
params = { account_name: 'test', email: email, user_full_name: user_full_name, password: 'Password1!' }
|
||||
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true', CW_API_ONLY_SERVER: 'true' do
|
||||
post api_v1_accounts_url,
|
||||
params: params,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(response.headers.keys).to include('access-token', 'token-type', 'client', 'expiry', 'uid')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Resend Confirmations API', type: :request do
|
||||
describe 'POST /resend_confirmation' do
|
||||
let(:email) { 'unconfirmed@example.com' }
|
||||
|
||||
context 'when the user exists and is unconfirmed' do
|
||||
before { create(:user, email: email, skip_confirmation: false) }
|
||||
|
||||
it 'sends confirmation instructions and returns 200' do
|
||||
expect do
|
||||
post '/resend_confirmation', params: { email: email }, as: :json
|
||||
end.to have_enqueued_mail(Devise::Mailer, :confirmation_instructions)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user exists and is already confirmed' do
|
||||
before { create(:user, email: email) }
|
||||
|
||||
it 'returns 200 without sending confirmation' do
|
||||
expect do
|
||||
post '/resend_confirmation', params: { email: email }, as: :json
|
||||
end.not_to have_enqueued_mail(Devise::Mailer, :confirmation_instructions)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the email does not exist' do
|
||||
it 'returns 200 without leaking email existence' do
|
||||
post '/resend_confirmation', params: { email: 'nobody@example.com' }, as: :json
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when hCaptcha is configured' do
|
||||
before do
|
||||
create(:user, email: email, skip_confirmation: false)
|
||||
allow(ChatwootCaptcha).to receive(:new).and_return(captcha)
|
||||
end
|
||||
|
||||
context 'with a valid captcha response' do
|
||||
let(:captcha) { instance_double(ChatwootCaptcha, valid?: true) }
|
||||
|
||||
it 'sends confirmation instructions' do
|
||||
expect do
|
||||
post '/resend_confirmation',
|
||||
params: { email: email, h_captcha_client_response: 'valid-token' },
|
||||
as: :json
|
||||
end.to have_enqueued_mail(Devise::Mailer, :confirmation_instructions)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an invalid captcha response' do
|
||||
let(:captcha) { instance_double(ChatwootCaptcha, valid?: false) }
|
||||
|
||||
it 'returns 200 without sending confirmation' do
|
||||
expect do
|
||||
post '/resend_confirmation',
|
||||
params: { email: email, h_captcha_client_response: 'bad-token' },
|
||||
as: :json
|
||||
end.not_to have_enqueued_mail(Devise::Mailer, :confirmation_instructions)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user