mirror of
https://github.com/slynn1324/tinypin.git
synced 2026-01-23 10:25:34 +00:00
Compare commits
9 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d645768dd0 | ||
|
|
6dc5f188e0 | ||
|
|
8af7f57094 | ||
|
|
62a61aed72 | ||
|
|
58c583d461 | ||
|
|
219f6d48b5 | ||
|
|
0cc19c48c0 | ||
|
|
fed01f948b | ||
|
|
0bccaef904 |
17 changed files with 8882 additions and 369 deletions
71
.github/workflows/codeql-analysis.yml
vendored
71
.github/workflows/codeql-analysis.yml
vendored
|
|
@ -1,71 +0,0 @@
|
||||||
# For most projects, this workflow file will not need changing; you simply need
|
|
||||||
# to commit it to your repository.
|
|
||||||
#
|
|
||||||
# You may wish to alter this file to override the set of languages analyzed,
|
|
||||||
# or to provide custom queries or build logic.
|
|
||||||
#
|
|
||||||
# ******** NOTE ********
|
|
||||||
# We have attempted to detect the languages in your repository. Please check
|
|
||||||
# the `language` matrix defined below to confirm you have the correct set of
|
|
||||||
# supported CodeQL languages.
|
|
||||||
#
|
|
||||||
name: "CodeQL"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ master ]
|
|
||||||
pull_request:
|
|
||||||
# The branches below must be a subset of the branches above
|
|
||||||
branches: [ master ]
|
|
||||||
schedule:
|
|
||||||
- cron: '40 2 * * 0'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
language: [ 'javascript' ]
|
|
||||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
|
||||||
# Learn more:
|
|
||||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v1
|
|
||||||
with:
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
||||||
# By default, queries listed here will override any specified in a config file.
|
|
||||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
|
||||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
|
||||||
- name: Autobuild
|
|
||||||
uses: github/codeql-action/autobuild@v1
|
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
|
||||||
# 📚 https://git.io/JvXDl
|
|
||||||
|
|
||||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
|
||||||
# and modify them (or add more) to build your code if your project
|
|
||||||
# uses a compiled language
|
|
||||||
|
|
||||||
#- run: |
|
|
||||||
# make bootstrap
|
|
||||||
# make release
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v1
|
|
||||||
|
|
@ -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) => {
|
app.addSetter("load.boards", async (data) => {
|
||||||
|
|
||||||
store.do("loader.show");
|
store.do("loader.show");
|
||||||
|
|
@ -138,7 +155,7 @@ app.addSetter('addPinModal.save', async (data) => {
|
||||||
if ( boardId == "new" ){
|
if ( boardId == "new" ){
|
||||||
let res = await fetch('api/boards', {
|
let res = await fetch('api/boards', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json', 'x-csrf-token': window.csrfToken },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
"name": data.addPinModal.newBoardName
|
"name": data.addPinModal.newBoardName
|
||||||
})
|
})
|
||||||
|
|
@ -161,7 +178,8 @@ app.addSetter('addPinModal.save', async (data) => {
|
||||||
let res = await fetch('api/pins', {
|
let res = await fetch('api/pins', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': "application/json"
|
'Content-Type': "application/json",
|
||||||
|
'x-csrf-token': window.csrfToken
|
||||||
},
|
},
|
||||||
body: JSON.stringify(postData)
|
body: JSON.stringify(postData)
|
||||||
});
|
});
|
||||||
|
|
@ -349,6 +367,8 @@ if ( target ){
|
||||||
|
|
||||||
store.do('hash.parse');
|
store.do('hash.parse');
|
||||||
|
|
||||||
|
store.do("load.user");
|
||||||
|
|
||||||
store.do('load.boards');
|
store.do('load.boards');
|
||||||
|
|
||||||
Reef.databind(appComponent);
|
Reef.databind(appComponent);
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,8 @@ app.addSetter("app.uploadDroppedFiles", async (data, evt) => {
|
||||||
|
|
||||||
let boardId = store.data.board.id;
|
let boardId = store.data.board.id;
|
||||||
|
|
||||||
|
const supportedTypes = ["image/jpeg","image/png","image/webp"];
|
||||||
|
|
||||||
if ( boardId ){
|
if ( boardId ){
|
||||||
let hasFiles = event.dataTransfer.types.find(i => i == "Files") == "Files";
|
let hasFiles = event.dataTransfer.types.find(i => i == "Files") == "Files";
|
||||||
if ( hasFiles ){
|
if ( hasFiles ){
|
||||||
|
|
@ -115,9 +117,9 @@ app.addSetter("app.uploadDroppedFiles", async (data, evt) => {
|
||||||
if ( evt.dataTransfer.items[i].kind === "file" ){
|
if ( evt.dataTransfer.items[i].kind === "file" ){
|
||||||
let file = evt.dataTransfer.items[i].getAsFile();
|
let file = evt.dataTransfer.items[i].getAsFile();
|
||||||
|
|
||||||
if ( file.type != "image/jpeg" && file.type != "image/png" ){
|
if ( !supportedTypes.includes(file.type)){
|
||||||
|
|
||||||
window.alert("Unsupported file type. JPEG and PNG images are supported.");
|
window.alert("Unsupported file type. JPEG, PNG, and WebP images are supported.");
|
||||||
console.log("Unsupported file type: " + file.type);
|
console.log("Unsupported file type: " + file.type);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -142,10 +142,12 @@ app.addSetter('addPinModal.fileChosen', (data, target) => {
|
||||||
|
|
||||||
let file = target.files[0];
|
let file = target.files[0];
|
||||||
|
|
||||||
|
const supportedTypes = ["image/jpeg","image/png","image/webp"];
|
||||||
|
|
||||||
// check type
|
// check type
|
||||||
if ( file.type != "image/jpeg" && file.type != "image/png" ){
|
if ( !supportedTypes.includes(file.type)){
|
||||||
|
|
||||||
window.alert("Unsupported file type. JPEG and PNG images are supported.");
|
window.alert("Unsupported file type. JPEG, PNG and WebP images are supported.");
|
||||||
console.log("Unsupported file type: " + file.type);
|
console.log("Unsupported file type: " + file.type);
|
||||||
|
|
||||||
document.getElementById("fileInput").value = "";
|
document.getElementById("fileInput").value = "";
|
||||||
|
|
|
||||||
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);
|
||||||
1189
package-lock.json
generated
1189
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -14,7 +14,7 @@
|
||||||
"cookie-parser": "^1.4.5",
|
"cookie-parser": "^1.4.5",
|
||||||
"csurf": "^1.11.0",
|
"csurf": "^1.11.0",
|
||||||
"eta": "^1.12.3",
|
"eta": "^1.12.3",
|
||||||
"express": "^4.17.1",
|
"express": "^4.21.2",
|
||||||
"express-rate-limit": "^5.4.0",
|
"express-rate-limit": "^5.4.0",
|
||||||
"express-ws": "^5.0.2",
|
"express-ws": "^5.0.2",
|
||||||
"multer": "^1.4.3",
|
"multer": "^1.4.3",
|
||||||
|
|
|
||||||
|
|
@ -108,45 +108,18 @@ module.exports = async () => {
|
||||||
app.set('json spaces', 2);
|
app.set('json spaces', 2);
|
||||||
app.use(cookieParser());
|
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) => {
|
||||||
// api method that are not subject to CSRF checks
|
let apiKey = req.headers["x-api-key"];
|
||||||
// handle raw uploads for pin creation
|
if ( apiKey ){
|
||||||
app.post("/up", async (req, res) => {
|
next();
|
||||||
|
} else {
|
||||||
try {
|
csrf({cookie:true})(req,res,next);
|
||||||
require("fs").writeFileSync("up.jpg", req.body);
|
|
||||||
|
|
||||||
// try to parse the image first... if this blows up we'll stop early
|
|
||||||
let image = await imageUtils.processImage(req.body);
|
|
||||||
|
|
||||||
let boardName = req.headers['board-name'].trim();
|
|
||||||
|
|
||||||
// get the board
|
|
||||||
let board = dao.findBoardByUserAndName(req.user.id, boardName);
|
|
||||||
|
|
||||||
if ( !board ){
|
|
||||||
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.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 uploading pin`, err);
|
|
||||||
res.status(500).send(SERVER_ERROR);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// // all other endpoints require csrf
|
||||||
// all other endpoints require csrf
|
// app.use(csrf({cookie:true}));
|
||||||
app.use(csrf({cookie:true}));
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// accept websocket connections. currently are parsing the userid from the path to
|
// accept websocket connections. currently are parsing the userid from the path to
|
||||||
// map the connections to only notify on changes from the same user.
|
// map the connections to only notify on changes from the same user.
|
||||||
|
|
@ -397,6 +370,37 @@ module.exports = async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// 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
|
||||||
|
let image = await imageUtils.processImage(req.body);
|
||||||
|
|
||||||
|
let boardName = req.headers['board-name'].trim();
|
||||||
|
|
||||||
|
// get the board
|
||||||
|
let board = dao.findBoardByUserAndName(req.user.id, boardName);
|
||||||
|
|
||||||
|
if ( !board ){
|
||||||
|
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.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 uploading pin`, err);
|
||||||
|
res.status(500).send(SERVER_ERROR);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// handle multipart uploads for pin creation
|
// handle multipart uploads for pin creation
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue