Skip to content

Commit

Permalink
Issue #113: Use same account for Google login and e-mail signup.
Browse files Browse the repository at this point in the history
  • Loading branch information
haumacher committed Jan 7, 2025
1 parent 19212ad commit 0c2140e
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ public class RegistrationServlet extends HttpServlet {

private static final String PASSWORD_ATTR = "passwd";

/**
* The authorization scope "email".
*/
public static final String IDENTIFIED_BY_EMAIL = "email";

private static final Logger LOG = LoggerFactory.getLogger(RegistrationServlet.class);

@Override
Expand All @@ -57,26 +52,23 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S
String email = (String) req.getSession().getAttribute("email");

String login;
String passwd;
try {
DB db = DBService.getInstance();
String extId = email.trim().toLowerCase();
login = db.getLogin(RegistrationServlet.IDENTIFIED_BY_EMAIL, extId);
login = db.getEmailLogin(email);
if (login == null) {
login = UUID.randomUUID().toString();
passwd = db.createUser(IDENTIFIED_BY_EMAIL, extId, login, email);
String passwd = db.createUser(login, email);
db.setEmail(login, email);
startSetup(req, resp, login, passwd);
} else {
passwd = db.resetPassword(login);
startSetup(req, resp, login, null);
}
} catch (Exception ex) {
LOG.error("Failed to create user: " + email, ex);

sendError(req, resp, "Bei der Erstellung des Accounts ist ein Fehler aufgetreten: " + ex.getMessage());
return;
}

startSetup(req, resp, login, passwd);
}

/**
Expand All @@ -85,13 +77,20 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S
public static void startSetup(HttpServletRequest req, HttpServletResponse resp,
String login, String passwd) throws ServletException, IOException {
LoginFilter.setAuthenticatedUser(req, login);
req.getSession().setAttribute(PASSWORD_ATTR, passwd);
if (passwd != null) {
req.getSession().setAttribute(PASSWORD_ATTR, passwd);
}

String location = LoginServlet.location(req);
if (location != null) {
resp.sendRedirect(req.getContextPath() + location);
} else {
resp.sendRedirect(req.getContextPath() + successPage(req));
if (passwd == null) {
// Was already registered, no automatic password-reset.
resp.sendRedirect(req.getContextPath() + SettingsServlet.PATH);
} else {
resp.sendRedirect(req.getContextPath() + successPage(req));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.io.IOException;
import java.util.UUID;

import jakarta.mail.internet.AddressException;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
Expand Down Expand Up @@ -45,17 +46,22 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S
DB db = DBService.getInstance();
String email = sessionInfo.getEmail();

String extId = email.trim().toLowerCase();
String login = db.getLogin(RegistrationServlet.IDENTIFIED_BY_EMAIL, extId);
String login;
String password;
if (login == null) {
login = UUID.randomUUID().toString();
password = db.createUser(RegistrationServlet.IDENTIFIED_BY_EMAIL, extId, login, email);
} else {
password = db.resetPassword(login);
try {
login = db.getEmailLogin(email);
if (login == null) {
login = UUID.randomUUID().toString();
password = db.createUser(login, email);
db.setEmail(login, email);
} else {
password = db.resetPassword(login);
}
} catch (AddressException e) {
ServletUtil.sendError(resp, "Invalid e-mail address.");
return;
}

db.setEmail(login, email);

ServletUtil.sendResult(req, resp, RegistrationResult.create().setSession(sessionInfo.getSession()).setLogin(login).setPassword(password));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.Optional;
import java.util.UUID;

import jakarta.mail.internet.AddressException;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
Expand Down Expand Up @@ -76,12 +77,29 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
displayName = null;
}

String extId = userProfile.getId();
String googleId = userProfile.getId();

Optional<Object> location = sessionStore.get(context, LoginServlet.LOCATION_ATTRIBUTE);

DB db = DBService.getInstance();
String login = db.getLogin(clientName, extId);
String login = db.getGoogleLogin(googleId);
if (login == null) {
if (email != null && !email.isBlank()) {
try {
login = db.getEmailLogin(email);
if (login != null) {
// Link accounts.
db.setGoogleId(login, googleId, displayName);
}
} catch (AddressException e) {
LOG.warn("Reveived invalid e-mail address during Google login of {} login: {}", googleId, email);

// Do not try again, see below.
email = null;
}
}
}

if (login == null) {
login = UUID.randomUUID().toString();

Expand All @@ -93,10 +111,14 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
}
}

String passwd = db.createUser(clientName, extId, login, displayName);
db.setExtId(login, extId);
String passwd = db.createUser(login, displayName);
db.setGoogleId(login, googleId, displayName);
if (email != null) {
db.setEmail(login, email);
try {
db.setEmail(login, email);
} catch (AddressException e) {
LOG.warn("Reveived invalid e-mail address during Google login of {} login: {}", googleId, email);
}
}

if (location.isEmpty()) {
Expand Down
50 changes: 38 additions & 12 deletions phoneblock/src/main/java/de/haumacher/phoneblock/db/DB.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@
import de.haumacher.phoneblock.mail.MailService;
import de.haumacher.phoneblock.mail.check.db.Domains;
import de.haumacher.phoneblock.scheduler.SchedulerService;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;

/**
* The database abstraction layer.
Expand Down Expand Up @@ -438,13 +440,13 @@ public String generateVerificationCode() {
/**
* Creates a new PhoneBlock user account.
* @param clientName The authorization scope for the new user.
* @param extId The ID in the given authorization scope.
* @param googleId The ID for Google authentication.
* @param login The user name (e.g. e-mail address) of the new account.
* @return The randomly generated password for the account.
*/
public String createUser(String clientName, String extId, String login, String displayName) {
public String createUser(String login, String displayName) {
String passwd = createPassword(20);
addUser(clientName, extId, login, displayName, passwd);
addUser(login, displayName, passwd);
return passwd;
}

Expand Down Expand Up @@ -653,21 +655,25 @@ public String createId(int length) {
/**
* Sets the user's e-mail address.
*/
public void setEmail(String login, String email) {
public void setEmail(String login, String email) throws AddressException {
try (SqlSession session = openSession()) {
Users users = session.getMapper(Users.class);
users.setEmail(login, email);
users.setEmail(login, canonicalEMail(email));
session.commit();
}
}

/**
* Sets the user's external ID in its OAuth authorization scope.
* @param displayName
*/
public void setExtId(String login, String extId) {
public void setGoogleId(String login, String googleId, String displayName) {
try (SqlSession session = openSession()) {
Users users = session.getMapper(Users.class);
users.setExtId(login, extId);
users.setGoogleId(login, googleId);
if (displayName != null && !displayName.isBlank()) {
users.setDisplayName(login, displayName);
}
session.commit();
}
}
Expand Down Expand Up @@ -1311,10 +1317,10 @@ public void shutdown() {
/**
* Adds the given user with the given password.
*/
public void addUser(String clientName, String extId, String login, String displayName, String passwd) {
public void addUser(String login, String displayName, String passwd) {
try (SqlSession session = openSession()) {
Users users = session.getMapper(Users.class);
users.addUser(login, clientName, extId, displayName, pwhash(passwd), System.currentTimeMillis());
users.addUser(login, displayName, pwhash(passwd), System.currentTimeMillis());
session.commit();
}
}
Expand All @@ -1341,16 +1347,36 @@ public String resetPassword(String login) {
}

/**
* The user ID, or <code>null</code>, if the user with the given login does not yet exist.
* The local user ID, or <code>null</code>, if the user with the given login does not yet exist.
*
* @param googleId the user's ID in the Google OpenID provider.
*/
public String getLogin(String clientName, String extId) {
public String getGoogleLogin(String googleId) {
try (SqlSession session = openSession()) {
Users users = session.getMapper(Users.class);

return users.getLogin(clientName, extId);
return users.getGoogleLogin(googleId);
}
}

/**
* The local user ID, or <code>null</code>, if the user with the given login does not yet exist.
*
* @param email the user's e-mail address.
*/
public String getEmailLogin(String email) throws AddressException {
try (SqlSession session = openSession()) {
Users users = session.getMapper(Users.class);

return users.getEmailLogin(canonicalEMail(email));
}
}

private String canonicalEMail(String email) throws AddressException {
InternetAddress address = new InternetAddress(email);
return address.getAddress().strip().toLowerCase();
}

/**
* Sets the last access time for the given user to the given timestamp.
*
Expand Down
26 changes: 17 additions & 9 deletions phoneblock/src/main/java/de/haumacher/phoneblock/db/Users.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,28 @@
import org.apache.ibatis.annotations.Update;

import de.haumacher.phoneblock.ab.DBAnswerbotInfo;
import de.haumacher.phoneblock.ab.proto.AnswerbotInfo;
import de.haumacher.phoneblock.db.settings.AuthToken;

/**
* Operations for user management.
*/
public interface Users {

@Insert("insert into USERS (LOGIN, CLIENTNAME, EXTID, DISPLAYNAME, PWHASH, REGISTERED, MIN_VOTES, MAX_LENGTH) " +
"values (#{login}, #{clientName}, #{extId}, #{displayName}, #{pwhash}, #{registered}, 4, 2000)")
void addUser(String login, String clientName, String extId, String displayName, byte[] pwhash, long registered);
@Insert("insert into USERS (LOGIN, DISPLAYNAME, PWHASH, REGISTERED, MIN_VOTES, MAX_LENGTH) " +
"values (#{login}, #{displayName}, #{pwhash}, #{registered}, 4, 2000)")
void addUser(String login, String displayName, byte[] pwhash, long registered);

@Update("update USERS set PWHASH=#{pwhash} where ID=#{userId}")
void setPassword(long userId, byte[] pwhash);

@Update("update USERS set EMAIL=#{email} where LOGIN=#{login}")
void setEmail(String login, String email);

@Update("update USERS set EXTID=#{extId} where LOGIN=#{login}")
void setExtId(String login, String extId);
@Update("update USERS set GOOGLEID=#{googleId} where LOGIN=#{login}")
void setGoogleId(String login, String googleId);

@Update("update USERS set DISPLAYNAME=#{displayName} where LOGIN=#{login}")
void setDisplayName(String login, String displayName);

@Delete("delete from USERS where LOGIN=#{login}")
void deleteUser(String login);
Expand Down Expand Up @@ -131,10 +133,16 @@ insert into TOKENS (
Long getUserId(String login);

/**
* Retrieves the user ID for the user with the given user name (e-mail).
* Retrieves the user ID for the user with the given Google ID.
*/
@Select("select LOGIN from USERS where GOOGLEID=#{googleId}")
String getGoogleLogin(String googleId);

/**
* Retrieves the user ID for the user with the given e-mail.
*/
@Select("select LOGIN from USERS where CLIENTNAME=#{clientName} and EXTID=#{extId}")
String getLogin(String clientName, String extId);
@Select("select LOGIN from USERS where EMAIL=#{email}")
String getEmailLogin(String email);

@Select("select ID, LOGIN, DISPLAYNAME, EMAIL, MIN_VOTES, MAX_LENGTH, WILDCARDS, LASTACCESS from USERS where LOGIN=#{login}")
DBUserSettings getSettings(String login);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ALTER TABLE USERS ADD GOOGLEID CHARACTER VARYING(64);

UPDATE USERS SET GOOGLEID = EXTID WHERE CLIENTNAME = 'Google2Client';

ALTER TABLE USERS ALTER COLUMN EXTID VARCHAR_IGNORECASE(64) NULL;
ALTER TABLE USERS ALTER COLUMN CLIENTNAME VARCHAR_IGNORECASE(64) NULL;
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,9 @@ CREATE TABLE USERS (
LOGIN CHARACTER VARYING(255) NOT NULL,
PWHASH BINARY VARYING(128) NOT NULL,

CLIENTNAME CHARACTER VARYING(64) NOT NULL,
EXTID CHARACTER VARYING(255),
CLIENTNAME CHARACTER VARYING(64) NULL,
EXTID CHARACTER VARYING(64) NULL,
GOOGLEID CHARACTER VARYING(64) NULL,

DISPLAYNAME CHARACTER VARYING(255) NOT NULL,
EMAIL CHARACTER VARYING(255),
Expand All @@ -158,8 +159,9 @@ CREATE TABLE USERS (
CONSTRAINT USERS_PK PRIMARY KEY (ID)
);

CREATE INDEX USERS_CLIENTNAME_IDX ON USERS (CLIENTNAME,EXTID);
CREATE UNIQUE INDEX USERS_UN_INDEX_4 ON USERS (LOGIN);
CREATE INDEX USERS_EMAIL_INDEX ON USERS (EMAIL);
CREATE INDEX USERS_GOOGLEID_IDX ON USERS (GOOGLEID);

CREATE TABLE PUBLIC.CONTRIBUTIONS (
ID BIGINT NOT NULL AUTO_INCREMENT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public static void main(String[] args) throws SQLException, IOException {
} else {
login = args[0];
}
String passwd = db.createUser(RegistrationServlet.IDENTIFIED_BY_EMAIL, login, login, login);
String passwd = db.createUser(login, login);

System.out.println(passwd);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public void tearDown() throws Exception {

@Test
void testAuthToken() {
_db.createUser("test", "test1", "user1", "User 1");
_db.createUser("user1", "User 1");

long time = 1000;
final long createTime = time;
Expand Down Expand Up @@ -264,8 +264,8 @@ void testDuplicateAdd() {

@Test
void testUserManagement() throws IOException {
_db.addUser("none", "1", "[email protected]", "Mr. X", "123");
_db.addUser("none", "2", "[email protected]", "Mr. Y", "123");
_db.addUser("[email protected]", "Mr. X", "123");
_db.addUser("[email protected]", "Mr. Y", "123");

assertEquals("[email protected]", _db.basicAuth(header("[email protected]", "123")));
assertNull(_db.basicAuth(header("[email protected]", "321")));
Expand Down Expand Up @@ -446,7 +446,7 @@ protected void checkPhone(String phone, int votes, int cnt10, int votes10, int c

@Test
void testRating() {
_db.createUser("domain", "ext-1", "user-1", "User 1");
_db.createUser("user-1", "User 1");

long time = 1;

Expand Down

0 comments on commit 0c2140e

Please sign in to comment.