325 lines
10 KiB
JavaScript
325 lines
10 KiB
JavaScript
(function () {
|
|
'use strict';
|
|
|
|
var $qs = function (s) { return window.document.querySelector(s); };
|
|
var $qsa = function (s) { return window.document.querySelectorAll(s); };
|
|
var info = {};
|
|
var steps = {};
|
|
var nonce;
|
|
var kid;
|
|
var i = 1;
|
|
|
|
//$qs('.js-acme-directory-url').value = 'https://acme-v02.api.letsencrypt.org/directory';
|
|
$qs('.js-acme-directory-url').value = 'https://acme-staging-v02.api.letsencrypt.org/directory';
|
|
|
|
function hideForms() {
|
|
$qsa('.js-acme-form').forEach(function (el) {
|
|
el.hidden = true;
|
|
});
|
|
}
|
|
|
|
function submitForm(ev) {
|
|
steps[i].submit(ev);
|
|
i += 1;
|
|
}
|
|
$qsa('.js-acme-form').forEach(function ($el) {
|
|
$el.addEventListener('submit', function (ev) {
|
|
ev.preventDefault();
|
|
submitForm(ev);
|
|
});
|
|
});
|
|
function updateChallengeType() {
|
|
var input = this || $qs('.js-acme-challenge-type');
|
|
console.log('ch type radio:', input.value);
|
|
$qs('.js-acme-table-wildcard').hidden = true;
|
|
$qs('.js-acme-table-http-01').hidden = true;
|
|
$qs('.js-acme-table-dns-01').hidden = true;
|
|
if (info.challenges.wildcard) {
|
|
$qs('.js-acme-table-wildcard').hidden = false;
|
|
}
|
|
if (info.challenges[input.value]) {
|
|
$qs('.js-acme-table-' + input.value).hidden = false;
|
|
}
|
|
}
|
|
$qsa('.js-acme-challenge-type').forEach(function ($el) {
|
|
$el.addEventListener('change', updateChallengeType);
|
|
});
|
|
|
|
steps[1] = function () {
|
|
hideForms();
|
|
$qs('.js-acme-form-domains').hidden = false;
|
|
};
|
|
steps[1].submit = function () {
|
|
info.identifiers = $qs('.js-acme-domains').value.split(/\s*,\s*/g).map(function (hostname) {
|
|
return { type: 'dns', value: hostname.trim() };
|
|
});
|
|
|
|
return BACME.directory($qs('.js-acme-directory-url').value).then(function (directory) {
|
|
$qs('.js-acme-tos-url').href = directory.meta.termsOfService;
|
|
return BACME.nonce().then(function (_nonce) {
|
|
nonce = _nonce;
|
|
|
|
steps[i]();
|
|
});
|
|
});
|
|
};
|
|
|
|
steps[2] = function () {
|
|
hideForms();
|
|
$qs('.js-acme-form-account').hidden = false;
|
|
};
|
|
steps[2].submit = function () {
|
|
var email = $qs('.js-acme-account-email').value.toLowerCase().trim();
|
|
|
|
info.contact = [ 'mailto:' + email ];
|
|
info.agree = $qs('.js-acme-account-tos').checked;
|
|
info.greenlockAgree = $qs('.js-gl-tos').checked;
|
|
// TODO
|
|
// options for
|
|
// * regenerate key
|
|
// * ECDSA / RSA / bitlength
|
|
|
|
// TODO ping with version and account creation
|
|
|
|
var jwk = JSON.parse(localStorage.getItem('account:' + email) || 'null');
|
|
var p;
|
|
|
|
function createKeypair() {
|
|
return BACME.accounts.generateKeypair({
|
|
type: 'ECDSA'
|
|
, bitlength: '256'
|
|
}).then(function (jwk) {
|
|
localStorage.setItem('account:' + email, JSON.stringify(jwk));
|
|
return jwk;
|
|
})
|
|
}
|
|
|
|
if (jwk) {
|
|
p = Promise.resolve(jwk);
|
|
} else {
|
|
p = createKeypair();
|
|
}
|
|
|
|
function createAccount(jwk) {
|
|
console.log('account jwk:');
|
|
console.log(jwk);
|
|
delete jwk.key_ops;
|
|
info.jwk = jwk;
|
|
return BACME.accounts.sign({
|
|
jwk: jwk
|
|
, contacts: [ 'mailto:' + email ]
|
|
, agree: info.agree
|
|
, nonce: nonce
|
|
, kid: kid
|
|
}).then(function (signedAccount) {
|
|
return BACME.accounts.set({
|
|
signedAccount: signedAccount
|
|
}).then(function (account) {
|
|
console.log('account:');
|
|
console.log(account);
|
|
kid = account.kid;
|
|
return kid;
|
|
});
|
|
});
|
|
}
|
|
|
|
return p.then(function (_jwk) {
|
|
jwk = _jwk;
|
|
kid = JSON.parse(localStorage.getItem('account-kid:' + email) || 'null');
|
|
var p2
|
|
|
|
// TODO save account id rather than always retrieving it
|
|
if (kid) {
|
|
p2 = Promise.resolve(kid);
|
|
} else {
|
|
p2 = createAccount(jwk);
|
|
}
|
|
|
|
return p2.then(function (_kid) {
|
|
kid = _kid;
|
|
info.kid = kid;
|
|
return BACME.orders.sign({
|
|
jwk: jwk
|
|
, identifiers: info.identifiers
|
|
, kid: kid
|
|
}).then(function (signedOrder) {
|
|
return BACME.orders.create({
|
|
signedOrder: signedOrder
|
|
}).then(function (order) {
|
|
info.finalizeUrl = order.finalize;
|
|
return BACME.thumbprint({ jwk: jwk }).then(function (thumbprint) {
|
|
return BACME.challenges.all().then(function (claims) {
|
|
console.log('claims:');
|
|
console.log(claims);
|
|
var obj = { 'dns-01': [], 'http-01': [], 'wildcard': [] };
|
|
var map = {
|
|
'http-01': '.js-acme-table-http-01'
|
|
, 'dns-01': '.js-acme-table-dns-01'
|
|
, 'wildcard': '.js-acme-table-wildcard'
|
|
}
|
|
var tpls = {};
|
|
info.challenges = obj;
|
|
Object.keys(map).forEach(function (k) {
|
|
var sel = map[k] + ' tbody';
|
|
console.log(sel);
|
|
tpls[k] = $qs(sel).innerHTML;
|
|
$qs(map[k] + ' tbody').innerHTML = '';
|
|
});
|
|
|
|
// TODO make Promise-friendly
|
|
return Promise.all(claims.map(function (claim) {
|
|
var hostname = claim.identifier.value;
|
|
return Promise.all(claim.challenges.map(function (c) {
|
|
var keyAuth = BACME.challenges['http-01']({
|
|
token: c.token
|
|
, thumbprint: thumbprint
|
|
, challengeDomain: hostname
|
|
});
|
|
return BACME.challenges['dns-01']({
|
|
keyAuth: keyAuth
|
|
, challengeDomain: hostname
|
|
}).then(function (dnsAuth) {
|
|
var data = {
|
|
type: c.type
|
|
, hostname: hostname
|
|
, url: c.url
|
|
, token: c.token
|
|
, keyAuthorization: keyAuth
|
|
, httpPath: keyAuth.path
|
|
, httpAuth: keyAuth.value
|
|
, dnsType: dnsAuth.type
|
|
, dnsHost: dnsAuth.host
|
|
, dnsAnswer: dnsAuth.answer
|
|
};
|
|
|
|
obj[c.type].push(data);
|
|
console.log('');
|
|
console.log('CHALLENGE');
|
|
console.log(claim);
|
|
console.log(c);
|
|
console.log(data);
|
|
console.log('');
|
|
|
|
if (claim.wildcard) {
|
|
obj.wildcard.push(data);
|
|
$qs(map.wildcard).innerHTML += '<tr><td>' + data.hostname + '</td><td>' + data.dnsHost + '</td><td>' + data.dnsAnswer + '</td></tr>';
|
|
} else {
|
|
obj[data.type].push(data);
|
|
if ('dns-01' === data.type) {
|
|
$qs(map[data.type]).innerHTML += '<tr><td>' + data.hostname + '</td><td>' + data.dnsHost + '</td><td>' + data.dnsAnswer + '</td></tr>';
|
|
} else if ('http-01' === data.type) {
|
|
$qs(map[data.type]).innerHTML += '<tr><td>' + data.hostname + '</td><td>' + data.httpPath + '</td><td>' + data.httpAuth + '</td></tr>';
|
|
} else {
|
|
throw new Error('Unexpected type: ' + data.type);
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
}));
|
|
})).then(function () {
|
|
|
|
// hide wildcard if no wildcard
|
|
// hide http-01 and dns-01 if only wildcard
|
|
if (!obj.wildcard.length) {
|
|
$qs('.js-acme-wildcard').hidden = true;
|
|
}
|
|
if (!obj['http-01'].length) {
|
|
$qs('.js-acme-challenges').hidden = true;
|
|
}
|
|
|
|
updateChallengeType();
|
|
|
|
steps[i]();
|
|
});
|
|
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}).catch(function (err) {
|
|
console.error('Step \'' + i + '\' Error:');
|
|
console.error(err);
|
|
});
|
|
};
|
|
|
|
steps[3] = function () {
|
|
hideForms();
|
|
$qs('.js-acme-form-challenges').hidden = false;
|
|
};
|
|
steps[3].submit = function () {
|
|
var chType = $qs('.js-acme-challenge-type').value;
|
|
var ps = [];
|
|
|
|
// do each wildcard, if any
|
|
// do each challenge, by selected type only
|
|
[ 'wildcard', chType].forEach(function (typ) {
|
|
info.challenges[typ].forEach(function (ch) {
|
|
// { jwk, challengeUrl, accountId (kid) }
|
|
ps.push(BACME.challenges.accept({
|
|
jwk: info.jwk
|
|
, challengeUrl: ch.url
|
|
, accountId: info.kid
|
|
}));
|
|
});
|
|
});
|
|
|
|
return Promise.all(ps).then(function (results) {
|
|
console.log('challenge status:', results);
|
|
var polls = results.slice(0);
|
|
var allsWell = true;
|
|
|
|
function checkPolls() {
|
|
return new Promise(function (resolve) {
|
|
setTimeout(resolve, 1000);
|
|
}).then(function () {
|
|
return Promise.all(polls.map(function (poll) {
|
|
return BACME.challenges.check({ challengePollUrl: poll.url });
|
|
})).then(function () {
|
|
polls = polls.filter(function (poll) {
|
|
//return 'valid' !== poll.status && 'invalid' !== poll.status;
|
|
if ('pending' === poll.status) {
|
|
return true;
|
|
}
|
|
if ('valid' !== poll.status) {
|
|
allsWell = false;
|
|
console.warn('BAD POLL STATUS', poll);
|
|
}
|
|
// TODO show status in HTML
|
|
});
|
|
|
|
if (polls.length) {
|
|
return checkPolls();
|
|
}
|
|
return true;
|
|
});
|
|
});
|
|
}
|
|
|
|
return checkPolls().then(function () {
|
|
if (allsWell) {
|
|
return submitForm();
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
// spinner
|
|
steps[4] = function () {
|
|
hideForms();
|
|
$qs('.js-acme-form-poll').hidden = false;
|
|
}
|
|
steps[4].submit = function () {
|
|
console.log('Congrats! Auto advancing...');
|
|
return BACME.order
|
|
};
|
|
|
|
steps[5] = function () {
|
|
hideForms();
|
|
$qs('.js-acme-form-download').hidden = false;
|
|
}
|
|
|
|
steps[1]();
|
|
}());
|