mirror of
https://github.com/AuthMe/AuthMeReloaded.git
synced 2024-12-24 01:27:35 +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". */
|
/** The player's name in the correct casing, e.g. "Xephi". */
|
||||||
private String realName;
|
private String realName;
|
||||||
private HashedPassword password;
|
private HashedPassword password;
|
||||||
|
private String totpKey;
|
||||||
private String email;
|
private String email;
|
||||||
private String lastIp;
|
private String lastIp;
|
||||||
private int groupId;
|
private int groupId;
|
||||||
@ -160,6 +161,10 @@ public class PlayerAuth {
|
|||||||
this.registrationDate = registrationDate;
|
this.registrationDate = registrationDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTotpKey() {
|
||||||
|
return totpKey;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (!(obj instanceof PlayerAuth)) {
|
if (!(obj instanceof PlayerAuth)) {
|
||||||
@ -195,6 +200,7 @@ public class PlayerAuth {
|
|||||||
private String name;
|
private String name;
|
||||||
private String realName;
|
private String realName;
|
||||||
private HashedPassword password;
|
private HashedPassword password;
|
||||||
|
private String totpKey;
|
||||||
private String lastIp;
|
private String lastIp;
|
||||||
private String email;
|
private String email;
|
||||||
private int groupId = -1;
|
private int groupId = -1;
|
||||||
@ -219,6 +225,7 @@ public class PlayerAuth {
|
|||||||
auth.nickname = checkNotNull(name).toLowerCase();
|
auth.nickname = checkNotNull(name).toLowerCase();
|
||||||
auth.realName = firstNonNull(realName, "Player");
|
auth.realName = firstNonNull(realName, "Player");
|
||||||
auth.password = firstNonNull(password, new HashedPassword(""));
|
auth.password = firstNonNull(password, new HashedPassword(""));
|
||||||
|
auth.totpKey = totpKey;
|
||||||
auth.email = DB_EMAIL_DEFAULT.equals(email) ? null : email;
|
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.lastIp = lastIp; // Don't check against default value 127.0.0.1 as it may be a legit value
|
||||||
auth.groupId = groupId;
|
auth.groupId = groupId;
|
||||||
@ -258,6 +265,11 @@ public class PlayerAuth {
|
|||||||
return password(new HashedPassword(hash, salt));
|
return password(new HashedPassword(hash, salt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder totpKey(String totpKey) {
|
||||||
|
this.totpKey = totpKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder lastIp(String lastIp) {
|
public Builder lastIp(String lastIp) {
|
||||||
this.lastIp = lastIp;
|
this.lastIp = lastIp;
|
||||||
return this;
|
return this;
|
||||||
|
@ -268,6 +268,15 @@ public class CacheDataSource implements DataSource {
|
|||||||
return source.getRecentlyLoggedInPlayers();
|
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
|
@Override
|
||||||
public void invalidateCache(String playerName) {
|
public void invalidateCache(String playerName) {
|
||||||
cachedAuths.invalidate(playerName);
|
cachedAuths.invalidate(playerName);
|
||||||
|
@ -14,6 +14,7 @@ public final class Columns {
|
|||||||
public final String REAL_NAME;
|
public final String REAL_NAME;
|
||||||
public final String PASSWORD;
|
public final String PASSWORD;
|
||||||
public final String SALT;
|
public final String SALT;
|
||||||
|
public final String TOTP_KEY;
|
||||||
public final String LAST_IP;
|
public final String LAST_IP;
|
||||||
public final String LAST_LOGIN;
|
public final String LAST_LOGIN;
|
||||||
public final String GROUP;
|
public final String GROUP;
|
||||||
@ -35,6 +36,7 @@ public final class Columns {
|
|||||||
REAL_NAME = settings.getProperty(DatabaseSettings.MYSQL_COL_REALNAME);
|
REAL_NAME = settings.getProperty(DatabaseSettings.MYSQL_COL_REALNAME);
|
||||||
PASSWORD = settings.getProperty(DatabaseSettings.MYSQL_COL_PASSWORD);
|
PASSWORD = settings.getProperty(DatabaseSettings.MYSQL_COL_PASSWORD);
|
||||||
SALT = settings.getProperty(DatabaseSettings.MYSQL_COL_SALT);
|
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_IP = settings.getProperty(DatabaseSettings.MYSQL_COL_LAST_IP);
|
||||||
LAST_LOGIN = settings.getProperty(DatabaseSettings.MYSQL_COL_LASTLOGIN);
|
LAST_LOGIN = settings.getProperty(DatabaseSettings.MYSQL_COL_LASTLOGIN);
|
||||||
GROUP = settings.getProperty(DatabaseSettings.MYSQL_COL_GROUP);
|
GROUP = settings.getProperty(DatabaseSettings.MYSQL_COL_GROUP);
|
||||||
|
@ -232,6 +232,25 @@ public interface DataSource extends Reloadable {
|
|||||||
*/
|
*/
|
||||||
List<PlayerAuth> getRecentlyLoggedInPlayers();
|
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.
|
* Reload the data source.
|
||||||
*/
|
*/
|
||||||
|
@ -398,6 +398,11 @@ public class FlatFile implements DataSource {
|
|||||||
throw new UnsupportedOperationException("Flat file no longer supported");
|
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.
|
* Creates a PlayerAuth object from the read data.
|
||||||
*
|
*
|
||||||
|
@ -248,6 +248,11 @@ public class MySQL implements DataSource {
|
|||||||
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN "
|
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN "
|
||||||
+ col.HAS_SESSION + " SMALLINT NOT NULL DEFAULT '0' AFTER " + col.IS_LOGGED);
|
+ 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");
|
ConsoleLogger.info("MySQL setup finished");
|
||||||
}
|
}
|
||||||
@ -728,6 +733,20 @@ public class MySQL implements DataSource {
|
|||||||
return players;
|
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 {
|
private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException {
|
||||||
String salt = col.SALT.isEmpty() ? null : row.getString(col.SALT);
|
String salt = col.SALT.isEmpty() ? null : row.getString(col.SALT);
|
||||||
int group = col.GROUP.isEmpty() ? -1 : row.getInt(col.GROUP);
|
int group = col.GROUP.isEmpty() ? -1 : row.getInt(col.GROUP);
|
||||||
@ -735,6 +754,7 @@ public class MySQL implements DataSource {
|
|||||||
.name(row.getString(col.NAME))
|
.name(row.getString(col.NAME))
|
||||||
.realName(row.getString(col.REAL_NAME))
|
.realName(row.getString(col.REAL_NAME))
|
||||||
.password(row.getString(col.PASSWORD), salt)
|
.password(row.getString(col.PASSWORD), salt)
|
||||||
|
.totpKey(row.getString(col.TOTP_KEY))
|
||||||
.lastLogin(getNullableLong(row, col.LAST_LOGIN))
|
.lastLogin(getNullableLong(row, col.LAST_LOGIN))
|
||||||
.lastIp(row.getString(col.LAST_IP))
|
.lastIp(row.getString(col.LAST_IP))
|
||||||
.email(row.getString(col.EMAIL))
|
.email(row.getString(col.EMAIL))
|
||||||
|
@ -171,6 +171,11 @@ public class SQLite implements DataSource {
|
|||||||
st.executeUpdate("ALTER TABLE " + tableName
|
st.executeUpdate("ALTER TABLE " + tableName
|
||||||
+ " ADD COLUMN " + col.HAS_SESSION + " INT NOT NULL DEFAULT '0';");
|
+ " 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");
|
ConsoleLogger.info("SQLite Setup finished");
|
||||||
}
|
}
|
||||||
@ -654,6 +659,21 @@ public class SQLite implements DataSource {
|
|||||||
return players;
|
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 {
|
private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException {
|
||||||
String salt = !col.SALT.isEmpty() ? row.getString(col.SALT) : null;
|
String salt = !col.SALT.isEmpty() ? row.getString(col.SALT) : null;
|
||||||
|
|
||||||
@ -662,6 +682,7 @@ public class SQLite implements DataSource {
|
|||||||
.email(row.getString(col.EMAIL))
|
.email(row.getString(col.EMAIL))
|
||||||
.realName(row.getString(col.REAL_NAME))
|
.realName(row.getString(col.REAL_NAME))
|
||||||
.password(row.getString(col.PASSWORD), salt)
|
.password(row.getString(col.PASSWORD), salt)
|
||||||
|
.totpKey(row.getString(col.TOTP_KEY))
|
||||||
.lastLogin(getNullableLong(row, col.LAST_LOGIN))
|
.lastLogin(getNullableLong(row, col.LAST_LOGIN))
|
||||||
.lastIp(row.getString(col.LAST_IP))
|
.lastIp(row.getString(col.LAST_IP))
|
||||||
.registrationDate(row.getLong(col.REGISTRATION_DATE))
|
.registrationDate(row.getLong(col.REGISTRATION_DATE))
|
||||||
|
@ -79,6 +79,10 @@ public final class DatabaseSettings implements SettingsHolder {
|
|||||||
public static final Property<String> MYSQL_COL_HASSESSION =
|
public static final Property<String> MYSQL_COL_HASSESSION =
|
||||||
newProperty("DataSource.mySQLColumnHasSession", "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")
|
@Comment("Column for storing the player's last IP")
|
||||||
public static final Property<String> MYSQL_COL_LAST_IP =
|
public static final Property<String> MYSQL_COL_LAST_IP =
|
||||||
newProperty("DataSource.mySQLColumnIp", "ip");
|
newProperty("DataSource.mySQLColumnIp", "ip");
|
||||||
|
@ -103,12 +103,14 @@ public abstract class AbstractDataSourceIntegrationTest {
|
|||||||
assertThat(bobbyAuth, hasRegistrationInfo("127.0.4.22", 1436778723L));
|
assertThat(bobbyAuth, hasRegistrationInfo("127.0.4.22", 1436778723L));
|
||||||
assertThat(bobbyAuth.getLastLogin(), equalTo(1449136800L));
|
assertThat(bobbyAuth.getLastLogin(), equalTo(1449136800L));
|
||||||
assertThat(bobbyAuth.getPassword(), equalToHash("$SHA$11aa0706173d7272$dbba966"));
|
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, 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, hasAuthLocation(124.1, 76.3, -127.8, "nether", 0.23f, 4.88f));
|
||||||
assertThat(userAuth, hasRegistrationInfo(null, 0));
|
assertThat(userAuth, hasRegistrationInfo(null, 0));
|
||||||
assertThat(userAuth.getLastLogin(), equalTo(1453242857L));
|
assertThat(userAuth.getLastLogin(), equalTo(1453242857L));
|
||||||
assertThat(userAuth.getPassword(), equalToHash("b28c32f624a4eb161d6adc9acb5bfc5b", "f750ba32"));
|
assertThat(userAuth.getPassword(), equalToHash("b28c32f624a4eb161d6adc9acb5bfc5b", "f750ba32"));
|
||||||
|
assertThat(userAuth.getTotpKey(), nullValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -494,4 +496,33 @@ public abstract class AbstractDataSourceIntegrationTest {
|
|||||||
contains("user24", "user20", "user22", "user29", "user28",
|
contains("user24", "user20", "user22", "user29", "user28",
|
||||||
"user16", "user18", "user12", "user14", "user11"));
|
"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,
|
id INTEGER AUTO_INCREMENT,
|
||||||
username VARCHAR(255) NOT NULL UNIQUE,
|
username VARCHAR(255) NOT NULL UNIQUE,
|
||||||
password VARCHAR(255) NOT NULL,
|
password VARCHAR(255) NOT NULL,
|
||||||
|
totp VARCHAR(16),
|
||||||
ip VARCHAR(40),
|
ip VARCHAR(40),
|
||||||
lastlogin BIGINT,
|
lastlogin BIGINT,
|
||||||
regdate BIGINT NOT NULL,
|
regdate BIGINT NOT NULL,
|
||||||
@ -22,7 +23,7 @@ CREATE TABLE authme (
|
|||||||
CONSTRAINT table_const_prim PRIMARY KEY (id)
|
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)
|
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');
|
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)
|
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);
|
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