Browse Source

change login flow to support sites that don't have a micropub endpoint

pull/10/head
Aaron Parecki 10 years ago
parent
commit
041befb4d1
10 changed files with 113 additions and 154 deletions
  1. +0
    -2
      README.md
  2. +79
    -37
      controllers/auth.php
  3. +2
    -2
      controllers/controllers.php
  4. +1
    -33
      public/bootstrap/css/bootstrap-theme.css
  5. +0
    -7
      public/bootstrap/css/bootstrap-theme.min.css
  6. +8
    -0
      views/auth_error.php
  7. +16
    -42
      views/auth_start.php
  8. +4
    -28
      views/docs.php
  9. +1
    -1
      views/layout.php
  10. +2
    -2
      views/new-post.php

+ 0
- 2
README.md View File

@ -11,8 +11,6 @@ You can post what you're drinking to your own site, or you can post to an accoun
### /auth/start
Copy from Quill.
Discover IndieAuth + Micropub endpoints.
#### Authorize

+ 79
- 37
controllers/auth.php View File

@ -47,8 +47,8 @@ function normalizeMeURL($url) {
return false;
// Invalid path
if($me['path'] != '/')
return false;
// if($me['path'] != '/')
// return false;
// query and fragment not allowed
if(array_key_exists('query', $me) || array_key_exists('fragment', $me))
@ -92,26 +92,27 @@ $app->get('/auth/start', function() use($app) {
$tokenEndpoint = IndieAuth\Client::discoverTokenEndpoint($me);
$micropubEndpoint = IndieAuth\Client::discoverMicropubEndpoint($me);
if($tokenEndpoint && $micropubEndpoint && $authorizationEndpoint) {
// Generate a "state" parameter for the request
$state = IndieAuth\Client::generateStateParameter();
$_SESSION['auth_state'] = $state;
// Generate a "state" parameter for the request
$state = IndieAuth\Client::generateStateParameter();
$_SESSION['auth_state'] = $state;
if($tokenEndpoint && $micropubEndpoint && $authorizationEndpoint) {
$scope = 'post';
$authorizationURL = IndieAuth\Client::buildAuthorizationURL($authorizationEndpoint, $me, buildRedirectURI(), clientID(), $state, $scope);
} else {
$authorizationURL = false;
$authorizationURL = IndieAuth\Client::buildAuthorizationURL('https://indieauth.com/auth', $me, buildRedirectURI(), clientID(), $state);
}
// If the user has already signed in before and has a micropub access token, skip
// the debugging screens and redirect immediately to the auth endpoint.
// This will still generate a new access token when they finish logging in.
$user = ORM::for_table('users')->where('url', $me)->find_one();
if($user && $user->micropub_access_token && !array_key_exists('restart', $params)) {
if($user && $user->access_token && !array_key_exists('restart', $params)) {
$user->micropub_endpoint = $micropubEndpoint;
$user->authorization_endpoint = $authorizationEndpoint;
$user->token_endpoint = $tokenEndpoint;
$user->type = $micropubEndpoint ? 'micropub' : 'local';
$user->save();
$app->redirect($authorizationURL, 301);
@ -125,6 +126,7 @@ $app->get('/auth/start', function() use($app) {
$user->micropub_endpoint = $micropubEndpoint;
$user->authorization_endpoint = $authorizationEndpoint;
$user->token_endpoint = $tokenEndpoint;
$user->type = $micropubEndpoint ? 'micropub' : 'local';
$user->save();
$html = render('auth_start', array(
@ -132,6 +134,7 @@ $app->get('/auth/start', function() use($app) {
'me' => $me,
'authorizing' => $me,
'meParts' => parse_url($me),
'micropubUser' => $authorizationEndpoint && $tokenEndpoint && $micropubEndpoint,
'tokenEndpoint' => $tokenEndpoint,
'micropubEndpoint' => $micropubEndpoint,
'authorizationEndpoint' => $authorizationEndpoint,
@ -199,48 +202,87 @@ $app->get('/auth/callback', function() use($app) {
// An authorization code is in the query string, and we want to exchange that for an access token at the token endpoint.
// Discover the endpoints
$authorizationEndpoint = IndieAuth\Client::discoverAuthorizationEndpoint($me);
$micropubEndpoint = IndieAuth\Client::discoverMicropubEndpoint($me);
$tokenEndpoint = IndieAuth\Client::discoverTokenEndpoint($me);
$redirectToDashboardImmediately = false;
if($tokenEndpoint) {
// Exchange auth code for an access token
$token = IndieAuth\Client::getAccessToken($tokenEndpoint, $params['code'], $params['me'], buildRedirectURI(), clientID(), $params['state'], true);
// If a valid access token was returned, store the token info in the session and they are signed in
if(k($token['auth'], array('me','access_token','scope'))) {
$_SESSION['auth'] = $token['auth'];
$_SESSION['me'] = $params['me'];
// TODO?
// This client requires the "post" scope.
// Make a request to the micropub endpoint to discover the syndication targets if any.
// Errors are silently ignored here. The user will be able to retry from the new post interface and get feedback.
// get_syndication_targets($user);
}
} else {
$token = array('auth'=>false, 'response'=>false);
}
// No token endpoint was discovered, instead, verify the auth code at the auth server or with indieauth.com
$redirectToDashboardImmediately = false;
// Never show the intermediate login confirmation page if we just authenticated them instead of got authorization
$redirectToDashboardImmediately = true;
// If a valid access token was returned, store the token info in the session and they are signed in
if(k($token['auth'], array('me','access_token','scope'))) {
$_SESSION['auth'] = $token['auth'];
$_SESSION['me'] = $params['me'];
$user = ORM::for_table('users')->where('url', $me)->find_one();
if($user) {
// Already logged in, update the last login date
$user->last_login = date('Y-m-d H:i:s');
// If they have logged in before and we already have an access token, then redirect to the dashboard now
if($user->micropub_access_token)
$redirectToDashboardImmediately = true;
} else {
// New user! Store the user in the database
$user = ORM::for_table('users')->create();
$user->url = $me;
$user->date_created = date('Y-m-d H:i:s');
if(!$authorizationEndpoint) {
$authorizationEndpoint = 'https://indieauth.com/auth';
}
$user->micropub_endpoint = $micropubEndpoint;
$user->access_token = $token['auth']['access_token'];
$user->token_scope = $token['auth']['scope'];
$user->token_response = $token['response'];
$user->save();
$_SESSION['user_id'] = $user->id();
// Make a request to the micropub endpoint to discover the syndication targets if any.
// Errors are silently ignored here. The user will be able to retry from the new post interface and get feedback.
get_syndication_targets($user);
$token['auth'] = IndieAuth\Client::verifyIndieAuthCode($authorizationEndpoint, $params['code'], $params['me'], buildRedirectURI(), clientID(), $params['state']);
if(k($token['auth'], 'me')) {
$token['response'] = ''; // hack becuase the verify call doesn't actually return the real response
$token['auth']['scope'] = '';
$token['auth']['access_token'] = '';
$_SESSION['auth'] = $token['auth'];
$_SESSION['me'] = $params['me'];
}
}
// Verify the login actually succeeded
if(!array_key_exists('me', $_SESSION)) {
$html = render('auth_error', array(
'title' => 'Sign-In Failed',
'error' => 'Unable to verify the sign-in attempt',
'errorDescription' => ''
));
$app->response()->body($html);
return;
}
$user = ORM::for_table('users')->where('url', $me)->find_one();
if($user) {
// Already logged in, update the last login date
$user->last_login = date('Y-m-d H:i:s');
// If they have logged in before and we already have an access token, then redirect to the dashboard now
if($user->access_token)
$redirectToDashboardImmediately = true;
} else {
// New user! Store the user in the database
$user = ORM::for_table('users')->create();
$user->url = $me;
$user->date_created = date('Y-m-d H:i:s');
$user->last_login = date('Y-m-d H:i:s');
}
$user->micropub_endpoint = $micropubEndpoint;
$user->access_token = $token['auth']['access_token'];
$user->token_scope = $token['auth']['scope'];
$user->token_response = $token['response'];
$user->save();
$_SESSION['user_id'] = $user->id();
unset($_SESSION['auth_state']);
if($redirectToDashboardImmediately) {

+ 2
- 2
controllers/controllers.php View File

@ -52,8 +52,8 @@ $app->get('/new', function() use($app) {
$html = render('new-post', array(
'title' => 'New Post',
'micropub_endpoint' => $user->micropub_endpoint,
'micropub_scope' => $user->micropub_scope,
'micropub_access_token' => $user->micropub_access_token,
'token_scope' => $user->token_scope,
'access_token' => $user->access_token,
'response_date' => $user->last_micropub_response_date,
'syndication_targets' => json_decode($user->syndication_targets, true),
'test_response' => $test_response,

+ 1
- 33
public/bootstrap/css/bootstrap-theme.css View File

@ -211,39 +211,7 @@
.navbar-fixed-bottom {
border-radius: 0;
}
.alert {
text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
}
.alert-success {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
background-repeat: repeat-x;
border-color: #b2dba1;
}
.alert-info {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
background-repeat: repeat-x;
border-color: #9acfea;
}
.alert-warning {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
background-repeat: repeat-x;
border-color: #f5e79e;
}
.alert-danger {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
background-repeat: repeat-x;
border-color: #dca7a7;
}
.progress {
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);

+ 0
- 7
public/bootstrap/css/bootstrap-theme.min.css
File diff suppressed because it is too large
View File


+ 8
- 0
views/auth_error.php View File

@ -0,0 +1,8 @@
<div class="narrow">
<?= partial('partials/header') ?>
<h2><?= $this->error ?></h2>
<p><?= $this->errorDescription ?></p>
</div>

+ 16
- 42
views/auth_start.php View File

@ -1,58 +1,32 @@
<div class="narrow">
<?= partial('partials/header') ?>
<div id="authorization_endpoint">
<h3>Authorization Endpoint</h3>
<?php if($this->micropubUser): ?>
<p><i>The authorization endpoint tells this app where to direct your browser to sign you in.</i></p>
<h3>Awesome! We found your Micropub endpoint!</h3>
<?php if($this->authorizationEndpoint): ?>
<div class="bs-callout bs-callout-success">Found your authorization endpoint: <code><?= $this->authorizationEndpoint ?></code></div>
<?php else: ?>
<div class="bs-callout bs-callout-danger">Could not find your authorization endpoint!</div>
<p>You need to set your authorization endpoint in a <code>&lt;link&gt;</code> tag on your home page.</p>
<?= partial('partials/auth-endpoint-help') ?>
<?php endif; ?>
</div>
<div id="token_endpoint">
<h3>Token Endpoint</h3>
<div class="alert alert-success">
Click below to authorize this app to create posts at your Micropub endpoint.
</div>
<p><i>The token endpoint is where this app will make a request to get an access token after obtaining authorization.</i></p>
<?php if($this->tokenEndpoint): ?>
<div class="bs-callout bs-callout-success">Found your token endpoint: <code><?= $this->tokenEndpoint ?></code></div>
<?php else: ?>
<div class="bs-callout bs-callout-danger">Could not find your token endpoint!</div>
<p>You need to set your token endpoint in a <code>&lt;link&gt;</code> tag on your home page.</p>
<?= partial('partials/token-endpoint-help') ?>
<?php endif; ?>
</div>
<h3>Would you like to use a hosted account?</h3>
<div id="micropub_endpoint">
<h3>Micropub Endpoint</h3>
<div class="alert alert-warning">
It looks like your site doesn't support <a href="http://micropub.net">Micropub</a>.
You can still use this site to track what you drink, and the posts will live here instead of on your own site.
</div>
<p><i>The Micropub endpoint is the URL this app will use to post new photos.</i></p>
<?php if($this->micropubEndpoint): ?>
<div class="bs-callout bs-callout-success">Found your Micropub endpoint: <code><?= $this->micropubEndpoint ?></code></div>
<?php else: ?>
<div class="bs-callout bs-callout-danger">Could not find your Micropub endpoint!</div>
<p>You need to set your Micropub endpoint in a <code>&lt;link&gt;</code> tag on your home page.</p>
<?= partial('partials/micropub-endpoint-help', $this) ?>
<?php endif; ?>
</div>
<?php if($this->authorizationURL): ?>
<h3>Ready!</h3>
<p>Clicking the button below will take you to <strong>your</strong> authorization server which is where you will allow this app to be able to post to your site.</p>
<a href="<?= $this->authorizationURL ?>" class="btn btn-primary"><?= $this->micropubUser ? 'Authorize' : 'Sign In' ?></a>
<a href="<?= $this->authorizationURL ?>" class="btn btn-primary">Authorize</a>
<?php endif; ?>
<div class="bs-callout bs-callout-<?= $this->micropubUser ? 'success' : 'warning' ?>">
Your authorization endpoint: <code><?= $this->authorizationEndpoint ?: 'none' ?></code><br>
Your token endpoint: <code><?= $this->tokenEndpoint ?: 'none' ?></code><br>
Your Micropub endpoint: <code><?= $this->micropubEndpoint ?: 'none' ?></code>
</div>
</div>

+ 4
- 28
views/docs.php View File

@ -4,7 +4,7 @@
<h2 id="introduction">Introduction</h2>
<div class="col-xs-6 col-md-4" style="float: right;">
<span class="thumbnail"><img src="/images/quill-ui.png"></span>
<span class="thumbnail"><img src="/images/teacup-ui.png"></span>
</div>
<p>This is a simple <a href="http://indiewebcamp.com/micropub">Micropub</a> client for
@ -20,41 +20,17 @@
<h2 id="endpoints">Configuring Endpoints</h2>
<h3>Authorization Endpoint</h3>
<p><i>The authorization endpoint tells this app where to direct your browser to sign you in.</i></p>
<?= partial('partials/auth-endpoint-help') ?>
<h3>Token Endpoint</h3>
<p><i>The token endpoint is where this app will make a request to get an access token after obtaining authorization.</i></p>
<?= partial('partials/token-endpoint-help') ?>
<h3>Micropub Endpoint</h3>
<p><i>The Micropub endpoint is the URL this app will use to post new photos.</i></p>
<?= partial('partials/micropub-endpoint-help') ?>
<p>The <a href="/creating-a-micropub-endpoint">Creating a Micropub Endpoint</a> tutorial will walk you through how to handle incoming POST requests from apps like this.</p>
<h2 id="syndication">Syndication Targets</h2>
<p>You can provide a list of supported syndication targets that will appear as checkboxes when you are creating a new post.</p>
<p>To do this, your Micropub endpoint will need to respond to a GET request containing a query string of <code>q=syndicate-to</code>. This request will be made with the access token that was generated for this app, so you can choose which syndication targets you want to allow this app to use.</p>
<p>Below is the request and expected response that Quill looks for.</p>
<pre><code>GET /micropub?q=syndicate-to HTTP/1.1
Authorization: Bearer xxxxxxxxxx
HTTP/1.1 200 OK
Content-type: application/x-www-form-urlencoded
syndicate-to=syndicate-to=twitter.com%2Faaronpk%2Cfacebook.com%2Faaronpk
</code></pre>
<p>The response should be a form-encoded reply with a single field, <code>syndicate-to</code>. The value is a comma-separated list of syndication targets. The actual values are up to your Micropub endpoint, but a good convention is to use the domain name of the service (e.g. twitter.com), or domain name and username (e.g. twitter.com/aaronpk).</p>
<p>If you do include the domain name, Quill will be able to show icons for recognized services next to the checkboxes.</p>
<p>Quill will check for your supported syndication targets when you sign in, but there is also a link on the new post screen to manually re-check if you'd like.</p>
</div>

+ 1
- 1
views/layout.php View File

@ -17,7 +17,7 @@
<meta name="viewport" content="initial-scale=1.0,user-scalable=no,maximum-scale=1" media="(device-height: 568px)" />
<link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="/bootstrap/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="/bootstrap/css/bootstrap-theme.css">
<link rel="stylesheet" href="/css/style.css">
<link rel="apple-touch-icon" sizes="57x57" href="/images/teacup-icon-57.png">

+ 2
- 2
views/new-post.php View File

@ -84,7 +84,7 @@
</tr>
<tr>
<td>scope</td>
<td><code><?= $this->micropub_scope ?></code> (should be a space-separated list of permissions including "post")</td>
<td><code><?= $this->token_scope ?></code> (should be a space-separated list of permissions including "post")</td>
</tr>
<tr>
<td>micropub endpoint</td>
@ -92,7 +92,7 @@
</tr>
<tr>
<td>access token</td>
<td>String of length <b><?= strlen($this->micropub_access_token) ?></b><?= (strlen($this->micropub_access_token) > 0) ? (', ending in <code>' . substr($this->micropub_access_token, -7) . '</code>') : '' ?> (should be greater than length 0)</td>
<td>String of length <b><?= strlen($this->access_token) ?></b><?= (strlen($this->access_token) > 0) ? (', ending in <code>' . substr($this->access_token, -7) . '</code>') : '' ?> (should be greater than length 0)</td>
</tr>
</table>
</div>

Loading…
Cancel
Save