mirror of
https://github.com/slynn1324/tinypin.git
synced 2026-01-23 18:35:59 +00:00
Compare commits
19 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d645768dd0 | ||
|
|
6dc5f188e0 | ||
|
|
8af7f57094 | ||
|
|
62a61aed72 | ||
|
|
58c583d461 | ||
|
|
219f6d48b5 | ||
|
|
0cc19c48c0 | ||
|
|
fed01f948b | ||
|
|
0bccaef904 | ||
|
|
0d1037a1f0 | ||
|
|
7e815f6512 | ||
|
|
59c07e8a43 | ||
|
|
ea96135e0c | ||
|
|
efa93f409f | ||
|
|
38d8fa6eab | ||
|
|
a573f4a073 | ||
|
|
8b866d1a72 | ||
|
|
0f3fc05594 | ||
|
|
8ee0acda17 |
35 changed files with 12231 additions and 2429 deletions
|
|
@ -4,8 +4,8 @@
|
|||
#only include these files
|
||||
!main.js
|
||||
!server
|
||||
!static
|
||||
!client
|
||||
!templates
|
||||
!package.json
|
||||
!package-lock.json
|
||||
!LICENSE
|
||||
!LICENSE
|
||||
|
|
|
|||
|
|
@ -16,4 +16,4 @@ RUN apk add build-base python3 && npm install --verbose && apk del build-base py
|
|||
|
||||
RUN mkdir /data && npm install
|
||||
|
||||
ENTRYPOINT ["sh", "-c" , "node server.js -i /data/images -d /data/tinypin.db"]
|
||||
ENTRYPOINT ["sh", "-c" , "node main.js -i /data/images -d /data/tinypin.db"]
|
||||
|
|
|
|||
|
|
@ -108,3 +108,7 @@ There is trivial security on the web pages to allow for multiple user support.
|
|||
- library > [node-fetch](https://www.npmjs.com/package/node-fetch)
|
||||
- library > [sharp](https://www.npmjs.com/package/sharp)
|
||||
- library > [yargs](https://www.npmjs.com/package/yargs)
|
||||
|
||||
## buy me a beer
|
||||
|
||||
If you find this useful and feel so inclinced, https://paypal.me/slynn1324. Otherwise, simply enjoy.
|
||||
|
|
|
|||
1626
bulma-custom/package-lock.json
generated
1626
bulma-custom/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"devDependencies": {
|
||||
"bulma": "^0.9.1",
|
||||
"node-sass": "^5.0.0"
|
||||
"bulma": "0.9.3",
|
||||
"node-sass": "6.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -73,6 +73,23 @@ app.addSetter("hash.parse", (data) => {
|
|||
|
||||
});
|
||||
|
||||
app.addSetter("load.user", async (data) => {
|
||||
store.do("loader.show");
|
||||
|
||||
let res = await fetch("/api/whoami");
|
||||
|
||||
if ( res.status == 200 ){
|
||||
data.user = await res.json();
|
||||
|
||||
window.csrfToken = data.user.csrf;
|
||||
} else {
|
||||
console.log("error getting user");
|
||||
}
|
||||
|
||||
|
||||
store.do("loader.hide");
|
||||
});
|
||||
|
||||
app.addSetter("load.boards", async (data) => {
|
||||
|
||||
store.do("loader.show");
|
||||
|
|
@ -138,7 +155,7 @@ app.addSetter('addPinModal.save', async (data) => {
|
|||
if ( boardId == "new" ){
|
||||
let res = await fetch('api/boards', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: { 'Content-Type': 'application/json', 'x-csrf-token': window.csrfToken },
|
||||
body: JSON.stringify({
|
||||
"name": data.addPinModal.newBoardName
|
||||
})
|
||||
|
|
@ -161,7 +178,8 @@ app.addSetter('addPinModal.save', async (data) => {
|
|||
let res = await fetch('api/pins', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': "application/json"
|
||||
'Content-Type': "application/json",
|
||||
'x-csrf-token': window.csrfToken
|
||||
},
|
||||
body: JSON.stringify(postData)
|
||||
});
|
||||
|
|
@ -349,6 +367,8 @@ if ( target ){
|
|||
|
||||
store.do('hash.parse');
|
||||
|
||||
store.do("load.user");
|
||||
|
||||
store.do('load.boards');
|
||||
|
||||
Reef.databind(appComponent);
|
||||
|
|
|
|||
177
client/app.js
177
client/app.js
|
|
@ -71,12 +71,15 @@ app.addSetter('load.board', async (data, force) => {
|
|||
});
|
||||
|
||||
app.addSetter('load.user', async (data) => {
|
||||
|
||||
console.log("load.user");
|
||||
store.do("loader.show");
|
||||
|
||||
let res = await fetch("/api/whoami");
|
||||
data.user = await res.json();
|
||||
|
||||
window.uid = data.user.id;
|
||||
window.csrfToken = data.user.csrf;
|
||||
dispatchSocketConnect();
|
||||
|
||||
store.do("loader.hide");
|
||||
|
|
@ -96,6 +99,107 @@ app.addSetter("hash.update", (data) => {
|
|||
}
|
||||
});
|
||||
|
||||
app.addSetter("app.uploadDroppedFiles", async (data, evt) => {
|
||||
|
||||
let boardId = store.data.board.id;
|
||||
|
||||
const supportedTypes = ["image/jpeg","image/png","image/webp"];
|
||||
|
||||
if ( boardId ){
|
||||
let hasFiles = event.dataTransfer.types.find(i => i == "Files") == "Files";
|
||||
if ( hasFiles ){
|
||||
|
||||
if ( evt.dataTransfer.items ){
|
||||
|
||||
let files = [];
|
||||
|
||||
for ( let i = 0; i < evt.dataTransfer.items.length; ++i ){
|
||||
if ( evt.dataTransfer.items[i].kind === "file" ){
|
||||
let file = evt.dataTransfer.items[i].getAsFile();
|
||||
|
||||
if ( !supportedTypes.includes(file.type)){
|
||||
|
||||
window.alert("Unsupported file type. JPEG, PNG, and WebP images are supported.");
|
||||
console.log("Unsupported file type: " + file.type);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// check size
|
||||
if ( file.size >= 26214400 ){
|
||||
window.alert("File size exceeds the 25MB limit.");
|
||||
console.log("File size exceeds the 25MB limit. size=" + file.size);
|
||||
document.getElementById("fileInput").value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
files.push(file);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Number of files=" + files.length);
|
||||
|
||||
for ( let i = 0; i < files.length; ++i ){
|
||||
|
||||
data.dropUploadMessage = `Uploading ${i+1} of ${files.length}`;
|
||||
|
||||
try {
|
||||
let newPin = await multipartUpload(files[i], boardId);
|
||||
if ( data.board && data.board.id == boardId ){
|
||||
data.board.pins.push(newPin);
|
||||
}
|
||||
} catch (e){
|
||||
window.alert("Error uploading images.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
data.dropUploadMessage = null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function PostException(statusCode, errorMessage){
|
||||
this.statusCode = statusCode;
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
async function multipartUpload(file, boardId, newBoardName, siteUrl, description){
|
||||
console.log("attempting multipart upload");
|
||||
let formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("boardId", boardId);
|
||||
if ( newBoardName ){
|
||||
formData.append("newBoardName", newBoardName);
|
||||
}
|
||||
if ( siteUrl ){
|
||||
formData.append("siteUrl", siteUrl);
|
||||
}
|
||||
if ( description ){
|
||||
formData.append("description", description);
|
||||
}
|
||||
|
||||
let res = await fetch("./multiup", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
headers: {
|
||||
"x-csrf-token": window.csrfToken
|
||||
}
|
||||
});
|
||||
|
||||
if ( res.status == 200 ){
|
||||
return res.json();
|
||||
} else {
|
||||
console.error("error uploading status=" + res.status + " body=" + await res.text());
|
||||
throw new PostException(res.status);
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchSocketConnect(){
|
||||
window.dispatchEvent(new CustomEvent("socket-connect"));
|
||||
}
|
||||
|
|
@ -121,7 +225,8 @@ let store = new Reef.Store({
|
|||
previewImageUrl: null,
|
||||
siteUrl: "",
|
||||
description: "",
|
||||
saveInProgress: false
|
||||
saveInProgress: false,
|
||||
didYouKnowDragAndDropMessageDisabled: window.localStorage.addPinModal_didYouKnowDragAndDropMessageDisabled == "true" || false
|
||||
},
|
||||
pinZoomModal: {
|
||||
active: false,
|
||||
|
|
@ -167,6 +272,23 @@ const appComponent = new Reef("#app", {
|
|||
<div id="editBoardModal"></div>
|
||||
<div id="aboutModal"></div>
|
||||
<div id="editPinModal"></div>
|
||||
<div id="dragAndDropModal" class="modal">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-content">
|
||||
<div class="box">
|
||||
<div class="m-6">drop to add pins</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal ${data.dropUploadMessage ? 'is-active' : ''}">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-content has-text-centered">
|
||||
<div class="box">
|
||||
<div class="button is-text is-large is-loading"></div>
|
||||
<div>${data.dropUploadMessage}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
//<div id="loader" class="button is-text ${data.loading ? 'is-loading' : ''}"></div>
|
||||
}
|
||||
|
|
@ -290,4 +412,55 @@ document.addEventListener("visibilitychange", async () => {
|
|||
}
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
window.dragInProgress = false;
|
||||
|
||||
window.ondragover = (evt) => {
|
||||
|
||||
let data = store.data;
|
||||
|
||||
if ( !data.board || data.addPinModal.active || data.editPinModal.active || data.aboutModal.active || data.pinZoomModal.active ){
|
||||
return;
|
||||
}
|
||||
|
||||
evt.preventDefault();
|
||||
|
||||
let hasFiles = event.dataTransfer.types.find(i => i == "Files") == "Files";
|
||||
if ( hasFiles ){
|
||||
window.dragInProgress = true;
|
||||
document.getElementById("dragAndDropModal").classList.add("is-active");
|
||||
}
|
||||
};
|
||||
|
||||
window.ondragleave = (evt) => {
|
||||
if ( evt.x == 0 && evt.y == 0 ){
|
||||
document.getElementById("dragAndDropModal").classList.remove("is-active");
|
||||
window.dragInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
window.ondrop = async (evt) => {
|
||||
|
||||
if ( window.dragInProgress ){
|
||||
evt.preventDefault();
|
||||
|
||||
document.getElementById("dragAndDropModal").classList.remove("is-active");
|
||||
|
||||
let hasFiles = event.dataTransfer.types.find(i => i == "Files") == "Files";
|
||||
if ( hasFiles ){
|
||||
store.do("app.uploadDroppedFiles", evt);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -437,4 +437,15 @@ body.socketConnected #socketConnected {
|
|||
.is-touch .navbar.is-light .navbar-brand .navbar-link:hover,
|
||||
.is-touch .navbar.is-light .navbar-brand .navbar-link.is-active {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
|
||||
#dragAndDropModal .modal-background{
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
#dragAndDropModal .box {
|
||||
background: none;
|
||||
border: 3px dashed #000;
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -21,9 +21,18 @@ app.addSetter('addPinModal.close', (data) => {
|
|||
data.addPinModal.description = "";
|
||||
data.addPinModal.newBoardName = "";
|
||||
data.addPinModal.saveInProgress = false;
|
||||
data.addPinModal.uploadFile = null;
|
||||
|
||||
|
||||
// weird hack to pick up whether it redraws or not...
|
||||
let fileInput = document.getElementById("fileInput");
|
||||
if ( fileInput ){
|
||||
fileInput.value = "";
|
||||
}
|
||||
});
|
||||
|
||||
app.addSetter('addPinModal.updatePreview', (data) => {
|
||||
|
||||
if ( data.addPinModal.imageUrl.startsWith("http") ){
|
||||
( async() => {
|
||||
let res = await fetch(data.addPinModal.imageUrl, {
|
||||
|
|
@ -38,6 +47,7 @@ app.addSetter('addPinModal.updatePreview', (data) => {
|
|||
} else {
|
||||
data.addPinModal.previewImageUrl = null;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
app.addSetter('addPinModal.save', async (data) => {
|
||||
|
|
@ -48,37 +58,64 @@ app.addSetter('addPinModal.save', async (data) => {
|
|||
|
||||
let boardId = data.addPinModal.boardId;
|
||||
|
||||
let postData = {
|
||||
boardId: boardId,
|
||||
newBoardName: data.addPinModal.newBoardName,
|
||||
imageUrl: data.addPinModal.imageUrl,
|
||||
siteUrl: data.addPinModal.siteUrl,
|
||||
description: data.addPinModal.description
|
||||
};
|
||||
|
||||
let res = await fetch('api/pins', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': "application/json"
|
||||
},
|
||||
body: JSON.stringify(postData)
|
||||
});
|
||||
|
||||
if ( res.status == 200 ){
|
||||
|
||||
let body = await res.json();
|
||||
if ( data.board && data.board.id == boardId ){
|
||||
data.board.pins.push(body);
|
||||
if ( data.addPinModal.uploadFile ){
|
||||
// do file upload
|
||||
|
||||
console.log("attempting multipart file uploading");
|
||||
|
||||
try {
|
||||
let newPin = await multipartUpload(data.addPinModal.uploadFile, boardId, data.addPinModal.newBoardName, data.addPinModal.siteUrl, data.addPinModal.description);
|
||||
if ( data.board && data.board.id == boardId ){
|
||||
data.board.pins.push(newPin);
|
||||
}
|
||||
|
||||
window.localStorage.addPinLastBoardId = boardId;
|
||||
store.do("addPinModal.close");
|
||||
|
||||
if ( boardId == "new" && !window.socketConnected ){
|
||||
store.do("load.boards");
|
||||
}
|
||||
} catch (e){
|
||||
window.alert("Error uploading images.");
|
||||
console.error("Error uploading images: ", e);
|
||||
}
|
||||
|
||||
window.localStorage.addPinLastBoardId = boardId;
|
||||
store.do("addPinModal.close");
|
||||
} else {
|
||||
|
||||
// if we don't have a listening socket, we need to trigger our own update
|
||||
if ( boardId == "new" && !window.socketConnected ){
|
||||
store.do("load.boards");
|
||||
let postData = {
|
||||
boardId: boardId,
|
||||
newBoardName: data.addPinModal.newBoardName,
|
||||
imageUrl: data.addPinModal.imageUrl,
|
||||
siteUrl: data.addPinModal.siteUrl,
|
||||
description: data.addPinModal.description
|
||||
};
|
||||
|
||||
let res = await fetch('api/pins', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': "application/json",
|
||||
"x-csrf-token" : window.csrfToken
|
||||
},
|
||||
body: JSON.stringify(postData)
|
||||
});
|
||||
|
||||
if ( res.status == 200 ){
|
||||
|
||||
let body = await res.json();
|
||||
if ( data.board && data.board.id == boardId ){
|
||||
data.board.pins.push(body);
|
||||
}
|
||||
|
||||
window.localStorage.addPinLastBoardId = boardId;
|
||||
store.do("addPinModal.close");
|
||||
|
||||
// if we don't have a listening socket, we need to trigger our own update
|
||||
if ( boardId == "new" && !window.socketConnected ){
|
||||
store.do("load.boards");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
store.do("loader.hide");
|
||||
|
||||
|
|
@ -101,6 +138,56 @@ app.addGetter('addPinModal.isValid', (data) => {
|
|||
return true;
|
||||
});
|
||||
|
||||
app.addSetter('addPinModal.fileChosen', (data, target) => {
|
||||
|
||||
let file = target.files[0];
|
||||
|
||||
const supportedTypes = ["image/jpeg","image/png","image/webp"];
|
||||
|
||||
// check type
|
||||
if ( !supportedTypes.includes(file.type)){
|
||||
|
||||
window.alert("Unsupported file type. JPEG, PNG and WebP images are supported.");
|
||||
console.log("Unsupported file type: " + file.type);
|
||||
|
||||
document.getElementById("fileInput").value = "";
|
||||
|
||||
}
|
||||
|
||||
// check size
|
||||
if ( file.size >= 26214400 ){
|
||||
window.alert("File size exceeds the 25MB limit.");
|
||||
console.log("File size exceeds the 25MB limit. size=" + file.size);
|
||||
document.getElementById("fileInput").value = "";
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
let imageUrl = window.URL.createObjectURL(file);
|
||||
|
||||
data.addPinModal.uploadFile = file;
|
||||
data.addPinModal.previewImageUrl = imageUrl;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
app.addSetter('addPinModal.removeUploadFile', (data, target) => {
|
||||
data.addPinModal.uploadFile = null;
|
||||
data.addPinModal.previewImageUrl = null;
|
||||
return false;
|
||||
});
|
||||
|
||||
app.addSetter('addPinModal.disableDidYouKnowDragAndDropMessage', (data) => {
|
||||
data.addPinModal.didYouKnowDragAndDropMessageDisabled = true;
|
||||
window.localStorage.addPinModal_didYouKnowDragAndDropMessageDisabled = "true";
|
||||
});
|
||||
|
||||
document.addEventListener("input", (evt) => {
|
||||
if ( evt.target.id == "fileInput" ){
|
||||
store.do("addPinModal.fileChosen", evt.target);
|
||||
}
|
||||
});
|
||||
|
||||
app.addComponent('addPinModal', (store) => { return new Reef("#addPinModal", {
|
||||
|
||||
store: store,
|
||||
|
|
@ -109,8 +196,11 @@ app.addComponent('addPinModal', (store) => { return new Reef("#addPinModal", {
|
|||
let imagePlaceholder = 'data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22300%22%20height%3D%22300%22%3E%3Crect%20x%3D%222%22%20y%3D%222%22%20width%3D%22300%22%20height%3D%22300%22%20style%3D%22fill%3A%23dedede%3B%22%2F%3E%3Ctext%20x%3D%2250%25%22%20y%3D%2250%25%22%20font-size%3D%2218%22%20text-anchor%3D%22middle%22%20alignment-baseline%3D%22middle%22%20font-family%3D%22monospace%2C%20sans-serif%22%20fill%3D%22%23555555%22%3Eimage%3C%2Ftext%3E%3C%2Fsvg%3E';
|
||||
|
||||
let options = "";
|
||||
|
||||
for ( let i = 0; i < data.boards.length; ++i ){
|
||||
options += `<option value="${data.boards[i].id}">${data.boards[i].name}</option>`;
|
||||
if ( data.showHiddenBoards || !data.boards[i].hidden ){
|
||||
options += `<option value="${data.boards[i].id}">${data.boards[i].name}</option>`;
|
||||
}
|
||||
}
|
||||
|
||||
let newBoardField = '';
|
||||
|
|
@ -134,6 +224,17 @@ app.addComponent('addPinModal', (store) => { return new Reef("#addPinModal", {
|
|||
<button class="delete" aria-label="close" data-onclick="addPinModal.close"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
${ !data.addPinModal.didYouKnowDragAndDropMessageDisabled ? /*html*/`
|
||||
<div class="message is-success">
|
||||
<div class="message-header">
|
||||
<p>Did you know?</p>
|
||||
<button type="button" class="delete" aria-label="delete" label="Don't show again" data-onclick="addPinModal.disableDidYouKnowDragAndDropMessage"></button>
|
||||
</div>
|
||||
<div class="message-body">
|
||||
Did you know? You can now upload files to an existing board by drag-and-drop!
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="add-pin-flex">
|
||||
<div class="add-pin-flex-left">
|
||||
<img id="add-pin-modal-img" src="${data.addPinModal.previewImageUrl ? data.addPinModal.previewImageUrl : imagePlaceholder}" />
|
||||
|
|
@ -141,6 +242,8 @@ app.addComponent('addPinModal', (store) => { return new Reef("#addPinModal", {
|
|||
<div class="add-pin-flex-right">
|
||||
<form>
|
||||
|
||||
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Board</label>
|
||||
<div class="select">
|
||||
|
|
@ -153,13 +256,31 @@ app.addComponent('addPinModal', (store) => { return new Reef("#addPinModal", {
|
|||
|
||||
${newBoardField}
|
||||
|
||||
${ data.addPinModal.uploadFile ? /*html*/`
|
||||
<div class="field">
|
||||
<label class="label">Image File</label>
|
||||
<div class="control">
|
||||
<span>${data.addPinModal.uploadFile.name}</span>
|
||||
<button type="button" class="delete" aria-label="remove" data-onclick="addPinModal.removeUploadFile"></button>
|
||||
</div>
|
||||
</div>
|
||||
` :
|
||||
/*html*/`
|
||||
<div class="field">
|
||||
<label class="label">Image Url</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" data-bind="addPinModal.imageUrl" data-onblur="addPinModal.updatePreview"/>
|
||||
<input class="input" type="text" data-bind="addPinModal.imageUrl" data-onblur="addPinModal.updatePreview" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<lable class="label">or choose file</lable>
|
||||
<div class="control">
|
||||
<input class="input" type="file" id="fileInput" />
|
||||
</div>
|
||||
</div>
|
||||
`}
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Website Url</label>
|
||||
<div class="control">
|
||||
|
|
|
|||
|
|
@ -32,7 +32,10 @@ app.addSetter("brickwall.deletePin", async (data) => {
|
|||
closeLightGallery();
|
||||
|
||||
let res = await fetch(`api/pins/${pinId}`, {
|
||||
method: 'DELETE'
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'x-csrf-token': window.csrfToken
|
||||
}
|
||||
});
|
||||
|
||||
if ( res.status == 200 ){
|
||||
|
|
@ -63,7 +66,7 @@ async function iosShare(){
|
|||
let index = getLightGalleryIndex();
|
||||
let pin = data.board.pins[index];
|
||||
|
||||
let result = await fetch("/api/pins/" + pin.id + "/otl", {method: 'POST'});
|
||||
let result = await fetch("/api/pins/" + pin.id + "/otl", {method: 'POST', headers: {'x-csrf-token':window.csrfToken}});
|
||||
let obj = await result.json();
|
||||
let t = obj.t;
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ app.addSetter('editBoardModal.save', async (data) => {
|
|||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-csrf-token': window.csrfToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: name,
|
||||
|
|
@ -70,7 +71,10 @@ app.addSetter('editBoardModal.delete', async (data) => {
|
|||
|
||||
|
||||
let res = await fetch(`/api/boards/${boardId}`, {
|
||||
method: 'DELETE'
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'x-csrf-token':window.csrfToken
|
||||
}
|
||||
});
|
||||
|
||||
if ( res.status == 200 ){
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ app.addGetter('editPinModal.isValid', (data) => {
|
|||
|
||||
let pin = getPinById(data.editPinModal.pin.id);
|
||||
|
||||
if ( pin.siteUrl == data.editPinModal.pin.siteUrl &&
|
||||
if ( pin && pin.siteUrl == data.editPinModal.pin.siteUrl &&
|
||||
pin.description == data.editPinModal.pin.description &&
|
||||
pin.boardId == data.editPinModal.pin.boardId ){
|
||||
return false;
|
||||
|
|
@ -56,7 +56,10 @@ app.addSetter("editPinModal.save", async (data) => {
|
|||
// TODO: make a helper method
|
||||
let res = await fetch("/api/boards", {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-csrf-token': window.csrfToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"name": data.editPinModal.newBoardName
|
||||
})
|
||||
|
|
@ -79,7 +82,8 @@ app.addSetter("editPinModal.save", async (data) => {
|
|||
let res = await fetch('api/pins/' + pin.id, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'x-csrf-token': window.csrfToken
|
||||
},
|
||||
body: JSON.stringify(postData)
|
||||
});
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ app.addComponent('navbar', (store) => { return new Reef("#navbar", {
|
|||
}
|
||||
|
||||
let settingsItem = "";
|
||||
if (data.user.admin == 1){
|
||||
if (data.user && data.user.admin == 1){
|
||||
settingsItem = `
|
||||
<a class="navbar-item has-text-right" href="./settings">
|
||||
<span>tinypin settings</span>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -146,6 +146,7 @@ Reef.databind = function(reef){
|
|||
} else {
|
||||
elem.checked = false;
|
||||
}
|
||||
|
||||
} else {
|
||||
elem.value = val;
|
||||
}
|
||||
|
|
@ -170,7 +171,7 @@ Reef.databind = function(reef){
|
|||
}
|
||||
|
||||
// multiple selects need special handling
|
||||
if ( target.tagName == 'SELECT' && target.matches("[multiple]") ){
|
||||
else if ( target.tagName == 'SELECT' && target.matches("[multiple]") ){
|
||||
val = [];
|
||||
let options = target.querySelectorAll("option");
|
||||
for ( let i = 0; i < options.length; ++i ){
|
||||
|
|
|
|||
|
|
@ -58,7 +58,11 @@ function getPinIndexById(id){
|
|||
}
|
||||
|
||||
function getPinById(id){
|
||||
return store.data.board.pins[getPinIndexById(id)];
|
||||
try{
|
||||
return store.data.board.pins[getPinIndexById(id)];
|
||||
} catch (e){
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function sleep(ms){ return new Promise((resolve) => setTimeout(resolve, ms)); }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
#!/bin/sh
|
||||
|
||||
VERSION=$(git rev-parse --verify --short HEAD)
|
||||
|
||||
docker build \
|
||||
-t slynn1324/tinypin \
|
||||
--build-arg VERSION=$(git rev-parse --verify --short HEAD) \
|
||||
--build-arg VERSION=$VERSION \
|
||||
.
|
||||
|
||||
|
|
|
|||
80
firefox-extension/background.js
Normal file
80
firefox-extension/background.js
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* Returns a handler which will open a new window when activated.
|
||||
*/
|
||||
function getClickHandler() {
|
||||
return function(info, tab) {
|
||||
|
||||
if ( !info.srcUrl.startsWith('http') ){
|
||||
window.alert("Image source is not a URL.");
|
||||
return;
|
||||
}
|
||||
|
||||
var w = 700;
|
||||
var h = 800;
|
||||
var left = (screen.width/2)-(w/2);
|
||||
var top = (screen.height/2)-(h/2);
|
||||
|
||||
let s = "";
|
||||
if ( info.linkUrl ){
|
||||
s = info.linkUrl;
|
||||
|
||||
// strip the google images redirect
|
||||
if ( s.startsWith("https://www.google.com/url?") ){
|
||||
let parts = s.split("?");
|
||||
|
||||
if ( parts.length == 2 ){
|
||||
|
||||
let params = parts[1].split("&");
|
||||
|
||||
for( let i = 0; i < params.length; ++i ){
|
||||
let kv = params[i].split("=");
|
||||
|
||||
if ( kv.length == 2 ){
|
||||
if ( kv[0] == "url" ){
|
||||
s = decodeURIComponent(kv[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s = encodeURIComponent(s);
|
||||
|
||||
} else {
|
||||
s = encodeURIComponent(info.pageUrl);
|
||||
}
|
||||
|
||||
|
||||
|
||||
var q = "i=" + encodeURIComponent(info.srcUrl) + "&s=" + s;
|
||||
|
||||
|
||||
browser.storage.sync.get({
|
||||
server: 'http://localhost:3000'
|
||||
}, function(items){
|
||||
let server = items.server;
|
||||
|
||||
if ( !server.endsWith('/') ){
|
||||
server += '/';
|
||||
}
|
||||
|
||||
var url = server + 'addpin.html#' + q;
|
||||
|
||||
// Create a new window to the info page.
|
||||
// browser.windows.create({ url: url, width: 520, height: 660 });
|
||||
browser.windows.create({ url: url, width: w, height: h, left: left, top: top, type: 'popup' });
|
||||
});
|
||||
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a context menu which will only show up for images.
|
||||
*/
|
||||
browser.contextMenus.create({
|
||||
"title" : "add to tinypin",
|
||||
"type" : "normal",
|
||||
"contexts" : ["image"],
|
||||
"onclick" : getClickHandler()
|
||||
});
|
||||
7704
firefox-extension/bulma-custom.css
Normal file
7704
firefox-extension/bulma-custom.css
Normal file
File diff suppressed because it is too large
Load diff
BIN
firefox-extension/icon128-blue.png
Normal file
BIN
firefox-extension/icon128-blue.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
BIN
firefox-extension/icon128.png
Normal file
BIN
firefox-extension/icon128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
firefox-extension/icon16.png
Normal file
BIN
firefox-extension/icon16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 224 B |
BIN
firefox-extension/icon32.png
Normal file
BIN
firefox-extension/icon32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 329 B |
BIN
firefox-extension/icon48.png
Normal file
BIN
firefox-extension/icon48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 536 B |
36
firefox-extension/manifest.json
Normal file
36
firefox-extension/manifest.json
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "add to tinypin",
|
||||
"version": "1.0.0",
|
||||
"description": "add to tinypin context menu plugin",
|
||||
"manifest_version": 2,
|
||||
"background" : {
|
||||
"scripts": ["background.js"],
|
||||
"persistent": true
|
||||
},
|
||||
"options_ui" : {
|
||||
"page": "options.html",
|
||||
"open_in_tab": false
|
||||
},
|
||||
"permissions" : [
|
||||
"contextMenus",
|
||||
"storage"
|
||||
],
|
||||
"icons": {
|
||||
"16" : "icon16.png",
|
||||
"32" : "icon32.png",
|
||||
"48" : "icon48.png",
|
||||
"128" : "icon128.png"
|
||||
},
|
||||
"browser_action" :{
|
||||
"default_title": "add to tinypin",
|
||||
"default_icon" :{
|
||||
"16": "icon16.png",
|
||||
"32": "icon32.png"
|
||||
}
|
||||
},
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "@slynn1324"
|
||||
}
|
||||
}
|
||||
}
|
||||
32
firefox-extension/options.html
Normal file
32
firefox-extension/options.html
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>tinypin options</title>
|
||||
<link rel="stylesheet" href="./bulma-custom.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="secton">
|
||||
|
||||
<div class="content" style="margin: 10px;">
|
||||
|
||||
|
||||
<div class="field">
|
||||
<label class="label">tinypin server url</label>
|
||||
<div class="control">
|
||||
<input class="input" id="server" type="text">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="button is-success" id="save">Save</button>
|
||||
<span id="status" style="line-height: 2.5em; color: #3273dc;"></span>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="options.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
25
firefox-extension/options.js
Normal file
25
firefox-extension/options.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
function restoreOptions(){
|
||||
browser.storage.sync.get({
|
||||
server: 'http://localhost:3000'
|
||||
}, function(items){
|
||||
document.getElementById('server').value = items.server;
|
||||
});
|
||||
}
|
||||
|
||||
function saveOptions(){
|
||||
let server = document.getElementById('server').value;
|
||||
|
||||
browser.storage.sync.set({
|
||||
server: server
|
||||
}, function(){
|
||||
let status = document.getElementById('status');
|
||||
status.innerText = 'Options saved.';
|
||||
setTimeout(function(){
|
||||
status.innerText = '';
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', restoreOptions);
|
||||
document.getElementById('save').addEventListener('click', saveOptions);
|
||||
2529
package-lock.json
generated
2529
package-lock.json
generated
File diff suppressed because it is too large
Load diff
16
package.json
16
package.json
|
|
@ -9,14 +9,18 @@
|
|||
"author": "slynn1324",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^7.1.2",
|
||||
"better-sqlite3": "^7.4.3",
|
||||
"body-parser": "^1.19.0",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"eta": "^1.12.1",
|
||||
"express": "^4.17.1",
|
||||
"express-ws": "^4.0.0",
|
||||
"csurf": "^1.11.0",
|
||||
"eta": "^1.12.3",
|
||||
"express": "^4.21.2",
|
||||
"express-rate-limit": "^5.4.0",
|
||||
"express-ws": "^5.0.2",
|
||||
"multer": "^1.4.3",
|
||||
"node-fetch": "^2.6.1",
|
||||
"sharp": "^0.27.0",
|
||||
"yargs": "^16.2.0"
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"sharp": "^0.29.1",
|
||||
"yargs": "^17.2.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,36 @@ function sendAuthCookie(res, c){
|
|||
res.cookie('s', tokenUtils.encrypt(c), {maxAge: 315569520000}); // 10 years
|
||||
}
|
||||
|
||||
function maybeGetUser(req){
|
||||
|
||||
if ( !req.cookies ){
|
||||
return null;
|
||||
}
|
||||
|
||||
// if we made it this far, we're eady to check for the cookie
|
||||
let s = req.cookies.s;
|
||||
|
||||
// TODO: should probably check if the user's access has been revoked,
|
||||
// but we currently don't allow deleting users anyway. A key rotation would
|
||||
// be the other solution, but that would log out all users and require new tokens
|
||||
// to be created.
|
||||
if ( s ){
|
||||
try {
|
||||
s = tokenUtils.decrypt(s);
|
||||
if ( s.i && s.u ){
|
||||
return {
|
||||
id: s.i,
|
||||
name: s.u
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(`error parsing cookie: `, err);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = async (req, res, next) => {
|
||||
|
||||
// we will also accept the auth token in the x-api-key header
|
||||
|
|
@ -60,9 +90,16 @@ module.exports = async (req, res, next) => {
|
|||
next();
|
||||
return;
|
||||
} if ( req.method == "GET" && req.originalUrl == "/login" ){
|
||||
|
||||
|
||||
if ( maybeGetUser(req) ){
|
||||
res.redirect("./");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("login");
|
||||
// res.type("html").sendFile(path.resolve('./templates/login.html'));
|
||||
res.render("login", { registerEnabled: dao.getProperty("registerEnabled") });
|
||||
res.render("login", { registerEnabled: dao.getProperty("registerEnabled"), csrfToken: req.csrfToken() });
|
||||
return;
|
||||
} else if ( req.method == "POST" && req.originalUrl == "/login" ){
|
||||
let username = req.body.username;
|
||||
|
|
@ -99,7 +136,7 @@ module.exports = async (req, res, next) => {
|
|||
return;
|
||||
}
|
||||
|
||||
res.render("register", {});
|
||||
res.render("register", { csrfToken: req.csrfToken() });
|
||||
return;
|
||||
} else if ( req.method == "POST" && req.originalUrl == "/register" ){
|
||||
|
||||
|
|
@ -135,26 +172,27 @@ module.exports = async (req, res, next) => {
|
|||
return;
|
||||
}
|
||||
|
||||
// if we made it this far, we're eady to check for the cookie
|
||||
let s = req.cookies.s;
|
||||
// // if we made it this far, we're eady to check for the cookie
|
||||
// let s = req.cookies.s;
|
||||
|
||||
// TODO: should probably check if the user's access has been revoked,
|
||||
// but we currently don't allow deleting users anyway. A key rotation would
|
||||
// be the other solution, but that would log out all users and require new tokens
|
||||
// to be created.
|
||||
if ( s ){
|
||||
try {
|
||||
s = tokenUtils.decrypt(s);
|
||||
if ( s.i && s.u ){
|
||||
req.user = {
|
||||
id: s.i,
|
||||
name: s.u
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`error parsing cookie: `, err);
|
||||
}
|
||||
}
|
||||
// // TODO: should probably check if the user's access has been revoked,
|
||||
// // but we currently don't allow deleting users anyway. A key rotation would
|
||||
// // be the other solution, but that would log out all users and require new tokens
|
||||
// // to be created.
|
||||
// if ( s ){
|
||||
// try {
|
||||
// s = tokenUtils.decrypt(s);
|
||||
// if ( s.i && s.u ){
|
||||
// req.user = {
|
||||
// id: s.i,
|
||||
// name: s.u
|
||||
// }
|
||||
// }
|
||||
// } catch (err) {
|
||||
// console.error(`error parsing cookie: `, err);
|
||||
// }
|
||||
// }
|
||||
req.user = maybeGetUser(req);
|
||||
|
||||
if ( !req.user ){
|
||||
res.redirect("/login");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
const yargs = require('yargs');
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const multer = require("multer")
|
||||
const RateLimit = require("express-rate-limit");
|
||||
const path = require('path');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const tokenUtil = require('./token-utils.js');
|
||||
|
|
@ -9,6 +11,20 @@ const conf = require("./conf.js");
|
|||
const imageUtils = require('./image-utils.js');
|
||||
var eta = require("eta");
|
||||
const tokenUtils = require('./token-utils.js');
|
||||
const csrf = require("csurf");
|
||||
const sanitizeFilename = require("sanitize-filename");
|
||||
|
||||
// consider using temp files, but we're going to limit the size so should be ok
|
||||
const upload = multer({storage:multer.memoryStorage(), limits: {fileSize: 26214400, files: 1}}); // 1 - 25MB file
|
||||
|
||||
// enable a rate limit so we can't swamp the server/filesystem -- 100 per second here. Pretty fast because we're likely
|
||||
// running locally and want to load images fast.
|
||||
// GitHub CodeQL - js/missing-rate-limiting
|
||||
const rateLimiter = new RateLimit({
|
||||
windowMs: 1000,
|
||||
max: 100
|
||||
});
|
||||
|
||||
|
||||
module.exports = async () => {
|
||||
|
||||
|
|
@ -84,13 +100,26 @@ module.exports = async () => {
|
|||
app.set("views", "./templates")
|
||||
const expressWs = require('express-ws')(app);
|
||||
|
||||
|
||||
app.use(rateLimiter); // rate limiting
|
||||
app.use(bodyParser.raw({type: 'image/jpeg', limit: '25mb'})); // accept image/jpeg files only
|
||||
app.use(bodyParser.urlencoded({ extended: false }))
|
||||
app.use(bodyParser.json());
|
||||
app.set('json spaces', 2);
|
||||
app.use(cookieParser());
|
||||
|
||||
// only appy csrf if we don't have an x-api-key header. The value of the x-api-key will be validated by the auth middleware
|
||||
app.use( (req,res,next) => {
|
||||
let apiKey = req.headers["x-api-key"];
|
||||
if ( apiKey ){
|
||||
next();
|
||||
} else {
|
||||
csrf({cookie:true})(req,res,next);
|
||||
}
|
||||
});
|
||||
|
||||
// // all other endpoints require csrf
|
||||
// app.use(csrf({cookie:true}));
|
||||
|
||||
// accept websocket connections. currently are parsing the userid from the path to
|
||||
// map the connections to only notify on changes from the same user.
|
||||
|
|
@ -141,7 +170,7 @@ module.exports = async () => {
|
|||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
res.send({name: user.username, id: user.id, admin: user.admin, version: VERSION});
|
||||
res.send({name: user.username, id: user.id, admin: user.admin, version: VERSION, csrf: req.csrfToken()});
|
||||
});
|
||||
|
||||
// list boards
|
||||
|
|
@ -223,7 +252,7 @@ module.exports = async () => {
|
|||
res.status(404).send(NOT_FOUND);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(`Error deleting board#${req.params.boardId}:`, err);
|
||||
console.log('Error deleting board# %s', req.params.boardId, err);
|
||||
res.status(500).send(SERVER_ERROR);
|
||||
}
|
||||
});
|
||||
|
|
@ -238,7 +267,7 @@ module.exports = async () => {
|
|||
res.status(404).send(NOT_FOUND);
|
||||
}
|
||||
} catch (err){
|
||||
console.error(`Error getting pin#${req.params.pinId}: ${err.message}`, err);
|
||||
console.error('Error getting pin# %s', req.params.pinId, err);
|
||||
res.status(500).send(SERVER_ERROR);
|
||||
}
|
||||
});
|
||||
|
|
@ -291,7 +320,7 @@ module.exports = async () => {
|
|||
res.status(404).send(NOT_FOUND);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(`Error updating pin#${req.params.pinId}`, err);
|
||||
console.log('Error updating pin# %s', req.params.pinId, err);
|
||||
res.status(500).send(SERVER_ERROR);
|
||||
}
|
||||
|
||||
|
|
@ -321,7 +350,7 @@ module.exports = async () => {
|
|||
}
|
||||
|
||||
} catch (err){
|
||||
console.log(`Error deleting pin#${req.params.pinId}`, err);
|
||||
console.log('Error deleting pin# %s', req.params.pinId, err);
|
||||
res.status(500).send(SERVER_ERROR);
|
||||
}
|
||||
});
|
||||
|
|
@ -340,10 +369,12 @@ module.exports = async () => {
|
|||
res.status(200).send({t: token});
|
||||
});
|
||||
|
||||
|
||||
// api method that are not subject to CSRF checks
|
||||
// handle raw uploads for pin creation
|
||||
app.post("/up", async (req, res) => {
|
||||
|
||||
try {
|
||||
|
||||
require("fs").writeFileSync("up.jpg", req.body);
|
||||
|
||||
// try to parse the image first... if this blows up we'll stop early
|
||||
|
|
@ -358,7 +389,7 @@ module.exports = async () => {
|
|||
board = dao.createBoard(req.user.id, boardName, 0);
|
||||
}
|
||||
|
||||
let pin = dao.createPin(req.user.id, board.id, null, null, null, null, image.original.height, image.original.width, image.thumbnail.height, image.thumbnailWidth);
|
||||
let pin = dao.createPin(req.user.id, board.id, null, null, null, null, image.original.height, image.original.width, image.thumbnail.height, image.thumbnail.height);
|
||||
|
||||
await imageUtils.saveImage(req.user.id, pin.id, image);
|
||||
|
||||
|
|
@ -371,6 +402,38 @@ module.exports = async () => {
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
// handle multipart uploads for pin creation
|
||||
app.post("/multiup", upload.single('file'), async(req, res) => {
|
||||
try {
|
||||
|
||||
let image = await imageUtils.processImage(req.file.buffer); // file.buffer only works with the Memory store for multer.
|
||||
|
||||
let boardId = req.body.boardId;
|
||||
|
||||
let board = null;
|
||||
|
||||
if ( boardId == "new" ){
|
||||
board = dao.createBoard(req.user.id, req.body.newBoardName, 0);
|
||||
} else {
|
||||
board = dao.getBoard(req.user.id, boardId);
|
||||
}
|
||||
|
||||
console.log(image);
|
||||
|
||||
let pin = dao.createPin(req.user.id, board.id, null, req.body.siteUrl, req.body.description, null, image.original.height, image.original.width, image.thumbnail.height, image.thumbnail.height);
|
||||
|
||||
await imageUtils.saveImage(req.user.id, pin.id, image);
|
||||
|
||||
broadcast(req.user.id, {updateBoard:board.id});
|
||||
res.status(200).send(pin);
|
||||
|
||||
} catch (err) {
|
||||
console.log(`Error creating pin via multipart upload`, err);
|
||||
res.status(500).send(SERVER_ERROR);
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/api/apikey", (req,res) => {
|
||||
let s = req.cookies['s'];
|
||||
console.log("s=" + s);
|
||||
|
|
@ -394,6 +457,7 @@ module.exports = async () => {
|
|||
}
|
||||
|
||||
res.render("settings", {
|
||||
csrfToken: req.csrfToken(),
|
||||
registerEnabled: registerEnabled,
|
||||
users: users,
|
||||
userId: req.user.id
|
||||
|
|
@ -441,8 +505,6 @@ module.exports = async () => {
|
|||
let password = req.body.password;
|
||||
let repeatPassword = req.body.repeatPassword;
|
||||
|
||||
console.log(`username: ${username} password: ${password} rp: ${repeatPassword}`);
|
||||
|
||||
if ( password != repeatPassword ){
|
||||
res.redirect("./settings#password-match")
|
||||
return;
|
||||
|
|
@ -454,7 +516,7 @@ module.exports = async () => {
|
|||
try{
|
||||
dao.createUser(username, 0, key, salt);
|
||||
} catch (err){
|
||||
console.log("error creating user " + username, err);
|
||||
console.log("error creating user %s", username, err);
|
||||
res.redirect("./settings#create-user-error");
|
||||
return;
|
||||
}
|
||||
|
|
@ -466,11 +528,17 @@ module.exports = async () => {
|
|||
|
||||
let uid = req.body.uid;
|
||||
|
||||
// uids must ONLY be integer numbers, to ensure that this is safe to use in the path below
|
||||
if ( !uid.match(/^[0-9]+$/) ){
|
||||
console.log("Invalid uid: %s", uid);
|
||||
res.redirect("./settings#delete-user-error");
|
||||
}
|
||||
|
||||
try {
|
||||
dao.deleteUser(uid);
|
||||
require("fs").rmdirSync(conf.getImagePath() + "/" + uid , { recursive: true });
|
||||
} catch (err){
|
||||
console.log("error deleting user " + uid, err);
|
||||
console.log("error deleting user %s", uid, err);
|
||||
res.redirect("./settings#delete-user-error");
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
<p class="modal-card-title">tinypin » log in</p>
|
||||
</header>
|
||||
<form method="post" action="./login">
|
||||
<input type="hidden" name="_csrf" value="<%= it.csrfToken %>" />
|
||||
<section class="modal-card-body">
|
||||
|
||||
<div class="field">
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
<p class="modal-card-title">tinypin » create account</p>
|
||||
</header>
|
||||
<form method="post" action="./register">
|
||||
<input type="hidden" name="_csrf" value="<%= it.csrfToken %>" />
|
||||
<section class="modal-card-body">
|
||||
|
||||
<div class="field">
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@
|
|||
<h1 style="border-bottom: 1px solid #eee;"><strong>Settings</strong></h1>
|
||||
<br />
|
||||
<form method="POST" action="./settings">
|
||||
<input type="hidden" name="_csrf" value="<%= it.csrfToken %>" />
|
||||
<input type="hidden" name="action" value="updateSettings">
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label is-normal">
|
||||
|
|
@ -101,7 +102,8 @@
|
|||
<br />
|
||||
|
||||
<form method="POST" action="./settings">
|
||||
<input type="hidden" name="action" value="updateUsers">
|
||||
<input type="hidden" name="_csrf" value="<%= it.csrfToken %>" />
|
||||
<input type="hidden" name="action" value="updateUsers" />
|
||||
<table class="table" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
@ -165,6 +167,7 @@
|
|||
<p class="modal-card-title">tinypin » create account</p>
|
||||
</header>
|
||||
<form method="post" action="./settings" onsubmit="return submitCreateUserForm()">
|
||||
<input type="hidden" name="_csrf" value="<%= it.csrfToken %>" />
|
||||
<input type="hidden" name="action" value="createUser" />
|
||||
<section class="modal-card-body">
|
||||
|
||||
|
|
@ -202,6 +205,7 @@
|
|||
</div>
|
||||
|
||||
<form id="deleteUserForm" action="./settings" method="POST">
|
||||
<input type="hidden" name="_csrf" value="<%= it.csrfToken %>" />
|
||||
<input type="hidden" name="action" value="deleteUser" />
|
||||
<input type="hidden" id="deleteUserUid" name="uid" value="" />
|
||||
</form>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue