implemented resetting passwords

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
2024-05-19 11:15:24 +00:00
parent 4dcde76a08
commit 3c864a12ed
13 changed files with 393 additions and 160 deletions

View File

@@ -10,6 +10,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import static de.srsoftware.widerhall.Constants.*;
import static de.srsoftware.widerhall.Util.t;
import static de.srsoftware.widerhall.data.MailingList.HOLD_TIME;
@@ -19,6 +20,7 @@ import static de.srsoftware.widerhall.data.MailingList.HOLD_TIME;
*/
public class Database {
private static final Logger LOG = LoggerFactory.getLogger(Database.class);
private static final String DB_VERSION = "db_version";
private static Database singleton = null; // we only need one db handle ever. This will be it.
private final Connection conn; // the actual db connection handle within the singleton
@@ -295,6 +297,14 @@ public class Database {
return this;
}
private void createVersionTable() throws SQLException {
var sql = "CREATE TABLE %s (%s %s NOT NULL PRIMARY KEY);".formatted(DB_VERSION,DB_VERSION,INT);
var db = Database.open();
db.query(sql).compile().run();
sql = "INSERT INTO %s VALUES (1)".formatted(DB_VERSION);
db.query(sql).compile().run();
}
private boolean columnExists(String tableName, String columnName) throws SQLException {
var rs = Database.open().select("pragma_table_info('"+tableName+"')","COUNT(*) AS num").where("name",columnName).compile().exec();
try {
@@ -357,6 +367,7 @@ public class Database {
try {
singleton = new Database(DriverManager.getConnection(url));
singleton.assertTables(); // must not be concatenated to exception above (assertTables accesses singleton)!
singleton.update202405();
} catch (SQLException sqle) {
sqle.printStackTrace();
}
@@ -373,6 +384,10 @@ public class Database {
return new Request(sql);
}
public Request query(String sql) {
return new Request(new StringBuilder(sql));
}
/**
* create a SELECT [flields] FROM [table] request.
* If no fields are supplied, a request in the form SELECT * FROM [table] will be generated.
@@ -390,7 +405,6 @@ public class Database {
return new Request(sql.append(" FROM ").append(tableName));
}
/**
* check, whether a table with the provided name exists
* @param tbName
@@ -420,4 +434,12 @@ public class Database {
public Request update(String tableName) {
return new Request(new StringBuilder("UPDATE ").append(tableName));
}
private Database update202405() throws SQLException {
if (!tableExists(Database.DB_VERSION)) {
createVersionTable();
User.addTokenColumn();
}
return this;
}
}

View File

@@ -49,8 +49,8 @@ public class MailingList implements MessageHandler, ProblemListener {
private static final String SMTP_PASS = "smtp_pass";
public static final String TABLE_NAME = "Lists";
private static final int STATE_PENDING = 0;
private static final int STATE_ENABLED = 1; // do we process incoming messages?
private static final int STATE_PUBLIC = 2; // can guests see this ML?
public static final int STATE_ENABLED = 1; // do we process incoming messages?
public static final int STATE_PUBLIC = 2; // can guests see this ML?
public static final int STATE_FORWARD_FROM = 4; // set original sender as FROM when forwarding?
public static final int STATE_FORWARD_ATTACHED = 8; // forward messages as attachment?
public static final int STATE_HIDE_RECEIVERS = 16; // send using BCC receivers
@@ -283,6 +283,18 @@ public class MailingList implements MessageHandler, ProblemListener {
return hasState(STATE_OPEN_FOR_GUESTS|STATE_OPEN_FOR_SUBSCRIBERS);
}
public static List<MailingList> listActive() {
try {
var list = new ArrayList<MailingList>();
var rs = Database.open().select(TABLE_NAME).where(STATE+" % 2",1).compile().exec();
while (rs.next()) list.add(MailingList.from(rs));
return list;
} catch (SQLException e){
LOG.debug("Failed to load active MailingLists");
}
return List.of();
}
/**
* Load a ML object by it's identifying email address.
* This is a cached method: if the ML has been loaded before, the already-loaded object will be returned.
@@ -308,6 +320,7 @@ public class MailingList implements MessageHandler, ProblemListener {
return ml;
}
/**
* Load a ML object by it's identifying email address.
* This is a cached method: if the ML has been loaded before, the already-loaded object will be returned.
@@ -679,6 +692,10 @@ public class MailingList implements MessageHandler, ProblemListener {
}
}
public void sendPasswordReset(String email, String subject,String text) throws MessagingException, UnsupportedEncodingException {
smtp.send(email(),name(),email,subject,text);
}
protected SmtpClient smtp(){
return smtp;
}

View File

@@ -6,6 +6,7 @@ import java.security.InvalidKeyException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import static de.srsoftware.widerhall.Constants.*;
@@ -20,10 +21,11 @@ public class User {
public static final int PERMISSION_ADMIN = 1;
public static final int PERMISSION_CREATE_LISTS = 2;
public static final String HASHED_PASS = "hashedPassword";
public static final String RESET_TOKEN = "resetToken";
public static final String SALT = "salt";
private static final HashMap<String,User> users = new HashMap<>();
private String email, salt, hashedPass, name;
private String email, salt, hashedPass, name, token;
private int permissions;
/**
@@ -34,11 +36,12 @@ public class User {
* @param hashedPass
* @param permissions
*/
public User(String email, String name, String salt, String hashedPass, int permissions) {
public User(String email, String name, String salt, String hashedPass, String token, int permissions) {
this.email = email.toLowerCase();
this.name = name;
this.salt = salt;
this.hashedPass = hashedPass;
this.token = token;
this.permissions = permissions;
}
@@ -81,6 +84,21 @@ public class User {
.run();
}
public static void addTokenColumn() throws SQLException {
String sql = "ALTER TABLE %s ADD COLUMN %s %s;".formatted(TABLE_NAME,RESET_TOKEN,VARCHAR);
Database.open().query(sql).compile().run();
}
public static User byToken(String token) throws SQLException {
if (token == null || token.isBlank()) return null;
var rs = Database.open().select(TABLE_NAME).where(RESET_TOKEN,token).compile().exec();
try {
if (rs.next()) return User.from(rs);
return null;
} finally {
rs.close();
}
}
/**
* Create a new user object by hashing it's password and storing user data, salt and hashed password to the db.
@@ -96,10 +114,10 @@ public class User {
String salt = null;
String hashedPass = null;
if (password != null) {
salt = Util.sha256(email + name + LocalDate.now());
salt = Util.sha256(email + LocalDateTime.now() + name);
hashedPass = Util.sha256(password + salt);
}
return new User(email,name,salt,hashedPass,0).save();
return new User(email,name,salt,hashedPass,null,0).save();
}
/**
@@ -115,7 +133,6 @@ public class User {
.append(PERMISSIONS).append(" ").append(INT).append(", ")
.append(SALT).append(" ").append(VARCHAR).append(", ")
.append(HASHED_PASS).append(" ").append(VARCHAR)
.append(");");
Database.open().query(sql).compile().run();
}
@@ -195,10 +212,16 @@ public class User {
rs.getString(NAME),
rs.getString(SALT),
rs.getString(HASHED_PASS),
rs.getString(RESET_TOKEN),
rs.getInt(PERMISSIONS)));
return user;
}
public String generateToken() throws SQLException {
token = Util.randomString(64);
Database.open().update(TABLE_NAME).set(RESET_TOKEN,token).where(EMAIL,this.email).compile().run();
return token;
}
/**
* Loads the user identified by it's email, but only if the provided password matches.
@@ -284,4 +307,18 @@ public class User {
req.compile().run();
return this;
}
public void setPassword(String newPassword) throws SQLException {
if (newPassword != null) {
String newSalt = Util.sha256(email + LocalDateTime.now() + name);
String newHashedPass = Util.sha256(newPassword + newSalt);
Database.open().update(TABLE_NAME).set(HASHED_PASS,newHashedPass).set(SALT,newSalt).where(EMAIL,email).compile().run();
hashedPass = newHashedPass;
salt = newSalt;
}
}
public String token() {
return token;
}
}