Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
37f40bb
chore: migrate login routes to inertia
stefanbinoj Dec 15, 2025
b921ce9
chore: inertia rendering update
stefanbinoj Dec 15, 2025
1f72a51
chore: ssr update
stefanbinoj Dec 15, 2025
3bff14a
fix : redirects on successful authentication
stefanbinoj Dec 15, 2025
d794a13
Merge branch 'antiwork:main' into inertia/login
stefanbinoj Dec 15, 2025
95769b7
Merge branch 'main' of https://github.com/antiwork/gumroad into inert…
stefanbinoj Dec 15, 2025
85e2d43
Merge branch 'inertia/login' of https://github.com/stefanbinoj/gumroa…
stefanbinoj Dec 15, 2025
c28aaaf
chore: update tests
stefanbinoj Dec 15, 2025
a93fa19
chore: fixxing tests
stefanbinoj Dec 15, 2025
77a14b1
chore: minor function name for better understandability
stefanbinoj Dec 15, 2025
d6bdd36
update comments
stefanbinoj Dec 16, 2025
0565f7b
remove comments
stefanbinoj Dec 16, 2025
b9b0dc7
remove unecessary json req api
stefanbinoj Dec 17, 2025
6a4009a
fixed comments
stefanbinoj Dec 17, 2025
96a1432
fix tests
stefanbinoj Dec 17, 2025
c82cdb5
chore: add bad_request status code
stefanbinoj Dec 18, 2025
d489e43
Merge branch 'main' into inertia/login
EmCousin Dec 18, 2025
1d0d65f
fix lints
stefanbinoj Dec 18, 2025
dbf5188
fix: recaptcha error
stefanbinoj Dec 19, 2025
f73643f
fix test-0
stefanbinoj Dec 19, 2025
571ea47
priority fixed
stefanbinoj Dec 19, 2025
a00d6f6
fix position
stefanbinoj Dec 19, 2025
fe02982
added json test cases
stefanbinoj Dec 19, 2025
2be128c
Merge branch 'main' into inertia/login
stefanbinoj Dec 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion app/controllers/concerns/inertia_rendering.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ module InertiaRendering
included do
inertia_share do
RenderingExtension.custom_context(view_context).merge(
current_user: current_user_props(current_user, impersonated_user),
authenticity_token: form_authenticity_token,
flash: inertia_flash_props,
title: @title
)
end
inertia_share if: :user_signed_in? do
{ current_user: current_user_props(current_user, impersonated_user) }
end
end

private
Expand Down
30 changes: 18 additions & 12 deletions app/controllers/logins_controller.rb
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
# frozen_string_literal: true

class LoginsController < Devise::SessionsController
include OauthApplicationConfig, ValidateRecaptcha
include OauthApplicationConfig, ValidateRecaptcha, InertiaRendering

skip_before_action :check_suspended, only: %i[create destroy]
before_action :block_json_request, only: :new
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated block_json_request to handle case of inertia explicitly

after_action :clear_dashboard_preference, only: :destroy
before_action :reset_impersonated_user, only: :destroy
before_action :set_noindex_header, only: :new, if: -> { params[:next]&.start_with?("/oauth/authorize") }

layout "inertia", only: [:new]

def new
@hide_layouts = true
Copy link
Contributor Author

@stefanbinoj stefanbinoj Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is removed because, earlier this instance variable was used in context of application.html.erb for not loading sidebar, but now this instance variable is used in context of inertia.html.erb

@load_recaptcha = true
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instance variable to load recaptcha script tag in inertia.html.erb

return redirect_to login_path(next: request.referrer) if params[:next].blank? && request_referrer_is_a_valid_after_login_path?
@auth_presenter = AuthPresenter.new(params:, application: @application)

auth_presenter = AuthPresenter.new(params:, application: @application)
render inertia: "Logins/New", props: auth_presenter.login_props
end

def create
site_key = GlobalConfig.get("RECAPTCHA_LOGIN_SITE_KEY")
if !(Rails.env.development? && site_key.blank?) && !valid_recaptcha_response?(site_key: site_key)
return respond_with_login_failure("Sorry, we could not verify the CAPTCHA. Please try again.")
return redirect_with_login_error("Sorry, we could not verify the CAPTCHA. Please try again.")
end

if params["user"].instance_of?(ActionController::Parameters)
Expand All @@ -27,11 +31,11 @@ def create
@user = User.where(email: login_identifier).first || User.where(username: login_identifier).first if login_identifier.present?
end

return respond_with_login_failure("An account does not exist with that email.") if @user.blank?
return redirect_with_login_error("An account does not exist with that email.") if @user.blank?

return respond_with_login_failure("Please try another password. The one you entered was incorrect.") unless @user.valid_password?(password)
return redirect_with_login_error("Please try another password. The one you entered was incorrect.") unless @user.valid_password?(password)

return respond_with_login_failure("You cannot log in because your account was permanently deleted. Please sign up for a new account to start selling!") if @user.deleted?
return redirect_with_login_error("You cannot log in because your account was permanently deleted. Please sign up for a new account to start selling!") if @user.deleted?

if @user.suspended_for_fraud?
check_suspended
Expand All @@ -44,16 +48,18 @@ def create
flash[:warning] = "Your password has previously appeared in a data breach as per haveibeenpwned.com and should never be used. We strongly recommend you change your password everywhere you have used it."
end

render json: { redirect_location: login_path_for(@user) }
redirect_to login_path_for(@user), allow_other_host: true
end
end

private
def respond_with_login_failure(message)
render json: { error_message: message }, status: :unprocessable_entity
def block_json_request
return if request.inertia?

head :bad_request if request.format.json?
end

def block_json_request
render json: {}, success: false, status: :bad_request if request.format.json?
def redirect_with_login_error(message)
redirect_to login_path, warning: message, status: :see_other
end
end
45 changes: 31 additions & 14 deletions app/controllers/signup_controller.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
# frozen_string_literal: true

class SignupController < Devise::RegistrationsController
include OauthApplicationConfig, ValidateRecaptcha
include OauthApplicationConfig, ValidateRecaptcha, InertiaRendering

before_action :verify_captcha_and_handle_existing_users, only: :create
before_action :set_noindex_header, only: :new, if: -> { params[:next]&.start_with?("/oauth/authorize") }

layout "inertia", only: [:new]

def new
@hide_layouts = true
Copy link
Contributor Author

@stefanbinoj stefanbinoj Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is removed because, earlier this instance variable was used in context of application.html.erb for not loading sidebar, but now this instance variable is used in context of inertia.html.erb

@auth_presenter = AuthPresenter.new(params:, application: @application)
@load_recaptcha = true
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instance variable to load recaptcha script tag in inertia.html.erb

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the event the user is already inside an Inertia-rendered screen, clicking a Link to /login or /signup will perform an XHR visit, and Inertia swaps out only the page component, so the inertia.html.erb template is not re-run. That means the script never loads, and invisible reCAPTCHA can’t initialize on those client-side transitions.

You're going to need to modify the useRecaptcha hook for this. Basically you need to check if window.grecaptcha.enterprise already exists, otherwise you'll have to load the recaptcha dynamically

auth_presenter = AuthPresenter.new(params:, application: @application)
render inertia: "Signup/New", props: auth_presenter.signup_props
end

def create
Expand Down Expand Up @@ -44,7 +47,10 @@ def create
# Do not require 2FA for newly signed up users
remember_two_factor_auth

render json: { success: true, redirect_location: login_path_for(@user) }
respond_to do |format|
format.html { redirect_to login_path_for(@user), allow_other_host: true }
format.json { render json: { success: true, redirect_location: login_path_for(@user) } }
end
else
error_message = if !params[:user] || params[:user][:email].blank?
"Please provide a valid email address."
Expand All @@ -54,10 +60,10 @@ def create
@user.errors.full_messages[0]
end

render json: {
success: false,
error_message:
}
respond_to do |format|
format.html { redirect_with_signup_error(error_message) }
format.json { render json: { success: false, error_message: error_message } }
end
Copy link
Contributor Author

@stefanbinoj stefanbinoj Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this create function was also consumed by Reciept.tsx here which expects json response (this cause 1 test to fail)

end
end

Expand Down Expand Up @@ -95,10 +101,11 @@ def verify_captcha_and_handle_existing_users
if params[:user] && params[:user][:buyer_signup].blank?
site_key = GlobalConfig.get("RECAPTCHA_SIGNUP_SITE_KEY")
if !(Rails.env.development? && site_key.blank?) && !valid_recaptcha_response?(site_key: site_key)
return render json: {
success: false,
error_message: "Sorry, we could not verify the CAPTCHA. Please try again."
}
respond_to do |format|
format.html { redirect_with_signup_error("Sorry, we could not verify the CAPTCHA. Please try again.") }
format.json { render json: { success: false, error_message: "Sorry, we could not verify the CAPTCHA. Please try again." } }
end
return
end
end

Expand All @@ -109,12 +116,22 @@ def verify_captcha_and_handle_existing_users

if !user.deleted? && user.try(:valid_password?, params[:user][:password])
sign_in_or_prepare_for_two_factor_auth(user)
render json: { success: true, redirect_location: login_path_for(user) }
respond_to do |format|
format.html { redirect_to login_path_for(user) }
format.json { render json: { success: true, redirect_location: login_path_for(user) } }
end
else
render json: { success: false, error_message: "An account already exists with this email." }
respond_to do |format|
format.html { redirect_with_signup_error("An account already exists with this email.") }
format.json { render json: { success: false, error_message: "An account already exists with this email." } }
end
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reason. Handling both json and html response

end
end

def redirect_with_signup_error(message)
redirect_to signup_path, warning: message, status: :see_other
end

def build_user_with_params(user_params = nil)
return unless user_params.present?

Expand Down
22 changes: 22 additions & 0 deletions app/javascript/components/FlashError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { usePage } from "@inertiajs/react";
import * as React from "react";

import { type AlertPayload } from "$app/components/server-components/Alert";

type PageProps = {
flash?: AlertPayload;
};

export const FlashError: React.FC = () => {
const { flash } = usePage<PageProps>().props;

if (flash?.status === "warning" && flash.message) {
return (
<div role="alert" className="danger">
{flash.message}
</div>
);
}

return null;
};
121 changes: 0 additions & 121 deletions app/javascript/components/server-components/SignupPage.tsx

This file was deleted.

29 changes: 0 additions & 29 deletions app/javascript/data/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,6 @@ import { cast } from "ts-safe-cast";

import { ResponseError, request } from "$app/utils/request";

export type LoginPayload = {
email: string;
password: string;
recaptchaResponse: string | null;
next: string | null;
};

export const login = async (data: LoginPayload): Promise<{ redirectLocation: string }> => {
const response = await request({
method: "POST",
url: Routes.login_path(),
accept: "json",
data: {
user: {
login_identifier: data.email,
password: data.password,
},
next: data.next,
"g-recaptcha-response": data.recaptchaResponse,
},
});
if (!response.ok) {
const { error_message } = cast<{ error_message: string }>(await response.json());
throw new ResponseError(error_message);
}
const { redirect_location } = cast<{ redirect_location: string }>(await response.json());
return { redirectLocation: redirect_location };
};

export const renewPassword = async (email: string) => {
const response = await request({
method: "POST",
Expand Down
Loading