Data Protection & Privacy
Data encryption, privacy compliance, and security measures for protecting sensitive information in Smart Shelf.
Data Protection & Privacy
Comprehensive data protection and privacy implementation for Smart Shelf, covering encryption strategies, privacy compliance (GDPR), and security measures for sensitive information.
Data Classification
Data Sensitivity Levels
// lib/security/data-classification.ts
export enum DataSensitivity {
PUBLIC = 'public', // Product catalogs, public information
INTERNAL = 'internal', // Business operations data
CONFIDENTIAL = 'confidential', // Financial data, pricing
RESTRICTED = 'restricted', // Personal data, authentication
}
export const DATA_CLASSIFICATION = {
// User data
'user_profiles.email': DataSensitivity.RESTRICTED,
'user_profiles.full_name': DataSensitivity.RESTRICTED,
'user_profiles.phone': DataSensitivity.RESTRICTED,
// Product data
'products.name': DataSensitivity.PUBLIC,
'products.description': DataSensitivity.PUBLIC,
'products.cost_price': DataSensitivity.CONFIDENTIAL,
'products.selling_price': DataSensitivity.INTERNAL,
// Financial data
'orders.total_amount': DataSensitivity.CONFIDENTIAL,
'purchase_orders.total_cost': DataSensitivity.CONFIDENTIAL,
// Operational data
'inventory.quantity_on_hand': DataSensitivity.INTERNAL,
'stock_movements.*': DataSensitivity.INTERNAL,
} as const;
export function getDataSensitivity(field: string): DataSensitivity {
return DATA_CLASSIFICATION[field as keyof typeof DATA_CLASSIFICATION] || DataSensitivity.INTERNAL;
}
Encryption Implementation
Field-Level Encryption
// lib/security/encryption.ts
import crypto from 'crypto';
export class FieldEncryption {
private static readonly ALGORITHM = 'aes-256-gcm';
private static readonly KEY_LENGTH = 32;
private static readonly IV_LENGTH = 16;
private static readonly TAG_LENGTH = 16;
private static getEncryptionKey(): Buffer {
const key = process.env.ENCRYPTION_KEY;
if (!key) {
throw new Error('Encryption key not configured');
}
return Buffer.from(key, 'hex');
}
static encrypt(plaintext: string): string {
try {
const key = this.getEncryptionKey();
const iv = crypto.randomBytes(this.IV_LENGTH);
const cipher = crypto.createCipher(this.ALGORITHM, key);
cipher.setAAD(Buffer.from('smart-shelf', 'utf8'));
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
// Combine IV + encrypted data + auth tag
return iv.toString('hex') + ':' + encrypted + ':' + authTag.toString('hex');
} catch (error) {
throw new Error('Encryption failed');
}
}
static decrypt(encryptedData: string): string {
try {
const parts = encryptedData.split(':');
if (parts.length !== 3) {
throw new Error('Invalid encrypted data format');
}
const key = this.getEncryptionKey();
const iv = Buffer.from(parts[0], 'hex');
const encrypted = parts[1];
const authTag = Buffer.from(parts[2], 'hex');
const decipher = crypto.createDecipher(this.ALGORITHM, key);
decipher.setAAD(Buffer.from('smart-shelf', 'utf8'));
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
} catch (error) {
throw new Error('Decryption failed');
}
}
}
// Usage in models
export class SecureUser {
id: string;
email: string; // Encrypted
full_name: string; // Encrypted
role: string;
static fromDatabase(data: any): SecureUser {
return {
id: data.id,
email: FieldEncryption.decrypt(data.encrypted_email),
full_name: FieldEncryption.decrypt(data.encrypted_full_name),
role: data.role,
};
}
toDatabase(): any {
return {
id: this.id,
encrypted_email: FieldEncryption.encrypt(this.email),
encrypted_full_name: FieldEncryption.encrypt(this.full_name),
role: this.role,
};
}
}
Database Encryption
-- Enable pgcrypto extension for database-level encryption
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- Function to encrypt sensitive data
CREATE OR REPLACE FUNCTION encrypt_pii(data TEXT)
RETURNS TEXT AS $$
BEGIN
RETURN encode(
encrypt(
data::bytea,
current_setting('app.encryption_key')::bytea,
'aes'
),
'base64'
);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Function to decrypt sensitive data
CREATE OR REPLACE FUNCTION decrypt_pii(encrypted_data TEXT)
RETURNS TEXT AS $$
BEGIN
RETURN convert_from(
decrypt(
decode(encrypted_data, 'base64'),
current_setting('app.encryption_key')::bytea,
'aes'
),
'UTF8'
);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Encrypted user profiles table
CREATE TABLE encrypted_user_profiles (
id UUID PRIMARY KEY REFERENCES auth.users(id),
encrypted_email TEXT NOT NULL,
encrypted_full_name TEXT NOT NULL,
encrypted_phone TEXT,
role user_role NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
Privacy Compliance (GDPR)
Data Subject Rights
// lib/privacy/gdpr.ts
export enum DataSubjectRight {
ACCESS = 'access', // Right to access personal data
RECTIFICATION = 'rectification', // Right to correct data
ERASURE = 'erasure', // Right to be forgotten
PORTABILITY = 'portability', // Right to data portability
RESTRICTION = 'restriction', // Right to restrict processing
OBJECTION = 'objection', // Right to object to processing
}
export interface DataProcessingRecord {
id: string;
user_id: string;
processing_purpose: string;
legal_basis: 'consent' | 'contract' | 'legal_obligation' | 'vital_interests' | 'public_task' | 'legitimate_interests';
data_categories: string[];
retention_period: string;
consent_given: boolean;
consent_date?: Date;
created_at: Date;
}
export class GDPRCompliance {
static async getPersonalData(userId: string): Promise<any> {
const supabase = createServerComponentClient({ cookies });
// Collect all personal data for the user
const personalData = {
profile: await this.getUserProfile(userId),
activities: await this.getUserActivities(userId),
preferences: await this.getUserPreferences(userId),
};
// Log access request
await this.logDataAccess(userId, 'personal_data_export');
return personalData;
}
static async deletePersonalData(userId: string, reason: string): Promise<void> {
const supabase = createServerComponentClient({ cookies });
try {
// Start transaction
await supabase.rpc('begin_transaction');
// Anonymize instead of delete to maintain referential integrity
await this.anonymizeUserData(userId);
// Log deletion
await this.logDataDeletion(userId, reason);
// Commit transaction
await supabase.rpc('commit_transaction');
} catch (error) {
await supabase.rpc('rollback_transaction');
throw error;
}
}
private static async anonymizeUserData(userId: string): Promise<void> {
const supabase = createServerComponentClient({ cookies });
// Anonymize user profile
await supabase
.from('user_profiles')
.update({
encrypted_email: FieldEncryption.encrypt('anonymized@example.com'),
encrypted_full_name: FieldEncryption.encrypt('Anonymized User'),
encrypted_phone: null,
is_active: false,
anonymized_at: new Date().toISOString(),
})
.eq('id', userId);
// Remove personal identifiers from audit logs
await supabase
.from('audit_log')
.update({
old_values: {},
new_values: {},
anonymized: true,
})
.eq('changed_by', userId);
}
static async recordConsent(
userId: string,
purpose: string,
legalBasis: string,
dataCategories: string[]
): Promise<void> {
const supabase = createServerComponentClient({ cookies });
await supabase
.from('data_processing_records')
.insert({
user_id: userId,
processing_purpose: purpose,
legal_basis: legalBasis,
data_categories: dataCategories,
consent_given: true,
consent_date: new Date().toISOString(),
});
}
static async withdrawConsent(userId: string, purpose: string): Promise<void> {
const supabase = createServerComponentClient({ cookies });
await supabase
.from('data_processing_records')
.update({
consent_given: false,
consent_withdrawn_at: new Date().toISOString(),
})
.eq('user_id', userId)
.eq('processing_purpose', purpose);
}
}
Data Retention Policies
// lib/privacy/retention.ts
export interface RetentionPolicy {
dataType: string;
retentionPeriod: number; // in days
deletionMethod: 'hard_delete' | 'soft_delete' | 'anonymize';
legalBasis: string;
}
export const RETENTION_POLICIES: RetentionPolicy[] = [
{
dataType: 'user_activity_logs',
retentionPeriod: 365,
deletionMethod: 'hard_delete',
legalBasis: 'Security monitoring',
},
{
dataType: 'financial_records',
retentionPeriod: 2555, // 7 years
deletionMethod: 'anonymize',
legalBasis: 'Legal obligation',
},
{
dataType: 'marketing_preferences',
retentionPeriod: 1095, // 3 years
deletionMethod: 'hard_delete',
legalBasis: 'Consent',
},
{
dataType: 'audit_logs',
retentionPeriod: 2190, // 6 years
deletionMethod: 'anonymize',
legalBasis: 'Legal obligation',
},
];
export class DataRetentionManager {
static async enforceRetentionPolicies(): Promise<void> {
for (const policy of RETENTION_POLICIES) {
await this.enforcePolicy(policy);
}
}
private static async enforcePolicy(policy: RetentionPolicy): Promise<void> {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - policy.retentionPeriod);
switch (policy.deletionMethod) {
case 'hard_delete':
await this.hardDelete(policy.dataType, cutoffDate);
break;
case 'soft_delete':
await this.softDelete(policy.dataType, cutoffDate);
break;
case 'anonymize':
await this.anonymizeData(policy.dataType, cutoffDate);
break;
}
}
private static async hardDelete(dataType: string, cutoffDate: Date): Promise<void> {
const supabase = createServerComponentClient({ cookies });
const { error } = await supabase
.from(dataType)
.delete()
.lt('created_at', cutoffDate.toISOString());
if (error) {
console.error(`Failed to delete expired ${dataType}:`, error);
}
}
private static async anonymizeData(dataType: string, cutoffDate: Date): Promise<void> {
const supabase = createServerComponentClient({ cookies });
const { error } = await supabase
.from(dataType)
.update({ anonymized: true, anonymized_at: new Date().toISOString() })
.lt('created_at', cutoffDate.toISOString())
.is('anonymized', null);
if (error) {
console.error(`Failed to anonymize expired ${dataType}:`, error);
}
}
}
Data Masking and Sanitization
Data Masking Utilities
// lib/security/data-masking.ts
export class DataMasking {
static maskEmail(email: string): string {
const [username, domain] = email.split('@');
const maskedUsername = username.substring(0, 2) + '*'.repeat(username.length - 2);
return `${maskedUsername}@${domain}`;
}
static maskPhone(phone: string): string {
if (phone.length < 4) return phone;
return '*'.repeat(phone.length - 4) + phone.slice(-4);
}
static maskCreditCard(cardNumber: string): string {
if (cardNumber.length < 4) return cardNumber;
return '*'.repeat(cardNumber.length - 4) + cardNumber.slice(-4);
}
static maskBankAccount(accountNumber: string): string {
if (accountNumber.length < 4) return accountNumber;
return '*'.repeat(accountNumber.length - 4) + accountNumber.slice(-4);
}
static sanitizeForLogging(data: any): any {
const sensitiveFields = ['password', 'token', 'secret', 'key', 'email', 'phone'];
if (typeof data !== 'object' || data === null) {
return data;
}
const sanitized = { ...data };
for (const key in sanitized) {
const isSecretField = sensitiveFields.some(field =>
key.toLowerCase().includes(field)
);
if (isSecretField) {
sanitized[key] = '[REDACTED]';
} else if (typeof sanitized[key] === 'object') {
sanitized[key] = this.sanitizeForLogging(sanitized[key]);
}
}
return sanitized;
}
}
// Usage in API responses
export function sanitizeUserForResponse(user: any): any {
return {
id: user.id,
email: DataMasking.maskEmail(user.email),
full_name: user.full_name,
role: user.role,
// Remove sensitive fields
created_at: user.created_at,
};
}
Secure Data Storage
Database Security Functions
-- Function to securely store sensitive data
CREATE OR REPLACE FUNCTION store_sensitive_data(
table_name TEXT,
record_id UUID,
field_name TEXT,
sensitive_value TEXT
)
RETURNS VOID AS $$
DECLARE
encrypted_value TEXT;
BEGIN
-- Encrypt the sensitive value
encrypted_value := encrypt_pii(sensitive_value);
-- Store in secure vault table
INSERT INTO secure_data_vault (
table_name,
record_id,
field_name,
encrypted_value,
created_at
) VALUES (
table_name,
record_id,
field_name,
encrypted_value,
NOW()
)
ON CONFLICT (table_name, record_id, field_name)
DO UPDATE SET
encrypted_value = EXCLUDED.encrypted_value,
updated_at = NOW();
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Function to retrieve sensitive data
CREATE OR REPLACE FUNCTION get_sensitive_data(
table_name TEXT,
record_id UUID,
field_name TEXT
)
RETURNS TEXT AS $$
DECLARE
encrypted_value TEXT;
BEGIN
SELECT sdv.encrypted_value INTO encrypted_value
FROM secure_data_vault sdv
WHERE sdv.table_name = get_sensitive_data.table_name
AND sdv.record_id = get_sensitive_data.record_id
AND sdv.field_name = get_sensitive_data.field_name;
IF encrypted_value IS NULL THEN
RETURN NULL;
END IF;
RETURN decrypt_pii(encrypted_value);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Secure data vault table
CREATE TABLE secure_data_vault (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
table_name TEXT NOT NULL,
record_id UUID NOT NULL,
field_name TEXT NOT NULL,
encrypted_value TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(table_name, record_id, field_name)
);
Privacy by Design Implementation
Privacy Impact Assessment
// lib/privacy/impact-assessment.ts
export interface PrivacyImpactAssessment {
feature: string;
dataTypes: string[];
processingPurpose: string;
legalBasis: string;
riskLevel: 'low' | 'medium' | 'high';
mitigationMeasures: string[];
approvedBy: string;
approvedAt: Date;
}
export class PrivacyByDesign {
static async conductPIA(feature: string): Promise<PrivacyImpactAssessment> {
const assessment: PrivacyImpactAssessment = {
feature,
dataTypes: this.identifyDataTypes(feature),
processingPurpose: this.getProcessingPurpose(feature),
legalBasis: this.determineLegalBasis(feature),
riskLevel: this.assessRiskLevel(feature),
mitigationMeasures: this.getMitigationMeasures(feature),
approvedBy: 'Data Protection Officer',
approvedAt: new Date(),
};
// Store assessment
await this.storePIA(assessment);
return assessment;
}
private static assessRiskLevel(feature: string): 'low' | 'medium' | 'high' {
const highRiskFeatures = ['user_tracking', 'financial_data', 'biometric_data'];
const mediumRiskFeatures = ['user_preferences', 'activity_logs'];
if (highRiskFeatures.some(risk => feature.includes(risk))) {
return 'high';
}
if (mediumRiskFeatures.some(risk => feature.includes(risk))) {
return 'medium';
}
return 'low';
}
}
This comprehensive data protection and privacy framework ensures Smart Shelf complies with privacy regulations while maintaining security and usability.