Add RememberMe authentication
This commit is contained in:
parent
0146e96fcf
commit
7e553f72fd
@ -23,12 +23,14 @@ require __DIR__.'/models/item.php';
|
|||||||
require __DIR__.'/models/schema.php';
|
require __DIR__.'/models/schema.php';
|
||||||
require __DIR__.'/models/auto_update.php';
|
require __DIR__.'/models/auto_update.php';
|
||||||
require __DIR__.'/models/database.php';
|
require __DIR__.'/models/database.php';
|
||||||
|
require __DIR__.'/models/remember_me.php';
|
||||||
|
|
||||||
if (file_exists('config.php')) require 'config.php';
|
if (file_exists('config.php')) require 'config.php';
|
||||||
|
|
||||||
defined('APP_VERSION') or define('APP_VERSION', 'master');
|
defined('APP_VERSION') or define('APP_VERSION', 'master');
|
||||||
defined('HTTP_TIMEOUT') or define('HTTP_TIMEOUT', 20);
|
defined('HTTP_TIMEOUT') or define('HTTP_TIMEOUT', 20);
|
||||||
|
|
||||||
|
defined('BASE_URL_DIRECTORY') or define('BASE_URL_DIRECTORY', dirname($_SERVER['PHP_SELF']));
|
||||||
defined('ROOT_DIRECTORY') or define('ROOT_DIRECTORY', __DIR__);
|
defined('ROOT_DIRECTORY') or define('ROOT_DIRECTORY', __DIR__);
|
||||||
defined('DATA_DIRECTORY') or define('DATA_DIRECTORY', 'data');
|
defined('DATA_DIRECTORY') or define('DATA_DIRECTORY', 'data');
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ use PicoFarad\Template;
|
|||||||
// Called before each action
|
// Called before each action
|
||||||
Router\before(function($action) {
|
Router\before(function($action) {
|
||||||
|
|
||||||
Session\open(dirname($_SERVER['PHP_SELF']), SESSION_SAVE_PATH);
|
Session\open(BASE_URL_DIRECTORY, SESSION_SAVE_PATH);
|
||||||
|
|
||||||
// Select another database
|
// Select another database
|
||||||
if (! empty($_SESSION['database'])) {
|
if (! empty($_SESSION['database'])) {
|
||||||
@ -20,8 +20,14 @@ Router\before(function($action) {
|
|||||||
$ignore_actions = array('login', 'google-auth', 'google-redirect-auth', 'mozilla-auth', 'bookmark-feed', 'select-db');
|
$ignore_actions = array('login', 'google-auth', 'google-redirect-auth', 'mozilla-auth', 'bookmark-feed', 'select-db');
|
||||||
|
|
||||||
if (! isset($_SESSION['user']) && ! in_array($action, $ignore_actions)) {
|
if (! isset($_SESSION['user']) && ! in_array($action, $ignore_actions)) {
|
||||||
|
|
||||||
|
if (! Model\RememberMe\authenticate()) {
|
||||||
Response\redirect('?action=login');
|
Response\redirect('?action=login');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else if (Model\RememberMe\has_cookie()) {
|
||||||
|
Model\RememberMe\refresh();
|
||||||
|
}
|
||||||
|
|
||||||
// Load translations
|
// Load translations
|
||||||
$language = Model\Config\get('language') ?: 'en_US';
|
$language = Model\Config\get('language') ?: 'en_US';
|
||||||
|
@ -11,6 +11,7 @@ use PicoFarad\Template;
|
|||||||
// Logout and destroy session
|
// Logout and destroy session
|
||||||
Router\get_action('logout', function() {
|
Router\get_action('logout', function() {
|
||||||
|
|
||||||
|
Model\RememberMe\destroy();
|
||||||
Session\close();
|
Session\close();
|
||||||
Response\redirect('?action=login');
|
Response\redirect('?action=login');
|
||||||
});
|
});
|
||||||
@ -18,7 +19,9 @@ Router\get_action('logout', function() {
|
|||||||
// Display form login
|
// Display form login
|
||||||
Router\get_action('login', function() {
|
Router\get_action('login', function() {
|
||||||
|
|
||||||
if (isset($_SESSION['user'])) Response\redirect('?action=unread');
|
if (isset($_SESSION['user'])) {
|
||||||
|
Response\redirect('?action=unread');
|
||||||
|
}
|
||||||
|
|
||||||
Response\html(Template\load('login', array(
|
Response\html(Template\load('login', array(
|
||||||
'google_auth_enable' => Model\Config\get('auth_google_token') !== '',
|
'google_auth_enable' => Model\Config\get('auth_google_token') !== '',
|
||||||
@ -36,7 +39,9 @@ Router\post_action('login', function() {
|
|||||||
$values = Request\values();
|
$values = Request\values();
|
||||||
list($valid, $errors) = Model\User\validate_login($values);
|
list($valid, $errors) = Model\User\validate_login($values);
|
||||||
|
|
||||||
if ($valid) Response\redirect('?action=unread');
|
if ($valid) {
|
||||||
|
Response\redirect('?action=unread');
|
||||||
|
}
|
||||||
|
|
||||||
Response\html(Template\load('login', array(
|
Response\html(Template\load('login', array(
|
||||||
'google_auth_enable' => Model\Config\get('auth_google_token') !== '',
|
'google_auth_enable' => Model\Config\get('auth_google_token') !== '',
|
||||||
|
@ -223,4 +223,6 @@ return array(
|
|||||||
'Unable to create the new database.' => 'Impossible créer la nouvelle base de données.',
|
'Unable to create the new database.' => 'Impossible créer la nouvelle base de données.',
|
||||||
'Add a new database (new user)' => 'Ajouter une nouvelle base de données (nouvel utilisateur)',
|
'Add a new database (new user)' => 'Ajouter une nouvelle base de données (nouvel utilisateur)',
|
||||||
'Create' => 'Créer',
|
'Create' => 'Créer',
|
||||||
|
'Unknown' => 'Inconnu',
|
||||||
|
'Remember Me' => 'Connexion automatique',
|
||||||
);
|
);
|
||||||
|
@ -8,7 +8,7 @@ use PicoDb\Database;
|
|||||||
use PicoFeed\Config as ReaderConfig;
|
use PicoFeed\Config as ReaderConfig;
|
||||||
use PicoFeed\Logging;
|
use PicoFeed\Logging;
|
||||||
|
|
||||||
const DB_VERSION = 24;
|
const DB_VERSION = 25;
|
||||||
const HTTP_USER_AGENT = 'Miniflux (http://miniflux.net)';
|
const HTTP_USER_AGENT = 'Miniflux (http://miniflux.net)';
|
||||||
|
|
||||||
// Get PicoFeed config
|
// Get PicoFeed config
|
||||||
@ -297,3 +297,48 @@ function save(array $values)
|
|||||||
|
|
||||||
return Database::get('db')->table('config')->update($values);
|
return Database::get('db')->table('config')->update($values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the user agent of the connected user
|
||||||
|
function get_user_agent()
|
||||||
|
{
|
||||||
|
return empty($_SERVER['HTTP_USER_AGENT']) ? t('Unknown') : $_SERVER['HTTP_USER_AGENT'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the real IP address of the connected user
|
||||||
|
function get_ip_address($only_public = false)
|
||||||
|
{
|
||||||
|
$keys = array(
|
||||||
|
'HTTP_CLIENT_IP',
|
||||||
|
'HTTP_X_FORWARDED_FOR',
|
||||||
|
'HTTP_X_FORWARDED',
|
||||||
|
'HTTP_X_CLUSTER_CLIENT_IP',
|
||||||
|
'HTTP_FORWARDED_FOR',
|
||||||
|
'HTTP_FORWARDED',
|
||||||
|
'REMOTE_ADDR'
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
|
||||||
|
if (isset($_SERVER[$key])) {
|
||||||
|
|
||||||
|
foreach (explode(',', $_SERVER[$key]) as $ip_address) {
|
||||||
|
|
||||||
|
$ip_address = trim($ip_address);
|
||||||
|
|
||||||
|
if ($only_public) {
|
||||||
|
|
||||||
|
// Return only public IP address
|
||||||
|
if (filter_var($ip_address, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
|
||||||
|
return $ip_address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
return $ip_address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t('Unknown');
|
||||||
|
}
|
||||||
|
307
models/remember_me.php
Normal file
307
models/remember_me.php
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Model\RememberMe;
|
||||||
|
|
||||||
|
use PicoDb\Database;
|
||||||
|
use Model\Config;
|
||||||
|
use Model\User;
|
||||||
|
use Model\Database as DatabaseModel;
|
||||||
|
|
||||||
|
const TABLE = 'remember_me';
|
||||||
|
const COOKIE_NAME = '_R_';
|
||||||
|
const EXPIRATION = 5184000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a remember me record
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
function find($token, $sequence)
|
||||||
|
{
|
||||||
|
return Database::get('db')
|
||||||
|
->table(TABLE)
|
||||||
|
->eq('token', $token)
|
||||||
|
->eq('sequence', $sequence)
|
||||||
|
->gt('expiration', time())
|
||||||
|
->findOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all sessions
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
function get_all()
|
||||||
|
{
|
||||||
|
return Database::get('db')
|
||||||
|
->table(TABLE)
|
||||||
|
->desc('date_creation')
|
||||||
|
->columns('id', 'ip', 'user_agent', 'date_creation', 'expiration')
|
||||||
|
->findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticate the user with the cookie
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function authenticate()
|
||||||
|
{
|
||||||
|
$credentials = read_cookie();
|
||||||
|
|
||||||
|
if ($credentials !== false) {
|
||||||
|
|
||||||
|
$record = find($credentials['token'], $credentials['sequence']);
|
||||||
|
|
||||||
|
if ($record) {
|
||||||
|
|
||||||
|
// Update the sequence
|
||||||
|
write_cookie(
|
||||||
|
$record['token'],
|
||||||
|
update($record['token'], $record['sequence']),
|
||||||
|
$record['expiration']
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create the session
|
||||||
|
$_SESSION['user'] = User\get($record['username']);
|
||||||
|
$_SESSION['config'] = Config\get_all();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the database and the cookie with a new sequence
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
function refresh()
|
||||||
|
{
|
||||||
|
$credentials = read_cookie();
|
||||||
|
|
||||||
|
if ($credentials !== false) {
|
||||||
|
|
||||||
|
$record = find($credentials['token'], $credentials['sequence']);
|
||||||
|
|
||||||
|
if ($record) {
|
||||||
|
|
||||||
|
// Update the sequence
|
||||||
|
write_cookie(
|
||||||
|
$record['token'],
|
||||||
|
update($record['token'], $record['sequence']),
|
||||||
|
$record['expiration']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a session record
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param integer $session_id Session id
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
function remove($session_id)
|
||||||
|
{
|
||||||
|
return Database::get('db')
|
||||||
|
->table(TABLE)
|
||||||
|
->eq('id', $session_id)
|
||||||
|
->remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the current RememberMe session and the cookie
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param integer $user_id User id
|
||||||
|
*/
|
||||||
|
function destroy()
|
||||||
|
{
|
||||||
|
$credentials = read_cookie();
|
||||||
|
|
||||||
|
if ($credentials !== false) {
|
||||||
|
|
||||||
|
delete_cookie();
|
||||||
|
|
||||||
|
Database::get('db')
|
||||||
|
->table(TABLE)
|
||||||
|
->eq('token', $credentials['token'])
|
||||||
|
->remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new RememberMe session
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param integer $dbname Database name
|
||||||
|
* @param integer $username Username
|
||||||
|
* @param string $ip IP Address
|
||||||
|
* @param string $user_agent User Agent
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
function create($dbname, $username, $ip, $user_agent)
|
||||||
|
{
|
||||||
|
$token = hash('sha256', $dbname.$username.$user_agent.$ip.Config\generate_token());
|
||||||
|
$sequence = Config\generate_token();
|
||||||
|
$expiration = time() + EXPIRATION;
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
|
||||||
|
Database::get('db')
|
||||||
|
->table(TABLE)
|
||||||
|
->insert(array(
|
||||||
|
'username' => $username,
|
||||||
|
'ip' => $ip,
|
||||||
|
'user_agent' => $user_agent,
|
||||||
|
'token' => $token,
|
||||||
|
'sequence' => $sequence,
|
||||||
|
'expiration' => $expiration,
|
||||||
|
'date_creation' => time(),
|
||||||
|
));
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'token' => $token,
|
||||||
|
'sequence' => $sequence,
|
||||||
|
'expiration' => $expiration,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove old sessions
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function cleanup()
|
||||||
|
{
|
||||||
|
return Database::get('db')
|
||||||
|
->table(TABLE)
|
||||||
|
->lt('expiration', time())
|
||||||
|
->remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new sequence token and update the database
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $token Session token
|
||||||
|
* @param string $sequence Sequence token
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function update($token, $sequence)
|
||||||
|
{
|
||||||
|
$new_sequence = Config\generate_token();
|
||||||
|
|
||||||
|
Database::get('db')
|
||||||
|
->table(TABLE)
|
||||||
|
->eq('token', $token)
|
||||||
|
->eq('sequence', $sequence)
|
||||||
|
->update(array('sequence' => $new_sequence));
|
||||||
|
|
||||||
|
return $new_sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode the cookie
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $token Session token
|
||||||
|
* @param string $sequence Sequence token
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function encode_cookie($token, $sequence)
|
||||||
|
{
|
||||||
|
return implode('|', array(base64_encode(DatabaseModel\select()), $token, $sequence));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode the value of a cookie
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $value Raw cookie data
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
function decode_cookie($value)
|
||||||
|
{
|
||||||
|
@list($database, $token, $sequence) = explode('|', $value);
|
||||||
|
|
||||||
|
DatabaseModel\select(base64_decode($database));
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'token' => $token,
|
||||||
|
'sequence' => $sequence,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the current user has a RememberMe cookie
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function has_cookie()
|
||||||
|
{
|
||||||
|
return ! empty($_COOKIE[COOKIE_NAME]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write and encode the cookie
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $token Session token
|
||||||
|
* @param string $sequence Sequence token
|
||||||
|
* @param string $expiration Cookie expiration
|
||||||
|
*/
|
||||||
|
function write_cookie($token, $sequence, $expiration)
|
||||||
|
{
|
||||||
|
setcookie(
|
||||||
|
COOKIE_NAME,
|
||||||
|
encode_cookie($token, $sequence),
|
||||||
|
$expiration,
|
||||||
|
BASE_URL_DIRECTORY,
|
||||||
|
null,
|
||||||
|
! empty($_SERVER['HTTPS']),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read and decode the cookie
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
function read_cookie()
|
||||||
|
{
|
||||||
|
if (empty($_COOKIE[COOKIE_NAME])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return decode_cookie($_COOKIE[COOKIE_NAME]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the cookie
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
function delete_cookie()
|
||||||
|
{
|
||||||
|
setcookie(
|
||||||
|
COOKIE_NAME,
|
||||||
|
'',
|
||||||
|
time() - 3600,
|
||||||
|
BASE_URL_DIRECTORY,
|
||||||
|
null,
|
||||||
|
! empty($_SERVER['HTTPS']),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
@ -2,6 +2,24 @@
|
|||||||
|
|
||||||
namespace Schema;
|
namespace Schema;
|
||||||
|
|
||||||
|
|
||||||
|
function version_25($pdo)
|
||||||
|
{
|
||||||
|
$pdo->exec(
|
||||||
|
'CREATE TABLE remember_me (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
username TEXT,
|
||||||
|
ip TEXT,
|
||||||
|
user_agent TEXT,
|
||||||
|
token TEXT,
|
||||||
|
sequence TEXT,
|
||||||
|
expiration INTEGER,
|
||||||
|
date_creation INTEGER
|
||||||
|
)'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function version_24($pdo)
|
function version_24($pdo)
|
||||||
{
|
{
|
||||||
$pdo->exec("ALTER TABLE config ADD COLUMN auto_update_url TEXT DEFAULT 'https://github.com/fguillot/miniflux/archive/master.zip'");
|
$pdo->exec("ALTER TABLE config ADD COLUMN auto_update_url TEXT DEFAULT 'https://github.com/fguillot/miniflux/archive/master.zip'");
|
||||||
|
@ -5,6 +5,9 @@ namespace Model\User;
|
|||||||
use SimpleValidator\Validator;
|
use SimpleValidator\Validator;
|
||||||
use SimpleValidator\Validators;
|
use SimpleValidator\Validators;
|
||||||
use PicoDb\Database;
|
use PicoDb\Database;
|
||||||
|
use Model\Config;
|
||||||
|
use Model\RememberMe;
|
||||||
|
use Model\Database as DatabaseModel;
|
||||||
|
|
||||||
// Get a user by username
|
// Get a user by username
|
||||||
function get($username)
|
function get($username)
|
||||||
@ -37,7 +40,13 @@ function validate_login(array $values)
|
|||||||
unset($user['password']);
|
unset($user['password']);
|
||||||
|
|
||||||
$_SESSION['user'] = $user;
|
$_SESSION['user'] = $user;
|
||||||
$_SESSION['config'] = \Model\Config\get_all();
|
$_SESSION['config'] = Config\get_all();
|
||||||
|
|
||||||
|
// Setup the remember me feature
|
||||||
|
if (! empty($values['remember_me'])) {
|
||||||
|
$credentials = RememberMe\create(DatabaseModel\select(), $values['username'], Config\get_ip_address(), Config\get_user_agent());
|
||||||
|
RememberMe\write_cookie($credentials['token'], $credentials['sequence'], $credentials['expiration']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
|
@ -36,6 +36,8 @@
|
|||||||
<?= Helper\form_label(t('Password'), 'password') ?>
|
<?= Helper\form_label(t('Password'), 'password') ?>
|
||||||
<?= Helper\form_password('password', $values, $errors, array('required')) ?>
|
<?= Helper\form_password('password', $values, $errors, array('required')) ?>
|
||||||
|
|
||||||
|
<?= Helper\form_checkbox('remember_me', t('Remember Me'), 1) ?><br/>
|
||||||
|
|
||||||
<?php if ($google_auth_enable): ?>
|
<?php if ($google_auth_enable): ?>
|
||||||
<p><br/><a href="?action=google-redirect-auth"><?= t('Login with my Google Account') ?></a></p>
|
<p><br/><a href="?action=google-redirect-auth"><?= t('Login with my Google Account') ?></a></p>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
Loading…
Reference in New Issue
Block a user