99 lines
2.8 KiB
JavaScript
99 lines
2.8 KiB
JavaScript
const { Message, ConditionCheck, Rental } = require("../models");
|
|
const { Op } = require("sequelize");
|
|
|
|
/**
|
|
* Service for verifying ownership/access to S3 files
|
|
* Used to authorize signed URL requests for private content
|
|
*/
|
|
class S3OwnershipService {
|
|
/**
|
|
* Extract file type from S3 key
|
|
* @param {string} key - S3 key like "messages/uuid.jpg"
|
|
* @returns {string|null} - File type or null if unknown
|
|
*/
|
|
static getFileTypeFromKey(key) {
|
|
if (!key) return null;
|
|
const folder = key.split("/")[0];
|
|
const folderMap = {
|
|
profiles: "profile",
|
|
items: "item",
|
|
messages: "message",
|
|
forum: "forum",
|
|
"condition-checks": "condition-check",
|
|
};
|
|
return folderMap[folder] || null;
|
|
}
|
|
|
|
/**
|
|
* Verify if a user can access a file
|
|
* @param {string} key - S3 key
|
|
* @param {string} userId - User ID making the request
|
|
* @returns {Promise<{authorized: boolean, reason?: string}>}
|
|
*/
|
|
static async canAccessFile(key, userId) {
|
|
const fileType = this.getFileTypeFromKey(key);
|
|
|
|
switch (fileType) {
|
|
case "profile":
|
|
case "item":
|
|
case "forum":
|
|
// Public folders - anyone can access
|
|
return { authorized: true };
|
|
case "message":
|
|
return this.verifyMessageAccess(key, userId);
|
|
case "condition-check":
|
|
return this.verifyConditionCheckAccess(key, userId);
|
|
default:
|
|
return { authorized: false, reason: "Unknown file type" };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify message image access - user must be sender OR receiver
|
|
* @param {string} key - S3 key
|
|
* @param {string} userId - User ID making the request
|
|
* @returns {Promise<{authorized: boolean, reason?: string}>}
|
|
*/
|
|
static async verifyMessageAccess(key, userId) {
|
|
const message = await Message.findOne({
|
|
where: {
|
|
imageFilename: key,
|
|
[Op.or]: [{ senderId: userId }, { receiverId: userId }],
|
|
},
|
|
});
|
|
return {
|
|
authorized: !!message,
|
|
reason: message ? null : "Not a participant in this message",
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Verify condition check image access - user must be rental owner OR renter
|
|
* @param {string} key - S3 key
|
|
* @param {string} userId - User ID making the request
|
|
* @returns {Promise<{authorized: boolean, reason?: string}>}
|
|
*/
|
|
static async verifyConditionCheckAccess(key, userId) {
|
|
const check = await ConditionCheck.findOne({
|
|
where: {
|
|
imageFilenames: { [Op.contains]: [key] },
|
|
},
|
|
include: [
|
|
{
|
|
model: Rental,
|
|
as: "rental",
|
|
where: {
|
|
[Op.or]: [{ ownerId: userId }, { renterId: userId }],
|
|
},
|
|
},
|
|
],
|
|
});
|
|
return {
|
|
authorized: !!check,
|
|
reason: check ? null : "Not a participant in this rental",
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = S3OwnershipService;
|