Add the possibility to link a Google Account or a Mozilla Account to Miniflux
This commit is contained in:
parent
95f5dd1257
commit
517ac8dcf0
@ -464,6 +464,29 @@
|
||||
}
|
||||
|
||||
|
||||
function mozilla_auth(action)
|
||||
{
|
||||
navigator.id.watch({
|
||||
onlogin: function(assertion) {
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "?action=" + action, true);
|
||||
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
||||
xhr.setRequestHeader("Connection", "close");
|
||||
|
||||
xhr.onload = function () {
|
||||
window.location.href = this.responseText;
|
||||
};
|
||||
|
||||
xhr.send("token=" + assertion);
|
||||
},
|
||||
onlogout: function() {}
|
||||
});
|
||||
|
||||
navigator.id.request();
|
||||
}
|
||||
|
||||
|
||||
document.onclick = function(e) {
|
||||
|
||||
var action = e.target.getAttribute("data-action");
|
||||
@ -502,6 +525,14 @@
|
||||
e.preventDefault();
|
||||
download_item();
|
||||
break;
|
||||
case 'mozilla-login':
|
||||
e.preventDefault();
|
||||
mozilla_auth("mozilla-auth");
|
||||
break;
|
||||
case 'mozilla-link':
|
||||
e.preventDefault();
|
||||
mozilla_auth("mozilla-link");
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
8
assets/js/persona.js
Normal file
8
assets/js/persona.js
Normal file
File diff suppressed because one or more lines are too long
127
index.php
127
index.php
@ -23,8 +23,9 @@ Session\open(dirname($_SERVER['PHP_SELF']));
|
||||
// Called before each action
|
||||
Router\before(function($action) {
|
||||
|
||||
if ($action !== 'login' && ! isset($_SESSION['user'])) {
|
||||
$ignore_actions = array('login', 'google-auth', 'google-redirect-auth', 'mozilla-auth');
|
||||
|
||||
if (! isset($_SESSION['user']) && ! in_array($action, $ignore_actions)) {
|
||||
Response\redirect('?action=login');
|
||||
}
|
||||
|
||||
@ -33,10 +34,13 @@ Router\before(function($action) {
|
||||
if ($language !== 'en_US') PicoTools\Translator\load($language);
|
||||
|
||||
// HTTP secure headers
|
||||
$frame_src = \PicoFeed\Filter::$iframe_whitelist;
|
||||
$frame_src[] = 'https://login.persona.org';
|
||||
|
||||
Response\csp(array(
|
||||
'media-src' => '*',
|
||||
'img-src' => '*',
|
||||
'frame-src' => \PicoFeed\Filter::$iframe_whitelist
|
||||
'frame-src' => $frame_src
|
||||
));
|
||||
|
||||
Response\xframe();
|
||||
@ -56,9 +60,11 @@ Router\get_action('logout', function() {
|
||||
// Display form login
|
||||
Router\get_action('login', function() {
|
||||
|
||||
if (isset($_SESSION['user'])) Response\redirect('index.php');
|
||||
if (isset($_SESSION['user'])) Response\redirect('?action=unread');
|
||||
|
||||
Response\html(Template\load('login', array(
|
||||
'google_auth_enable' => Model\get_config_value('auth_google_token') !== '',
|
||||
'mozilla_auth_enable' => Model\get_config_value('auth_mozilla_token') !== '',
|
||||
'errors' => array(),
|
||||
'values' => array()
|
||||
)));
|
||||
@ -74,6 +80,8 @@ Router\post_action('login', function() {
|
||||
if ($valid) Response\redirect('?action=unread');
|
||||
|
||||
Response\html(Template\load('login', array(
|
||||
'google_auth_enable' => Model\get_config_value('auth_google_token') !== '',
|
||||
'mozilla_auth_enable' => Model\get_config_value('auth_mozilla_token') !== '',
|
||||
'errors' => $errors,
|
||||
'values' => $values
|
||||
)));
|
||||
@ -658,6 +666,117 @@ Router\post_action('config', function() {
|
||||
});
|
||||
|
||||
|
||||
// Link to a Google Account (redirect)
|
||||
Router\get_action('google-redirect-link', function() {
|
||||
|
||||
require 'vendor/PicoTools/AuthProvider.php';
|
||||
Response\Redirect(AuthProvider\google_get_url(Helper\get_current_base_url(), '?action=google-link'));
|
||||
});
|
||||
|
||||
|
||||
// Link to a Google Account (association)
|
||||
Router\get_action('google-link', function() {
|
||||
|
||||
require 'vendor/PicoTools/AuthProvider.php';
|
||||
|
||||
list($valid, $token) = AuthProvider\google_validate();
|
||||
|
||||
if ($valid) {
|
||||
Model\save_auth_token('google', $token);
|
||||
Session\flash(t('Your Google Account is linked to Miniflux.'));
|
||||
}
|
||||
else {
|
||||
Session\flash_error(t('Unable to link Miniflux to your Google Account.'));
|
||||
}
|
||||
|
||||
Response\redirect('?action=config');
|
||||
});
|
||||
|
||||
|
||||
// Authenticate with a Google Account (redirect)
|
||||
Router\get_action('google-redirect-auth', function() {
|
||||
|
||||
require 'vendor/PicoTools/AuthProvider.php';
|
||||
Response\Redirect(AuthProvider\google_get_url(Helper\get_current_base_url(), '?action=google-auth'));
|
||||
});
|
||||
|
||||
|
||||
// Authenticate with a Google Account (callback url)
|
||||
Router\get_action('google-auth', function() {
|
||||
|
||||
require 'vendor/PicoTools/AuthProvider.php';
|
||||
|
||||
list($valid, $token) = AuthProvider\google_validate();
|
||||
|
||||
if ($valid && $token === Model\get_config_value('auth_google_token')) {
|
||||
|
||||
$_SESSION['user'] = array(
|
||||
'username' => Model\get_config_value('username'),
|
||||
'language' => Model\get_config_value('language'),
|
||||
);
|
||||
|
||||
Response\redirect('?action=unread');
|
||||
}
|
||||
else {
|
||||
|
||||
Response\html(Template\load('login', array(
|
||||
'google_auth_enable' => Model\get_config_value('auth_google_token') !== '',
|
||||
'mozilla_auth_enable' => Model\get_config_value('auth_mozilla_token') !== '',
|
||||
'errors' => array('login' => t('Unable to authenticate with Google')),
|
||||
'values' => array()
|
||||
)));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Authenticate with a Mozilla Persona (ajax check)
|
||||
Router\post_action('mozilla-auth', function() {
|
||||
|
||||
require 'vendor/PicoTools/AuthProvider.php';
|
||||
|
||||
list($valid, $token) = AuthProvider\mozilla_validate(Request\value('token'));
|
||||
|
||||
if ($valid && $token === Model\get_config_value('auth_mozilla_token')) {
|
||||
|
||||
$_SESSION['user'] = array(
|
||||
'username' => Model\get_config_value('username'),
|
||||
'language' => Model\get_config_value('language'),
|
||||
);
|
||||
|
||||
Response\text('?action=unread');
|
||||
}
|
||||
else {
|
||||
Response\text("?action=login");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Link Miniflux to a Mozilla Account (ajax check)
|
||||
Router\post_action('mozilla-link', function() {
|
||||
|
||||
require 'vendor/PicoTools/AuthProvider.php';
|
||||
|
||||
list($valid, $token) = AuthProvider\mozilla_validate(Request\value('token'));
|
||||
|
||||
if ($valid) {
|
||||
Model\save_auth_token('mozilla', $token);
|
||||
Session\flash(t('Your Mozilla Persona Account is linked to Miniflux.'));
|
||||
}
|
||||
else {
|
||||
Session\flash_error(t('Unable to link Miniflux to your Mozilla Persona Account.'));
|
||||
}
|
||||
|
||||
Response\text("?action=config");
|
||||
});
|
||||
|
||||
|
||||
// Remove account link
|
||||
Router\get_action('unlink-account-provider', function() {
|
||||
Model\remove_auth_token(Request\param('type'));
|
||||
Response\redirect('?action=config');
|
||||
});
|
||||
|
||||
|
||||
// Display unread items
|
||||
Router\notfound(function() {
|
||||
|
||||
@ -665,7 +784,7 @@ Router\notfound(function() {
|
||||
|
||||
$offset = Request\int_param('offset', 0);
|
||||
$items = Model\get_items('unread', $offset, Model\get_config_value('items_per_page'));
|
||||
$nb_items = Model\count_items('unread');;
|
||||
$nb_items = Model\count_items('unread');
|
||||
|
||||
if ($nb_items === 0) Response\redirect('?action=feeds¬hing_to_read=1');
|
||||
|
||||
|
@ -1,6 +1,17 @@
|
||||
<?php
|
||||
|
||||
return array(
|
||||
'Your Google Account is linked to Miniflux' => 'Votre compte Google est relié à Miniflux',
|
||||
'Link Miniflux to my Google account' => 'Lier Miniflux à mon compte Google',
|
||||
'Your Mozilla Persona Account is linked to Miniflux' => 'Votre compte Mozilla Persona est relié à Miniflux',
|
||||
'Link Miniflux to my Mozilla Persona account' => 'Lier Miniflux à mon compte Mozilla Persona',
|
||||
'Your Google Account is linked to Miniflux.' => 'Votre compte Google est relié à Miniflux.',
|
||||
'Unable to link Miniflux to your Google Account.' => 'Impossible de lier Miniflux à votre compte Google',
|
||||
'Unable to authenticate with Google' => 'Impossible de s\'authentifier avec Google',
|
||||
'Your Mozilla Persona Account is linked to Miniflux.' => 'Votre compte Mozilla Persona est lié avec Miniflux.',
|
||||
'Unable to link Miniflux to your Mozilla Persona Account.' => 'Impossible de lier Miniflux avec votre compte Mozilla Persona.',
|
||||
'Login with my Google Account' => 'Se connecter avec mon compte Google',
|
||||
'Login with my Mozilla Persona Account' => 'Se connecter avec mon compte Mozilla Persona',
|
||||
'Bookmarklet:' => 'Bookmarklet :',
|
||||
'Subscribe with Miniflux' => 'S\'abonner avec Miniflux',
|
||||
'Drag and drop this link to your bookmarks' => 'Glisser-déposer ce lien dans vos favoris',
|
||||
|
40
model.php
40
model.php
@ -24,7 +24,7 @@ use PicoFeed\Reader;
|
||||
use PicoFeed\Export;
|
||||
|
||||
|
||||
const DB_VERSION = 15;
|
||||
const DB_VERSION = 16;
|
||||
const HTTP_USERAGENT = 'Miniflux - http://miniflux.net';
|
||||
const HTTP_FAKE_USERAGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.62 Safari/537.36';
|
||||
const LIMIT_ALL = -1;
|
||||
@ -126,6 +126,28 @@ function new_tokens()
|
||||
}
|
||||
|
||||
|
||||
function save_auth_token($type, $value)
|
||||
{
|
||||
return \PicoTools\singleton('db')
|
||||
->table('config')
|
||||
->update(array(
|
||||
'auth_'.$type.'_token' => $value
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
function remove_auth_token($type)
|
||||
{
|
||||
\PicoTools\singleton('db')
|
||||
->table('config')
|
||||
->update(array(
|
||||
'auth_'.$type.'_token' => ''
|
||||
));
|
||||
|
||||
$_SESSION['config'] = get_config();
|
||||
}
|
||||
|
||||
|
||||
function export_feeds()
|
||||
{
|
||||
$opml = new Export(get_feeds());
|
||||
@ -885,7 +907,18 @@ function get_config()
|
||||
{
|
||||
return \PicoTools\singleton('db')
|
||||
->table('config')
|
||||
->columns('username', 'language', 'autoflush', 'nocontent', 'items_per_page', 'theme', 'api_token', 'feed_token')
|
||||
->columns(
|
||||
'username',
|
||||
'language',
|
||||
'autoflush',
|
||||
'nocontent',
|
||||
'items_per_page',
|
||||
'theme',
|
||||
'api_token',
|
||||
'feed_token',
|
||||
'auth_google_token',
|
||||
'auth_mozilla_token'
|
||||
)
|
||||
->findOne();
|
||||
}
|
||||
|
||||
@ -976,11 +1009,8 @@ function save_config(array $values)
|
||||
{
|
||||
// Update the password if needed
|
||||
if (! empty($values['password'])) {
|
||||
|
||||
$values['password'] = \password_hash($values['password'], PASSWORD_BCRYPT);
|
||||
|
||||
} else {
|
||||
|
||||
unset($values['password']);
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,13 @@
|
||||
namespace Schema;
|
||||
|
||||
|
||||
function version_16($pdo)
|
||||
{
|
||||
$pdo->exec('ALTER TABLE config ADD COLUMN auth_google_token TEXT DEFAULT ""');
|
||||
$pdo->exec('ALTER TABLE config ADD COLUMN auth_mozilla_token TEXT DEFAULT ""');
|
||||
}
|
||||
|
||||
|
||||
function version_15($pdo)
|
||||
{
|
||||
$pdo->exec('ALTER TABLE feeds ADD COLUMN download_content INTEGER DEFAULT 0');
|
||||
|
@ -11,7 +11,7 @@
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="./assets/img/touch-icon-ipad-retina.png">
|
||||
<title><?= isset($title) ? Helper\escape($title) : 'miniflux' ?></title>
|
||||
<link href="<?= Helper\css() ?>" rel="stylesheet" media="screen">
|
||||
<script type="text/javascript" src="./assets/js/app.js?version=<?= filemtime('assets/js/app.js') ?>" defer></script>
|
||||
<script type="text/javascript" src="assets/js/app.js?version=<?= filemtime('assets/js/app.js') ?>" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
|
@ -27,6 +27,23 @@
|
||||
|
||||
<?= Helper\form_checkbox('nocontent', t('Do not fetch the content of articles'), 1, isset($values['nocontent']) ? $values['nocontent'] : false) ?><br />
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<?php if ($values['auth_google_token']): ?>
|
||||
<?= t('Your Google Account is linked to Miniflux') ?>, <a href="?action=unlink-account-provider&type=google"><?= t('remove') ?></a>
|
||||
<?php else: ?>
|
||||
<a href="?action=google-redirect-link"><?= t('Link Miniflux to my Google account') ?></a>
|
||||
<?php endif ?>
|
||||
</li>
|
||||
<li>
|
||||
<?php if ($values['auth_mozilla_token']): ?>
|
||||
<?= t('Your Mozilla Persona Account is linked to Miniflux') ?>, <a href="?action=unlink-account-provider&type=mozilla"><?= t('remove') ?></a>
|
||||
<?php else: ?>
|
||||
<a href="#" data-action="mozilla-link"><?= t('Link Miniflux to my Mozilla Persona account') ?></a>
|
||||
<?php endif ?>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
|
||||
</div>
|
||||
@ -70,4 +87,6 @@
|
||||
<li><a href="?action=console"><?= t('Console') ?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<script type="text/javascript" src="assets/js/persona.js" async></script>
|
@ -11,6 +11,10 @@
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="./assets/img/touch-icon-ipad-retina.png">
|
||||
<title>miniflux</title>
|
||||
<link href="<?= Helper\css() ?>" rel="stylesheet" media="screen">
|
||||
<script type="text/javascript" src="assets/js/app.js?version=<?= filemtime('assets/js/app.js') ?>" defer></script>
|
||||
<?php if ($mozilla_auth_enable): ?>
|
||||
<script type="text/javascript" src="assets/js/persona.js" defer></script>
|
||||
<?php endif ?>
|
||||
</head>
|
||||
<body id="login-page">
|
||||
<section class="page">
|
||||
@ -20,9 +24,7 @@
|
||||
<section>
|
||||
|
||||
<?php if (isset($errors['login'])): ?>
|
||||
|
||||
<p class="alert alert-error"><?= Helper\escape($errors['login']) ?></p>
|
||||
|
||||
<?php endif ?>
|
||||
|
||||
<form method="post" action="?action=login">
|
||||
@ -33,6 +35,14 @@
|
||||
<?= Helper\form_label(t('Password'), 'password') ?>
|
||||
<?= Helper\form_password('password', $values, $errors, array('required')) ?>
|
||||
|
||||
<?php if ($google_auth_enable): ?>
|
||||
<p><br/><a href="?action=google-redirect-auth"><?= t('Login with my Google Account') ?></a></p>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($mozilla_auth_enable): ?>
|
||||
<p><br/><a href="#" data-action="mozilla-login"><?= t('Login with my Mozilla Persona Account') ?></a></p>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Sign in') ?>" class="btn btn-blue"/>
|
||||
</div>
|
||||
|
87
vendor/PicoTools/AuthProvider.php
vendored
Normal file
87
vendor/PicoTools/AuthProvider.php
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace AuthProvider;
|
||||
|
||||
function google_get_url($realm, $return_path)
|
||||
{
|
||||
$return_to = $realm.$return_path;
|
||||
$url = 'https://accounts.google.com/o/openid2/auth?';
|
||||
$params = array();
|
||||
$params['openid.ns'] = 'http://specs.openid.net/auth/2.0';
|
||||
$params['openid.mode'] = 'checkid_setup';
|
||||
$params['openid.return_to'] = $return_to;
|
||||
$params['openid.realm'] = $realm;
|
||||
$params['openid.identity'] = 'http://specs.openid.net/auth/2.0/identifier_select';
|
||||
$params['openid.claimed_id'] = 'http://specs.openid.net/auth/2.0/identifier_select';
|
||||
|
||||
return $url.http_build_query($params, '', '&');
|
||||
}
|
||||
|
||||
function google_validate()
|
||||
{
|
||||
$identity = '';
|
||||
|
||||
if (! ini_get('allow_url_fopen')) {
|
||||
die('You must have "allow_url_fopen=On" to use this feature!');
|
||||
}
|
||||
|
||||
if (! isset($_GET['openid_mode']) || $_GET['openid_mode'] !== 'id_res') {
|
||||
return array(false, $identity);
|
||||
}
|
||||
|
||||
$params = array();
|
||||
$params['openid.ns'] = 'http://specs.openid.net/auth/2.0';
|
||||
$params['openid.mode'] = 'check_authentication';
|
||||
$params['openid.assoc_handle'] = $_GET['openid_assoc_handle'];
|
||||
$params['openid.signed'] = $_GET['openid_signed'];
|
||||
$params['openid.sig'] = $_GET['openid_sig'];
|
||||
|
||||
foreach (explode(',', $_GET['openid_signed']) as $item) {
|
||||
$params['openid.'.$item] = $_GET['openid_' . str_replace('.', '_', $item)];
|
||||
}
|
||||
|
||||
$context = stream_context_create(array(
|
||||
'http'=>array(
|
||||
'method'=> 'POST',
|
||||
'header'=> implode("\r\n", array(
|
||||
'Content-type: application/x-www-form-urlencoded',
|
||||
'Accept: application/xrds+xml, */*'
|
||||
)),
|
||||
'content' => http_build_query($params, '', '&')
|
||||
)));
|
||||
|
||||
$response = file_get_contents('https://www.google.com/accounts/o8/ud', false, $context);
|
||||
$identity = $_GET['openid_identity'];
|
||||
|
||||
return array(strpos($response, 'is_valid:true') !== false, $identity);
|
||||
}
|
||||
|
||||
|
||||
function mozilla_validate($token)
|
||||
{
|
||||
if (! ini_get('allow_url_fopen')) {
|
||||
die('You must have "allow_url_fopen=On" to use this feature!');
|
||||
}
|
||||
|
||||
$params = array(
|
||||
'assertion' => $token,
|
||||
'audience' => (isset($_SERVER['HTTPS']) ? 'https://' : 'http://').$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT']
|
||||
);
|
||||
|
||||
$context = stream_context_create(array(
|
||||
'http'=> array(
|
||||
'method'=> 'POST',
|
||||
'header'=> implode("\r\n", array(
|
||||
'Content-type: application/x-www-form-urlencoded',
|
||||
)),
|
||||
'content' => http_build_query($params, '', '&')
|
||||
)));
|
||||
|
||||
$body = file_get_contents('https://verifier.login.persona.org/verify', false, $context);
|
||||
$response = json_decode($body, true);
|
||||
|
||||
return array(
|
||||
$response['status'] === 'okay',
|
||||
$response['email']
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user