accept encrypted username with confirm-username-hash requests
This commit is contained in:
parent
ade2e9c6cf
commit
67343f6bdc
|
@ -18,6 +18,7 @@ import org.signal.libsignal.usernames.Username;
|
||||||
import org.whispersystems.textsecuregcm.entities.AccountIdentifierResponse;
|
import org.whispersystems.textsecuregcm.entities.AccountIdentifierResponse;
|
||||||
import org.whispersystems.textsecuregcm.entities.AccountIdentityResponse;
|
import org.whispersystems.textsecuregcm.entities.AccountIdentityResponse;
|
||||||
import org.whispersystems.textsecuregcm.entities.ConfirmUsernameHashRequest;
|
import org.whispersystems.textsecuregcm.entities.ConfirmUsernameHashRequest;
|
||||||
|
import org.whispersystems.textsecuregcm.entities.EncryptedUsername;
|
||||||
import org.whispersystems.textsecuregcm.entities.ReserveUsernameHashRequest;
|
import org.whispersystems.textsecuregcm.entities.ReserveUsernameHashRequest;
|
||||||
import org.whispersystems.textsecuregcm.entities.ReserveUsernameHashResponse;
|
import org.whispersystems.textsecuregcm.entities.ReserveUsernameHashResponse;
|
||||||
import org.whispersystems.textsecuregcm.entities.UsernameHashResponse;
|
import org.whispersystems.textsecuregcm.entities.UsernameHashResponse;
|
||||||
|
@ -87,9 +88,10 @@ public class AccountTest {
|
||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
|
|
||||||
// confirm a username
|
// confirm a username
|
||||||
final ConfirmUsernameHashRequest confirmUsernameHashRequest = new ConfirmUsernameHashRequest(
|
final ConfirmUsernameHashRequest confirmUsernameHashRequest = new ConfirmUsernameHashRequest(
|
||||||
reservedUsername.getHash(),
|
reservedUsername.getHash(),
|
||||||
reservedUsername.generateProof()
|
reservedUsername.generateProof(),
|
||||||
|
new EncryptedUsername("cluck cluck i'm a parrot".getBytes())
|
||||||
);
|
);
|
||||||
// try unauthorized
|
// try unauthorized
|
||||||
Operations
|
Operations
|
||||||
|
|
|
@ -350,17 +350,15 @@ public class AccountController {
|
||||||
throw new WebApplicationException(Response.status(422).build());
|
throw new WebApplicationException(Response.status(422).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whenever a valid request for a username change arrives,
|
|
||||||
// we're making sure to clear username link. This may happen before and username changes are written to the db
|
|
||||||
// but verifying zk proof means that request itself is valid from the client's perspective
|
|
||||||
clearUsernameLink(auth.getAccount());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final Account account = accounts.confirmReservedUsernameHash(auth.getAccount(), confirmRequest.usernameHash());
|
final Account account = accounts.confirmReservedUsernameHash(
|
||||||
return account
|
auth.getAccount(),
|
||||||
.getUsernameHash()
|
confirmRequest.usernameHash(),
|
||||||
.map(UsernameHashResponse::new)
|
Optional.ofNullable(confirmRequest.encryptedUsername()).map(EncryptedUsername::usernameLinkEncryptedValue).orElse(null));
|
||||||
.orElseThrow(() -> new IllegalStateException("Could not get username after setting"));
|
final UUID linkHandle = account.getUsernameLinkHandle();
|
||||||
|
return new UsernameHashResponse(
|
||||||
|
account.getUsernameHash().orElseThrow(() -> new IllegalStateException("Could not get username after setting")),
|
||||||
|
linkHandle == null ? null : new UsernameLinkHandle(linkHandle));
|
||||||
} catch (final UsernameReservationNotFoundException e) {
|
} catch (final UsernameReservationNotFoundException e) {
|
||||||
throw new WebApplicationException(Status.CONFLICT);
|
throw new WebApplicationException(Status.CONFLICT);
|
||||||
} catch (final UsernameHashNotAvailableException e) {
|
} catch (final UsernameHashNotAvailableException e) {
|
||||||
|
|
|
@ -5,12 +5,18 @@
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.entities;
|
package org.whispersystems.textsecuregcm.entities;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
import org.whispersystems.textsecuregcm.controllers.AccountController;
|
import org.whispersystems.textsecuregcm.controllers.AccountController;
|
||||||
import org.whispersystems.textsecuregcm.util.ByteArrayBase64UrlAdapter;
|
import org.whispersystems.textsecuregcm.util.ByteArrayBase64UrlAdapter;
|
||||||
import org.whispersystems.textsecuregcm.util.ExactlySize;
|
import org.whispersystems.textsecuregcm.util.ExactlySize;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
public record ConfirmUsernameHashRequest(
|
public record ConfirmUsernameHashRequest(
|
||||||
@JsonSerialize(using = ByteArrayBase64UrlAdapter.Serializing.class)
|
@JsonSerialize(using = ByteArrayBase64UrlAdapter.Serializing.class)
|
||||||
@JsonDeserialize(using = ByteArrayBase64UrlAdapter.Deserializing.class)
|
@JsonDeserialize(using = ByteArrayBase64UrlAdapter.Deserializing.class)
|
||||||
|
@ -19,5 +25,10 @@ public record ConfirmUsernameHashRequest(
|
||||||
|
|
||||||
@JsonSerialize(using = ByteArrayBase64UrlAdapter.Serializing.class)
|
@JsonSerialize(using = ByteArrayBase64UrlAdapter.Serializing.class)
|
||||||
@JsonDeserialize(using = ByteArrayBase64UrlAdapter.Deserializing.class)
|
@JsonDeserialize(using = ByteArrayBase64UrlAdapter.Deserializing.class)
|
||||||
byte[] zkProof
|
byte[] zkProof,
|
||||||
|
|
||||||
|
@Schema(description = "The encrypted username to be stored for username links")
|
||||||
|
@Nullable
|
||||||
|
@Valid
|
||||||
|
EncryptedUsername encryptedUsername
|
||||||
) {}
|
) {}
|
||||||
|
|
|
@ -7,9 +7,12 @@ package org.whispersystems.textsecuregcm.entities;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.whispersystems.textsecuregcm.controllers.AccountController;
|
import org.whispersystems.textsecuregcm.controllers.AccountController;
|
||||||
import org.whispersystems.textsecuregcm.util.ByteArrayBase64UrlAdapter;
|
import org.whispersystems.textsecuregcm.util.ByteArrayBase64UrlAdapter;
|
||||||
import org.whispersystems.textsecuregcm.util.ExactlySize;
|
import org.whispersystems.textsecuregcm.util.ExactlySize;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
|
||||||
public record UsernameHashResponse(
|
public record UsernameHashResponse(
|
||||||
|
@ -17,5 +20,11 @@ public record UsernameHashResponse(
|
||||||
@JsonSerialize(using = ByteArrayBase64UrlAdapter.Serializing.class)
|
@JsonSerialize(using = ByteArrayBase64UrlAdapter.Serializing.class)
|
||||||
@JsonDeserialize(using = ByteArrayBase64UrlAdapter.Deserializing.class)
|
@JsonDeserialize(using = ByteArrayBase64UrlAdapter.Deserializing.class)
|
||||||
@ExactlySize(AccountController.USERNAME_HASH_LENGTH)
|
@ExactlySize(AccountController.USERNAME_HASH_LENGTH)
|
||||||
byte[] usernameHash
|
@Schema(description = "The hash of the confirmed username, as supplied in the request")
|
||||||
|
byte[] usernameHash,
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Valid
|
||||||
|
@Schema(description = "A handle that can be included in username links to retrieve the stored encrypted username")
|
||||||
|
UsernameLinkHandle usernameLinkHandle
|
||||||
) {}
|
) {}
|
||||||
|
|
|
@ -30,6 +30,8 @@ import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.textsecuregcm.util.AsyncTimerUtil;
|
import org.whispersystems.textsecuregcm.util.AsyncTimerUtil;
|
||||||
|
@ -386,15 +388,18 @@ public class Accounts extends AbstractDynamoDbStore {
|
||||||
* @param usernameHash believed to be available
|
* @param usernameHash believed to be available
|
||||||
* @throws ContestedOptimisticLockException if the account has been updated or the username has taken by someone else
|
* @throws ContestedOptimisticLockException if the account has been updated or the username has taken by someone else
|
||||||
*/
|
*/
|
||||||
public void confirmUsernameHash(final Account account, final byte[] usernameHash)
|
public void confirmUsernameHash(final Account account, final byte[] usernameHash, @Nullable final byte[] encryptedUsername)
|
||||||
throws ContestedOptimisticLockException {
|
throws ContestedOptimisticLockException {
|
||||||
final long startNanos = System.nanoTime();
|
final long startNanos = System.nanoTime();
|
||||||
|
|
||||||
final Optional<byte[]> maybeOriginalUsernameHash = account.getUsernameHash();
|
final Optional<byte[]> maybeOriginalUsernameHash = account.getUsernameHash();
|
||||||
final Optional<byte[]> maybeOriginalReservationHash = account.getReservedUsernameHash();
|
final Optional<byte[]> maybeOriginalReservationHash = account.getReservedUsernameHash();
|
||||||
|
final Optional<UUID> maybeOriginalUsernameLinkHandle = Optional.ofNullable(account.getUsernameLinkHandle());
|
||||||
|
final Optional<byte[]> maybeOriginalEncryptedUsername = account.getEncryptedUsername();
|
||||||
|
|
||||||
account.setUsernameHash(usernameHash);
|
account.setUsernameHash(usernameHash);
|
||||||
account.setReservedUsernameHash(null);
|
account.setReservedUsernameHash(null);
|
||||||
|
account.setUsernameLinkDetails(encryptedUsername == null ? null : UUID.randomUUID(), encryptedUsername);
|
||||||
|
|
||||||
boolean succeeded = false;
|
boolean succeeded = false;
|
||||||
|
|
||||||
|
@ -420,21 +425,32 @@ public class Accounts extends AbstractDynamoDbStore {
|
||||||
.build())
|
.build())
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
|
final StringBuilder updateExpr = new StringBuilder("SET #data = :data, #username_hash = :username_hash");
|
||||||
|
final Map<String, AttributeValue> expressionAttributeValues = new HashMap<>(Map.of(
|
||||||
|
":data", AttributeValues.fromByteArray(SystemMapper.jsonMapper().writeValueAsBytes(account)),
|
||||||
|
":username_hash", AttributeValues.fromByteArray(usernameHash),
|
||||||
|
":version", AttributeValues.fromInt(account.getVersion()),
|
||||||
|
":version_increment", AttributeValues.fromInt(1)));
|
||||||
|
if (account.getUsernameLinkHandle() != null) {
|
||||||
|
updateExpr.append(", #ul = :ul");
|
||||||
|
expressionAttributeValues.put(":ul", AttributeValues.fromUUID(account.getUsernameLinkHandle()));
|
||||||
|
} else {
|
||||||
|
updateExpr.append(" REMOVE #ul");
|
||||||
|
}
|
||||||
|
updateExpr.append(" ADD #version :version_increment");
|
||||||
|
|
||||||
writeItems.add(
|
writeItems.add(
|
||||||
TransactWriteItem.builder()
|
TransactWriteItem.builder()
|
||||||
.update(Update.builder()
|
.update(Update.builder()
|
||||||
.tableName(accountsTableName)
|
.tableName(accountsTableName)
|
||||||
.key(Map.of(KEY_ACCOUNT_UUID, AttributeValues.fromUUID(account.getUuid())))
|
.key(Map.of(KEY_ACCOUNT_UUID, AttributeValues.fromUUID(account.getUuid())))
|
||||||
.updateExpression("SET #data = :data, #username_hash = :username_hash ADD #version :version_increment")
|
.updateExpression(updateExpr.toString())
|
||||||
.conditionExpression("#version = :version")
|
.conditionExpression("#version = :version")
|
||||||
.expressionAttributeNames(Map.of("#data", ATTR_ACCOUNT_DATA,
|
.expressionAttributeNames(Map.of("#data", ATTR_ACCOUNT_DATA,
|
||||||
"#username_hash", ATTR_USERNAME_HASH,
|
"#username_hash", ATTR_USERNAME_HASH,
|
||||||
|
"#ul", ATTR_USERNAME_LINK_UUID,
|
||||||
"#version", ATTR_VERSION))
|
"#version", ATTR_VERSION))
|
||||||
.expressionAttributeValues(Map.of(
|
.expressionAttributeValues(expressionAttributeValues)
|
||||||
":data", AttributeValues.fromByteArray(SystemMapper.jsonMapper().writeValueAsBytes(account)),
|
|
||||||
":username_hash", AttributeValues.fromByteArray(usernameHash),
|
|
||||||
":version", AttributeValues.fromInt(account.getVersion()),
|
|
||||||
":version_increment", AttributeValues.fromInt(1)))
|
|
||||||
.build())
|
.build())
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
|
@ -460,6 +476,7 @@ public class Accounts extends AbstractDynamoDbStore {
|
||||||
if (!succeeded) {
|
if (!succeeded) {
|
||||||
account.setUsernameHash(maybeOriginalUsernameHash.orElse(null));
|
account.setUsernameHash(maybeOriginalUsernameHash.orElse(null));
|
||||||
account.setReservedUsernameHash(maybeOriginalReservationHash.orElse(null));
|
account.setReservedUsernameHash(maybeOriginalReservationHash.orElse(null));
|
||||||
|
account.setUsernameLinkDetails(maybeOriginalUsernameLinkHandle.orElse(null), maybeOriginalEncryptedUsername.orElse(null));
|
||||||
}
|
}
|
||||||
SET_USERNAME_TIMER.record(System.nanoTime() - startNanos, TimeUnit.NANOSECONDS);
|
SET_USERNAME_TIMER.record(System.nanoTime() - startNanos, TimeUnit.NANOSECONDS);
|
||||||
}
|
}
|
||||||
|
|
|
@ -503,11 +503,12 @@ public class AccountsManager {
|
||||||
*
|
*
|
||||||
* @param account the account to update
|
* @param account the account to update
|
||||||
* @param reservedUsernameHash the previously reserved username hash
|
* @param reservedUsernameHash the previously reserved username hash
|
||||||
|
* @param encryptedUsername the encrypted form of the previously reserved username for the username link
|
||||||
* @return the updated account with the username hash field set
|
* @return the updated account with the username hash field set
|
||||||
* @throws UsernameHashNotAvailableException if the reserved username hash has been taken (because the reservation expired)
|
* @throws UsernameHashNotAvailableException if the reserved username hash has been taken (because the reservation expired)
|
||||||
* @throws UsernameReservationNotFoundException if `reservedUsernameHash` was not reserved in the account
|
* @throws UsernameReservationNotFoundException if `reservedUsernameHash` was not reserved in the account
|
||||||
*/
|
*/
|
||||||
public Account confirmReservedUsernameHash(final Account account, final byte[] reservedUsernameHash) throws UsernameHashNotAvailableException, UsernameReservationNotFoundException {
|
public Account confirmReservedUsernameHash(final Account account, final byte[] reservedUsernameHash, @Nullable final byte[] encryptedUsername) throws UsernameHashNotAvailableException, UsernameReservationNotFoundException {
|
||||||
if (!experimentEnrollmentManager.isEnrolled(account.getUuid(), USERNAME_EXPERIMENT_NAME)) {
|
if (!experimentEnrollmentManager.isEnrolled(account.getUuid(), USERNAME_EXPERIMENT_NAME)) {
|
||||||
throw new UsernameHashNotAvailableException();
|
throw new UsernameHashNotAvailableException();
|
||||||
}
|
}
|
||||||
|
@ -532,7 +533,7 @@ public class AccountsManager {
|
||||||
if (!accounts.usernameHashAvailable(Optional.of(account.getUuid()), reservedUsernameHash)) {
|
if (!accounts.usernameHashAvailable(Optional.of(account.getUuid()), reservedUsernameHash)) {
|
||||||
throw new UsernameHashNotAvailableException();
|
throw new UsernameHashNotAvailableException();
|
||||||
}
|
}
|
||||||
accounts.confirmUsernameHash(a, reservedUsernameHash);
|
accounts.confirmUsernameHash(a, reservedUsernameHash, encryptedUsername);
|
||||||
},
|
},
|
||||||
() -> accounts.getByAccountIdentifier(account.getUuid()).orElseThrow(),
|
() -> accounts.getByAccountIdentifier(account.getUuid()).orElseThrow(),
|
||||||
AccountChangeValidator.USERNAME_CHANGE_VALIDATOR);
|
AccountChangeValidator.USERNAME_CHANGE_VALIDATOR);
|
||||||
|
@ -731,6 +732,7 @@ public class AccountsManager {
|
||||||
try {
|
try {
|
||||||
final Account clone = mapper.readValue(mapper.writeValueAsBytes(account), Account.class);
|
final Account clone = mapper.readValue(mapper.writeValueAsBytes(account), Account.class);
|
||||||
clone.setUuid(account.getUuid());
|
clone.setUuid(account.getUuid());
|
||||||
|
clone.setUsernameLinkHandle(account.getUsernameLinkHandle());
|
||||||
|
|
||||||
return clone;
|
return clone;
|
||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
|
|
|
@ -82,6 +82,12 @@ public class AssignUsernameCommand extends EnvironmentCommand<WhisperServerConfi
|
||||||
.required(true)
|
.required(true)
|
||||||
.help("The username hash to assign");
|
.help("The username hash to assign");
|
||||||
|
|
||||||
|
subparser.addArgument("-e", "--encryptedUsername")
|
||||||
|
.dest("encryptedUsername")
|
||||||
|
.type(String.class)
|
||||||
|
.required(false)
|
||||||
|
.help("The encrypted username for the username link");
|
||||||
|
|
||||||
subparser.addArgument("-a", "--aci")
|
subparser.addArgument("-a", "--aci")
|
||||||
.dest("aci")
|
.dest("aci")
|
||||||
.type(String.class)
|
.type(String.class)
|
||||||
|
@ -210,14 +216,19 @@ public class AssignUsernameCommand extends EnvironmentCommand<WhisperServerConfi
|
||||||
experimentEnrollmentManager, registrationRecoveryPasswordsManager, Clock.systemUTC());
|
experimentEnrollmentManager, registrationRecoveryPasswordsManager, Clock.systemUTC());
|
||||||
|
|
||||||
final String usernameHash = namespace.getString("usernameHash");
|
final String usernameHash = namespace.getString("usernameHash");
|
||||||
|
final String encryptedUsername = namespace.getString("encryptedUsername");
|
||||||
final UUID accountIdentifier = UUID.fromString(namespace.getString("aci"));
|
final UUID accountIdentifier = UUID.fromString(namespace.getString("aci"));
|
||||||
|
|
||||||
accountsManager.getByAccountIdentifier(accountIdentifier).ifPresentOrElse(account -> {
|
accountsManager.getByAccountIdentifier(accountIdentifier).ifPresentOrElse(account -> {
|
||||||
try {
|
try {
|
||||||
final AccountsManager.UsernameReservation reservation = accountsManager.reserveUsernameHash(account,
|
final AccountsManager.UsernameReservation reservation = accountsManager.reserveUsernameHash(account,
|
||||||
List.of(Base64.getUrlDecoder().decode(usernameHash)));
|
List.of(Base64.getUrlDecoder().decode(usernameHash)));
|
||||||
final Account result = accountsManager.confirmReservedUsernameHash(account, Base64.getUrlDecoder().decode(usernameHash));
|
final Account result = accountsManager.confirmReservedUsernameHash(
|
||||||
System.out.println("New username hash: " + usernameHash);
|
account,
|
||||||
|
reservation.reservedUsernameHash(),
|
||||||
|
encryptedUsername == null ? null : Base64.getUrlDecoder().decode(encryptedUsername));
|
||||||
|
System.out.println("New username hash: " + Base64.getUrlEncoder().encodeToString(result.getUsernameHash().orElseThrow()));
|
||||||
|
System.out.println("New username link handle: " + result.getUsernameLinkHandle().toString());
|
||||||
} catch (final UsernameHashNotAvailableException e) {
|
} catch (final UsernameHashNotAvailableException e) {
|
||||||
throw new IllegalArgumentException("Username hash already taken");
|
throw new IllegalArgumentException("Username hash already taken");
|
||||||
} catch (final UsernameReservationNotFoundException e) {
|
} catch (final UsernameReservationNotFoundException e) {
|
||||||
|
|
|
@ -8,6 +8,7 @@ package org.whispersystems.textsecuregcm.controllers;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyList;
|
import static org.mockito.ArgumentMatchers.anyList;
|
||||||
import static org.mockito.Mockito.anyLong;
|
import static org.mockito.Mockito.anyLong;
|
||||||
|
@ -102,11 +103,15 @@ class AccountControllerTest {
|
||||||
private static final String SENDER_TRANSFER = "+14151111112";
|
private static final String SENDER_TRANSFER = "+14151111112";
|
||||||
private static final String BASE_64_URL_USERNAME_HASH_1 = "9p6Tip7BFefFOJzv4kv4GyXEYsBVfk_WbjNejdlOvQE";
|
private static final String BASE_64_URL_USERNAME_HASH_1 = "9p6Tip7BFefFOJzv4kv4GyXEYsBVfk_WbjNejdlOvQE";
|
||||||
private static final String BASE_64_URL_USERNAME_HASH_2 = "NLUom-CHwtemcdvOTTXdmXmzRIV7F05leS8lwkVK_vc";
|
private static final String BASE_64_URL_USERNAME_HASH_2 = "NLUom-CHwtemcdvOTTXdmXmzRIV7F05leS8lwkVK_vc";
|
||||||
|
private static final String BASE_64_URL_ENCRYPTED_USERNAME_1 = "md1votbj9r794DsqTNrBqA";
|
||||||
|
private static final String BASE_64_URL_ENCRYPTED_USERNAME_2 = "9hrqVLy59bzgPse-S9NUsA";
|
||||||
|
|
||||||
private static final String INVALID_BASE_64_URL_USERNAME_HASH = "fA+VkNbvB6dVfx/6NpaRSK6mvhhAUBgDNWFaD7+7gvs=";
|
private static final String INVALID_BASE_64_URL_USERNAME_HASH = "fA+VkNbvB6dVfx/6NpaRSK6mvhhAUBgDNWFaD7+7gvs=";
|
||||||
private static final String TOO_SHORT_BASE_64_URL_USERNAME_HASH = "P2oMuxx0xgGxSpTO0ACq3IztEOBDaV9t9YFu4bAGpQ";
|
private static final String TOO_SHORT_BASE_64_URL_USERNAME_HASH = "P2oMuxx0xgGxSpTO0ACq3IztEOBDaV9t9YFu4bAGpQ";
|
||||||
private static final byte[] USERNAME_HASH_1 = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH_1);
|
private static final byte[] USERNAME_HASH_1 = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH_1);
|
||||||
private static final byte[] USERNAME_HASH_2 = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH_2);
|
private static final byte[] USERNAME_HASH_2 = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH_2);
|
||||||
|
private static final byte[] ENCRYPTED_USERNAME_1 = Base64.getUrlDecoder().decode(BASE_64_URL_ENCRYPTED_USERNAME_1);
|
||||||
|
private static final byte[] ENCRYPTED_USERNAME_2 = Base64.getUrlDecoder().decode(BASE_64_URL_ENCRYPTED_USERNAME_2);
|
||||||
private static final byte[] INVALID_USERNAME_HASH = Base64.getDecoder().decode(INVALID_BASE_64_URL_USERNAME_HASH);
|
private static final byte[] INVALID_USERNAME_HASH = Base64.getDecoder().decode(INVALID_BASE_64_URL_USERNAME_HASH);
|
||||||
private static final byte[] TOO_SHORT_USERNAME_HASH = Base64.getUrlDecoder().decode(TOO_SHORT_BASE_64_URL_USERNAME_HASH);
|
private static final byte[] TOO_SHORT_USERNAME_HASH = Base64.getUrlDecoder().decode(TOO_SHORT_BASE_64_URL_USERNAME_HASH);
|
||||||
private static final String BASE_64_URL_ZK_PROOF = "2kambOgmdeeIO0faCMgR6HR4G2BQ5bnhXdIe9ZuZY0NmQXSra5BzDBQ7jzy1cvoEqUHYLpBYMrXudkYPJaWoQg";
|
private static final String BASE_64_URL_ZK_PROOF = "2kambOgmdeeIO0faCMgR6HR4G2BQ5bnhXdIe9ZuZY0NmQXSra5BzDBQ7jzy1cvoEqUHYLpBYMrXudkYPJaWoQg";
|
||||||
|
@ -625,30 +630,57 @@ class AccountControllerTest {
|
||||||
void testConfirmUsernameHash()
|
void testConfirmUsernameHash()
|
||||||
throws UsernameHashNotAvailableException, UsernameReservationNotFoundException, BaseUsernameException {
|
throws UsernameHashNotAvailableException, UsernameReservationNotFoundException, BaseUsernameException {
|
||||||
Account account = mock(Account.class);
|
Account account = mock(Account.class);
|
||||||
|
final UUID uuid = UUID.randomUUID();
|
||||||
when(account.getUsernameHash()).thenReturn(Optional.of(USERNAME_HASH_1));
|
when(account.getUsernameHash()).thenReturn(Optional.of(USERNAME_HASH_1));
|
||||||
when(accountsManager.confirmReservedUsernameHash(any(), eq(USERNAME_HASH_1))).thenReturn(account);
|
when(account.getUsernameLinkHandle()).thenReturn(uuid);
|
||||||
|
when(accountsManager.confirmReservedUsernameHash(any(), eq(USERNAME_HASH_1), eq(ENCRYPTED_USERNAME_1))).thenReturn(account);
|
||||||
Response response =
|
Response response =
|
||||||
resources.getJerseyTest()
|
resources.getJerseyTest()
|
||||||
.target("/v1/accounts/username_hash/confirm")
|
.target("/v1/accounts/username_hash/confirm")
|
||||||
.request()
|
.request()
|
||||||
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||||
.put(Entity.json(new ConfirmUsernameHashRequest(USERNAME_HASH_1, ZK_PROOF)));
|
.put(Entity.json(new ConfirmUsernameHashRequest(USERNAME_HASH_1, ZK_PROOF, new EncryptedUsername(ENCRYPTED_USERNAME_1))));
|
||||||
assertThat(response.getStatus()).isEqualTo(200);
|
assertThat(response.getStatus()).isEqualTo(200);
|
||||||
assertArrayEquals(response.readEntity(UsernameHashResponse.class).usernameHash(), USERNAME_HASH_1);
|
|
||||||
|
final UsernameHashResponse respEntity = response.readEntity(UsernameHashResponse.class);
|
||||||
|
assertArrayEquals(respEntity.usernameHash(), USERNAME_HASH_1);
|
||||||
|
assertEquals(respEntity.usernameLinkHandle().usernameLinkHandle(), uuid);
|
||||||
|
verify(usernameZkProofVerifier).verifyProof(ZK_PROOF, USERNAME_HASH_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConfirmUsernameHashOld()
|
||||||
|
throws UsernameHashNotAvailableException, UsernameReservationNotFoundException, BaseUsernameException {
|
||||||
|
Account account = mock(Account.class);
|
||||||
|
final UUID uuid = UUID.randomUUID();
|
||||||
|
when(account.getUsernameHash()).thenReturn(Optional.of(USERNAME_HASH_1));
|
||||||
|
when(account.getUsernameLinkHandle()).thenReturn(null);
|
||||||
|
when(accountsManager.confirmReservedUsernameHash(any(), eq(USERNAME_HASH_1), eq(null))).thenReturn(account);
|
||||||
|
Response response =
|
||||||
|
resources.getJerseyTest()
|
||||||
|
.target("/v1/accounts/username_hash/confirm")
|
||||||
|
.request()
|
||||||
|
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||||
|
.put(Entity.json(new ConfirmUsernameHashRequest(USERNAME_HASH_1, ZK_PROOF, null)));
|
||||||
|
assertThat(response.getStatus()).isEqualTo(200);
|
||||||
|
|
||||||
|
final UsernameHashResponse respEntity = response.readEntity(UsernameHashResponse.class);
|
||||||
|
assertArrayEquals(respEntity.usernameHash(), USERNAME_HASH_1);
|
||||||
|
assertNull(respEntity.usernameLinkHandle());
|
||||||
verify(usernameZkProofVerifier).verifyProof(ZK_PROOF, USERNAME_HASH_1);
|
verify(usernameZkProofVerifier).verifyProof(ZK_PROOF, USERNAME_HASH_1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testConfirmUnreservedUsernameHash()
|
void testConfirmUnreservedUsernameHash()
|
||||||
throws UsernameHashNotAvailableException, UsernameReservationNotFoundException, BaseUsernameException {
|
throws UsernameHashNotAvailableException, UsernameReservationNotFoundException, BaseUsernameException {
|
||||||
when(accountsManager.confirmReservedUsernameHash(any(), eq(USERNAME_HASH_1)))
|
when(accountsManager.confirmReservedUsernameHash(any(), eq(USERNAME_HASH_1), any()))
|
||||||
.thenThrow(new UsernameReservationNotFoundException());
|
.thenThrow(new UsernameReservationNotFoundException());
|
||||||
Response response =
|
Response response =
|
||||||
resources.getJerseyTest()
|
resources.getJerseyTest()
|
||||||
.target("/v1/accounts/username_hash/confirm")
|
.target("/v1/accounts/username_hash/confirm")
|
||||||
.request()
|
.request()
|
||||||
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||||
.put(Entity.json(new ConfirmUsernameHashRequest(USERNAME_HASH_1, ZK_PROOF)));
|
.put(Entity.json(new ConfirmUsernameHashRequest(USERNAME_HASH_1, ZK_PROOF, new EncryptedUsername(ENCRYPTED_USERNAME_1))));
|
||||||
assertThat(response.getStatus()).isEqualTo(409);
|
assertThat(response.getStatus()).isEqualTo(409);
|
||||||
verify(usernameZkProofVerifier).verifyProof(ZK_PROOF, USERNAME_HASH_1);
|
verify(usernameZkProofVerifier).verifyProof(ZK_PROOF, USERNAME_HASH_1);
|
||||||
}
|
}
|
||||||
|
@ -656,14 +688,14 @@ class AccountControllerTest {
|
||||||
@Test
|
@Test
|
||||||
void testConfirmLapsedUsernameHash()
|
void testConfirmLapsedUsernameHash()
|
||||||
throws UsernameHashNotAvailableException, UsernameReservationNotFoundException, BaseUsernameException {
|
throws UsernameHashNotAvailableException, UsernameReservationNotFoundException, BaseUsernameException {
|
||||||
when(accountsManager.confirmReservedUsernameHash(any(), eq(USERNAME_HASH_1)))
|
when(accountsManager.confirmReservedUsernameHash(any(), eq(USERNAME_HASH_1), any()))
|
||||||
.thenThrow(new UsernameHashNotAvailableException());
|
.thenThrow(new UsernameHashNotAvailableException());
|
||||||
Response response =
|
Response response =
|
||||||
resources.getJerseyTest()
|
resources.getJerseyTest()
|
||||||
.target("/v1/accounts/username_hash/confirm")
|
.target("/v1/accounts/username_hash/confirm")
|
||||||
.request()
|
.request()
|
||||||
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||||
.put(Entity.json(new ConfirmUsernameHashRequest(USERNAME_HASH_1, ZK_PROOF)));
|
.put(Entity.json(new ConfirmUsernameHashRequest(USERNAME_HASH_1, ZK_PROOF, new EncryptedUsername(ENCRYPTED_USERNAME_1))));
|
||||||
assertThat(response.getStatus()).isEqualTo(410);
|
assertThat(response.getStatus()).isEqualTo(410);
|
||||||
verify(usernameZkProofVerifier).verifyProof(ZK_PROOF, USERNAME_HASH_1);
|
verify(usernameZkProofVerifier).verifyProof(ZK_PROOF, USERNAME_HASH_1);
|
||||||
}
|
}
|
||||||
|
@ -695,7 +727,7 @@ class AccountControllerTest {
|
||||||
.target("/v1/accounts/username_hash/confirm")
|
.target("/v1/accounts/username_hash/confirm")
|
||||||
.request()
|
.request()
|
||||||
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||||
.put(Entity.json(new ConfirmUsernameHashRequest(usernameHash, ZK_PROOF)));
|
.put(Entity.json(new ConfirmUsernameHashRequest(usernameHash, ZK_PROOF, new EncryptedUsername(ENCRYPTED_USERNAME_1))));
|
||||||
assertThat(response.getStatus()).isEqualTo(422);
|
assertThat(response.getStatus()).isEqualTo(422);
|
||||||
verifyNoInteractions(usernameZkProofVerifier);
|
verifyNoInteractions(usernameZkProofVerifier);
|
||||||
}
|
}
|
||||||
|
@ -708,7 +740,7 @@ class AccountControllerTest {
|
||||||
.target("/v1/accounts/username_hash/confirm")
|
.target("/v1/accounts/username_hash/confirm")
|
||||||
.request()
|
.request()
|
||||||
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||||
.put(Entity.json(new ConfirmUsernameHashRequest(USERNAME_HASH_1, ZK_PROOF)));
|
.put(Entity.json(new ConfirmUsernameHashRequest(USERNAME_HASH_1, ZK_PROOF, new EncryptedUsername(ENCRYPTED_USERNAME_1))));
|
||||||
assertThat(response.getStatus()).isEqualTo(422);
|
assertThat(response.getStatus()).isEqualTo(422);
|
||||||
verify(usernameZkProofVerifier).verifyProof(ZK_PROOF, USERNAME_HASH_1);
|
verify(usernameZkProofVerifier).verifyProof(ZK_PROOF, USERNAME_HASH_1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,8 +76,13 @@ import org.whispersystems.textsecuregcm.tests.util.RedisClusterHelper;
|
||||||
class AccountsManagerTest {
|
class AccountsManagerTest {
|
||||||
private static final String BASE_64_URL_USERNAME_HASH_1 = "9p6Tip7BFefFOJzv4kv4GyXEYsBVfk_WbjNejdlOvQE";
|
private static final String BASE_64_URL_USERNAME_HASH_1 = "9p6Tip7BFefFOJzv4kv4GyXEYsBVfk_WbjNejdlOvQE";
|
||||||
private static final String BASE_64_URL_USERNAME_HASH_2 = "NLUom-CHwtemcdvOTTXdmXmzRIV7F05leS8lwkVK_vc";
|
private static final String BASE_64_URL_USERNAME_HASH_2 = "NLUom-CHwtemcdvOTTXdmXmzRIV7F05leS8lwkVK_vc";
|
||||||
|
private static final String BASE_64_URL_ENCRYPTED_USERNAME_1 = "md1votbj9r794DsqTNrBqA";
|
||||||
|
private static final String BASE_64_URL_ENCRYPTED_USERNAME_2 = "9hrqVLy59bzgPse-S9NUsA";
|
||||||
|
|
||||||
private static final byte[] USERNAME_HASH_1 = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH_1);
|
private static final byte[] USERNAME_HASH_1 = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH_1);
|
||||||
private static final byte[] USERNAME_HASH_2 = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH_2);
|
private static final byte[] USERNAME_HASH_2 = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH_2);
|
||||||
|
private static final byte[] ENCRYPTED_USERNAME_1 = Base64.getUrlDecoder().decode(BASE_64_URL_ENCRYPTED_USERNAME_1);
|
||||||
|
private static final byte[] ENCRYPTED_USERNAME_2 = Base64.getUrlDecoder().decode(BASE_64_URL_ENCRYPTED_USERNAME_2);
|
||||||
|
|
||||||
private Accounts accounts;
|
private Accounts accounts;
|
||||||
private DeletedAccounts deletedAccounts;
|
private DeletedAccounts deletedAccounts;
|
||||||
|
@ -1217,8 +1222,8 @@ class AccountsManagerTest {
|
||||||
final Account account = AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(), new ArrayList<>(), new byte[16]);
|
final Account account = AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(), new ArrayList<>(), new byte[16]);
|
||||||
setReservationHash(account, USERNAME_HASH_1);
|
setReservationHash(account, USERNAME_HASH_1);
|
||||||
when(accounts.usernameHashAvailable(eq(Optional.of(account.getUuid())), eq(USERNAME_HASH_1))).thenReturn(true);
|
when(accounts.usernameHashAvailable(eq(Optional.of(account.getUuid())), eq(USERNAME_HASH_1))).thenReturn(true);
|
||||||
accountsManager.confirmReservedUsernameHash(account, USERNAME_HASH_1);
|
accountsManager.confirmReservedUsernameHash(account, USERNAME_HASH_1, ENCRYPTED_USERNAME_1);
|
||||||
verify(accounts).confirmUsernameHash(eq(account), eq(USERNAME_HASH_1));
|
verify(accounts).confirmUsernameHash(eq(account), eq(USERNAME_HASH_1), eq(ENCRYPTED_USERNAME_1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1227,7 +1232,7 @@ class AccountsManagerTest {
|
||||||
setReservationHash(account, USERNAME_HASH_1);
|
setReservationHash(account, USERNAME_HASH_1);
|
||||||
when(accounts.usernameHashAvailable(eq(Optional.of(account.getUuid())), eq(USERNAME_HASH_1))).thenReturn(true);
|
when(accounts.usernameHashAvailable(eq(Optional.of(account.getUuid())), eq(USERNAME_HASH_1))).thenReturn(true);
|
||||||
assertThrows(UsernameReservationNotFoundException.class,
|
assertThrows(UsernameReservationNotFoundException.class,
|
||||||
() -> accountsManager.confirmReservedUsernameHash(account, USERNAME_HASH_2));
|
() -> accountsManager.confirmReservedUsernameHash(account, USERNAME_HASH_2, ENCRYPTED_USERNAME_2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1237,8 +1242,8 @@ class AccountsManagerTest {
|
||||||
setReservationHash(account, USERNAME_HASH_1);
|
setReservationHash(account, USERNAME_HASH_1);
|
||||||
when(accounts.usernameHashAvailable(eq(Optional.of(account.getUuid())), eq(USERNAME_HASH_1))).thenReturn(false);
|
when(accounts.usernameHashAvailable(eq(Optional.of(account.getUuid())), eq(USERNAME_HASH_1))).thenReturn(false);
|
||||||
assertThrows(UsernameHashNotAvailableException.class, () -> accountsManager.confirmReservedUsernameHash(account,
|
assertThrows(UsernameHashNotAvailableException.class, () -> accountsManager.confirmReservedUsernameHash(account,
|
||||||
USERNAME_HASH_1));
|
USERNAME_HASH_1, ENCRYPTED_USERNAME_1));
|
||||||
verify(accounts, never()).confirmUsernameHash(any(), any());
|
verify(accounts, never()).confirmUsernameHash(any(), any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1247,7 +1252,7 @@ class AccountsManagerTest {
|
||||||
account.setUsernameHash(USERNAME_HASH_1);
|
account.setUsernameHash(USERNAME_HASH_1);
|
||||||
|
|
||||||
// reserved username already set, should be treated as a replay
|
// reserved username already set, should be treated as a replay
|
||||||
accountsManager.confirmReservedUsernameHash(account, USERNAME_HASH_1);
|
accountsManager.confirmReservedUsernameHash(account, USERNAME_HASH_1, ENCRYPTED_USERNAME_1);
|
||||||
verifyNoInteractions(accounts);
|
verifyNoInteractions(accounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1256,8 +1261,8 @@ class AccountsManagerTest {
|
||||||
final Account account = AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(),
|
final Account account = AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(),
|
||||||
new ArrayList<>(), new byte[16]);
|
new ArrayList<>(), new byte[16]);
|
||||||
assertThrows(UsernameReservationNotFoundException.class,
|
assertThrows(UsernameReservationNotFoundException.class,
|
||||||
() -> accountsManager.confirmReservedUsernameHash(account, USERNAME_HASH_1));
|
() -> accountsManager.confirmReservedUsernameHash(account, USERNAME_HASH_1, ENCRYPTED_USERNAME_1));
|
||||||
verify(accounts, never()).confirmUsernameHash(any(), any());
|
verify(accounts, never()).confirmUsernameHash(any(), any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -52,9 +52,13 @@ class AccountsManagerUsernameIntegrationTest {
|
||||||
|
|
||||||
private static final String BASE_64_URL_USERNAME_HASH_1 = "9p6Tip7BFefFOJzv4kv4GyXEYsBVfk_WbjNejdlOvQE";
|
private static final String BASE_64_URL_USERNAME_HASH_1 = "9p6Tip7BFefFOJzv4kv4GyXEYsBVfk_WbjNejdlOvQE";
|
||||||
private static final String BASE_64_URL_USERNAME_HASH_2 = "NLUom-CHwtemcdvOTTXdmXmzRIV7F05leS8lwkVK_vc";
|
private static final String BASE_64_URL_USERNAME_HASH_2 = "NLUom-CHwtemcdvOTTXdmXmzRIV7F05leS8lwkVK_vc";
|
||||||
|
private static final String BASE_64_URL_ENCRYPTED_USERNAME_1 = "md1votbj9r794DsqTNrBqA";
|
||||||
|
private static final String BASE_64_URL_ENCRYPTED_USERNAME_2 = "9hrqVLy59bzgPse-S9NUsA";
|
||||||
private static final int SCAN_PAGE_SIZE = 1;
|
private static final int SCAN_PAGE_SIZE = 1;
|
||||||
private static final byte[] USERNAME_HASH_1 = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH_1);
|
private static final byte[] USERNAME_HASH_1 = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH_1);
|
||||||
private static final byte[] USERNAME_HASH_2 = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH_2);
|
private static final byte[] USERNAME_HASH_2 = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH_2);
|
||||||
|
private static final byte[] ENCRYPTED_USERNAME_1 = Base64.getUrlDecoder().decode(BASE_64_URL_ENCRYPTED_USERNAME_1);
|
||||||
|
private static final byte[] ENCRYPTED_USERNAME_2 = Base64.getUrlDecoder().decode(BASE_64_URL_ENCRYPTED_USERNAME_2);
|
||||||
|
|
||||||
@RegisterExtension
|
@RegisterExtension
|
||||||
static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(
|
static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(
|
||||||
|
@ -203,10 +207,14 @@ class AccountsManagerUsernameIntegrationTest {
|
||||||
// confirm
|
// confirm
|
||||||
account = accountsManager.confirmReservedUsernameHash(
|
account = accountsManager.confirmReservedUsernameHash(
|
||||||
reservation.account(),
|
reservation.account(),
|
||||||
reservation.reservedUsernameHash());
|
reservation.reservedUsernameHash(),
|
||||||
|
ENCRYPTED_USERNAME_1);
|
||||||
assertArrayEquals(account.getUsernameHash().orElseThrow(), USERNAME_HASH_1);
|
assertArrayEquals(account.getUsernameHash().orElseThrow(), USERNAME_HASH_1);
|
||||||
assertThat(accountsManager.getByUsernameHash(USERNAME_HASH_1).orElseThrow().getUuid()).isEqualTo(
|
assertThat(accountsManager.getByUsernameHash(USERNAME_HASH_1).orElseThrow().getUuid()).isEqualTo(
|
||||||
account.getUuid());
|
account.getUuid());
|
||||||
|
assertThat(account.getUsernameLinkHandle()).isNotNull();
|
||||||
|
assertThat(accountsManager.getByUsernameLinkHandle(account.getUsernameLinkHandle()).orElseThrow().getUuid())
|
||||||
|
.isEqualTo(account.getUuid());
|
||||||
|
|
||||||
// clear
|
// clear
|
||||||
account = accountsManager.clearUsernameHash(account);
|
account = accountsManager.clearUsernameHash(account);
|
||||||
|
@ -241,8 +249,8 @@ class AccountsManagerUsernameIntegrationTest {
|
||||||
assertArrayEquals(reservation2.reservedUsernameHash(), USERNAME_HASH_1);
|
assertArrayEquals(reservation2.reservedUsernameHash(), USERNAME_HASH_1);
|
||||||
|
|
||||||
assertThrows(UsernameHashNotAvailableException.class,
|
assertThrows(UsernameHashNotAvailableException.class,
|
||||||
() -> accountsManager.confirmReservedUsernameHash(reservation1.account(), USERNAME_HASH_1));
|
() -> accountsManager.confirmReservedUsernameHash(reservation1.account(), USERNAME_HASH_1, ENCRYPTED_USERNAME_1));
|
||||||
account2 = accountsManager.confirmReservedUsernameHash(reservation2.account(), USERNAME_HASH_1);
|
account2 = accountsManager.confirmReservedUsernameHash(reservation2.account(), USERNAME_HASH_1, ENCRYPTED_USERNAME_1);
|
||||||
assertEquals(accountsManager.getByUsernameHash(USERNAME_HASH_1).orElseThrow().getUuid(), account2.getUuid());
|
assertEquals(accountsManager.getByUsernameHash(USERNAME_HASH_1).orElseThrow().getUuid(), account2.getUuid());
|
||||||
assertArrayEquals(account2.getUsernameHash().orElseThrow(), USERNAME_HASH_1);
|
assertArrayEquals(account2.getUsernameHash().orElseThrow(), USERNAME_HASH_1);
|
||||||
}
|
}
|
||||||
|
@ -256,7 +264,7 @@ class AccountsManagerUsernameIntegrationTest {
|
||||||
// Set username hash
|
// Set username hash
|
||||||
final AccountsManager.UsernameReservation reservation1 = accountsManager.reserveUsernameHash(account, List.of(
|
final AccountsManager.UsernameReservation reservation1 = accountsManager.reserveUsernameHash(account, List.of(
|
||||||
USERNAME_HASH_1));
|
USERNAME_HASH_1));
|
||||||
account = accountsManager.confirmReservedUsernameHash(reservation1.account(), USERNAME_HASH_1);
|
account = accountsManager.confirmReservedUsernameHash(reservation1.account(), USERNAME_HASH_1, ENCRYPTED_USERNAME_1);
|
||||||
|
|
||||||
// Reserve another hash on the same account
|
// Reserve another hash on the same account
|
||||||
final AccountsManager.UsernameReservation reservation2 = accountsManager.reserveUsernameHash(account, List.of(
|
final AccountsManager.UsernameReservation reservation2 = accountsManager.reserveUsernameHash(account, List.of(
|
||||||
|
@ -265,6 +273,7 @@ class AccountsManagerUsernameIntegrationTest {
|
||||||
|
|
||||||
assertArrayEquals(account.getReservedUsernameHash().orElseThrow(), USERNAME_HASH_2);
|
assertArrayEquals(account.getReservedUsernameHash().orElseThrow(), USERNAME_HASH_2);
|
||||||
assertArrayEquals(account.getUsernameHash().orElseThrow(), USERNAME_HASH_1);
|
assertArrayEquals(account.getUsernameHash().orElseThrow(), USERNAME_HASH_1);
|
||||||
|
assertArrayEquals(account.getEncryptedUsername().orElseThrow(), ENCRYPTED_USERNAME_1);
|
||||||
|
|
||||||
// Clear the set username hash but not the reserved one
|
// Clear the set username hash but not the reserved one
|
||||||
account = accountsManager.clearUsernameHash(account);
|
account = accountsManager.clearUsernameHash(account);
|
||||||
|
@ -272,7 +281,8 @@ class AccountsManagerUsernameIntegrationTest {
|
||||||
assertThat(account.getUsernameHash()).isEmpty();
|
assertThat(account.getUsernameHash()).isEmpty();
|
||||||
|
|
||||||
// Confirm second reservation
|
// Confirm second reservation
|
||||||
account = accountsManager.confirmReservedUsernameHash(account, reservation2.reservedUsernameHash());
|
account = accountsManager.confirmReservedUsernameHash(account, reservation2.reservedUsernameHash(), ENCRYPTED_USERNAME_2);
|
||||||
assertArrayEquals(account.getUsernameHash().orElseThrow(), USERNAME_HASH_2);
|
assertArrayEquals(account.getUsernameHash().orElseThrow(), USERNAME_HASH_2);
|
||||||
|
assertArrayEquals(account.getEncryptedUsername().orElseThrow(), ENCRYPTED_USERNAME_2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,8 +79,12 @@ class AccountsTest {
|
||||||
|
|
||||||
private static final String BASE_64_URL_USERNAME_HASH_1 = "9p6Tip7BFefFOJzv4kv4GyXEYsBVfk_WbjNejdlOvQE";
|
private static final String BASE_64_URL_USERNAME_HASH_1 = "9p6Tip7BFefFOJzv4kv4GyXEYsBVfk_WbjNejdlOvQE";
|
||||||
private static final String BASE_64_URL_USERNAME_HASH_2 = "NLUom-CHwtemcdvOTTXdmXmzRIV7F05leS8lwkVK_vc";
|
private static final String BASE_64_URL_USERNAME_HASH_2 = "NLUom-CHwtemcdvOTTXdmXmzRIV7F05leS8lwkVK_vc";
|
||||||
|
private static final String BASE_64_URL_ENCRYPTED_USERNAME_1 = "md1votbj9r794DsqTNrBqA";
|
||||||
|
private static final String BASE_64_URL_ENCRYPTED_USERNAME_2 = "9hrqVLy59bzgPse-S9NUsA";
|
||||||
private static final byte[] USERNAME_HASH_1 = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH_1);
|
private static final byte[] USERNAME_HASH_1 = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH_1);
|
||||||
private static final byte[] USERNAME_HASH_2 = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH_2);
|
private static final byte[] USERNAME_HASH_2 = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH_2);
|
||||||
|
private static final byte[] ENCRYPTED_USERNAME_1 = Base64.getUrlDecoder().decode(BASE_64_URL_ENCRYPTED_USERNAME_1);
|
||||||
|
private static final byte[] ENCRYPTED_USERNAME_2 = Base64.getUrlDecoder().decode(BASE_64_URL_ENCRYPTED_USERNAME_2);
|
||||||
|
|
||||||
private static final int SCAN_PAGE_SIZE = 1;
|
private static final int SCAN_PAGE_SIZE = 1;
|
||||||
|
|
||||||
|
@ -350,9 +354,11 @@ class AccountsTest {
|
||||||
final SecureRandom byteGenerator = new SecureRandom();
|
final SecureRandom byteGenerator = new SecureRandom();
|
||||||
final byte[] usernameHash = new byte[32];
|
final byte[] usernameHash = new byte[32];
|
||||||
byteGenerator.nextBytes(usernameHash);
|
byteGenerator.nextBytes(usernameHash);
|
||||||
|
final byte[] encryptedUsername = new byte[16];
|
||||||
|
byteGenerator.nextBytes(encryptedUsername);
|
||||||
|
|
||||||
// Set up the existing account to have a username hash
|
// Set up the existing account to have a username hash
|
||||||
accounts.confirmUsernameHash(account, usernameHash);
|
accounts.confirmUsernameHash(account, usernameHash, encryptedUsername);
|
||||||
|
|
||||||
verifyStoredState("+14151112222", account.getUuid(), account.getPhoneNumberIdentifier(), usernameHash, account, true);
|
verifyStoredState("+14151112222", account.getUuid(), account.getPhoneNumberIdentifier(), usernameHash, account, true);
|
||||||
|
|
||||||
|
@ -738,16 +744,20 @@ class AccountsTest {
|
||||||
assertThat(accounts.getByUsernameHash(USERNAME_HASH_1)).isEmpty();
|
assertThat(accounts.getByUsernameHash(USERNAME_HASH_1)).isEmpty();
|
||||||
|
|
||||||
accounts.reserveUsernameHash(account, USERNAME_HASH_1, Duration.ofDays(1));
|
accounts.reserveUsernameHash(account, USERNAME_HASH_1, Duration.ofDays(1));
|
||||||
accounts.confirmUsernameHash(account, USERNAME_HASH_1);
|
accounts.confirmUsernameHash(account, USERNAME_HASH_1, ENCRYPTED_USERNAME_1);
|
||||||
|
final UUID oldHandle = account.getUsernameLinkHandle();
|
||||||
|
|
||||||
{
|
{
|
||||||
final Optional<Account> maybeAccount = accounts.getByUsernameHash(USERNAME_HASH_1);
|
final Optional<Account> maybeAccount = accounts.getByUsernameHash(USERNAME_HASH_1);
|
||||||
|
|
||||||
verifyStoredState(account.getNumber(), account.getUuid(), account.getPhoneNumberIdentifier(), USERNAME_HASH_1, maybeAccount.orElseThrow(), account);
|
verifyStoredState(account.getNumber(), account.getUuid(), account.getPhoneNumberIdentifier(), USERNAME_HASH_1, maybeAccount.orElseThrow(), account);
|
||||||
|
final Optional<Account> maybeAccount2 = accounts.getByUsernameLinkHandle(oldHandle);
|
||||||
|
verifyStoredState(account.getNumber(), account.getUuid(), account.getPhoneNumberIdentifier(), USERNAME_HASH_1, maybeAccount2.orElseThrow(), account);
|
||||||
}
|
}
|
||||||
|
|
||||||
accounts.reserveUsernameHash(account, USERNAME_HASH_2, Duration.ofDays(1));
|
accounts.reserveUsernameHash(account, USERNAME_HASH_2, Duration.ofDays(1));
|
||||||
accounts.confirmUsernameHash(account, USERNAME_HASH_2);
|
accounts.confirmUsernameHash(account, USERNAME_HASH_2, ENCRYPTED_USERNAME_2);
|
||||||
|
final UUID newHandle = account.getUsernameLinkHandle();
|
||||||
|
|
||||||
assertThat(accounts.getByUsernameHash(USERNAME_HASH_1)).isEmpty();
|
assertThat(accounts.getByUsernameHash(USERNAME_HASH_1)).isEmpty();
|
||||||
assertThat(DYNAMO_DB_EXTENSION.getDynamoDbClient()
|
assertThat(DYNAMO_DB_EXTENSION.getDynamoDbClient()
|
||||||
|
@ -756,6 +766,7 @@ class AccountsTest {
|
||||||
.key(Map.of(Accounts.ATTR_USERNAME_HASH, AttributeValues.fromByteArray(USERNAME_HASH_1)))
|
.key(Map.of(Accounts.ATTR_USERNAME_HASH, AttributeValues.fromByteArray(USERNAME_HASH_1)))
|
||||||
.build())
|
.build())
|
||||||
.item()).isEmpty();
|
.item()).isEmpty();
|
||||||
|
assertThat(accounts.getByUsernameLinkHandle(oldHandle)).isEmpty();
|
||||||
|
|
||||||
{
|
{
|
||||||
final Optional<Account> maybeAccount = accounts.getByUsernameHash(USERNAME_HASH_2);
|
final Optional<Account> maybeAccount = accounts.getByUsernameHash(USERNAME_HASH_2);
|
||||||
|
@ -763,6 +774,9 @@ class AccountsTest {
|
||||||
assertThat(maybeAccount).isPresent();
|
assertThat(maybeAccount).isPresent();
|
||||||
verifyStoredState(account.getNumber(), account.getUuid(), account.getPhoneNumberIdentifier(),
|
verifyStoredState(account.getNumber(), account.getUuid(), account.getPhoneNumberIdentifier(),
|
||||||
USERNAME_HASH_2, maybeAccount.get(), account);
|
USERNAME_HASH_2, maybeAccount.get(), account);
|
||||||
|
final Optional<Account> maybeAccount2 = accounts.getByUsernameLinkHandle(newHandle);
|
||||||
|
verifyStoredState(account.getNumber(), account.getUuid(), account.getPhoneNumberIdentifier(),
|
||||||
|
USERNAME_HASH_2, maybeAccount2.get(), account);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -777,7 +791,7 @@ class AccountsTest {
|
||||||
// first account reserves and confirms username hash
|
// first account reserves and confirms username hash
|
||||||
assertThatNoException().isThrownBy(() -> {
|
assertThatNoException().isThrownBy(() -> {
|
||||||
accounts.reserveUsernameHash(firstAccount, USERNAME_HASH_1, Duration.ofDays(1));
|
accounts.reserveUsernameHash(firstAccount, USERNAME_HASH_1, Duration.ofDays(1));
|
||||||
accounts.confirmUsernameHash(firstAccount, USERNAME_HASH_1);
|
accounts.confirmUsernameHash(firstAccount, USERNAME_HASH_1, ENCRYPTED_USERNAME_1);
|
||||||
});
|
});
|
||||||
|
|
||||||
final Optional<Account> maybeAccount = accounts.getByUsernameHash(USERNAME_HASH_1);
|
final Optional<Account> maybeAccount = accounts.getByUsernameHash(USERNAME_HASH_1);
|
||||||
|
@ -789,13 +803,13 @@ class AccountsTest {
|
||||||
assertThatExceptionOfType(ContestedOptimisticLockException.class)
|
assertThatExceptionOfType(ContestedOptimisticLockException.class)
|
||||||
.isThrownBy(() -> accounts.reserveUsernameHash(secondAccount, USERNAME_HASH_1, Duration.ofDays(1)));
|
.isThrownBy(() -> accounts.reserveUsernameHash(secondAccount, USERNAME_HASH_1, Duration.ofDays(1)));
|
||||||
assertThatExceptionOfType(ContestedOptimisticLockException.class)
|
assertThatExceptionOfType(ContestedOptimisticLockException.class)
|
||||||
.isThrownBy(() -> accounts.confirmUsernameHash(secondAccount, USERNAME_HASH_1));
|
.isThrownBy(() -> accounts.confirmUsernameHash(secondAccount, USERNAME_HASH_1, ENCRYPTED_USERNAME_1));
|
||||||
|
|
||||||
// throw an error if first account tries to reserve or confirm the username hash that it has already confirmed
|
// throw an error if first account tries to reserve or confirm the username hash that it has already confirmed
|
||||||
assertThatExceptionOfType(ContestedOptimisticLockException.class)
|
assertThatExceptionOfType(ContestedOptimisticLockException.class)
|
||||||
.isThrownBy(() -> accounts.reserveUsernameHash(firstAccount, USERNAME_HASH_1, Duration.ofDays(1)));
|
.isThrownBy(() -> accounts.reserveUsernameHash(firstAccount, USERNAME_HASH_1, Duration.ofDays(1)));
|
||||||
assertThatExceptionOfType(ContestedOptimisticLockException.class)
|
assertThatExceptionOfType(ContestedOptimisticLockException.class)
|
||||||
.isThrownBy(() -> accounts.confirmUsernameHash(firstAccount, USERNAME_HASH_1));
|
.isThrownBy(() -> accounts.confirmUsernameHash(firstAccount, USERNAME_HASH_1, ENCRYPTED_USERNAME_1));
|
||||||
|
|
||||||
assertThat(secondAccount.getReservedUsernameHash()).isEmpty();
|
assertThat(secondAccount.getReservedUsernameHash()).isEmpty();
|
||||||
assertThat(secondAccount.getUsernameHash()).isEmpty();
|
assertThat(secondAccount.getUsernameHash()).isEmpty();
|
||||||
|
@ -809,7 +823,7 @@ class AccountsTest {
|
||||||
account.setVersion(account.getVersion() + 77);
|
account.setVersion(account.getVersion() + 77);
|
||||||
|
|
||||||
assertThatExceptionOfType(ContestedOptimisticLockException.class)
|
assertThatExceptionOfType(ContestedOptimisticLockException.class)
|
||||||
.isThrownBy(() -> accounts.confirmUsernameHash(account, USERNAME_HASH_1));
|
.isThrownBy(() -> accounts.confirmUsernameHash(account, USERNAME_HASH_1, ENCRYPTED_USERNAME_1));
|
||||||
|
|
||||||
assertThat(account.getUsernameHash()).isEmpty();
|
assertThat(account.getUsernameHash()).isEmpty();
|
||||||
}
|
}
|
||||||
|
@ -820,7 +834,7 @@ class AccountsTest {
|
||||||
accounts.create(account);
|
accounts.create(account);
|
||||||
|
|
||||||
accounts.reserveUsernameHash(account, USERNAME_HASH_1, Duration.ofDays(1));
|
accounts.reserveUsernameHash(account, USERNAME_HASH_1, Duration.ofDays(1));
|
||||||
accounts.confirmUsernameHash(account, USERNAME_HASH_1);
|
accounts.confirmUsernameHash(account, USERNAME_HASH_1, ENCRYPTED_USERNAME_1);
|
||||||
assertThat(accounts.getByUsernameHash(USERNAME_HASH_1)).isPresent();
|
assertThat(accounts.getByUsernameHash(USERNAME_HASH_1)).isPresent();
|
||||||
|
|
||||||
accounts.clearUsernameHash(account);
|
accounts.clearUsernameHash(account);
|
||||||
|
@ -844,7 +858,7 @@ class AccountsTest {
|
||||||
accounts.create(account);
|
accounts.create(account);
|
||||||
|
|
||||||
accounts.reserveUsernameHash(account, USERNAME_HASH_1, Duration.ofDays(1));
|
accounts.reserveUsernameHash(account, USERNAME_HASH_1, Duration.ofDays(1));
|
||||||
accounts.confirmUsernameHash(account, USERNAME_HASH_1);
|
accounts.confirmUsernameHash(account, USERNAME_HASH_1, ENCRYPTED_USERNAME_1);
|
||||||
|
|
||||||
account.setVersion(account.getVersion() + 12);
|
account.setVersion(account.getVersion() + 12);
|
||||||
|
|
||||||
|
@ -868,10 +882,10 @@ class AccountsTest {
|
||||||
assertThrows(ContestedOptimisticLockException.class,
|
assertThrows(ContestedOptimisticLockException.class,
|
||||||
() -> accounts.reserveUsernameHash(account2, USERNAME_HASH_1, Duration.ofDays(1)));
|
() -> accounts.reserveUsernameHash(account2, USERNAME_HASH_1, Duration.ofDays(1)));
|
||||||
assertThrows(ContestedOptimisticLockException.class,
|
assertThrows(ContestedOptimisticLockException.class,
|
||||||
() -> accounts.confirmUsernameHash(account2, USERNAME_HASH_1));
|
() -> accounts.confirmUsernameHash(account2, USERNAME_HASH_1, ENCRYPTED_USERNAME_1));
|
||||||
assertThat(accounts.getByUsernameHash(USERNAME_HASH_1)).isEmpty();
|
assertThat(accounts.getByUsernameHash(USERNAME_HASH_1)).isEmpty();
|
||||||
|
|
||||||
accounts.confirmUsernameHash(account1, USERNAME_HASH_1);
|
accounts.confirmUsernameHash(account1, USERNAME_HASH_1, ENCRYPTED_USERNAME_1);
|
||||||
assertThat(account1.getReservedUsernameHash()).isEmpty();
|
assertThat(account1.getReservedUsernameHash()).isEmpty();
|
||||||
assertArrayEquals(account1.getUsernameHash().orElseThrow(), USERNAME_HASH_1);
|
assertArrayEquals(account1.getUsernameHash().orElseThrow(), USERNAME_HASH_1);
|
||||||
assertThat(accounts.getByUsernameHash(USERNAME_HASH_1).get().getUuid()).isEqualTo(account1.getUuid());
|
assertThat(accounts.getByUsernameHash(USERNAME_HASH_1).get().getUuid()).isEqualTo(account1.getUuid());
|
||||||
|
@ -898,7 +912,7 @@ class AccountsTest {
|
||||||
assertThat(accounts.usernameHashAvailable(Optional.of(UUID.randomUUID()), USERNAME_HASH_1)).isFalse();
|
assertThat(accounts.usernameHashAvailable(Optional.of(UUID.randomUUID()), USERNAME_HASH_1)).isFalse();
|
||||||
assertThat(accounts.usernameHashAvailable(Optional.of(account1.getUuid()), USERNAME_HASH_1)).isTrue();
|
assertThat(accounts.usernameHashAvailable(Optional.of(account1.getUuid()), USERNAME_HASH_1)).isTrue();
|
||||||
|
|
||||||
accounts.confirmUsernameHash(account1, USERNAME_HASH_1);
|
accounts.confirmUsernameHash(account1, USERNAME_HASH_1, ENCRYPTED_USERNAME_1);
|
||||||
assertThat(accounts.usernameHashAvailable(USERNAME_HASH_1)).isFalse();
|
assertThat(accounts.usernameHashAvailable(USERNAME_HASH_1)).isFalse();
|
||||||
assertThat(accounts.usernameHashAvailable(Optional.empty(), USERNAME_HASH_1)).isFalse();
|
assertThat(accounts.usernameHashAvailable(Optional.empty(), USERNAME_HASH_1)).isFalse();
|
||||||
assertThat(accounts.usernameHashAvailable(Optional.of(UUID.randomUUID()), USERNAME_HASH_1)).isFalse();
|
assertThat(accounts.usernameHashAvailable(Optional.of(UUID.randomUUID()), USERNAME_HASH_1)).isFalse();
|
||||||
|
@ -918,7 +932,7 @@ class AccountsTest {
|
||||||
|
|
||||||
// only account1 should be able to confirm the reserved hash
|
// only account1 should be able to confirm the reserved hash
|
||||||
assertThrows(ContestedOptimisticLockException.class,
|
assertThrows(ContestedOptimisticLockException.class,
|
||||||
() -> accounts.confirmUsernameHash(account2, USERNAME_HASH_1));
|
() -> accounts.confirmUsernameHash(account2, USERNAME_HASH_1, ENCRYPTED_USERNAME_1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -942,12 +956,12 @@ class AccountsTest {
|
||||||
runnable.run();
|
runnable.run();
|
||||||
assertEquals(account2.getReservedUsernameHash().orElseThrow(), USERNAME_HASH_1);
|
assertEquals(account2.getReservedUsernameHash().orElseThrow(), USERNAME_HASH_1);
|
||||||
|
|
||||||
accounts.confirmUsernameHash(account2, USERNAME_HASH_1);
|
accounts.confirmUsernameHash(account2, USERNAME_HASH_1, ENCRYPTED_USERNAME_1);
|
||||||
|
|
||||||
assertThrows(ContestedOptimisticLockException.class,
|
assertThrows(ContestedOptimisticLockException.class,
|
||||||
() -> accounts.reserveUsernameHash(account1, USERNAME_HASH_1, Duration.ofDays(2)));
|
() -> accounts.reserveUsernameHash(account1, USERNAME_HASH_1, Duration.ofDays(2)));
|
||||||
assertThrows(ContestedOptimisticLockException.class,
|
assertThrows(ContestedOptimisticLockException.class,
|
||||||
() -> accounts.confirmUsernameHash(account1, USERNAME_HASH_1));
|
() -> accounts.confirmUsernameHash(account1, USERNAME_HASH_1, ENCRYPTED_USERNAME_1));
|
||||||
assertThat(accounts.getByUsernameHash(USERNAME_HASH_1).get().getUuid()).isEqualTo(account2.getUuid());
|
assertThat(accounts.getByUsernameHash(USERNAME_HASH_1).get().getUuid()).isEqualTo(account2.getUuid());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -970,7 +984,7 @@ class AccountsTest {
|
||||||
assertThrows(ContestedOptimisticLockException.class,
|
assertThrows(ContestedOptimisticLockException.class,
|
||||||
() -> accounts.reserveUsernameHash(account, USERNAME_HASH_1, Duration.ofDays(1)));
|
() -> accounts.reserveUsernameHash(account, USERNAME_HASH_1, Duration.ofDays(1)));
|
||||||
assertThrows(ContestedOptimisticLockException.class,
|
assertThrows(ContestedOptimisticLockException.class,
|
||||||
() -> accounts.confirmUsernameHash(account, USERNAME_HASH_1));
|
() -> accounts.confirmUsernameHash(account, USERNAME_HASH_1, ENCRYPTED_USERNAME_1));
|
||||||
assertThat(account.getReservedUsernameHash()).isEmpty();
|
assertThat(account.getReservedUsernameHash()).isEmpty();
|
||||||
assertThat(account.getUsernameHash()).isEmpty();
|
assertThat(account.getUsernameHash()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue