Improved signin/signout state logic.
This commit is contained in:
parent
f796888901
commit
09d5e64c55
@ -10,13 +10,11 @@
|
|||||||
|
|
||||||
@interface MPAppDelegate_Shared (Key)
|
@interface MPAppDelegate_Shared (Key)
|
||||||
|
|
||||||
- (void)loadSavedKey;
|
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password;
|
||||||
- (IBAction)signOut:(id)sender;
|
- (void)signOut;
|
||||||
|
|
||||||
- (BOOL)tryMasterPassword:(NSString *)tryPassword forUser:(MPUserEntity *)user;
|
- (void)storeSavedKeyFor:(MPUserEntity *)user;
|
||||||
- (void)storeSavedKey;
|
- (void)forgetSavedKeyFor:(MPUserEntity *)user;
|
||||||
- (void)forgetSavedKey;
|
|
||||||
- (void)unsetKey;
|
|
||||||
|
|
||||||
- (NSData *)keyWithLength:(NSUInteger)keyLength;
|
- (NSData *)keyWithLength:(NSUInteger)keyLength;
|
||||||
|
|
||||||
|
@ -21,109 +21,127 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
|||||||
matches:nil];
|
matches:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)forgetSavedKey {
|
- (NSData *)loadSavedKeyFor:(MPUserEntity *)user {
|
||||||
|
|
||||||
if ([PearlKeyChain deleteItemForQuery:keyQuery(self.activeUser)] != errSecItemNotFound) {
|
NSData *key = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
|
||||||
inf(@"Removed key from keychain.");
|
if (key)
|
||||||
|
inf(@"Found key (for: %@) in keychain.", user.name);
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
|
else {
|
||||||
#ifdef TESTFLIGHT_SDK_VERSION
|
user.saveKey = NO;
|
||||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPForgotten];
|
inf(@"No key found (for: %@) in keychain.", user.name);
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)signOut:(id)sender {
|
|
||||||
|
|
||||||
[self forgetSavedKey];
|
|
||||||
[self unsetKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (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];
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// A key ID is not known -> recording a new master password.
|
|
||||||
user.keyID = tryKeyID;
|
|
||||||
[[MPAppDelegate_Shared get] saveContext];
|
|
||||||
}
|
|
||||||
user.lastUsed = [NSDate date];
|
|
||||||
|
|
||||||
#ifdef TESTFLIGHT_SDK_VERSION
|
|
||||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPEntered];
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (self.key != tryKey) {
|
|
||||||
self.key = tryKey;
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeySet object:self];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.activeUser = user;
|
return key;
|
||||||
|
|
||||||
#ifdef TESTFLIGHT_SDK_VERSION
|
|
||||||
[TestFlight passCheckpoint:MPTestFlightCheckpointSetKey];
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return YES;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)storeSavedKey {
|
- (void)storeSavedKeyFor:(MPUserEntity *)user {
|
||||||
|
|
||||||
if (self.activeUser.saveKey) {
|
if (user.saveKey) {
|
||||||
NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery(self.activeUser)];
|
NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
|
||||||
|
|
||||||
if (![existingKey isEqualToData:self.key]) {
|
if (![existingKey isEqualToData:self.key]) {
|
||||||
inf(@"Updating key in keychain.");
|
inf(@"Updating key in keychain.");
|
||||||
[PearlKeyChain addOrUpdateItemForQuery:keyQuery(self.activeUser)
|
[PearlKeyChain addOrUpdateItemForQuery:keyQuery(user)
|
||||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
self.key, (__bridge id)kSecValueData,
|
self.key, (__bridge id) kSecValueData,
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
|
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id) kSecAttrAccessible,
|
||||||
#endif
|
#endif
|
||||||
nil]];
|
nil]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)unsetKey {
|
- (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.key = nil;
|
||||||
self.activeUser = nil;
|
self.activeUser = nil;
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyUnset object:self];
|
|
||||||
|
[[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:MPTestFlightCheckpointMPMismatch];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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:MPTestFlightCheckpointSignedIn];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSData *)keyWithLength:(NSUInteger)keyLength {
|
- (NSData *)keyWithLength:(NSUInteger)keyLength {
|
||||||
|
@ -59,22 +59,21 @@ typedef enum {
|
|||||||
#define MPTestFlightCheckpointDeactivated @"MPTestFlightCheckpointDeactivated"
|
#define MPTestFlightCheckpointDeactivated @"MPTestFlightCheckpointDeactivated"
|
||||||
#define MPTestFlightCheckpointTerminated @"MPTestFlightCheckpointTerminated"
|
#define MPTestFlightCheckpointTerminated @"MPTestFlightCheckpointTerminated"
|
||||||
#define MPTestFlightCheckpointShowGuide @"MPTestFlightCheckpointShowGuide"
|
#define MPTestFlightCheckpointShowGuide @"MPTestFlightCheckpointShowGuide"
|
||||||
#define MPTestFlightCheckpointMPForgotten @"MPTestFlightCheckpointMPForgotten"
|
#define MPTestFlightCheckpointForgetSavedKey @"MPTestFlightCheckpointForgetSavedKey"
|
||||||
#define MPTestFlightCheckpointMPChanged @"MPTestFlightCheckpointMPChanged"
|
#define MPTestFlightCheckpointChangeMP @"MPTestFlightCheckpointChangeMP"
|
||||||
#define MPTestFlightCheckpointMPUnstored @"MPTestFlightCheckpointMPUnstored"
|
|
||||||
#define MPTestFlightCheckpointMPMismatch @"MPTestFlightCheckpointMPMismatch"
|
#define MPTestFlightCheckpointMPMismatch @"MPTestFlightCheckpointMPMismatch"
|
||||||
#define MPTestFlightCheckpointMPEntered @"MPTestFlightCheckpointMPEntered"
|
#define MPTestFlightCheckpointMPValid @"MPTestFlightCheckpointMPValid"
|
||||||
#define MPTestFlightCheckpointLocalStoreIncompatible @"MPTestFlightCheckpointLocalStoreIncompatible"
|
#define MPTestFlightCheckpointLocalStoreIncompatible @"MPTestFlightCheckpointLocalStoreIncompatible"
|
||||||
#define MPTestFlightCheckpointCloudStoreIncompatible @"MPTestFlightCheckpointCloudStoreIncompatible"
|
#define MPTestFlightCheckpointCloudStoreIncompatible @"MPTestFlightCheckpointCloudStoreIncompatible"
|
||||||
#define MPTestFlightCheckpointSetKey @"MPTestFlightCheckpointSetKey"
|
#define MPTestFlightCheckpointSignedIn @"MPTestFlightCheckpointSetKey"
|
||||||
#define MPTestFlightCheckpointCloudEnabled @"MPTestFlightCheckpointCloudEnabled"
|
#define MPTestFlightCheckpointCloudEnabled @"MPTestFlightCheckpointCloudEnabled"
|
||||||
#define MPTestFlightCheckpointCloudDisabled @"MPTestFlightCheckpointCloudDisabled"
|
#define MPTestFlightCheckpointCloudDisabled @"MPTestFlightCheckpointCloudDisabled"
|
||||||
#define MPTestFlightCheckpointSitesImported @"MPTestFlightCheckpointSitesImported"
|
#define MPTestFlightCheckpointSitesImported @"MPTestFlightCheckpointSitesImported"
|
||||||
#define MPTestFlightCheckpointSitesExported @"MPTestFlightCheckpointSitesExported"
|
#define MPTestFlightCheckpointSitesExported @"MPTestFlightCheckpointSitesExported"
|
||||||
|
|
||||||
#define MPNotificationStoreUpdated @"MPNotificationStoreUpdated"
|
#define MPNotificationStoreUpdated @"MPNotificationStoreUpdated"
|
||||||
#define MPNotificationKeySet @"MPNotificationKeySet"
|
#define MPNotificationSignedIn @"MPNotificationKeySet"
|
||||||
#define MPNotificationKeyUnset @"MPNotificationKeyUnset"
|
#define MPNotificationSignedOut @"MPNotificationKeyUnset"
|
||||||
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
|
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
|
||||||
#define MPNotificationElementUsed @"MPNotificationElementUsed"
|
#define MPNotificationElementUsed @"MPNotificationElementUsed"
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
|
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
|
||||||
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
||||||
<attribute name="lastUsed" attributeType="Date" 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="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||||
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" 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"/>
|
<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="avatar_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
||||||
<attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
|
<attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||||
<attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
|
<attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
|
||||||
<attribute name="name" attributeType="String" syncable="YES" isSyncIdentityProperty="YES"/>
|
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||||
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO" 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"/>
|
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
|
||||||
</entity>
|
</entity>
|
||||||
<elements>
|
<elements>
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
|
|
||||||
+ (MPAppDelegate *)get;
|
+ (MPAppDelegate *)get;
|
||||||
|
|
||||||
|
- (void)checkConfig;
|
||||||
- (void)showGuide;
|
- (void)showGuide;
|
||||||
- (void)loadKey:(BOOL)animated;
|
|
||||||
|
|
||||||
- (void)export;
|
- (void)export;
|
||||||
- (void)changeMP;
|
- (void)changeMP;
|
||||||
|
@ -50,6 +50,12 @@
|
|||||||
return (MPAppDelegate *)[super get];
|
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 {
|
- (void)showGuide {
|
||||||
|
|
||||||
[self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self];
|
[self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self];
|
||||||
@ -57,21 +63,6 @@
|
|||||||
[TestFlight passCheckpoint:MPTestFlightCheckpointShowGuide];
|
[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 {
|
- (void)export {
|
||||||
|
|
||||||
[PearlAlert showNotice:
|
[PearlAlert showNotice:
|
||||||
@ -131,10 +122,10 @@
|
|||||||
if (buttonIndex == [alert cancelButtonIndex])
|
if (buttonIndex == [alert cancelButtonIndex])
|
||||||
return;
|
return;
|
||||||
|
|
||||||
[[MPAppDelegate get] forgetSavedKey];
|
self.activeUser.keyID = nil;
|
||||||
[[MPAppDelegate get] loadKey:YES];
|
[self signOut];
|
||||||
|
|
||||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPChanged];
|
[TestFlight passCheckpoint:MPTestFlightCheckpointChangeMP];
|
||||||
}
|
}
|
||||||
cancelTitle:[PearlStrings get].commonButtonAbort
|
cancelTitle:[PearlStrings get].commonButtonAbort
|
||||||
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||||
@ -147,12 +138,6 @@
|
|||||||
[self checkConfig];
|
[self checkConfig];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)checkConfig {
|
|
||||||
|
|
||||||
if ([[MPConfig get].iCloud boolValue] != [self.storeManager iCloudEnabled])
|
|
||||||
[self.storeManager useiCloudStore:[[MPConfig get].iCloud boolValue] alertUser:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - UIApplicationDelegate
|
#pragma mark - UIApplicationDelegate
|
||||||
|
|
||||||
|
|
||||||
@ -288,6 +273,10 @@
|
|||||||
[[UISegmentedControl appearance] setDividerImage:segmentSelectedUnselected forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
[[UISegmentedControl appearance] setDividerImage:segmentSelectedUnselected forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||||
[[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected 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
|
[[NSNotificationCenter defaultCenter] addObserverForName:kIASKAppSettingChanged object:nil queue:nil
|
||||||
usingBlock:^(NSNotification *note) {
|
usingBlock:^(NSNotification *note) {
|
||||||
[self checkConfig];
|
[self checkConfig];
|
||||||
@ -379,10 +368,6 @@
|
|||||||
|
|
||||||
if ([[MPiOSConfig get].showQuickStart boolValue])
|
if ([[MPiOSConfig get].showQuickStart boolValue])
|
||||||
[self showGuide];
|
[self showGuide];
|
||||||
else {
|
|
||||||
[self loadKey:NO];
|
|
||||||
[self checkConfig];
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestFlight passCheckpoint:MPTestFlightCheckpointActivated];
|
[TestFlight passCheckpoint:MPTestFlightCheckpointActivated];
|
||||||
|
|
||||||
@ -421,10 +406,8 @@
|
|||||||
|
|
||||||
[self saveContext];
|
[self saveContext];
|
||||||
|
|
||||||
if (![[MPiOSConfig get].rememberLogin boolValue]) {
|
if (![[MPiOSConfig get].rememberLogin boolValue])
|
||||||
[self unsetKey];
|
[self signOut];
|
||||||
[self loadKey:NO];
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestFlight passCheckpoint:MPTestFlightCheckpointDeactivated];
|
[TestFlight passCheckpoint:MPTestFlightCheckpointDeactivated];
|
||||||
}
|
}
|
||||||
|
@ -32,14 +32,6 @@
|
|||||||
[MPiOSConfig get].showQuickStart = [NSNumber numberWithBool:NO];
|
[MPiOSConfig get].showQuickStart = [NSNumber numberWithBool:NO];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidDisappear:(BOOL)animated {
|
|
||||||
|
|
||||||
[super viewDidDisappear:animated];
|
|
||||||
|
|
||||||
[[MPAppDelegate get] loadKey:animated];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
- (void)viewDidUnload {
|
- (void)viewDidUnload {
|
||||||
|
|
||||||
[self setScrollView:nil];
|
[self setScrollView:nil];
|
||||||
|
@ -71,6 +71,8 @@
|
|||||||
|
|
||||||
[super viewWillAppear:animated];
|
[super viewWillAppear:animated];
|
||||||
|
|
||||||
|
if (![MPAppDelegate get].activeUser)
|
||||||
|
[self.navigationController performSegueWithIdentifier:@"MP_Unlock" sender:self];
|
||||||
if (self.activeElement.user != [MPAppDelegate get].activeUser)
|
if (self.activeElement.user != [MPAppDelegate get].activeUser)
|
||||||
self.activeElement = nil;
|
self.activeElement = nil;
|
||||||
self.searchDisplayController.searchBar.text = nil;
|
self.searchDisplayController.searchBar.text = nil;
|
||||||
@ -103,6 +105,8 @@
|
|||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
[[MPAppDelegate get] checkConfig];
|
||||||
|
|
||||||
[super viewDidAppear:animated];
|
[super viewDidAppear:animated];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +224,7 @@
|
|||||||
- (void)webViewDidFinishLoad:(UIWebView *)webView {
|
- (void)webViewDidFinishLoad:(UIWebView *)webView {
|
||||||
|
|
||||||
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:
|
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:
|
||||||
PearlString(@"setClass('%@');", ClassNameFromMPElementType(self.activeElement.type))];
|
PearlString(@"setClass('%@');", ClassNameFromMPElementType(self.activeElement.type))];
|
||||||
if (error.length)
|
if (error.length)
|
||||||
err(@"helpView.setClass: %@", error);
|
err(@"helpView.setClass: %@", error);
|
||||||
}
|
}
|
||||||
@ -419,8 +423,8 @@
|
|||||||
case 5:
|
case 5:
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
[[MPAppDelegate get] signOut:self];
|
[[MPAppDelegate get] forgetSavedKeyFor:[MPAppDelegate get].activeUser];
|
||||||
[[MPAppDelegate get] loadKey:YES];
|
[[MPAppDelegate get] signOut];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#import <QuartzCore/QuartzCore.h>
|
#import <QuartzCore/QuartzCore.h>
|
||||||
#import "MPPreferencesViewController.h"
|
#import "MPPreferencesViewController.h"
|
||||||
#import "MPAppDelegate.h"
|
#import "MPAppDelegate.h"
|
||||||
|
#import "MPAppDelegate_Key.h"
|
||||||
|
|
||||||
@interface MPPreferencesViewController ()
|
@interface MPPreferencesViewController ()
|
||||||
|
|
||||||
@ -114,7 +115,10 @@
|
|||||||
|
|
||||||
- (IBAction)didToggleSwitch:(UISwitch *)sender {
|
- (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
|
@end
|
||||||
|
@ -119,12 +119,6 @@
|
|||||||
[self.avatarsView autoSizeContentIgnoreHidden:YES ignoreInvisible:YES limitPadding:NO ignoreSubviews:nil];
|
[self.avatarsView autoSizeContentIgnoreHidden:YES ignoreInvisible:YES limitPadding:NO ignoreSubviews:nil];
|
||||||
|
|
||||||
[self updateLayoutAnimated:YES allowScroll:YES completion: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 {
|
- (UIButton *)setupAvatar:(UIButton *)avatar forUser:(MPUserEntity *)user {
|
||||||
@ -169,6 +163,10 @@
|
|||||||
|
|
||||||
if (!self.selectedUser)
|
if (!self.selectedUser)
|
||||||
[self.passwordField resignFirstResponder];
|
[self.passwordField resignFirstResponder];
|
||||||
|
else if ([[MPAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:nil]) {
|
||||||
|
[self dismissModalViewControllerAnimated:YES];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
[self updateLayoutAnimated:YES allowScroll:YES completion:^(BOOL finished) {
|
[self updateLayoutAnimated:YES allowScroll:YES completion:^(BOOL finished) {
|
||||||
if (finished)
|
if (finished)
|
||||||
@ -230,6 +228,7 @@
|
|||||||
self.nameLabel.backgroundColor = [UIColor blackColor];
|
self.nameLabel.backgroundColor = [UIColor blackColor];
|
||||||
self.oldNameLabel.center = self.nameLabel.center;
|
self.oldNameLabel.center = self.nameLabel.center;
|
||||||
self.avatarShadowColor = [UIColor whiteColor];
|
self.avatarShadowColor = [UIColor whiteColor];
|
||||||
|
self.deleteTip.alpha = 0;
|
||||||
} else if (!self.selectedUser && self.passwordView.alpha == 1) {
|
} else if (!self.selectedUser && self.passwordView.alpha == 1) {
|
||||||
self.passwordView.alpha = 0;
|
self.passwordView.alpha = 0;
|
||||||
self.avatarsView.center = CGPointMake(160, 240);
|
self.avatarsView.center = CGPointMake(160, 240);
|
||||||
@ -238,6 +237,7 @@
|
|||||||
self.nameLabel.backgroundColor = [UIColor clearColor];
|
self.nameLabel.backgroundColor = [UIColor clearColor];
|
||||||
self.oldNameLabel.center = self.nameLabel.center;
|
self.oldNameLabel.center = self.nameLabel.center;
|
||||||
self.avatarShadowColor = [UIColor lightGrayColor];
|
self.avatarShadowColor = [UIColor lightGrayColor];
|
||||||
|
self.deleteTip.alpha = [self.avatarToUser count] > 2? 1: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
MPUserEntity *targetedUser = self.selectedUser;
|
MPUserEntity *targetedUser = self.selectedUser;
|
||||||
@ -294,14 +294,14 @@
|
|||||||
[self setSpinnerActive:YES];
|
[self setSpinnerActive:YES];
|
||||||
|
|
||||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
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(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
if (unlocked) {
|
if (unlocked) {
|
||||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long) (NSEC_PER_SEC * 0.5f)), dispatch_get_main_queue(), ^{
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long) (NSEC_PER_SEC * 0.5f)), dispatch_get_main_queue(), ^{
|
||||||
[self dismissModalViewControllerAnimated:YES];
|
[self dismissModalViewControllerAnimated:YES];
|
||||||
});
|
});
|
||||||
} else
|
} else if (self.passwordField.text.length)
|
||||||
[self setPasswordTip:@"Incorrect password."];
|
[self setPasswordTip:@"Incorrect password."];
|
||||||
|
|
||||||
[self setSpinnerActive:NO];
|
[self setSpinnerActive:NO];
|
||||||
|
@ -925,6 +925,7 @@ L4m3P4sSw0rD</string>
|
|||||||
<connections>
|
<connections>
|
||||||
<segue destination="PQa-Xl-A3x" kind="relationship" relationship="rootViewController" id="LUg-eF-JQd"/>
|
<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="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>
|
</connections>
|
||||||
</navigationController>
|
</navigationController>
|
||||||
</objects>
|
</objects>
|
||||||
|
@ -7,7 +7,7 @@ body {
|
|||||||
|
|
||||||
color: black;
|
color: black;
|
||||||
|
|
||||||
font: 105% Exo, sans-serif;
|
font: 105% "Hoefler Text", Garamond, Baskerville, "Baskerville Old Face", "Times New Roman", serif;
|
||||||
font-weight: 100;
|
font-weight: 100;
|
||||||
}
|
}
|
||||||
h1, h2, h3, h4 {
|
h1, h2, h3, h4 {
|
||||||
@ -15,6 +15,11 @@ h1, h2, h3, h4 {
|
|||||||
|
|
||||||
font-weight: 600;
|
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 {
|
strong {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
@ -62,12 +67,28 @@ img {
|
|||||||
/* Classes */
|
/* Classes */
|
||||||
.stripe {
|
.stripe {
|
||||||
background: rgba(255, 255, 255, 0.3);
|
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;
|
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);
|
-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);
|
-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);
|
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 */
|
/* Page */
|
||||||
header {
|
header {
|
||||||
@ -79,11 +100,15 @@ header {
|
|||||||
-moz-box-shadow: 0 0 50px #666;
|
-moz-box-shadow: 0 0 50px #666;
|
||||||
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;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 150px;
|
||||||
margin: 0 0 5em;
|
margin: 0 0 5em;
|
||||||
padding: 1em 0;
|
padding: 1em 0 0;
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@ -111,11 +136,15 @@ header .divider {
|
|||||||
-moz-box-shadow: 0 0 10px #000;
|
-moz-box-shadow: 0 0 10px #000;
|
||||||
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;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
margin: 0 0 5em;
|
margin: 0 0 5em;
|
||||||
padding: 0.5em 0;
|
padding: 10px 0 0;
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@ -127,6 +156,8 @@ header .divider {
|
|||||||
}
|
}
|
||||||
header a, header .link, header :link,
|
header a, header .link, header :link,
|
||||||
#fixedheader a, #fixedheader .link, #fixedheader :link {
|
#fixedheader a, #fixedheader .link, #fixedheader :link {
|
||||||
|
font-family: Exo;
|
||||||
|
font-weight: 700;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
header a:hover, header .link:hover,
|
header a:hover, header .link:hover,
|
||||||
@ -187,9 +218,40 @@ blockquote:before {
|
|||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
.appstore {
|
.appstore {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
right: 10px;
|
display: none;
|
||||||
|
z-index: 100;
|
||||||
font-size: 0;
|
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 {
|
.columns {
|
||||||
position: relative;
|
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" />
|
<link rel="stylesheet" type="text/css" href="css/screen.css" />
|
||||||
|
|
||||||
<script src="js/jquery-1.6.1.min.js" type="text/javascript"></script>
|
<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 src="js/functions.js" type="text/javascript"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var _gaq = _gaq || [];
|
var _gaq = _gaq || [];
|
||||||
@ -67,6 +80,7 @@
|
|||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body id="frontpage">
|
<body id="frontpage">
|
||||||
|
<a class="appstore" href="http://itunes.com/apps/MasterPassword"><img src="img/appstore.png" /></a>
|
||||||
<header>
|
<header>
|
||||||
|
|
||||||
<a class="appstore" href="http://itunes.com/apps/MasterPassword"><img src="img/appstore.png" /></a>
|
<a class="appstore" href="http://itunes.com/apps/MasterPassword"><img src="img/appstore.png" /></a>
|
||||||
@ -75,7 +89,6 @@
|
|||||||
|
|
||||||
</header>
|
</header>
|
||||||
<div id="fixedheader">
|
<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>
|
<h2><a href="index.html">Master Password</a></h2>
|
||||||
</div>
|
</div>
|
||||||
<!--a href="http://bit.ly/vNN5Zi" onclick="_gaq.push(['_trackPageview', '/outbound/testflight']);" id="ribbon"></a-->
|
<!--a href="http://bit.ly/vNN5Zi" onclick="_gaq.push(['_trackPageview', '/outbound/testflight']);" id="ribbon"></a-->
|
||||||
@ -87,19 +100,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
<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>
|
||||||
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>Even the sites you use that hold nothing of value can easily be used by attackers to impersonate you.</p>
|
||||||
</p>
|
|
||||||
<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 <em>exclusive</em> passwords.
|
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>
|
|
||||||
<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>
|
||||||
Learn how <a href="#how">below</a>.
|
|
||||||
</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" />
|
<hr class="clear" />
|
||||||
<!--p>
|
<!--p>
|
||||||
@ -127,7 +144,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</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.
|
The application <strong>generates secure, random and unique passwords</strong> in a format that's easy for you to copy.
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
Loading…
Reference in New Issue
Block a user