campverse-backend / Services /secureStorageService.js
CampVerse CI
Deploy backend from GitHub Actions
c904036
Raw History Blame Contribute Delete
6.62 kB
/**
* Secure Storage Service
* Integrates Supabase Storage with Google OAuth + MongoDB authentication
* Provides secure, user-specific file access with temporary signed URLs
*/
const { createClient } = require('@supabase/supabase-js');
const jwt = require('jsonwebtoken');
const { logger } = require('../Middleware/errorHandler');
class SecureStorageService {
constructor() {
this.supabaseUrl = process.env.SUPABASE_URL;
this.supabaseAnonKey = process.env.SUPABASE_ANON_KEY;
this.supabaseServiceKey = process.env.SUPABASE_SERVICE_KEY;
this.bucketName = process.env.SUPABASE_BUCKET_NAME || 'campverse';
this.jwtSecret = process.env.JWT_SECRET;
this.baseFolder = 'CampVerse';
this.initialized = false;
if (this.supabaseUrl && this.supabaseServiceKey) {
// Use service role for admin operations (bypasses RLS)
this.supabaseAdmin = createClient(this.supabaseUrl, this.supabaseServiceKey, {
auth: {
autoRefreshToken: false,
persistSession: false
}
});
this.initialized = true;
logger.info('Secure Storage Service initialized with admin privileges');
} else {
logger.error('Supabase credentials missing for secure storage');
}
}
/**
* Upload file with user-specific access control
*/
async uploadUserFile(fileBuffer, filename, userId, category = 'profiles', mimetype) {
try {
if (!this.initialized) {
throw new Error('Secure Storage Service not initialized');
}
// Create user-specific file path
const timestamp = Date.now();
const uniqueId = Math.random().toString(36).substring(2, 8);
const cleanFilename = `${timestamp}_${uniqueId}_${filename}`;
const filePath = `${this.baseFolder}/users/${userId}/${category}/${cleanFilename}`;
// Upload using admin client (bypasses RLS)
const { error } = await this.supabaseAdmin.storage
.from(this.bucketName)
.upload(filePath, fileBuffer, {
contentType: mimetype,
metadata: {
originalName: filename,
uploadedAt: new Date().toISOString(),
userId,
category
}
});
if (error) {
throw new Error(`Secure upload error: ${error.message}`);
}
logger.info(`Secure file uploaded for user ${userId}: ${filePath}`);
return {
path: filePath,
filename: cleanFilename,
size: fileBuffer.length,
mimetype,
userId,
uploadedAt: new Date().toISOString()
};
} catch (error) {
logger.error(`Failed to upload secure file for user ${userId}:`, error);
throw error;
}
}
/**
* Generate secure signed URL for authorized user access
*/
async generateSecureUrl(filePath, userId, expiresIn = 3600) {
try {
if (!this.initialized) {
throw new Error('Secure Storage Service not initialized');
}
// Verify user has access to this file path
if (!filePath.includes(`/users/${userId}/`)) {
throw new Error('Unauthorized access to file');
}
// Generate signed URL using admin client
const { data, error } = await this.supabaseAdmin.storage
.from(this.bucketName)
.createSignedUrl(filePath, expiresIn);
if (error) {
throw new Error(`Failed to generate secure URL: ${error.message}`);
}
logger.info(`Secure URL generated for user ${userId}: ${filePath}`);
return data.signedUrl;
} catch (error) {
logger.error(`Failed to generate secure URL for user ${userId}:`, error);
throw error;
}
}
/**
* Delete user's file with access control
*/
async deleteUserFile(filePath, userId) {
try {
if (!this.initialized) {
throw new Error('Secure Storage Service not initialized');
}
// Verify user has access to this file
if (!filePath.includes(`/users/${userId}/`)) {
throw new Error('Unauthorized file deletion attempt');
}
const { error } = await this.supabaseAdmin.storage
.from(this.bucketName)
.remove([filePath]);
if (error) {
throw new Error(`Failed to delete file: ${error.message}`);
}
logger.info(`Secure file deleted for user ${userId}: ${filePath}`);
return true;
} catch (error) {
logger.error(`Failed to delete secure file for user ${userId}:`, error);
throw error;
}
}
/**
* List user's files with access control
*/
async listUserFiles(userId, category = null) {
try {
if (!this.initialized) {
throw new Error('Secure Storage Service not initialized');
}
const folderPath = category
? `${this.baseFolder}/users/${userId}/${category}`
: `${this.baseFolder}/users/${userId}`;
const { data, error } = await this.supabaseAdmin.storage
.from(this.bucketName)
.list(folderPath);
if (error) {
throw new Error(`Failed to list files: ${error.message}`);
}
return data || [];
} catch (error) {
logger.error(`Failed to list files for user ${userId}:`, error);
throw error;
}
}
/**
* Create a secure access token for file access
*/
createFileAccessToken(userId, filePath, expiresIn = '1h') {
const payload = {
userId,
filePath,
type: 'file_access',
iat: Math.floor(Date.now() / 1000)
};
return jwt.sign(payload, this.jwtSecret, { expiresIn });
}
/**
* Verify file access token
*/
verifyFileAccessToken(token) {
try {
const payload = jwt.verify(token, this.jwtSecret);
return payload;
} catch (error) {
throw new Error('Invalid or expired file access token');
}
}
/**
* Health check
*/
async healthCheck() {
try {
if (!this.initialized) {
return {
status: 'error',
message: 'Secure Storage Service not initialized'
};
}
// Test admin access
const { error } = await this.supabaseAdmin.storage
.from(this.bucketName)
.list('', { limit: 1 });
if (error) {
return {
status: 'error',
message: `Secure Storage error: ${error.message}`
};
}
return {
status: 'healthy',
message: 'Secure Storage Service operational',
bucket: this.bucketName,
security: 'enabled'
};
} catch (error) {
return {
status: 'error',
message: error.message
};
}
}
}
module.exports = { SecureStorageService };