diff --git a/.gitignore b/.gitignore index ea276b4..88ea054 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ server/config.php +api/config.php .htaccess diff --git a/api/.htaccess_example b/api/.htaccess_example new file mode 100644 index 0000000..a02d3cc --- /dev/null +++ b/api/.htaccess_example @@ -0,0 +1,8 @@ +RewriteEngine on +# root directory: +RewriteBase /projects/RegattenApp/api/ + + + +# Show site +RewriteRule ^(.*)$ index.php?request=$1 [QSA] \ No newline at end of file diff --git a/api/config_example.php b/api/config_example.php new file mode 100644 index 0000000..bcd2ceb --- /dev/null +++ b/api/config_example.php @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/api/database.php b/api/database.php new file mode 100644 index 0000000..108d3c1 --- /dev/null +++ b/api/database.php @@ -0,0 +1,157 @@ +num_rows > 0) { + $i = 0; + while ($row = $response->fetch_assoc()) { + if (isset($row['id'])) { + $id = $row['id']; + } else { + $id = $i; + $i ++; + } + foreach ($row as $key => $value) { + $result[$id][$key] = $value; + } + } + } + return $result; + } else { + logE("database", "get_data\nInvalid request\n" . $query . "\n" . mysqli_error($mysqli)); + return false; + } + } + + function db_update_data($mysqli, $table, $data, $where, $limit = false) { + $rest = ''; + if ($where != false) { + $rest .= ' WHERE ' . $where; + } + if ($limit != false) { + $rest .= sprintf(' LIMIT %d', $limit); + } + $set = ''; + $first = true; + foreach ($data as $key => $value) { + if ($first) { + $first = false; + } else { + $set .= ', '; + } + if ($value === null) { + $set .= '`' . mysqli_real_escape_string($mysqli, $key) . '`=NULL'; + } else { + $set .= '`' . mysqli_real_escape_string($mysqli, $key) . '`="' . mysqli_real_escape_string($mysqli, $value) . '"'; + } + } + if (defined('DB_CHANGE_TIME')) $set .= ', `changed`=NOW()'; + $query = 'UPDATE ' . mysqli_real_escape_string($mysqli, $table) . ' SET ' . $set . $rest . ';'; + $response = mysqli_query($mysqli, $query); + + if ($response === false) { + logE("database", "update_data\nInvalid request\n" . $query . "\n" . mysqli_error($mysqli)); + } elseif (defined('DB_CHANGE_TIME')) { + mysqli_query($mysqli, 'UPDATE `_updatetimes` SET `update`=NOW() WHERE `table`="' . mysqli_real_escape_string($mysqli, $table) . '";'); + } + + return $response; + } + + function db_insert_data($mysqli, $table, $data) { + $fields = ''; + $values = ''; + $first = true; + foreach ($data as $key => $value) { + if ($first) { + $first = false; + } else { + $fields .= ', '; + $values .= ', '; + } + $fields .= '`' . mysqli_real_escape_string($mysqli, $key) . '`'; + if ($value === null) { + $values .= 'NULL'; + } else { + $values .= '"' . mysqli_real_escape_string($mysqli, $value) . '"'; + } + } + if (defined('DB_CHANGE_TIME')) { + $fields .= ', `changed`'; + $values .= ', NOW()'; + } + $query = 'INSERT INTO `' . mysqli_real_escape_string($mysqli, $table) . '` (' . $fields . ') VALUES (' . $values . ');'; + $response = mysqli_query($mysqli, $query); + if ($response === false) { + logE("database", "insert_data\nInvalid request\n" . $query . "\n" . mysqli_error($mysqli)); + } else { + $response = mysqli_insert_id($mysqli); + if (defined('DB_CHANGE_TIME')) { + mysqli_query($mysqli, 'UPDATE `_updatetimes` SET `update`=NOW() WHERE `table`="' . mysqli_real_escape_string($mysqli, $table) . '";'); + } + } + + return $response; + } + + function db_delete_data($mysqli, $table, $where, $limit = false) { + $rest = ''; + if ($where != false) { + $rest .= ' WHERE ' . $where; + } + if ($limit != false) { + $rest .= sprintf(' LIMIT %d', $limit); + } + $query = 'DELETE FROM `' . mysqli_real_escape_string($mysqli, $table) . '`' . $rest . ';'; + $response = mysqli_query($mysqli, $query); + if ($response === false) { + logE("database", "delete_data\nInvalid request\n" . $query . "\n" . mysqli_error($mysqli)); + } elseif (defined('DB_CHANGE_TIME')) { + mysqli_query($mysqli, 'UPDATE `_updatetimes` SET `update`=NOW() WHERE `table`="' . mysqli_real_escape_string($mysqli, $table) . '";'); + } + + return $response; + } + +?> \ No newline at end of file diff --git a/api/index.php b/api/index.php new file mode 100644 index 0000000..4102512 --- /dev/null +++ b/api/index.php @@ -0,0 +1,123 @@ += 1) { + $action = array_shift($request); + } else { + $action = ''; + } + + define('DONE_OKAY', 0); + define('DONE_EMPTY', 1); + define('DONE_DATABASE', 2); + define('DONE_UNAUTHORIZED', 3); + define('DONE_BAD_REQUEST', 4); + define('DONE_CONFLICT', 5); + define('DONE_SERVER_ERROR', 6); + function done($donecode, $content = null) { + switch ($donecode) { + case DONE_OKAY: + header('HTTP/1.0 200 OK'); + break; + case DONE_EMPTY: + header('HTTP/1.0 204 No Content'); + break; + case DONE_DATABASE: + header('HTTP/1.0 500 Internal Server Error'); + if ($content === null) { + $content = array('error' => 'database error'); + } + break; + case DONE_UNAUTHORIZED: + header('HTTP/1.0 401 Unauthorized'); + if ($content === null) { + $content = array('error' => 'unauthorized'); + } + break; + case DONE_BAD_REQUEST: + header('HTTP/1.0 400 Bad Request'); + if ($content === null) { + $content = array('error' => 'bad request'); + } + break; + case DONE_CONFLICT: + header('HTTP/1.0 409 Conflict'); + break; + case DONE_SERVER_ERROR: + header('HTTP/1.0 500 Internal Server Error'); + break; + default: + header('HTTP/1.0 500 Internal Server Error'); + break; + } + header('Content-Type: application/json'); + if ($content !== null) { + echo json_encode($content); + } else { + echo '{ }'; + } + exit; + } + + if (isset($_REQUEST['auth']['id'], $_REQUEST['auth']['hash'])) { + $user_id = auth_check($mysqli, $_REQUEST['auth']['id'], $_REQUEST['auth']['hash']); + } else { + $user_id = false; + } + $perm = get_perm($mysqli, $user_id); + + function has_perm($permission) { + global $perm; + return ($perm & $permission) == $permission; + } + + function checkPermission($perm) { + if (!has_perm($perm)) done(DONE_UNAUTHORIZED, ['error' => 'permission denied']); + } + + function checkRequest($param) { + if (!isset($_REQUEST[$param])) done(DONE_BAD_REQUEST, ['error' => 'missing parameter: ' . $param]); + } + + function replaceChanged($array) { + return array_map(function ($entry) { + unset($entry['changed']); + return $entry; + }, $array); + } + + switch ($action) { + + case 'login': + checkRequest('username'); + checkRequest('password'); + checkRequest('device'); + $auth = auth_login($mysqli, $_REQUEST['username'], $_REQUEST['password'], $_REQUEST['device']); + if ($auth === false) done(DONE_UNAUTHORIZED); + done(DONE_OKAY, $auth); + break; + + case 'logout': + checkPermission(PERM_REGISTERED); + auth_logout($mysqli, $_REQUEST['auth']['id']); + done(DONE_OKAY); + break; + + default: + done(DONE_BAD_REQUEST, ['error' => 'action invalid']); + + } + +?> \ No newline at end of file diff --git a/api/login.php b/api/login.php new file mode 100644 index 0000000..852f82a --- /dev/null +++ b/api/login.php @@ -0,0 +1,107 @@ + $user['id'], + 'salt' => $salt, + 'authhash' => $hash, + 'device' => $device + ]; + $auth['id'] = db_insert_data($mysqli, DB_TABLE_LOGINS, $data); + return $auth; + } + + function auth_logout($mysqli, $id) { + db_delete_data($mysqli, DB_TABLE_LOGINS, 'id = "' . mysqli_real_escape_string($mysqli, $id) . '"', 1); + return true; + } + + function auth_check($mysqli, $id, $hash) { + $auth = db_get_data($mysqli, DB_TABLE_LOGINS, '*', 'id="' . mysqli_real_escape_string($mysqli, $id) . '"', 1); + if (($auth === false) or (count($auth) != 1)) return false; + $auth = array_values($auth)[0]; + $hash = hash('sha512', $hash . $auth['salt']); + if ($hash != $auth['authhash']) return false; + db_update_data($mysqli, DB_TABLE_LOGINS, ['id' => $auth['id']], 'id="' . $auth['id'] . '"', 1); // update changed field => last login + return $auth['user']; + } + +?> \ No newline at end of file diff --git a/client/scripts/custom.js.php b/client/scripts/custom.js.php index 6adc5d4..51229a1 100644 --- a/client/scripts/custom.js.php +++ b/client/scripts/custom.js.php @@ -31,7 +31,7 @@ $(document).ready(function(){ 'use strict' var isAJAX = true; //Enables or disable AJAX page transitions and loading. - var isDevelopment = true; // Enables development mode. Clean cache & Stops BG & Highlights from changing defaults. + var isDevelopment = false; // Enables development mode. Clean cache & Stops BG & Highlights from changing defaults. function init_template(){ diff --git a/client/scripts/pwa.js.php b/client/scripts/pwa.js.php index f306efc..7b35cfc 100644 --- a/client/scripts/pwa.js.php +++ b/client/scripts/pwa.js.php @@ -18,7 +18,7 @@ $(document).ready(function(){ var pwaVersion = ''; //must be identical to _manifest.json version. If not it will create update window loop var pwaCookie = true; // if set to false, the PWA prompt will appear even if the user selects "maybe later" - var pwaNoCache = true; // always keep the cache clear to serve the freshest possible content + var pwaNoCache = false; // always keep the cache clear to serve the freshest possible content $('[data-pwa-version]').data('pwa-version', pwaVersion); @@ -132,7 +132,7 @@ $(document).ready(function(){ caches.delete('workbox-runtime').then(function() { console.log('Content Updated - Cache Removed!'); }); - localStorage.clear(); + //localStorage.clear(); sessionStorage.clear() caches.keys().then(cacheNames => { cacheNames.forEach(cacheName => { diff --git a/client/scripts/regatten.js.php b/client/scripts/regatten.js.php index bb702e1..1885768 100644 --- a/client/scripts/regatten.js.php +++ b/client/scripts/regatten.js.php @@ -6,6 +6,8 @@ ?> +const apiUrl = '/api/'; + var randomId = function() { return '_' + Math.random().toString(36).substr(2, 9); } var badges = { @@ -96,8 +98,115 @@ var toastWarn = function (text, time = 3000) { return makeToast('bg-yellow1-dar var toastInfo = function (text, time = 3000) { return makeToast('bg-blue2-dark', 'fa-info', text, time); } var toastError = function (text, time = 3000) { return makeToast('bg-red2-dark', 'fa-times', text, time); } +var login = function() { + showLoader(); + var username = $('#input-login-username').val(); + var password = $('#input-login-password').val(); + $('#input-login-username').val(''); + $('#input-login-password').val(''); + $.ajax({ + url: apiUrl + 'login', + method: 'POST', + data: { + username: username, + password: password, + device: navigator.userAgent + }, + error: function (xhr, status, error) { + if (xhr.status == 401) { + toastError('Benutzername oder Passwort falsch'); + $('#input-login-username').val(username); + } else if (xhr.status == 0) { + toastError('Du bist momentan offline.
Stelle eine Internetverbindung her, um Dich anzumelden'); + $('#menu-login').hideMenu(); + } else { + console.log('Login: unbekannter Fehler', status, error); + console.log(xhr); + toastError('Ein unbekannter Fehler ist aufgetreten. Bitte versuche es noch einmal', 5000); + } + hideLoader(); + }, + success: function (data, status, xhr) { + localStorage.setItem('auth_id', data.id); + localStorage.setItem('auth_hash', data.auth); + localStorage.setItem('auth_user', data.user); + localStorage.setItem('auth_username', data.username); + location.reload(); + } + }); +} + +var logoutClearStorage = function() { + localStorage.removeItem('auth_id'); + localStorage.removeItem('auth_hash'); + localStorage.removeItem('auth_user'); + localStorage.removeItem('auth_username'); + location.reload(); +} + +var logout = function() { + showLoader(); + var auth = { + id: localStorage.getItem('auth_id'), + hash: localStorage.getItem('auth_hash') + } + if ((auth.id === null) || (auth.hash === null)) { + console.log('Not logged in'); + logoutClearStorage(); + return; + } + $.ajax({ + url: apiUrl + 'logout', + method: 'POST', + data: { + auth: auth + }, + error: function (xhr, status, error) { + if (xhr.status == 401) { + console.log('Not logged in'); + logoutClearStorage(); + } else if (xhr.status == 0) { + console.log('Could not delete auth from server'); + logoutClearStorage(); + } else { + console.log('Logout: unbekannter Fehler', status, error); + console.log(xhr); + toastError('Ein unbekannter Fehler ist aufgetreten. Bitte versuche es noch einmal', 5000); + hideLoader(); + } + }, + success: function (data, status, xhr) { + logoutClearStorage(); + } + }); +} + var initRegatten = function() { - loggedin = true; + loggedin = (localStorage.getItem('auth_id') !== null); + + if (loggedin) { + var auth = { + id: localStorage.getItem('auth_id'), + hash: localStorage.getItem('auth_hash') + } + var user = { + id: localStorage.getItem('auth_user'), + name: localStorage.getItem('auth_username') + } + if ((auth.hash === null) || (user.id === null) || (user.name === null)) { + logoutClearStorage(); + return; + } + } + + if (loggedin) { + $('.show-notloggedin').css('display', 'none'); + $('.replace-userid-href').attr('href', $('.replace-userid-href').attr('href').replace('%USERID%', user.id)); + $('.replace-username').html(user.name); + } else { + $('.show-loggedin').css('display', 'none'); + } + if (typeof siteScript !== 'undefined') { siteScript(); } diff --git a/content/index.php b/content/index.php index 09047f8..9baa79b 100644 --- a/content/index.php +++ b/content/index.php @@ -35,11 +35,14 @@ $sp['output'] .= $tpl->load('card', [$content, 'html-id' => 'card-last']); // Calendar - $content = "

Regatta-Kalender

"; - $content .= "

Du willst alle Regatta-Termine in deinem Kalender, aber nicht alles abtippen?
Kein Problem! Abonniere einfach unseren ics-Kalender.

"; - $content .= "

Nur die Regatten, zu denen Du gehst?
Auch kein Problem! Erstelle einfach eine Saison-Planung und abonniere Deinen persönlichen Kalender.

"; - $content .= $tpl->load('button', ['Regatta-Kalender', '#', 'css-class' => 'mb-2']); - $content .= $tpl->load('button', ['Kalender für Timon', '#']); + $content = '

Regatta-Kalender

'; + $content .= '

Du willst alle Regatta-Termine in deinem Kalender, aber nicht alles abtippen?
Kein Problem! Abonniere einfach unseren ics-Kalender.

'; + $content .= '

Nur die Regatten, zu denen Du gehst?
Auch kein Problem! '; + $content .= 'Erstelle einfach eine Saison-Planung und abonniere Deinen persönlichen Kalender.'; + $content .= 'Registriere Dich einfach kostenlos, erstelle eine Saison-Planung und wir erstellen Dir einen persönlichen Kalender.'; + $content .= '

'; + $content .= $tpl->load('button', [' Regatta-Kalender', 'https://regatten.net/client/calendar/' . BOATCLASS . '/everything.ics', 'css-class' => 'mb-2']); + $content .= $tpl->load('button', [' Kalender für ', 'https://regatten.net/client/calendar/' . BOATCLASS . '/user_%USERID%.ics', 'css-class' => 'show-loggedin replace-userid-href']); $sp['output'] .= $tpl->load('card', [$content]); diff --git a/server/page/menus.php b/server/page/menus.php index fb062c3..fe8cb79 100644 --- a/server/page/menus.php +++ b/server/page/menus.php @@ -105,20 +105,59 @@ - + Login - + Registrieren FREE + + + Account + + + + + Logout + + + + + +