package io.conducive.server.db.impl;
import io.conducive.server.bind.DBFile;
import io.conducive.server.db.UserDAO;
import io.conducive.server.db.exception.DuplicateUserException;
import io.conducive.shared.model.User;
import static com.google.common.base.Preconditions.*;
import static com.google.common.hash.Hashing.*;
import org.mapdb.*;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.File;
import java.util.*;
@Singleton
public class UserDAOImpl extends AbstractMapDBDao implements UserDAO {
private final static int EXP = 17;
@Inject
public UserDAOImpl(@DBFile File dbFile, DB db) {
super(dbFile, db);
}
@Override
public User getUser(final String username, final String password, final String salt) {
checkNotNull(username, "Username should not be null");
checkNotNull(password, "Password should not be null");
final User user = users().get(username);
if (user != null) {
if (stretch(password, salt, EXP).equals(user.getHash())) {
// don't surface the hash
return new User(username);
}
}
return null;
}
@Override
public void createUser(final User user)
throws DuplicateUserException
{
checkNotNull(user.getUsername(), "Username should not be null");
checkNotNull(user.getHash(), "Password should not be null");
checkNotNull(user.getSalt(), "Salt should not be null");
if (users().containsKey(user.getUsername())) {
throw new DuplicateUserException();
}
// hash from client is already stretched. stretch some more. distance from what is stored vs password is
// 2^(LoginView.EXP + UserDAOImpl.EXP), i.e. 2^29
logger.info("Stretching");
User toStore = new User(user.getUsername(), stretch(user.getHash(), user.getSalt(), EXP));
toStore.setSalt(user.getSalt());
logger.info("Done stretching");
users().put(user.getUsername(), toStore);
// save to disk
commit();
}
private NavigableMap<String, User> users() {
return oneToOne("users");
}
public static String hash(final String password) {
try {
return sha256().hashBytes(password.getBytes("UTF-8")).toString();
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
/**
* Lifted from "Cryptographic Engineering" - make it more expensive to crack a password by repeatedly hashing
* with salt.
*
* TODO use scrypt once we have time.
*
* The effort to crack the password is increased by 2^exp. Salt can be public, as can exp.
* @param password
* @param salt
* @param exp
* @return
*/
public static String stretch(String password, String salt, int exp) {
String hash = hash(password + salt);
for (int i = 0; i < Math.pow(2, exp); i++) {
hash = hash(hash + password + salt);
}
return hash;
}
}