|
|
@ -1,4 +1,4 @@ |
|
|
|
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3'); |
|
|
|
const { DeleteObjectCommand, PutObjectCommand, S3Client } = require('@aws-sdk/client-s3'); |
|
|
|
const cors = require('cors'); |
|
|
|
const crypto = require('crypto'); |
|
|
|
const didJwt = require('did-jwt'); |
|
|
@ -57,20 +57,13 @@ app.post('/image', uploadMulter.single('image'), async (req, res) => { |
|
|
|
return res.status(400).send(JSON.stringify({ success: false, message: 'No file uploaded.' })); |
|
|
|
} |
|
|
|
|
|
|
|
// Verify the JWT
|
|
|
|
try { |
|
|
|
const auth = req.headers.authorization; |
|
|
|
if (!auth || !auth.startsWith('Bearer ')) { |
|
|
|
return res.status(401).send(JSON.stringify({ success: false, message: 'Missing "Bearer JWT" in Authorization header.'})); |
|
|
|
} |
|
|
|
const jwt = auth.substring('Bearer '.length); |
|
|
|
const verified = await didJwt.verifyJWT(jwt, { resolver }); |
|
|
|
if (!verified.verified) { |
|
|
|
const errorTime = new Date().toISOString(); |
|
|
|
console.error(errorTime, 'Got invalid JWT in Authorization header:', verified); |
|
|
|
return res.status(401).send(JSON.stringify({ success: false, message: 'Got invalid JWT in Authorization header. See server logs at ' + errorTime })); |
|
|
|
const decoded = await decodeJwt(req, res) |
|
|
|
if (!decoded.success) { |
|
|
|
return decoded.result; |
|
|
|
} |
|
|
|
const issuerDid = verified.issuer; |
|
|
|
const issuerDid = decoded.issuerDid; |
|
|
|
const jwt = decoded.jwt; |
|
|
|
|
|
|
|
// Check the user's limits, first from the DB and then from the server
|
|
|
|
let limitPerWeek = await new Promise((resolve, reject) => { |
|
|
@ -122,7 +115,7 @@ app.post('/image', uploadMulter.single('image'), async (req, res) => { |
|
|
|
// check the user's claims so far this week
|
|
|
|
const startOfWeekDate = DateTime.utc().startOf('week') // luxon weeks start on Mondays
|
|
|
|
const startOfWeekString = startOfWeekDate.toISO() |
|
|
|
let imagesCount = await new Promise((resolve, reject) => { |
|
|
|
const imagesCount = await new Promise((resolve, reject) => { |
|
|
|
db.get( |
|
|
|
'SELECT COUNT(*) AS week_count FROM image WHERE did = ? AND time >= ?', |
|
|
|
[issuerDid, startOfWeekString], |
|
|
@ -151,6 +144,24 @@ app.post('/image', uploadMulter.single('image'), async (req, res) => { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
// look to see if this image already exists
|
|
|
|
const imageUrl = await new Promise((resolve, reject) => { |
|
|
|
db.get( |
|
|
|
'SELECT url FROM image WHERE final_file = ? and did = ?', |
|
|
|
[ fileName, issuerDid ], |
|
|
|
(dbErr, row) => { |
|
|
|
if (dbErr) { |
|
|
|
console.error(currentDate, 'Error getting image for user from database:', dbErr) |
|
|
|
// continue anyway
|
|
|
|
} |
|
|
|
resolve(row?.url); |
|
|
|
} |
|
|
|
); |
|
|
|
}); |
|
|
|
if (imageUrl) { |
|
|
|
return res.status(201).send(JSON.stringify({ success: true, url: imageUrl, message: 'This image already existed.' })); |
|
|
|
} |
|
|
|
|
|
|
|
// record the upload in the database
|
|
|
|
const currentDate = new Date().toISOString(); |
|
|
|
const localFile = reqFile.path.startsWith(uploadDir + '/') ? reqFile.path.substring(uploadDir.length + 1) : reqFile.path; |
|
|
@ -177,7 +188,7 @@ app.post('/image', uploadMulter.single('image'), async (req, res) => { |
|
|
|
); |
|
|
|
}); |
|
|
|
|
|
|
|
// send to AWS
|
|
|
|
// send to S3
|
|
|
|
const params = { |
|
|
|
Body: data, |
|
|
|
Bucket: bucketName, // S3 Bucket name
|
|
|
@ -189,9 +200,9 @@ app.post('/image', uploadMulter.single('image'), async (req, res) => { |
|
|
|
if (response.$metadata.httpStatusCode !== 200) { |
|
|
|
const errorTime = new Date().toISOString(); |
|
|
|
console.error(errorTime, "Error uploading to S3 with bad HTTP status, with metadata:", response.$metadata); |
|
|
|
res.status(500).send(JSON.stringify({ |
|
|
|
return res.status(500).send(JSON.stringify({ |
|
|
|
success: false, |
|
|
|
message: "Got bad status of " + response.$metadata.httpStatusCode + " from AWS. See server logs at " + errorTime |
|
|
|
message: "Got bad status of " + response.$metadata.httpStatusCode + " from S3. See server logs at " + errorTime |
|
|
|
})); |
|
|
|
} else { |
|
|
|
fs.rm(reqFile.path, (err) => { |
|
|
@ -199,12 +210,12 @@ app.post('/image', uploadMulter.single('image'), async (req, res) => { |
|
|
|
console.error("Error deleting temp file", reqFile.path, "with error (but continuing):", err); |
|
|
|
} |
|
|
|
}); |
|
|
|
res.send(JSON.stringify({success: true, url: finalUrl})); |
|
|
|
return res.status(200).send(JSON.stringify({success: true, url: finalUrl})); |
|
|
|
} |
|
|
|
} catch (uploadError) { |
|
|
|
const errorTime = new Date().toISOString(); |
|
|
|
console.error(errorTime, "Error uploading to S3:", uploadError); |
|
|
|
res.status(500).send(JSON.stringify({ |
|
|
|
return res.status(500).send(JSON.stringify({ |
|
|
|
success: false, |
|
|
|
message: "Got error uploading file. See server logs at " + errorTime + " Error Details: " + uploadError |
|
|
|
})); |
|
|
@ -220,6 +231,133 @@ app.post('/image', uploadMulter.single('image'), async (req, res) => { |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
/** |
|
|
|
* DELETE endpoint |
|
|
|
* returns { success: true } if successful |
|
|
|
* returns { success: false, message: string } if not successful |
|
|
|
*/ |
|
|
|
app.delete('/image/:url', async (req, res) => { |
|
|
|
try { |
|
|
|
const decoded = await decodeJwt(req, res) |
|
|
|
if (!decoded.success) { |
|
|
|
return decoded.result; |
|
|
|
} |
|
|
|
const issuerDid = decoded.issuerDid; |
|
|
|
|
|
|
|
const url = req.params.url; |
|
|
|
|
|
|
|
const currentDate = new Date().toISOString(); |
|
|
|
|
|
|
|
// look for file name of this image
|
|
|
|
const thisUserImageFile = await new Promise((resolve, reject) => { |
|
|
|
db.get( |
|
|
|
'SELECT final_file FROM image WHERE url = ? and did = ?', |
|
|
|
[ url, issuerDid ], |
|
|
|
(dbErr, row) => { |
|
|
|
if (dbErr) { |
|
|
|
console.error(currentDate, 'Error getting image for user from database:', dbErr) |
|
|
|
reject(dbErr); |
|
|
|
} |
|
|
|
resolve(row?.final_file); |
|
|
|
} |
|
|
|
); |
|
|
|
}); |
|
|
|
if (!thisUserImageFile) { |
|
|
|
return res.status(404).send(JSON.stringify({ success: false, message: 'No image entry found for user ' + issuerDid + ' & URL ' + url })); |
|
|
|
} |
|
|
|
|
|
|
|
// check if any other user recorded this image
|
|
|
|
const otherUserImage = await new Promise((resolve, reject) => { |
|
|
|
db.get( |
|
|
|
'SELECT did FROM image WHERE url = ? and did != ?', |
|
|
|
[ url, issuerDid ], |
|
|
|
(dbErr, row) => { |
|
|
|
if (dbErr) { |
|
|
|
console.error(currentDate, 'Error getting image for user from database:', dbErr) |
|
|
|
reject(dbErr); |
|
|
|
} |
|
|
|
resolve(row?.did); |
|
|
|
} |
|
|
|
); |
|
|
|
}); |
|
|
|
|
|
|
|
if (!otherUserImage) { |
|
|
|
// remove from S3 since nobody else recorded it
|
|
|
|
const params = { |
|
|
|
Bucket: bucketName, // S3 Bucket name
|
|
|
|
Key: thisUserImageFile, // File name to use in S3
|
|
|
|
}; |
|
|
|
const command = new DeleteObjectCommand(params); |
|
|
|
const response = await s3Client.send(command); |
|
|
|
if (response.$metadata.httpStatusCode !== 200 |
|
|
|
&& response.$metadata.httpStatusCode !== 202 |
|
|
|
&& response.$metadata.httpStatusCode !== 204) { |
|
|
|
const errorTime = new Date().toISOString(); |
|
|
|
console.error(errorTime, "Error deleting from S3 with bad HTTP status, with metadata:", response.$metadata); |
|
|
|
return res.status(500).send(JSON.stringify({ |
|
|
|
success: false, |
|
|
|
message: "Got bad status of " + response.$metadata.httpStatusCode + " from S3. See server logs at " + errorTime |
|
|
|
})); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// now remove the DB record for requesting user
|
|
|
|
await new Promise((resolve, reject) => { |
|
|
|
db.run( |
|
|
|
'DELETE FROM image where url = ? AND did = ?', |
|
|
|
[ url, issuerDid ], |
|
|
|
(dbErr) => { |
|
|
|
if (dbErr) { |
|
|
|
const currentDate = new Date().toISOString(); |
|
|
|
console.error(currentDate, "Error deleting record from", issuerDid, "into database:", dbErr); |
|
|
|
// don't continue because then we'll have storage we cannot track (and potentially limit)
|
|
|
|
reject(dbErr); |
|
|
|
} |
|
|
|
resolve(); |
|
|
|
} |
|
|
|
); |
|
|
|
}); |
|
|
|
return res.status(204).send(JSON.stringify({ success: true })); |
|
|
|
} catch (error) { |
|
|
|
const errorTime = new Date().toISOString(); |
|
|
|
console.error(errorTime, "Error processing image delete:", error); |
|
|
|
return res.status(500).send(JSON.stringify({ |
|
|
|
success: false, |
|
|
|
message: "Got error processing image delete. See server logs at " + errorTime + " Error Details: " + error |
|
|
|
})); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
/** |
|
|
|
* retrieve Bearer JWT from Authorization header and return either: |
|
|
|
* { success: true, issuerDid: string, jwt: string } |
|
|
|
* ... or ... |
|
|
|
* { success: false, result: HTTP send result } |
|
|
|
*/ |
|
|
|
async function decodeJwt(req, res) { |
|
|
|
const auth = req.headers.authorization; |
|
|
|
if (!auth || !auth.startsWith('Bearer ')) { |
|
|
|
return { |
|
|
|
success: false, |
|
|
|
result: res.status(401).send(JSON.stringify({success: false, message: 'Missing "Bearer JWT" in Authorization header.'})) |
|
|
|
}; |
|
|
|
} |
|
|
|
const jwt = auth.substring('Bearer '.length); |
|
|
|
const verified = await didJwt.verifyJWT(jwt, { resolver }); |
|
|
|
if (!verified.verified) { |
|
|
|
const errorTime = new Date().toISOString(); |
|
|
|
console.error(errorTime, 'Got invalid JWT in Authorization header:', verified); |
|
|
|
return { |
|
|
|
success: false, |
|
|
|
result: res.status(401).send(JSON.stringify({ |
|
|
|
success: false, |
|
|
|
message: 'Got invalid JWT in Authorization header. See server logs at ' + errorTime |
|
|
|
})) |
|
|
|
}; |
|
|
|
} |
|
|
|
return { success: true, issuerDid: verified.issuer, jwt: jwt }; |
|
|
|
} |
|
|
|
|
|
|
|
app.listen(port, () => { |
|
|
|
console.log(`Server running at http://localhost:${port}`); |
|
|
|
}); |
|
|
|