change bcrypt dep, fix buildHome

This commit is contained in:
Ean Milligan
2026-04-23 19:09:30 -04:00
parent 1ceae4d158
commit 884b5b81a0
4 changed files with 133 additions and 124 deletions

View File

@@ -22,7 +22,7 @@
},
"nodeModulesDir": "none",
"imports": {
"@bcrypt": "jsr:@felix/bcrypt",
"@bcrypt": "https://deno.land/x/bcrypt/mod.ts",
"@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",

94
deno.lock generated
View File

@@ -1,94 +1,16 @@
{
"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:@std/http@1.0.15": "1.0.15"
},
"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"
},
"@std/encoding@1.0.10": {
"integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1"
},
"@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"
},
"@std/http@1.0.15": {
"integrity": "435a4934b4e196e82a8233f724da525f7b7112f3566502f28815e94764c19159",
"dependencies": [
"jsr:@std/cli",
"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@^1.0.9",
"jsr:@std/streams"
]
},
"@std/internal@1.0.13": {
"integrity": "2f9546691d4ac2d32859c82dff284aaeac980ddeca38430d07941e7e288725c0"
},
"@std/media-types@1.1.0": {
"integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4"
},
"@std/net@1.0.4": {
"integrity": "2f403b455ebbccf83d8a027d29c5a9e3a2452fea39bb2da7f2c04af09c8bc852"
},
"@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"
"integrity": "435a4934b4e196e82a8233f724da525f7b7112f3566502f28815e94764c19159"
}
},
"redirects": {
"https://deno.land/x/bcrypt/mod.ts": "https://deno.land/x/bcrypt@v0.4.1/mod.ts"
},
"remote": {
"https://deno.land/std@0.104.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58",
"https://deno.land/std@0.104.0/async/deadline.ts": "1d6ac7aeaee22f75eb86e4e105d6161118aad7b41ae2dd14f4cfd3bf97472b93",
@@ -119,6 +41,11 @@
"https://deno.land/std@0.104.0/testing/_diff.ts": "5d3693155f561d1a5443ac751ac70aab9f5d67b4819a621d4b96b8a1a1c89620",
"https://deno.land/std@0.104.0/testing/asserts.ts": "e4311d45d956459d4423bc267208fe154b5294989da2ed93257b6a85cae0427e",
"https://deno.land/std@0.77.0/fmt/colors.ts": "c5665c66f1a67228f21c5989bbb04b36d369b98dd7ceac06f5e26856c81c2531",
"https://deno.land/x/bcrypt@v0.4.1/mod.ts": "ff09bdae282583cf5f7d87efe37ddcecef7f14f6d12e8b8066a3058db8c6c2f7",
"https://deno.land/x/bcrypt@v0.4.1/src/bcrypt/base64.ts": "b8266450a4f1eb6960f60f2f7986afc4dde6b45bd2d7ee7ba10789e67e17b9f7",
"https://deno.land/x/bcrypt@v0.4.1/src/bcrypt/bcrypt.ts": "ec221648cc6453ea5e3803bc817c01157dada06aa6f7a0ba6b9f87aae32b21e2",
"https://deno.land/x/bcrypt@v0.4.1/src/main.ts": "08d201b289c8d9c46f8839c69cd6625b213863db29775c7a200afc3b540e64f8",
"https://deno.land/x/bcrypt@v0.4.1/src/worker.ts": "5a73bdfee9c9e622f47c9733d374b627dce52fb3ec1e74c8226698b3fc57ffac",
"https://deno.land/x/bytes_formater@v1.4.0/deps.ts": "4f98f74e21145423b873a5ca6ead66dc3e674fa81e230a0a395f9b86aafeceea",
"https://deno.land/x/bytes_formater@v1.4.0/format.ts": "657c41b9f180c3ed0f934dcf75f77b09b6a610be98bb07525bffe2acfd5af4d5",
"https://deno.land/x/bytes_formater@v1.4.0/mod.ts": "c6bf35303f53d74e9134eb13f666fb388fb4c62c6b12b17542bbadade250a864",
@@ -159,7 +86,6 @@
},
"workspace": {
"dependencies": [
"jsr:@felix/bcrypt@*",
"jsr:@std/http@1.0.15"
]
}

4
mod.ts
View File

@@ -1,4 +1,4 @@
import { hash, verify } from '@bcrypt';
import { hash, compare } from '@bcrypt';
import { customAlphabet } from '@nanoid';
import { STATUS_CODE, STATUS_TEXT, StatusCode } from '@std/http/status';
@@ -128,7 +128,7 @@ Deno.serve({ port: config.api.port }, async (req) => {
});
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
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.');
if (!(await compare(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;

View File

@@ -36,46 +36,129 @@ export default async (userId: string, userName: string) => {
return `<div>
<script>
const actionMethod = new Map([['rename','PUT'],['move','PUT'],['undelete','PUT'],['delete','DELETE'],['perm-delete','DELETE']]);
function doAction(action,planId){
let newName='';
if(action==='rename'||action==='move'){newName=prompt(\`Please provide a new \${action==='rename'?'plan':'folder'} name:\`);if(!newName){return;}}
let userName=localStorage.getItem('name');
if(!userName){userName=prompt('Please enter your username:');if(!userName){return;}}
const userPIN=prompt('Please enter your PIN:');
fetch(\`/api/\${action}/\${planId}\`,{method:actionMethod.get(action),body:JSON.stringify({name:userName,pin:userPIN,folder:newName,planName:newName})})
.catch((e)=>{e.text().then((text)=>{alert(text);});})
.then((r) => {
if(r.status===200){localStorage.setItem('name',userName);r.text().then((text)=>{alert(text);window.location.reload();});}
else{r.text().then((text)=>{alert(text);});}
});
const actionMethod = new Map([
['rename', 'PUT'],
['move', 'PUT'],
['undelete', 'PUT'],
['delete', 'DELETE'],
['perm-delete', 'DELETE'],
]);
function doAction(action, planId) {
let newName = '';
if (action === 'rename' || action === 'move') {
newName = prompt(\`Please provide a new \${action==='rename'?'plan':'folder'} name:\`);
if (!newName) {
return;
}
}
let userName = localStorage.getItem('name');
if (!userName) {
userName = prompt('Please enter your username:');
if (!userName) {
return;
}
}
const userPIN = prompt('Please enter your PIN:');
fetch(\`/api/\${action}/\${planId}\`, {
method: actionMethod.get(action),
body: JSON.stringify({ name: userName, pin: userPIN, folder: newName, planName: newName }),
})
.catch((e) => {
e.text().then((text) => {
alert(text);
});
})
.then((r) => {
if (r.status === 200) {
localStorage.setItem('name', userName);
r.text().then((text) => {
alert(text);
window.location.reload();
});
} else {
r.text().then((text) => {
alert(text);
});
}
});
}
function exportPlans(){
fetch('/api/export/${userId}')
.catch((e)=>{e.text().then((text)=>{alert(text);});})
.then((r)=>{r.text().then((text)=>{alert(text);});});
function exportPlans() {
fetch('/api/export/${userId}')
.catch((e) => {
e.text().then((text) => {
alert(text);
});
})
.then((r) => {
r.text().then((text) => {
alert(text);
});
});
}
function openPlan(planId){window.open(\`${config.api.publicDomain}share#\${planId}\`);}
async function sharePlan(planId){
const link=\`${config.api.publicDomain}share#\${planId}\`;
try{await navigator.clipboard.writeText(link);alert('Link copied to clipboard');}
catch (error){prompt('Failed to copy to clipboard, please select and copy the link below:',link);}
function openPlan(planId) {
window.open(\`${config.api.publicDomain}share#\${planId}\`);
}
function deleteAccount(){
const userName=prompt('Please enter your username:')
if(!userName.trim()){return;}
const userPin=prompt('Please enter your PIN:');
if(!userPin.trim()){return;}
if(!confirm('Are you sure you want to delete your account? This is permanent and irreversible, and will delete all plans saved to your account as well.')){return;}
fetch('/api/auth',{method:'POST',body:JSON.stringify({name:userName.trim(),pin:userPin.trim()})})
.catch((e)=>{e.text().then((text)=>{alert(text);});})
.then((r) => {if(r.status===200){r.json().then((j)=>{
let deleteCode='';if(j.hasEmail&&j.deleteCodeSet)deleteCode=prompt('Please enter the Delete Code emailed to you (if you don\'t see it, please check your spam folder):');
if(j.hasEmail&&j.deleteCodeSet&&!deleteCode){alert('Delete code required.');return;}
fetch('/api/unenroll',{method:'DELETE',body:JSON.stringify({name:userName.trim(),pin:userPin.trim(),deleteCode:deleteCode.trim()})})
.catch((e)=>{e.text().then((text)=>{alert(text);});})
.then((r) => {if(r.status===200){alert('Account and plans deleted.');window.location.reload();}else{r.text().then((text)=>{alert(text);});}});
});}else{r.text().then((text)=>{alert(text);});}});
async function sharePlan(planId) {
const link = \`${config.api.publicDomain}share#\${planId}\`;
try {
await navigator.clipboard.writeText(link);
alert('Link copied to clipboard');
} catch (error) {
prompt('Failed to copy to clipboard, please select and copy the link below:', link);
}
}
function deleteAccount() {
const userName = prompt('Please enter your username:');
if (!userName.trim()) {
return;
}
const userPin = prompt('Please enter your PIN:');
if (!userPin.trim()) {
return;
}
if (!confirm('Are you sure you want to delete your account? This is permanent and irreversible, and will delete all plans saved to your account as well.')) {
return;
}
fetch('/api/auth', { method: 'POST', body: JSON.stringify({ name: userName.trim(), pin: userPin.trim() }) })
.catch((e) => {
e.text().then((text) => {
alert(text);
});
})
.then((r) => {
if (r.status === 200) {
r.json().then((j) => {
let deleteCode = '';
if (j.hasEmail && j.deleteCodeSet) {
deleteCode = prompt("Please enter the Delete Code emailed to you (if you don't see it, please check your spam folder):");
}
if (j.hasEmail && j.deleteCodeSet && !deleteCode) {
alert('Delete code required.');
return;
}
fetch('/api/unenroll', { method: 'DELETE', body: JSON.stringify({ name: userName.trim(), pin: userPin.trim(), deleteCode: deleteCode.trim() }) })
.catch((e) => {
e.text().then((text) => {
alert(text);
});
})
.then((r) => {
if (r.status === 200) {
alert('Account and plans deleted.');
window.location.reload();
} else {
r.text().then((text) => {
alert(text);
});
}
});
});
} else {
r.text().then((text) => {
alert(text);
});
}
});
}
</script>
<p>This is a very basic management page. Please excuse the number of alert/prompts that will come up when you click on things as it was the quickest way to build it out.</p>