A => LICENSE +15 -0
@@ 1,15 @@
+ISC License
+
+Copyright (c) 2019, Thomas Sileo
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
A => README.md +3 -0
@@ 1,3 @@
+# BlobStash app WebAuthn demo
+
+A demo app for using the WebAuthn library.
A => app.lua +38 -0
@@ 1,38 @@
+local template = require('template')
+local router = require('router').new()
+local wa = require('webauthn')
+
+router:get('/', function(params)
+ local js = wa.begin_registration()
+ local credentials = wa.registered_credentials()
+ app.response:write(template.render('register.html', 'layout.html', { js = js, credentials = credentials }))
+end)
+
+router:post('/', function(params)
+ local f = app.request:form()
+ local err = wa.finish_registration(f:get('data'))
+ if err ~= nil then
+ app.response:write(err)
+ end
+ app.response:redirect(url_for('/'))
+end)
+
+router:get('/login', function(params)
+ local js = nil
+ local credentials = wa.registered_credentials()
+ if #credentials > 0 then
+ js = wa.begin_login()
+ end
+ app.response:write(template.render('login.html', 'layout.html', { js = js, credentials = credentials }))
+end)
+
+router:post('/login', function(params)
+ local f = app.request:form()
+ local err = wa.finish_login(f:get('data'))
+ if err ~= nil then
+ app.response:write('failed')
+ end
+ app.response:write('ok')
+end)
+
+router:run()
A => templates/layout.html +28 -0
@@ 1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <meta charset="utf-8">
+ <meta http-equiv="x-ua-compatible" content="ie=edge">
+ <title>WebAuthn Demo</title>
+ <meta name="mobile-web-app-capable" content="yes" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ </head>
+ <body>
+<h1>WebAuthn demo</h1>
+
+<h2>Registered keys</h2>
+{{ if .credentials }}
+<ul>
+{{ range .credentials }}
+ <li>{{ . }}</li>
+{{ end }}
+</ul>
+<p><a href="{{ url_for "/login" }}">Login</a></p>
+{{ else }}
+<p>No keys registered yet</p>
+{{ end }}
+
+{{ template "body" . }}
+ </body>
+</html>
A => templates/login.html +18 -0
@@ 1,18 @@
+{{ define "body" }}
+<p><a href="{{ url_for "/" }}">Register</a></p>
+<p><a href="#" id="login_link">begin login</a></p>
+<form action="" method="POST">
+ <input type="text" id="rpayload" value="" name="data">
+ <input type="submit" value="finish login">
+</form>
+<script src="{{ url_for_js "/webauthn.js" }}"></script>
+<script>
+var credentialRequestOptions = JSON.parse({{.js}});
+document.getElementById("login_link").onclick = function() {
+ Webauthn.login(credentialRequestOptions, payload => {
+ console.log(payload);
+ document.getElementById("rpayload").value = JSON.stringify(payload);
+ });
+}
+</script>
+{{ end }}
A => templates/register.html +16 -0
@@ 1,16 @@
+{{ define "body" }}
+<p><a href="#" id="register_link">begin register</a></p>
+<form action="" method="POST">
+ <input type="text" id="rpayload" value="" name="data">
+ <input type="submit" value="finish register">
+</form>
+<script src="{{ url_for_js "/webauthn.js" }}"></script>
+<script>
+var credentialCreationOptions = JSON.parse({{.js}});
+document.getElementById("register_link").onclick = function() {
+ Webauthn.register(credentialCreationOptions, payload =>
+ document.getElementById("rpayload").value = JSON.stringify(payload)
+ );
+}
+</script>
+{{ end }}