Skip to content

Commit a9ddab0

Browse files
committed
API v1.1 draft
• Fixed HETU for FI • Fixed salt not being used in calculation for hashes (also hotfixed 1.0) • DOB and Registered use ISO 8601 time standards • DOB and Registered can't overlap in impossible ways • Added customizable password options
1 parent 1d147ed commit a9ddab0

File tree

134 files changed

+47290
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

134 files changed

+47290
-0
lines changed

api/1.1/api.js

Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
/*
2+
• Fixed HETU for FI
3+
• Fixed salt not being used in calculation for hashes (also hotfixed 1.0)
4+
• DOB and Registered use ISO 8601 time standards
5+
• DOB and Registered can't overlap in impossible ways
6+
• Added customizable password options
7+
*/
8+
9+
const mersenne = require('mersenne');
10+
const moment = require('moment');
11+
const crypto = require('crypto');
12+
const YAML = require('yamljs');
13+
const js2xmlparser = require('js2xmlparser');
14+
const converter = require('json-2-csv');
15+
const version = '1.1';
16+
17+
// Load the datasets if not defined
18+
if (typeof datasets[version] === 'undefined') {
19+
require('./loadDatasets')((data, injectNats) => {
20+
datasets[version] = data;
21+
injects[version] = injectNats;
22+
});
23+
}
24+
25+
const originalFieldList = 'gender, name, location, email,\
26+
login, registered, dob, phone, cell, id, picture, nat';
27+
28+
const originalFieldArray = originalFieldList
29+
.split(',')
30+
.filter((i) => i !== '')
31+
.map((w) => w.trim().toLowerCase());
32+
33+
var Generator = function(options) {
34+
// Check for multiple vals
35+
this.checkOptions(options);
36+
37+
options = options || {};
38+
this.results = Number(options.results);
39+
this.seed = options.seed || '';
40+
this.lego = typeof options.lego !== 'undefined' && options.lego !== 'false' ? true : false;
41+
this.gender = options.gender || null;
42+
this.format = (options.format || options.fmt || 'json').toLowerCase();
43+
this.nat = options.nat || options.nationality || null;
44+
this.noInfo = typeof options.noinfo !== 'undefined' && options.lego !== 'false' ? true : false;
45+
this.page = Number(options.page) || 1;
46+
this.password = options.password;
47+
48+
// Include all fields by default
49+
this.inc = options.inc || originalFieldList;
50+
this.exc = options.exc || '';
51+
52+
this.inc = this.inc.split(',').filter((i) => i !== '').map((w) => w.trim().toLowerCase());
53+
this.exc = this.exc.split(',').filter((i) => i !== '').map((w) => w.trim().toLowerCase());
54+
55+
// Remove exclusions
56+
this.inc = this.inc.filter((w) => this.exc.indexOf(w) === -1);
57+
58+
// Update exclusions list to inverse of inclusions
59+
this.exc = originalFieldArray.filter((w) => this.inc.indexOf(w) === -1);
60+
61+
if (this.nat !== null) {
62+
this.nat = this.nat.split(',').filter((i) => i !== '');
63+
}
64+
65+
if (this.nat !== null) this.nat = uppercaseify(this.nat);
66+
this.nats = this.getNats(); // Returns array of nats
67+
this.constantTime = 1471295130;
68+
this.version = version;
69+
70+
// Sanitize values
71+
if (isNaN(this.results) || this.results < 0 || this.results > settings.maxResults || this.results === '') this.results = 1;
72+
73+
if (this.gender !== 'male' && this.gender !== 'female' || this.seed !== '') {
74+
this.gender = null;
75+
}
76+
77+
if (this.lego) this.nat = 'LEGO';
78+
else if (this.nat !== null && !(this.validNat(this.nat))) this.nat = null;
79+
80+
if (this.seed.length === 18) {
81+
this.nat = this.nats[parseInt(this.seed.slice(-2), 16)];
82+
} else if (this.seed === '') {
83+
this.defaultSeed();
84+
}
85+
86+
if (this.page < 0 || this.page > 10000) this.page = 1;
87+
///////////////////
88+
89+
this.seedRNG();
90+
};
91+
92+
Generator.prototype.generate = function(cb) {
93+
this.results = this.results || 1;
94+
95+
let output = [];
96+
let nat, inject;
97+
98+
for (let i = 0; i < this.results; i++) {
99+
current = {};
100+
nat = this.nat === null ? this.randomNat() : this.nat;
101+
if (Array.isArray(nat)) {
102+
nat = nat[range(0, nat.length-1)];
103+
}
104+
inject = injects[version][nat];
105+
106+
current.gender = this.gender === null ? randomItem(['male', 'female']) : this.gender;
107+
108+
let name = this.randomName(current.gender, nat);
109+
this.include('name', {
110+
title: current.gender === 'male' ? 'mr' : randomItem(datasets[version].common.title),
111+
first: name[0],
112+
last: name[1]
113+
});
114+
115+
this.include('location', {
116+
street: range(1000, 9999) + ' ' + randomItem(datasets[version][nat].street),
117+
city: randomItem(datasets[version][nat].cities),
118+
state: randomItem(datasets[version][nat].states),
119+
postcode: range(10000, 99999)
120+
});
121+
122+
this.include('email', name[0] + '.' + name[1].replace(/ /g, '') + '@example.com');
123+
124+
let salt = random(2, 8);
125+
let password = this.password === undefined ? randomItem(datasets[version].common.passwords) : this.genPassword();
126+
this.include('login', {
127+
username: randomItem(datasets[version].common.user1) + randomItem(datasets[version].common.user2) + range(100, 999),
128+
password,
129+
salt: salt,
130+
md5: crypto.createHash('md5').update(password + salt).digest('hex'),
131+
sha1: crypto.createHash('sha1').update(password + salt).digest('hex'),
132+
sha256: crypto.createHash('sha256').update(password + salt).digest('hex')
133+
});
134+
135+
let dob = range(-800000000000, this.constantTime*1000 - 86400000*365*21);
136+
this.include('dob', moment(dob).format('YYYY-MM-DD HH:mm:ss'));
137+
this.include('registered', moment(range(1016688461000, this.constantTime*1000)).format('YYYY-MM-DD HH:mm:ss'));
138+
139+
let id, genderText
140+
if (nat != 'LEGO') {
141+
id = current.gender == 'male' ? range(0, 99) : range(0, 96);
142+
genderText = current.gender == 'male' ? 'men' : 'women';
143+
} else {
144+
id = range(0, 9);
145+
genderText = 'lego';
146+
}
147+
base = 'https://randomuser.me/api/';
148+
149+
this.include('picture', {
150+
large: base + 'portraits/' + genderText + '/' + id + '.jpg',
151+
medium: base + 'portraits/med/' + genderText + '/' + id + '.jpg',
152+
thumbnail: base + 'portraits/thumb/' + genderText + '/' + id + '.jpg'
153+
});
154+
155+
inject(this.inc, current); // Inject unique fields for nationality
156+
157+
this.include('nat', nat);
158+
159+
// Gender hack - Remove gender if the user doesn't want it in the results
160+
if (this.inc.indexOf('gender') === -1) {
161+
delete current.gender;
162+
}
163+
164+
output.push(current);
165+
}
166+
167+
let json = {
168+
results: output,
169+
info: {
170+
seed: String(this.seed + (this.nat !== null && !Array.isArray(this.nat) ? pad((this.nats.indexOf(this.nat)).toString(16), 2) : '')),
171+
results: this.results,
172+
page: this.page,
173+
version: this.version
174+
}
175+
};
176+
177+
if (this.noInfo) delete json.info;
178+
179+
this.defaultSeed();
180+
this.seedRNG();
181+
182+
if (this.format === 'yaml') {
183+
cb(YAML.stringify(json, 4), "yaml");
184+
} else if (this.format === 'xml') {
185+
cb(js2xmlparser('user', json), "xml");
186+
} else if (this.format === 'prettyjson' || this.format === 'pretty') {
187+
cb(JSON.stringify(json, null, 2), "json");
188+
} else if (this.format === 'csv') {
189+
converter.json2csv(json.results, (err, csv) => {
190+
cb(csv, "csv");
191+
});
192+
} else {
193+
cb(JSON.stringify(json), "json");
194+
}
195+
};
196+
197+
198+
Generator.prototype.seedRNG = function() {
199+
let seed = this.seed;
200+
if (this.seed.length === 18) {
201+
seed = this.seed.substring(0, 16);
202+
}
203+
seed = this.page !== 1 ? seed + String(this.page) : seed;
204+
205+
seed = parseInt(crypto.createHash('md5').update(seed).digest('hex').substring(0, 8), 16);
206+
mersenne.seed(seed);
207+
};
208+
209+
Generator.prototype.defaultSeed = function() {
210+
this.seed = random(1, 16);
211+
};
212+
213+
Generator.prototype.randomNat = function() {
214+
return this.nats[range(0, this.nats.length-1)];
215+
};
216+
217+
Generator.prototype.validNat = function(nat) {
218+
if (Array.isArray(nat)) {
219+
for (let i = 0; i < nat.length; i++) {
220+
if (this.nats.indexOf(nat[i]) === -1) {
221+
return false;
222+
}
223+
}
224+
} else {
225+
return this.nats.indexOf(nat) !== -1;
226+
}
227+
return true;
228+
};
229+
230+
Generator.prototype.randomName = function(gender, nat) {
231+
gender = gender === undefined ? randomItem(['male', 'female']) : gender;
232+
return [randomItem(datasets[version][nat][gender + '_first']), randomItem(datasets[version][nat]['last'])];
233+
};
234+
235+
Generator.prototype.getNats = function() {
236+
let exclude = ['common', 'LEGO'];
237+
let nats = Object.keys(datasets[version]).filter((nat) => {
238+
return exclude.indexOf(nat) == -1;
239+
});
240+
return nats;
241+
};
242+
243+
Generator.prototype.include = function(field, value) {
244+
if (this.inc.indexOf(field) !== -1) {
245+
current[field] = value;
246+
}
247+
};
248+
249+
Generator.prototype.checkOptions = function(options) {
250+
let keys = Object.keys(options);
251+
for (let i = 0; i < keys.length; i++) {
252+
if (Array.isArray(options[keys[i]])) {
253+
options[keys[i]] = options[keys[i]][options[keys[i]].length-1];
254+
}
255+
}
256+
};
257+
258+
Generator.prototype.genPassword = function() {
259+
if (this.password.length === 0) {
260+
return randomItem(datasets[version].common.passwords);
261+
}
262+
263+
let charsets = {
264+
special: " !\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~",
265+
upper: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
266+
lower: "abcdefghijklmnopqrstuvwxyz",
267+
number: "0123456789"
268+
};
269+
270+
// Parse sections
271+
let sections = ["special", "upper", "lower", "number"];
272+
let matches = this.password.split(',').filter(val => sections.indexOf(val) !== -1)
273+
274+
if (matches.length === 0) {
275+
return randomItem(datasets[version].common.passwords);
276+
}
277+
278+
matches = matches.filter((v,i,self) => self.indexOf(v) === i);
279+
280+
// Construct charset to choose from
281+
let charset = "";
282+
matches.forEach(match => {
283+
charset += charsets[match];
284+
});
285+
286+
let length = this.password.split(',').slice(-1)[0];
287+
288+
// Range
289+
let min, max;
290+
if (length.indexOf('-') !== -1) {
291+
let range = length.split('-').map(Number);
292+
min = Math.min(...range);
293+
max = Math.max(...range);
294+
} else {
295+
min = Number(Number(length));
296+
max = min;
297+
}
298+
min = min > 64 || min < 1 || min === undefined || isNaN(min) ? 8 : min;
299+
max = max > 64 || max < 1 || max === undefined || isNaN(max) ? 64 : max;
300+
301+
let passLen = range(min, max);
302+
303+
// Generate password
304+
let password = "";
305+
for (let i = 0; i < passLen; i++) {
306+
password += String(charset[range(0, charset.length-1)]);
307+
}
308+
309+
return password;
310+
};
311+
312+
let random = (mode, length) => {
313+
let result = '';
314+
let chars;
315+
316+
if (mode == 1) {
317+
chars = 'abcdef1234567890';
318+
} else if (mode == 2) {
319+
chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
320+
} else if (mode == 3) {
321+
chars = '0123456789';
322+
} else if (mode == 4) {
323+
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
324+
}
325+
for (let i = 0; i < length; i++) {
326+
result += chars[range(0, chars.length-1)];
327+
}
328+
329+
return result;
330+
};
331+
332+
let randomItem = (arr) => {
333+
return arr[range(0, arr.length-1)];
334+
};
335+
336+
let pad = (n, width, z) => {
337+
z = z || '0';
338+
n = n + '';
339+
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
340+
}
341+
342+
let range = (min, max) => {
343+
return min + mersenne.rand(max-min+1);
344+
};
345+
346+
let uppercaseify = (val) => {
347+
if (Array.isArray(val)) {
348+
return val.map((str) => {
349+
return str.toUpperCase();
350+
});
351+
} else {
352+
return val.toUpperCase();
353+
}
354+
}
355+
356+
let include = (inc, field, value) => {
357+
if (inc.indexOf(field) !== -1) {
358+
if (typeof value === 'function') value();
359+
else current[field] = value;
360+
}
361+
};
362+
363+
module.exports = Generator;

api/1.1/data/AU/inject.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module.exports = (inc, contents) => {
2+
var pic = contents.picture;
3+
delete contents.picture;
4+
5+
include(inc, 'phone', '0' + range(0, 9) + '-' + range(0, 9) + random(3, 3) + '-' + random(3, 4));
6+
include(inc, 'cell', '04' + random(3, 2) + '-' + random(3, 3) + '-' + random(3, 3));
7+
include(inc, 'id', {
8+
name: 'TFN',
9+
value: random(3, 9)
10+
});
11+
include(inc, 'picture', pic);
12+
include(inc, 'location', () => {
13+
contents.location.postcode = range(200, 9999); // Override common postcode with AU range
14+
15+
// Override common postcode with AU range
16+
var oldStreet = contents.location.street;
17+
contents.location.street = contents.location.street.replace(/(\d+)/, range(1,9999));
18+
});
19+
};

0 commit comments

Comments
 (0)