mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-19 18:08:03 +01:00
Support for push preauth
This commit is contained in:
@@ -247,7 +247,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
DisabledPermittedAccount.class, disabledPermittedAccountAuthFilter)));
|
||||
environment.jersey().register(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class)));
|
||||
|
||||
environment.jersey().register(new AccountController(pendingAccountsManager, accountsManager, abusiveHostRules, rateLimiters, smsSender, directoryQueue, messagesManager, turnTokenGenerator, config.getTestDevices(), recaptchaClient));
|
||||
environment.jersey().register(new AccountController(pendingAccountsManager, accountsManager, abusiveHostRules, rateLimiters, smsSender, directoryQueue, messagesManager, turnTokenGenerator, config.getTestDevices(), recaptchaClient, gcmSender, apnSender));
|
||||
environment.jersey().register(new DeviceController(pendingDevicesManager, accountsManager, messagesManager, directoryQueue, rateLimiters, config.getMaxDevices()));
|
||||
environment.jersey().register(new DirectoryController(rateLimiters, directory, directoryCredentialsGenerator));
|
||||
environment.jersey().register(new ProvisioningController(rateLimiters, pushSender));
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -13,11 +15,15 @@ public class StoredVerificationCode {
|
||||
@JsonProperty
|
||||
private long timestamp;
|
||||
|
||||
@JsonProperty
|
||||
private String pushCode;
|
||||
|
||||
public StoredVerificationCode() {}
|
||||
|
||||
public StoredVerificationCode(String code, long timestamp) {
|
||||
public StoredVerificationCode(String code, long timestamp, String pushCode) {
|
||||
this.code = code;
|
||||
this.timestamp = timestamp;
|
||||
this.pushCode = pushCode;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
@@ -28,8 +34,16 @@ public class StoredVerificationCode {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public String getPushCode() {
|
||||
return pushCode;
|
||||
}
|
||||
|
||||
public boolean isValid(String theirCodeString) {
|
||||
if (timestamp + TimeUnit.MINUTES.toMillis(30) < System.currentTimeMillis()) {
|
||||
if (timestamp + TimeUnit.MINUTES.toMillis(10) < System.currentTimeMillis()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Util.isEmpty(code) || Util.isEmpty(theirCodeString)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -38,4 +52,5 @@ public class StoredVerificationCode {
|
||||
|
||||
return MessageDigest.isEqual(ourCode, theirCode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,6 +37,10 @@ import org.whispersystems.textsecuregcm.entities.GcmRegistrationId;
|
||||
import org.whispersystems.textsecuregcm.entities.RegistrationLock;
|
||||
import org.whispersystems.textsecuregcm.entities.RegistrationLockFailure;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.push.APNSender;
|
||||
import org.whispersystems.textsecuregcm.push.ApnMessage;
|
||||
import org.whispersystems.textsecuregcm.push.GCMSender;
|
||||
import org.whispersystems.textsecuregcm.push.GcmMessage;
|
||||
import org.whispersystems.textsecuregcm.recaptcha.RecaptchaClient;
|
||||
import org.whispersystems.textsecuregcm.sms.SmsSender;
|
||||
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
|
||||
@@ -48,6 +52,7 @@ import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
||||
import org.whispersystems.textsecuregcm.storage.PendingAccountsManager;
|
||||
import org.whispersystems.textsecuregcm.util.Constants;
|
||||
import org.whispersystems.textsecuregcm.util.Hex;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
import org.whispersystems.textsecuregcm.util.VerificationCode;
|
||||
|
||||
@@ -90,16 +95,18 @@ public class AccountController {
|
||||
private final Meter captchaFailureMeter = metricRegistry.meter(name(AccountController.class, "captcha_failure" ));
|
||||
|
||||
|
||||
private final PendingAccountsManager pendingAccounts;
|
||||
private final AccountsManager accounts;
|
||||
private final AbusiveHostRules abusiveHostRules;
|
||||
private final RateLimiters rateLimiters;
|
||||
private final SmsSender smsSender;
|
||||
private final DirectoryQueue directoryQueue;
|
||||
private final MessagesManager messagesManager;
|
||||
private final TurnTokenGenerator turnTokenGenerator;
|
||||
private final Map<String, Integer> testDevices;
|
||||
private final RecaptchaClient recaptchaClient;
|
||||
private final PendingAccountsManager pendingAccounts;
|
||||
private final AccountsManager accounts;
|
||||
private final AbusiveHostRules abusiveHostRules;
|
||||
private final RateLimiters rateLimiters;
|
||||
private final SmsSender smsSender;
|
||||
private final DirectoryQueue directoryQueue;
|
||||
private final MessagesManager messagesManager;
|
||||
private final TurnTokenGenerator turnTokenGenerator;
|
||||
private final Map<String, Integer> testDevices;
|
||||
private final RecaptchaClient recaptchaClient;
|
||||
private final GCMSender gcmSender;
|
||||
private final APNSender apnSender;
|
||||
|
||||
public AccountController(PendingAccountsManager pendingAccounts,
|
||||
AccountsManager accounts,
|
||||
@@ -110,7 +117,9 @@ public class AccountController {
|
||||
MessagesManager messagesManager,
|
||||
TurnTokenGenerator turnTokenGenerator,
|
||||
Map<String, Integer> testDevices,
|
||||
RecaptchaClient recaptchaClient)
|
||||
RecaptchaClient recaptchaClient,
|
||||
GCMSender gcmSender,
|
||||
APNSender apnSender)
|
||||
{
|
||||
this.pendingAccounts = pendingAccounts;
|
||||
this.accounts = accounts;
|
||||
@@ -122,6 +131,41 @@ public class AccountController {
|
||||
this.testDevices = testDevices;
|
||||
this.turnTokenGenerator = turnTokenGenerator;
|
||||
this.recaptchaClient = recaptchaClient;
|
||||
this.gcmSender = gcmSender;
|
||||
this.apnSender = apnSender;
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/{type}/preauth/{token}/{number}")
|
||||
public Response getPreAuth(@PathParam("type") String pushType,
|
||||
@PathParam("token") String pushToken,
|
||||
@PathParam("number") String number)
|
||||
{
|
||||
if (!"apn".equals(pushType) && !"fcm".equals(pushType)) {
|
||||
return Response.status(400).build();
|
||||
}
|
||||
|
||||
if (!Util.isValidNumber(number)) {
|
||||
return Response.status(400).build();
|
||||
}
|
||||
|
||||
String pushChallenge = generatePushChallenge();
|
||||
StoredVerificationCode storedVerificationCode = new StoredVerificationCode(null,
|
||||
System.currentTimeMillis(),
|
||||
pushChallenge);
|
||||
|
||||
pendingAccounts.store(number, storedVerificationCode);
|
||||
|
||||
if ("fcm".equals(pushType)) {
|
||||
gcmSender.sendMessage(new GcmMessage(pushToken, number, 0, GcmMessage.Type.CHALLENGE, Optional.of(storedVerificationCode.getPushCode())));
|
||||
} else if ("apn".equals(pushType)) {
|
||||
apnSender.sendMessage(new ApnMessage(pushToken, number, 0, true, Optional.of(storedVerificationCode.getPushCode())));
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@Timed
|
||||
@@ -132,7 +176,8 @@ public class AccountController {
|
||||
@HeaderParam("X-Forwarded-For") String forwardedFor,
|
||||
@HeaderParam("Accept-Language") Optional<String> locale,
|
||||
@QueryParam("client") Optional<String> client,
|
||||
@QueryParam("captcha") Optional<String> captcha)
|
||||
@QueryParam("captcha") Optional<String> captcha,
|
||||
@QueryParam("challenge") Optional<String> pushChallenge)
|
||||
throws RateLimitExceededException
|
||||
{
|
||||
if (!Util.isValidNumber(number)) {
|
||||
@@ -145,7 +190,8 @@ public class AccountController {
|
||||
.reduce((a, b) -> b)
|
||||
.orElseThrow();
|
||||
|
||||
CaptchaRequirement requirement = requiresCaptcha(number, transport, forwardedFor, requester, captcha);
|
||||
Optional<StoredVerificationCode> storedChallenge = pendingAccounts.getCodeForNumber(number);
|
||||
CaptchaRequirement requirement = requiresCaptcha(number, transport, forwardedFor, requester, captcha, storedChallenge, pushChallenge);
|
||||
|
||||
if (requirement.isCaptchaRequired()) {
|
||||
if (requirement.isAutoBlock() && shouldAutoBlock(requester)) {
|
||||
@@ -170,7 +216,8 @@ public class AccountController {
|
||||
|
||||
VerificationCode verificationCode = generateVerificationCode(number);
|
||||
StoredVerificationCode storedVerificationCode = new StoredVerificationCode(verificationCode.getVerificationCode(),
|
||||
System.currentTimeMillis());
|
||||
System.currentTimeMillis(),
|
||||
storedChallenge.map(StoredVerificationCode::getPushCode).orElse(null));
|
||||
|
||||
pendingAccounts.store(number, storedVerificationCode);
|
||||
|
||||
@@ -397,7 +444,10 @@ public class AccountController {
|
||||
}
|
||||
|
||||
private CaptchaRequirement requiresCaptcha(String number, String transport, String forwardedFor,
|
||||
String requester, Optional<String> captchaToken)
|
||||
String requester,
|
||||
Optional<String> captchaToken,
|
||||
Optional<StoredVerificationCode> storedVerificationCode,
|
||||
Optional<String> pushChallenge)
|
||||
{
|
||||
|
||||
if (captchaToken.isPresent()) {
|
||||
@@ -412,6 +462,14 @@ public class AccountController {
|
||||
}
|
||||
}
|
||||
|
||||
if (pushChallenge.isPresent()) {
|
||||
Optional<String> storedPushChallenge = storedVerificationCode.map(StoredVerificationCode::getPushCode);
|
||||
|
||||
if (!pushChallenge.get().equals(storedPushChallenge.orElse(null))) {
|
||||
return new CaptchaRequirement(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
List<AbusiveHostRule> abuseRules = abusiveHostRules.getAbusiveHostRulesFor(requester);
|
||||
|
||||
for (AbusiveHostRule abuseRule : abuseRules) {
|
||||
@@ -493,7 +551,8 @@ public class AccountController {
|
||||
pendingAccounts.remove(number);
|
||||
}
|
||||
|
||||
@VisibleForTesting protected VerificationCode generateVerificationCode(String number) {
|
||||
@VisibleForTesting protected
|
||||
VerificationCode generateVerificationCode(String number) {
|
||||
if (testDevices.containsKey(number)) {
|
||||
return new VerificationCode(testDevices.get(number));
|
||||
}
|
||||
@@ -503,6 +562,14 @@ public class AccountController {
|
||||
return new VerificationCode(randomInt);
|
||||
}
|
||||
|
||||
private String generatePushChallenge() {
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] challenge = new byte[16];
|
||||
random.nextBytes(challenge);
|
||||
|
||||
return Hex.toStringCondensed(challenge);
|
||||
}
|
||||
|
||||
private static class CaptchaRequirement {
|
||||
private final boolean captchaRequired;
|
||||
private final boolean autoBlock;
|
||||
|
||||
@@ -144,7 +144,8 @@ public class DeviceController {
|
||||
|
||||
VerificationCode verificationCode = generateVerificationCode();
|
||||
StoredVerificationCode storedVerificationCode = new StoredVerificationCode(verificationCode.getVerificationCode(),
|
||||
System.currentTimeMillis());
|
||||
System.currentTimeMillis(),
|
||||
null);
|
||||
|
||||
pendingDevices.store(account.getNumber(), storedVerificationCode);
|
||||
|
||||
|
||||
@@ -94,6 +94,8 @@ public class APNSender implements Managed {
|
||||
Futures.addCallback(future, new FutureCallback<ApnResult>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable ApnResult result) {
|
||||
if (message.getChallengeData().isPresent()) return;
|
||||
|
||||
if (result == null) {
|
||||
logger.warn("*** RECEIVED NULL APN RESULT ***");
|
||||
} else if (result.getStatus() == ApnResult.Status.NO_SUCH_USER) {
|
||||
|
||||
@@ -157,7 +157,7 @@ public class ApnFallbackManager implements Managed, Runnable {
|
||||
continue;
|
||||
}
|
||||
|
||||
apnSender.sendMessage(new ApnMessage(apnId, separated.get().first(), separated.get().second(), true));
|
||||
apnSender.sendMessage(new ApnMessage(apnId, separated.get().first(), separated.get().second(), true, Optional.empty()));
|
||||
retry.mark();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
package org.whispersystems.textsecuregcm.push;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public class ApnMessage {
|
||||
|
||||
public static final String APN_PAYLOAD = "{\"aps\":{\"sound\":\"default\",\"alert\":{\"loc-key\":\"APN_Message\"}}}";
|
||||
public static final long MAX_EXPIRATION = Integer.MAX_VALUE * 1000L;
|
||||
public static final String APN_NOTIFICATION_PAYLOAD = "{\"aps\":{\"sound\":\"default\",\"alert\":{\"loc-key\":\"APN_Message\"}}}";
|
||||
public static final String APN_CHALLENGE_PAYLOAD = "{\"aps\":{\"sound\":\"default\",\"alert\":{\"loc-key\":\"APN_Message\"}}, \"challenge\" : \"%s\"}";
|
||||
public static final long MAX_EXPIRATION = Integer.MAX_VALUE * 1000L;
|
||||
|
||||
private final String apnId;
|
||||
private final String number;
|
||||
private final long deviceId;
|
||||
private final boolean isVoip;
|
||||
private final String apnId;
|
||||
private final String number;
|
||||
private final long deviceId;
|
||||
private final boolean isVoip;
|
||||
private final Optional<String> challengeData;
|
||||
|
||||
public ApnMessage(String apnId, String number, long deviceId, boolean isVoip) {
|
||||
this.apnId = apnId;
|
||||
this.number = number;
|
||||
this.deviceId = deviceId;
|
||||
this.isVoip = isVoip;
|
||||
public ApnMessage(String apnId, String number, long deviceId, boolean isVoip, Optional<String> challengeData) {
|
||||
this.apnId = apnId;
|
||||
this.number = number;
|
||||
this.deviceId = deviceId;
|
||||
this.isVoip = isVoip;
|
||||
this.challengeData = challengeData;
|
||||
}
|
||||
|
||||
public boolean isVoip() {
|
||||
@@ -26,7 +34,13 @@ public class ApnMessage {
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return APN_PAYLOAD;
|
||||
if (!challengeData.isPresent()) return APN_NOTIFICATION_PAYLOAD;
|
||||
else return String.format(APN_CHALLENGE_PAYLOAD, challengeData.get());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public Optional<String> getChallengeData() {
|
||||
return challengeData;
|
||||
}
|
||||
|
||||
public long getExpirationTime() {
|
||||
|
||||
@@ -66,14 +66,22 @@ public class GCMSender implements Managed {
|
||||
.withDestination(message.getGcmId())
|
||||
.withPriority("high");
|
||||
|
||||
String key = message.isReceipt() ? "receipt" : "notification";
|
||||
Message request = builder.withDataPart(key, "").build();
|
||||
String key;
|
||||
|
||||
switch (message.getType()) {
|
||||
case RECEIPT: key = "receipt"; break;
|
||||
case NOTIFICATION: key = "notification"; break;
|
||||
case CHALLENGE: key = "challenge"; break;
|
||||
default: throw new AssertionError();
|
||||
}
|
||||
|
||||
Message request = builder.withDataPart(key, message.getData().orElse("")).build();
|
||||
|
||||
CompletableFuture<Result> future = signalSender.send(request);
|
||||
markOutboundMeter(key);
|
||||
|
||||
future.handle((result, throwable) -> {
|
||||
if (result != null) {
|
||||
if (result != null && message.getType() != GcmMessage.Type.CHALLENGE) {
|
||||
if (result.isUnregistered() || result.isInvalidRegistrationId()) {
|
||||
executor.submit(() -> handleBadRegistration(message));
|
||||
} else if (result.hasCanonicalRegistrationId()) {
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
package org.whispersystems.textsecuregcm.push;
|
||||
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public class GcmMessage {
|
||||
|
||||
private final String gcmId;
|
||||
private final String number;
|
||||
private final int deviceId;
|
||||
private final boolean receipt;
|
||||
public enum Type {
|
||||
RECEIPT, NOTIFICATION, CHALLENGE
|
||||
}
|
||||
|
||||
public GcmMessage(String gcmId, String number, int deviceId, boolean receipt) {
|
||||
this.gcmId = gcmId;
|
||||
this.number = number;
|
||||
this.deviceId = deviceId;
|
||||
this.receipt = receipt;
|
||||
private final String gcmId;
|
||||
private final String number;
|
||||
private final int deviceId;
|
||||
private final Type type;
|
||||
private final Optional<String> data;
|
||||
|
||||
public GcmMessage(String gcmId, String number, int deviceId, Type type, Optional<String> data) {
|
||||
this.gcmId = gcmId;
|
||||
this.number = number;
|
||||
this.deviceId = deviceId;
|
||||
this.type = type;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getGcmId() {
|
||||
@@ -22,11 +32,16 @@ public class GcmMessage {
|
||||
return number;
|
||||
}
|
||||
|
||||
public boolean isReceipt() {
|
||||
return receipt;
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public int getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public Optional<String> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.whispersystems.textsecuregcm.util.BlockingThreadPoolExecutor;
|
||||
import org.whispersystems.textsecuregcm.util.Constants;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.codahale.metrics.MetricRegistry.name;
|
||||
@@ -105,7 +106,7 @@ public class PushSender implements Managed {
|
||||
|
||||
private void sendGcmNotification(Account account, Device device) {
|
||||
GcmMessage gcmMessage = new GcmMessage(device.getGcmId(), account.getNumber(),
|
||||
(int)device.getId(), false);
|
||||
(int)device.getId(), GcmMessage.Type.NOTIFICATION, Optional.empty());
|
||||
|
||||
gcmSender.sendMessage(gcmMessage);
|
||||
}
|
||||
@@ -126,10 +127,10 @@ public class PushSender implements Managed {
|
||||
}
|
||||
|
||||
if (!Util.isEmpty(device.getVoipApnId())) {
|
||||
apnMessage = new ApnMessage(device.getVoipApnId(), account.getNumber(), device.getId(), true);
|
||||
apnMessage = new ApnMessage(device.getVoipApnId(), account.getNumber(), device.getId(), true, Optional.empty());
|
||||
RedisOperation.unchecked(() -> apnFallbackManager.schedule(account, device));
|
||||
} else {
|
||||
apnMessage = new ApnMessage(device.getApnId(), account.getNumber(), device.getId(), false);
|
||||
apnMessage = new ApnMessage(device.getApnId(), account.getNumber(), device.getId(), false, Optional.empty());
|
||||
}
|
||||
|
||||
apnSender.sendMessage(apnMessage);
|
||||
|
||||
@@ -19,17 +19,10 @@ package org.whispersystems.textsecuregcm.storage;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.SharedMetricRegistries;
|
||||
import com.codahale.metrics.Timer;
|
||||
import org.jdbi.v3.core.mapper.RowMapper;
|
||||
import org.jdbi.v3.core.statement.PreparedBatch;
|
||||
import org.jdbi.v3.core.statement.StatementContext;
|
||||
import org.jdbi.v3.core.transaction.TransactionIsolationLevel;
|
||||
import org.whispersystems.textsecuregcm.auth.StoredVerificationCode;
|
||||
import org.whispersystems.textsecuregcm.storage.mappers.StoredVerificationCodeRowMapper;
|
||||
import org.whispersystems.textsecuregcm.util.Constants;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.codahale.metrics.MetricRegistry.name;
|
||||
@@ -49,17 +42,17 @@ public class PendingAccounts {
|
||||
this.database.getDatabase().registerRowMapper(new StoredVerificationCodeRowMapper());
|
||||
}
|
||||
|
||||
public void insert(String number, String verificationCode, long timestamp) {
|
||||
database.use(jdbi -> jdbi.useTransaction(TransactionIsolationLevel.SERIALIZABLE, handle -> {
|
||||
public void insert(String number, String verificationCode, long timestamp, String pushCode) {
|
||||
database.use(jdbi -> jdbi.useHandle(handle -> {
|
||||
try (Timer.Context ignored = insertTimer.time()) {
|
||||
handle.createUpdate("DELETE FROM pending_accounts WHERE number = :number")
|
||||
.bind("number", number)
|
||||
.execute();
|
||||
|
||||
handle.createUpdate("INSERT INTO pending_accounts (number, verification_code, timestamp) VALUES (:number, :verification_code, :timestamp)")
|
||||
handle.createUpdate("INSERT INTO pending_accounts (number, verification_code, timestamp, push_code) " +
|
||||
"VALUES (:number, :verification_code, :timestamp, :push_code) " +
|
||||
"ON CONFLICT(number) DO UPDATE " +
|
||||
"SET verification_code = EXCLUDED.verification_code, timestamp = EXCLUDED.timestamp, push_code = EXCLUDED.push_code")
|
||||
.bind("verification_code", verificationCode)
|
||||
.bind("timestamp", timestamp)
|
||||
.bind("number", number)
|
||||
.bind("push_code", pushCode)
|
||||
.execute();
|
||||
}
|
||||
}));
|
||||
@@ -68,7 +61,7 @@ public class PendingAccounts {
|
||||
public Optional<StoredVerificationCode> getCodeForNumber(String number) {
|
||||
return database.with(jdbi ->jdbi.withHandle(handle -> {
|
||||
try (Timer.Context ignored = getCodeForNumberTimer.time()) {
|
||||
return handle.createQuery("SELECT verification_code, timestamp FROM pending_accounts WHERE number = :number")
|
||||
return handle.createQuery("SELECT verification_code, timestamp, push_code FROM pending_accounts WHERE number = :number")
|
||||
.bind("number", number)
|
||||
.mapTo(StoredVerificationCode.class)
|
||||
.findFirst();
|
||||
|
||||
@@ -48,7 +48,7 @@ public class PendingAccountsManager {
|
||||
|
||||
public void store(String number, StoredVerificationCode code) {
|
||||
memcacheSet(number, code);
|
||||
pendingAccounts.insert(number, code.getCode(), code.getTimestamp());
|
||||
pendingAccounts.insert(number, code.getCode(), code.getTimestamp(), code.getPushCode());
|
||||
}
|
||||
|
||||
public void remove(String number) {
|
||||
|
||||
@@ -57,7 +57,7 @@ public class PendingDevices {
|
||||
public Optional<StoredVerificationCode> getCodeForNumber(String number) {
|
||||
return database.with(jdbi -> jdbi.withHandle(handle -> {
|
||||
try (Timer.Context timer = getCodeForNumberTimer.time()) {
|
||||
return handle.createQuery("SELECT verification_code, timestamp FROM pending_devices WHERE number = :number")
|
||||
return handle.createQuery("SELECT verification_code, timestamp, NULL as push_code FROM pending_devices WHERE number = :number")
|
||||
.bind("number", number)
|
||||
.mapTo(StoredVerificationCode.class)
|
||||
.findFirst();
|
||||
|
||||
@@ -12,6 +12,7 @@ public class StoredVerificationCodeRowMapper implements RowMapper<StoredVerifica
|
||||
@Override
|
||||
public StoredVerificationCode map(ResultSet resultSet, StatementContext ctx) throws SQLException {
|
||||
return new StoredVerificationCode(resultSet.getString("verification_code"),
|
||||
resultSet.getLong("timestamp"));
|
||||
resultSet.getLong("timestamp"),
|
||||
resultSet.getString("push_code"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,14 +31,18 @@ public class Hex {
|
||||
};
|
||||
|
||||
public static String toString(byte[] bytes) {
|
||||
return toString(bytes, 0, bytes.length);
|
||||
return toString(bytes, 0, bytes.length, false);
|
||||
}
|
||||
|
||||
public static String toString(byte[] bytes, int offset, int length) {
|
||||
public static String toStringCondensed(byte[] bytes) {
|
||||
return toString(bytes, 0, bytes.length, true);
|
||||
}
|
||||
|
||||
public static String toString(byte[] bytes, int offset, int length, boolean condensed) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (int i = 0; i < length; i++) {
|
||||
appendHexChar(buf, bytes[offset + i]);
|
||||
buf.append(' ');
|
||||
if (!condensed) buf.append(' ');
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@@ -170,6 +170,10 @@ public class Util {
|
||||
return Arrays.hashCode(objects);
|
||||
}
|
||||
|
||||
public static boolean isEquals(Object first, Object second) {
|
||||
return (first == null && second == null) || (first == second) || (first != null && first.equals(second));
|
||||
}
|
||||
|
||||
public static long todayInMillis() {
|
||||
return TimeUnit.DAYS.toMillis(TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user