diff options
author | Aaron Parecki <aaron@parecki.com> | 2014-05-24 14:41:21 -0700 |
---|---|---|
committer | Aaron Parecki <aaron@parecki.com> | 2014-05-24 14:41:21 -0700 |
commit | 3f82ec2f757c62c25a31b461e0a0cddc14886117 (patch) | |
tree | 8eb85c7f356df87f2bf477a54c7ab521002492a1 /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.php | 68 | ||||
-rw-r--r-- | views/auth_error.php | 4 | ||||
-rw-r--r-- | views/auth_start.php | 54 | ||||
-rw-r--r-- | views/creating-a-micropub-endpoint.php | 90 | ||||
-rw-r--r-- | views/dashboard.php | 214 | ||||
-rw-r--r-- | views/docs.php | 22 | ||||
-rw-r--r-- | views/index.php | 9 | ||||
-rw-r--r-- | views/layout.php | 90 | ||||
-rw-r--r-- | views/partials/auth-endpoint-help.php | 3 | ||||
-rw-r--r-- | views/partials/micropub-endpoint-help.php | 3 | ||||
-rw-r--r-- | views/partials/token-endpoint-help.php | 6 | ||||
-rw-r--r-- | views/signin.php | 10 |
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><link></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><link></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><link></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 »</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">© <?=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><link rel="authorization_endpoint" href="https://indieauth.com/auth"></code></pre></p> + <p><pre><code>Link: <https://indieauth.com/auth>; 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><link rel="micropub" href="https://<?= (property_exists($this, 'meParts') ? $this->meParts['host'] : 'example.com') ?>/micropub"></code></pre></p> + <p><pre><code>Link: <https://<?= (property_exists($this, 'meParts') ? $this->meParts['host'] : 'example.com') ?>/micropub>; 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><link rel="token_endpoint" href="https://tokens.indieauth.com/token"></code></pre></p> + <p><pre><code>Link: <https://tokens.indieauth.com/token>; 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> + |