From 555e33040b9f6ada56f64a7127a4952f2efc75a9 Mon Sep 17 00:00:00 2001 From: brian Date: Tue, 13 Apr 2021 00:52:57 -0400 Subject: [PATCH] refuses to load private bins; fixed NodeJS startup error connectiong to Postgres --- .gitignore | 1 + docker-compose.yml | 4 +- nginx/public_html/App.js | 1 + nginx/public_html/handlers/App.js | 27 ++++-- nginx/public_html/handlers/index.js | 22 ++++- nginx/public_html/index.css | 10 ++ nginx/public_html/index.js | 7 +- node/server.js | 145 +++++++++++++++++----------- 8 files changed, 148 insertions(+), 69 deletions(-) diff --git a/.gitignore b/.gitignore index d333b23..6d479db 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,4 @@ dist # Stores VSCode versions used for testing VSCode extensions .vscode-test +pgdata \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 62efbef..865b2d6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,7 +37,7 @@ services: environment: - POSTGRES_USER=pastebin - POSTGRES_PASSWORD=buginoo - #volumes: - # - ./postgres/pgdata:/var/lib/postgresql/data + volumes: + - ./postgres/pgdata:/var/lib/postgresql/data expose: - "5432" \ No newline at end of file diff --git a/nginx/public_html/App.js b/nginx/public_html/App.js index e87f318..c5acd31 100644 --- a/nginx/public_html/App.js +++ b/nginx/public_html/App.js @@ -27,6 +27,7 @@ function App(vnode_init){ const o = function(handler){ return handler.bind(null, s, dispatch); } const o1 = function(handler, p1){ return handler.bind(null, s, dispatch, p1); } return m('.app', {key: 'app'}, [ + m('.error-message', {key:'error-message'}, s.error_message), m('.top', {key: 'top'}, [ m('.top-left', {key: 'top-left'}, [ m('button', {key: 'button', onclick: o(new_note_handler)}, 'New Note...'), diff --git a/nginx/public_html/handlers/App.js b/nginx/public_html/handlers/App.js index ea034bc..36063a3 100644 --- a/nginx/public_html/handlers/App.js +++ b/nginx/public_html/handlers/App.js @@ -10,7 +10,12 @@ const preventDblClickSelection = function(e){ const load_notes = function(state, dispatch){ api.post('/load-notes', {bin_id: state.bin_id}) .then(res=>{ - dispatch('notes-loaded', res.notes); + if(res.success===true){ + dispatch('notes-loaded', res.notes); + } + else if(res.authorized===false){ + dispatch('notes-unauthorized'); + } }); }; const runSearch = function(state, dispatch){ @@ -86,11 +91,21 @@ const choose_bin_handler = function(state, dispatch, bin_id){ dispatch('bin-requested', bin_id); api.post('/load-bin', {bin_id}) .then(res=>{ - dispatch('bin-loaded', res.bin); - }); - api.post('/load-notes', {bin_id}) - .then(res=>{ - dispatch('notes-loaded', res.notes); + if(res.success===true){ + dispatch('bin-loaded', res.bin); + api.post('/load-notes', {bin_id}) + .then(res=>{ + if(res.success===true && res.authorized===true){ + dispatch('notes-loaded', res.notes); + } + else if(res.success===false && res.authorized===false){ + dispatch('notes-unauthorized'); + } + }); + } + else if(res.success===false && res.authorized==false){ + dispatch('bin-unauthorized'); + } }); }; diff --git a/nginx/public_html/handlers/index.js b/nginx/public_html/handlers/index.js index 7b814ed..cf6ded9 100644 --- a/nginx/public_html/handlers/index.js +++ b/nginx/public_html/handlers/index.js @@ -3,11 +3,18 @@ import api from '../api.js'; const initial_load_bin_handler = function(state, dispatch, bin_id){ api.post('/load-bin', {bin_id}) .then(res => { - dispatch('bin-loaded', res.bin); + if(res.success===false && res.authorized===false){ + dispatch('bin-unauthorized'); + } + else{ + dispatch('bin-loaded', res.bin); + } }); api.post('/load-notes', {bin_id}) .then(res => { - dispatch('notes-loaded', res.notes); + if(res.success===true){ + dispatch('notes-loaded', res.notes); + } }); }; @@ -22,11 +29,18 @@ const hash_change_handler = function(state, dispatch, e){ dispatch('bin-requested', bin_id); api.post('/load-bin', {bin_id}) .then(res=>{ - dispatch('bin-loaded', res.bin); + if(res.success==true){ + dispatch('bin-loaded', res.bin); + } + else if(res.authorized===false){ + dispatch('bin-unauthorized'); + } }); api.post('/load-notes', {bin_id}) .then(res => { - dispatch('notes-loaded', res.notes); + if(res.success==true){ + dispatch('notes-loaded', res.notes); + } }); } }; diff --git a/nginx/public_html/index.css b/nginx/public_html/index.css index d943146..5239215 100644 --- a/nginx/public_html/index.css +++ b/nginx/public_html/index.css @@ -14,6 +14,16 @@ body{ } /* end full-page app */ +.error-message{ + position: fixed; + top: 0px; + margin-top: 1rem; + width: 40%; + margin-right: auto; + margin-left: auto; + background-color: #ee8888; + } + .app{ height: 100%; width: 100%; diff --git a/nginx/public_html/index.js b/nginx/public_html/index.js index 11c3d91..f92e565 100644 --- a/nginx/public_html/index.js +++ b/nginx/public_html/index.js @@ -99,12 +99,14 @@ function addToBinListIfLoggedIn(state){ const reducer = handleActions({ 'new-bin': (s, bin) => { i(s,bin); ref(s,s,'bin_id',bin.id); s.notes=[]; s.temp_bin_name=bin.id; }, - 'bin-requested': (s, bin_id) => { i(s,{id:bin_id}); ref(s,s,'bin_id',bin_id); s.notes = []; }, + 'bin-requested': (s, bin_id) => { i(s,{id:bin_id}); ref(s,s,'bin_id',bin_id); s.notes = []; s.error_message=''; }, 'bin-loaded': (s, bin) => { i(s,bin); ref(s,s,'bin_id',bin.id); s.temp_bin_name=bin.name; }, + 'bin-unauthorized': (s) => { s.error_message = 'Not authorized to load bin. Please sign-in.'; }, 'update-search-term': (s, search_term) => { s.search_term=search_term; }, 'update-search-results': (s, _notes) => { integrateAll(s,s.notes,'note_id',_notes); s.notes =_notes.map(n=>({is_editing: false, temp_text: '', bin_id: s.bin_id, note_id:n.id})); }, 'add-note': (s, {id, date}) => { i(s, {id: id, text: '', modified: date}); s.notes.unshift({is_editing: true, temp_text: '', bin_id: s.bin_id, is_focused:true, is_new: true, note_id: id}); }, 'notes-loaded': (s, _notes) => { integrateAll(s,_notes); s.notes = _notes.map(n=>({is_editing: false, temp_text: n.text, bin_id: s.bin_id, note_id:n.id})); }, + 'notes-unauthorized': (s) => { s.error_message = 'Not authorized to load notes in bin. Please sign-in.'; }, 'immediately-cancel-note': (s, note_id) => { disintegrate(s,note_id); s.notes.splice(s.notes.findIndex(n=>n.note_id===note_id), 1); }, 'update-note-text': (s, {id, text}) => { const note_s=s.notes.find(n=>n.note_id===id); note_s.is_new=false; s.db[note_s.note_id].model.text=text; }, // updates underlying note text (i.e. the "model", not the app note_state) "in the background" (e.g. from a server-pushed update), regardless of whether it's being edited; "save" is a separate action, below 'update-note-editing': (s, {id, is_editing}) => { const note_s=s.notes.find(n=>n.note_id===id); note_s.is_editing=is_editing; note_s.temp_text=s.db[note_s.note_id].model.text; }, @@ -146,7 +148,8 @@ const reducer = handleActions({ ref_count: 1, model: {id: bin_id, name: bin_id, user_id: ''} } - } + }, + error_message: '' //search_result_notes: [] }); diff --git a/node/server.js b/node/server.js index 773e405..b224a38 100644 --- a/node/server.js +++ b/node/server.js @@ -30,22 +30,86 @@ const load_notes_stmt = +" JOIN note AS n" +" ON n.id = bn.note_id" +" WHERE bn.bin_id = $1"; +const load_bin_stmt = + "SELECT b.id, b.name, bu.user_id as owner_user_id, s.user_id as session_user_id FROM bin AS b" + +" FULL JOIN bin_user AS bu" // we want the bin regardless of whether it has an associated user, hence LEFT JOIN + +" ON bu.bin_id = b.id" + +" FULL JOIN session AS s" + +" ON s.id = $2" + +" WHERE b.id = $1"; +const search_stmt = + "SELECT n.id, n.text, n.modified FROM note AS n" + +" INNER JOIN bin_note AS bn" + +" ON bn.note_id = n.id AND bn.bin_id = $2" + +" WHERE n.text LIKE '%' || $1 || '%'"; // need to use string concat otherwise the `$1` is viewed as part of the string instead of a placeholder +const order_desc_stmt = " ORDER BY n.modified DESC"; +const order_asc_stmt = " ORDER BY n.modified ASC"; +const upsert_note_stmt = + "INSERT INTO note (id, text, modified) VALUES ($1, $2, NOW())" + +" ON CONFLICT (id)" + +" DO UPDATE SET text = EXCLUDED.text, modified = EXCLUDED.modified"; +const upsert_bin_stmt = + "INSERT INTO bin (id, name) VALUES ($1, $1)" + +" ON CONFLICT (id)" + +" DO NOTHING"; +const upsert_bin_note_stmt = + "INSERT INTO bin_note (bin_id, note_id) VALUES ($1, $2)" + +" ON CONFLICT (bin_id, note_id)" + +" DO NOTHING"; +const upsert_bin_user_stmt = + "INSERT INTO bin_user (bin_id, user_id)" + +" (SELECT $1, s.user_id FROM session AS s WHERE s.id = $2)" + +" ON CONFLICT (bin_id, user_id)" + +" DO NOTHING"; +const login_check_stmt = + "SELECT id, password FROM user_ AS u" + +" WHERE u.username = $1"; +const register_stmt = + "INSERT INTO user_ (id, username, password) VALUES ($1, $2, $3)"; +const session_create_stmt = + "INSERT INTO session (id, user_id) VALUES ($1, $2)"; +const user_bin_list_stmt = + "SELECT b.id, b.name FROM bin_user AS bu" + +" INNER JOIN bin AS b" + +" ON bu.bin_id = b.id" + +" WHERE bu.user_id = $1"; +const delete_session_stmt = + "DELETE FROM session WHERE id = $1"; +const rename_bin_stmt = + "INSERT INTO bin (id, name) VALUES ($1, $2)" + +" ON CONFLICT (id)" + +" DO UPDATE SET name = EXCLUDED.name;"; + + // {bin_id} router.post('/load-notes', (req, res)=>{ - const bin_id = req.body.bin_id; - db.query(load_notes_stmt, [bin_id]) - .then(result => { - res.json({success:true, notes:result.rows}) + const {bin_id, session_id} = req.body; + db.query(load_bin_stmt, [bin_id, session_id]) + .then(bin_result =>{ + if(bin_result.rows.length > 0){ + const bin = bin_result.rows[0]; + // if it's a public bin: + if(bin.owner_user_id===null){ + db.query(load_notes_stmt, [bin_id]) + .then(result => { + res.json({success:true, authorized:false, notes:result.rows}) + }); + } + // if it's a private bin, and the owner is signed-in: + else if(bin.owner_user_id===bin.session_user_id){ + db.query(load_notes_stmt, [bin_id]) + .then(result => { + res.json({success:true, authorized:true, notes:result.rows}) + }); + } + else{ + res.json({success:false, authorized:false}); + } + + } }); }); -const load_bin_stmt = - "SELECT b.id, b.name FROM bin AS b" - +" FULL JOIN bin_user AS bu" // we want the bin regardless of whether it has an associated user, hence LEFT JOIN - +" ON bu.bin_id = b.id" - +" INNER JOIN session AS s" - +" ON (bu.bin_id IS NULL) OR (s.id = $2 AND s.user_id = bu.user_id)" - +" WHERE b.id = $1"; router.post('/load-bin', (req, res)=>{ const {bin_id, session_id} = req.body; // if a bin has no associated user, it's considered public and can be accessed even when not logged-in. @@ -55,22 +119,26 @@ router.post('/load-bin', (req, res)=>{ const bin = result.rows[0]; // if a bin with given id was found: if(result.rows.length>0){ - res.json({success:true, bin:{id:bin.id, name:bin.name}}); + const bin = result.rows[0]; + // if it's a public bin: + if(bin.owner_user_id===null){ + res.json({success:true, authorized:false, bin:{id:bin.id, name:bin.name}}); + } + // if it's a private bin, and the owner is signed-in: + else if(bin.owner_user_id===bin.session_user_id){ + res.json({success:true, authorized:true, bin:{id:bin.id, name:bin.name}}); + } + else{ + res.json({success:false, authorized:false}); + } } else{ - res.json({success:true, bin:{id:bin_id, name:bin_id}}); + res.json({success:true, authorized:true, bin:{id:bin_id, name:bin_id}}); } }); // {status: 'ok', bin:{id:bin.id}, notes: bin.notes} }); -const search_stmt = - "SELECT n.id, n.text, n.modified FROM note AS n" - +" INNER JOIN bin_note AS bn" - +" ON bn.note_id = n.id AND bn.bin_id = $2" - +" WHERE n.text LIKE '%' || $1 || '%'"; // need to use string concat otherwise the `$1` is viewed as part of the string instead of a placeholder -const order_desc_stmt = " ORDER BY n.modified DESC"; -const order_asc_stmt = " ORDER BY n.modified ASC"; // {search_term, sorting, bin_id} router.post('/search', (req, res)=>{ const {search_term, bin_id, sorting} = req.body; @@ -80,23 +148,6 @@ router.post('/search', (req, res)=>{ }); }); -const upsert_note_stmt = - "INSERT INTO note (id, text, modified) VALUES ($1, $2, NOW())" - +" ON CONFLICT (id)" - +" DO UPDATE SET text = EXCLUDED.text, modified = EXCLUDED.modified"; -const upsert_bin_stmt = - "INSERT INTO bin (id, name) VALUES ($1, $1)" - +" ON CONFLICT (id)" - +" DO NOTHING"; -const upsert_bin_note_stmt = - "INSERT INTO bin_note (bin_id, note_id) VALUES ($1, $2)" - +" ON CONFLICT (bin_id, note_id)" - +" DO NOTHING"; -const upsert_bin_user_stmt = - "INSERT INTO bin_user (bin_id, user_id)" - +" (SELECT $1, s.user_id FROM session AS s WHERE s.id = $2)" - +" ON CONFLICT (bin_id, user_id)" - +" DO NOTHING"; // {bin_id, note_id, text} router.post('/save', (req, res)=>{ const {bin_id, note_id, text, session_id} = req.body; @@ -116,18 +167,6 @@ router.post('/save', (req, res)=>{ // {status: 'ok'} }); -const login_check_stmt = - "SELECT id, password FROM user_ AS u" - +" WHERE u.username = $1"; -const register_stmt = - "INSERT INTO user_ (id, username, password) VALUES ($1, $2, $3)"; -const session_create_stmt = - "INSERT INTO session (id, user_id) VALUES ($1, $2)"; -const user_bin_list_stmt = - "SELECT b.id, b.name FROM bin_user AS bu" - +" INNER JOIN bin AS b" - +" ON bu.bin_id = b.id" - +" WHERE bu.user_id = $1"; router.post('/login', (req, res)=>{ const {username, password} = req.body; const password_from_client=password; @@ -165,17 +204,13 @@ router.post('/login', (req, res)=>{ }); }); -const delete_session_stmt = - "DELETE FROM session WHERE id = $1"; + router.post('/logout', (req, res)=>{ db.query(delete_session_stmt, [req.body.session_id]); res.json({success:true}); }); -const rename_bin_stmt = - "INSERT INTO bin (id, name) VALUES ($1, $2)" - +" ON CONFLICT (id)" - +" DO UPDATE SET name = EXCLUDED.name;"; + router.post('/bin-rename', (req,res)=>{ const {bin_id, name, session_id} = req.body; db.query(rename_bin_stmt, [bin_id, name])