LocalAccount.java
/*
* The coLAB project
* Copyright (C) 2021-2023 AlbaSim, MEI, HEIG-VD, HES-SO
*
* Licensed under the MIT License
*/
package ch.colabproject.colab.api.model.user;
import ch.colabproject.colab.api.Helper;
import ch.colabproject.colab.api.exceptions.ColabMergeException;
import ch.colabproject.colab.api.model.ColabEntity;
import javax.json.bind.annotation.JsonbTransient;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Index;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
* Password based authentication.
*
* @author maxence
*/
@Entity
@NamedQuery(name = "LocalAccount.findByEmail",
query = "SELECT a from LocalAccount a where a.email = :email")
@Table(
// make sure to have JOINED inheritance, otherwise indexes and constraints will be ignored!
indexes = {
@Index(columnList = "email", unique = true)
})
public class LocalAccount extends Account {
private static final long serialVersionUID = 1L;
// ---------------------------------------------------------------------------------------------
// fields
// ---------------------------------------------------------------------------------------------
/**
* username-like email address
*/
@Size(max = 255)
@Email
@NotNull
private String email;
/**
* salt+password hash. Hashed with currentDbHashMethod and dbSalt.
*/
@JsonbTransient
@NotNull
private byte[] hashedPassword;
/**
* Has the email address been verified with a VerificationToken ?
*/
@NotNull
private Boolean verified;
/**
* Salt to used before hashing the password
*/
@JsonbTransient
@NotNull
private byte[] dbSalt;
/**
* Hash method to use to hashedPassword the salt+password
*/
@Column(length = 100)
@Enumerated(value = EnumType.STRING)
@JsonbTransient
@NotNull
private HashMethod currentDbHashMethod;
/**
* New hash method to use to hash the salt+password. If not null, rotate hash method on next
* successful authentication
*/
@Column(length = 100)
@Enumerated(value = EnumType.STRING)
@JsonbTransient
private HashMethod nextDbHashMethod;
/**
* Salt the client shall use to before hashing its password. Salt is hex-encoded byte array
*/
@Size(max = 255)
@NotNull
@JsonbTransient
private String clientSalt;
/**
* New salt the client shall use to prefix its password before hashing it.
* <p>
* In case this is not null, client shall send two hashes. First one is the plain_password
* prefixed with clientSalt and hashed with currentClientHashMethod (to authenticate), second is
* its password prefixed with this new salt and hashed with nextClientHashMethod if set, current
* otherwise. successful authentication (to rotate salt and/or method)
*/
@Size(max = 255)
@JsonbTransient
private String newClientSalt;
/**
* Hash method the client shall use to hashedPassword the clientSalt+plain_password
*/
@Column(length = 100)
@Enumerated(value = EnumType.STRING)
@NotNull
@JsonbTransient
private HashMethod currentClientHashMethod;
/**
* New hash method the client shall use to hash its clientSalt+plain_password.
* <p>
* In case this is not null, client shall send two hashes. First one is its plain_password
* prefixed clientSalt and hashed with currentClientHashMethod (to authenticate), second is its
* password prefixed with the new_salt (if set)or the current salt and hashed with this method
*/
@Column(length = 100)
@Enumerated(value = EnumType.STRING)
@JsonbTransient
private HashMethod nextClientHashMethod;
// ---------------------------------------------------------------------------------------------
// getters and setters
// ---------------------------------------------------------------------------------------------
/**
* @return email associated with this account
*/
public String getEmail() {
return email;
}
/**
* update email. If the email is not the same, this account will not be verified any longer
*
* @param email new email to use.
*/
public void setEmail(String email) {
if (Helper.isEmailAddress(email)) { // FIXME sandra see with Maxence if it is useful to do
// it here
if (!email.equals(this.email)) {
this.verified = false;
}
this.email = email;
}
}
/**
* get the stored hashedPassword to challenge authentication against
*
* @return hashedPassword hashedPassword to challenge authentication against
*/
public byte[] getHashedPassword() {
return hashedPassword;
}
/**
* Update hashedPassword
*
* @param hashedPassword new hashedPassword
*/
public void setHashedPassword(byte[] hashedPassword) {
this.hashedPassword = hashedPassword;
}
/**
* has the email address been verified ?
*
* @return true if the account is verified
*/
public Boolean isVerified() {
return verified;
}
/**
* Set if the account has been verified or not
*
* @param verified yes or no ?
*/
public void setVerified(Boolean verified) {
this.verified = verified;
}
/**
* @return the salt to used server-side
*/
public byte[] getDbSalt() {
return dbSalt;
}
/**
* Update the server-side hashedPassword
*
* @param dbSalt new server-side salt
*/
public void setDbSalt(byte[] dbSalt) {
this.dbSalt = dbSalt;
}
/**
* @return the current hashedPassword method to used to hashedPassword provided password
*/
public HashMethod getCurrentDbHashMethod() {
return currentDbHashMethod;
}
/**
* Set the method to use to hashedPassword provided password
*
* @param currentDbHashMethod hashedPassword method
*/
public void setCurrentDbHashMethod(HashMethod currentDbHashMethod) {
this.currentDbHashMethod = currentDbHashMethod;
}
/**
* @return the next hashedPassword method to use. If not null, hashedPassword methods will be
* rotated on next authentication
*/
public HashMethod getNextDbHashMethod() {
return nextDbHashMethod;
}
/**
* change the next hashedPassword method to used
*
* @param nextDbHashMethod next hashedPassword method
*/
public void setNextDbHashMethod(HashMethod nextDbHashMethod) {
this.nextDbHashMethod = nextDbHashMethod;
}
/**
* @return the salt the user shall use before client-side plain_password hashedPassword
*/
public String getClientSalt() {
return clientSalt;
}
/**
* Update the salt the client shall use
*
* @param clientSalt client salt
*/
public void setClientSalt(String clientSalt) {
this.clientSalt = clientSalt;
}
/**
* @return the salt to use to rotate authentication
*/
public String getNewClientSalt() {
return newClientSalt;
}
/**
* set a next client-side salt
*
* @param newClientSalt new salt we want the client to use
*/
public void setNewClientSalt(String newClientSalt) {
this.newClientSalt = newClientSalt;
}
/**
* @return the hashedPassword method the client shall use to hashedPassword its
* salt+plain_password
*/
public HashMethod getCurrentClientHashMethod() {
return currentClientHashMethod;
}
/**
* CHange hashedPassword method the client shall use
*
* @param currentClientHashMethod hashedPassword method the client shall use
*/
public void setCurrentClientHashMethod(HashMethod currentClientHashMethod) {
this.currentClientHashMethod = currentClientHashMethod;
}
/**
* @return the next hashedPassword method the client shall use
*/
public HashMethod getNextClientHashMethod() {
return nextClientHashMethod;
}
/**
* Update the next client-side hashedPassword method
*
* @param nextClientHashMethod new next client hashedPassword method
*/
public void setNextClientHashMethod(HashMethod nextClientHashMethod) {
this.nextClientHashMethod = nextClientHashMethod;
}
// ---------------------------------------------------------------------------------------------
// concerning the whole class
// ---------------------------------------------------------------------------------------------
@Override
public void mergeToUpdate(ColabEntity other) throws ColabMergeException {
if (other instanceof LocalAccount) {
LocalAccount o = (LocalAccount) other;
this.setDeletionStatus(o.getDeletionStatus());
this.setEmail(o.getEmail());
// the others fields cannot be changed by a simple update
} else {
throw new ColabMergeException(this, other);
}
}
@Override
public String toString() {
return "LocalAccount{" + "id=" + this.getId() + ", deletion=" + getDeletionStatus()
+ ", email=" + email + ", verified=" + verified + '}';
}
}