Browse Source

move to internal user management because it's impossible to implement
proper transparent offline mode with http auth (worker is incapable of
authenticating properly)

MIGRATION:

1. disable HTTP authentication (this is important!)
2. add two new tables to db/scratch.db (sessions & users)
3. create users via useradm.php (same names and passwords, previous data
is kept)

Andrew Dolgov 2 years ago
parent
commit
9f4927825b
9 changed files with 295 additions and 21 deletions
  1. 9 4
      backend.php
  2. 20 0
      db.php
  3. 12 3
      index.php
  4. 73 0
      login.php
  5. 10 0
      logout.php
  6. 16 0
      schema.sql
  7. 68 0
      sessions.php
  8. 59 0
      useradm.php
  9. 28 14
      worker.js

+ 9 - 4
backend.php

@@ -1,15 +1,16 @@
 <?php
 
 	require_once "config.php";
+	require_once "sessions.php";
+	require_once "db.php";
+
+	$ldb = Db::get();
 
 	$op = $_REQUEST["op"];
 
 	header("Content-type: text/json");
 
-	$ldb = new SQLite3(SCRATCH_DB);
-	$ldb->busyTimeout(30*1000);
-
-	$owner = SQLite3::escapeString($_SERVER["PHP_AUTH_USER"]);
+	$owner = SQLite3::escapeString($_SESSION["owner"]);
 
 	if (!$owner) {
 		header($_SERVER["SERVER_PROTOCOL"]." 401 Unauthorized");
@@ -43,7 +44,11 @@
 		}
 
 		break;
+	case "getowner":
+		$owner = SQLite3::escapeString($_SESSION["owner"]);;
 
+		print json_encode(["owner" => $owner]);
+		break;
 	case "getinfo":
 		$id = (int) $_REQUEST["id"];
 

+ 20 - 0
db.php

@@ -0,0 +1,20 @@
+<?php
+class Db {
+	private static $instance;
+	private $dbh;
+
+	private function __construct() {
+		$this->dbh = new SQLite3(__DIR__ . "/" . SCRATCH_DB);
+		$this->dbh->busyTimeout(30*1000);
+	}
+
+	public static function get() {
+		if (self::$instance == null)
+			self::$instance = new self();
+
+		return self::$instance->dbh;
+	}
+
+};
+
+?>

+ 12 - 3
index.php

@@ -4,9 +4,15 @@
 	}
 
 	require_once "config.php";
+	require_once "sessions.php";
+	require_once "db.php";
 
-	$owner = SQLite3::escapeString($_SERVER["PHP_AUTH_USER"]);
+	@$owner = SQLite3::escapeString($_SESSION["owner"]);
 
+	if (!$owner) {
+		header("Location: login.php");
+		exit;
+	}
 
 	if (basename(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)) != 'index.php') {
 		header('Location: index.php');
@@ -27,8 +33,7 @@
 		die(dirname(SCRATCH_DB) . " directory is not writable");
 	}
 
-	$ldb = new SQLite3(SCRATCH_DB);
-	$ldb->busyTimeout(30*1000);
+	$ldb = Db::get();
 ?>
 <!DOCTYPE html>
 <html>
@@ -85,6 +90,10 @@
 			<button type="submit" class="btn btn-default">Search</button>
 		</form>
 
+		<ul class="nav navbar-nav navbar-right">
+		<li><a href="logout.php">Logout</a></li>
+		</li>
+
 	</div>
 
 </div>

+ 73 - 0
login.php

@@ -0,0 +1,73 @@
+<?php
+	require_once "config.php";
+	require_once "sessions.php";
+
+	@$op = $_REQUEST["op"];
+
+	if ($op == "perform-login") {
+		$user = SQLite3::escapeString($_REQUEST["user"]);
+		$password = SQLite3::escapeString('SHA256:' . hash('sha256', "$user:" . $_REQUEST["password"]));
+
+		$dbh = Db::get();
+
+		$res = $dbh->query("SELECT id FROM epube_users WHERE user = '$user' AND pass = '$password'");
+
+		if ($line = $res->fetchArray(SQLITE3_ASSOC)) {
+			$_SESSION["owner"] = $user;
+			header("Location: index.php");
+		}
+	}
+
+?>
+<!DOCTYPE html>
+<html>
+<head>
+	<meta name="viewport" content="width=device-width, initial-scale=1.0">
+	<link href="lib/bootstrap/v3/css/bootstrap.min.css" rel="stylesheet" media="screen">
+	<link href="lib/bootstrap/v3/css/bootstrap-theme.min.css" rel="stylesheet" media="screen">
+	<link href="lib/qtip2/jquery.qtip.min.css" rel="stylesheet" media="screen">
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+	<script src="lib/bootstrap/v3/js/jquery.js"></script>
+	<script src="lib/bootstrap/v3/js/bootstrap.min.js"></script>
+	<script src="lib/holder.min.js"></script>
+	<script src="lib/localforage.min.js"></script>
+	<script src="lib/qtip2/jquery.qtip.min.js"></script>
+	<title>The Epube</title>
+	<link type="text/css" rel="stylesheet" media="screen" href="css/index.css" />
+	<link rel="shortcut icon" type="image/png" href="img/favicon.png" />
+	<link rel="icon" sizes="192x192" href="img/favicon_hires.png">
+	<link rel="manifest" href="manifest.json">
+	<meta name="mobile-web-app-capable" content="yes">
+	<script src="js/index.js"></script>
+	<script src="js/common.js"></script>
+</head>
+<body>
+
+<div class="navbar navbar-default navbar-static-top">
+<div class="container">
+	<div class="navbar-header">
+		<span class="navbar-brand"><a href="?">The Epube</a></span>
+	</div>
+</div>
+</div>
+
+<div class="container">
+
+	<form>
+		<input type="hidden" name="op" value="perform-login">
+
+		<div class="form-group">
+			<label>User</label>
+			<input class="form-control" required="true" name="user">
+		</div>
+		<div class="form-group">
+			<label>Password</label>
+			<input type="password" class="form-control" name="password" required="true">
+		</div>
+		<button type="submit" class="btn btn-default">Log in</button>
+	</form>
+
+</div>
+
+</body>
+</html>

+ 10 - 0
logout.php

@@ -0,0 +1,10 @@
+<?php
+	require_once "config.php";
+	require_once "sessions.php";
+
+	session_destroy();
+
+	header("Location: login.php");
+
+?>
+

+ 16 - 0
schema.sql

@@ -1,5 +1,9 @@
 drop table if exists epube_pagination;
 drop table if exists epube_books;
+drop table if exists epube_users;
+drop table if exists epube_sessions;
+
+drop index if exists epube_sessions_expire;
 
 create table epube_pagination(
 	id integer not null primary key autoincrement,
@@ -13,3 +17,15 @@ create table epube_books(
 	owner varchar(200) not null,
 	lastcfi varchar(200) not null,
 	lastread integer not null);
+
+create table epube_users(
+	id integer not null primary key autoincrement,
+	user varchar(100) not null,
+	pass varchar(200) not null);
+
+create table epube_sessions (
+	id varchar(250) not null primary key,
+	data text,
+	expire integer not null);
+
+create index epube_sessions_expire on epube_sessions(expire);

+ 68 - 0
sessions.php

@@ -0,0 +1,68 @@
+<?php
+	require_once "config.php";
+	require_once "db.php";
+
+	define('SESSION_LIFETIME', 365 * 86400);
+
+	ini_set("session.name", "epube_sid");
+	ini_set("session.use_only_cookies", true);
+	ini_set("session.gc_maxlifetime", SESSION_LIFETIME);
+	ini_set("session.cookie_lifetime", SESSION_LIFETIME);
+
+	if (@$_SERVER['HTTPS'] == "on") {
+		ini_set("session.cookie_secure", true);
+	}
+
+	session_set_cookie_params(SESSION_LIFETIME);
+
+	function s_open ($s, $n) {
+		return true;
+	}
+
+	function s_read ($id){
+		$res = Db::get()->query("SELECT data FROM epube_sessions WHERE id='$id'");
+
+		if ($line = $res->fetchArray(SQLITE3_ASSOC)) {
+			return base64_decode($line["data"]);
+		} else {
+			$expire = time() + SESSION_LIFETIME;
+
+			Db::get()->query("INSERT INTO epube_sessions (id, data, expire)
+					VALUES ('$id', '', '$expire')");
+		}
+
+	}
+
+	function s_write ($id, $data) {
+		$data = base64_encode($data);
+		$expire = time() + SESSION_LIFETIME;
+
+		Db::get()->query("UPDATE epube_sessions SET data = '$data', expire = '$expire' WHERE id = '$id'");
+
+		return true;
+	}
+
+	function s_close () {
+		return true;
+	}
+
+	function s_destroy($id) {
+		Db::get()->query("DELETE FROM epube_sessions WHERE id = '$id'");
+
+		return true;
+	}
+
+	function s_gc ($expire) {
+		Db::get()->query("DELETE FROM epube_sessions WHERE expire < " . time());
+
+		return true;
+	}
+	session_set_save_handler("s_open",
+		"s_close", "s_read", "s_write",
+		"s_destroy", "s_gc");
+
+	register_shutdown_function('session_write_close');
+
+	session_start();
+
+?>

+ 59 - 0
useradm.php

@@ -0,0 +1,59 @@
+#!/usr/bin/php
+<?php
+	require_once "config.php";
+	require_once "db.php";
+
+	$dbh = Db::get();
+
+	$longopts = [ "add:", "del:", "list", "help" ];
+
+	$options = getopt("", $longopts);
+
+	if (count($options) == 0 || isset($options["help"])) {
+		print "Manage Epube user database. Usage:
+	--add USER:PASSWORD
+	--del USER
+	--list\n";
+	}
+
+	if (isset($options["del"])) {
+		$user = SQLite3::escapeString($options["del"]);
+
+		print "Deleting user $user...\n";
+		$dbh->query("DELETE FROM epube_users WHERE user = '$user'");
+	}
+
+	if (isset($options["list"])) {
+		$res = $dbh->query("SELECT id, user FROM epube_users ORDER BY user");
+
+		while ($line = $res->fetchArray(SQLITE3_ASSOC)) {
+			printf("%d. %s\n", $line["id"], $line["user"]);
+		}
+
+	}
+
+	if (isset($options["add"])) {
+		@list($user, $pass) = explode(":", $options["add"]);
+
+		if (!$user || !$pass) {
+			print "Not enough arguments.\n";
+			exit;
+		}
+
+		$user = SQLite3::escapeString($user);
+		$pass_hash = SQLite3::escapeString('SHA256:' . hash('sha256', "$user:$pass"));
+
+		print "Adding user $user with password $pass...\n";
+
+		$res = $dbh->query("SELECT user FROM epube_users WHERE user = '$user'");
+
+		if ($line = $res->fetchArray(SQLITE3_ASSOC)) {
+			print "User already exists.\n";
+		} else {
+			$dbh->query("INSERT INTO epube_users (user, pass)
+					VALUES ('$user', '$pass_hash')");
+		}
+
+	}
+
+?>

+ 28 - 14
worker.js

@@ -44,7 +44,7 @@ self.addEventListener('message', function(event){
 			cache.keys().then(function(keys) {
 				for (var i = 0; i < keys.length; i++) {
 
-					fetch(keys[i],{credentials:'same-origin'}).then(function(resp) {
+					fetch(keys[i]).then(function(resp) {
 						if (resp.status == 200) {
 							cache.put(resp.url, resp);
 						}
@@ -59,24 +59,38 @@ self.addEventListener('message', function(event){
 this.addEventListener('fetch', function(event) {
 	var req = event.request.clone();
 
-	if (!navigator.onLine) {
-		event.respondWith(
-			caches.match(req).then(function(resp) {
+	event.respondWith(
+		caches.match(req).then(function(resp) {
 
-				if (resp) return resp;
+			if (resp) {
+				return resp;
+			}
 
-				if (req.url.match("read.html")) {
-					return caches.match("read.html");
-				}
+			if (req.url.match("read.html")) {
+				return caches.match("read.html");
+			}
 
-				if (req.url.match("offline.html")) {
-					return caches.match("offline.html");
+			if (req.url.match("offline.html")) {
+				return caches.match("offline.html");
+			}
+
+			return fetch(req).then(function(resp) {
+
+				if (resp.status == 200) {
+					if (resp.url.match("backend.php\\?op=cover")) {
+						return caches.open(CACHE_NAME).then(function(cache) {
+							cache.put(resp.url, resp.clone());
+							return resp;
+						});
+					}
 				}
 
-				if (req.url.match("index.php")) {
+				return resp;
+			}).catch(function() {
+				if (req.url[req.url.length-1] == "/" || req.url.match("index.php")) {
 					return caches.match("offline.html");
 				}
-			})
-		);
-	}
+			});
+		})
+	);
 });