Skip to content

Commit

Permalink
Hash random codes
Browse files Browse the repository at this point in the history
  • Loading branch information
LaurentTreguier committed Aug 26, 2024
1 parent 78424e3 commit ea1389f
Show file tree
Hide file tree
Showing 11 changed files with 66 additions and 45 deletions.
19 changes: 10 additions & 9 deletions src/main/java/app/fyreplace/api/emails/EmailBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import app.fyreplace.api.data.RandomCode;
import app.fyreplace.api.services.LocaleService;
import app.fyreplace.api.services.RandomService;
import io.quarkus.elytron.security.common.BcryptUtil;
import io.quarkus.mailer.Mail;
import io.quarkus.mailer.Mailer;
import io.quarkus.qute.TemplateInstance;
Expand Down Expand Up @@ -41,7 +42,7 @@ public abstract class EmailBase extends Mail {
@Inject
LocaleService localeService;

private RandomCode code;
private String randomCodeClearText;

private Email email;

Expand All @@ -62,17 +63,17 @@ public void sendTo(final Email email) {
.setTo(List.of(email.email)));
}

protected RandomCode getRandomCode() {
if (code != null) {
return code;
protected String getRandomCode() {
if (randomCodeClearText != null) {
return randomCodeClearText;
}

randomCodeClearText = randomService.generateCode(RandomCode.LENGTH);
final var randomCode = new RandomCode();
randomCode.email = email;
randomCode.code = randomService.generateCode(RandomCode.LENGTH);
randomCode.code = BcryptUtil.bcryptHash(randomCodeClearText);
randomCode.persist();
code = randomCode;
return code;
return randomCodeClearText;
}

protected String getLink() {
Expand All @@ -96,12 +97,12 @@ public static final class TemplateCommonData {
public final String appName;
public final URI appUrl;
public final URI websiteUrl;
public final RandomCode code;
public final String code;
public final String link;
public final Instant expiration = Instant.now().plus(RandomCode.LIFETIME);

private TemplateCommonData(
ResourceBundle res, String appName, URI appUrl, URI websiteUrl, RandomCode code, String link) {
ResourceBundle res, String appName, URI appUrl, URI websiteUrl, String code, String link) {
this.res = res;
this.appName = appName;
this.appUrl = appUrl;
Expand Down
16 changes: 10 additions & 6 deletions src/main/java/app/fyreplace/api/endpoints/EmailsEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import app.fyreplace.api.exceptions.ConflictException;
import app.fyreplace.api.exceptions.ForbiddenException;
import io.quarkus.cache.CacheResult;
import io.quarkus.elytron.security.common.BcryptUtil;
import io.quarkus.panache.common.Sort;
import io.quarkus.security.Authenticated;
import jakarta.inject.Inject;
Expand Down Expand Up @@ -140,14 +141,17 @@ public long countEmails() {
@CacheResult(cacheName = "requests", keyGenerator = DuplicateRequestKeyGenerator.class)
public Response activateEmail(@NotNull @Valid final EmailActivation input) {
final var email = Email.<Email>find("email", input.email()).firstResult();
final var randomCode = RandomCode.<RandomCode>find("email = ?1 and code = ?2", email, input.code())
.firstResult();

if (randomCode == null) {
throw new NotFoundException();
try (final var stream = RandomCode.<RandomCode>stream("email", email)) {
var randomCode = stream.filter(rc -> BcryptUtil.matches(input.code(), rc.code))
.findFirst();

if (randomCode.isPresent()) {
randomCode.get().validateEmail();
return Response.ok().build();
}
}

randomCode.validateEmail();
return Response.ok().build();
throw new NotFoundException();
}
}
9 changes: 7 additions & 2 deletions src/main/java/app/fyreplace/api/endpoints/TokensEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,14 @@ public final class TokensEndpoint {
@CacheResult(cacheName = "requests", keyGenerator = DuplicateRequestKeyGenerator.class)
public Response createToken(@NotNull @Valid final TokenCreation input) {
final var email = getEmail(input.identifier());
final var randomCode = RandomCode.<RandomCode>find("email = ?1 and code = ?2", email, input.secret())
.firstResult();
final var password = Password.<Password>find("user", email.user).firstResult();
final RandomCode randomCode;

try (final var stream = RandomCode.<RandomCode>stream("email", email)) {
randomCode = stream.filter(rc -> BcryptUtil.matches(input.secret(), rc.code))
.findFirst()
.orElse(null);
}

if (randomCode != null) {
randomCode.validateEmail();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<mj-column padding="0">
<mj-include path="../_logo.mjml"/>
<mj-text mj-class="text" padding-bottom="20px">{d.res.getString("codeDescription")}</mj-text>
<mj-text mj-class="text" font-size="36px" font-weight="bold" padding-bottom="20px">{d.code.code}</mj-text>
<mj-text mj-class="text" font-size="36px" font-weight="bold" padding-bottom="20px">{d.code}</mj-text>
<mj-text mj-class="text" padding-bottom="20px">{d.res.getString("linkDescription")}</mj-text>
<mj-button mj-class="text" href="{d.link}" rel="noopener noreferrer" padding-bottom="20px">
{d.res.getString("button")}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{d.res.getString("codeDescription")}
{d.code.code}
{d.code}

{d.res.getString("linkDescription")}
{d.link}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
d.appName)}
</mj-text>
<mj-text mj-class="text" padding-bottom="20px">{d.res.getString("codeDescription")}</mj-text>
<mj-text mj-class="text" font-size="36px" font-weight="bold" padding-bottom="20px">{d.code.code}</mj-text>
<mj-text mj-class="text" font-size="36px" font-weight="bold" padding-bottom="20px">{d.code}</mj-text>
<mj-text mj-class="text" padding-bottom="20px">{d.res.getString("linkDescription")}</mj-text>
<mj-button mj-class="text" href="{d.link}" rel="noopener noreferrer" padding-bottom="20px">
{d.res.getString("button")}
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/templates/UserActivationEmail/text.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{d.res.getString("title").replace("$1", d.appName)}

{d.res.getString("codeDescription")}
{d.code.code}
{d.code}

{d.res.getString("linkDescription")}
{d.link}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<mj-column padding="0">
<mj-include path="../_logo.mjml"/>
<mj-text mj-class="text" padding-bottom="20px">{d.res.getString("codeDescription")}</mj-text>
<mj-text mj-class="text" font-size="36px" font-weight="bold" padding-bottom="20px">{d.code.code}</mj-text>
<mj-text mj-class="text" font-size="36px" font-weight="bold" padding-bottom="20px">{d.code}</mj-text>
<mj-text mj-class="text" padding-bottom="20px">{d.res.getString("linkDescription")}</mj-text>
<mj-button mj-class="text" href="{d.link}" rel="noopener noreferrer" padding-bottom="20px">
{d.res.getString("button")}
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/templates/UserConnectionEmail/text.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{d.res.getString("codeDescription")}
{d.code.code}
{d.code}

{d.res.getString("linkDescription")}
{d.link}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import app.fyreplace.api.endpoints.EmailsEndpoint;
import app.fyreplace.api.services.RandomService;
import app.fyreplace.api.testing.UserTestsBase;
import io.quarkus.elytron.security.common.BcryptUtil;
import io.quarkus.test.common.http.TestHTTPEndpoint;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
Expand All @@ -27,14 +28,14 @@ public final class ActivateEmailTests extends UserTestsBase {
RandomService randomService;

private Email newEmail;

private RandomCode randomCode;
private String randomCodeClearText;

@Test
@TestSecurity(user = "user_0")
public void activateEmail() {
given().contentType(ContentType.JSON)
.body(new EmailActivation(newEmail.email, randomCode.code))
.body(new EmailActivation(newEmail.email, randomCodeClearText))
.post("activate")
.then()
.statusCode(200);
Expand All @@ -45,7 +46,7 @@ public void activateEmail() {
@TestSecurity(user = "user_0")
public void activateEmailWithInvalidEmail() {
given().contentType(ContentType.JSON)
.body(new EmailActivation("invalid", randomCode.code))
.body(new EmailActivation("invalid", randomCodeClearText))
.post("activate")
.then()
.statusCode(400);
Expand All @@ -57,7 +58,7 @@ public void activateEmailWithInvalidEmail() {
public void activateEmailWithOtherEmail() {
final var otherUser = requireNonNull(User.findByUsername("user_1"));
given().contentType(ContentType.JSON)
.body(new EmailActivation(otherUser.mainEmail.email, randomCode.code))
.body(new EmailActivation(otherUser.mainEmail.email, randomCodeClearText))
.post("activate")
.then()
.statusCode(404);
Expand Down Expand Up @@ -93,7 +94,8 @@ public void beforeEach() {
newEmail.persist();
randomCode = new RandomCode();
randomCode.email = newEmail;
randomCode.code = randomService.generateCode(RandomCode.LENGTH);
randomCodeClearText = randomService.generateCode(RandomCode.LENGTH);
randomCode.code = BcryptUtil.bcryptHash(randomCodeClearText);
randomCode.persist();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,17 @@ public final class CreateTokenTests extends UserTestsBase {
RandomService randomService;

private RandomCode normalUserRandomCode;
private RandomCode otherNormalUserRandomCode;
private String normalUserRandomCodeClearText;
private String otherNormalUserRandomCodeClearText;
private RandomCode newUserRandomCode;
private String newUserRandomCodeClearText;
private Password password;

@Test
public void createTokenWithUsername() {
final var randomCodeCount = RandomCode.count();
given().contentType(ContentType.JSON)
.body(new TokenCreation(normalUserRandomCode.email.user.username, normalUserRandomCode.code))
.body(new TokenCreation(normalUserRandomCode.email.user.username, normalUserRandomCodeClearText))
.post()
.then()
.statusCode(201)
Expand All @@ -53,7 +55,7 @@ public void createTokenWithNewUsername() {
final var randomCodeCount = RandomCode.count();
assertFalse(newUserRandomCode.email.verified);
given().contentType(ContentType.JSON)
.body(new TokenCreation(newUserRandomCode.email.user.username, newUserRandomCode.code))
.body(new TokenCreation(newUserRandomCode.email.user.username, newUserRandomCodeClearText))
.post()
.then()
.statusCode(201)
Expand All @@ -68,7 +70,7 @@ public void createTokenWithNewUsername() {
public void createTokenWithEmail() {
final var randomCodeCount = RandomCode.count();
given().contentType(ContentType.JSON)
.body(new TokenCreation(normalUserRandomCode.email.email, normalUserRandomCode.code))
.body(new TokenCreation(normalUserRandomCode.email.email, normalUserRandomCodeClearText))
.post()
.then()
.statusCode(201)
Expand All @@ -82,7 +84,7 @@ public void createTokenWithNewEmail() {
final var randomCodeCount = RandomCode.count();
assertFalse(newUserRandomCode.email.verified);
given().contentType(ContentType.JSON)
.body(new TokenCreation(newUserRandomCode.email.email, newUserRandomCode.code))
.body(new TokenCreation(newUserRandomCode.email.email, newUserRandomCodeClearText))
.post()
.then()
.statusCode(201)
Expand Down Expand Up @@ -112,7 +114,7 @@ public void createTokenWithPassword() {
@Test
public void createTokenWithInvalidUsername() {
given().contentType(ContentType.JSON)
.body(new TokenCreation("bad", normalUserRandomCode.code))
.body(new TokenCreation("bad", normalUserRandomCodeClearText))
.post()
.then()
.statusCode(404);
Expand All @@ -129,15 +131,15 @@ public void createTokenWithInvalidSecret() {

@Test
public void createTokenTwice() {
final var input = new TokenCreation(normalUserRandomCode.email.user.username, normalUserRandomCode.code);
final var input = new TokenCreation(normalUserRandomCode.email.user.username, normalUserRandomCodeClearText);
given().contentType(ContentType.JSON).body(input).post().then().statusCode(201);
given().contentType(ContentType.JSON).body(input).post().then().statusCode(400);
}

@Test
public void createTokenWithOtherCode() {
given().contentType(ContentType.JSON)
.body(new TokenCreation(normalUserRandomCode.email.user.username, otherNormalUserRandomCode.code))
.body(new TokenCreation(normalUserRandomCode.email.user.username, otherNormalUserRandomCodeClearText))
.post()
.then()
.statusCode(400);
Expand All @@ -153,20 +155,27 @@ public void createTokenWithEmptyInput() {
@Override
public void beforeEach() {
super.beforeEach();
normalUserRandomCode = makeRandomCode("user_0");
otherNormalUserRandomCode = makeRandomCode("user_1");
newUserRandomCode = makeRandomCode("user_inactive_0");
normalUserRandomCodeClearText = generateCode();
normalUserRandomCode = makeRandomCode("user_0", normalUserRandomCodeClearText);
otherNormalUserRandomCodeClearText = generateCode();
makeRandomCode("user_1", otherNormalUserRandomCodeClearText);
newUserRandomCodeClearText = generateCode();
newUserRandomCode = makeRandomCode("user_inactive_0", newUserRandomCodeClearText);
password = new Password();
password.user = User.findByUsername("user_inactive_1");
password.password = BcryptUtil.bcryptHash("password");
password.persist();
}

private RandomCode makeRandomCode(final String username) {
final var code = new RandomCode();
code.email = requireNonNull(User.findByUsername(username)).mainEmail;
code.code = randomService.generateCode(RandomCode.LENGTH);
code.persist();
return code;
private RandomCode makeRandomCode(final String username, final String code) {
final var randomCode = new RandomCode();
randomCode.email = requireNonNull(User.findByUsername(username)).mainEmail;
randomCode.code = BcryptUtil.bcryptHash(code);
randomCode.persist();
return randomCode;
}

private String generateCode() {
return randomService.generateCode(RandomCode.LENGTH);
}
}

0 comments on commit ea1389f

Please sign in to comment.