diff --git a/db/initialize.ts b/db/initialize.ts index 6ca20c5..c366bd4 100644 --- a/db/initialize.ts +++ b/db/initialize.ts @@ -18,7 +18,7 @@ await dbClient.execute(` CREATE TABLE users ( id varchar(20) NOT NULL, name varchar(20) NOT NULL, - pin varchar(16) NOT NULL, + hash varchar(60) NOT NULL, email varchar(255) NULL, deleteCode varchar(20) NULL, PRIMARY KEY (id), diff --git a/deno.json b/deno.json index 2d5c7b9..5c08951 100755 --- a/deno.json +++ b/deno.json @@ -22,6 +22,7 @@ }, "nodeModulesDir": "none", "imports": { + "@bcrypt": "jsr:@felix/bcrypt", "@mysql": "https://deno.land/x/mysql@v2.12.1/mod.ts", "@nanoid": "https://deno.land/x/nanoid@v3.0.0/mod.ts", "@std/http": "jsr:@std/http@1.0.15", diff --git a/deno.lock b/deno.lock index 41462f2..8810658 100644 --- a/deno.lock +++ b/deno.lock @@ -1,17 +1,40 @@ { "version": "5", "specifiers": { + "jsr:@denosaurs/plug@^1.1.0": "1.1.0", + "jsr:@felix/bcrypt@*": "1.0.8", "jsr:@std/cli@^1.0.17": "1.0.17", + "jsr:@std/encoding@1": "1.0.10", "jsr:@std/encoding@^1.0.10": "1.0.10", + "jsr:@std/fmt@1": "1.0.7", "jsr:@std/fmt@^1.0.7": "1.0.7", + "jsr:@std/fs@1": "1.0.23", "jsr:@std/html@^1.0.3": "1.0.3", "jsr:@std/http@1.0.15": "1.0.15", + "jsr:@std/internal@^1.0.12": "1.0.13", "jsr:@std/media-types@^1.1.0": "1.1.0", "jsr:@std/net@^1.0.4": "1.0.4", + "jsr:@std/path@1": "1.0.9", "jsr:@std/path@^1.0.9": "1.0.9", + "jsr:@std/path@^1.1.4": "1.1.4", "jsr:@std/streams@^1.0.9": "1.0.9" }, "jsr": { + "@denosaurs/plug@1.1.0": { + "integrity": "eb2f0b7546c7bca2000d8b0282c54d50d91cf6d75cb26a80df25a6de8c4bc044", + "dependencies": [ + "jsr:@std/encoding@1", + "jsr:@std/fmt@1", + "jsr:@std/fs", + "jsr:@std/path@1" + ] + }, + "@felix/bcrypt@1.0.8": { + "integrity": "59c41160fc027882479c512db5d53792c4d91aadcd49467c85caa2f1679046f2", + "dependencies": [ + "jsr:@denosaurs/plug" + ] + }, "@std/cli@1.0.17": { "integrity": "e15b9abe629e17be90cc6216327f03a29eae613365f1353837fa749aad29ce7b" }, @@ -21,6 +44,13 @@ "@std/fmt@1.0.7": { "integrity": "2a727c043d8df62cd0b819b3fb709b64dd622e42c3b1bb817ea7e6cc606360fb" }, + "@std/fs@1.0.23": { + "integrity": "3ecbae4ce4fee03b180fa710caff36bb5adb66631c46a6460aaad49515565a37", + "dependencies": [ + "jsr:@std/internal", + "jsr:@std/path@^1.1.4" + ] + }, "@std/html@1.0.3": { "integrity": "7a0ac35e050431fb49d44e61c8b8aac1ebd55937e0dc9ec6409aa4bab39a7988" }, @@ -28,15 +58,18 @@ "integrity": "435a4934b4e196e82a8233f724da525f7b7112f3566502f28815e94764c19159", "dependencies": [ "jsr:@std/cli", - "jsr:@std/encoding", - "jsr:@std/fmt", + "jsr:@std/encoding@^1.0.10", + "jsr:@std/fmt@^1.0.7", "jsr:@std/html", "jsr:@std/media-types", "jsr:@std/net", - "jsr:@std/path", + "jsr:@std/path@^1.0.9", "jsr:@std/streams" ] }, + "@std/internal@1.0.13": { + "integrity": "2f9546691d4ac2d32859c82dff284aaeac980ddeca38430d07941e7e288725c0" + }, "@std/media-types@1.1.0": { "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" }, @@ -46,6 +79,12 @@ "@std/path@1.0.9": { "integrity": "260a49f11edd3db93dd38350bf9cd1b4d1366afa98e81b86167b4e3dd750129e" }, + "@std/path@1.1.4": { + "integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5", + "dependencies": [ + "jsr:@std/internal" + ] + }, "@std/streams@1.0.9": { "integrity": "a9d26b1988cdd7aa7b1f4b51e1c36c1557f3f252880fa6cc5b9f37078b1a5035" } @@ -120,6 +159,7 @@ }, "workspace": { "dependencies": [ + "jsr:@felix/bcrypt@*", "jsr:@std/http@1.0.15" ] } diff --git a/mod.ts b/mod.ts index bdbaf04..1e9b4d6 100644 --- a/mod.ts +++ b/mod.ts @@ -1,3 +1,4 @@ +import { hash, verify } from '@bcrypt'; import { customAlphabet } from '@nanoid'; import { STATUS_CODE, STATUS_TEXT, StatusCode } from '@std/http/status'; @@ -101,12 +102,13 @@ Deno.serve({ port: config.api.port }, async (req) => { if (userNameMatches.length === 0) { if (body.name.length < 4 || body.name.length > 20) return genericResponse(STATUS_CODE.BadRequest, `Name too ${body.name.length < 4 ? 'short' : 'long'}.`); - if (body.pin.length < 4 || body.pin.length > 16) return genericResponse(STATUS_CODE.BadRequest, `PIN too ${body.pin.length < 4 ? 'short' : 'long'}.`); + if (body.pin.length < 4) return genericResponse(STATUS_CODE.BadRequest, `PIN too ${body.pin.length < 4 ? 'short' : 'long'}.`); if (body.email.length > 255) return genericResponse(STATUS_CODE.BadRequest, 'Email too long.'); const id = nanoid(); + const pinHash = await hash(body.pin); - await dbClient.execute('INSERT INTO users(id,name,pin,email) values(?,?,?,?)', [id, body.name, body.pin, body.email]).catch(() => { + await dbClient.execute('INSERT INTO users(id,name,hash,email) values(?,?,?,?)', [id, body.name, pinHash, body.email]).catch(() => { failed = true; }); @@ -121,11 +123,12 @@ Deno.serve({ port: config.api.port }, async (req) => { } else { const body = await req.json(); - const loginMatch = await dbClient.query('SELECT id, email, deleteCode FROM users WHERE name = ? AND pin = ?', [body.name, body.pin]).catch(() => { + const loginMatch = await dbClient.query('SELECT id, hash, email, deleteCode FROM users WHERE name = ?', [body.name]).catch(() => { failed = true; }); if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB."); - if (loginMatch.length === 0) return genericResponse(STATUS_CODE.Forbidden, 'Invalid name/PIN combination.'); + if (loginMatch.length === 0) return genericResponse(STATUS_CODE.Forbidden, 'Invalid name/PIN combination. Remember name is case sensitive.'); + if (!(await verify(body.pin, loginMatch[0].hash))) return genericResponse(STATUS_CODE.Forbidden, 'Invalid name/PIN combination.'); const id = loginMatch[0].id; const email = loginMatch[0].email; const hasEmail = email.length > 0;