Skip to content

Instantly share code, notes, and snippets.

@eugeniop
Last active August 29, 2015 14:24
Show Gist options
  • Save eugeniop/e8b148acbdd9deff9ffa to your computer and use it in GitHub Desktop.
Save eugeniop/e8b148acbdd9deff9ffa to your computer and use it in GitHub Desktop.
var request = require('request');
var qs = require('qs');
var jwt = require('jsonwebtoken');
return function (context, req, res) {
require('async').series([
/*
* We only care about POST and GET
*/
function(callback) {
if (req.method !== 'POST' && req.method !== 'GET') {
res.writeHead(404);
return res.end('Page not found');
}
return callback();
},
/*
* 1. GET: Render initial View with OTP.
*/
function(callback) {
if (req.method === 'GET') {
renderOtpView();
}
return callback();
},
/*
* 2. Validate OTP
*/
function(callback) {
if (req.method === 'POST') {
yubico_validate(context.data.yubikey_clientid, context.body.otp, function(err,resp) {
if (err) {
return callback(err);
}
if(resp.status==='OK'){
//Return result to Auth0 (includes OTP and Status. Only when OK)
var token = jwt.sign({
status: resp.status,
otp: resp.otp
},
new Buffer(context.data.yubikey_secret, 'base64'),
{
subject: context.data.user,
expiresInMinutes: 1,
audience: context.data.yubikey_clientid,
issuer: 'urn:auth0:yubikey:mfa'
});
res.writeHead(301, {Location: context.data.returnUrl + "?id_token=" + token});
res.end();
}
return callback([resp.status]);
});
return callback();
}
},
], function(err) {
if (Array.isArray(err)) {
return renderOtpView(err);
}
if (typeof err === 'string') {
return renderOtpView([err]);
}
if (typeof err === 'object') {
var errors = [];
errors.push(err.message || err);
return renderOtpView(errors);
}
});
function yubico_validate(clientId, otp, done){
var params = {
id: clientId,
otp: otp,
nonce: uid(16)
};
request.get('http://api.yubico.com/wsapi/2.0/verify',
{
qs: params
},function(e,r,b){
if(e) return done(e);
if(r.statusCode !== 200) return done(new Error('Error: ' + r.statusCode));
var yubico_response=qs.parse(b.replace(/\r\n/g, '&'));
if(yubico_response.nonce !== params.nonce) return done(new Error('Invalid response - nonce doesn\'t match'));
done(null,yubico_response);
});
}
function uid(len) {
var buf = []
, chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
, charlen = chars.length;
for (var i = 0; i < len; ++i) {
buf.push(chars[getRandomInt(0, charlen - 1)]);
}
return buf.join('');
}
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function renderOtpView(errors) {
res.writeHead(200, {
'Content-Type': 'text/html'
});
res.end(require('ejs').render(otpForm.stringify(), {
user: context.data.user,
errors: errors || []
}));
}
function otpForm() {
/*
<html>
<body>
<form method="POST"
<p>
Hi <strong><%- user %></strong>, please tap your Yubikey.
</p>
Yubikey OTP:<br>
<input type="text" name="otp" id="otp" />
<br>
<input type="submit" value="Submit">
</form>
<div style="color: red;">
<% errors.forEach(function(error){ %>
<p>
<%= error %>
</p>
<%})%>
</div>
</body>
</html>
*/
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment