update errors and iframe
This commit is contained in:
parent
881bf97334
commit
84e1863da2
|
@ -11,6 +11,7 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -28,8 +29,30 @@ import (
|
|||
ua "github.com/mileusna/useragent"
|
||||
)
|
||||
|
||||
var errTokenNotVerified = apiError{"token has not been verified"}
|
||||
var errUsedToken = apiError{"token has already been used"}
|
||||
var errInvalidEmail = apiError{"invalid email address"}
|
||||
|
||||
// API Errors
|
||||
type serverError struct {
|
||||
error string
|
||||
}
|
||||
|
||||
func (e serverError) Error() string {
|
||||
return e.error
|
||||
}
|
||||
|
||||
type apiError struct {
|
||||
error string
|
||||
}
|
||||
|
||||
func (e apiError) Error() string {
|
||||
return e.error
|
||||
}
|
||||
|
||||
type HTTPResponse struct {
|
||||
Error string `json:"error"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Code string `json:"code,omitempty"`
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
|
@ -135,13 +158,22 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler {
|
|||
return
|
||||
}
|
||||
|
||||
contact, err = lintEmail(contact)
|
||||
if nil != err {
|
||||
b, _ := json.Marshal(&HTTPResponse{
|
||||
Error: err.Error(),
|
||||
Code: "E_USER",
|
||||
})
|
||||
w.Write(b)
|
||||
return
|
||||
}
|
||||
|
||||
_, ok, err := contactKV.Load(contact)
|
||||
if nil != err {
|
||||
fmt.Fprintf(os.Stderr, "meta: error loading contact: %s\n", err.Error())
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if ok {
|
||||
b, _ := json.Marshal(&HTTPResponse{
|
||||
Success: true,
|
||||
|
@ -152,6 +184,7 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler {
|
|||
|
||||
b, _ := json.Marshal(&HTTPResponse{
|
||||
Error: "not found",
|
||||
Code: "E_USER",
|
||||
})
|
||||
w.Write(b)
|
||||
})
|
||||
|
@ -217,6 +250,13 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler {
|
|||
otp, err = consumeOTPReceipt("TODO", receipt, agent, addr)
|
||||
}
|
||||
if nil != err {
|
||||
if errTokenNotVerified == err {
|
||||
b, _ := json.Marshal(&HTTPResponse{
|
||||
Error: err.Error(),
|
||||
})
|
||||
w.Write(b)
|
||||
return
|
||||
}
|
||||
// TODO propagate error types
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
fmt.Fprintf(w, "%s", err)
|
||||
|
@ -821,7 +861,7 @@ func newOTP(email string, agent string, addr string) (id string, secret []byte,
|
|||
// keep it secret, keep it safe
|
||||
os.FileMode(0600),
|
||||
); nil != err {
|
||||
return "", nil, errors.New("database connection failed when writing verification token")
|
||||
return "", nil, serverError{"database connection failed when writing verification token"}
|
||||
}
|
||||
return receipt, secret, nil
|
||||
}
|
||||
|
@ -847,12 +887,12 @@ func checkOTP(hash, email, agent, addr string, consume otpConsumer) (*OTP, error
|
|||
tokfile := filepath.Join(tokenPrefix, hash+".tok.txt")
|
||||
b, err := ioutil.ReadFile(tokfile)
|
||||
if nil != err {
|
||||
return nil, errors.New("database connection failed when reading verification token")
|
||||
return nil, serverError{"database connection failed when reading verification token"}
|
||||
}
|
||||
|
||||
otp := OTP{}
|
||||
if err := json.Unmarshal(b, &otp); nil != err {
|
||||
return nil, errors.New("database verification token parse failed")
|
||||
return nil, serverError{"database verification token parse failed"}
|
||||
}
|
||||
|
||||
if 0 == subtle.ConstantTimeCompare([]byte(otp.Email), []byte(email)) {
|
||||
|
@ -862,7 +902,7 @@ func checkOTP(hash, email, agent, addr string, consume otpConsumer) (*OTP, error
|
|||
|
||||
if consume.secret {
|
||||
if !otp.SecretUsed.IsZero() {
|
||||
return nil, errors.New("token has already been used")
|
||||
return nil, errUsedToken
|
||||
}
|
||||
otp.SecretUsed = time.Now()
|
||||
if addr != otp.ReceiptIP {
|
||||
|
@ -874,10 +914,10 @@ func checkOTP(hash, email, agent, addr string, consume otpConsumer) (*OTP, error
|
|||
}
|
||||
} else if consume.receipt {
|
||||
if otp.SecretUsed.IsZero() {
|
||||
return nil, errors.New("token has not been verified")
|
||||
return nil, errTokenNotVerified
|
||||
}
|
||||
if !otp.ReceiptUsed.IsZero() {
|
||||
return nil, errors.New("token has already been used")
|
||||
return nil, errUsedToken
|
||||
}
|
||||
otp.ReceiptUsed = time.Now()
|
||||
}
|
||||
|
@ -890,24 +930,38 @@ func checkOTP(hash, email, agent, addr string, consume otpConsumer) (*OTP, error
|
|||
// keep it secret, keep it safe
|
||||
os.FileMode(0600),
|
||||
); nil != err {
|
||||
return nil, errors.New("database connection failed when consuming token")
|
||||
return nil, serverError{"database connection failed when consuming token"}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("THE TOKEN IS GOOD. GOOD!!")
|
||||
|
||||
return &otp, nil
|
||||
}
|
||||
|
||||
func lintEmail(email string) (string, error) {
|
||||
// TODO check DNS for MX records
|
||||
parts := strings.Split(email, "@")
|
||||
domain := parts[1]
|
||||
if 2 != len(parts) || strings.Contains(email, " \t\n") {
|
||||
return "", errInvalidEmail
|
||||
}
|
||||
mxs, err := net.LookupMX(domain)
|
||||
if len(mxs) < 1 || nil != err {
|
||||
// TODO it possible in some cases that this
|
||||
// could be a network error
|
||||
return "", errInvalidEmail
|
||||
}
|
||||
return strings.ToLower(email), nil
|
||||
}
|
||||
|
||||
func startVerification(baseURL, contact, agent, addr string) (receipt string, err error) {
|
||||
email := strings.Replace(strings.TrimPrefix(contact, "mailto:"), " ", "+", -1)
|
||||
if "" == email {
|
||||
return "", errors.New("missing contact:[\"mailto:me@example.com\"]")
|
||||
}
|
||||
|
||||
// TODO check DNS for MX records
|
||||
if !strings.Contains(email, "@") || strings.Contains(email, " \t\n") {
|
||||
return "", errors.New("invalid email address")
|
||||
email, err = lintEmail(email)
|
||||
if nil != err {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// TODO expect JWK in JWS/JWT
|
||||
|
@ -922,9 +976,10 @@ func startVerification(baseURL, contact, agent, addr string) (receipt string, er
|
|||
subject := "Verify New Account"
|
||||
// TODO go tpl
|
||||
// TODO determine OS and Browser from user agent
|
||||
page := "pocket/iframe.html"
|
||||
text := fmt.Sprintf(
|
||||
"It looks like you just tried to register a new Pocket ID account.\n\n Verify account: %s#/verify/%s\n\n%s on %s %s from %s\n\nNot you? Just ignore this message.",
|
||||
baseURL, base64.RawURLEncoding.EncodeToString(secret), ua.Name, ua.OS, ua.Device, addr,
|
||||
"It looks like you just tried to register a new Pocket ID account.\n\n Verify account: %s/%s#/verify/%s\n\n%s on %s %s from %s\n\nNot you? Just ignore this message.",
|
||||
baseURL, page, base64.RawURLEncoding.EncodeToString(secret), ua.Name, ua.OS, ua.Device, addr,
|
||||
)
|
||||
fmt.Println("email:", text)
|
||||
if !strings.Contains(contact, "+noreply") {
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
'use strict';
|
||||
/*global crypto*/
|
||||
|
||||
var Hashcash = module.exports;
|
||||
|
||||
var textEncoder = new TextEncoder();
|
||||
Hashcash.solve = async function solveHc(hc) {
|
||||
var solution = 0;
|
||||
var parts = hc.split(':').slice(0, 6);
|
||||
if (parts.length < 6) {
|
||||
throw new Error('invalid Hashcash-Challenge: ' + hc);
|
||||
}
|
||||
|
||||
var bits = parseInt(parts[1], 10) || -1;
|
||||
if (bits > 10 || bits < 0) {
|
||||
throw new Error('bad bit values');
|
||||
}
|
||||
console.log('bits:', bits);
|
||||
hc = parts.join(':') + ':';
|
||||
async function next() {
|
||||
var answer = hc + int52ToBase64(solution);
|
||||
var u8 = textEncoder.encode(answer);
|
||||
// REALLY SLOW due to async tasks and C++ context switch
|
||||
var hash = await crypto.subtle.digest('SHA-256', u8);
|
||||
hash = new Uint8Array(hash);
|
||||
if (checkHc(hash, bits)) {
|
||||
return answer;
|
||||
}
|
||||
solution += 1;
|
||||
return next();
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
|
||||
function int52ToBase64(n) {
|
||||
var hex = n.toString(16);
|
||||
if (hex.length % 2) {
|
||||
hex = '0' + hex;
|
||||
}
|
||||
|
||||
var bin = [];
|
||||
var i = 0;
|
||||
var d;
|
||||
var b;
|
||||
while (i < hex.length) {
|
||||
d = parseInt(hex.slice(i, i + 2), 16);
|
||||
b = String.fromCharCode(d);
|
||||
bin.push(b);
|
||||
i += 2;
|
||||
}
|
||||
|
||||
return btoa(bin.join('')).replace(/=/g, '');
|
||||
}
|
||||
|
||||
function checkHc(hash, bits) {
|
||||
var n = Math.floor(bits / 8);
|
||||
var m = bits % 8;
|
||||
var i;
|
||||
if (m > 0) {
|
||||
n += 1;
|
||||
}
|
||||
|
||||
for (i = 0; i < n && i < hash.length; i += 1) {
|
||||
if (bits > 8) {
|
||||
bits -= 8;
|
||||
if (0 !== hash[i]) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 !== hash[i] >> (8 - bits)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
213
public/main.js
213
public/main.js
|
@ -1,214 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
var request = require('./request.js');
|
||||
var PocketId = require('./pocketid.js');
|
||||
var state = {};
|
||||
var auths = clearAuths();
|
||||
|
||||
function $$(sel, el) {
|
||||
if (el) {
|
||||
return el.querySelectorAll(sel) || [];
|
||||
}
|
||||
return document.body.querySelectorAll(sel) || [];
|
||||
}
|
||||
|
||||
function $(sel, el) {
|
||||
if (el) {
|
||||
return el.querySelector(sel);
|
||||
}
|
||||
return document.body.querySelector(sel);
|
||||
}
|
||||
|
||||
function clearAuths() {
|
||||
var _auths = {
|
||||
google: {
|
||||
promise: null,
|
||||
idToken: ''
|
||||
}
|
||||
};
|
||||
_auths.google.promise = new Promise(function (res, rej) {
|
||||
_auths.google.resolve = res;
|
||||
_auths.google.reject = rej;
|
||||
});
|
||||
return _auths;
|
||||
}
|
||||
|
||||
window.onSignIn = async function onSignIn(googleUser) {
|
||||
// Useful data for your client-side scripts:
|
||||
var profile = googleUser.getBasicProfile();
|
||||
// Don't send this directly to your server!
|
||||
console.log('ID: ' + profile.getId());
|
||||
console.log('Full Name: ' + profile.getName());
|
||||
console.log('Given Name: ' + profile.getGivenName());
|
||||
console.log('Family Name: ' + profile.getFamilyName());
|
||||
console.log('Image URL: ' + profile.getImageUrl());
|
||||
console.log('Email: ' + profile.getEmail());
|
||||
|
||||
// The ID token you need to pass to your backend:
|
||||
auths.google.idToken = googleUser.getAuthResponse().id_token;
|
||||
console.log('ID Token: ' + auths.google.idToken);
|
||||
auths.google.resolve(auths.google.idToken);
|
||||
};
|
||||
|
||||
function setFlow(cont, flow) {
|
||||
$$(cont).forEach(function (el) {
|
||||
el.hidden = true;
|
||||
});
|
||||
console.log(flow);
|
||||
$(flow).hidden = false;
|
||||
}
|
||||
|
||||
async function unlock() {
|
||||
var key;
|
||||
try {
|
||||
key = await PocketId.unlock(function () {
|
||||
setFlow('.authn-container', '.authn-unlock');
|
||||
return new Promise(function (resolve, reject) {
|
||||
window.unlocker = { resolve: resolve, reject: reject };
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"Had a key, but couldn't unlock it. TODO: Just send email?"
|
||||
);
|
||||
console.error(e);
|
||||
return;
|
||||
}
|
||||
|
||||
setFlow('.authn-container', '.authn-loading');
|
||||
|
||||
if (key) {
|
||||
genTokenWithKey(key);
|
||||
return;
|
||||
await PocketId.createIdToken({ key: key });
|
||||
}
|
||||
|
||||
PocketId.signIdToken(id_token).then(function (resp) {
|
||||
console.log('Response:');
|
||||
console.log(resp);
|
||||
});
|
||||
}
|
||||
|
||||
function genTokenWithKey() {
|
||||
// TODO: generate token
|
||||
// TODO: check if the key is still considered valid
|
||||
// TODO: generate new key and authorize
|
||||
}
|
||||
|
||||
(async function () {
|
||||
var loc = window.location;
|
||||
|
||||
console.log('/new-hashcash?');
|
||||
var resp = await request({
|
||||
method: 'POST',
|
||||
url: loc.protocol + '//' + loc.hostname + '/api/new-hashcash'
|
||||
});
|
||||
console.log(resp);
|
||||
|
||||
console.log('/test-hashcash?');
|
||||
resp = await request({
|
||||
method: 'POST',
|
||||
url: loc.protocol + '//' + loc.hostname + '/api/test-hashcash'
|
||||
});
|
||||
console.log(resp);
|
||||
})();
|
||||
|
||||
setFlow('.authn-container', '.authn-email');
|
||||
|
||||
$('.authn-email form').addEventListener('submit', function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
state.email = $('.authn-email [name=username]').value;
|
||||
|
||||
setFlow('.authn-container', '.authn-loading');
|
||||
return PocketId.auth
|
||||
.meta({ email: state.email })
|
||||
.catch(function (err) {
|
||||
window.alert('Error: ' + err.message);
|
||||
})
|
||||
.then(function (resp) {
|
||||
// if the user exists, go to the continue screen
|
||||
// otherwise go to the new user screen
|
||||
console.log('meta:', resp);
|
||||
if (!resp.body.success) {
|
||||
// This is a completely new user
|
||||
setFlow('.authn-container', '.authn-new-user');
|
||||
return;
|
||||
}
|
||||
// The user exists, but this is a new device
|
||||
setFlow('.authn-container', '.authn-existing');
|
||||
});
|
||||
});
|
||||
|
||||
function verifyNewDevice() {
|
||||
return PocketId.auth
|
||||
.verify({ scheme: 'mailto:', email: state.email })
|
||||
.catch(function (err) {
|
||||
window.alert('Error: ' + err.message);
|
||||
})
|
||||
.then(function (resp) {
|
||||
console.log(resp);
|
||||
localStorage.setItem(
|
||||
'pocketid', // + state.email,
|
||||
JSON.stringify({
|
||||
receipt: resp.body.receipt,
|
||||
email: state.email,
|
||||
createdAt: new Date().toISOString()
|
||||
})
|
||||
);
|
||||
window.alert("Go check yo' email!");
|
||||
return PocketId.auth
|
||||
.consume({
|
||||
email: state.email,
|
||||
receipt: resp.body.receipt
|
||||
})
|
||||
.then(function (resp) {
|
||||
// this should have a token we can inspect
|
||||
// and return to the calling application.
|
||||
console.log(resp);
|
||||
window.alert('all set!');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$('.authn-existing form').addEventListener('submit', function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
setFlow('.authn-container', '.authn-loading');
|
||||
verifyNewDevice();
|
||||
});
|
||||
|
||||
$('.authn-new-user form').addEventListener('submit', function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
// We don't need to worry about checking if the key exists
|
||||
// even if it does, the account has been deactivated
|
||||
|
||||
setFlow('.authn-container', '.authn-loading');
|
||||
verifyNewDevice();
|
||||
});
|
||||
|
||||
var route = window.location.hash.split('/').slice(1);
|
||||
console.log('route:', route);
|
||||
switch (route[0]) {
|
||||
case 'verify':
|
||||
var pstate = JSON.parse(localStorage.getItem('pocketid') || '{}');
|
||||
PocketId.auth
|
||||
.consume({
|
||||
receipt: pstate.receipt,
|
||||
secret: route[1]
|
||||
})
|
||||
.then(function (resp) {
|
||||
console.log('token for this device to save:', resp);
|
||||
window.alert('goodness!');
|
||||
})
|
||||
.catch(function (e) {
|
||||
console.error(e);
|
||||
window.alert('network error, try again');
|
||||
});
|
||||
break;
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
require('./pocket/consumer.js');
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var Keypairs = require('@root/keypairs');
|
||||
var PocketId = module.exports;
|
||||
var request = require('./request.js');
|
||||
|
||||
var keyJson = window.localStorage.getItem('private.jwk.json');
|
||||
|
||||
PocketId.signIdToken = async function (idToken) {
|
||||
var pair = await Keypairs.parseOrGenerate({ key: keyJson });
|
||||
var jwt = await Keypairs.signJwt({
|
||||
jwk: pair.private,
|
||||
iss: window.location.protocol + '//' + window.location.hostname,
|
||||
exp: '15m',
|
||||
claims: {
|
||||
contact: ['google:' + idToken]
|
||||
}
|
||||
});
|
||||
return jwt;
|
||||
};
|
||||
|
||||
PocketId.auth = {};
|
||||
PocketId.auth.meta = async function ({ email }) {
|
||||
var loc = window.location;
|
||||
var body = await request({
|
||||
method: 'GET',
|
||||
url:
|
||||
loc.protocol +
|
||||
'//' +
|
||||
loc.hostname +
|
||||
'/api/authn/meta?contact=' +
|
||||
'mailto:' +
|
||||
email
|
||||
});
|
||||
return body;
|
||||
};
|
||||
|
||||
PocketId.auth.verify = async function ({ scheme, email }) {
|
||||
if (!scheme) {
|
||||
scheme = 'mailto:';
|
||||
}
|
||||
|
||||
var loc = window.location;
|
||||
var body = await request({
|
||||
method: 'GET',
|
||||
url:
|
||||
loc.protocol +
|
||||
'//' +
|
||||
loc.hostname +
|
||||
'/api/authn/verify?contact=' +
|
||||
scheme +
|
||||
email
|
||||
});
|
||||
|
||||
return body;
|
||||
};
|
||||
|
||||
PocketId.auth.consume = async function ({
|
||||
email = '',
|
||||
receipt = '',
|
||||
secret = '',
|
||||
count = 0
|
||||
}) {
|
||||
var loc = window.location;
|
||||
var resp = await request({
|
||||
method: 'GET',
|
||||
url:
|
||||
loc.protocol +
|
||||
'//' +
|
||||
loc.hostname +
|
||||
'/api/authn/consume?contact=' +
|
||||
(email ? 'mailto:' + email : '') +
|
||||
'&receipt=' +
|
||||
receipt +
|
||||
'&secret=' +
|
||||
secret
|
||||
});
|
||||
|
||||
if (resp.body.success) {
|
||||
// There should be a token here
|
||||
// (or the pubkey should have been given beforehand)
|
||||
return resp.body;
|
||||
}
|
||||
|
||||
if (resp.body.error) {
|
||||
// TODO special errors are hard failures
|
||||
}
|
||||
|
||||
if (count > 600) {
|
||||
throw new Error('abandoned login');
|
||||
}
|
||||
|
||||
return timeout(5000).then(function () {
|
||||
console.log('check otp again');
|
||||
return PocketId.auth.consume({
|
||||
email,
|
||||
secret,
|
||||
receipt,
|
||||
count: count || 0
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
async function timeout(ms) {
|
||||
return new Promise(function (resolve) {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
var textEncoder = new TextEncoder();
|
||||
PocketId.genKey = async function ({ email }) {
|
||||
// Ideally we'd use PBKDF2 or better but... web standards...
|
||||
// TODO put a random salt
|
||||
var emailU8 = textEncoder.encode(email);
|
||||
var salt = await crypto.subtle.digest('SHA-256', emailU8);
|
||||
var u8 = textEncoder.encode(answer);
|
||||
var hash = await crypto.subtle.digest('SHA-256', u8);
|
||||
};
|
|
@ -1,85 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var Hashcash = require('./hashcash.js');
|
||||
var _sites = {};
|
||||
|
||||
module.exports = async function (opts) {
|
||||
if (!opts.headers) {
|
||||
opts.headers = {};
|
||||
}
|
||||
|
||||
if (opts.json) {
|
||||
if (true === opts.json) {
|
||||
opts.body = JSON.stringify(opts.body);
|
||||
} else {
|
||||
opts.body = JSON.stringify(opts.json);
|
||||
}
|
||||
if (!opts.headers['Content-Type'] && !opts.headers['content-type']) {
|
||||
opts.headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
}
|
||||
|
||||
if (!opts.mode) {
|
||||
opts.mode = 'cors';
|
||||
}
|
||||
|
||||
var url = new URL(opts.url);
|
||||
if (!_sites[url.hostname]) {
|
||||
_sites[url.hostname] = { nonces: [] };
|
||||
}
|
||||
|
||||
var site = _sites[url.hostname];
|
||||
var hc = site.hashcashChallenge;
|
||||
if (hc) {
|
||||
delete site.hashcashChallenge;
|
||||
site.hashcash = await Hashcash.solve(hc);
|
||||
}
|
||||
if (site.hashcash) {
|
||||
opts.headers.Hashcash = site.hashcash;
|
||||
}
|
||||
|
||||
var response = await window.fetch(opts.url, opts);
|
||||
var headerNames = response.headers.keys();
|
||||
var hs = {};
|
||||
var h;
|
||||
while (true) {
|
||||
h = headerNames.next();
|
||||
if (h.done) {
|
||||
break;
|
||||
}
|
||||
hs[h.value] = response.headers.get(h.value);
|
||||
}
|
||||
|
||||
var body;
|
||||
if (hs['content-type'].includes('application/json')) {
|
||||
body = await response.json();
|
||||
} else {
|
||||
body = await response.text();
|
||||
try {
|
||||
body = JSON.parse(body);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
var resp = {};
|
||||
|
||||
resp.body = body;
|
||||
resp.headers = hs;
|
||||
resp.toJSON = function () {
|
||||
return {
|
||||
headers: hs,
|
||||
body: body
|
||||
};
|
||||
};
|
||||
|
||||
if (resp.headers['hashcash-challenge']) {
|
||||
_sites[url.hostname].hashcashChallenge =
|
||||
resp.headers['hashcash-challenge'];
|
||||
}
|
||||
if (resp.headers.nonce) {
|
||||
site.nonces.push(resp.headers.nonce);
|
||||
}
|
||||
|
||||
console.log(resp);
|
||||
return resp;
|
||||
};
|
Loading…
Reference in New Issue