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/auto_update.php';
|
||||
require __DIR__.'/models/database.php';
|
||||
require __DIR__.'/models/remember_me.php';
|
||||
|
||||
if (file_exists('config.php')) require 'config.php';
|
||||
|
||||
defined('APP_VERSION') or define('APP_VERSION', 'master');
|
||||
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('DATA_DIRECTORY') or define('DATA_DIRECTORY', 'data');
|
||||
|
||||
|
@ -9,7 +9,7 @@ use PicoFarad\Template;
|
||||
// Called before each 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
|
||||
if (! empty($_SESSION['database'])) {
|
||||
@ -20,7 +20,13 @@ Router\before(function($action) {
|
||||
$ignore_actions = array('login', 'google-auth', 'google-redirect-auth', 'mozilla-auth', 'bookmark-feed', 'select-db');
|
||||
|
||||
if (! isset($_SESSION['user']) && ! in_array($action, $ignore_actions)) {
|
||||
Response\redirect('?action=login');
|
||||
|
||||
if (! Model\RememberMe\authenticate()) {
|
||||
Response\redirect('?action=login');
|
||||
}
|
||||
}
|
||||
else if (Model\RememberMe\has_cookie()) {
|
||||
Model\RememberMe\refresh();
|
||||
}
|
||||
|
||||
// Load translations
|
||||
|
@ -11,6 +11,7 @@ use PicoFarad\Template;
|
||||
// Logout and destroy session
|
||||
Router\get_action('logout', function() {
|
||||
|
||||
Model\RememberMe\destroy();
|
||||
Session\close();
|
||||
Response\redirect('?action=login');
|
||||
});
|
||||
@ -18,7 +19,9 @@ Router\get_action('logout', function() {
|
||||
// Display form login
|
||||
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(
|
||||
'google_auth_enable' => Model\Config\get('auth_google_token') !== '',
|
||||
@ -36,7 +39,9 @@ Router\post_action('login', function() {
|
||||
$values = Request\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(
|
||||
'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.',
|
||||
'Add a new database (new user)' => 'Ajouter une nouvelle base de données (nouvel utilisateur)',
|
||||
'Create' => 'Créer',
|
||||
'Unknown' => 'Inconnu',
|
||||
'Remember Me' => 'Connexion automatique',
|
||||
);
|
||||
|
@ -8,7 +8,7 @@ use PicoDb\Database;
|
||||
use PicoFeed\Config as ReaderConfig;
|
||||
use PicoFeed\Logging;
|
||||
|
||||
const DB_VERSION = 24;
|
||||
const DB_VERSION = 25;
|
||||
const HTTP_USER_AGENT = 'Miniflux (http://miniflux.net)';
|
||||
|
||||
// Get PicoFeed config
|
||||
@ -297,3 +297,48 @@ function save(array $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;
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
$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\Validators;
|
||||
use PicoDb\Database;
|
||||
use Model\Config;
|
||||
use Model\RememberMe;
|
||||
use Model\Database as DatabaseModel;
|
||||
|
||||
// Get a user by username
|
||||
function get($username)
|
||||
@ -37,7 +40,13 @@ function validate_login(array $values)
|
||||
unset($user['password']);
|
||||
|
||||
$_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 {
|
||||
|
||||
|
@ -36,6 +36,8 @@
|
||||
<?= Helper\form_label(t('Password'), 'password') ?>
|
||||
<?= Helper\form_password('password', $values, $errors, array('required')) ?>
|
||||
|
||||
<?= Helper\form_checkbox('remember_me', t('Remember Me'), 1) ?><br/>
|
||||
|
||||
<?php if ($google_auth_enable): ?>
|
||||
<p><br/><a href="?action=google-redirect-auth"><?= t('Login with my Google Account') ?></a></p>
|
||||
<?php endif ?>
|
||||
|
Loading…
Reference in New Issue
Block a user