TMPasswordDeserializer.java
/*
* Copyright (C) 2023 B3Partners B.V.
*
* SPDX-License-Identifier: MIT
*/
package org.tailormap.api.util;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.validation.constraints.NotNull;
import java.io.IOException;
import java.util.Locale;
import me.gosimple.nbvcxz.Nbvcxz;
import me.gosimple.nbvcxz.resources.ConfigurationBuilder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.tailormap.api.configuration.TailormapPasswordStrengthConfig;
import org.tailormap.api.security.InvalidPasswordException;
public class TMPasswordDeserializer extends JsonDeserializer<String> {
private static final PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
public static PasswordEncoder encoder() {
return encoder;
}
/**
* When deserializing a JSON field containing a plaintext password validate it is strong enough and hash it with the
* default PasswordEncoder (bcrypt).
*
* @param jsonParser parser
* @param context context
* @return The bcrypt hashed password
* @throws IOException when JSON processing fails, {@code InvalidPasswordException} when the password is not strong
* enough
*/
@Override
public String deserialize(@NotNull JsonParser jsonParser, DeserializationContext context) throws IOException {
ObjectCodec codec = jsonParser.getCodec();
JsonNode node = codec.readTree(jsonParser);
if (node == null) {
throw new InvalidPasswordException(jsonParser, "Password is required");
}
String password = node.asText();
boolean validation = TailormapPasswordStrengthConfig.getValidation();
int minLength = TailormapPasswordStrengthConfig.getMinLength();
int minStrength = TailormapPasswordStrengthConfig.getMinStrength();
if (validation && !validatePasswordStrength(password, minLength, minStrength)) {
throw new InvalidPasswordException(jsonParser, "Password too short or too easily guessable");
}
return encoder.encode(node.asText());
}
public static boolean validatePasswordStrength(String password, int minLength, int minStrength) {
if (StringUtils.isBlank(password)) {
return false;
}
if (password.length() < minLength) {
return false;
}
me.gosimple.nbvcxz.resources.Configuration configuration = new ConfigurationBuilder()
.setLocale(Locale.forLanguageTag(LocaleContextHolder.getLocale().toLanguageTag()))
.setDistanceCalc(true)
.createConfiguration();
return new Nbvcxz(configuration).estimate(password).getBasicScore() >= minStrength;
}
}