· 4 years ago · Jan 17, 2021, 05:26 AM
1import http = require('http');
2import sqlite3 = require('sqlite3')
3import sqlite = require('sqlite'); // adds ES6 promises for sqlite3 - https://github.com/kriasoft/node-sqlite
4import cluster = require('cluster');
5
6const hostname = '127.0.0.1'
7const port = 3000
8
9const USE_CLUSTERING: boolean = true
10const numCPUs = require('os').cpus().length;
11let numWorkers: number = Math.round(numCPUs / 1)
12
13const dbfile: string = "test-js-userdb.sqlite"
14let db: sqlite.Database = null
15
16async function open(filename: string) {
17 db = await sqlite.open({ filename, driver: sqlite3.Database })
18}
19
20async function close() {
21 await db.run("pragma analysis_limit = 100");
22 await db.run("pragma optimize");
23 await db.close();
24}
25
26async function ensure(filename: string) {
27 if (!db) await open(filename)
28}
29
30async function create(filename: string) {
31 await ensure(filename);
32 await db.run("pragma trusted_schema = false")
33 await db.run("pragma journal_mode = WAL")
34 // can't figure out how to config defensive from node
35
36 await db.run("drop table if exists users")
37 await db.run(`create table users( username text primary key not null, plainpass text not null)`)
38}
39
40async function uuid(): Promise<string> {
41 const row = await db.get("select lower(hex(randomblob(16))) as uuid")
42 if (row) return row.uuid; else return null
43}
44
45// the try/catch and retry mechanics are there to deal with "Error: SQLITE_BUSY: database is locked"
46async function create_user(username: string, plainpass: string): Promise<boolean> {
47 const MAX_TRIES: number = 10
48 let numTries: number = 0
49 let errmsg: string = null
50 while (numTries++ < MAX_TRIES) {
51 try {
52 await db.run(`insert into users values('${username}', '${plainpass}')`)
53 return true
54 } catch (e) {
55 errmsg = e.toString()
56 }
57 }
58 console.error(`Could not create user ${username} after ${MAX_TRIES} tries due to ${errmsg}`)
59 return false
60}
61
62async function valid_plainpass(username: string, checkpass: string): Promise<boolean> {
63 const row = await db.get(`select plainpass from users where username='${username}'`)
64 return (row !== undefined && row.plainpass === checkpass)
65}
66
67async function web_create_user(req: http.IncomingMessage, res: http.ServerResponse) {
68 const random_name: string = await uuid()
69 const random_pass: string = await uuid()
70 const success: boolean = await create_user(random_name, random_pass)
71 res.writeHead(200, { 'Content-Type': 'text/plain' })
72 if (success) res.end(`Created user ${random_name} with pass ${random_pass}`);
73 else res.end(`Could not create user ${random_name} with pass ${random_pass}`);
74}
75
76async function web_check_password(req: http.IncomingMessage, res: http.ServerResponse) {
77 const random_name: string = await uuid()
78 const random_pass: string = await uuid()
79 const result: boolean = await valid_plainpass(random_name, random_pass)
80 res.writeHead(200, { 'Content-Type': 'text/plain' })
81 res.end(`Checked user ${random_name} with pass ${random_pass}: ${result}`)
82}
83
84async function web_default(req: http.IncomingMessage, res: http.ServerResponse) {
85 res.writeHead(200, { 'Content-Type': 'text/plain' })
86 res.end(`Try paths /create or /check`)
87}
88
89(async () => {
90 if (cluster.isMaster || !USE_CLUSTERING) {
91 console.log(`Creating database ${dbfile}`)
92 await create(dbfile);
93 }
94
95 if (cluster.isMaster && USE_CLUSTERING) {
96 // Fork workers.
97 for (let i = 0; i < numWorkers; i++) cluster.fork();
98 cluster.on('exit', (worker, code, signal) => {
99 console.log(`worker ${worker.process.pid} died`);
100 });
101 } else {
102 // Worker code
103 await ensure(dbfile)
104 const server = http.createServer(async (req: http.IncomingMessage, res: http.ServerResponse) => {
105 switch (req.url) {
106 case "/create": return await web_create_user(req, res)
107 case "/check": return await web_check_password(req, res)
108 default: return await web_default(req, res)
109 }
110 })
111 server.listen(port, hostname, () => {
112 console.log(`listening at http://${hostname}:${port}`)
113 })
114 }
115})()
116