2
0

Improved signin/signout state logic.

This commit is contained in:
Maarten Billemont 2012-06-08 00:40:30 +02:00
parent f796888901
commit 09d5e64c55
16 changed files with 259 additions and 181 deletions

View File

@ -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;

View File

@ -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))];
}

View File

@ -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"

View File

@ -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>

View File

@ -14,8 +14,8 @@
+ (MPAppDelegate *)get;
- (void)checkConfig;
- (void)showGuide;
- (void)loadKey:(BOOL)animated;
- (void)export;
- (void)changeMP;

View File

@ -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];
}

View File

@ -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];

View File

@ -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;
}
}

View File

@ -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

View File

@ -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];

View File

@ -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>

View File

@ -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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

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

View File

@ -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>