Users.java

package org.flasby.entity;

import java.security.Principal;
import java.util.HashSet;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;

import org.mindrot.jbcrypt.BCrypt;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.nulabinc.zxcvbn.Strength;
import com.nulabinc.zxcvbn.Zxcvbn;

import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;

@Getter
@Entity
@Table(name = "users")
public class Users<Role> implements Principal  {

    @Id @GeneratedValue Long id;

    public int hashCode() {
        return Objects.hash(id);
    }

    // Roles are excluded from equality testing
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (!getClass().isAssignableFrom(obj.getClass()))
            return false;
        @SuppressWarnings("unchecked")
        Users<Role> other = (Users<Role>) obj;
        return Objects.equals(id, other.getId());
    }

    private Properties properties = new Properties();

    public Properties getProperties() {
        return new Properties(properties);
    }
    protected String name;
    protected String alias;
    protected boolean disabled;
    protected int failedLoginAttempts;

    protected void setDisabled( boolean disabled ) {
        this.disabled = disabled;
    }
    protected void setOnline( boolean online ) {
        this.online = online;
    }
    
    @JsonIgnore
    String password;
    boolean online = false;

    @Enumerated(EnumType.STRING)
    @ElementCollection(fetch = FetchType.EAGER)
    public Set<Role> roles = new HashSet<>();

    
    protected Users() {
    }

    /**
     * sets the password and returns the password strength.<br>
     * 0 is very poor<br>
     * 5 is as good as it gets<br>
     */
    public int setPassword(final String plaintext) {
        Zxcvbn zxcvbn = new Zxcvbn();
        Strength strength = zxcvbn.measure(plaintext);
        this.password = BCrypt.hashpw(plaintext, BCrypt.gensalt());
        return strength.getScore();
    }

    public boolean checkPassword( final String plaintext ) {
        boolean ok = BCrypt.checkpw(plaintext, getPassword() );
        if ( !ok ) {
            failedLoginAttempts++;
            if ( failedLoginAttempts > 10 ) {
                disabled = true;
            }
        } else {
            failedLoginAttempts = 0;
        }
        return ok;
    }

    @SafeVarargs
    public Users(final String name, final String alias, final String password, final boolean disabled, final Role ...roles) {
        this.name = name;
        this.alias = alias;
        setPassword( password );
        this.disabled = disabled;
        for (Role role : roles) {
            this.roles.add(role);
        }
        failedLoginAttempts = 0;
    }

    public String getProperty( String key ) {
        return properties.getProperty(key);
    }

    public void setProperty( String key, String value ) {
        properties.setProperty(key, value);
    }

    @Override
    public String toString() {
        return "User [alias=" + alias + ", disabled=" + disabled + ", failedLoginAttempts=" + failedLoginAttempts
                + ", id=" + id + ", name=" + name + ", online=" + online + ", password=<elided>>, properties="
                + properties + ", roles=" + roles + "]";
    }
    
}