Browse Source

authentication overhaul

Nathaniel van Diepen 10 years ago
parent
commit
745515ed45
14 changed files with 203 additions and 87 deletions
  1. 31 9
      api.php
  2. 13 1
      css/style.css
  3. 7 6
      data/pages/index.template
  4. 1 0
      data/pages/login.template
  5. 15 0
      data/pages/user.template
  6. BIN
      img/load.gif
  7. 1 4
      index.php
  8. 1 0
      install/install.sql
  9. 66 40
      js/index.js
  10. 5 1
      php/database.php
  11. 7 5
      php/functions.php
  12. 5 0
      php/include.php
  13. 33 9
      php/security.php
  14. 18 12
      php/user.php

+ 31 - 9
api.php

@@ -8,7 +8,18 @@
 			$id = $_GET['id'];
 			switch($_GET['type']){
 				case 'user':
-					// TODO - handle user requests
+					$ret['template'] = file_get_contents(PATH_DATA.'pages/user.template');
+					$user = userObj($id);
+					$context = Array(
+						'name'=>$user['name'],
+						'email'=>$user['email']
+					);
+					if($LOGGEDIN){
+						$context['key'] = true;
+						$context['user'] = userObj($_SESSION['username']);
+					};
+					$ret['context'] = $context;
+					retj($ret,$id);
 				break;
 				case 'group':
 					// TODO - handle group requests
@@ -25,12 +36,13 @@
 				case 'template':
 					$ret['template'] = file_get_contents(PATH_DATA.'pages/'.$id.'.template');
 					if(file_exists(PATH_DATA.'context/'.$id.'.json')){
-						$context = json_decode(file_get_contents(PATH_DATA.'context/'.$id.'.json'));
+						$context = objectToArray(json_decode(file_get_contents(PATH_DATA.'context/'.$id.'.json')));
 					}else{
 						$context = Array();
 					}
-					if(loggedIn()){
-						$context['key'] = $SESSION['key'];
+					if($LOGGEDIN){
+						$context['key'] = true;
+						$context['user'] = userObj($_SESSION['username']);
 					};
 					$ret['context'] = $context;
 					retj($ret,$id);
@@ -38,31 +50,41 @@
 				case 'action':
 						switch($id){
 							case 'login':
+								$ret['state'] = Array(
+									'data'=>Array(
+										'type'=>'template',
+										'id'=>'login',
+									)
+								);
 								if(isset($_GET['username'])&&isset($_GET['password'])){
 									$key = login($_GET['username'],$_GET['password']);
 									if($key){
-										$ret['key'] = $key;
+										$_SESSION['username'] = $_GET['username'];
 									}else{
 										$ret['error'] = "Login failed. Username or Password didn't match.";
 									}
 								}else{
 									$ret['error'] = "Please provide a valid username and password.";
 								}
-								$ret['state'] = Array('data'=>Array('type'=>'template','id'=>'login'));
 								retj($ret,$id);
 							break;
 							case 'register':
+								$ret['state'] = Array(
+									'data'=>Array(
+										'type'=>'template',
+										'id'=>'register'
+									)
+								);
 								if(isset($_GET['username'])&&isset($_GET['password'])&&isset($_GET['email'])){
 									if(addUser($_GET['username'],$_GET['password'],$_GET['email'])){
-										$ret['key'] = securityKey($_GET['username'],salt());
-										setKey($ret['key']);
+										$key = login($_GET['username'],$_GET['password']);
+										$_SESSION['username'] = $_GET['username'];
 									}else{
 										$ret['error'] = "Could not add user. ".$mysqli->error;
 									}
 								}else{
 									$ret['error'] = "That username already exists!";
 								}
-								$ret['state'] = Array('data'=>Array('type'=>'template','id'=>'register'));
 								retj($ret,$id);
 							break;
 							default:

+ 13 - 1
css/style.css

@@ -5,8 +5,20 @@ html,body{
 	padding: 0;
 	margin: 0;
 }
-#content{
+div#content{
 	width: 100%;
 	height: 100%;
 	overflow: auto;
+}
+div#loading{
+	width: 100%;
+	height: 100%;
+	position: absolute;
+	top: 0;
+	left: 0;
+	background-color: gray;
+	background-color: rgba(0,0,0,0.5);
+	background-image: url('../img/load.gif');
+	background-repeat: no-repeat;
+	background-position: center;
 }

+ 7 - 6
data/pages/index.template

@@ -6,11 +6,12 @@
 </p>
 <p>
 	{{#unless key}}
-	<a href="#page-login">Login</a>
-	-
-	<a href="#page-register">Register</a>
+		<a href="#page-login">Login</a>
+		-
+		<a href="#page-register">Register</a>
+	{{else}}
+		<a href="#page-logout">Logout</a>
+		-
+		<a href="#~{{user.name}}">{{user.name}}</a>
 	{{/unless}}
-	{{#if key}}
-	<a href="#page-logout">Logout</a>
-	{{/if}}
 </p>

+ 1 - 0
data/pages/login.template

@@ -28,6 +28,7 @@
 				setKey(null);
 				loadState('page-login');
 			}
+			return false;
 		});
 		return false;
 	}).children('.cancel').click(function(){

+ 15 - 0
data/pages/user.template

@@ -0,0 +1,15 @@
+<div>
+	{{name}}
+</div>
+<div>
+	{{email}}
+</div>
+{{#unless key}}
+	<a href="#page-index">Home</a>
+{{else}}
+	<a href="#page-logout">Logout</a>
+	-
+	<a href="#~{{user.name}}">{{user.name}}</a>
+	-
+	<a href="#page-index">Home</a>
+{{/unless}}

BIN
img/load.gif


+ 1 - 4
index.php

@@ -25,10 +25,6 @@
 				$json = Array();
 				$json['state'] = Array();
 				$json['state']['data'] = $_GET;
-				if(isset($_GET['key'])){
-					$json['key'] = $_GET['key'];
-					$json['state']['data']['key'] = $_GET['key'];
-				}
 				switch($type){
 					case 'user':$url='~'.$id;break;
 					case 'group':$url='+'.$id;break;
@@ -72,5 +68,6 @@
 	<body>
 		<div id="topbar"></div>
 		<div id="content"></div>
+		<div id="loading"></div>
 	</body>
 </html>

+ 1 - 0
install/install.sql

@@ -150,6 +150,7 @@ CREATE TABLE IF NOT EXISTS `users` (
   `email` varchar(254) NOT NULL,
   `password` varchar(128) NOT NULL,
   `salt` varchar(25) NOT NULL,
+  `key` varchar(128) NOT NULL,
   PRIMARY KEY (`id`),
   UNIQUE KEY `name` (`name`)
 ) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;

+ 66 - 40
js/index.js

@@ -1,6 +1,7 @@
 // TODO - Add initial page loading and handlers
 (function($,History){
 	var State = History.getState(),
+		Old = {},
 		Key = null,
 		flag = false,
 		settings = {},
@@ -33,6 +34,7 @@
 			return Key;
 		},
 		apiCall = window.apiCall = function(data,callback){
+			$('#loading').show();
 			data.get = 'api';
 			data.timestamp = +new Date;
 			if(exists(State.data.key)){
@@ -40,7 +42,7 @@
 			}
 			$.get(location.href,data,function(d){
 				if(exists(d['error'])){
-					alert(d.error);
+					error(d);
 				}else{
 					if(location.href.substr(location.href.lastIndexOf('/')+1) != d.state.url){
 						History.pushState(d.state.data,d.state.title,d.state.url);
@@ -52,36 +54,62 @@
 			},'json');
 		},
 		loadState = window.loadState = function(href,callback){
+			$('#loading').show();
 			var data = {
 				get:'state',
-				timestamp:+new Date
+				timestamp: +new Date
 			};
-			if(Key !== null){
-				data.key = Key;
-			}
 			$.get(href,data,function(d){
-				History.pushState(d.state.data,document.title,href);
-				State = History.getState();
-				if(exists(callback)){
-					callback();
+				if(exists(d['error'])){
+					error(d);
+				}else{
+					History.pushState(d.state.data,document.title,href);
+					getNewState();
+					if(exists(callback)){
+						callback(d);
+					}
+					console.log('loadState');
 				}
 			},'json');
 		},
 		apiState = window.apiState = function(href,callback){
+			$('#loading').show();
 			var data = {
 				get:'state',
-				timestamp:+new Date
+				timestamp: +new Date
 			};
-			if(Key !== null){
-				data.key = Key;
-			}
 			$.get(href,data,function(d){
-				History.replaceState(d.state.data,document.title,href);
-				State = History.getState();
-				if(exists(callback)){
-					callback();
+				if(exists(d['error'])){
+					error(d);
+				}else{
+					History.replaceState(d.state.data,document.title,href);
+					getNewState();
+					if(exists(callback)){
+						callback();
+					}
 				}
 			},'json');
+		},
+		error = function(e){
+			e = '['+State.url+']'+e.error+"\n"+(exists(e.state)?JSON.stringify(e.state):'');
+			console.error(e);
+		},
+		getNewState = function(){
+			State = History.getState();
+			console.log("State change. "+JSON.stringify(State.data));
+		},
+		equal = function(o1,o2){
+			for(var i in o1){
+				if(!exists(o2[i])||o2[i]!=o1[i]){
+					return false;
+				}
+			}
+			for(i in o2){
+				if(!exists(o1[i])||o2[i]!=o1[i]){
+					return false;
+				}
+			}
+			return true;
 		};
 	if(exists($.cookie('key'))){
 		setKey($.cookie('key'));
@@ -90,24 +118,13 @@
 	}
 	$(document).ready(function(){
 		$(window).on('statechange',function(){
-			var Old = State;
-			State = History.getState();
-			if(Key !== null){
-				State.key = Key;
-				State.data.key = Key;
-			}else{
-				if(exists(State.data['key'])){
-					Key = State.data.key;
-				}else if(exists(State['key'])){
-					Key = State.key;
-				}
-			}
-			if(State.data.type != Old.data.type || State.data.id != Old.data.id){
-				console.log("State change. "+JSON.stringify(State));
+			getNewState();
+			if(!equal(State.data,Old)){
 				switch(State.data.type){
-					case 'template':
-						api(State.data,function(d){
-							if(!exists(d.context.key)){
+					case 'template':case 'user':
+						apiCall(State.data,function(d){
+							if(!exists(d.context.key)&&Key!==null){
+								console.log('Context detected logout');
 								setKey(null);
 							}
 							$('#content').html(Handlebars.compile(d.template)(d.context)).mCustomScrollbar('destroy');
@@ -126,12 +143,21 @@
 									});
 								}
 							});
+							$('#loading').hide();
 						});
 					break;
 					case 'action':break;
 					default:
-						alert("Something went wrong!\nYour current state:\n"+JSON.stringify(State));
+						error({
+							url: State.url,
+							error: "Something went wrong!"
+						});
 				}
+				Old = State.data;
+			}else{
+				console.log(State.data,Old);
+				console.warn('Stopped double load of '+Old.type+'-'+Old.id);
+				$('#loading').hide();
 			}
 		});
 		if($.isEmptyObject(State.data)){
@@ -144,12 +170,9 @@
 			flag = true;
 		}
 		var data = {
-			get:'settings',
-			timestamp:+new Date
+			get: 'settings',
+			timestamp: +new Date
 		};
-		if(Key !== null){
-			data.key = Key;
-		}
 		$.get(location.href,data,function(d){
 			settings = d;
 			apiState(location.href,function(){
@@ -178,4 +201,7 @@
 		});
 		return o;
 	};
+	$.ajaxSetup({
+		async: false
+	});
 })(jQuery,History);

+ 5 - 1
php/database.php

@@ -8,7 +8,11 @@
 	function query($query,$args = Array()){
 		global $mysqli;
 		for ($i=0;$i<count($args);$i++){
-			$args[$i] = $mysqli->real_escape_string($args[$i]);
+			if(is_string($args[$i])){
+				$args[$i] = $mysqli->real_escape_string($args[$i]);
+			}else{
+				return false;
+			}
 		}
 		return $mysqli->query(vsprintf($query,$args));
 	}

+ 7 - 5
php/functions.php

@@ -1,30 +1,32 @@
 <?php
 	require_once(realpath(dirname(__FILE__)).'/config.php');
 	// TODO - create php functions for the api
-	function retj($json,$title){
+	function retj($json,$title=null){
+		if(is_null($title)){
+			$title = $_GET['id'];
+		}
 		$type=$_GET['type'];
 		$id=$_GET['id'];
 		if(!isset($json['state'])){
 			$json['state'] = Array();
 		}
+		unset($_GET['password']);
 		if(!isset($json['state']['data'])){
 			$json['state']['data'] = $_GET;
 		}else{
 			foreach($_GET as $key => $val){
-				if(!isset($json['state']['data'][$key])){
+				if(!isset($json['state']['data'][$key])&&$key!='password'){
 					$json['state']['data'][$key] = $val;
 				}
 			}
 		}
-		if(isset($json['key'])){
-			$json['state']['key'] = $json['key'];
-		}
 		$json['state']['title'] = $title;
 		switch($type){
 			case 'user':$url='~'.$id;break;
 			case 'group':$url='+'.$id;break;
 			case 'issue':$url='!'.$id;break;
 			case 'template':$url='page-'.$id;break;
+			case 'action':$url='';break;
 			default:$url=$type.'-'.$id;
 		}
 		$json['state']['url'] = $url;

+ 5 - 0
php/include.php

@@ -1,4 +1,9 @@
 <?php
+	if(!is_writable(session_save_path())){
+		retj(Array(
+			'error'=>'Session save path ('.session_save_path().') is not writable.'
+		),'error');
+	}
 	@session_start();
 	require_once(realpath(dirname(__FILE__)).'/config.php');
 	require_once(PATH_PHP.'database.php');

+ 33 - 9
php/security.php

@@ -16,29 +16,53 @@
 		return saltedHash($username,$salt);
 	}
 	function authenticate(){
-		global $SESSION;
 		if(loggedIn()){
-			setKey($SESSION['key']);
+			setKey(getKey());
 		}
 	}
+	function login($username,$password){
+		global $LOGGEDIN;
+		if($res = query("SELECT name,password,salt FROM `users` WHERE name = '%s'",Array($username))){
+			if($res->num_rows == 1){
+				$row = $res->fetch_assoc();
+				if(compareSaltedHash($password,$row['salt'],$row['password'])){
+					$_SESSION['username'] = $username;
+					$key = securityKey($username,$_SERVER['REMOTE_ADDR']);
+					setKey($key);
+					$LOGGEDIN = true;
+					return $key;
+				}
+			}
+		}
+		return false;
+	}
 	function loggedIn(){
-		global $SESSION;
-		if(isset($_GET['key'])&&isset($SESSION['key'])&&isset($SESSION['username'])&&isUser($SESSION['username'])){
-			if($_GET['key'] == $SESSION['key']){
+		global $LOGGEDIN;
+		global $_COOKIE;
+		if(isset($_COOKIE['username'])&&isset($_COOKIE['key'])){
+			if(securityKey($_COOKIE['username'],$_SERVER['REMOTE_ADDR'])==$_COOKIE['key']){
+				$_SESSION['username'] = $_COOKIE['username'];
+				setKey($_COOKIE['key']);
+				$LOGGEDIN = true;
 				return true;
 			}
 		}
 		setKey(null);
+		$LOGGEDIN = false;
 		return false;
 	}
 	function setKey($key){
-		global $SESSION;
 		if($key == null){
-			unset($SESSION['key']);
-			unset($SESSION['username']);
+			unset($_SESSION['key']);
+			unset($_SESSION['username']);
 		}else{
-			$SESSION['key'] = $key;
+			$_SESSION['key'] = $key;
+			setcookie('username',$_SESSION['username'],time()+get('timeout'));
 			setcookie('key',$key,time()+get('timeout'));
 		}
+		return $key;
+	}
+	function getKey(){
+		return isset($_SESSION['key'])?$_SESSION['key']:null;
 	}
 ?>

+ 18 - 12
php/user.php

@@ -11,18 +11,6 @@
 		$hash = $mysqli->escape_string(saltedHash($password,$salt));
 		return query("INSERT INTO `users` (email,name,password,salt) VALUES ('%s','%s','%s','%s')",Array($email,$username,$hash,$salt));
 	}
-	function login($username,$password){
-		global $mysqli;
-		if($res = query("SELECT name,password,salt FROM `users` WHERE name = '%s'",Array($username))){
-			if($res->num_rows == 1){
-				$row = $res->fetch_assoc();
-				if(compareSaltedHash($password,$row['salt'],$row['password'])){
-					return securityKey($username,salt());
-				}			
-			}
-		}
-		return false;
-	}
 	function isUser($name){
 		if(query("SELECT id FROM `users` WHERE name='%s'",Array($name))){
 			return true;
@@ -30,4 +18,22 @@
 			return false;
 		}
 	}
+	function userId($name){
+		if($user = query("SELECT id FROM `users` WHERE name='%s'",Array($name))){
+			$user = $user->fetch_assoc();
+			return $user['id'];
+		}else{
+			return false;
+		}
+	}
+	function userObj($id){
+		if(is_string($id)){
+			$id = userId($id);
+		}
+		if($result = query("SELECT * FROM `users` WHERE id='%d'",Array($id))){
+			return $result->fetch_assoc();
+		}else{
+			return false;
+		}
+	}
 ?>