Skip to content

Commit

Permalink
Update Z api docs (#167)
Browse files Browse the repository at this point in the history
  • Loading branch information
jxjj authored May 14, 2024
1 parent 82a2c35 commit fc03cb4
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 149 deletions.
310 changes: 162 additions & 148 deletions app/views/api_keys/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,167 +2,181 @@
<%= t('views.api_keys.index.title') %>
<% end %>
<div class="tw-p-4">
<div class="umn-post-it">
<div class="tw-prose tw-prose-lg tw-py-8 tw-mx-auto">
<div class="tw-flex tw-flex-col tw-gap-2">
<div class="panel panel-default">
<div class="panel-heading">Access ID</div>
<div class="panel-body">
<%= current_user.uid %>
<div class="umn-post-it md:tw-grid tw-grid-cols-4 tw-py-16 tw-gap-8">
<aside class="tw-col-start-4 tw-row-start-1 tw-mb-8">
<div class="tw-sticky tw-top-4 tw-p-4 tw-bg-neutral-100">
<h2 class="tw-uppercase tw-text-xs tw-text-neutral-400 tw-mt-0 tw-mb-2">Contents</h2>
<ul>
<li><a href="#using-the-api">Using the API</a></li>
<li>
<a href="#create-zlink">Create Z-links</a>
</li>
<li>
<a href="#update-zlink">Update Z-links</a>
</li>
</ul>
</div>
</aside>
<div class="tw-prose tw-prose-lg tw-mx-auto tw-col-span-3 tw-grid tw-col-start-1 tw-row-start-1">
<section id="using-the-api">
<header class="tw-sticky tw-top-0 tw-backdrop-blur-sm">
<h2 class="tw-mt-0">Using the Z Api</h2>
</header>
<div class="tw-flex tw-flex-col tw-gap-2">
<div class="panel panel-default">
<div class="panel-heading">Access ID</div>
<div class="panel-body">
<%= current_user.uid %>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">Secret Key</div>
<div class="panel-body">
<%= current_user.secret_key %>
<%= link_to api_keys_path, method: :post, class: 'btn btn-default clipboard-btn' do %>
<i class=" fa fa-key"></i>
Generate new key
<% end %>
<div class="panel panel-default">
<div class="panel-heading">Secret Key</div>
<div class="panel-body">
<%= current_user.secret_key %>
<%= link_to api_keys_path, method: :post, class: 'btn btn-default clipboard-btn' do %>
<i class=" fa fa-key"></i>
Generate new key
<% end %>
</div>
</div>
</div>
</div>
<p>Z features an API to allow Short URL creation directly from your applications.</p>
<p>The API is configured to use <strong>JSON Web Tokens</strong> (or <strong>JWT</strong>, <a href="https://jwt.io">https://jwt.io</a>) to ensure secure communications between the client and server. This is a popular method that has libraries for most popular programming languages. See their website for specific language support.</p>
<p>Z has one API endpoint, <strong><a href="http://z.umn.edu/api/v1/urls"><%= "#{request.base_url}#{api_v1_urls_path}" %></a></strong>. You can POST to this endpoint with your <strong>Access ID</strong> and the <strong>JWT</strong> (signed by your <strong>secret key</strong>), separated by a colon in an <strong>Authorization</strong> Header, like so:</p>
<pre>
<code>Header Name: Authorization
Header Value: access_id:jwt
</code></pre>
<ul>
<li><a href="#token-information">Token Information</a>
<ul>
<li><a href="#header">Header</a></li>
<li><a href="#payload">Payload</a>
<ul>
<li><a href="#supported-keys">Supported keys</a></li>
</ul>
</li>
<li><a href="#signature">Signature</a></li>
</ul>
</li>
<li><a href="#return-values">Return Values</a></li>
<li><a href="#complete-example">A Complete Example in Ruby</a></li>
</ul>
<a name="token-information" id="token-information"></a>
<h2>Token Information</h2>
<hr>
<p>The JWT is an encrypted string that has three components - a <strong>header</strong>, <strong>payload</strong>, and <strong>signature</strong>, each separated by a period.</p>
<a name="header" id="header"></a>
<h3>Header</h3>
<hr>
<p>The header is the standard configuration for H256.</p>
<pre>
<code>Header:
{
&quot;alg&quot;: &quot;HS256&quot;,
&quot;typ&quot;: &quot;JWT&quot;
}
</code></pre>
<a name="payload" id="payload"></a>
<h3>Payload</h3>
<hr>
<p>The payload is a single JSON object, containing one key, “urls”. This key contains an array of JSON objects for each URL you would like created. Example:</p>
<pre>
<code>Payload:
{
&quot;urls&quot;: [
{ &quot;url&quot;: &quot;http://google.com&quot;, },
{
&quot;url&quot;: &quot;http://example.com/internet&quot;,
&quot;keyword&quot;: &quot;einter&quot;
},
...
]
}
</code></pre>
<a name="supported-keys" id="supported-keys"></a>
<h4>Supported keys</h4>
<p>The supported keys for a URL are as follows:</p>
<ul>
<li>&quot;<strong>url</strong>&quot; : The URL you would like shortened (required)</li>
<li>&quot;<strong>keyword</strong>&quot; : The keyword you would prefer (optional)</li>
<li>&quot;<strong>collection</strong>&quot; : The collection name you would like this URL to be placed into. (optional).</li>
</ul>
<p>If no keyword or collection are specified, Z will auto-generate a short URL for you. If the “keyword” you request is already taken, the URL will not be created. If a “collection” name cannot be found, the URL will not be created.</p>
<a name="signature" id="signature"></a>
<h3>Signature</h3>
<hr>
<p>The final signature is an encoded string containing the header, the payload, and signed with your secret key.</p>
<pre><code>Signature:
HMACSHA256(
base64UrlEncode(header) + &quot;.&quot; +
base64UrlEncode(payload),
&lt;secret_key&gt;
)
</code></pre>
<a name="return-values" id="return-values"></a>
<h2>Return values</h2>
<hr>
<p>Upon sending the request, the you can expect the response to be an array of JSON objects, with each item containing the original URL data from the request, as well as a result object that contains two items.</p>
<p>The first result key is “status”, which will either be “success” or “error”.</p>
<p>The second result key is “message”, which will either contain a string with the new Short URL for your item, or an array of errors returned by the system.</p>
<pre>
<code>Result:
[
{
&quot;url&quot;: 'http://google.com',
&quot;result&quot;:
{
&quot;status&quot;: 'success',
&quot;message&quot;: 'http://localhost:3000/2siu'
}
},
{
&quot;url&quot;: 'http://example.com/internet',
&quot;keyword&quot;: 'einter',
&quot;result&quot;:
{
&quot;status&quot;: 'error',
&quot;message&quot;: [
'Keyword has already been taken.'
]
}
<p>Z features an API for creating and updating directly from your applications.</p>
<p>
Each API request must have an http <code>Authorization</code> header with your <b>Access ID</b> (above) and a payload signed with your <b>Secret Key</b> (above) in the form of a <a href="https://jwt.io">JSON Web Token</a> (or JWT). The access id and signed payload are separated by a colon <code>:</code>.
</p>
<pre><code>Authorization: {your_access_id}:{your_signed_payload}</code></pre>
<p>
To create your signed payload, use <a href="https://jwt.io/libraries">a JWT library</a> for your programming language.
</p>
<p>
The examples below use JavaScript and the <a href="https://github.com/auth0/node-jsonwebtoken">jsonwebtoken</a> library. You can also <a href="https://github.com/UMN-LATIS/z-api-example">find an example implementation</a> on Github.
</p>
</section>
<section id="create-zlink">
<header class="tw-sticky tw-top-0 tw-backdrop-blur-sm">
<h2 class="tw-m-0">Create Z-Links (batch)</h2>
<p class="tw-text-center tw-mt-0"><b>POST `/api/v1/urls`</b></p>
</header>
<h3>Payload</h3>
<pre><code>{
"urls": [
{ "url": "https://example.org" },
{ "url": "https://example.org/1", "keyword": "my-zlink" },
{
"url": "https://example.org/2",
"keyword": "my-zlink2",
"collection": "my-collection-name"
}
]
</code></pre>
<p>Requests that are sent with invalid tokens, incorrect headers, or invalid access IDs, will be returned with a response of <strong>401 Unauthorized</strong>.</p>
<a name="complete-example" id="complete-example"></a>
<h2>Complete Example</h2>
<hr>
<p>Thankfully, many JWT libraries do most of the hard work for us -- we just provide the payload and secret key and the library will create the token for us.</p>
<p>Here’s a full example using Ruby with a JWT library (<a href="https://github.com/jwt/ruby-jwt">https://github.com/jwt/ruby-jwt</a>).</p>
<p>Here, we assume that your Access ID is “12345asdf”, your secret key is “SuperSecretKey”, and you want to create a short URL for the website &quot;<a href="http://example.com/fun/site/12345/neat.php">http://example.com/fun/site/12345/neat.php</a>&quot;</p>
<pre>
<code>require 'jwt'
require 'net/http'
require 'uri'
],
}</code></pre>
<p>Parameters:</p>
<ul>
<li><code>urls</code> <small>(required)</small> - array of urls to create</li>
<li><code>urls.url</code> <small>(required)</small> - long url of the z-link</li>
<li><code>urls.keyword</code> - short code which redirects to the url</li>
<li><code>urls.collection</code> - name of collection to add this url to</li>
</ul>
<h3>Example</h3>
<p>Request:</p>
<pre><code>import jsonwebtoken from "jsonwebtoken";

access_id = '1234asdf'
secret_key = 'SuperSecretKey'
const ACCESS_ID = "my_access_id"; // use your access_id from above
const SECRET_KEY = "fhRzRyD..."; // use your secret_key from above

payload = {
urls: [
{ url: 'http://example.com/fun/site/12345/neat.php' }
]
const payload = {
urls: [
{ url: "https://example.org" },
{ url: "https://example.org/1", keyword: "my-zlink" },
// ...
],
};

// signing your payload with `SECRET_KEY`
const signedPayload = jsonwebtoken.sign(payload, SECRET_KEY)

// send the POST request to the server
const response = await fetch(
`https://z.umn.edu/api/v1/urls`,
{
method: "POST",
headers: {
Authorization: `${ACCESS_ID}:${signedPayload}`,
},
}
);

jwt = JWT.encode payload, secret_key, 'HS256'
const json = await response.json();
console.log(JSON.stringify(json));
</code></pre>
<p>Response:</p>
<pre><code>[{
"url": "https://example.org",
"result": {
"status": "success",
"message": "https://z.umn.edu/rd4n3b"
},
},{
"url": "https://example.org/1",
"keyword": "my-zlink",
"result": {
"status": "error",
"message": ["Keyword has already been taken"]
},
}];</code></pre>
</section>
<section id="update-zlink">
<header class="tw-sticky tw-top-0 tw-backdrop-blur-sm tw-bg-white/25">
<h2 class="tw-m-0">Update Z-Link Url</h2>
<p class="tw-m-0 tw-text-center"><b>PUT `/api/v1/urls/{keyword}`</b></p>
</header>
<h3>Payload</h3>
<pre><code>{
"keyword": "my-zlink",
"url": "https://example.org/updated-url"
}</code></pre>
<p>Parameters:</p>
<ul>
<li><code>keyword</code> <small>(required)</small> - short code which redirects to the url. This cannot be updated via API.</li>
<li><code>url</code> <small>(required)</small> - long url of the z-link</li>
</ul>
<h3>Example</h3>
<p>Request:</p>
<pre><code>import jsonwebtoken from "jsonwebtoken";

# jwt =
# eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmxzIjpbeyJ1cmwiOiJodHRwOi8vZXhhbXBsZS5jb20vZnVuL3NpdGUvMTIzNDUvbmVhdC5waHAifV19.l8oAAw6SeJgyrE7v65Pxdxz7Yya8UZYCcc3kj5Amieo
const ACCESS_ID = "my_access_id"; // use your access_id from above
const SECRET_KEY = "fhRzRyD..."; // use your secret_key from above

uri = URI('<%= "#{request.base_url}#{api_v1_urls_path}" %>')
const payload = {
keyword: "my-zlink",
url: "https://example.org/updated-url"
};

Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
request = Net::HTTP::Post.new uri
request.add_field('Authorization', "#{access_id}:#{jwt}")
// sign your payload with `SECRET_KEY`
const signedPayload = jsonwebtoken.sign(payload, SECRET_KEY)

response = http.request request
puts response.body
end
// send the POST request to the server
const response = await fetch(
`https://z.umn.edu/api/v1/urls/${payload.keyword}`,
{
method: "PUT",
headers: {
Authorization: `${ACCESS_ID}:${signedPayload}`,
},
}
);

</code></pre>
const json = await response.json();
console.log(JSON.stringify(json));
</code></pre>
<p>Response:</p>
<pre><code>{
"status": "success",
"message": {
"url": "https://example.org/updated-url",
"keyword": "my-zlink",
}
}</code></pre>
</section>
</div>
</div>
</div>
2 changes: 1 addition & 1 deletion app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<%= yield %>
</main>
<div data-behavior="vue">
<app-footer/>
<app-footer></app-footer>
</div>
</body>
</html>

0 comments on commit fc03cb4

Please sign in to comment.