diff --git a/README.md b/README.md
index 0aeb8e6..090404f 100644
--- a/README.md
+++ b/README.md
@@ -68,3 +68,5 @@ A callback from Telegraph will include the following post body parameters:
## Credits
Telegraph photo: https://www.flickr.com/photos/nostri-imago/3407786186
+
+Telegraph icon: https://thenounproject.com/search/?q=telegraph&i=22058
diff --git a/composer.json b/composer.json
index b85bc10..6bd3bd4 100644
--- a/composer.json
+++ b/composer.json
@@ -3,6 +3,8 @@
"php": ">=5.5",
"mf2/mf2": "0.2.*",
"indieweb/mention-client": "1.*",
+ "indieauth/client": "0.1.*",
+ "firebase/php-jwt": "~3.0",
"league/route": "~1.2",
"league/plates": "~3.1"
},
@@ -13,7 +15,9 @@
"files": [
"config.php",
"lib/helpers.php",
- "controllers/Controller.php"
+ "controllers/Controller.php",
+ "controllers/Auth.php",
+ "controllers/API.php"
]
}
}
diff --git a/composer.lock b/composer.lock
index 0921ad2..d64b131 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,9 +4,177 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "5cc37d4bc843c6cbd92dc660ef9e71d8",
- "content-hash": "07f934dcf7d5e224f6c980c51525aa81",
+ "hash": "5630557b773b8342de2ebfcfbe23f013",
+ "content-hash": "88dfc4a35925d318e92d4881b37d70a0",
"packages": [
+ {
+ "name": "barnabywalters/mf-cleaner",
+ "version": "v0.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/barnabywalters/php-mf-cleaner.git",
+ "reference": "ef6a16628db6e8aee2b4f8bb8093d18c24b74cd4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/barnabywalters/php-mf-cleaner/zipball/ef6a16628db6e8aee2b4f8bb8093d18c24b74cd4",
+ "reference": "ef6a16628db6e8aee2b4f8bb8093d18c24b74cd4",
+ "shasum": ""
+ },
+ "require-dev": {
+ "php": ">=5.3",
+ "phpunit/phpunit": "*"
+ },
+ "suggest": {
+ "mf2/mf2": "To parse microformats2 structures from (X)HTML"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/BarnabyWalters/Mf2/Functions.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Barnaby Walters",
+ "email": "barnaby@waterpigs.co.uk"
+ }
+ ],
+ "description": "Cleans up microformats2 array structures",
+ "time": "2014-10-06 23:11:15"
+ },
+ {
+ "name": "firebase/php-jwt",
+ "version": "v3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/firebase/php-jwt.git",
+ "reference": "fa8a06e96526eb7c0eeaa47e4f39be59d21f16e1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/firebase/php-jwt/zipball/fa8a06e96526eb7c0eeaa47e4f39be59d21f16e1",
+ "reference": "fa8a06e96526eb7c0eeaa47e4f39be59d21f16e1",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Firebase\\JWT\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Neuman Vong",
+ "email": "neuman+pear@twilio.com",
+ "role": "Developer"
+ },
+ {
+ "name": "Anant Narayanan",
+ "email": "anant@php.net",
+ "role": "Developer"
+ }
+ ],
+ "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
+ "homepage": "https://github.com/firebase/php-jwt",
+ "time": "2015-07-22 18:31:08"
+ },
+ {
+ "name": "indieauth/client",
+ "version": "0.1.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/indieweb/indieauth-client-php.git",
+ "reference": "6504ed0d4714084e9955f639d6e5cf4e976f9038"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/indieweb/indieauth-client-php/zipball/6504ed0d4714084e9955f639d6e5cf4e976f9038",
+ "reference": "6504ed0d4714084e9955f639d6e5cf4e976f9038",
+ "shasum": ""
+ },
+ "require": {
+ "barnabywalters/mf-cleaner": "0.*",
+ "indieweb/link-rel-parser": "0.1.1",
+ "mf2/mf2": "0.2.*",
+ "php": ">5.3.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "IndieAuth": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache 2.0"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Parecki",
+ "homepage": "http://aaronparecki.com"
+ }
+ ],
+ "description": "IndieAuth Client Library",
+ "time": "2015-08-30 22:29:40"
+ },
+ {
+ "name": "indieweb/link-rel-parser",
+ "version": "0.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/indieweb/link-rel-parser-php.git",
+ "reference": "9e0e635fd301a8b1da7bc181f651f029c531dbb6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/indieweb/link-rel-parser-php/zipball/9e0e635fd301a8b1da7bc181f651f029c531dbb6",
+ "reference": "9e0e635fd301a8b1da7bc181f651f029c531dbb6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/IndieWeb/link_rel_parser.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Parecki",
+ "homepage": "http://aaronparecki.com"
+ },
+ {
+ "name": "Tantek Çelik",
+ "homepage": "http://tantek.com"
+ }
+ ],
+ "description": "Parse rel values from HTTP headers",
+ "homepage": "https://github.com/indieweb/link-rel-parser-php",
+ "keywords": [
+ "http",
+ "indieweb",
+ "microformats2"
+ ],
+ "time": "2013-12-23 00:14:58"
+ },
{
"name": "indieweb/mention-client",
"version": "1.0.0",
diff --git a/controllers/API.php b/controllers/API.php
new file mode 100644
index 0000000..8794760
--- /dev/null
+++ b/controllers/API.php
@@ -0,0 +1,9 @@
+setContent(view('login', [
+ 'title' => 'Sign In to Telegraph',
+ 'return_to' => $request->get('return_to')
+ ]));
+ return $response;
+ }
+
+ public function login_start(Request $request, Response $response) {
+
+ if(!$request->get('url') || !($me = IndieAuth\Client::normalizeMeURL($request->get('url')))) {
+ $response->setContent(view('login', [
+ 'title' => 'Sign In to Telegraph',
+ 'error' => 'Invalid URL',
+ 'error_description' => 'The URL you entered, "' . htmlspecialchars($request->get('url')) . '" is not valid.'
+ ]));
+ return $response;
+ }
+
+ $authorizationEndpoint = IndieAuth\Client::discoverAuthorizationEndpoint($me);
+
+ $state = JWT::encode([
+ 'me' => $me,
+ 'authorization_endpoint' => $authorizationEndpoint,
+ 'return_to' => $request->get('return_to'),
+ 'time' => time(),
+ 'exp' => time()+300 // verified by the JWT library
+ ], Config::$secretKey);
+
+ if($authorizationEndpoint) {
+ // If the user specified only an authorization endpoint, use that
+ $authorizationURL = IndieAuth\Client::buildAuthorizationURL($authorizationEndpoint, $me, self::_buildRedirectURI(), Config::$clientID, $state);
+ } else {
+ // Otherwise, fall back to indieauth.com
+ $authorizationURL = IndieAuth\Client::buildAuthorizationURL(Config::$defaultAuthorizationEndpoint, $me, self::_buildRedirectURI(), Config::$clientID, $state);
+ }
+
+ $response->setStatusCode(302);
+ $response->headers->set('Location', $authorizationURL);
+ return $response;
+ }
+
+ public function login_callback(Request $request, Response $response) {
+
+ if(!$request->get('state') || !$request->get('code') || !$request->get('me')) {
+ $response->setContent(view('login', [
+ 'title' => 'Sign In to Telegraph',
+ 'error' => 'Missing Parameters',
+ 'error_description' => 'The auth server did not return the necessary parameters, state
and code
and me
.'
+ ]));
+ return $response;
+ }
+
+ // Validate the "state" parameter to ensure this request originated at this client
+ try {
+ $state = JWT::decode($request->get('state'), Config::$secretKey, ['HS256']);
+
+ if(!$state) {
+ $response->setContent(view('login', [
+ 'title' => 'Sign In to Telegraph',
+ 'error' => 'Invalid State',
+ 'error_description' => 'The state
parameter was not valid.'
+ ]));
+ return $response;
+ }
+ } catch(Exception $e) {
+ $response->setContent(view('login', [
+ 'title' => 'Sign In to Telegraph',
+ 'error' => 'Invalid State',
+ 'error_description' => 'The state
parameter was invalid:
'.htmlspecialchars($e->getMessage())
+ ]));
+ return $response;
+ }
+
+ // Discover the authorization endpoint from the "me" that was returned by the auth server
+ // This allows the auth server to return a different URL than the user originally entered,
+ // for example if the user enters multiusersite.example the auth server can return multiusersite.example/alice
+ if($state->authorization_endpoint) { // only discover the auth endpoint if one was originally found, otherwise use our fallback
+ $authorizationEndpoint = IndieAuth\Client::discoverAuthorizationEndpoint($request->get('me'));
+ } else {
+ $authorizationEndpoint = Config::$defaultAuthorizationEndpoint;
+ }
+
+ // Verify the code with the auth server
+ $token = IndieAuth\Client::verifyIndieAuthCode($authorizationEndpoint, $request->get('code'), $request->get('me'), self::_buildRedirectURI(), Config::$clientID, $request->get('state'), true);
+
+ if(!array_key_exists('auth', $token) || !array_key_exists('me', $token['auth'])) {
+ // The auth server didn't return a "me" URL
+ $response->setContent(view('login', [
+ 'title' => 'Sign In to Telegraph',
+ 'error' => 'Invalid Auth Server Response',
+ 'error_description' => 'The authorization server did not return a valid response:
'.htmlspecialchars(json_encode($token))
+ ]));
+ return $response;
+ }
+
+ // Create or load the user
+
+ session_start();
+ $_SESSION['me'] = $token['auth']['me'];
+ $response->setStatusCode(302);
+ $response->headers->set('Location', ($state->return_to ?: '/dashboard'));
+ return $response;
+ }
+
+ private static function _buildRedirectURI() {
+ return 'http' . (Config::$ssl ? 's' : '') . '://' . $_SERVER['SERVER_NAME'] . '/login/callback';
+ }
+
+}
diff --git a/controllers/Controller.php b/controllers/Controller.php
index 292caf2..5714f1c 100644
--- a/controllers/Controller.php
+++ b/controllers/Controller.php
@@ -4,6 +4,18 @@ use Symfony\Component\HttpFoundation\Response;
class Controller {
+ private function _is_logged_in(&$request, &$response) {
+ session_start();
+ if(!array_key_exists('me', $_SESSION)) {
+ session_destroy();
+ $response->setStatusCode(302);
+ $response->headers->set('Location', '/login?return_to='.$request->getPathInfo());
+ return false;
+ } else {
+ return true;
+ }
+ }
+
public function index(Request $request, Response $response) {
$response->setContent(view('index', [
'title' => 'Telegraph'
@@ -11,4 +23,15 @@ class Controller {
return $response;
}
+ public function dashboard(Request $request, Response $response) {
+ if(!$this->_is_logged_in($request, $response)) {
+ return $response;
+ }
+
+ $response->setContent(view('dashboard', [
+ 'title' => 'Dashboard'
+ ]));
+ return $response;
+ }
+
}
diff --git a/public/assets/telegraph-logo-256.png b/public/assets/telegraph-logo-256.png
new file mode 100644
index 0000000..fcc1a63
Binary files /dev/null and b/public/assets/telegraph-logo-256.png differ
diff --git a/public/index.php b/public/index.php
index 710eab1..fc810dd 100644
--- a/public/index.php
+++ b/public/index.php
@@ -8,6 +8,15 @@ $router = new League\Route\RouteCollection;
$templates = new League\Plates\Engine(dirname(__FILE__).'/../views');
$router->addRoute('GET', '/', 'Controller::index');
+$router->addRoute('GET', '/dashboard', 'Controller::dashboard');
+$router->addRoute('GET', '/api', 'Controller::api');
+
+$router->addRoute('POST', '/webmention', 'API::webmention');
+$router->addRoute('GET', '/webmention/{code}', 'API::webmention_status');
+
+$router->addRoute('GET', '/login', 'Auth::login');
+$router->addRoute('POST', '/login/start', 'Auth::login_start');
+$router->addRoute('GET', '/login/callback', 'Auth::login_callback');
diff --git a/views/dashboard.php b/views/dashboard.php
new file mode 100644
index 0000000..2ca7e67
--- /dev/null
+++ b/views/dashboard.php
@@ -0,0 +1,3 @@
+layout('layout', ['title' => $title]); ?>
+
+= print_r($_SESSION); ?>
diff --git a/views/index.php b/views/index.php
index 153bb49..8d5485f 100644
--- a/views/index.php
+++ b/views/index.php
@@ -153,7 +153,7 @@ $menu = [
Let Telegraph send webmentions for you. With a simple API, Telegraph will handle sending webmentions to other websites. Let Telegraph handle webmention discovery, and retrying on temporary failures. Telegraph will notify your site when a webmention was successfully sent.
+Let Telegraph send webmentions for you. With a simple API, Telegraph will handle sending webmentions to other websites. Let us handle webmention discovery, and retrying on temporary failures. Telegraph will notify your site when a webmention was successfully sent.
You can even let Telegraph subscribe to your feed, and it will send webmentions whenever you publish a new post.