Move Focus to OTT Button When Username is Read-Only

Closes gh-18817

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
This commit is contained in:
Josh Cummings 2026-04-07 18:18:57 -06:00
parent 245733a631
commit 036ccff1f5
4 changed files with 42 additions and 35 deletions

View File

@ -174,10 +174,10 @@ public class OneTimeTokenLoginConfigurerTests {
<p>
<label for="ott-username" class="screenreader">Username</label>
<input type="text" id="ott-username" name="username" placeholder="Username" required>
<input type="text" id="ott-username" name="username" placeholder="Username" required autofocus>
</p>
<input name="_csrf" type="hidden" value="%s" />
<button class="primary" type="submit">Send Token</button>
<button class="primary" type="submit" form="ott-form">Send Token</button>
</form>

View File

@ -366,6 +366,7 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
String usernameInput = (username != null)
? HtmlTemplates.fromTemplate(ONE_TIME_READONLY_USERNAME_INPUT).withValue("username", username).render()
: ONE_TIME_USERNAME_INPUT;
String buttonAutofocus = (username != null) ? " autofocus" : "";
return HtmlTemplates.fromTemplate(ONE_TIME_TEMPLATE)
.withValue("generateOneTimeTokenUrl", contextPath + this.generateOneTimeTokenUrl)
@ -373,6 +374,7 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
.withRawHtml("logoutMessage", renderSuccess(logoutSuccess))
.withRawHtml("hiddenInputs", hiddenInputs)
.withRawHtml("usernameInput", usernameInput)
.withRawHtml("buttonAutofocus", buttonAutofocus)
.render();
}
@ -604,7 +606,7 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
{{usernameInput}}
</p>
{{hiddenInputs}}
<button class="primary" type="submit">Send Token</button>
<button class="primary" type="submit" form="ott-form"{{buttonAutofocus}}>Send Token</button>
</form>
""";
@ -613,7 +615,7 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
""";
private static final String ONE_TIME_USERNAME_INPUT = """
<input type="text" id="ott-username" name="username" placeholder="Username" required>
<input type="text" id="ott-username" name="username" placeholder="Username" required autofocus>
""";
}

View File

@ -252,7 +252,7 @@ public class LoginPageGeneratingWebFilter implements WebFilter {
{{errorMessage}}{{logoutMessage}}
<p>
<label for="ott-username" class="screenreader">Username</label>
<input type="text" id="ott-username" name="username" placeholder="Username" required>
<input type="text" id="ott-username" name="username" placeholder="Username" required autofocus>
</p>
{{csrf}}
<button class="primary" type="submit" form="ott-form">Send Token</button>

View File

@ -190,18 +190,19 @@ public class DefaultLoginPageGeneratingFilterTests {
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(new MockHttpServletRequest("GET", "/login"), response, this.chain);
assertThat(response.getContentAsString()).contains("Request a One-Time Token");
assertThat(response.getContentAsString()).contains("""
<form id="ott-form" class="login-form" method="post" action="/ott/authenticate">
<h2>Request a One-Time Token</h2>
assertThat(response.getContentAsString()).contains(
"""
<form id="ott-form" class="login-form" method="post" action="/ott/authenticate">
<h2>Request a One-Time Token</h2>
<p>
<label for="ott-username" class="screenreader">Username</label>
<input type="text" id="ott-username" name="username" placeholder="Username" required>
</p>
<p>
<label for="ott-username" class="screenreader">Username</label>
<input type="text" id="ott-username" name="username" placeholder="Username" required autofocus>
</p>
<button class="primary" type="submit">Send Token</button>
</form>
""");
<button class="primary" type="submit" form="ott-form">Send Token</button>
</form>
""");
}
@Test
@ -216,18 +217,19 @@ public class DefaultLoginPageGeneratingFilterTests {
FactorGrantedAuthority.OTT_AUTHORITY);
filter.doFilter(loginRequest, response, this.chain);
assertThat(response.getContentAsString()).contains("Request a One-Time Token");
assertThat(response.getContentAsString()).contains("""
<form id="ott-form" class="login-form" method="post" action="/ott/authenticate">
<h2>Request a One-Time Token</h2>
assertThat(response.getContentAsString()).contains(
"""
<form id="ott-form" class="login-form" method="post" action="/ott/authenticate">
<h2>Request a One-Time Token</h2>
<p>
<label for="ott-username" class="screenreader">Username</label>
<input type="text" id="ott-username" name="username" placeholder="Username" required>
</p>
<p>
<label for="ott-username" class="screenreader">Username</label>
<input type="text" id="ott-username" name="username" placeholder="Username" required autofocus>
</p>
<button class="primary" type="submit">Send Token</button>
</form>
""");
<button class="primary" type="submit" form="ott-form">Send Token</button>
</form>
""");
assertThat(response.getContentAsString()).doesNotContain("Password");
}
@ -245,18 +247,19 @@ public class DefaultLoginPageGeneratingFilterTests {
.get("/login?factor.type=ott&factor.type=password&factor.reason=missing&factor.reason=missing")
.build(), response, this.chain);
assertThat(response.getContentAsString()).contains("Request a One-Time Token");
assertThat(response.getContentAsString()).contains("""
<form id="ott-form" class="login-form" method="post" action="/ott/authenticate">
<h2>Request a One-Time Token</h2>
assertThat(response.getContentAsString()).contains(
"""
<form id="ott-form" class="login-form" method="post" action="/ott/authenticate">
<h2>Request a One-Time Token</h2>
<p>
<label for="ott-username" class="screenreader">Username</label>
<input type="text" id="ott-username" name="username" placeholder="Username" required>
</p>
<p>
<label for="ott-username" class="screenreader">Username</label>
<input type="text" id="ott-username" name="username" placeholder="Username" required autofocus>
</p>
<button class="primary" type="submit">Send Token</button>
</form>
""");
<button class="primary" type="submit" form="ott-form">Send Token</button>
</form>
""");
assertThat(response.getContentAsString()).contains("Password");
}
@ -297,6 +300,8 @@ public class DefaultLoginPageGeneratingFilterTests {
"""
<input type="text" id="ott-username" name="username" value="user" placeholder="Username" required readonly>
""");
assertThat(response.getContentAsString()).contains("""
<button class="primary" type="submit" form="ott-form" autofocus>Send Token</button>""");
assertThat(response.getContentAsString()).contains("""
<input type="text" id="username" name="username" value="user" placeholder="Username" required readonly>
""");