Browse Source

implement indieauth sign-in

pull/3/head
Aaron Parecki 8 years ago
parent
commit
6175ad184c
12 changed files with 418 additions and 7 deletions
  1. +2
    -0
      README.md
  2. +5
    -1
      composer.json
  3. +170
    -2
      composer.lock
  4. +9
    -0
      controllers/API.php
  5. +117
    -0
      controllers/Auth.php
  6. +23
    -0
      controllers/Controller.php
  7. BIN
      public/assets/telegraph-logo-256.png
  8. +9
    -0
      public/index.php
  9. +3
    -0
      views/dashboard.php
  10. +4
    -4
      views/index.php
  11. +22
    -0
      views/layout.php
  12. +54
    -0
      views/login.php

+ 2
- 0
README.md View File

@ -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

+ 5
- 1
composer.json View File

@ -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"
]
}
}

+ 170
- 2
composer.lock View File

@ -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",

+ 9
- 0
controllers/API.php View File

@ -0,0 +1,9 @@
<?php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class API {
}

+ 117
- 0
controllers/Auth.php View File

@ -0,0 +1,117 @@
<?php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use \Firebase\JWT\JWT;
class Auth {
public function login(Request $request, Response $response) {
$response->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, "<strong>' . htmlspecialchars($request->get('url')) . '</strong>" 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, <code>state</code> and <code>code</code> and <code>me</code>.'
]));
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 <code>state</code> parameter was not valid.'
]));
return $response;
}
} catch(Exception $e) {
$response->setContent(view('login', [
'title' => 'Sign In to Telegraph',
'error' => 'Invalid State',
'error_description' => 'The <code>state</code> parameter was invalid:<br>'.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:<br>'.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';
}
}

+ 23
- 0
controllers/Controller.php View File

@ -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;
}
}

BIN
public/assets/telegraph-logo-256.png View File

Before After
Width: 256  |  Height: 256  |  Size: 5.4 KiB

+ 9
- 0
public/index.php View File

@ -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');

+ 3
- 0
views/dashboard.php View File

@ -0,0 +1,3 @@
<?php $this->layout('layout', ['title' => $title]); ?>
<?= print_r($_SESSION); ?>

+ 4
- 4
views/index.php View File

@ -153,7 +153,7 @@ $menu = [
<?php endforeach; ?>
<div class="right menu">
<div class="item">
<a class="ui button">Log in</a>
<a class="ui button" href="/login">Log in</a>
</div>
</div>
</div>
@ -164,7 +164,7 @@ $menu = [
<?php foreach($menu as $href=>$name): ?>
<a class="item" href="<?= $href ?>"><?= $name ?></a>
<?php endforeach; ?>
<a class="item">Login</a>
<a class="item" href="/login">Login</a>
</div>
@ -191,7 +191,7 @@ $menu = [
Telegraph
</h1>
<h2>Easily send Webmentions from your website</h2>
<div class="ui huge primary button">Get Started <i class="right arrow icon"></i></div>
<a class="ui huge primary button" href="/login">Get Started <i class="right arrow icon"></i></a>
</div>
</div>
@ -201,7 +201,7 @@ $menu = [
<div class="row">
<div class="eight wide column">
<h3 class="ui header">We send webmentions for you</h3>
<p>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.</p>
<p>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.</p>
<h3 class="ui header">Send webmentions automatically</h3>
<p>You can even let Telegraph subscribe to your feed, and it will send webmentions whenever you publish a new post.</p>
</div>

+ 22
- 0
views/layout.php View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<title><?= $this->e($title) ?></title>
<link href="/assets/styles.css" rel="stylesheet">
<link href="/semantic-ui/semantic.min.css" rel="stylesheet">
<script src="/assets/jquery.js"></script>
<script src="/semantic-ui/semantic.js"></script>
</head>
<body>
<?= $this->section('content') ?>
<footer>
</footer>
</body>
</html>

+ 54
- 0
views/login.php View File

@ -0,0 +1,54 @@
<?php $this->layout('layout', ['title' => $title]); ?>
<style type="text/css">
body {
background-color: #DADADA;
}
body > .grid {
height: 100%;
}
.image {
margin-top: -100px;
}
.column {
max-width: 450px;
}
</style>
<div class="ui middle aligned center aligned grid">
<div class="column">
<h2 class="ui teal image header">
<img src="/assets/telegraph-logo-256.png" class="image">
<div class="content">
Sign in to Telegraph
</div>
</h2>
<?php if(isset($error)): ?>
<div class="ui warning message">
<div class="header"><?= $error ?></div>
<?= $error_description ?>
</div>
<?php endif; ?>
<form class="ui large form" action="/login/start" method="POST" >
<div class="ui stacked segment">
<div class="field">
<div class="ui left icon input">
<i class="globe icon"></i>
<input type="url" name="url" placeholder="Your Web Address" value="http://aaronparecki.com">
</div>
</div>
<input type="hidden" name="return_to" value="<?= $return_to ?>">
<button class="ui fluid large teal submit button">Login</button>
</div>
<div class="ui error message"></div>
</form>
<div class="ui message">
What's this? <a href="https://indieauth.com/setup">About IndieAuth</a>
</div>
</div>
</div>

Loading…
Cancel
Save