forked from DoctorMcKay/node-steam-totp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
132 lines (106 loc) · 3.5 KB
/
index.js
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
var Crypto = require('crypto');
/**
* Returns the current local Unix time
* @param {number} [timeOffset=0] - This many seconds will be added to the returned time
* @returns {number}
*/
exports.time = function(timeOffset) {
return Math.floor(Date.now() / 1000) + (timeOffset || 0);
};
/**
* Generate a Steam-style TOTP authentication code.
* @param {Buffer|string} secret - Your TOTP shared_secret as a Buffer, hex, or base64
* @param {number} [timeOffset=0] - If you know how far off your clock is from the Steam servers, put the offset here in seconds
* @returns {string}
*/
exports.generateAuthCode = exports.getAuthCode = function(secret, timeOffset) {
secret = bufferizeSecret(secret);
var time = exports.time(timeOffset);
var buffer = new Buffer(8);
buffer.writeUInt32BE(0, 0); // This will stop working in 2038!
buffer.writeUInt32BE(Math.floor(time / 30), 4);
var hmac = Crypto.createHmac('sha1', secret);
hmac = hmac.update(buffer).digest();
var start = hmac[19] & 0x0F;
hmac = hmac.slice(start, start + 4);
var fullcode = hmac.readUInt32BE(0) & 0x7fffffff;
var chars = '23456789BCDFGHJKMNPQRTVWXY';
var code = '';
for(var i = 0; i < 5; i++) {
code += chars.charAt(fullcode % chars.length);
fullcode /= chars.length;
}
return code;
};
/**
* Generate a base64 confirmation key for use with mobile trade confirmations. The key can only be used once.
* @param {Buffer|string} identitySecret - The identity_secret that you received when enabling two-factor authentication
* @param {number} time - The Unix time for which you are generating this secret. Generally should be the current time.
* @param {string} tag - The tag which identifies what this request (and therefore key) will be for. "conf" to load the confirmations page, "details" to load details about a trade, "allow" to confirm a trade, "cancel" to cancel it.
* @returns {string}
*/
exports.generateConfirmationKey = exports.getConfirmationKey = function(identitySecret, time, tag) {
identitySecret = bufferizeSecret(identitySecret);
var dataLen = 8;
if(tag) {
if(tag.length > 32) {
dataLen += 32;
} else {
dataLen += tag.length;
}
}
var buffer = new Buffer(dataLen);
buffer.writeUInt32BE(0, 0); // This will stop working in 2038!
buffer.writeUInt32BE(time, 4);
if(tag) {
buffer.write(tag, 8);
}
var hmac = Crypto.createHmac('sha1', identitySecret);
return hmac.update(buffer).digest('base64');
};
exports.getTimeOffset = function(callback) {
var start = Date.now();
var req = require('https').request({
"hostname": "api.steampowered.com",
"path": "/ITwoFactorService/QueryTime/v1/",
"method": "POST"
}, function(res) {
if(res.statusCode != 200) {
callback(new Error("HTTP error " + res.statusCode));
return;
}
var response = '';
res.on('data', function(chunk) {
response += chunk;
});
res.on('end', function() {
try {
response = JSON.parse(response).response;
} catch(e) {
callback(new Error("Malformed response"));
}
if(!response || !response.server_time) {
callback(new Error("Malformed response"));
}
var end = Date.now();
var offset = response.server_time - exports.time();
callback(null, offset, end - start);
});
});
req.on('error', function(err) {
callback(err);
});
req.end();
};
function bufferizeSecret(secret) {
if(typeof secret === 'string') {
// Check if it's hex
if(secret.match(/[0-9a-f]{40}/i)) {
return new Buffer(secret, 'hex');
} else {
// Looks like it's base64
return new Buffer(secret, 'base64');
}
}
return secret;
}