auth
This commit is contained in:
parent
c3ed2483ea
commit
cf0b9f036b
11
localwebsite/classes/User.php
Normal file
11
localwebsite/classes/User.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class User extends model {
|
||||||
|
|
||||||
|
const DB_TABLE = 'users';
|
||||||
|
|
||||||
|
public int $id;
|
||||||
|
public string $username;
|
||||||
|
public string $password;
|
||||||
|
|
||||||
|
}
|
66
localwebsite/classes/auth.php
Normal file
66
localwebsite/classes/auth.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class auth {
|
||||||
|
|
||||||
|
public static ?User $authorizedUser = null;
|
||||||
|
|
||||||
|
const SESSION_TIMEOUT = 86400 * 365;
|
||||||
|
const COOKIE_NAME = 'auth';
|
||||||
|
|
||||||
|
public static function getToken(): ?string {
|
||||||
|
return $_COOKIE[self::COOKIE_NAME] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function setToken(string $token) {
|
||||||
|
setcookie(self::COOKIE_NAME,
|
||||||
|
$token,
|
||||||
|
time() + self::SESSION_TIMEOUT,
|
||||||
|
'/',
|
||||||
|
config::get('auth_cookie_host'),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function resetToken() {
|
||||||
|
if (!headers_sent())
|
||||||
|
setcookie(self::COOKIE_NAME, null, -1, '/', config::get('auth_cookie_host'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function id(bool $do_check = true): int {
|
||||||
|
if ($do_check)
|
||||||
|
self::check();
|
||||||
|
|
||||||
|
if (!self::$authorizedUser)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return self::$authorizedUser->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function check(?string $pwhash = null): bool {
|
||||||
|
if (self::$authorizedUser !== null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// get auth token
|
||||||
|
if (!$pwhash)
|
||||||
|
$pwhash = self::getToken();
|
||||||
|
|
||||||
|
if (!is_string($pwhash))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// find session by given token
|
||||||
|
$user = users::getUserByPwhash($pwhash);
|
||||||
|
if (is_null($user)) {
|
||||||
|
self::resetToken();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$authorizedUser = $user;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function logout() {
|
||||||
|
self::resetToken();
|
||||||
|
self::$authorizedUser = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
10
localwebsite/classes/config.php
Normal file
10
localwebsite/classes/config.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class config {
|
||||||
|
|
||||||
|
public static function get(string $key) {
|
||||||
|
global $config;
|
||||||
|
return is_callable($config[$key]) ? $config[$key]() : $config[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
39
localwebsite/classes/users.php
Normal file
39
localwebsite/classes/users.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class users {
|
||||||
|
|
||||||
|
public static function add(string $username, string $password): int {
|
||||||
|
$db = getDB();
|
||||||
|
$db->insert('users', [
|
||||||
|
'username' => $username,
|
||||||
|
'password' => pwhash($password)
|
||||||
|
]);
|
||||||
|
return $db->insertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function exists(string $username): bool {
|
||||||
|
$db = getDB();
|
||||||
|
$count = (int)$db->querySingle("SELECT COUNT(*) FROM users WHERE username=?", $username);
|
||||||
|
return $count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function validatePassword(string $username, string $password): bool {
|
||||||
|
$db = getDB();
|
||||||
|
$row = $db->querySingleRow("SELECT * FROM users WHERE username=?", $username);
|
||||||
|
if (!$row)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return $row['password'] == pwhash($password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getUserByPwhash(string $pwhash): ?User {
|
||||||
|
$db = getDB();
|
||||||
|
$data = $db->querySingleRow("SELECT * FROM users WHERE password=?", $pwhash);
|
||||||
|
return $data ? new User($data) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function setPassword(int $id, string $new_password) {
|
||||||
|
getDB()->exec("UPDATE users SET password=? WHERE id=?", pwhash($new_password), $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -57,8 +57,8 @@ return [
|
|||||||
],
|
],
|
||||||
|
|
||||||
'cam_hls_access_key' => '',
|
'cam_hls_access_key' => '',
|
||||||
'cam_hls_proto' => 'http',
|
'cam_hls_proto' => 'http', // bool|callable
|
||||||
'cam_hls_host' => '192.168.1.1',
|
'cam_hls_host' => '192.168.1.1', // bool|callable
|
||||||
'cam_list' => [
|
'cam_list' => [
|
||||||
// fill me with names
|
// fill me with names
|
||||||
],
|
],
|
||||||
@ -70,4 +70,8 @@ return [
|
|||||||
],
|
],
|
||||||
|
|
||||||
'database_path' => getenv('HOME').'/.config/homekit.localwebsite.sqlite3',
|
'database_path' => getenv('HOME').'/.config/homekit.localwebsite.sqlite3',
|
||||||
|
|
||||||
|
'auth_cookie_host' => '',
|
||||||
|
'auth_need' => false, // bool|callable
|
||||||
|
'auth_pw_salt' => '',
|
||||||
];
|
];
|
||||||
|
@ -7,13 +7,19 @@ class database {
|
|||||||
protected SQLite3 $link;
|
protected SQLite3 $link;
|
||||||
|
|
||||||
public function __construct(string $db_path) {
|
public function __construct(string $db_path) {
|
||||||
|
$will_create = !file_exists($db_path);
|
||||||
$this->link = new SQLite3($db_path);
|
$this->link = new SQLite3($db_path);
|
||||||
|
if ($will_create)
|
||||||
|
setperm($db_path);
|
||||||
$this->link->enableExceptions(true);
|
$this->link->enableExceptions(true);
|
||||||
$this->upgradeSchema();
|
$this->upgradeSchema();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function upgradeSchema() {
|
protected function upgradeSchema() {
|
||||||
$cur = $this->getSchemaVersion();
|
$cur = $this->getSchemaVersion();
|
||||||
|
if ($cur == self::SCHEMA_VERSION)
|
||||||
|
return;
|
||||||
|
|
||||||
if ($cur < 1) {
|
if ($cur < 1) {
|
||||||
$this->link->exec("CREATE TABLE users (
|
$this->link->exec("CREATE TABLE users (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
@ -77,4 +83,49 @@ class database {
|
|||||||
return $this->link->querySingle($this->prepareQuery($sql, ...$params), true);
|
return $this->link->querySingle($this->prepareQuery($sql, ...$params), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function performInsert(string $command, string $table, array $fields): SQLite3Result {
|
||||||
|
$names = [];
|
||||||
|
$values = [];
|
||||||
|
$count = 0;
|
||||||
|
foreach ($fields as $k => $v) {
|
||||||
|
$names[] = $k;
|
||||||
|
$values[] = $v;
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "{$command} INTO `{$table}` (`" . implode('`, `', $names) . "`) VALUES (" . implode(', ', array_fill(0, $count, '?')) . ")";
|
||||||
|
array_unshift($values, $sql);
|
||||||
|
|
||||||
|
return call_user_func_array([$this, 'query'], $values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function insert(string $table, array $fields): SQLite3Result {
|
||||||
|
return $this->performInsert('INSERT', $table, $fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function replace(string $table, array $fields): SQLite3Result {
|
||||||
|
return $this->performInsert('REPLACE', $table, $fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function insertId(): int {
|
||||||
|
return $this->link->lastInsertRowID();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update($table, $rows, ...$cond): SQLite3Result {
|
||||||
|
$fields = [];
|
||||||
|
$args = [];
|
||||||
|
foreach ($rows as $row_name => $row_value) {
|
||||||
|
$fields[] = "`{$row_name}`=?";
|
||||||
|
$args[] = $row_value;
|
||||||
|
}
|
||||||
|
$sql = "UPDATE `$table` SET " . implode(', ', $fields);
|
||||||
|
if (!empty($cond)) {
|
||||||
|
$sql .= " WHERE " . $cond[0];
|
||||||
|
if (count($cond) > 1)
|
||||||
|
$args = array_merge($args, array_slice($cond, 1));
|
||||||
|
}
|
||||||
|
return $this->query($sql, ...$args);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
243
localwebsite/engine/model.php
Normal file
243
localwebsite/engine/model.php
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
abstract class model {
|
||||||
|
|
||||||
|
const DB_TABLE = null;
|
||||||
|
const DB_KEY = 'id';
|
||||||
|
|
||||||
|
const STRING = 0;
|
||||||
|
const INTEGER = 1;
|
||||||
|
const FLOAT = 2;
|
||||||
|
const ARRAY = 3;
|
||||||
|
const BOOLEAN = 4;
|
||||||
|
const JSON = 5;
|
||||||
|
const SERIALIZED = 6;
|
||||||
|
|
||||||
|
protected static array $SpecCache = [];
|
||||||
|
|
||||||
|
public static function create_instance(...$args) {
|
||||||
|
$cl = get_called_class();
|
||||||
|
return new $cl(...$args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct(array $raw) {
|
||||||
|
if (!isset(self::$SpecCache[static::class])) {
|
||||||
|
list($fields, $model_name_map, $db_name_map) = static::get_spec();
|
||||||
|
self::$SpecCache[static::class] = [
|
||||||
|
'fields' => $fields,
|
||||||
|
'model_name_map' => $model_name_map,
|
||||||
|
'db_name_map' => $db_name_map
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (self::$SpecCache[static::class]['fields'] as $field)
|
||||||
|
$this->{$field['model_name']} = self::cast_to_type($field['type'], $raw[$field['db_name']]);
|
||||||
|
|
||||||
|
if (is_null(static::DB_TABLE))
|
||||||
|
trigger_error('class '.get_class($this).' doesn\'t have DB_TABLE defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $fields
|
||||||
|
*
|
||||||
|
* TODO: support adding or subtracting (SET value=value+1)
|
||||||
|
*/
|
||||||
|
public function edit($fields) {
|
||||||
|
$db = getDB();
|
||||||
|
|
||||||
|
$model_upd = [];
|
||||||
|
$db_upd = [];
|
||||||
|
|
||||||
|
foreach ($fields as $name => $value) {
|
||||||
|
$index = self::$SpecCache[static::class]['db_name_map'][$name] ?? null;
|
||||||
|
if (is_null($index)) {
|
||||||
|
debugError(__METHOD__.': field `'.$name.'` not found in '.static::class);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$field = self::$SpecCache[static::class]['fields'][$index];
|
||||||
|
switch ($field['type']) {
|
||||||
|
case self::ARRAY:
|
||||||
|
if (is_array($value)) {
|
||||||
|
$db_upd[$name] = implode(',', $value);
|
||||||
|
$model_upd[$field['model_name']] = $value;
|
||||||
|
} else {
|
||||||
|
debugError(__METHOD__.': field `'.$name.'` is expected to be array. skipping.');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::INTEGER:
|
||||||
|
$value = (int)$value;
|
||||||
|
$db_upd[$name] = $value;
|
||||||
|
$model_upd[$field['model_name']] = $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::FLOAT:
|
||||||
|
$value = (float)$value;
|
||||||
|
$db_upd[$name] = $value;
|
||||||
|
$model_upd[$field['model_name']] = $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::BOOLEAN:
|
||||||
|
$db_upd[$name] = $value ? 1 : 0;
|
||||||
|
$model_upd[$field['model_name']] = $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::JSON:
|
||||||
|
$db_upd[$name] = jsonEncode($value);
|
||||||
|
$model_upd[$field['model_name']] = $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::SERIALIZED:
|
||||||
|
$db_upd[$name] = serialize($value);
|
||||||
|
$model_upd[$field['model_name']] = $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$value = (string)$value;
|
||||||
|
$db_upd[$name] = $value;
|
||||||
|
$model_upd[$field['model_name']] = $value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($db_upd) && !$db->update(static::DB_TABLE, $db_upd, static::DB_KEY."=?", $this->get_id())) {
|
||||||
|
debugError(__METHOD__.': failed to update database');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($model_upd)) {
|
||||||
|
foreach ($model_upd as $name => $value)
|
||||||
|
$this->{$name} = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_id() {
|
||||||
|
return $this->{to_camel_case(static::DB_KEY)};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function as_array(array $fields = [], array $custom_getters = []): array {
|
||||||
|
if (empty($fields))
|
||||||
|
$fields = array_keys(static::$SpecCache[static::class]['db_name_map']);
|
||||||
|
|
||||||
|
$array = [];
|
||||||
|
foreach ($fields as $field) {
|
||||||
|
if (isset($custom_getters[$field]) && is_callable($custom_getters[$field])) {
|
||||||
|
$array[$field] = $custom_getters[$field]();
|
||||||
|
} else {
|
||||||
|
$array[$field] = $this->{to_camel_case($field)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function cast_to_type(int $type, $value) {
|
||||||
|
switch ($type) {
|
||||||
|
case self::BOOLEAN:
|
||||||
|
return (bool)$value;
|
||||||
|
|
||||||
|
case self::INTEGER:
|
||||||
|
return (int)$value;
|
||||||
|
|
||||||
|
case self::FLOAT:
|
||||||
|
return (float)$value;
|
||||||
|
|
||||||
|
case self::ARRAY:
|
||||||
|
return array_filter(explode(',', $value));
|
||||||
|
|
||||||
|
case self::JSON:
|
||||||
|
$val = jsonDecode($value);
|
||||||
|
if (!$val)
|
||||||
|
$val = null;
|
||||||
|
return $val;
|
||||||
|
|
||||||
|
case self::SERIALIZED:
|
||||||
|
$val = unserialize($value);
|
||||||
|
if ($val === false)
|
||||||
|
$val = null;
|
||||||
|
return $val;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return (string)$value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function get_spec(): array {
|
||||||
|
$rc = new ReflectionClass(static::class);
|
||||||
|
$props = $rc->getProperties(ReflectionProperty::IS_PUBLIC);
|
||||||
|
|
||||||
|
$list = [];
|
||||||
|
$index = 0;
|
||||||
|
|
||||||
|
$model_name_map = [];
|
||||||
|
$db_name_map = [];
|
||||||
|
|
||||||
|
foreach ($props as $prop) {
|
||||||
|
if ($prop->isStatic())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$name = $prop->getName();
|
||||||
|
if (startsWith($name, '_'))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$type = $prop->getType();
|
||||||
|
$phpdoc = $prop->getDocComment();
|
||||||
|
|
||||||
|
$mytype = null;
|
||||||
|
if (!$prop->hasType() && !$phpdoc)
|
||||||
|
$mytype = self::STRING;
|
||||||
|
else {
|
||||||
|
$typename = $type->getName();
|
||||||
|
switch ($typename) {
|
||||||
|
case 'string':
|
||||||
|
$mytype = self::STRING;
|
||||||
|
break;
|
||||||
|
case 'int':
|
||||||
|
$mytype = self::INTEGER;
|
||||||
|
break;
|
||||||
|
case 'float':
|
||||||
|
$mytype = self::FLOAT;
|
||||||
|
break;
|
||||||
|
case 'array':
|
||||||
|
$mytype = self::ARRAY;
|
||||||
|
break;
|
||||||
|
case 'bool':
|
||||||
|
$mytype = self::BOOLEAN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($phpdoc != '') {
|
||||||
|
$pos = strpos($phpdoc, '@');
|
||||||
|
if ($pos === false)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (substr($phpdoc, $pos+1, 4) == 'json')
|
||||||
|
$mytype = self::JSON;
|
||||||
|
else if (substr($phpdoc, $pos+1, 5) == 'array')
|
||||||
|
$mytype = self::ARRAY;
|
||||||
|
else if (substr($phpdoc, $pos+1, 10) == 'serialized')
|
||||||
|
$mytype = self::SERIALIZED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_null($mytype))
|
||||||
|
debugError(__METHOD__.": ".$name." is still null in ".static::class);
|
||||||
|
|
||||||
|
$dbname = from_camel_case($name);
|
||||||
|
$list[] = [
|
||||||
|
'type' => $mytype,
|
||||||
|
'model_name' => $name,
|
||||||
|
'db_name' => $dbname
|
||||||
|
];
|
||||||
|
|
||||||
|
$model_name_map[$name] = $index;
|
||||||
|
$db_name_map[$dbname] = $index;
|
||||||
|
|
||||||
|
$index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$list, $model_name_map, $db_name_map];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -242,6 +242,10 @@ function bytesToUnitsLabel(GMP $b): string {
|
|||||||
return gmp_strval($b);
|
return gmp_strval($b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pwhash(string $s): string {
|
||||||
|
return hash('sha256', config::get('auth_pw_salt').'|'.$s);
|
||||||
|
}
|
||||||
|
|
||||||
$ShutdownFunctions = [];
|
$ShutdownFunctions = [];
|
||||||
|
|
||||||
function append_shutdown_function(callable $f) {
|
function append_shutdown_function(callable $f) {
|
||||||
@ -255,11 +259,27 @@ function prepend_shutdown_function(callable $f) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDB(): database {
|
function getDB(): database {
|
||||||
global $config;
|
|
||||||
static $link = null;
|
static $link = null;
|
||||||
|
|
||||||
if (is_null($link))
|
if (is_null($link))
|
||||||
$link = new database($config['database_path']);
|
$link = new database(config::get('database_path'));
|
||||||
|
|
||||||
return $link;
|
return $link;
|
||||||
|
}
|
||||||
|
|
||||||
|
function to_camel_case(string $input, string $separator = '_'): string {
|
||||||
|
return lcfirst(str_replace($separator, '', ucwords($input, $separator)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function from_camel_case(string $s): string {
|
||||||
|
$buf = '';
|
||||||
|
$len = strlen($s);
|
||||||
|
for ($i = 0; $i < $len; $i++) {
|
||||||
|
if (!ctype_upper($s[$i])) {
|
||||||
|
$buf .= $s[$i];
|
||||||
|
} else {
|
||||||
|
$buf .= '_'.strtolower($s[$i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $buf;
|
||||||
}
|
}
|
36
localwebsite/handlers/AuthHandler.php
Normal file
36
localwebsite/handlers/AuthHandler.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class AuthHandler extends RequestHandler {
|
||||||
|
|
||||||
|
protected function before_dispatch(string $method, string $act) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GET_auth() {
|
||||||
|
list($error) = $this->input('error');
|
||||||
|
$this->tpl->set(['error' => $error]);
|
||||||
|
$this->tpl->set_title('Авторизация');
|
||||||
|
$this->tpl->render_page('auth.twig');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function POST_auth() {
|
||||||
|
list($username, $password) = $this->input('username, password');
|
||||||
|
|
||||||
|
$result = users::validatePassword($username, $password);
|
||||||
|
if (!$result) {
|
||||||
|
debugError('invalid login attempt: '.$_SERVER['REMOTE_ADDR'].', '.$_SERVER['HTTP_USER_AGENT'].", username=$username, password=$password");
|
||||||
|
redirect('/auth/?error='.urlencode('неверный логин или пароль'));
|
||||||
|
}
|
||||||
|
|
||||||
|
auth::setToken(pwhash($password));
|
||||||
|
redirect('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GET_deauth() {
|
||||||
|
if (auth::id())
|
||||||
|
auth::logout();
|
||||||
|
|
||||||
|
redirect('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -8,11 +8,6 @@ class MiscHandler extends RequestHandler
|
|||||||
$this->tpl->render_page('index.twig');
|
$this->tpl->render_page('index.twig');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function GET_phpinfo() {
|
|
||||||
phpinfo();
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function GET_sensors_page() {
|
public function GET_sensors_page() {
|
||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
@ -68,9 +63,9 @@ class MiscHandler extends RequestHandler
|
|||||||
$hls_opts['debug'] = true;
|
$hls_opts['debug'] = true;
|
||||||
|
|
||||||
$this->tpl->add_external_static('js', 'https://cdn.jsdelivr.net/npm/hls.js@latest');
|
$this->tpl->add_external_static('js', 'https://cdn.jsdelivr.net/npm/hls.js@latest');
|
||||||
|
|
||||||
$hls_host = is_callable($config['cam_hls_host']) ? $config['cam_hls_host']() : $config['cam_hls_host'];
|
$hls_host = config::get('cam_hls_host');
|
||||||
$hls_proto = is_callable($config['cam_hls_proto']) ? $config['cam_hls_proto']() : $config['cam_hls_proto'];
|
$hls_proto = config::get('cam_hls_proto');
|
||||||
|
|
||||||
$this->tpl->set([
|
$this->tpl->set([
|
||||||
'hls_host' => $hls_host,
|
'hls_host' => $hls_host,
|
||||||
@ -89,4 +84,8 @@ class MiscHandler extends RequestHandler
|
|||||||
print_r($_SERVER);
|
print_r($_SERVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function GET_phpinfo() {
|
||||||
|
phpinfo();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -15,6 +15,12 @@ class RequestHandler extends request_handler {
|
|||||||
$this->tpl->add_static('polyfills.js');
|
$this->tpl->add_static('polyfills.js');
|
||||||
$this->tpl->add_static('app.js');
|
$this->tpl->add_static('app.js');
|
||||||
$this->tpl->add_static('app.css');
|
$this->tpl->add_static('app.css');
|
||||||
|
|
||||||
|
if (auth::id()) {
|
||||||
|
$this->tpl->set_global([
|
||||||
|
'auth_user' => auth::$authorizedUser
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dispatch(string $act) {
|
public function dispatch(string $act) {
|
||||||
@ -38,4 +44,9 @@ class RequestHandler extends request_handler {
|
|||||||
ajax_error('unknown act "'.$act.'"', 404);
|
ajax_error('unknown act "'.$act.'"', 404);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function before_dispatch(string $method, string $act) {
|
||||||
|
if (config::get('auth_need') && !auth::id())
|
||||||
|
redirect('/auth/');
|
||||||
|
}
|
||||||
}
|
}
|
@ -28,6 +28,10 @@ $router->add('phpinfo/', 'Misc phpinfo');
|
|||||||
$router->add('cams/', 'Misc cams');
|
$router->add('cams/', 'Misc cams');
|
||||||
$router->add('debug/', 'Misc debug');
|
$router->add('debug/', 'Misc debug');
|
||||||
|
|
||||||
|
// auth
|
||||||
|
$router->add('auth/', 'Auth auth');
|
||||||
|
$router->add('deauth/', 'Auth deauth');
|
||||||
|
|
||||||
|
|
||||||
$route = routerFind($router);
|
$route = routerFind($router);
|
||||||
if ($route === false)
|
if ($route === false)
|
||||||
|
24
localwebsite/templates-web/auth.twig
Normal file
24
localwebsite/templates-web/auth.twig
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{% include 'bc.twig' with {
|
||||||
|
history: [
|
||||||
|
{text: "Авторизация" }
|
||||||
|
]
|
||||||
|
} %}
|
||||||
|
|
||||||
|
{% if error %}
|
||||||
|
<div class="mt-4 alert alert-danger"><b>Ошибка:</b> {{ error }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
<form method="post" action="/auth/">
|
||||||
|
<div class="mt-2">
|
||||||
|
<input type="text" name="username" placeholder="Логин" class="form-control">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2">
|
||||||
|
<input type="password" name="password" placeholder="Пароль" class="form-control">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2">
|
||||||
|
<button type="submit" class="btn btn-outline-primary">Войти</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
12
localwebsite/templates-web/bc.twig
Normal file
12
localwebsite/templates-web/bc.twig
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/">Главная</a></li>
|
||||||
|
{% for item in history %}
|
||||||
|
<li class="breadcrumb-item"{% if loop.last %} aria-current="page"{% endif %}>
|
||||||
|
{% if item.link %}<a href="{{ item.link }}">{% endif %}
|
||||||
|
{{ item.html ? item.html|raw : item.text }}
|
||||||
|
{% if item.link %}</a>{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
</nav>
|
@ -1,9 +1,8 @@
|
|||||||
<nav aria-label="breadcrumb">
|
{% include 'bc.twig' with {
|
||||||
<ol class="breadcrumb">
|
history: [
|
||||||
<li class="breadcrumb-item"><a href="/">Главная</a></li>
|
{text: "Камеры" }
|
||||||
<li class="breadcrumb-item active" aria-current="page">Камеры</li>
|
]
|
||||||
</ol>
|
} %}
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div id="videos" class="camfeeds"></div>
|
<div id="videos" class="camfeeds"></div>
|
||||||
|
|
||||||
|
@ -5,6 +5,12 @@
|
|||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
{% if auth_user %}
|
||||||
|
<div class="mb-4 alert alert-secondary">
|
||||||
|
Вы авторизованы как <b>{{ auth_user.username }}</b>. <a href="/deauth/">Выйти</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<h6>Интернет</h6>
|
<h6>Интернет</h6>
|
||||||
<ul class="list-group list-group-flush">
|
<ul class="list-group list-group-flush">
|
||||||
<li class="list-group-item"><a href="/modem/status/">Состояние</a></li>
|
<li class="list-group-item"><a href="/modem/status/">Состояние</a></li>
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
<nav aria-label="breadcrumb">
|
{% include 'bc.twig' with {
|
||||||
<ol class="breadcrumb">
|
history: [
|
||||||
<li class="breadcrumb-item"><a href="/">Главная</a></li>
|
{text: "Инвертор" }
|
||||||
<li class="breadcrumb-item active" aria-current="page">Инвертор</li>
|
]
|
||||||
</ol>
|
} %}
|
||||||
</nav>
|
|
||||||
|
|
||||||
<h6 class="text-primary">Статус</h6>
|
<h6 class="text-primary">Статус</h6>
|
||||||
<div id="inverter_status">
|
<div id="inverter_status">
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
<nav aria-label="breadcrumb">
|
{% include 'bc.twig' with {
|
||||||
<ol class="breadcrumb">
|
history: [
|
||||||
<li class="breadcrumb-item"><a href="/">Главная</a></li>
|
{text: "Модемы" }
|
||||||
<li class="breadcrumb-item active" aria-current="page">Модемы</li>
|
]
|
||||||
</ol>
|
} %}
|
||||||
</nav>
|
|
||||||
|
|
||||||
{% for modem_key, modem in modems %}
|
{% for modem_key, modem in modems %}
|
||||||
<h6 class="text-primary{% if not loop.first %} mt-4{% endif %}">{{ modem.label }}</h6>
|
<h6 class="text-primary{% if not loop.first %} mt-4{% endif %}">{{ modem.label }}</h6>
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
<nav aria-label="breadcrumb">
|
{% include 'bc.twig' with {
|
||||||
<ol class="breadcrumb">
|
history: [
|
||||||
<li class="breadcrumb-item"><a href="/">Главная</a></li>
|
{text: "Насос" }
|
||||||
<li class="breadcrumb-item active" aria-current="page">Насос</li>
|
]
|
||||||
</ol>
|
} %}
|
||||||
</nav>
|
|
||||||
|
|
||||||
<form action="/pump/" method="get">
|
<form action="/pump/" method="get">
|
||||||
<input type="hidden" name="set" value="{{ status == 'on' ? 'off' : 'on' }}" />
|
<input type="hidden" name="set" value="{{ status == 'on' ? 'off' : 'on' }}" />
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
<nav aria-label="breadcrumb">
|
{% include 'bc.twig' with {
|
||||||
<ol class="breadcrumb">
|
history: [
|
||||||
<li class="breadcrumb-item"><a href="/">Главная</a></li>
|
{text: "Маршрутизация" }
|
||||||
<li class="breadcrumb-item active" aria-current="page">Маршрутизация</li>
|
]
|
||||||
</ol>
|
} %}
|
||||||
</nav>
|
|
||||||
|
|
||||||
{% set routing_tabs = [
|
{% set routing_tabs = [
|
||||||
{tab: 'smallhome', url: '/routing/', label: 'Маленький дом'},
|
{tab: 'smallhome', url: '/routing/', label: 'Маленький дом'},
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
<nav aria-label="breadcrumb">
|
{% include 'bc.twig' with {
|
||||||
<ol class="breadcrumb">
|
history: [
|
||||||
<li class="breadcrumb-item"><a href="/">Главная</a></li>
|
{text: "Датчики" }
|
||||||
<li class="breadcrumb-item active" aria-current="page">Датчики</li>
|
]
|
||||||
</ol>
|
} %}
|
||||||
</nav>
|
|
||||||
|
|
||||||
{% for key, sensor in sensors %}
|
{% for key, sensor in sensors %}
|
||||||
<h6 class="text-primary{% if not loop.first %} mt-4{% endif %}">{{ sensor.name }}</h6>
|
<h6 class="text-primary{% if not loop.first %} mt-4{% endif %}">{{ sensor.name }}</h6>
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
<nav aria-label="breadcrumb">
|
{% include 'bc.twig' with {
|
||||||
<ol class="breadcrumb">
|
history: [
|
||||||
<li class="breadcrumb-item"><a href="/">Главная</a></li>
|
{text: "SMS-сообщения" }
|
||||||
<li class="breadcrumb-item active" aria-current="page">SMS-сообщения</li>
|
]
|
||||||
</ol>
|
} %}
|
||||||
</nav>
|
|
||||||
|
|
||||||
<nav>
|
<nav>
|
||||||
<div class="nav nav-tabs" id="nav-tab">
|
<div class="nav nav-tabs" id="nav-tab">
|
||||||
|
66
localwebsite/utils.php
Executable file
66
localwebsite/utils.php
Executable file
@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__.'/init.php';
|
||||||
|
|
||||||
|
function read_stdin(?string $prompt = null, bool $multiline = true) {
|
||||||
|
if (!is_null($prompt))
|
||||||
|
echo $prompt;
|
||||||
|
|
||||||
|
if (!$multiline)
|
||||||
|
return trim(fgets(STDIN));
|
||||||
|
|
||||||
|
$fp = fopen('php://stdin', 'r');
|
||||||
|
$data = stream_get_contents($fp);
|
||||||
|
fclose($fp);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function usage() {
|
||||||
|
global $argv;
|
||||||
|
echo <<<EOF
|
||||||
|
usage: {$argv[0]} COMMAND
|
||||||
|
|
||||||
|
Supported commands:
|
||||||
|
add-user
|
||||||
|
change-password
|
||||||
|
|
||||||
|
EOF;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($argv[1]))
|
||||||
|
usage();
|
||||||
|
|
||||||
|
switch ($argv[1]) {
|
||||||
|
case 'add-user':
|
||||||
|
$username = read_stdin('enter username: ', false);
|
||||||
|
$password = read_stdin('enter password: ', false);
|
||||||
|
|
||||||
|
if (users::exists($username)) {
|
||||||
|
fwrite(STDERR, "user already exists\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = users::add($username, $password);
|
||||||
|
echo "added user, id = $id\n";
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'change-password':
|
||||||
|
$id = (int)read_stdin('enter ID: ', false);
|
||||||
|
if (!$id)
|
||||||
|
die("invalid id\n");
|
||||||
|
|
||||||
|
$password = read_stdin('enter new password: ', false);
|
||||||
|
if (!$password)
|
||||||
|
die("invalid password\n");
|
||||||
|
|
||||||
|
users::setPassword($id, $password);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
fwrite(STDERR, "invalid command\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user