summaryrefslogtreecommitdiff
path: root/views
diff options
context:
space:
mode:
authorAaron Parecki <aaron@parecki.com>2014-05-24 14:41:21 -0700
committerAaron Parecki <aaron@parecki.com>2014-05-24 14:41:21 -0700
commit3f82ec2f757c62c25a31b461e0a0cddc14886117 (patch)
tree8eb85c7f356df87f2bf477a54c7ab521002492a1 /views
Working app! Copied signin logic from OwnYourGram. New "post" interface for writing a simple text post. Also supports browser geolocation.
Diffstat (limited to 'views')
-rw-r--r--views/auth_callback.php68
-rw-r--r--views/auth_error.php4
-rw-r--r--views/auth_start.php54
-rw-r--r--views/creating-a-micropub-endpoint.php90
-rw-r--r--views/dashboard.php214
-rw-r--r--views/docs.php22
-rw-r--r--views/index.php9
-rw-r--r--views/layout.php90
-rw-r--r--views/partials/auth-endpoint-help.php3
-rw-r--r--views/partials/micropub-endpoint-help.php3
-rw-r--r--views/partials/token-endpoint-help.php6
-rw-r--r--views/signin.php10
12 files changed, 573 insertions, 0 deletions
diff --git a/views/auth_callback.php b/views/auth_callback.php
new file mode 100644
index 0000000..44c2daa
--- /dev/null
+++ b/views/auth_callback.php
@@ -0,0 +1,68 @@
+<?php if($this->tokenEndpoint): ?>
+
+ <?php if(!$this->auth): ?>
+
+ <h3>Bad response from token endpoint</h3>
+ <p>Your token endpoint returned a response that was not understood.</p>
+
+ <?php else: ?>
+
+ <?php if(k($this->auth, 'error')): ?>
+
+ <h3>Error</h3>
+ <p>Got an error response from the token endpoint:</p>
+ <div class="bs-callout bs-callout-danger">
+ <h4><?= $this->auth['error'] ?></h4>
+ <?= k($this->auth, 'error_description') ? ('<p>'.$this->auth['error_description'].'</p>') : '' ?>
+ </div>
+
+ <?php else: ?>
+
+ <!-- Check for all the required parts of the token -->
+ <?php if(k($this->auth, array('me','access_token','scope'))): ?>
+
+ <h3>Success!</h3>
+
+ <p>All required values were found! You are now signed in.</p>
+ <p><a href="/new" class="btn btn-primary">Continue</a></p>
+
+ <?php else: ?>
+
+ <?php if(!k($this->auth, 'access_token')): ?>
+ <h4>Missing <code>access_token</code></h4>
+ <p>The token endpoint did not return an access token. The <code>access_token</code> parameter is the token the client will use to make requests to the Micropub endpoint.</p>
+ <?php endif; ?>
+
+ <?php if(!k($this->auth, 'me')): ?>
+ <h4>Missing <code>me</code></h4>
+ <p>The token endpoint did not return a "me" parameter. The <code>me</code> parameter lets this client know what user the token is for.</p>
+ <?php endif; ?>
+
+ <?php if(!k($this->auth, 'scope')): ?>
+ <h4>Missing <code>scope</code></h4>
+ <p>The token endpoint did not return a "scope" parameter. The <code>scope</code> parameter lets this client what permission the token represents.</p>
+ <?php endif; ?>
+
+ <?php endif; ?>
+
+ <?php endif; ?>
+ <?php endif; ?>
+
+
+ <h3>Token endpoint response</h3>
+
+ <p>Below is the raw response from your token endpoint (<?= $this->tokenEndpoint ?>):</p>
+ <div class="bs-callout bs-callout-info pre">
+ <?= $this->curl_error ?>
+ <?= htmlspecialchars($this->response) ?>
+ </div>
+
+
+<?php else: ?>
+
+
+ <h3>Error</h3>
+ <p>Could not find your token endpoint. We found it last time, so double check nothing on your website has changed in the mean time.</p>
+
+
+<?php endif; ?>
diff --git a/views/auth_error.php b/views/auth_error.php
new file mode 100644
index 0000000..818ded4
--- /dev/null
+++ b/views/auth_error.php
@@ -0,0 +1,4 @@
+<h2><?= $this->error ?></h2>
+
+<p><?= $this->errorDescription ?></p>
+
diff --git a/views/auth_start.php b/views/auth_start.php
new file mode 100644
index 0000000..819fd65
--- /dev/null
+++ b/views/auth_start.php
@@ -0,0 +1,54 @@
+<div id="authorization_endpoint">
+ <h3>Authorization Endpoint</h3>
+
+ <p><i>The authorization endpoint tells this app where to direct your browser to sign you in.</i></p>
+
+ <?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 or in an HTTP header.</p>
+ <?= partial('partials/auth-endpoint-help') ?>
+ <?php endif; ?>
+</div>
+
+<div id="token_endpoint">
+ <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>
+
+ <?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 or in an HTTP header.</p>
+ <?= partial('partials/token-endpoint-help') ?>
+ <?php endif; ?>
+
+</div>
+
+<div id="micropub_endpoint">
+ <h3>Micropub Endpoint</h3>
+
+ <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 or in an HTTP header.</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">Authorize</a>
+
+<?php endif; ?>
+
diff --git a/views/creating-a-micropub-endpoint.php b/views/creating-a-micropub-endpoint.php
new file mode 100644
index 0000000..617b52f
--- /dev/null
+++ b/views/creating-a-micropub-endpoint.php
@@ -0,0 +1,90 @@
+<?php ob_start() ?>
+## The Micropub Endpoint
+
+After a client has obtained an access token and discovered the user's Micropub endpoint
+it is ready to make requests to create posts.
+
+### The Request
+
+This is not intended to be a comprehensive guide to Micropub, and only includes the
+fields that this client sends.
+
+The request to create a post will be sent with as a standard HTTP form-encoded request
+The example code here is written in PHP but the idea is applicable in any language.
+
+The request will contain the following POST parameters:
+
+* `h=entry` - Indicates the type of object being created, in this case an <a href="http://indiewebcamp.com/h-entry">h-entry</a>.
+* `content` - The text content the user entered, in this case the caption on the Instagram photo.
+* `category` - A comma-separated list of tags that you entered
+* `location` - A "geo" URI including the latitude and longitude of the photo if included. (Will look like `geo:37.786971,-122.399677;u=50`, where u=50 indicates the "uncertainty" of the location in meters)
+* `in-reply-to` - If set, this is a URL that the post is in reply to
+
+The request will also contain an access token in the HTTP `Authorization` header:
+
+<pre>
+Authorization: Bearer XXXXXXXX
+</pre>
+
+
+### Verifying Access Tokens
+
+Before you can begin processing the photo, you must first verify the access token is valid
+and contains at least the "post" scope.
+
+How exactly you do this is dependent on your architecture. You can query the token endpoint
+to check if an access token is still valid. See [https://tokens.indieauth.com/#verify tokens.indieauth.com]
+for more information.
+
+Once you have looked up the token info, you need to make a determination
+about whether that access token is still valid. You'll have the following information
+at hand that can be used to check:
+
+* `me` - The user who this access token corresponds to.
+* `client_id` - The app that generated the token.
+* `scope` - The list of scopes that were authorized by the user.
+* `issued_at` - The date the token was issued.
+
+Keep in mind that it may be possible for another user besides yourself to have created
+an access token at your token endpoint, so the first thing you'll do when verifying
+is making sure the "me" parameter matches your own domain. This way you are the only
+one that can create posts on your website.
+
+
+### Validating the Request Parameters
+
+A valid request to create a post will contain the parameters listed above. For now,
+you can verify the presence of everything in the list, or you can try to genericize your
+micropub endpoint so that it can also create [http://ownyourgram.com/creating-a-micropub-endpoint photo posts].
+
+At a bare minimum, a Micropub request will contain the following:
+
+* `h=entry`
+* `content`
+
+The access token must also contain at least the "post" scope.
+
+
+### The Response
+
+Once you've validated the access token and checked for the presence of all required parameters,
+you can create a post in your website with the information provided.
+
+If a post was successfully created, the endpoint must return an `HTTP 201` response with a
+`Location` header that points to the URL of the post. No body is required for the response.
+
+<pre>
+HTTP/1.1 201 Created
+Location: http://example.com/post/100
+</pre>
+
+If there was an error, the response should include an HTTP error code as appropriate,
+and optionally an HTML or other body with more information. Below is a list of possible errors.
+
+* `HTTP 401 Unauthorized` - No access token was provided in the request.
+* `HTTP 403 Forbidden` - An access token was provided, but the authenticated user does not have permission to complete the request.
+* `HTTP 400 Bad Request` - Something was wrong with the request, such as a missing "h" parameter, or other missing data. The response body may contain more human-readable information about the error.
+
+
+
+<?= Markdown(ob_get_clean()) ?>
diff --git a/views/dashboard.php b/views/dashboard.php
new file mode 100644
index 0000000..7a35ba1
--- /dev/null
+++ b/views/dashboard.php
@@ -0,0 +1,214 @@
+
+ <div style="max-width: 700px; margin: 0 auto;">
+ <form role="form">
+
+ <div class="form-group">
+ <label for="note_content"><code>content</code></label>
+ <textarea id="note_content" value="" class="form-control" style="height: 4em;"></textarea>
+ </div>
+
+ <div class="form-group">
+ <label for="note_in_reply_to"><code>in-reply-to</code> (optional, a URL you are replying to)</label>
+ <input type="text" id="note_in_reply_to" value="" class="form-control">
+ </div>
+
+ <div class="form-group">
+ <label for="note_category"><code>category</code> (comma-separated list of tags)</label>
+ <input type="text" id="note_category" value="" class="form-control" placeholder="e.g. web, personal">
+ </div>
+
+ <div class="form-group">
+ <label for="note_location"><code>location</code></label>
+ <input type="checkbox" id="note_location_chk" value="">
+ <img src="/images/spinner.gif" id="note_location_loading" style="display: none;">
+
+ <input type="text" id="note_location_msg" value="" class="form-control" placeholder="" readonly="readonly">
+ <input type="hidden" id="note_location">
+
+ <div id="note_location_img" style="display: none;">
+ <img src="" height="180" id="note_location_img_wide" class="img-responsive">
+ <img src="" height="320" id="note_location_img_small" class="img-responsive">
+ </div>
+ </div>
+
+ </form>
+
+ <button class="btn btn-success" id="btn_post">Post</button>
+
+ <div class="alert alert-success hidden" id="test_success"><strong>Success! We found a Location header in the response!</strong><br>Your post should be on your website now!<br><a href="" id="post_href">View your post</a></div>
+ <div class="alert alert-danger hidden" id="test_error"><strong>Your endpoint did not return a Location header.</strong><br>See <a href="/creating-a-micropub-endpoint">Creating a Micropub Endpoint</a> for more information.</div>
+
+
+ <?php if($this->test_response): ?>
+ <h4>Last response from your Micropub endpoint <span id="last_response_date">(<?= relative_time($this->response_date) ?>)</span></h4>
+ <?php endif; ?>
+ <pre id="test_response" style="width: 100%; min-height: 240px;"><?= htmlspecialchars($this->test_response) ?></pre>
+
+
+ <div class="callout">
+ <p>Clicking "Post" will post this note to your Micropub endpoint. Below is some information about the request that will be made.</p>
+
+ <table class="table table-condensed">
+ <tr>
+ <td>me</td>
+ <td><code><?= session('me') ?></code> (should be your URL)</td>
+ </tr>
+ <tr>
+ <td>scope</td>
+ <td><code><?= $this->micropub_scope ?></code> (should be a space-separated list of permissions including "post")</td>
+ </tr>
+ <tr>
+ <td>micropub endpoint</td>
+ <td><code><?= $this->micropub_endpoint ?></code> (should be a URL)</td>
+ </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>
+ </tr>
+ </table>
+ </div>
+
+ </div>
+
+<script>
+$(function(){
+
+ $("#btn_post").click(function(){
+ $.post("/micropub/post", {
+ content: $("#note_content").val(),
+ in_reply_to: $("#note_in_reply_to").val(),
+ location: $("#note_location").val(),
+ category: $("#note_category").val()
+ }, function(data){
+ var response = JSON.parse(data);
+
+ if(response.location != false) {
+ $("#test_success").removeClass('hidden');
+ $("#test_error").addClass('hidden');
+ $("#post_href").attr("href", response.location);
+
+ $("#note_content").val("");
+ $("#note_in_reply_to").val("");
+ $("#note_category").val("");
+
+ } else {
+ $("#test_success").addClass('hidden');
+ $("#test_error").removeClass('hidden');
+ }
+
+ $("#last_response_date").html("(just now)");
+ $("#test_response").html(response.response);
+ })
+ });
+
+ function location_error(msg) {
+ $("#note_location_msg").val(msg);
+ $("#note_location_chk").removeAttr("checked");
+ $("#note_location_loading").hide();
+ $("#note_location_img").hide();
+ $("#note_location_msg").removeClass("img-visible");
+ }
+
+ var map_template_wide = "<?= static_map('{lat}', '{lng}', 180, 700, 15) ?>";
+ var map_template_small = "<?= static_map('{lat}', '{lng}', 320, 480, 15) ?>";
+
+ $("#note_location_chk").click(function(){
+ if($(this).attr("checked") == "checked") {
+ if(navigator.geolocation) {
+ $("#note_location_loading").show();
+
+ navigator.geolocation.getCurrentPosition(function(position){
+
+ $("#note_location_loading").hide();
+ console.log(position);
+ var geo = "geo:" + (Math.round(position.coords.latitude * 100000) / 100000) + "," + (Math.round(position.coords.longitude * 100000) / 100000) + ";u=" + position.coords.accuracy;
+ $("#note_location_msg").val(geo);
+ $("#note_location").val(geo);
+ $("#note_location_img_small").attr("src", map_template_small.replace('{lat}', position.coords.latitude).replace('{lng}', position.coords.longitude));
+ $("#note_location_img_wide").attr("src", map_template_wide.replace('{lat}', position.coords.latitude).replace('{lng}', position.coords.longitude));
+ $("#note_location_img").show();
+ $("#note_location_msg").addClass("img-visible");
+
+ }, function(err){
+ if(err.code == 1) {
+ location_error("The website was not able to get permission");
+ } else if(err.code == 2) {
+ location_error("Location information was unavailable");
+ } else if(err.code == 3) {
+ location_error("Timed out getting location");
+ }
+ });
+
+ } else {
+ location_error("Browser location is not supported");
+ }
+ } else {
+ $("#note_location_img").hide();
+ $("#note_location_msg").removeClass("img-visible");
+ $("#note_location_msg").val('');
+ $("#note_location").val('');
+ }
+ });
+
+});
+</script>
+<style type="text/css">
+
+ #last_response_date {
+ font-size: 80%;
+ font-weight: normal;
+ }
+
+ #btn_post {
+ margin-bottom: 10px;
+ }
+
+ @media all and (max-width: 480px) {
+ #note_location_img_wide {
+ display: none;
+ }
+ #note_location_img_small {
+ display: block;
+ }
+ }
+ @media all and (min-width: 480px) {
+ #note_location_img_wide {
+ display: block;
+ }
+ #note_location_img_small {
+ display: none;
+ }
+ }
+
+ .img-visible {
+ -webkit-border-bottom-right-radius: 0;
+ -webkit-border-bottom-left-radius: 0;
+ -moz-border-radius-bottomright: 0;
+ -moz-border-radius-bottomleft: 0;
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+ }
+
+ #note_location_img img {
+ margin-top: -1px;
+ border: 1px solid #ccc;
+ -webkit-border-bottom-right-radius: 4px;
+ -webkit-border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+ -moz-border-radius-bottomleft: 4px;
+ border-bottom-right-radius: 4px;
+ border-bottom-left-radius: 4px;
+ }
+
+ .callout {
+ border-left: 4px #5bc0de solid;
+ background-color: #f4f8fa;
+ padding: 20px;
+ margin-top: 10px;
+ }
+ .callout table {
+ margin-bottom: 0;
+ }
+
+</style>
+
diff --git a/views/docs.php b/views/docs.php
new file mode 100644
index 0000000..5b2fc3a
--- /dev/null
+++ b/views/docs.php
@@ -0,0 +1,22 @@
+<h2>Introduction</h2>
+
+<div class="col-xs-6 col-md-4" style="float: right;">
+ <span class="thumbnail"><img src="/images/indiepost-ui.png"></span>
+</div>
+
+<p>This is a simple <a href="http://indiewebcamp.com/micropub">Micropub</a> client for creating text posts on your own website. To use it, you will need to turn your website into an OAuth provider, and implement a Micropub endpoint that this app will send requests to.</p>
+
+<p>Once you've signed in, you'll see an interface like the below which you can use to write a post. Clicking "post" will make a Micropub request to your endpoint.<p>
+
+<h2>Configuring Endpoints</h2>
+
+<h3>Authorization Endpoint</h3>
+<?= partial('partials/auth-endpoint-help') ?>
+
+<h3>Token Endpoint</h3>
+<?= partial('partials/token-endpoint-help') ?>
+
+<h3>Micropub Endpoint</h3>
+<?= 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>
diff --git a/views/index.php b/views/index.php
new file mode 100644
index 0000000..2c62f09
--- /dev/null
+++ b/views/index.php
@@ -0,0 +1,9 @@
+ <div class="jumbotron">
+ <h2>#IndiePost</h2>
+ <p>How does it work?</p>
+ <ol>
+ <li>Sign in with your domain</li>
+ <li>Post a note!</li>
+ </ol>
+ <p><a href="/signin" class="btn btn-primary btn-lg" role="button">Get Started &raquo;</a></p>
+ </div>
diff --git a/views/layout.php b/views/layout.php
new file mode 100644
index 0000000..d2421d9
--- /dev/null
+++ b/views/layout.php
@@ -0,0 +1,90 @@
+<!doctype html>
+<html lang="en">
+ <head>
+ <title><?= $this->title ?></title>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <link rel="pingback" href="http://webmention.io/aaronpk/xmlrpc" />
+ <link rel="webmention" href="http://webmention.io/aaronpk/webmention" />
+
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css">
+ <link rel="stylesheet" href="/bootstrap/css/bootstrap-theme.min.css">
+ <link rel="stylesheet" href="/css/style.css">
+
+ <script src="/js/jquery-1.7.1.min.js"></script>
+ </head>
+
+<body role="document">
+<script type="text/javascript">
+
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', '<?= Config::$gaid ?>']);
+ _gaq.push(['_trackPageview']);
+
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
+
+</script>
+
+<div class="navbar navbar-inverse navbar-fixed-top">
+ <div class="container">
+ <div class="navbar-header">
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <a class="navbar-brand" href="/">IndiePost</a>
+ </div>
+ <div class="navbar-collapse collapse">
+ <ul class="nav navbar-nav">
+ <? if(session('me')) { ?>
+ <li><a href="/new">New Post</a></li>
+ <? } ?>
+ <li><a href="/docs">Docs</a></li>
+ <!-- <li><a href="/about">About</a></li> -->
+ <!-- <li><a href="/contact">Contact</a></li> -->
+ </ul>
+ <? if(session('me')) { ?>
+ <ul class="nav navbar-nav navbar-right">
+ <li><a href="/user?domain=<?= urlencode(session('me')) ?>"><?= session('me') ?></a></li>
+ <li><a href="/signout">Sign Out</a></li>
+ </ul>
+ <? } else if(property_exists($this, 'authorizing')) { ?>
+ <ul class="nav navbar-right">
+ <li class="navbar-text"><?= $this->authorizing ?></li>
+ </ul>
+ <? } else { ?>
+ <ul class="nav navbar-right" style="font-size: 8pt;">
+ <li><a href="https://indieauth.com/setup">What's This?</a></li>
+ </ul>
+ <form action="/auth/start" method="get" class="navbar-form navbar-right">
+ <input type="text" name="me" placeholder="yourdomain.com" class="form-control" />
+ <button type="submit" class="btn">Sign In</button>
+ <input type="hidden" name="redirect_uri" value="https://<?= $_SERVER['SERVER_NAME'] ?>/indieauth" />
+ </form>
+ <? } ?>
+ </div>
+ </div>
+</div>
+
+<div class="page">
+
+ <div class="container">
+ <?= $this->fetch($this->page . '.php') ?>
+ </div>
+
+ <div class="footer">
+ <p class="credits">&copy; <?=date('Y')?> by <a href="http://aaronparecki.com">Aaron Parecki</a>.
+ This code is <a href="https://github.com/aaronpk/IndiePost">open source</a>.
+ Feel free to send a pull request, or <a href="https://github.com/aaronpk/IndiePost/issues">file an issue</a>.</p>
+ </div>
+</div>
+
+</body>
+</html> \ No newline at end of file
diff --git a/views/partials/auth-endpoint-help.php b/views/partials/auth-endpoint-help.php
new file mode 100644
index 0000000..6378db4
--- /dev/null
+++ b/views/partials/auth-endpoint-help.php
@@ -0,0 +1,3 @@
+ <p>You can create your own authorization endpoint, but it's easier to use an existing service such as <a href="https://indieauth.com/">IndieAuth.com</a>. To delegate to IndieAuth.com, you can use the markup provided below.</p>
+ <p><pre><code>&lt;link rel="authorization_endpoint" href="https://indieauth.com/auth"&gt;</code></pre></p>
+ <p><pre><code>Link: &lt;https://indieauth.com/auth&gt;; rel="authorization_endpoint"</code></pre></p>
diff --git a/views/partials/micropub-endpoint-help.php b/views/partials/micropub-endpoint-help.php
new file mode 100644
index 0000000..35705ca
--- /dev/null
+++ b/views/partials/micropub-endpoint-help.php
@@ -0,0 +1,3 @@
+ <p>You will need to <a href="/creating-a-micropub-endpoint">create a Micropub endpoint</a> for your website which can create posts on your site. Once you've created the Micropub endpoint, you can indicate its location using the markup below.</p>
+ <p><pre><code>&lt;link rel="micropub" href="https://<?= (property_exists($this, 'meParts') ? $this->meParts['host'] : 'example.com') ?>/micropub"&gt;</code></pre></p>
+ <p><pre><code>Link: &lt;https://<?= (property_exists($this, 'meParts') ? $this->meParts['host'] : 'example.com') ?>/micropub&gt;; rel="micropub"</code></pre></p>
diff --git a/views/partials/token-endpoint-help.php b/views/partials/token-endpoint-help.php
new file mode 100644
index 0000000..ea5442e
--- /dev/null
+++ b/views/partials/token-endpoint-help.php
@@ -0,0 +1,6 @@
+ <p>You can <a href="/creating-a-token-endpoint">create your own token endpoint</a> for
+ your website which can issue access tokens when given an authorization code, but
+ it's easier to use an existing service such as <a href="https://tokens.indieauth.com">tokens.indieauth.com</a>.
+ To use this service as your token endpoint, use the markup provided below.</p>
+ <p><pre><code>&lt;link rel="token_endpoint" href="https://tokens.indieauth.com/token"&gt;</code></pre></p>
+ <p><pre><code>Link: &lt;https://tokens.indieauth.com/token&gt;; rel="token_endpoint"</code></pre></p>
diff --git a/views/signin.php b/views/signin.php
new file mode 100644
index 0000000..228cf32
--- /dev/null
+++ b/views/signin.php
@@ -0,0 +1,10 @@
+
+<form action="/auth/start" method="get">
+ <input type="text" name="me" placeholder="http://me.com" value="" class="form-control"><br>
+
+ <input type="hidden" name="client_id" value="https://indiepost.micropub.net">
+ <input type="hidden" name="redirect_uri" value="https://indiepost.micropub.net/auth/callback">
+
+ <input type="submit" value="Sign In" class="btn btn-primary">
+</form>
+