mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 02:20:39 +00:00
Update libsignal-service-java
- Use session based number verification and registration
This commit is contained in:
parent
20b3563f21
commit
276ecef300
19 changed files with 359 additions and 158 deletions
|
@ -76,6 +76,13 @@
|
|||
{"name":"storeSession","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","org.signal.libsignal.protocol.state.SessionRecord"] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"org.asamk.signal.manager.storage.senderKeys.SenderKeyStore",
|
||||
"methods":[
|
||||
{"name":"loadSenderKey","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","java.util.UUID"] },
|
||||
{"name":"storeSenderKey","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","java.util.UUID","org.signal.libsignal.protocol.groups.state.SenderKeyRecord"] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"org.graalvm.jniutils.JNIExceptionWrapperEntryPoints",
|
||||
"methods":[{"name":"getClassName","parameterTypes":["java.lang.Class"] }]
|
||||
|
|
|
@ -78,22 +78,10 @@
|
|||
"allDeclaredFields":true,
|
||||
"allDeclaredMethods":true
|
||||
},
|
||||
{
|
||||
"name":"com.google.protobuf.DescriptorMessageInfoFactory"
|
||||
},
|
||||
{
|
||||
"name":"com.google.protobuf.ExtensionRegistry"
|
||||
},
|
||||
{
|
||||
"name":"com.google.protobuf.ExtensionSchemaFull"
|
||||
},
|
||||
{
|
||||
"name":"com.google.protobuf.GeneratedMessageLite",
|
||||
"fields":[{"name":"unknownFields"}]
|
||||
},
|
||||
{
|
||||
"name":"com.google.protobuf.GeneratedMessageV3"
|
||||
},
|
||||
{
|
||||
"name":"com.google.protobuf.Internal$LongList",
|
||||
"allDeclaredMethods":true
|
||||
|
@ -108,19 +96,10 @@
|
|||
"allDeclaredMethods":true,
|
||||
"allDeclaredConstructors":true
|
||||
},
|
||||
{
|
||||
"name":"com.google.protobuf.MapFieldSchemaFull"
|
||||
},
|
||||
{
|
||||
"name":"com.google.protobuf.NewInstanceSchemaFull"
|
||||
},
|
||||
{
|
||||
"name":"com.google.protobuf.PrimitiveNonBoxingCollection",
|
||||
"allDeclaredMethods":true
|
||||
},
|
||||
{
|
||||
"name":"com.google.protobuf.UnknownFieldSetSchema"
|
||||
},
|
||||
{
|
||||
"name":"com.sun.crypto.provider.AESCipher$General",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
|
@ -465,9 +444,6 @@
|
|||
{
|
||||
"name":"javax.smartcardio.CardPermission"
|
||||
},
|
||||
{
|
||||
"name":"libcore.io.Memory"
|
||||
},
|
||||
{
|
||||
"name":"long",
|
||||
"allDeclaredMethods":true,
|
||||
|
@ -982,10 +958,6 @@
|
|||
{"name":"startColor","parameterTypes":[] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"org.asamk.signal.json.JsonStreamSerializer",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.asamk.signal.json.JsonSyncDataMessage",
|
||||
"allDeclaredFields":true,
|
||||
|
@ -1819,9 +1791,6 @@
|
|||
"name":"org.freedesktop.dbus.interfaces.Properties$PropertiesChanged",
|
||||
"allPublicConstructors":true
|
||||
},
|
||||
{
|
||||
"name":"org.robolectric.Robolectric"
|
||||
},
|
||||
{
|
||||
"name":"org.signal.cdsi.proto.ClientRequest",
|
||||
"fields":[
|
||||
|
@ -2288,7 +2257,6 @@
|
|||
"queryAllDeclaredMethods":true,
|
||||
"queryAllDeclaredConstructors":true,
|
||||
"methods":[
|
||||
{"name":"getCode","parameterTypes":[] },
|
||||
{"name":"getNumber","parameterTypes":[] },
|
||||
{"name":"getRegistrationLock","parameterTypes":[] }
|
||||
]
|
||||
|
@ -2712,6 +2680,25 @@
|
|||
"allDeclaredMethods":true,
|
||||
"allDeclaredConstructors":true
|
||||
},
|
||||
{
|
||||
"name":"org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataJson",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllDeclaredConstructors":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":["java.lang.String","java.lang.Integer","java.lang.Integer","java.lang.Integer","boolean","java.util.List","boolean"] }]
|
||||
},
|
||||
{
|
||||
"name":"org.whispersystems.signalservice.internal.push.RegistrationSessionRequestBody",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllDeclaredConstructors":true,
|
||||
"methods":[
|
||||
{"name":"getAccountAttributes","parameterTypes":[] },
|
||||
{"name":"getRecoveryPassword","parameterTypes":[] },
|
||||
{"name":"getSessionId","parameterTypes":[] },
|
||||
{"name":"getSkipDeviceTransfer","parameterTypes":[] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"org.whispersystems.signalservice.internal.push.SendGroupMessageResponse",
|
||||
"allDeclaredFields":true,
|
||||
|
@ -2896,16 +2883,6 @@
|
|||
{"name":"timestamp_"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$BodyRange",
|
||||
"fields":[
|
||||
{"name":"associatedValueCase_"},
|
||||
{"name":"associatedValue_"},
|
||||
{"name":"bitField0_"},
|
||||
{"name":"length_"},
|
||||
{"name":"start_"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Contact",
|
||||
"fields":[
|
||||
|
@ -3445,6 +3422,33 @@
|
|||
"queryAllDeclaredConstructors":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.whispersystems.signalservice.internal.push.UpdateVerificationSessionRequestBody",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllDeclaredConstructors":true,
|
||||
"methods":[
|
||||
{"name":"getCaptcha","parameterTypes":[] },
|
||||
{"name":"getMcc","parameterTypes":[] },
|
||||
{"name":"getMnc","parameterTypes":[] },
|
||||
{"name":"getPushChallenge","parameterTypes":[] },
|
||||
{"name":"getPushToken","parameterTypes":[] },
|
||||
{"name":"getPushTokenType","parameterTypes":[] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"org.whispersystems.signalservice.internal.push.VerificationSessionMetadataRequestBody",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllDeclaredConstructors":true,
|
||||
"methods":[
|
||||
{"name":"getMcc","parameterTypes":[] },
|
||||
{"name":"getMnc","parameterTypes":[] },
|
||||
{"name":"getNumber","parameterTypes":[] },
|
||||
{"name":"getPushToken","parameterTypes":[] },
|
||||
{"name":"getPushTokenType","parameterTypes":[] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"org.whispersystems.signalservice.internal.push.VerifyAccountResponse",
|
||||
"allDeclaredFields":true,
|
||||
|
@ -3544,6 +3548,7 @@
|
|||
"name":"org.whispersystems.signalservice.internal.storage.protos.ManifestRecord",
|
||||
"fields":[
|
||||
{"name":"identifiers_"},
|
||||
{"name":"sourceDevice_"},
|
||||
{"name":"version_"}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -4,6 +4,7 @@ import org.asamk.signal.manager.api.CaptchaRequiredException;
|
|||
import org.asamk.signal.manager.api.IncorrectPinException;
|
||||
import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException;
|
||||
import org.asamk.signal.manager.api.PinLockedException;
|
||||
import org.asamk.signal.manager.api.RateLimitException;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
@ -12,7 +13,7 @@ public interface RegistrationManager extends Closeable {
|
|||
|
||||
void register(
|
||||
boolean voiceVerification, String captcha
|
||||
) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException;
|
||||
) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException;
|
||||
|
||||
void verifyAccount(
|
||||
String verificationCode, String pin
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.asamk.signal.manager.api.CaptchaRequiredException;
|
|||
import org.asamk.signal.manager.api.IncorrectPinException;
|
||||
import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException;
|
||||
import org.asamk.signal.manager.api.PinLockedException;
|
||||
import org.asamk.signal.manager.api.RateLimitException;
|
||||
import org.asamk.signal.manager.api.UpdateProfile;
|
||||
import org.asamk.signal.manager.config.ServiceConfig;
|
||||
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
|
||||
|
@ -27,6 +28,7 @@ import org.asamk.signal.manager.helper.AccountFileUpdater;
|
|||
import org.asamk.signal.manager.helper.PinHelper;
|
||||
import org.asamk.signal.manager.storage.SignalAccount;
|
||||
import org.asamk.signal.manager.util.NumberVerificationUtils;
|
||||
import org.asamk.signal.manager.util.Utils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
|
@ -35,15 +37,13 @@ import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
|||
import org.whispersystems.signalservice.api.push.ACI;
|
||||
import org.whispersystems.signalservice.api.push.PNI;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException;
|
||||
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
|
||||
import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.asamk.signal.manager.config.ServiceConfig.capabilities;
|
||||
|
||||
class RegistrationManagerImpl implements RegistrationManager {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(RegistrationManagerImpl.class);
|
||||
|
@ -106,7 +106,7 @@ class RegistrationManagerImpl implements RegistrationManager {
|
|||
@Override
|
||||
public void register(
|
||||
boolean voiceVerification, String captcha
|
||||
) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException {
|
||||
) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException {
|
||||
if (account.isRegistered()
|
||||
&& account.getServiceEnvironment() != null
|
||||
&& account.getServiceEnvironment() != serviceEnvironmentConfig.getType()) {
|
||||
|
@ -117,14 +117,21 @@ class RegistrationManagerImpl implements RegistrationManager {
|
|||
return;
|
||||
}
|
||||
|
||||
NumberVerificationUtils.requestVerificationCode(accountManager, captcha, voiceVerification);
|
||||
String sessionId = NumberVerificationUtils.handleVerificationSession(accountManager,
|
||||
account.getSessionId(account.getNumber()),
|
||||
id -> account.setSessionId(account.getNumber(), id),
|
||||
voiceVerification,
|
||||
captcha);
|
||||
NumberVerificationUtils.requestVerificationCode(accountManager, sessionId, voiceVerification);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyAccount(
|
||||
String verificationCode, String pin
|
||||
) throws IOException, PinLockedException, IncorrectPinException {
|
||||
final var result = NumberVerificationUtils.verifyNumber(verificationCode,
|
||||
var sessionId = account.getSessionId(account.getNumber());
|
||||
final var result = NumberVerificationUtils.verifyNumber(sessionId,
|
||||
verificationCode,
|
||||
pin,
|
||||
pinHelper,
|
||||
this::verifyAccountWithCode);
|
||||
|
@ -186,17 +193,7 @@ class RegistrationManagerImpl implements RegistrationManager {
|
|||
userAgent,
|
||||
null,
|
||||
ServiceConfig.AUTOMATIC_NETWORK_RETRY);
|
||||
accountManager.setAccountAttributes(null,
|
||||
account.getLocalRegistrationId(),
|
||||
true,
|
||||
null,
|
||||
account.getRegistrationLock(),
|
||||
account.getSelfUnidentifiedAccessKey(),
|
||||
account.isUnrestrictedUnidentifiedAccess(),
|
||||
capabilities,
|
||||
account.isDiscoverableByPhoneNumber(),
|
||||
account.getEncryptedDeviceName(),
|
||||
account.getLocalPniRegistrationId());
|
||||
accountManager.setAccountAttributes(account.getAccountAttributes(null));
|
||||
account.setRegistered(true);
|
||||
logger.info("Reactivated existing account, verify is not necessary.");
|
||||
if (newManagerListener != null) {
|
||||
|
@ -215,29 +212,18 @@ class RegistrationManagerImpl implements RegistrationManager {
|
|||
return false;
|
||||
}
|
||||
|
||||
private ServiceResponse<VerifyAccountResponse> verifyAccountWithCode(
|
||||
final String verificationCode, final String registrationLock
|
||||
) {
|
||||
if (registrationLock == null) {
|
||||
return accountManager.verifyAccount(verificationCode,
|
||||
account.getLocalRegistrationId(),
|
||||
true,
|
||||
account.getSelfUnidentifiedAccessKey(),
|
||||
account.isUnrestrictedUnidentifiedAccess(),
|
||||
ServiceConfig.capabilities,
|
||||
account.isDiscoverableByPhoneNumber(),
|
||||
account.getLocalPniRegistrationId());
|
||||
} else {
|
||||
return accountManager.verifyAccountWithRegistrationLockPin(verificationCode,
|
||||
account.getLocalRegistrationId(),
|
||||
true,
|
||||
registrationLock,
|
||||
account.getSelfUnidentifiedAccessKey(),
|
||||
account.isUnrestrictedUnidentifiedAccess(),
|
||||
ServiceConfig.capabilities,
|
||||
account.isDiscoverableByPhoneNumber(),
|
||||
account.getLocalPniRegistrationId());
|
||||
private VerifyAccountResponse verifyAccountWithCode(
|
||||
final String sessionId, final String verificationCode, final String registrationLock
|
||||
) throws IOException {
|
||||
try {
|
||||
Utils.handleResponseException(accountManager.verifyAccount(verificationCode, sessionId));
|
||||
} catch (AlreadyVerifiedException e) {
|
||||
// Already verified so can continue registering
|
||||
}
|
||||
return Utils.handleResponseException(accountManager.registerAccount(sessionId,
|
||||
null,
|
||||
account.getAccountAttributes(registrationLock),
|
||||
true));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -88,6 +88,10 @@ public class SignalDependencies {
|
|||
return serviceEnvironmentConfig;
|
||||
}
|
||||
|
||||
public SignalSessionLock getSessionLock() {
|
||||
return sessionLock;
|
||||
}
|
||||
|
||||
public SignalServiceAccountManager getAccountManager() {
|
||||
return getOrCreate(() -> accountManager,
|
||||
() -> accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
|
||||
|
@ -115,7 +119,7 @@ public class SignalDependencies {
|
|||
|
||||
public GroupsV2Operations getGroupsV2Operations() {
|
||||
return getOrCreate(() -> groupsV2Operations,
|
||||
() -> groupsV2Operations = capabilities.isGv2()
|
||||
() -> groupsV2Operations = capabilities.getGv2()
|
||||
? new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration()),
|
||||
ServiceConfig.GROUP_MAX_SIZE)
|
||||
: null);
|
||||
|
@ -123,7 +127,7 @@ public class SignalDependencies {
|
|||
|
||||
private ClientZkOperations getClientZkOperations() {
|
||||
return getOrCreate(() -> clientZkOperations,
|
||||
() -> clientZkOperations = capabilities.isGv2()
|
||||
() -> clientZkOperations = capabilities.getGv2()
|
||||
? ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration())
|
||||
: null);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,13 @@ package org.asamk.signal.manager.api;
|
|||
|
||||
public class CaptchaRequiredException extends Exception {
|
||||
|
||||
private long nextAttemptTimestamp;
|
||||
|
||||
public CaptchaRequiredException(final long nextAttemptTimestamp) {
|
||||
super("Captcha required");
|
||||
this.nextAttemptTimestamp = nextAttemptTimestamp;
|
||||
}
|
||||
|
||||
public CaptchaRequiredException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
@ -9,4 +16,8 @@ public class CaptchaRequiredException extends Exception {
|
|||
public CaptchaRequiredException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public long getNextAttemptTimestamp() {
|
||||
return nextAttemptTimestamp;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package org.asamk.signal.manager.api;
|
||||
|
||||
public class RateLimitException extends Exception {
|
||||
|
||||
private final long nextAttemptTimestamp;
|
||||
|
||||
public RateLimitException(final long nextAttemptTimestamp) {
|
||||
super("Rate limit");
|
||||
this.nextAttemptTimestamp = nextAttemptTimestamp;
|
||||
}
|
||||
|
||||
public long getNextAttemptTimestamp() {
|
||||
return nextAttemptTimestamp;
|
||||
}
|
||||
}
|
|
@ -7,10 +7,11 @@ import org.asamk.signal.manager.api.IncorrectPinException;
|
|||
import org.asamk.signal.manager.api.InvalidDeviceLinkException;
|
||||
import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException;
|
||||
import org.asamk.signal.manager.api.PinLockedException;
|
||||
import org.asamk.signal.manager.config.ServiceConfig;
|
||||
import org.asamk.signal.manager.api.RateLimitException;
|
||||
import org.asamk.signal.manager.storage.SignalAccount;
|
||||
import org.asamk.signal.manager.util.KeyUtils;
|
||||
import org.asamk.signal.manager.util.NumberVerificationUtils;
|
||||
import org.asamk.signal.manager.util.Utils;
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
|
||||
|
@ -21,6 +22,7 @@ import org.whispersystems.signalservice.api.push.ACI;
|
|||
import org.whispersystems.signalservice.api.push.PNI;
|
||||
import org.whispersystems.signalservice.api.push.ServiceIdType;
|
||||
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
|
||||
import org.whispersystems.signalservice.api.util.DeviceNameUtil;
|
||||
|
@ -128,9 +130,14 @@ public class AccountHelper {
|
|||
|
||||
public void startChangeNumber(
|
||||
String newNumber, String captcha, boolean voiceVerification
|
||||
) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException {
|
||||
) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException {
|
||||
final var accountManager = dependencies.createUnauthenticatedAccountManager(newNumber, account.getPassword());
|
||||
NumberVerificationUtils.requestVerificationCode(accountManager, captcha, voiceVerification);
|
||||
String sessionId = NumberVerificationUtils.handleVerificationSession(accountManager,
|
||||
account.getSessionId(newNumber),
|
||||
id -> account.setSessionId(newNumber, id),
|
||||
voiceVerification,
|
||||
captcha);
|
||||
NumberVerificationUtils.requestVerificationCode(accountManager, sessionId, voiceVerification);
|
||||
}
|
||||
|
||||
public void finishChangeNumber(
|
||||
|
@ -140,17 +147,28 @@ public class AccountHelper {
|
|||
final List<OutgoingPushMessage> deviceMessages = null;
|
||||
final Map<String, SignedPreKeyEntity> devicePniSignedPreKeys = null;
|
||||
final Map<String, Integer> pniRegistrationIds = null;
|
||||
final var result = NumberVerificationUtils.verifyNumber(verificationCode,
|
||||
var sessionId = account.getSessionId(account.getNumber());
|
||||
final var result = NumberVerificationUtils.verifyNumber(sessionId,
|
||||
verificationCode,
|
||||
pin,
|
||||
context.getPinHelper(),
|
||||
(verificationCode1, registrationLock) -> dependencies.getAccountManager()
|
||||
.changeNumber(new ChangePhoneNumberRequest(newNumber,
|
||||
verificationCode1,
|
||||
(sessionId1, verificationCode1, registrationLock) -> {
|
||||
final var accountManager = dependencies.getAccountManager();
|
||||
try {
|
||||
Utils.handleResponseException(accountManager.verifyAccount(verificationCode, sessionId1));
|
||||
} catch (AlreadyVerifiedException e) {
|
||||
// Already verified so can continue changing number
|
||||
}
|
||||
return Utils.handleResponseException(accountManager.changeNumber(new ChangePhoneNumberRequest(
|
||||
sessionId1,
|
||||
null,
|
||||
newNumber,
|
||||
registrationLock,
|
||||
account.getPniIdentityKeyPair().getPublicKey(),
|
||||
deviceMessages,
|
||||
devicePniSignedPreKeys,
|
||||
pniRegistrationIds)));
|
||||
});
|
||||
// TODO handle response
|
||||
updateSelfIdentifiers(newNumber, account.getAci(), PNI.parseOrThrow(result.first().getPni()));
|
||||
}
|
||||
|
@ -162,18 +180,7 @@ public class AccountHelper {
|
|||
}
|
||||
|
||||
public void updateAccountAttributes() throws IOException {
|
||||
dependencies.getAccountManager()
|
||||
.setAccountAttributes(null,
|
||||
account.getLocalRegistrationId(),
|
||||
true,
|
||||
null,
|
||||
account.getRegistrationLock(),
|
||||
account.getSelfUnidentifiedAccessKey(),
|
||||
account.isUnrestrictedUnidentifiedAccess(),
|
||||
ServiceConfig.capabilities,
|
||||
account.isDiscoverableByPhoneNumber(),
|
||||
account.getEncryptedDeviceName(),
|
||||
account.getLocalPniRegistrationId());
|
||||
dependencies.getAccountManager().setAccountAttributes(account.getAccountAttributes(null));
|
||||
}
|
||||
|
||||
public void addDevice(DeviceLinkInfo deviceLinkInfo) throws IOException, InvalidDeviceLinkException {
|
||||
|
|
|
@ -80,7 +80,7 @@ public class IdentityHelper {
|
|||
final var address = account.getRecipientAddressResolver()
|
||||
.resolveRecipientAddress(account.getRecipientResolver().resolveRecipient(serviceId));
|
||||
|
||||
return Utils.computeSafetyNumber(capabilities.isUuid(),
|
||||
return Utils.computeSafetyNumber(capabilities.getUuid(),
|
||||
account.getSelfRecipientAddress(),
|
||||
account.getAciIdentityKeyPair().getPublicKey(),
|
||||
address.getServiceId().equals(serviceId)
|
||||
|
|
|
@ -44,12 +44,14 @@ import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
|
|||
import org.signal.libsignal.metadata.SelfSendException;
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair;
|
||||
import org.signal.libsignal.protocol.InvalidMessageException;
|
||||
import org.signal.libsignal.protocol.groups.GroupSessionBuilder;
|
||||
import org.signal.libsignal.protocol.message.DecryptionErrorMessage;
|
||||
import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.crypto.SignalGroupSessionBuilder;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
|
@ -275,7 +277,8 @@ public final class IncomingMessageHandler {
|
|||
logger.debug("Received a sender key distribution message for distributionId {} from {}",
|
||||
message.getDistributionId(),
|
||||
protocolAddress);
|
||||
dependencies.getMessageSender().processSenderKeyDistributionMessage(protocolAddress, message);
|
||||
new SignalGroupSessionBuilder(dependencies.getSessionLock(),
|
||||
new GroupSessionBuilder(account.getSenderKeyStore())).process(protocolAddress, message);
|
||||
}
|
||||
|
||||
if (content.getDecryptionErrorMessage().isPresent()) {
|
||||
|
|
|
@ -144,22 +144,28 @@ public class ReceiveHelper {
|
|||
logger.debug("Checking for new message from server");
|
||||
try {
|
||||
isWaitingForMessage = true;
|
||||
var result = signalWebSocket.readOrEmpty(timeout.toMillis(), envelope1 -> {
|
||||
var queueNotEmpty = signalWebSocket.readMessageBatch(timeout.toMillis(), 1, batch -> {
|
||||
logger.debug("Retrieved {} envelopes!", batch.size());
|
||||
isWaitingForMessage = false;
|
||||
for (final var it : batch) {
|
||||
SignalServiceEnvelope envelope1 = new SignalServiceEnvelope(it.getEnvelope(),
|
||||
it.getServerDeliveredTimestamp());
|
||||
final var recipientId = envelope1.hasSourceUuid() ? account.getRecipientResolver()
|
||||
.resolveRecipient(envelope1.getSourceAddress()) : null;
|
||||
logger.trace("Storing new message from {}", recipientId);
|
||||
// store message on disk, before acknowledging receipt to the server
|
||||
cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1, recipientId);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
isWaitingForMessage = false;
|
||||
backOffCounter = 0;
|
||||
|
||||
if (result.isPresent()) {
|
||||
if (queueNotEmpty) {
|
||||
if (remainingMessages > 0) {
|
||||
remainingMessages -= 1;
|
||||
}
|
||||
envelope = result.get();
|
||||
envelope = cachedMessage[0].loadEnvelope();
|
||||
logger.debug("New message received from server");
|
||||
} else {
|
||||
logger.debug("Received indicator that server queue is empty");
|
||||
|
|
|
@ -62,6 +62,7 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore;
|
||||
import org.whispersystems.signalservice.api.SignalServiceDataStore;
|
||||
import org.whispersystems.signalservice.api.account.AccountAttributes;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.api.push.ACI;
|
||||
|
@ -97,6 +98,8 @@ import java.util.List;
|
|||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.asamk.signal.manager.config.ServiceConfig.capabilities;
|
||||
|
||||
public class SignalAccount implements Closeable {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(SignalAccount.class);
|
||||
|
@ -119,6 +122,8 @@ public class SignalAccount implements Closeable {
|
|||
private String number;
|
||||
private ACI aci;
|
||||
private PNI pni;
|
||||
private String sessionId;
|
||||
private String sessionNumber;
|
||||
private String encryptedDeviceName;
|
||||
private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
|
||||
private boolean isMultiDevice = false;
|
||||
|
@ -551,6 +556,12 @@ public class SignalAccount implements Closeable {
|
|||
throw new IOException("Config file contains an invalid pni, needs to be a valid UUID", e);
|
||||
}
|
||||
}
|
||||
if (rootNode.hasNonNull("sessionId")) {
|
||||
sessionId = rootNode.get("sessionId").asText();
|
||||
}
|
||||
if (rootNode.hasNonNull("sessionNumber")) {
|
||||
sessionNumber = rootNode.get("sessionNumber").asText();
|
||||
}
|
||||
if (rootNode.hasNonNull("deviceName")) {
|
||||
encryptedDeviceName = rootNode.get("deviceName").asText();
|
||||
}
|
||||
|
@ -926,6 +937,8 @@ public class SignalAccount implements Closeable {
|
|||
.put("serviceEnvironment", serviceEnvironment == null ? null : serviceEnvironment.name())
|
||||
.put("uuid", aci == null ? null : aci.toString())
|
||||
.put("pni", pni == null ? null : pni.toString())
|
||||
.put("sessionId", sessionId)
|
||||
.put("sessionNumber", sessionNumber)
|
||||
.put("deviceName", encryptedDeviceName)
|
||||
.put("deviceId", deviceId)
|
||||
.put("isMultiDevice", isMultiDevice)
|
||||
|
@ -1293,6 +1306,21 @@ public class SignalAccount implements Closeable {
|
|||
save();
|
||||
}
|
||||
|
||||
public AccountAttributes getAccountAttributes(String registrationLock) {
|
||||
return new AccountAttributes(null,
|
||||
getLocalRegistrationId(),
|
||||
true,
|
||||
null,
|
||||
registrationLock != null ? registrationLock : getRegistrationLock(),
|
||||
getSelfUnidentifiedAccessKey(),
|
||||
isUnrestrictedUnidentifiedAccess(),
|
||||
capabilities,
|
||||
isDiscoverableByPhoneNumber(),
|
||||
encryptedDeviceName,
|
||||
getLocalPniRegistrationId(),
|
||||
null); // TODO recoveryPassword?
|
||||
}
|
||||
|
||||
public ServiceId getAccountId(ServiceIdType serviceIdType) {
|
||||
return serviceIdType.equals(ServiceIdType.ACI) ? aci : pni;
|
||||
}
|
||||
|
@ -1347,6 +1375,19 @@ public class SignalAccount implements Closeable {
|
|||
return getRecipientResolver().resolveRecipient(getSelfRecipientAddress());
|
||||
}
|
||||
|
||||
public String getSessionId(final String forNumber) {
|
||||
if (!forNumber.equals(sessionNumber)) {
|
||||
return null;
|
||||
}
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(final String sessionNumber, final String sessionId) {
|
||||
this.sessionNumber = sessionNumber;
|
||||
this.sessionId = sessionId;
|
||||
save();
|
||||
}
|
||||
|
||||
public byte[] getEncryptedDeviceName() {
|
||||
return encryptedDeviceName == null ? null : Base64.getDecoder().decode(encryptedDeviceName);
|
||||
}
|
||||
|
|
|
@ -15,22 +15,31 @@ public final class CachedMessage {
|
|||
|
||||
private final File file;
|
||||
|
||||
private SignalServiceEnvelope envelope;
|
||||
|
||||
CachedMessage(final File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
CachedMessage(final File file, SignalServiceEnvelope envelope) {
|
||||
this.file = file;
|
||||
this.envelope = envelope;
|
||||
}
|
||||
|
||||
File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public SignalServiceEnvelope loadEnvelope() {
|
||||
if (envelope == null) {
|
||||
try {
|
||||
return MessageCacheUtils.loadEnvelope(file);
|
||||
envelope = MessageCacheUtils.loadEnvelope(file);
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to load cached message envelope “{}”: {}", file, e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return envelope;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
try {
|
||||
|
|
|
@ -54,7 +54,7 @@ public class MessageCache {
|
|||
try {
|
||||
var cacheFile = getMessageCacheFile(recipientId, now, envelope.getTimestamp());
|
||||
MessageCacheUtils.storeEnvelope(envelope, cacheFile);
|
||||
return new CachedMessage(cacheFile);
|
||||
return new CachedMessage(cacheFile, envelope);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to store encrypted message in disk cache, ignoring: {}", e.getMessage());
|
||||
return null;
|
||||
|
|
|
@ -5,41 +5,94 @@ import org.asamk.signal.manager.api.IncorrectPinException;
|
|||
import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException;
|
||||
import org.asamk.signal.manager.api.Pair;
|
||||
import org.asamk.signal.manager.api.PinLockedException;
|
||||
import org.asamk.signal.manager.api.RateLimitException;
|
||||
import org.asamk.signal.manager.helper.PinHelper;
|
||||
import org.whispersystems.signalservice.api.KbsPinData;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushChallengeRequiredException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.TokenNotAcceptedException;
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||
import org.whispersystems.signalservice.internal.push.LockedException;
|
||||
import org.whispersystems.signalservice.internal.push.RequestVerificationCodeResponse;
|
||||
import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse;
|
||||
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class NumberVerificationUtils {
|
||||
|
||||
public static String handleVerificationSession(
|
||||
SignalServiceAccountManager accountManager,
|
||||
String sessionId,
|
||||
Consumer<String> sessionIdSaver,
|
||||
boolean voiceVerification,
|
||||
String captcha
|
||||
) throws CaptchaRequiredException, IOException, RateLimitException {
|
||||
RegistrationSessionMetadataResponse sessionResponse;
|
||||
try {
|
||||
sessionResponse = getValidSession(accountManager, sessionId);
|
||||
} catch (PushChallengeRequiredException |
|
||||
org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException e) {
|
||||
if (captcha != null) {
|
||||
sessionResponse = submitCaptcha(accountManager, sessionId, captcha);
|
||||
} else {
|
||||
throw new CaptchaRequiredException("Captcha Required");
|
||||
}
|
||||
}
|
||||
|
||||
sessionId = sessionResponse.getBody().getId();
|
||||
sessionIdSaver.accept(sessionId);
|
||||
|
||||
if (sessionResponse.getBody().getVerified()) {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
if (sessionResponse.getBody().getAllowedToRequestCode()) {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
final var nextAttempt = voiceVerification
|
||||
? sessionResponse.getBody().getNextCall()
|
||||
: sessionResponse.getBody().getNextSms();
|
||||
if (nextAttempt != null && nextAttempt > 0) {
|
||||
final var timestamp = sessionResponse.getHeaders().getTimestamp() + nextAttempt * 1000;
|
||||
throw new RateLimitException(timestamp);
|
||||
}
|
||||
|
||||
final var nextVerificationAttempt = sessionResponse.getBody().getNextVerificationAttempt();
|
||||
if (nextVerificationAttempt != null && nextVerificationAttempt > 0) {
|
||||
final var timestamp = sessionResponse.getHeaders().getTimestamp() + nextVerificationAttempt * 1000;
|
||||
throw new CaptchaRequiredException(timestamp);
|
||||
}
|
||||
|
||||
if (sessionResponse.getBody().getRequestedInformation().contains("captcha")) {
|
||||
if (captcha != null) {
|
||||
sessionResponse = submitCaptcha(accountManager, sessionId, captcha);
|
||||
}
|
||||
if (!sessionResponse.getBody().getAllowedToRequestCode()) {
|
||||
throw new CaptchaRequiredException("Captcha Required");
|
||||
}
|
||||
}
|
||||
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public static void requestVerificationCode(
|
||||
SignalServiceAccountManager accountManager, String captcha, boolean voiceVerification
|
||||
SignalServiceAccountManager accountManager, String sessionId, boolean voiceVerification
|
||||
) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException {
|
||||
captcha = captcha == null ? null : captcha.replace("signalcaptcha://", "");
|
||||
final ServiceResponse<RequestVerificationCodeResponse> response;
|
||||
final ServiceResponse<RegistrationSessionMetadataResponse> response;
|
||||
final var locale = Utils.getDefaultLocale(Locale.US);
|
||||
if (voiceVerification) {
|
||||
response = accountManager.requestVoiceVerificationCode(locale,
|
||||
Optional.ofNullable(captcha),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
response = accountManager.requestVoiceVerificationCode(sessionId, locale, false);
|
||||
} else {
|
||||
response = accountManager.requestSmsVerificationCode(locale,
|
||||
false,
|
||||
Optional.ofNullable(captcha),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
response = accountManager.requestSmsVerificationCode(sessionId, locale, false);
|
||||
}
|
||||
try {
|
||||
handleResponseException(response);
|
||||
Utils.handleResponseException(response);
|
||||
} catch (org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException e) {
|
||||
throw new CaptchaRequiredException(e.getMessage(), e);
|
||||
} catch (org.whispersystems.signalservice.api.push.exceptions.NonNormalizedPhoneNumberException e) {
|
||||
|
@ -51,11 +104,11 @@ public class NumberVerificationUtils {
|
|||
}
|
||||
|
||||
public static Pair<VerifyAccountResponse, MasterKey> verifyNumber(
|
||||
String verificationCode, String pin, PinHelper pinHelper, Verifier verifier
|
||||
String sessionId, String verificationCode, String pin, PinHelper pinHelper, Verifier verifier
|
||||
) throws IOException, PinLockedException, IncorrectPinException {
|
||||
verificationCode = verificationCode.replace("-", "");
|
||||
try {
|
||||
final var response = verifyAccountWithCode(verificationCode, null, verifier);
|
||||
final var response = verifier.verify(sessionId, verificationCode, null);
|
||||
|
||||
return new Pair<>(response, null);
|
||||
} catch (LockedException e) {
|
||||
|
@ -72,7 +125,7 @@ public class NumberVerificationUtils {
|
|||
var registrationLock = registrationLockData.getMasterKey().deriveRegistrationLock();
|
||||
VerifyAccountResponse response;
|
||||
try {
|
||||
response = verifyAccountWithCode(verificationCode, registrationLock, verifier);
|
||||
response = verifier.verify(sessionId, verificationCode, registrationLock);
|
||||
} catch (LockedException _e) {
|
||||
throw new AssertionError("KBS Pin appeared to matched but reg lock still failed!");
|
||||
}
|
||||
|
@ -81,29 +134,53 @@ public class NumberVerificationUtils {
|
|||
}
|
||||
}
|
||||
|
||||
private static VerifyAccountResponse verifyAccountWithCode(
|
||||
final String verificationCode, final String registrationLock, final Verifier verifier
|
||||
private static RegistrationSessionMetadataResponse validateSession(
|
||||
final SignalServiceAccountManager accountManager, final String sessionId
|
||||
) throws IOException {
|
||||
final var response = verifier.verify(verificationCode, registrationLock);
|
||||
handleResponseException(response);
|
||||
return response.getResult().get();
|
||||
if (sessionId == null || sessionId.isEmpty()) {
|
||||
throw new NoSuchSessionException();
|
||||
}
|
||||
return Utils.handleResponseException(accountManager.getRegistrationSession(sessionId));
|
||||
}
|
||||
|
||||
private static void handleResponseException(final ServiceResponse<?> response) throws IOException {
|
||||
final var throwableOptional = response.getExecutionError().or(response::getApplicationError);
|
||||
if (throwableOptional.isPresent()) {
|
||||
if (throwableOptional.get() instanceof IOException) {
|
||||
throw (IOException) throwableOptional.get();
|
||||
} else {
|
||||
throw new IOException(throwableOptional.get());
|
||||
private static RegistrationSessionMetadataResponse requestValidSession(
|
||||
final SignalServiceAccountManager accountManager
|
||||
) throws NoSuchSessionException, IOException {
|
||||
return Utils.handleResponseException(accountManager.createRegistrationSession(null, "", ""));
|
||||
}
|
||||
|
||||
private static RegistrationSessionMetadataResponse getValidSession(
|
||||
final SignalServiceAccountManager accountManager, final String sessionId
|
||||
) throws IOException {
|
||||
try {
|
||||
return validateSession(accountManager, sessionId);
|
||||
} catch (NoSuchSessionException e) {
|
||||
return requestValidSession(accountManager);
|
||||
}
|
||||
}
|
||||
|
||||
private static RegistrationSessionMetadataResponse submitCaptcha(
|
||||
SignalServiceAccountManager accountManager, String sessionId, String captcha
|
||||
) throws IOException, CaptchaRequiredException {
|
||||
captcha = captcha == null ? null : captcha.replace("signalcaptcha://", "");
|
||||
try {
|
||||
return Utils.handleResponseException(accountManager.submitCaptchaToken(sessionId, captcha));
|
||||
} catch (PushChallengeRequiredException |
|
||||
org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException |
|
||||
TokenNotAcceptedException _e) {
|
||||
throw new CaptchaRequiredException("Captcha not accepted");
|
||||
} catch (NonSuccessfulResponseCodeException e) {
|
||||
if (e.getCode() == 400) {
|
||||
throw new CaptchaRequiredException("Captcha has invalid format");
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public interface Verifier {
|
||||
|
||||
ServiceResponse<VerifyAccountResponse> verify(
|
||||
String verificationCode, String registrationLock
|
||||
);
|
||||
VerifyAccountResponse verify(
|
||||
String sessionId, String verificationCode, String registrationLock
|
||||
) throws IOException;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import org.signal.libsignal.protocol.fingerprint.NumericFingerprintGenerator;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
|
@ -128,4 +129,16 @@ public class Utils {
|
|||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public static <T> T handleResponseException(final ServiceResponse<T> response) throws IOException {
|
||||
final var throwableOptional = response.getExecutionError().or(response::getApplicationError);
|
||||
if (throwableOptional.isPresent()) {
|
||||
if (throwableOptional.get() instanceof IOException) {
|
||||
throw (IOException) throwableOptional.get();
|
||||
} else {
|
||||
throw new IOException(throwableOptional.get());
|
||||
}
|
||||
}
|
||||
return response.getResult().orElse(null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ dependencyResolutionManagement {
|
|||
library("logback", "ch.qos.logback", "logback-classic").version("1.4.5")
|
||||
|
||||
|
||||
library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_67")
|
||||
library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_68")
|
||||
library("protobuf", "com.google.protobuf", "protobuf-javalite").version("3.22.0")
|
||||
library("sqlite", "org.xerial", "sqlite-jdbc").version("3.40.1.0")
|
||||
library("hikari", "com.zaxxer", "HikariCP").version("5.0.1")
|
||||
|
|
|
@ -13,7 +13,9 @@ import org.asamk.signal.commands.exceptions.UserErrorException;
|
|||
import org.asamk.signal.manager.RegistrationManager;
|
||||
import org.asamk.signal.manager.api.CaptchaRequiredException;
|
||||
import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException;
|
||||
import org.asamk.signal.manager.api.RateLimitException;
|
||||
import org.asamk.signal.output.JsonWriter;
|
||||
import org.asamk.signal.util.DateUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
@ -65,6 +67,12 @@ public class RegisterCommand implements RegistrationCommand, JsonRpcRegistration
|
|||
) throws UserErrorException, IOErrorException {
|
||||
try {
|
||||
m.register(voiceVerification, captcha);
|
||||
} catch (RateLimitException e) {
|
||||
String message = "Rate limit reached";
|
||||
if (e.getNextAttemptTimestamp() > 0) {
|
||||
message += "\nNext attempt may be tried at " + DateUtils.formatTimestamp(e.getNextAttemptTimestamp());
|
||||
}
|
||||
throw new UserErrorException(message);
|
||||
} catch (CaptchaRequiredException e) {
|
||||
String message;
|
||||
if (captcha == null) {
|
||||
|
@ -76,6 +84,10 @@ public class RegisterCommand implements RegistrationCommand, JsonRpcRegistration
|
|||
} else {
|
||||
message = "Invalid captcha given.";
|
||||
}
|
||||
if (e.getNextAttemptTimestamp() > 0) {
|
||||
message += "\nNext Captcha may be provided at "
|
||||
+ DateUtils.formatTimestamp(e.getNextAttemptTimestamp());
|
||||
}
|
||||
throw new UserErrorException(message);
|
||||
} catch (NonNormalizedPhoneNumberException e) {
|
||||
throw new UserErrorException("Failed to register: " + e.getMessage(), e);
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.asamk.signal.manager.api.CaptchaRequiredException;
|
|||
import org.asamk.signal.manager.api.IncorrectPinException;
|
||||
import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException;
|
||||
import org.asamk.signal.manager.api.PinLockedException;
|
||||
import org.asamk.signal.manager.api.RateLimitException;
|
||||
import org.asamk.signal.manager.api.UserAlreadyExistsException;
|
||||
import org.freedesktop.dbus.DBusPath;
|
||||
|
||||
|
@ -59,6 +60,9 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl {
|
|||
}
|
||||
try (final RegistrationManager registrationManager = c.getNewRegistrationManager(number)) {
|
||||
registrationManager.register(voiceVerification, captcha);
|
||||
} catch (RateLimitException e) {
|
||||
String message = "Rate limit reached";
|
||||
throw new SignalControl.Error.Failure(message);
|
||||
} catch (CaptchaRequiredException e) {
|
||||
String message = captcha == null ? "Captcha required for verification." : "Invalid captcha given.";
|
||||
throw new SignalControl.Error.RequiresCaptcha(message);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue