mirror of
https://github.com/AuthMe/AuthMeReloaded.git
synced 2024-12-18 14:47:47 +01:00
#1141 Add TOTP key field to database and PlayerAuth
- Add new field for storing TOTP key - Implement data source methods for manipulation of its value
This commit is contained in:
parent
930f5609bf
commit
9954c82cb6
@ -26,6 +26,7 @@ public class PlayerAuth {
|
||||
/** The player's name in the correct casing, e.g. "Xephi". */
|
||||
private String realName;
|
||||
private HashedPassword password;
|
||||
private String totpKey;
|
||||
private String email;
|
||||
private String lastIp;
|
||||
private int groupId;
|
||||
@ -160,6 +161,10 @@ public class PlayerAuth {
|
||||
this.registrationDate = registrationDate;
|
||||
}
|
||||
|
||||
public String getTotpKey() {
|
||||
return totpKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof PlayerAuth)) {
|
||||
@ -195,6 +200,7 @@ public class PlayerAuth {
|
||||
private String name;
|
||||
private String realName;
|
||||
private HashedPassword password;
|
||||
private String totpKey;
|
||||
private String lastIp;
|
||||
private String email;
|
||||
private int groupId = -1;
|
||||
@ -219,6 +225,7 @@ public class PlayerAuth {
|
||||
auth.nickname = checkNotNull(name).toLowerCase();
|
||||
auth.realName = firstNonNull(realName, "Player");
|
||||
auth.password = firstNonNull(password, new HashedPassword(""));
|
||||
auth.totpKey = totpKey;
|
||||
auth.email = DB_EMAIL_DEFAULT.equals(email) ? null : email;
|
||||
auth.lastIp = lastIp; // Don't check against default value 127.0.0.1 as it may be a legit value
|
||||
auth.groupId = groupId;
|
||||
@ -258,6 +265,11 @@ public class PlayerAuth {
|
||||
return password(new HashedPassword(hash, salt));
|
||||
}
|
||||
|
||||
public Builder totpKey(String totpKey) {
|
||||
this.totpKey = totpKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder lastIp(String lastIp) {
|
||||
this.lastIp = lastIp;
|
||||
return this;
|
||||
|
@ -268,6 +268,15 @@ public class CacheDataSource implements DataSource {
|
||||
return source.getRecentlyLoggedInPlayers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setTotpKey(String user, String totpKey) {
|
||||
boolean result = source.setTotpKey(user, totpKey);
|
||||
if (result) {
|
||||
cachedAuths.refresh(user);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCache(String playerName) {
|
||||
cachedAuths.invalidate(playerName);
|
||||
|
@ -14,6 +14,7 @@ public final class Columns {
|
||||
public final String REAL_NAME;
|
||||
public final String PASSWORD;
|
||||
public final String SALT;
|
||||
public final String TOTP_KEY;
|
||||
public final String LAST_IP;
|
||||
public final String LAST_LOGIN;
|
||||
public final String GROUP;
|
||||
@ -35,6 +36,7 @@ public final class Columns {
|
||||
REAL_NAME = settings.getProperty(DatabaseSettings.MYSQL_COL_REALNAME);
|
||||
PASSWORD = settings.getProperty(DatabaseSettings.MYSQL_COL_PASSWORD);
|
||||
SALT = settings.getProperty(DatabaseSettings.MYSQL_COL_SALT);
|
||||
TOTP_KEY = settings.getProperty(DatabaseSettings.MYSQL_COL_TOTP_KEY);
|
||||
LAST_IP = settings.getProperty(DatabaseSettings.MYSQL_COL_LAST_IP);
|
||||
LAST_LOGIN = settings.getProperty(DatabaseSettings.MYSQL_COL_LASTLOGIN);
|
||||
GROUP = settings.getProperty(DatabaseSettings.MYSQL_COL_GROUP);
|
||||
|
@ -232,6 +232,25 @@ public interface DataSource extends Reloadable {
|
||||
*/
|
||||
List<PlayerAuth> getRecentlyLoggedInPlayers();
|
||||
|
||||
/**
|
||||
* Sets the given TOTP key to the player's account.
|
||||
*
|
||||
* @param user the name of the player to modify
|
||||
* @param totpKey the totp key to set
|
||||
* @return True upon success, false upon failure
|
||||
*/
|
||||
boolean setTotpKey(String user, String totpKey);
|
||||
|
||||
/**
|
||||
* Removes the TOTP key if present of the given player's account.
|
||||
*
|
||||
* @param user the name of the player to modify
|
||||
* @return True upon success, false upon failure
|
||||
*/
|
||||
default boolean removeTotpKey(String user) {
|
||||
return setTotpKey(user, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the data source.
|
||||
*/
|
||||
|
@ -398,6 +398,11 @@ public class FlatFile implements DataSource {
|
||||
throw new UnsupportedOperationException("Flat file no longer supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setTotpKey(String user, String totpKey) {
|
||||
throw new UnsupportedOperationException("Flat file no longer supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PlayerAuth object from the read data.
|
||||
*
|
||||
|
@ -248,6 +248,11 @@ public class MySQL implements DataSource {
|
||||
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN "
|
||||
+ col.HAS_SESSION + " SMALLINT NOT NULL DEFAULT '0' AFTER " + col.IS_LOGGED);
|
||||
}
|
||||
|
||||
if (isColumnMissing(md, col.TOTP_KEY)) {
|
||||
st.executeUpdate("ALTER TABLE " + tableName
|
||||
+ " ADD COLUMN " + col.TOTP_KEY + " VARCHAR(16);");
|
||||
}
|
||||
}
|
||||
ConsoleLogger.info("MySQL setup finished");
|
||||
}
|
||||
@ -728,6 +733,20 @@ public class MySQL implements DataSource {
|
||||
return players;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setTotpKey(String user, String totpKey) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.TOTP_KEY + " = ? WHERE " + col.NAME + " = ?";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, totpKey);
|
||||
pst.setString(2, user.toLowerCase());
|
||||
pst.executeUpdate();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
logSqlException(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException {
|
||||
String salt = col.SALT.isEmpty() ? null : row.getString(col.SALT);
|
||||
int group = col.GROUP.isEmpty() ? -1 : row.getInt(col.GROUP);
|
||||
@ -735,6 +754,7 @@ public class MySQL implements DataSource {
|
||||
.name(row.getString(col.NAME))
|
||||
.realName(row.getString(col.REAL_NAME))
|
||||
.password(row.getString(col.PASSWORD), salt)
|
||||
.totpKey(row.getString(col.TOTP_KEY))
|
||||
.lastLogin(getNullableLong(row, col.LAST_LOGIN))
|
||||
.lastIp(row.getString(col.LAST_IP))
|
||||
.email(row.getString(col.EMAIL))
|
||||
|
@ -171,6 +171,11 @@ public class SQLite implements DataSource {
|
||||
st.executeUpdate("ALTER TABLE " + tableName
|
||||
+ " ADD COLUMN " + col.HAS_SESSION + " INT NOT NULL DEFAULT '0';");
|
||||
}
|
||||
|
||||
if (isColumnMissing(md, col.TOTP_KEY)) {
|
||||
st.executeUpdate("ALTER TABLE " + tableName
|
||||
+ " ADD COLUMN " + col.TOTP_KEY + " VARCHAR(16);");
|
||||
}
|
||||
}
|
||||
ConsoleLogger.info("SQLite Setup finished");
|
||||
}
|
||||
@ -654,6 +659,21 @@ public class SQLite implements DataSource {
|
||||
return players;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean setTotpKey(String user, String totpKey) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.TOTP_KEY + " = ? WHERE " + col.NAME + " = ?";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, totpKey);
|
||||
pst.setString(2, user.toLowerCase());
|
||||
pst.executeUpdate();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
logSqlException(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException {
|
||||
String salt = !col.SALT.isEmpty() ? row.getString(col.SALT) : null;
|
||||
|
||||
@ -662,6 +682,7 @@ public class SQLite implements DataSource {
|
||||
.email(row.getString(col.EMAIL))
|
||||
.realName(row.getString(col.REAL_NAME))
|
||||
.password(row.getString(col.PASSWORD), salt)
|
||||
.totpKey(row.getString(col.TOTP_KEY))
|
||||
.lastLogin(getNullableLong(row, col.LAST_LOGIN))
|
||||
.lastIp(row.getString(col.LAST_IP))
|
||||
.registrationDate(row.getLong(col.REGISTRATION_DATE))
|
||||
|
@ -79,6 +79,10 @@ public final class DatabaseSettings implements SettingsHolder {
|
||||
public static final Property<String> MYSQL_COL_HASSESSION =
|
||||
newProperty("DataSource.mySQLColumnHasSession", "hasSession");
|
||||
|
||||
@Comment("Column for storing a player's TOTP key (for two-factor authentication)")
|
||||
public static final Property<String> MYSQL_COL_TOTP_KEY =
|
||||
newProperty("DataSource.mySQLtotpKey", "totp");
|
||||
|
||||
@Comment("Column for storing the player's last IP")
|
||||
public static final Property<String> MYSQL_COL_LAST_IP =
|
||||
newProperty("DataSource.mySQLColumnIp", "ip");
|
||||
|
@ -103,12 +103,14 @@ public abstract class AbstractDataSourceIntegrationTest {
|
||||
assertThat(bobbyAuth, hasRegistrationInfo("127.0.4.22", 1436778723L));
|
||||
assertThat(bobbyAuth.getLastLogin(), equalTo(1449136800L));
|
||||
assertThat(bobbyAuth.getPassword(), equalToHash("$SHA$11aa0706173d7272$dbba966"));
|
||||
assertThat(bobbyAuth.getTotpKey(), equalTo("JBSWY3DPEHPK3PXP"));
|
||||
|
||||
assertThat(userAuth, hasAuthBasicData("user", "user", "user@example.org", "34.56.78.90"));
|
||||
assertThat(userAuth, hasAuthLocation(124.1, 76.3, -127.8, "nether", 0.23f, 4.88f));
|
||||
assertThat(userAuth, hasRegistrationInfo(null, 0));
|
||||
assertThat(userAuth.getLastLogin(), equalTo(1453242857L));
|
||||
assertThat(userAuth.getPassword(), equalToHash("b28c32f624a4eb161d6adc9acb5bfc5b", "f750ba32"));
|
||||
assertThat(userAuth.getTotpKey(), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -494,4 +496,33 @@ public abstract class AbstractDataSourceIntegrationTest {
|
||||
contains("user24", "user20", "user22", "user29", "user28",
|
||||
"user16", "user18", "user12", "user14", "user11"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSetTotpKey() {
|
||||
// given
|
||||
DataSource dataSource = getDataSource();
|
||||
String newTotpKey = "My new TOTP key";
|
||||
|
||||
// when
|
||||
dataSource.setTotpKey("BObBy", newTotpKey);
|
||||
dataSource.setTotpKey("does-not-exist", "bogus");
|
||||
|
||||
// then
|
||||
assertThat(dataSource.getAuth("bobby").getTotpKey(), equalTo(newTotpKey));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRemoveTotpKey() {
|
||||
// given
|
||||
DataSource dataSource = getDataSource();
|
||||
|
||||
// when
|
||||
dataSource.removeTotpKey("BoBBy");
|
||||
dataSource.removeTotpKey("user");
|
||||
dataSource.removeTotpKey("does-not-exist");
|
||||
|
||||
// then
|
||||
assertThat(dataSource.getAuth("bobby").getTotpKey(), nullValue());
|
||||
assertThat(dataSource.getAuth("user").getTotpKey(), nullValue());
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ CREATE TABLE authme (
|
||||
id INTEGER AUTO_INCREMENT,
|
||||
username VARCHAR(255) NOT NULL UNIQUE,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
totp VARCHAR(16),
|
||||
ip VARCHAR(40),
|
||||
lastlogin BIGINT,
|
||||
regdate BIGINT NOT NULL,
|
||||
@ -22,7 +23,7 @@ CREATE TABLE authme (
|
||||
CONSTRAINT table_const_prim PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
INSERT INTO authme (id, username, password, ip, lastlogin, x, y, z, world, yaw, pitch, email, isLogged, realname, salt, regdate, regip)
|
||||
VALUES (1,'bobby','$SHA$11aa0706173d7272$dbba966','123.45.67.89',1449136800,1.05,2.1,4.2,'world',-0.44,2.77,'your@email.com',0,'Bobby',NULL,1436778723,'127.0.4.22');
|
||||
INSERT INTO authme (id, username, password, ip, lastlogin, x, y, z, world, yaw, pitch, email, isLogged, realname, salt, regdate, regip, totp)
|
||||
VALUES (1,'bobby','$SHA$11aa0706173d7272$dbba966','123.45.67.89',1449136800,1.05,2.1,4.2,'world',-0.44,2.77,'your@email.com',0,'Bobby',NULL,1436778723,'127.0.4.22','JBSWY3DPEHPK3PXP');
|
||||
INSERT INTO authme (id, username, password, ip, lastlogin, x, y, z, world, yaw, pitch, email, isLogged, realname, salt, regdate)
|
||||
VALUES (NULL,'user','b28c32f624a4eb161d6adc9acb5bfc5b','34.56.78.90',1453242857,124.1,76.3,-127.8,'nether',0.23,4.88,'user@example.org',0,'user','f750ba32',0);
|
||||
|
Loading…
Reference in New Issue
Block a user