From bc6a4ea8d82ff1f957109f81f1860ff19635c81e Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 18 Jun 2018 10:54:06 -0400 Subject: [PATCH] Add re-registration UI. --- .../src/ViewControllers/DebugUI/DebugUIMisc.m | 15 ++-- .../HomeView/HomeViewController.m | 81 ++++++++++++++++++- .../CodeVerificationViewController.m | 29 ++++--- .../Registration/RegistrationViewController.m | 60 ++++++++++++++ .../translations/en.lproj/Localizable.strings | 9 ++- .../src/Account/TSAccountManager.h | 19 +++-- .../src/Account/TSAccountManager.m | 76 ++++++++++++----- 7 files changed, 239 insertions(+), 50 deletions(-) diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m b/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m index 80609c4ba..6b3232ed8 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m @@ -24,14 +24,6 @@ NS_ASSUME_NONNULL_BEGIN -@interface TSAccountManager (DebugUI) - -- (void)resetForRegistration; - -@end - -#pragma mark - - @interface OWSStorage (DebugUI) - (NSData *)databasePassword; @@ -147,7 +139,12 @@ NS_ASSUME_NONNULL_BEGIN + (void)reregister { DDLogInfo(@"%@ re-registering.", self.logTag); - [[TSAccountManager sharedInstance] resetForRegistration]; + + if (![[TSAccountManager sharedInstance] resetForReregistration]) { + OWSFail(@"%@ could not reset for re-registration.", self.logTag); + return; + } + [[Environment current].preferences unsetRecordedAPNSTokens]; RegistrationViewController *viewController = [RegistrationViewController new]; diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index 9f62b3cdf..56406706b 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -5,12 +5,14 @@ #import "HomeViewController.h" #import "AppDelegate.h" #import "AppSettingsViewController.h" +#import "CodeVerificationViewController.h" #import "HomeViewCell.h" #import "NewContactThreadViewController.h" #import "OWSNavigationController.h" #import "OWSPrimaryStorage.h" #import "ProfileViewController.h" #import "PushManager.h" +#import "RegistrationViewController.h" #import "Signal-Swift.h" #import "SignalApp.h" #import "TSAccountManager.h" @@ -210,11 +212,12 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations [SignalApp.sharedApp setHomeViewController:self]; } + __weak HomeViewController *weakSelf = self; ReminderView *deregisteredView = - [ReminderView nagWithText:NSLocalizedString(@"INBOX_VIEW_DEREGISTRATION_WARNING", + [ReminderView nagWithText:NSLocalizedString(@"DEREGISTRATION_WARNING", @"Label warning the user that they have been de-registered.") tapAction:^{ - // TODO: + [weakSelf showReRegistrationUI]; }]; [self.view addSubview:deregisteredView]; [deregisteredView autoPinWidthToSuperview]; @@ -1407,6 +1410,80 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations _emptyBoxLabel.attributedText = fullLabelString; } +- (void)showReRegistrationUI +{ + UIAlertController *actionSheetController = + [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + + __weak HomeViewController *weakSelf = self; + [actionSheetController + addAction:[UIAlertAction + actionWithTitle:NSLocalizedString(@"DEREGISTRATION_REREGISTER_WITH_SAME_PHONE_NUMBER", + @"Label for button that lets users re-register using the same phone number.") + style:UIAlertActionStyleDestructive + handler:^(UIAlertAction *action) { + [weakSelf reregisterWithSamePhoneNumber]; + }]]; + + [actionSheetController addAction:[OWSAlerts cancelAction]]; + + [self presentViewController:actionSheetController animated:YES completion:nil]; +} + +- (void)reregisterWithSamePhoneNumber +{ + DDLogInfo(@"%@ reregisterWithSamePhoneNumber.", self.logTag); + + if (![[TSAccountManager sharedInstance] resetForReregistration]) { + OWSFail(@"%@ could not reset for re-registration.", self.logTag); + return; + } + + [[Environment current].preferences unsetRecordedAPNSTokens]; + + [ModalActivityIndicatorViewController + presentFromViewController:self + canCancel:NO + backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) { + [TSAccountManager + registerWithPhoneNumber:[TSAccountManager sharedInstance].reregisterationPhoneNumber + success:^{ + DDLogInfo(@"%@ re-registering: send verification code succeeded.", self.logTag); + + dispatch_async(dispatch_get_main_queue(), ^{ + [modalActivityIndicator dismissWithCompletion:^{ + CodeVerificationViewController *viewController = + [CodeVerificationViewController new]; + + OWSNavigationController *navigationController = + [[OWSNavigationController alloc] initWithRootViewController:viewController]; + navigationController.navigationBarHidden = YES; + + [UIApplication sharedApplication].delegate.window.rootViewController + = navigationController; + }]; + }); + } + failure:^(NSError *error) { + DDLogError(@"%@ re-registering: send verification code failed.", self.logTag); + + dispatch_async(dispatch_get_main_queue(), ^{ + [modalActivityIndicator dismissWithCompletion:^{ + if (error.code == 400) { + [OWSAlerts showAlertWithTitle:NSLocalizedString(@"REGISTRATION_ERROR", nil) + message:NSLocalizedString( + @"REGISTRATION_NON_VALID_NUMBER", nil)]; + } else { + [OWSAlerts showAlertWithTitle:error.localizedDescription + message:error.localizedRecoverySuggestion]; + } + }]; + }); + } + smsVerification:YES]; + }]; +} + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/Registration/CodeVerificationViewController.m b/Signal/src/ViewControllers/Registration/CodeVerificationViewController.m index 9124b9024..0cc01b497 100644 --- a/Signal/src/ViewControllers/Registration/CodeVerificationViewController.m +++ b/Signal/src/ViewControllers/Registration/CodeVerificationViewController.m @@ -112,16 +112,25 @@ NS_ASSUME_NONNULL_BEGIN [titleLabel autoSetDimension:ALDimensionHeight toSize:40]; [titleLabel autoHCenterInSuperview]; - UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom]; - [backButton - setTitle:NSLocalizedString(@"VERIFICATION_BACK_BUTTON", @"button text for back button on verification view") - forState:UIControlStateNormal]; - [backButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; - backButton.titleLabel.font = [UIFont ows_mediumFontWithSize:14.f]; - [header addSubview:backButton]; - [backButton autoPinLeadingToSuperviewMarginWithInset:10.f]; - [backButton autoAlignAxis:ALAxisHorizontal toSameAxisOfView:titleLabel]; - [backButton addTarget:self action:@selector(backButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + // This view is used in more than one context. + // + // * Usually, it is pushed atop RegistrationViewController in which + // case we want a "back" button. + // * It can also be used to re-register from the app's "de-registration" + // views, in which case RegistrationViewController is not used and we + // do _not_ want a "back" button. + if (self.navigationController.viewControllers.count > 1) { + UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [backButton + setTitle:NSLocalizedString(@"VERIFICATION_BACK_BUTTON", @"button text for back button on verification view") + forState:UIControlStateNormal]; + [backButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; + backButton.titleLabel.font = [UIFont ows_mediumFontWithSize:14.f]; + [header addSubview:backButton]; + [backButton autoPinLeadingToSuperviewMarginWithInset:10.f]; + [backButton autoAlignAxis:ALAxisHorizontal toSameAxisOfView:titleLabel]; + [backButton addTarget:self action:@selector(backButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + } _phoneNumberLabel = [UILabel new]; _phoneNumberLabel.textColor = [UIColor ows_darkGrayColor]; diff --git a/Signal/src/ViewControllers/Registration/RegistrationViewController.m b/Signal/src/ViewControllers/Registration/RegistrationViewController.m index ecc463ed3..48bb9d6b9 100644 --- a/Signal/src/ViewControllers/Registration/RegistrationViewController.m +++ b/Signal/src/ViewControllers/Registration/RegistrationViewController.m @@ -278,6 +278,61 @@ NSString *const kKeychainKey_LastRegisteredPhoneNumber = @"kKeychainKey_LastRegi [self.activateButton setEnabled:YES]; [self.spinnerView stopAnimating]; [self.phoneNumberTextField becomeFirstResponder]; + + if ([TSAccountManager sharedInstance].isReregistering) { + // If re-registering, pre-populate the country (country code, calling code, country name) + // and phone number state. + NSString *_Nullable phoneNumberE164 = [TSAccountManager sharedInstance].reregisterationPhoneNumber; + if (!phoneNumberE164) { + OWSFail(@"%@ Could not resume re-registration; missing phone number.", self.logTag); + } else if ([self tryToApplyPhoneNumberE164:phoneNumberE164]) { + // Don't let user edit their phone number while re-registering. + self.phoneNumberTextField.enabled = NO; + } + } +} + +- (BOOL)tryToApplyPhoneNumberE164:(NSString *)phoneNumberE164 +{ + OWSAssert(phoneNumberE164); + + if (phoneNumberE164.length < 1) { + OWSFail(@"%@ Could not resume re-registration; invalid phoneNumberE164.", self.logTag); + return NO; + } + PhoneNumber *_Nullable parsedPhoneNumber = [PhoneNumber phoneNumberFromE164:phoneNumberE164]; + if (!parsedPhoneNumber) { + OWSFail(@"%@ Could not resume re-registration; couldn't parse phoneNumberE164.", self.logTag); + return NO; + } + NSNumber *_Nullable callingCode = parsedPhoneNumber.getCountryCode; + if (!callingCode) { + OWSFail(@"%@ Could not resume re-registration; missing callingCode.", self.logTag); + return NO; + } + NSString *callingCodeText = [NSString stringWithFormat:@"+%d", callingCode.intValue]; + NSArray *_Nullable countryCodes = + [PhoneNumberUtil.sharedThreadLocal countryCodesFromCallingCode:callingCodeText]; + if (countryCodes.count < 1) { + OWSFail(@"%@ Could not resume re-registration; unknown countryCode.", self.logTag); + return NO; + } + NSString *countryCode = countryCodes.firstObject; + NSString *_Nullable countryName = [PhoneNumberUtil countryNameFromCountryCode:countryCode]; + if (!countryName) { + OWSFail(@"%@ Could not resume re-registration; unknown countryName.", self.logTag); + return NO; + } + if (![phoneNumberE164 hasPrefix:callingCodeText]) { + OWSFail(@"%@ Could not resume re-registration; non-matching calling code.", self.logTag); + return NO; + } + NSString *phoneNumberWithoutCallingCode = [phoneNumberE164 substringFromIndex:callingCodeText.length]; + + [self updateCountryWithName:countryName callingCode:callingCodeText countryCode:countryCode]; + self.phoneNumberTextField.text = phoneNumberWithoutCallingCode; + + return YES; } #pragma mark - Country @@ -385,6 +440,11 @@ NSString *const kKeychainKey_LastRegisteredPhoneNumber = @"kKeychainKey_LastRegi - (void)countryCodeRowWasTapped:(UIGestureRecognizer *)sender { + if (TSAccountManager.sharedInstance.isReregistering) { + // Don't let user edit their phone number while re-registering. + return; + } + if (sender.state == UIGestureRecognizerStateRecognized) { [self changeCountryCodeTapped]; } diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 0c39bc411..f63544a0d 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -647,6 +647,12 @@ /* Title of the alert before redirecting to GitHub Issues. */ "DEBUG_LOG_GITHUB_ISSUE_ALERT_TITLE" = "GitHub Redirection"; +/* Label for button that lets users re-register using the same phone number. */ +"DEREGISTRATION_REREGISTER_WITH_SAME_PHONE_NUMBER" = "Re-register this phone number"; + +/* Label warning the user that they have been de-registered. */ +"DEREGISTRATION_WARNING" = "Device no longer registered! This is likely because you registered your phone number with Signal on a different device. Tap to re-register."; + /* {{Short Date}} when device last communicated with Signal Server. */ "DEVICE_LAST_ACTIVE_AT_LABEL" = "Last active: %@"; @@ -1022,9 +1028,6 @@ /* Label reminding the user that they are in archive mode. */ "INBOX_VIEW_ARCHIVE_MODE_REMINDER" = "These conversations are archived. They will appear in the inbox if new messages are received."; -/* Label warning the user that they have been de-registered. */ -"INBOX_VIEW_DEREGISTRATION_WARNING" = "You are not logged in. Another device may have been registered with your phone number."; - /* Multi-line label explaining how to show names instead of phone numbers in your inbox */ "INBOX_VIEW_MISSING_CONTACTS_PERMISSION" = "To see the names of your contacts, update your system settings to allow contact access."; diff --git a/SignalServiceKit/src/Account/TSAccountManager.h b/SignalServiceKit/src/Account/TSAccountManager.h index 18e1446a2..a60dc410f 100644 --- a/SignalServiceKit/src/Account/TSAccountManager.h +++ b/SignalServiceKit/src/Account/TSAccountManager.h @@ -18,10 +18,7 @@ extern NSString *const kNSNotificationName_LocalNumberDidChange; @interface TSAccountManager : NSObject -// This property is exposed for testing purposes only. -#ifdef DEBUG -@property (nonatomic, nullable) NSString *phoneNumberAwaitingVerification; -#endif +@property (nonatomic, nullable, readonly) NSString *phoneNumberAwaitingVerification; #pragma mark - Initializers @@ -117,12 +114,22 @@ extern NSString *const kNSNotificationName_LocalNumberDidChange; + (void)unregisterTextSecureWithSuccess:(void (^)(void))success failure:(void (^)(NSError *error))failureBlock; -#pragma mark - Deregistration +#pragma mark - De-Registration -// De-registration reflects whether or not the service has received a 403 +// De-registration reflects whether or not the client has received +// a 403 from the service. - (BOOL)isDeregistered; - (void)setIsDeregistered:(BOOL)isDeregistered; +#pragma mark - Re-registration + +// Re-registration is the process of re-registering _with the same phone number_. + +// Returns YES on success. +- (BOOL)resetForReregistration; +- (NSString *)reregisterationPhoneNumber; +- (BOOL)isReregistering; + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Account/TSAccountManager.m b/SignalServiceKit/src/Account/TSAccountManager.m index 3e4d599a8..09c0e19f7 100644 --- a/SignalServiceKit/src/Account/TSAccountManager.m +++ b/SignalServiceKit/src/Account/TSAccountManager.m @@ -29,6 +29,7 @@ NSString *const kNSNotificationName_LocalNumberDidChange = @"kNSNotificationName NSString *const TSAccountManager_RegisteredNumberKey = @"TSStorageRegisteredNumberKey"; NSString *const TSAccountManager_IsDeregisteredKey = @"TSAccountManager_IsDeregisteredKey"; +NSString *const TSAccountManager_ReregisteringPhoneNumberKey = @"TSAccountManager_ReregisteringPhoneNumberKey"; NSString *const TSAccountManager_LocalRegistrationIdKey = @"TSStorageLocalRegistrationId"; NSString *const TSAccountManager_UserAccountCollection = @"TSStorageUserAccountCollection"; @@ -39,10 +40,7 @@ NSString *const TSAccountManager_ServerSignalingKey = @"TSStorageServerSignaling @property (nonatomic, readonly) BOOL isRegistered; -// This property is exposed publicly for testing purposes only. -#ifndef DEBUG @property (nonatomic, nullable) NSString *phoneNumberAwaitingVerification; -#endif @property (nonatomic, nullable) NSString *cachedLocalNumber; @property (nonatomic, readonly) YapDatabaseConnection *dbConnection; @@ -75,6 +73,10 @@ NSString *const TSAccountManager_ServerSignalingKey = @"TSStorageServerSignaling selector:@selector(yapDatabaseModifiedExternally:) name:YapDatabaseModifiedExternallyNotification object:nil]; + + self.phoneNumberAwaitingVerification = + [self.dbConnection stringForKey:TSAccountManager_ReregisteringPhoneNumberKey + inCollection:TSAccountManager_UserAccountCollection]; } return self; @@ -106,22 +108,6 @@ NSString *const TSAccountManager_ServerSignalingKey = @"TSStorageServerSignaling userInfo:nil]; } -- (void)resetForRegistration -{ - @synchronized(self) - { - _isRegistered = NO; - _cachedLocalNumber = nil; - _phoneNumberAwaitingVerification = nil; - _cachedIsDeregistered = nil; - [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [transaction removeAllObjectsInCollection:TSAccountManager_UserAccountCollection]; - - [[OWSPrimaryStorage sharedManager] resetSessionStore:transaction]; - }]; - } -} - + (BOOL)isRegistered { return [[self sharedInstance] isRegistered]; @@ -198,6 +184,11 @@ NSString *const TSAccountManager_ServerSignalingKey = @"TSStorageServerSignaling [self.dbConnection setObject:localNumber forKey:TSAccountManager_RegisteredNumberKey inCollection:TSAccountManager_UserAccountCollection]; + + [self.dbConnection removeObjectForKey:TSAccountManager_ReregisteringPhoneNumberKey + inCollection:TSAccountManager_UserAccountCollection]; + + self.phoneNumberAwaitingVerification = nil; } } @@ -531,7 +522,7 @@ NSString *const TSAccountManager_ServerSignalingKey = @"TSStorageServerSignaling } } -#pragma mark - Deregistration +#pragma mark - De-Registration - (BOOL)isDeregistered { @@ -571,6 +562,51 @@ NSString *const TSAccountManager_ServerSignalingKey = @"TSStorageServerSignaling userInfo:nil]; } +#pragma mark - Re-registration + +- (BOOL)resetForReregistration +{ + @synchronized(self) { + NSString *_Nullable localNumber = self.localNumber; + if (!localNumber) { + OWSFail(@"%@ can't re-register without valid local number.", self.logTag); + return NO; + } + + _isRegistered = NO; + _cachedLocalNumber = nil; + _phoneNumberAwaitingVerification = nil; + _cachedIsDeregistered = nil; + [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [transaction removeAllObjectsInCollection:TSAccountManager_UserAccountCollection]; + + [[OWSPrimaryStorage sharedManager] resetSessionStore:transaction]; + + [transaction setObject:localNumber + forKey:TSAccountManager_ReregisteringPhoneNumberKey + inCollection:TSAccountManager_UserAccountCollection]; + }]; + return YES; + } +} + +- (NSString *)reregisterationPhoneNumber +{ + OWSAssert([self isReregistering]); + + NSString *_Nullable result = [self.dbConnection stringForKey:TSAccountManager_ReregisteringPhoneNumberKey + inCollection:TSAccountManager_UserAccountCollection]; + OWSAssert(result); + return result; +} + +- (BOOL)isReregistering +{ + return nil != + [self.dbConnection stringForKey:TSAccountManager_ReregisteringPhoneNumberKey + inCollection:TSAccountManager_UserAccountCollection]; +} + @end NS_ASSUME_NONNULL_END