22
22
import org .keycloak .models .KeycloakSession ;
23
23
import org .keycloak .models .KeycloakSessionFactory ;
24
24
import org .keycloak .models .RealmModel ;
25
+ import org .keycloak .models .RequiredActionConfigModel ;
25
26
import org .keycloak .models .UserModel ;
26
27
import org .keycloak .models .utils .FormMessage ;
27
28
import org .keycloak .protocol .AuthorizationEndpointBase ;
29
+ import org .keycloak .provider .ProviderConfigProperty ;
30
+ import org .keycloak .provider .ProviderConfigurationBuilder ;
28
31
import org .keycloak .services .messages .Messages ;
29
32
import org .keycloak .services .validation .Validation ;
30
33
import org .keycloak .sessions .AuthenticationSessionModel ;
@@ -42,8 +45,6 @@ public class VerifyEmailCodeAction implements RequiredActionProvider, RequiredAc
42
45
public static final String EMAIL_CODE_FORM = "email-code-form.ftl" ;
43
46
public static final String EMAIL_CODE_NOTE = "emailCode" ;
44
47
45
- private VerifyEmailCodeActionConfig config ;
46
-
47
48
@ Override
48
49
public void evaluateTriggers (RequiredActionContext context ) {
49
50
if (context .getRealm ().isVerifyEmail () && !context .getUser ().isEmailVerified ()) {
@@ -75,19 +76,20 @@ public void requiredActionChallenge(RequiredActionContext context, FormMessage e
75
76
LoginFormsProvider form = context .form ();
76
77
authSession .setClientNote (AuthorizationEndpointBase .APP_INITIATED_FLOW , null );
77
78
79
+ VerifyEmailCodeActionConfig config = new VerifyEmailCodeActionConfig (context .getConfig ());
80
+
78
81
// 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
79
82
if (!Objects .equals (authSession .getAuthNote (Constants .VERIFY_EMAIL_KEY ), email )) {
80
83
authSession .setAuthNote (Constants .VERIFY_EMAIL_KEY , email );
81
84
EventBuilder event = context .getEvent ().clone ().event (EventType .SEND_VERIFY_EMAIL ).detail (Details .EMAIL , email );
82
- generateAndSendEmailCode (context );
85
+ generateAndSendEmailCode (context , config );
83
86
}
84
87
85
88
if (errorMessage != null ) {
86
89
form .setErrors (List .of (errorMessage ));
87
90
}
88
91
89
92
form .setAttribute ("codePattern" , config .getCodePattern ());
90
- form .setAttribute ("codeLength" , config .getCodeLengthUi ());
91
93
form .setAttribute ("tryAutoSubmit" , config .isTryAutoSubmit ());
92
94
93
95
Response challenge = form .createForm (EMAIL_CODE_FORM );
@@ -135,37 +137,37 @@ public void processAction(RequiredActionContext context) {
135
137
}
136
138
137
139
138
- private void generateAndSendEmailCode (RequiredActionContext context ) {
140
+ protected void generateAndSendEmailCode (RequiredActionContext context , VerifyEmailCodeActionConfig config ) {
139
141
140
142
if (context .getAuthenticationSession ().getAuthNote (EMAIL_CODE_NOTE ) != null ) {
141
143
// skip sending email code
142
144
return ;
143
145
}
144
146
145
147
var emailCode = SecretGenerator .getInstance ().randomString (config .getCodeLength (), SecretGenerator .DIGITS );
146
- sendEmailWithCode (context , toDisplayCode (emailCode ));
148
+ sendEmailWithCode (context , toDisplayCode (emailCode , config ));
147
149
148
150
context .getAuthenticationSession ().setAuthNote (EMAIL_CODE_NOTE , emailCode );
149
151
}
150
152
151
- private String toDisplayCode (String emailCode ) {
153
+ protected String toDisplayCode (String emailCode , VerifyEmailCodeActionConfig config ) {
152
154
return new StringBuilder (emailCode ).insert (config .getCodeLength () / 2 , "-" ).toString ();
153
155
}
154
156
155
- private String fromDisplayCode (String code ) {
157
+ protected String fromDisplayCode (String code ) {
156
158
return code .replace ("-" , "" );
157
159
}
158
160
159
- private void resetEmailCode (RequiredActionContext context ) {
161
+ protected void resetEmailCode (RequiredActionContext context ) {
160
162
context .getAuthenticationSession ().removeAuthNote (EMAIL_CODE_NOTE );
161
163
}
162
164
163
- private boolean validateCode (RequiredActionContext context , String givenCode ) {
165
+ protected boolean validateCode (RequiredActionContext context , String givenCode ) {
164
166
var emailCode = context .getAuthenticationSession ().getAuthNote (EMAIL_CODE_NOTE );
165
167
return emailCode .equals (givenCode );
166
168
}
167
169
168
- private void sendEmailWithCode (RequiredActionContext context , String code ) {
170
+ protected void sendEmailWithCode (RequiredActionContext context , String code ) {
169
171
170
172
RealmModel realm = context .getRealm ();
171
173
UserModel user = context .getUser ();
@@ -216,30 +218,59 @@ public RequiredActionProvider create(KeycloakSession session) {
216
218
217
219
@ Override
218
220
public void init (Config .Scope config ) {
219
- this .config = new VerifyEmailCodeActionConfig (config );
221
+ // this.config = new VerifyEmailCodeActionConfig(config);
220
222
}
221
223
222
224
@ Override
223
225
public void postInit (KeycloakSessionFactory factory ) {
224
226
225
227
}
226
228
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
+
227
261
@ Data
228
262
public static class VerifyEmailCodeActionConfig {
229
263
230
264
private int codeLength ;
231
265
232
- private int codeLengthUi ;
233
-
234
266
private String codePattern ;
235
267
236
268
private boolean tryAutoSubmit ;
237
269
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" ));
243
274
}
244
275
}
245
276
}
0 commit comments