register-new-account.js
3.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/*!
* letiny
* Copyright(c) 2015 Anatol Sommer <anatol@anatol.at>
* Some code used from https://github.com/letsencrypt/boulder/tree/master/test/js
* MPL 2.0
*/
'use strict';
module.exports.create = function (deps) {
var NOOP=function () {}, log=NOOP;
var acmeRequest = deps.acmeRequest;
var RSA = deps.RSA;
var Acme = deps.Acme;
function registerNewAccount(options, cb) {
function register() {
state.acme.post(options.newRegUrl, {
resource:'new-reg',
contact:['mailto:'+options.email]
}, getTerms);
}
function getTerms(err, res) {
var links;
if (err) {
return handleErr(err, 'Registration request failed: ' + err.toString());
}
if (Math.floor(res.statusCode/100)!==2) {
return handleErr(err, 'Registration request failed: ' + res.body.toString('utf8'));
}
links=Acme.parseLink(res.headers.link);
if (!links || !('next' in links)) {
return handleErr(err, 'Server didn\'t provide information to proceed (1)');
}
state.registrationUrl=res.headers.location;
// TODO should we pass this along?
//state.newAuthorizationUrl=links.next;
state.termsRequired=('terms-of-service' in links);
if (state.termsRequired) {
state.termsUrl=links['terms-of-service'];
options.agreeToTerms(state.termsUrl, function (err, agree) {
if (err) {
return handleErr(err);
}
if (!agree) {
return handleErr(new Error("You must agree to the terms of use at '" + state.termsUrl + "'"));
}
state.agreeTerms = agree;
state.termsUrl=links['terms-of-service'];
log(state.termsUrl);
acmeRequest.create().get(state.termsUrl, getAgreement);
});
} else {
cb(null, null);
}
}
function getAgreement(err/*, res, body*/) {
if (err) {
return handleErr(err, 'Couldn\'t get agreement');
}
log('The CA requires your agreement to terms:\n'+state.termsUrl);
sendAgreement();
}
function sendAgreement() {
if (state.termsRequired && !state.agreeTerms) {
return handleErr(null, 'The CA requires your agreement to terms: '+state.termsUrl);
}
log('Posting agreement to: '+state.registrationUrl);
state.acme.post(state.registrationUrl, {
resource:'reg',
agreement:state.termsUrl
}, function(err, res, body) {
var data;
if (err || Math.floor(res.statusCode/100)!==2) {
return handleErr(err, 'Couldn\'t POST agreement back to server', body);
}
if ('string' === typeof body || '{' === body[0] || '{' === String.fromCharCode(body[0])) {
try {
data = JSON.parse(body.toString('utf8'));
} catch(e) {
cb(e);
return;
}
}
cb(null, data);
});
}
function handleErr(err, text, info) {
log(text, err, info);
cb(err || new Error(text));
}
var state = {};
if (!options.accountKeypair) {
if (!options.accountPrivateKeyPem) {
return handleErr(new Error("options.accountKeypair must be an object with `privateKeyPem` and/or `privateKeyJwk`"));
}
console.warn("'accountPrivateKeyPem' is deprecated. Use options.accountKeypair.privateKeyPem instead.");
options.accountKeypair = RSA.import({ privateKeyPem: options.accountPrivateKeyPem });
}
if (!options.agreeToTerms) {
cb(new Error("options.agreeToTerms must be function (tosUrl, fn => (err, true))"));
return;
}
if (!options.newRegUrl) {
cb(new Error("options.newRegUrl must be the a new registration url"));
return;
}
if (!options.email) {
cb(new Error("options.email must be an email"));
return;
}
state.accountKeypair = options.accountKeypair;
state.acme=new Acme(state.accountKeypair);
register();
}
return registerNewAccount;
};