Skip to content

Commit 586f003

Browse files
Revise VerifyEmailCodeAction
- We now use the new required action configuration support - If try-auto-submit is enabled, we attempt submit the form if the input matches the configured pattern.
1 parent 1473fd5 commit 586f003

File tree

2 files changed

+54
-23
lines changed

2 files changed

+54
-23
lines changed

keycloak/extensions/src/main/java/com/github/thomasdarimont/keycloak/custom/auth/verifyemailcode/VerifyEmailCodeAction.java

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@
2222
import org.keycloak.models.KeycloakSession;
2323
import org.keycloak.models.KeycloakSessionFactory;
2424
import org.keycloak.models.RealmModel;
25+
import org.keycloak.models.RequiredActionConfigModel;
2526
import org.keycloak.models.UserModel;
2627
import org.keycloak.models.utils.FormMessage;
2728
import org.keycloak.protocol.AuthorizationEndpointBase;
29+
import org.keycloak.provider.ProviderConfigProperty;
30+
import org.keycloak.provider.ProviderConfigurationBuilder;
2831
import org.keycloak.services.messages.Messages;
2932
import org.keycloak.services.validation.Validation;
3033
import org.keycloak.sessions.AuthenticationSessionModel;
@@ -42,8 +45,6 @@ public class VerifyEmailCodeAction implements RequiredActionProvider, RequiredAc
4245
public static final String EMAIL_CODE_FORM = "email-code-form.ftl";
4346
public static final String EMAIL_CODE_NOTE = "emailCode";
4447

45-
private VerifyEmailCodeActionConfig config;
46-
4748
@Override
4849
public void evaluateTriggers(RequiredActionContext context) {
4950
if (context.getRealm().isVerifyEmail() && !context.getUser().isEmailVerified()) {
@@ -75,19 +76,20 @@ public void requiredActionChallenge(RequiredActionContext context, FormMessage e
7576
LoginFormsProvider form = context.form();
7677
authSession.setClientNote(AuthorizationEndpointBase.APP_INITIATED_FLOW, null);
7778

79+
VerifyEmailCodeActionConfig config = new VerifyEmailCodeActionConfig(context.getConfig());
80+
7881
// Do not allow resending e-mail by simple page refresh, i.e. when e-mail sent, it should be resent properly via email-verification endpoint
7982
if (!Objects.equals(authSession.getAuthNote(Constants.VERIFY_EMAIL_KEY), email)) {
8083
authSession.setAuthNote(Constants.VERIFY_EMAIL_KEY, email);
8184
EventBuilder event = context.getEvent().clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, email);
82-
generateAndSendEmailCode(context);
85+
generateAndSendEmailCode(context, config);
8386
}
8487

8588
if (errorMessage != null) {
8689
form.setErrors(List.of(errorMessage));
8790
}
8891

8992
form.setAttribute("codePattern", config.getCodePattern());
90-
form.setAttribute("codeLength", config.getCodeLengthUi());
9193
form.setAttribute("tryAutoSubmit", config.isTryAutoSubmit());
9294

9395
Response challenge = form.createForm(EMAIL_CODE_FORM);
@@ -135,37 +137,37 @@ public void processAction(RequiredActionContext context) {
135137
}
136138

137139

138-
private void generateAndSendEmailCode(RequiredActionContext context) {
140+
protected void generateAndSendEmailCode(RequiredActionContext context, VerifyEmailCodeActionConfig config) {
139141

140142
if (context.getAuthenticationSession().getAuthNote(EMAIL_CODE_NOTE) != null) {
141143
// skip sending email code
142144
return;
143145
}
144146

145147
var emailCode = SecretGenerator.getInstance().randomString(config.getCodeLength(), SecretGenerator.DIGITS);
146-
sendEmailWithCode(context, toDisplayCode(emailCode));
148+
sendEmailWithCode(context, toDisplayCode(emailCode, config));
147149

148150
context.getAuthenticationSession().setAuthNote(EMAIL_CODE_NOTE, emailCode);
149151
}
150152

151-
private String toDisplayCode(String emailCode) {
153+
protected String toDisplayCode(String emailCode, VerifyEmailCodeActionConfig config) {
152154
return new StringBuilder(emailCode).insert(config.getCodeLength() / 2, "-").toString();
153155
}
154156

155-
private String fromDisplayCode(String code) {
157+
protected String fromDisplayCode(String code) {
156158
return code.replace("-", "");
157159
}
158160

159-
private void resetEmailCode(RequiredActionContext context) {
161+
protected void resetEmailCode(RequiredActionContext context) {
160162
context.getAuthenticationSession().removeAuthNote(EMAIL_CODE_NOTE);
161163
}
162164

163-
private boolean validateCode(RequiredActionContext context, String givenCode) {
165+
protected boolean validateCode(RequiredActionContext context, String givenCode) {
164166
var emailCode = context.getAuthenticationSession().getAuthNote(EMAIL_CODE_NOTE);
165167
return emailCode.equals(givenCode);
166168
}
167169

168-
private void sendEmailWithCode(RequiredActionContext context, String code) {
170+
protected void sendEmailWithCode(RequiredActionContext context, String code) {
169171

170172
RealmModel realm = context.getRealm();
171173
UserModel user = context.getUser();
@@ -216,30 +218,59 @@ public RequiredActionProvider create(KeycloakSession session) {
216218

217219
@Override
218220
public void init(Config.Scope config) {
219-
this.config = new VerifyEmailCodeActionConfig(config);
221+
// this.config = new VerifyEmailCodeActionConfig(config);
220222
}
221223

222224
@Override
223225
public void postInit(KeycloakSessionFactory factory) {
224226

225227
}
226228

229+
@Override
230+
public List<ProviderConfigProperty> getConfigMetadata() {
231+
232+
List<ProviderConfigProperty> configProperties = ProviderConfigurationBuilder.create() //
233+
.property() //
234+
.name("code-length") //
235+
.label("Code Length") //
236+
.required(true) //
237+
.defaultValue(8) //
238+
.helpText("Length of email code") //
239+
.type(ProviderConfigProperty.INTEGER_TYPE) //
240+
.add() //
241+
.property() //
242+
.name("code-pattern") //
243+
.label("Code Pattern String") //
244+
.required(true) //
245+
.defaultValue("\\d{4}-\\d{4}") //
246+
.helpText("Format pattern to render the email code. Use \\d as a placeholder for a digit") //
247+
.type(ProviderConfigProperty.STRING_TYPE) //
248+
.add() //
249+
.property() //
250+
.name("try-auto-submit") //
251+
.label("Try auto submit") //
252+
.required(true) //
253+
.defaultValue(false) //
254+
.helpText("Submits the form if the input is complete") //
255+
.type(ProviderConfigProperty.BOOLEAN_TYPE) //
256+
.add() //
257+
.build();
258+
return configProperties;
259+
}
260+
227261
@Data
228262
public static class VerifyEmailCodeActionConfig {
229263

230264
private int codeLength;
231265

232-
private int codeLengthUi;
233-
234266
private String codePattern;
235267

236268
private boolean tryAutoSubmit;
237269

238-
public VerifyEmailCodeActionConfig(Config.Scope config) {
239-
this.codeLength = config.getInt("code-length", 8);
240-
this.codePattern = config.get("code-pattern", "\\d{4}-\\d{4}");
241-
this.codeLengthUi = config.getInt("code-length-ui", 8 + 1); //+1 for "-"
242-
this.tryAutoSubmit = config.getBoolean("try-auto-submit", false);
270+
public VerifyEmailCodeActionConfig(RequiredActionConfigModel config) {
271+
this.codeLength = Integer.parseInt(config.getConfigValue("code-length", "8"));
272+
this.codePattern = config.getConfigValue("code-pattern", "\\d{4}-\\d{4}");
273+
this.tryAutoSubmit = Boolean.parseBoolean(config.getConfigValue("try-auto-submit", "false"));
243274
}
244275
}
245276
}

keycloak/themes/internal/login/email-code-form.ftl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
${msg('emailCodeFormTitle')}
77
<#elseif section = "form">
88
<script>
9-
function tryCompleteForm(codeLength) {
10-
let code = document.querySelector("#emailCode").value;
11-
if (code.length === codeLength) {
9+
function tryCompleteForm() {
10+
let emailCodeInput = document.querySelector("#emailCode");
11+
if (emailCodeInput.validity.valid) {
1212
document.querySelector("#kc-email-code-login-form").submit();
1313
}
1414
}
@@ -20,7 +20,7 @@
2020
<div class="${properties.kcInputWrapperClass!}">
2121
<label for="emailCode">${msg('accessCode')}:</label>
2222
<input id="emailCode" name="emailCode" type="text" inputmode="numeric" pattern="${codePattern}" autofocus
23-
class="${properties.kcInputClass!}" <#if tryAutoSubmit> oninput="tryCompleteForm(${codeLength})" </#if>
23+
class="${properties.kcInputClass!}" <#if tryAutoSubmit> oninput="tryCompleteForm()" </#if>
2424
required autocomplete="one-time-code"/>
2525
</div>
2626
</div>

0 commit comments

Comments
 (0)