Skip to content

Commit

Permalink
feat: add parameter to customize token lifespan (#92)
Browse files Browse the repository at this point in the history
* feat: add parameter to customize token lifespan

* chore: fix format

* chore: add NumberFormatException

* chora: change input type
  • Loading branch information
endroca authored Nov 11, 2024
1 parent a0dc586 commit 374d88d
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.phasetwo.keycloak.magic.auth;

import static io.phasetwo.keycloak.magic.MagicLink.CREATE_NONEXISTENT_USER_CONFIG_PROPERTY;
import static io.phasetwo.keycloak.magic.auth.util.Authenticators.get;
import static io.phasetwo.keycloak.magic.auth.util.Authenticators.is;
import static org.keycloak.services.validation.Validation.FIELD_USERNAME;

Expand Down Expand Up @@ -29,6 +30,7 @@ public class MagicLinkAuthenticator extends UsernamePasswordForm {
static final String UPDATE_PASSWORD_ACTION_CONFIG_PROPERTY = "ext-magic-update-password-action";

static final String ACTION_TOKEN_PERSISTENT_CONFIG_PROPERTY = "ext-magic-allow-token-reuse";
static final String ACTION_TOKEN_LIFE_SPAN = "ext-magic-token-life-span";

@Override
public void authenticate(AuthenticationFlowContext context) {
Expand Down Expand Up @@ -83,12 +85,14 @@ public void action(AuthenticationFlowContext context) {
if (user == null
|| MagicLink.trimToNull(user.getEmail()) == null
|| !MagicLink.isValidEmail(user.getEmail())) {
context.getEvent()
.detail(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME, email)
.event(EventType.LOGIN_ERROR).error(Errors.INVALID_EMAIL);
context
.getAuthenticationSession()
.setAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME, email);
.getEvent()
.detail(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME, email)
.event(EventType.LOGIN_ERROR)
.error(Errors.INVALID_EMAIL);
context
.getAuthenticationSession()
.setAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME, email);
log.debugf("user attempted to login with username/email: %s", email);
context.forceChallenge(context.form().createForm("view-email.ftl"));
return;
Expand All @@ -101,11 +105,13 @@ public void action(AuthenticationFlowContext context) {
return; // the enabledUser method sets the challenge
}

OptionalInt lifespan = getActionTokenLifeSpan(context, "");

MagicLinkActionToken token =
MagicLink.createActionToken(
user,
clientId,
OptionalInt.empty(),
lifespan,
rememberMe(context),
context.getAuthenticationSession(),
isActionTokenPersistent(context, true));
Expand Down Expand Up @@ -143,6 +149,22 @@ private boolean isActionTokenPersistent(AuthenticationFlowContext context, boole
return is(context, ACTION_TOKEN_PERSISTENT_CONFIG_PROPERTY, defaultValue);
}

private OptionalInt getActionTokenLifeSpan(
AuthenticationFlowContext context, String defaultValue) {
String lifespan = get(context, ACTION_TOKEN_LIFE_SPAN, defaultValue);

if ("".equals(lifespan)) {
return OptionalInt.empty();
}

try {
return OptionalInt.of(Integer.parseInt(lifespan));
} catch (NumberFormatException e) {
log.error("Failed to parse lifespan", e);
return OptionalInt.empty();
}
}

@Override
protected boolean validateForm(
AuthenticationFlowContext context, MultivaluedMap<String, String> formData) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,15 @@ public List<ProviderConfigProperty> getConfigProperties() {
"Toggle whether magic link should be persistent until expired.");
actionTokenPersistent.setDefaultValue(true);

return List.of(createUser, updateProfile, updatePassword, actionTokenPersistent);
ProviderConfigProperty actionTokenLifeSpan = new ProviderConfigProperty();
actionTokenLifeSpan.setType(ProviderConfigProperty.STRING_TYPE);
actionTokenLifeSpan.setName(MagicLinkAuthenticator.ACTION_TOKEN_LIFE_SPAN);
actionTokenLifeSpan.setLabel("Token lifespan");
actionTokenLifeSpan.setHelpText(
"Amount of time the magic link is valid, in seconds. If this value is not specific, it will use the default 86400s (1 day)");

return List.of(
createUser, updateProfile, updatePassword, actionTokenPersistent, actionTokenLifeSpan);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,18 @@ public static boolean is(

return Boolean.parseBoolean(v.trim());
}

public static String get(
AuthenticationFlowContext context, String propName, String defaultValue) {
AuthenticatorConfigModel authenticatorConfig = context.getAuthenticatorConfig();
if (authenticatorConfig == null) return defaultValue;

Map<String, String> config = authenticatorConfig.getConfig();
if (config == null) return defaultValue;

String v = config.get(propName);
if (Strings.isNullOrEmpty(v)) return defaultValue;

return v.trim();
}
}

0 comments on commit 374d88d

Please sign in to comment.