Improved signin/signout state logic.
This commit is contained in:
parent
f796888901
commit
09d5e64c55
@ -10,13 +10,11 @@
|
||||
|
||||
@interface MPAppDelegate_Shared (Key)
|
||||
|
||||
- (void)loadSavedKey;
|
||||
- (IBAction)signOut:(id)sender;
|
||||
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password;
|
||||
- (void)signOut;
|
||||
|
||||
- (BOOL)tryMasterPassword:(NSString *)tryPassword forUser:(MPUserEntity *)user;
|
||||
- (void)storeSavedKey;
|
||||
- (void)forgetSavedKey;
|
||||
- (void)unsetKey;
|
||||
- (void)storeSavedKeyFor:(MPUserEntity *)user;
|
||||
- (void)forgetSavedKeyFor:(MPUserEntity *)user;
|
||||
|
||||
- (NSData *)keyWithLength:(NSUInteger)keyLength;
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
@implementation MPAppDelegate_Shared (Key)
|
||||
|
||||
static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
|
||||
|
||||
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"Saved Master Password", (__bridge id)kSecAttrService,
|
||||
@ -21,113 +21,131 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
matches:nil];
|
||||
}
|
||||
|
||||
- (void)forgetSavedKey {
|
||||
|
||||
if ([PearlKeyChain deleteItemForQuery:keyQuery(self.activeUser)] != errSecItemNotFound) {
|
||||
inf(@"Removed key from keychain.");
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPForgotten];
|
||||
#endif
|
||||
- (NSData *)loadSavedKeyFor:(MPUserEntity *)user {
|
||||
|
||||
NSData *key = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
|
||||
if (key)
|
||||
inf(@"Found key (for: %@) in keychain.", user.name);
|
||||
|
||||
else {
|
||||
user.saveKey = NO;
|
||||
inf(@"No key found (for: %@) in keychain.", user.name);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
- (IBAction)signOut:(id)sender {
|
||||
- (void)storeSavedKeyFor:(MPUserEntity *)user {
|
||||
|
||||
[self forgetSavedKey];
|
||||
[self unsetKey];
|
||||
}
|
||||
if (user.saveKey) {
|
||||
NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
|
||||
|
||||
- (void)loadSavedKey {
|
||||
|
||||
if (self.activeUser.saveKey) {
|
||||
// Key should be saved in keychain. Load it.
|
||||
self.key = [PearlKeyChain dataOfItemForQuery:keyQuery(self.activeUser)];
|
||||
inf(@"Looking for key in keychain: %@.", self.key? @"found": @"missing");
|
||||
if (self.key)
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeySet object:self];
|
||||
} else {
|
||||
// Key should not be stored in keychain. Delete it.
|
||||
if ([PearlKeyChain deleteItemForQuery:keyQuery(self.activeUser)] != errSecItemNotFound)
|
||||
inf(@"Removed key from keychain.");
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPUnstored];
|
||||
if (![existingKey isEqualToData:self.key]) {
|
||||
inf(@"Updating key in keychain.");
|
||||
[PearlKeyChain addOrUpdateItemForQuery:keyQuery(user)
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
self.key, (__bridge id) kSecValueData,
|
||||
#if TARGET_OS_IPHONE
|
||||
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id) kSecAttrAccessible,
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)tryMasterPassword:(NSString *)tryPassword forUser:(MPUserEntity *)user {
|
||||
|
||||
if (![tryPassword length])
|
||||
return NO;
|
||||
|
||||
NSData *tryKey = keyForPassword(tryPassword, user.name);
|
||||
NSData *tryKeyID = keyIDForKey(tryKey);
|
||||
inf(@"Key ID was known? %@.", user.keyID? @"YES": @"NO");
|
||||
if (user.keyID) {
|
||||
// A key ID is known -> a master password is set.
|
||||
// Make sure the user's entered master password matches it.
|
||||
if (![user.keyID isEqual:tryKeyID]) {
|
||||
wrn(@"Key ID mismatch. Expected: %@, answer: %@.", [user.keyID encodeHex], [tryKeyID encodeHex]);
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
|
||||
#endif
|
||||
return NO;
|
||||
nil]];
|
||||
}
|
||||
} else {
|
||||
// A key ID is not known -> recording a new master password.
|
||||
user.keyID = tryKeyID;
|
||||
[[MPAppDelegate_Shared get] saveContext];
|
||||
}
|
||||
user.lastUsed = [NSDate date];
|
||||
}
|
||||
|
||||
- (void)forgetSavedKeyFor:(MPUserEntity *)user {
|
||||
|
||||
OSStatus result = [PearlKeyChain deleteItemForQuery:keyQuery(user)];
|
||||
if (result == noErr || result == errSecItemNotFound) {
|
||||
user.saveKey = NO;
|
||||
|
||||
if (result == noErr) {
|
||||
inf(@"Removed key (for: %@) from keychain.", user.name);
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointForgetSavedKey];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)signOut {
|
||||
|
||||
self.key = nil;
|
||||
self.activeUser = nil;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedOut object:self];
|
||||
}
|
||||
|
||||
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password {
|
||||
|
||||
NSData *tryKey = nil;
|
||||
|
||||
// Method 1: When the user has no keyID set, set a new key from the given master password.
|
||||
if (!user.keyID) {
|
||||
if ([password length])
|
||||
if ((tryKey = keyForPassword(password, user.name))) {
|
||||
user.keyID = keyIDForKey(tryKey);
|
||||
[[MPAppDelegate_Shared get] saveContext];
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: Depending on the user's saveKey, load or remove the key from the keychain.
|
||||
if (!user.saveKey)
|
||||
// Key should not be stored in keychain. Delete it.
|
||||
[self forgetSavedKeyFor:user];
|
||||
|
||||
else if (!tryKey) {
|
||||
// Key should be saved in keychain. Load it.
|
||||
if ((tryKey = [self loadSavedKeyFor:user]))
|
||||
if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
|
||||
// Loaded password doesn't match user's keyID. Forget saved password: it is incorrect.
|
||||
tryKey = nil;
|
||||
[self forgetSavedKeyFor:user];
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPEntered];
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
|
||||
#endif
|
||||
|
||||
if (self.key != tryKey) {
|
||||
self.key = tryKey;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeySet object:self];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Method 3: Check the given master password string.
|
||||
if (!tryKey) {
|
||||
if ([password length])
|
||||
if ((tryKey = keyForPassword(password, user.name)))
|
||||
if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
|
||||
tryKey = nil;
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// No more methods left, fail if key still not known.
|
||||
if (!tryKey)
|
||||
return NO;
|
||||
|
||||
if (![self.key isEqualToData:tryKey]) {
|
||||
self.key = tryKey;
|
||||
[self storeSavedKeyFor:user];
|
||||
}
|
||||
|
||||
user.lastUsed = [NSDate date];
|
||||
self.activeUser = user;
|
||||
|
||||
[[MPAppDelegate_Shared get] saveContext];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedIn object:self];
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointSetKey];
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointSignedIn];
|
||||
#endif
|
||||
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)storeSavedKey {
|
||||
|
||||
if (self.activeUser.saveKey) {
|
||||
NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery(self.activeUser)];
|
||||
|
||||
if (![existingKey isEqualToData:self.key]) {
|
||||
inf(@"Updating key in keychain.");
|
||||
[PearlKeyChain addOrUpdateItemForQuery:keyQuery(self.activeUser)
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
self.key, (__bridge id)kSecValueData,
|
||||
#if TARGET_OS_IPHONE
|
||||
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
|
||||
#endif
|
||||
nil]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)unsetKey {
|
||||
|
||||
self.key = nil;
|
||||
self.activeUser = nil;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyUnset object:self];
|
||||
}
|
||||
|
||||
- (NSData *)keyWithLength:(NSUInteger)keyLength {
|
||||
|
||||
|
||||
return [self.key subdataWithRange:NSMakeRange(0, MIN(keyLength, self.key.length))];
|
||||
}
|
||||
|
||||
|
@ -59,22 +59,21 @@ typedef enum {
|
||||
#define MPTestFlightCheckpointDeactivated @"MPTestFlightCheckpointDeactivated"
|
||||
#define MPTestFlightCheckpointTerminated @"MPTestFlightCheckpointTerminated"
|
||||
#define MPTestFlightCheckpointShowGuide @"MPTestFlightCheckpointShowGuide"
|
||||
#define MPTestFlightCheckpointMPForgotten @"MPTestFlightCheckpointMPForgotten"
|
||||
#define MPTestFlightCheckpointMPChanged @"MPTestFlightCheckpointMPChanged"
|
||||
#define MPTestFlightCheckpointMPUnstored @"MPTestFlightCheckpointMPUnstored"
|
||||
#define MPTestFlightCheckpointForgetSavedKey @"MPTestFlightCheckpointForgetSavedKey"
|
||||
#define MPTestFlightCheckpointChangeMP @"MPTestFlightCheckpointChangeMP"
|
||||
#define MPTestFlightCheckpointMPMismatch @"MPTestFlightCheckpointMPMismatch"
|
||||
#define MPTestFlightCheckpointMPEntered @"MPTestFlightCheckpointMPEntered"
|
||||
#define MPTestFlightCheckpointMPValid @"MPTestFlightCheckpointMPValid"
|
||||
#define MPTestFlightCheckpointLocalStoreIncompatible @"MPTestFlightCheckpointLocalStoreIncompatible"
|
||||
#define MPTestFlightCheckpointCloudStoreIncompatible @"MPTestFlightCheckpointCloudStoreIncompatible"
|
||||
#define MPTestFlightCheckpointSetKey @"MPTestFlightCheckpointSetKey"
|
||||
#define MPTestFlightCheckpointSignedIn @"MPTestFlightCheckpointSetKey"
|
||||
#define MPTestFlightCheckpointCloudEnabled @"MPTestFlightCheckpointCloudEnabled"
|
||||
#define MPTestFlightCheckpointCloudDisabled @"MPTestFlightCheckpointCloudDisabled"
|
||||
#define MPTestFlightCheckpointSitesImported @"MPTestFlightCheckpointSitesImported"
|
||||
#define MPTestFlightCheckpointSitesExported @"MPTestFlightCheckpointSitesExported"
|
||||
|
||||
#define MPNotificationStoreUpdated @"MPNotificationStoreUpdated"
|
||||
#define MPNotificationKeySet @"MPNotificationKeySet"
|
||||
#define MPNotificationKeyUnset @"MPNotificationKeyUnset"
|
||||
#define MPNotificationSignedIn @"MPNotificationKeySet"
|
||||
#define MPNotificationSignedOut @"MPNotificationKeyUnset"
|
||||
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
|
||||
#define MPNotificationElementUsed @"MPNotificationElementUsed"
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
|
||||
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
||||
<attribute name="lastUsed" attributeType="Date" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES" isSyncIdentityProperty="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
|
||||
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
||||
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" syncable="YES"/>
|
||||
@ -18,8 +18,8 @@
|
||||
<attribute name="avatar_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
||||
<attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES" isSyncIdentityProperty="YES"/>
|
||||
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO"/>
|
||||
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
|
@ -14,8 +14,8 @@
|
||||
|
||||
+ (MPAppDelegate *)get;
|
||||
|
||||
- (void)checkConfig;
|
||||
- (void)showGuide;
|
||||
- (void)loadKey:(BOOL)animated;
|
||||
|
||||
- (void)export;
|
||||
- (void)changeMP;
|
||||
|
@ -50,6 +50,12 @@
|
||||
return (MPAppDelegate *)[super get];
|
||||
}
|
||||
|
||||
- (void)checkConfig {
|
||||
|
||||
if ([[MPConfig get].iCloud boolValue] != [self.storeManager iCloudEnabled])
|
||||
[self.storeManager useiCloudStore:[[MPConfig get].iCloud boolValue] alertUser:YES];
|
||||
}
|
||||
|
||||
- (void)showGuide {
|
||||
|
||||
[self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self];
|
||||
@ -57,21 +63,6 @@
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointShowGuide];
|
||||
}
|
||||
|
||||
- (void)loadKey:(BOOL)animated {
|
||||
|
||||
if (!self.key)
|
||||
// Try and load the key from the keychain.
|
||||
[self loadSavedKey];
|
||||
|
||||
if (!self.key)
|
||||
// Ask the user to set the key through his master password.
|
||||
PearlMainThread(^{
|
||||
[self.navigationController presentViewController:
|
||||
[self.navigationController.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
|
||||
animated:animated completion:nil];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)export {
|
||||
|
||||
[PearlAlert showNotice:
|
||||
@ -131,10 +122,10 @@
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
return;
|
||||
|
||||
[[MPAppDelegate get] forgetSavedKey];
|
||||
[[MPAppDelegate get] loadKey:YES];
|
||||
self.activeUser.keyID = nil;
|
||||
[self signOut];
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPChanged];
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointChangeMP];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonAbort
|
||||
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
@ -147,12 +138,6 @@
|
||||
[self checkConfig];
|
||||
}
|
||||
|
||||
- (void)checkConfig {
|
||||
|
||||
if ([[MPConfig get].iCloud boolValue] != [self.storeManager iCloudEnabled])
|
||||
[self.storeManager useiCloudStore:[[MPConfig get].iCloud boolValue] alertUser:YES];
|
||||
}
|
||||
|
||||
#pragma mark - UIApplicationDelegate
|
||||
|
||||
|
||||
@ -287,7 +272,11 @@
|
||||
[[UISegmentedControl appearance] setDividerImage:segmentUnselectedUnselected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UISegmentedControl appearance] setDividerImage:segmentSelectedUnselected forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];*/
|
||||
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[self.navigationController performSegueWithIdentifier:@"MP_Unlock" sender:nil];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:kIASKAppSettingChanged object:nil queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[self checkConfig];
|
||||
@ -379,11 +368,7 @@
|
||||
|
||||
if ([[MPiOSConfig get].showQuickStart boolValue])
|
||||
[self showGuide];
|
||||
else {
|
||||
[self loadKey:NO];
|
||||
[self checkConfig];
|
||||
}
|
||||
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointActivated];
|
||||
|
||||
[super applicationDidBecomeActive:application];
|
||||
@ -421,11 +406,9 @@
|
||||
|
||||
[self saveContext];
|
||||
|
||||
if (![[MPiOSConfig get].rememberLogin boolValue]) {
|
||||
[self unsetKey];
|
||||
[self loadKey:NO];
|
||||
}
|
||||
|
||||
if (![[MPiOSConfig get].rememberLogin boolValue])
|
||||
[self signOut];
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointDeactivated];
|
||||
}
|
||||
|
||||
|
@ -32,14 +32,6 @@
|
||||
[MPiOSConfig get].showQuickStart = [NSNumber numberWithBool:NO];
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated {
|
||||
|
||||
[super viewDidDisappear:animated];
|
||||
|
||||
[[MPAppDelegate get] loadKey:animated];
|
||||
}
|
||||
|
||||
|
||||
- (void)viewDidUnload {
|
||||
|
||||
[self setScrollView:nil];
|
||||
|
@ -71,6 +71,8 @@
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
if (![MPAppDelegate get].activeUser)
|
||||
[self.navigationController performSegueWithIdentifier:@"MP_Unlock" sender:self];
|
||||
if (self.activeElement.user != [MPAppDelegate get].activeUser)
|
||||
self.activeElement = nil;
|
||||
self.searchDisplayController.searchBar.text = nil;
|
||||
@ -103,6 +105,8 @@
|
||||
}
|
||||
}];
|
||||
|
||||
[[MPAppDelegate get] checkConfig];
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
@ -220,7 +224,7 @@
|
||||
- (void)webViewDidFinishLoad:(UIWebView *)webView {
|
||||
|
||||
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:
|
||||
PearlString(@"setClass('%@');", ClassNameFromMPElementType(self.activeElement.type))];
|
||||
PearlString(@"setClass('%@');", ClassNameFromMPElementType(self.activeElement.type))];
|
||||
if (error.length)
|
||||
err(@"helpView.setClass: %@", error);
|
||||
}
|
||||
@ -419,8 +423,8 @@
|
||||
case 5:
|
||||
#endif
|
||||
{
|
||||
[[MPAppDelegate get] signOut:self];
|
||||
[[MPAppDelegate get] loadKey:YES];
|
||||
[[MPAppDelegate get] forgetSavedKeyFor:[MPAppDelegate get].activeUser];
|
||||
[[MPAppDelegate get] signOut];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import "MPPreferencesViewController.h"
|
||||
#import "MPAppDelegate.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
|
||||
@interface MPPreferencesViewController ()
|
||||
|
||||
@ -114,7 +115,10 @@
|
||||
|
||||
- (IBAction)didToggleSwitch:(UISwitch *)sender {
|
||||
|
||||
[MPAppDelegate get].activeUser.saveKey = sender.on;
|
||||
if (([MPAppDelegate get].activeUser.saveKey = sender.on))
|
||||
[[MPAppDelegate get] storeSavedKeyFor:[MPAppDelegate get].activeUser];
|
||||
else
|
||||
[[MPAppDelegate get] forgetSavedKeyFor:[MPAppDelegate get].activeUser];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -119,12 +119,6 @@
|
||||
[self.avatarsView autoSizeContentIgnoreHidden:YES ignoreInvisible:YES limitPadding:NO ignoreSubviews:nil];
|
||||
|
||||
[self updateLayoutAnimated:YES allowScroll:YES completion:nil];
|
||||
|
||||
self.deleteTip.alpha = 0;
|
||||
if ([users count] > 1)
|
||||
[UIView animateWithDuration:0.5f animations:^{
|
||||
self.deleteTip.alpha = 1;
|
||||
}];
|
||||
}
|
||||
|
||||
- (UIButton *)setupAvatar:(UIButton *)avatar forUser:(MPUserEntity *)user {
|
||||
@ -169,7 +163,11 @@
|
||||
|
||||
if (!self.selectedUser)
|
||||
[self.passwordField resignFirstResponder];
|
||||
|
||||
else if ([[MPAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:nil]) {
|
||||
[self dismissModalViewControllerAnimated:YES];
|
||||
return;
|
||||
}
|
||||
|
||||
[self updateLayoutAnimated:YES allowScroll:YES completion:^(BOOL finished) {
|
||||
if (finished)
|
||||
if (self.selectedUser)
|
||||
@ -230,6 +228,7 @@
|
||||
self.nameLabel.backgroundColor = [UIColor blackColor];
|
||||
self.oldNameLabel.center = self.nameLabel.center;
|
||||
self.avatarShadowColor = [UIColor whiteColor];
|
||||
self.deleteTip.alpha = 0;
|
||||
} else if (!self.selectedUser && self.passwordView.alpha == 1) {
|
||||
self.passwordView.alpha = 0;
|
||||
self.avatarsView.center = CGPointMake(160, 240);
|
||||
@ -238,6 +237,7 @@
|
||||
self.nameLabel.backgroundColor = [UIColor clearColor];
|
||||
self.oldNameLabel.center = self.nameLabel.center;
|
||||
self.avatarShadowColor = [UIColor lightGrayColor];
|
||||
self.deleteTip.alpha = [self.avatarToUser count] > 2? 1: 0;
|
||||
}
|
||||
|
||||
MPUserEntity *targetedUser = self.selectedUser;
|
||||
@ -294,14 +294,14 @@
|
||||
[self setSpinnerActive:YES];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
BOOL unlocked = [[MPAppDelegate get] tryMasterPassword:self.passwordField.text forUser:self.selectedUser];
|
||||
BOOL unlocked = [[MPAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:self.passwordField.text];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (unlocked) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long) (NSEC_PER_SEC * 0.5f)), dispatch_get_main_queue(), ^{
|
||||
[self dismissModalViewControllerAnimated:YES];
|
||||
});
|
||||
} else
|
||||
} else if (self.passwordField.text.length)
|
||||
[self setPasswordTip:@"Incorrect password."];
|
||||
|
||||
[self setSpinnerActive:NO];
|
||||
|
@ -925,6 +925,7 @@ L4m3P4sSw0rD</string>
|
||||
<connections>
|
||||
<segue destination="PQa-Xl-A3x" kind="relationship" relationship="rootViewController" id="LUg-eF-JQd"/>
|
||||
<segue destination="qz3-eG-aEi" kind="modal" identifier="MP_Guide" id="vyG-wN-8hU"/>
|
||||
<segue destination="Nbn-Rv-sP1" kind="modal" identifier="MP_Unlock" id="6s2-3H-q5S"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
</objects>
|
||||
|
@ -7,7 +7,7 @@ body {
|
||||
|
||||
color: black;
|
||||
|
||||
font: 105% Exo, sans-serif;
|
||||
font: 105% "Hoefler Text", Garamond, Baskerville, "Baskerville Old Face", "Times New Roman", serif;
|
||||
font-weight: 100;
|
||||
}
|
||||
h1, h2, h3, h4 {
|
||||
@ -15,6 +15,11 @@ h1, h2, h3, h4 {
|
||||
|
||||
font-weight: 600;
|
||||
}
|
||||
h1 {
|
||||
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", "Liberation Sans", sans-serif;
|
||||
|
||||
font-weight: 100;
|
||||
}
|
||||
strong {
|
||||
font-weight: 400;
|
||||
}
|
||||
@ -62,12 +67,28 @@ img {
|
||||
/* Classes */
|
||||
.stripe {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.7);
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
border-width: 1px 0;
|
||||
-webkit-box-shadow: inset 0 0 15px rgba(255, 255, 255, 0.5), 2px 2px 6px rgba(200, 200, 200, 0.5);
|
||||
-moz-box-shadow: inset 0 0 15px rgba(255, 255, 255, 0.5), 2px 2px 6px rgba(200, 200, 200, 0.5);
|
||||
box-shadow: inset 0 0 15px rgba(255, 255, 255, 0.5), 2px 2px 6px rgba(200, 200, 200, 0.5);
|
||||
}
|
||||
.button {
|
||||
display: inline-block;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
border: 1px solid rgba(255, 255, 255, 1);
|
||||
border-radius: 5px;
|
||||
-webkit-box-shadow: 1px 1px 6px rgba(200, 200, 200, 0.5);
|
||||
-moz-box-shadow: 1px 1px 6px rgba(200, 200, 200, 0.5);
|
||||
box-shadow: 1px 1px 6px rgba(200, 200, 200, 0.5);
|
||||
padding: 1em;
|
||||
|
||||
text-decoration: none;
|
||||
}
|
||||
.button:hover {
|
||||
background: rgba(240, 240, 240, 0.5);
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
/* Page */
|
||||
header {
|
||||
@ -79,11 +100,15 @@ header {
|
||||
-moz-box-shadow: 0 0 50px #666;
|
||||
box-shadow: 0 0 50px #666;
|
||||
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
margin: 0 0 5em;
|
||||
padding: 1em 0;
|
||||
padding: 1em 0 0;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
@ -111,11 +136,15 @@ header .divider {
|
||||
-moz-box-shadow: 0 0 10px #000;
|
||||
box-shadow: 0 0 10px #000;
|
||||
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
margin: 0 0 5em;
|
||||
padding: 0.5em 0;
|
||||
padding: 10px 0 0;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
@ -127,6 +156,8 @@ header .divider {
|
||||
}
|
||||
header a, header .link, header :link,
|
||||
#fixedheader a, #fixedheader .link, #fixedheader :link {
|
||||
font-family: Exo;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
header a:hover, header .link:hover,
|
||||
@ -187,9 +218,40 @@ blockquote:before {
|
||||
z-index: -1;
|
||||
}
|
||||
.appstore {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
position: fixed;
|
||||
display: none;
|
||||
z-index: 100;
|
||||
font-size: 0;
|
||||
|
||||
/* appstore-bubble.png *
|
||||
top: 8px;
|
||||
right: 5px;
|
||||
*/
|
||||
/* appstore.png */
|
||||
top: 25px;
|
||||
right: 25px;
|
||||
}
|
||||
.appstore img {
|
||||
border-radius: 5px;
|
||||
-webkit-box-shadow: 0 0 30px #AAA;
|
||||
-moz-box-shadow: 0 0 30px #AAA;
|
||||
box-shadow: 0 0 30px #AAA;
|
||||
}
|
||||
.appstore:hover img {
|
||||
-webkit-box-shadow: 0 0 30px #FFF;
|
||||
-moz-box-shadow: 0 0 30px #FFF;
|
||||
box-shadow: 0 0 30px #FFF;
|
||||
}
|
||||
header .appstore {
|
||||
position: absolute;
|
||||
display: block;
|
||||
|
||||
top: auto;
|
||||
/* appstore-bubble.png *
|
||||
bottom: -73px;
|
||||
*/
|
||||
/* appstore.png */
|
||||
bottom: -25px;
|
||||
}
|
||||
.columns {
|
||||
position: relative;
|
||||
|
BIN
Site/img/appstore-bubble.png
Normal file
BIN
Site/img/appstore-bubble.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.1 KiB |
BIN
Site/img/bubble.png
Normal file
BIN
Site/img/bubble.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.0 KiB |
Binary file not shown.
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 139 KiB |
@ -14,6 +14,19 @@
|
||||
<link rel="stylesheet" type="text/css" href="css/screen.css" />
|
||||
|
||||
<script src="js/jquery-1.6.1.min.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$(window).scroll(function() {
|
||||
if ($(window).scrollTop() > 100) {
|
||||
$(".appstore").show();
|
||||
$("header .appstore").hide();
|
||||
} else {
|
||||
$(".appstore").hide();
|
||||
$("header .appstore").show();
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
<script src="js/functions.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
var _gaq = _gaq || [];
|
||||
@ -67,6 +80,7 @@
|
||||
</script>
|
||||
</head>
|
||||
<body id="frontpage">
|
||||
<a class="appstore" href="http://itunes.com/apps/MasterPassword"><img src="img/appstore.png" /></a>
|
||||
<header>
|
||||
|
||||
<a class="appstore" href="http://itunes.com/apps/MasterPassword"><img src="img/appstore.png" /></a>
|
||||
@ -75,7 +89,6 @@
|
||||
|
||||
</header>
|
||||
<div id="fixedheader">
|
||||
<a class="appstore" href="http://itunes.com/apps/MasterPassword"><img src="img/appstore-small.png" /></a>
|
||||
<h2><a href="index.html">Master Password</a></h2>
|
||||
</div>
|
||||
<!--a href="http://bit.ly/vNN5Zi" onclick="_gaq.push(['_trackPageview', '/outbound/testflight']);" id="ribbon"></a-->
|
||||
@ -87,19 +100,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Stop worrying about <em>passwords</em></h1>
|
||||
<h1>Stop worrying<br />
|
||||
about passwords</h1>
|
||||
|
||||
<h2>Memorizing passwords or even saving them in our browser, an application or the cloud just isn't good enough.</h2>
|
||||
<h2>Admit it, you're terrible at memorizing passwords.</h2>
|
||||
|
||||
<p>
|
||||
Master Password is a solution that <strong>voids the need to <em>keep</em> your passwords anywhere</strong>. Not in your head, not on your computer and not in the cloud.
|
||||
</p>
|
||||
<p>
|
||||
Nothing to store means nothing to lose. At the same time it makes sure that your accounts are adequately protected with <em>exclusive</em> passwords.
|
||||
</p>
|
||||
<p>
|
||||
Learn how <a href="#how">below</a>.
|
||||
</p>
|
||||
<p>Just like the rest of the world, your passwords are too simple or reused between many sites. To security specialists or hackers, <em>your accounts are like empty houses with the door wide open</em>.</p>
|
||||
<p>Even the sites you use that hold nothing of value can easily be used by attackers to impersonate you.</p>
|
||||
|
||||
<p><b>Master Password</b> is a <em>stateless solution</em>, which means <strong>your passwords don't need to be saved <em>anywhere</em></strong>. Not in your head, not on your computer and not in the cloud.<br />
|
||||
Nothing to store means nothing to lose. At the same time it makes sure that your accounts are adequately protected with secure and <em>unique</em> passwords.</p>
|
||||
|
||||
<p>Master Password is <b>different</b> from other vault-like password solutions. It helps you set <b>secure passwords</b> for your sites, and at the same time makes <b>losing your passwords almost impossible</b>.</p>
|
||||
|
||||
<p>Built on algorithms such as <a href="http://www.bsdcan.org/2009/schedule/events/147.en.html">scrypt</a> and <a href="http://en.wikipedia.org/wiki/HMAC">HMAC-SHA256</a>, your master password is kept safe even if websites you use get hacked.</p>
|
||||
|
||||
<p>On that topic, if you're not using Master Password yet and <a href="http://www.washingtonpost.com/business/technology/linkedin-eharmony-deal-with-breach-aftermath/2012/06/07/gJQAwqs5KV_story.html">you have a <b>LinkedIn</b> account, you should <em>worry</em> (click for details)</a>.<br />
|
||||
Change your password <em>now</em>, and change it into something secure and unique.</p>
|
||||
|
||||
<hr class="clear" />
|
||||
<!--p>
|
||||
@ -127,7 +144,7 @@
|
||||
</div>
|
||||
<br />
|
||||
</div>
|
||||
<h2>Generates secure passwords</h2>
|
||||
<h2>Creates secure passwords</h2>
|
||||
The application <strong>generates secure, random and unique passwords</strong> in a format that's easy for you to copy.
|
||||
</div>
|
||||
<div>
|
||||
|
Loading…
Reference in New Issue
Block a user