the start

This commit is contained in:
nmemmert committed 2025-10-24 10:23:12 -04:00
1 parent e41e78f9a3
commit a79dddff60
22 files changed
+4856 -99

No files matched your search

+81
View File
@@ -0,0 +1,81 @@
// Simple encryption utility using Web Crypto API
// Note: In production, consider using a more robust encryption library
const encoder = new TextEncoder();
const decoder = new TextDecoder();
export async function encryptData(data: string, key: string): Promise<string> {
const keyMaterial = await crypto.subtle.importKey(
'raw',
encoder.encode(key),
'PBKDF2',
false,
['deriveBits', 'deriveKey']
);
const salt = crypto.getRandomValues(new Uint8Array(16));
const derivedKey = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt,
iterations: 100000,
hash: 'SHA-256',
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt']
);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: iv },
derivedKey,
encoder.encode(data)
);
// Combine salt, iv, and encrypted data
const combined = new Uint8Array(salt.length + iv.length + encrypted.byteLength);
combined.set(salt, 0);
combined.set(iv, salt.length);
combined.set(new Uint8Array(encrypted), salt.length + iv.length);
return btoa(String.fromCharCode(...combined));
}
export async function decryptData(encryptedData: string, key: string): Promise<string> {
const combined = new Uint8Array(atob(encryptedData).split('').map(c => c.charCodeAt(0)));
const salt = combined.slice(0, 16);
const iv = combined.slice(16, 28);
const encrypted = combined.slice(28);
const keyMaterial = await crypto.subtle.importKey(
'raw',
encoder.encode(key),
'PBKDF2',
false,
['deriveBits', 'deriveKey']
);
const derivedKey = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt,
iterations: 100000,
hash: 'SHA-256',
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['decrypt']
);
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: iv },
derivedKey,
encrypted
);
return decoder.decode(decrypted);
}
+132
View File
@@ -0,0 +1,132 @@
import { doc, getDoc, setDoc, updateDoc } from 'firebase/firestore';
import { db } from './firebase';
import { encryptData, decryptData } from './crypto';
interface Envelope {
id: string;
name: string;
allocated: number;
spent: number;
color: string;
accountId: string; // Link envelope to specific account
incomeAllocation?: number; // Percentage (0-100) or fixed amount if negative
incomeAllocationType?: 'percentage' | 'fixed'; // How to interpret incomeAllocation
}
interface Transaction {
id: string;
envelopeId?: string;
accountId: string; // Link transaction to specific account
amount: number;
description: string;
date: Date;
category?: string;
}
interface Account {
id: string;
name: string;
type: 'checking' | 'savings' | 'credit_card' | 'mortgage' | 'investment' | 'loan';
balance: number;
institution?: string;
accountNumber?: string;
color: string;
isActive: boolean;
defaultPaycheckAmount?: number;
}
interface UserData {
accounts: Account[];
envelopes: Envelope[];
transactions: Transaction[];
setupCompleted: boolean;
}
const ENCRYPTION_KEY = process.env.NEXT_PUBLIC_ENCRYPTION_KEY || 'default-key-change-in-production';
export class DataService {
private static async encryptUserData(data: UserData): Promise<string> {
return await encryptData(JSON.stringify(data), ENCRYPTION_KEY);
}
private static async decryptUserData(encryptedData: string): Promise<UserData> {
const decrypted = await decryptData(encryptedData, ENCRYPTION_KEY);
return JSON.parse(decrypted);
}
static async saveUserData(userId: string, data: UserData): Promise<void> {
try {
const encryptedData = await this.encryptUserData(data);
const userDocRef = doc(db, 'users', userId);
await setDoc(userDocRef, {
data: encryptedData,
lastUpdated: new Date(),
});
} catch (error) {
console.error('Error saving user data:', error);
throw error;
}
}
static async loadUserData(userId: string): Promise<UserData | null> {
try {
const userDocRef = doc(db, 'users', userId);
const userDoc = await getDoc(userDocRef);
if (userDoc.exists()) {
const encryptedData = userDoc.data()?.data;
if (encryptedData) {
return await this.decryptUserData(encryptedData);
}
}
// Return default data if no data exists
return {
accounts: [
{
id: 'checking-1',
name: 'Main Checking',
type: 'checking' as const,
balance: 2500.00,
institution: 'Bank of America',
color: 'bg-blue-500',
isActive: true,
},
{
id: 'savings-1',
name: 'Emergency Savings',
type: 'savings' as const,
balance: 5000.00,
institution: 'Bank of America',
color: 'bg-green-500',
isActive: true,
},
],
envelopes: [
{ id: '1', name: 'Groceries', allocated: 500, spent: 0, color: 'bg-green-500', accountId: 'checking-1' },
{ id: '2', name: 'Transportation', allocated: 300, spent: 0, color: 'bg-blue-500', accountId: 'checking-1' },
{ id: '3', name: 'Entertainment', allocated: 200, spent: 0, color: 'bg-purple-500', accountId: 'checking-1' },
{ id: '4', name: 'Utilities', allocated: 250, spent: 0, color: 'bg-orange-500', accountId: 'checking-1' },
],
transactions: [],
setupCompleted: false,
};
} catch (error) {
console.error('Error loading user data:', error);
throw error;
}
}
static async updateUserData(userId: string, updates: Partial<UserData>): Promise<void> {
try {
const currentData = await this.loadUserData(userId);
if (currentData) {
const updatedData = { ...currentData, ...updates };
await this.saveUserData(userId, updatedData);
}
} catch (error) {
console.error('Error updating user data:', error);
throw error;
}
}
}
+21
View File
@@ -0,0 +1,21 @@
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
// Initialize Firebase services
export const auth = getAuth(app);
export const db = getFirestore(app);
export default app;