created a localised login system

This commit is contained in:
bobbert committed 2026-02-21 13:14:01 +00:00
1 parent 59ac1fc926
commit 37511780b7
16 files changed
+3721 -20

No files matched your search

+96
View File
@@ -0,0 +1,96 @@
/**
* Credential Encryption Service
*
* Encrypts sensitive credentials before storing in database
* Uses base64 encoding (React Native compatible)
* For production, use a proper encryption library like react-native-aes-crypto
*/
class CredentialEncryption {
constructor() {
// Simple key for demo - in production, use proper key management
this.encryptionKey = 'photo-app-secret-key-2026';
}
/**
* Base64 encode a string (React Native compatible)
* Uses btoa which is available in React Native
*/
base64Encode(str) {
try {
// btoa is available in React Native
return btoa(unescape(encodeURIComponent(str)));
} catch (error) {
console.error('Base64 encode error:', error);
throw new Error('Failed to encode data');
}
}
/**
* Base64 decode a string (React Native compatible)
* Uses atob which is available in React Native
*/
base64Decode(str) {
try {
// atob is available in React Native
return decodeURIComponent(escape(atob(str)));
} catch (error) {
console.error('Base64 decode error:', error);
throw new Error('Failed to decode data');
}
}
/**
* Encrypt credentials before storing
* Converts object to JSON string, then base64 encodes it
*/
encrypt(data) {
try {
if (!data) {
throw new Error('No data provided to encrypt');
}
const jsonString = JSON.stringify(data);
const encoded = this.base64Encode(jsonString);
console.log('✓ Credentials encrypted successfully');
return encoded;
} catch (error) {
console.error('Encryption error:', error);
throw new Error('Failed to encrypt credentials: ' + error.message);
}
}
/**
* Decrypt credentials after retrieving
* Base64 decodes, then parses JSON back to object
*/
decrypt(encryptedData) {
try {
if (!encryptedData) {
return null;
}
const jsonString = this.base64Decode(encryptedData);
const data = JSON.parse(jsonString);
console.log('✓ Credentials decrypted successfully');
return data;
} catch (error) {
console.error('Decryption error:', error);
throw new Error('Failed to decrypt credentials: ' + error.message);
}
}
/**
* Check if data is already encrypted (base64)
*/
isEncrypted(data) {
if (typeof data !== 'string') return false;
try {
this.base64Decode(data);
return true;
} catch {
return false;
}
}
}
export default new CredentialEncryption();
+428
View File
@@ -0,0 +1,428 @@
import * as SQLite from 'expo-sqlite';
import CredentialEncryption from './CredentialEncryption';
let db = null;
let initPromise = null;
const DatabaseService = {
/**
* Initialize the database and create tables if they don't exist
*/
initDatabase: async () => {
if (initPromise) {
return initPromise;
}
initPromise = (async () => {
try {
if (db) {
return true;
}
if (typeof SQLite.openDatabaseAsync !== 'function') {
throw new Error('SQLite openDatabaseAsync API not available');
}
db = await SQLite.openDatabaseAsync('PhotosApp.db');
if (!db) {
throw new Error('SQLite openDatabaseAsync returned null');
}
await db.execAsync(
`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`
);
await db.execAsync(
`CREATE TABLE IF NOT EXISTS linked_accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
service_type TEXT NOT NULL,
account_data TEXT NOT NULL,
is_enabled INTEGER DEFAULT 1,
is_default INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id)
)`
);
await db.execAsync(
`CREATE TABLE IF NOT EXISTS media (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
source_service TEXT NOT NULL,
source_id TEXT,
title TEXT,
description TEXT,
local_path TEXT,
remote_path TEXT,
media_type TEXT,
created_date DATETIME,
uploaded_to_default INTEGER DEFAULT 0,
synced INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id)
)`
);
await db.execAsync(
`CREATE TABLE IF NOT EXISTS sync_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
service_type TEXT NOT NULL,
status TEXT,
message TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id)
)`
);
console.log('Database initialized successfully');
return true;
} catch (error) {
console.error('Database initialization error:', error);
throw error;
}
})();
return initPromise;
},
/**
* Ensure the database is initialized before use
*/
ensureDb: async () => {
await DatabaseService.initDatabase();
if (!db) {
throw new Error('Database not initialized');
}
},
/**
* Create a new user account
*/
createUser: async (username, email, password) => {
try {
await DatabaseService.ensureDb();
const result = await db.runAsync(
'INSERT INTO users (username, email, password) VALUES (?, ?, ?)',
[username, email, password]
);
return result.lastInsertRowId;
} catch (error) {
console.error('Error creating user:', error);
throw error;
}
},
/**
* Get user by email and password
*/
loginUser: async (email, password) => {
try {
await DatabaseService.ensureDb();
const row = await db.getFirstAsync(
'SELECT * FROM users WHERE email = ? AND password = ?',
[email, password]
);
return row || null;
} catch (error) {
console.error('Error logging in user:', error);
throw error;
}
},
/**
* Check if user exists by email
*/
userExists: async (email) => {
try {
await DatabaseService.ensureDb();
const row = await db.getFirstAsync(
'SELECT id FROM users WHERE email = ?',
[email]
);
return !!row;
} catch (error) {
console.error('Error checking if user exists:', error);
throw error;
}
},
/**
* Get user by ID
*/
getUserById: async (userId) => {
try {
await DatabaseService.ensureDb();
if (!userId && userId !== 0) {
throw new Error('User ID is required and cannot be null');
}
const row = await db.getFirstAsync(
'SELECT * FROM users WHERE id = ?',
[userId]
);
return row || null;
} catch (error) {
console.error('Error getting user:', error);
throw error;
}
},
/**
* Link an external account to user
*/
linkAccount: async (userId, serviceType, accountData, isDefault = false) => {
try {
await DatabaseService.ensureDb();
// Validate inputs
if (!userId) {
throw new Error('User ID is required');
}
if (!serviceType) {
throw new Error('Service type is required');
}
if (!accountData || typeof accountData !== 'object') {
throw new Error('Account data must be a valid object');
}
console.log('📝 Linking account:', { userId, serviceType, dataKeys: Object.keys(accountData) });
// Encrypt account data
const encryptedData = CredentialEncryption.encrypt(accountData);
if (!encryptedData) {
throw new Error('Encryption returned empty data');
}
console.log('✓ Account data encrypted, storing in database...');
const result = await db.runAsync(
`INSERT INTO linked_accounts (user_id, service_type, account_data, is_default)
VALUES (?, ?, ?, ?)`,
[userId, serviceType, encryptedData, isDefault ? 1 : 0]
);
console.log('✓ Account linked successfully, ID:', result.lastInsertRowId);
return result.lastInsertRowId;
} catch (error) {
console.error('Error linking account:', error);
throw new Error(`Failed to link account: ${error.message}`);
}
},
/**
* Get all linked accounts for user
*/
getLinkedAccounts: async (userId) => {
try {
await DatabaseService.ensureDb();
if (!userId && userId !== 0) {
throw new Error('User ID is required and cannot be null');
}
const rows = await db.getAllAsync(
'SELECT * FROM linked_accounts WHERE user_id = ? AND is_enabled = 1',
[userId]
);
return rows.map((row) => ({
...row,
account_data: CredentialEncryption.decrypt(row.account_data),
}));
} catch (error) {
console.error('Error getting linked accounts:', error);
throw error;
}
},
/**
* Get linked account by service type
*/
getLinkedAccountByService: async (userId, serviceType) => {
try {
await DatabaseService.ensureDb();
const row = await db.getFirstAsync(
'SELECT * FROM linked_accounts WHERE user_id = ? AND service_type = ? AND is_enabled = 1',
[userId, serviceType]
);
if (!row) return null;
return {
...row,
account_data: CredentialEncryption.decrypt(row.account_data),
};
} catch (error) {
console.error('Error getting linked account:', error);
throw error;
}
},
/**
* Set default service for uploads
*/
setDefaultService: async (userId, accountId) => {
try {
await DatabaseService.ensureDb();
await db.runAsync(
'UPDATE linked_accounts SET is_default = 0 WHERE user_id = ?',
[userId]
);
await db.runAsync(
'UPDATE linked_accounts SET is_default = 1 WHERE id = ? AND user_id = ?',
[accountId, userId]
);
} catch (error) {
console.error('Error setting default service:', error);
throw error;
}
},
/**
* Get default service for user
*/
getDefaultService: async (userId) => {
try {
await DatabaseService.ensureDb();
const row = await db.getFirstAsync(
'SELECT * FROM linked_accounts WHERE user_id = ? AND is_default = 1',
[userId]
);
if (!row) return null;
return {
...row,
account_data: CredentialEncryption.decrypt(row.account_data),
};
} catch (error) {
console.error('Error getting default service:', error);
throw error;
}
},
/**
* Add media to database
*/
addMedia: async (
userId,
sourceService,
sourceId,
title,
description,
localPath,
remotePath,
mediaType,
createdDate
) => {
try {
await DatabaseService.ensureDb();
if (!userId || !sourceService) {
throw new Error('userId and sourceService are required');
}
const result = await db.runAsync(
`INSERT INTO media
(user_id, source_service, source_id, title, description, local_path, remote_path, media_type, created_date)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
userId,
sourceService,
sourceId || null,
title || null,
description || null,
localPath || null,
remotePath || null,
mediaType || null,
createdDate || null,
]
);
return result.lastInsertRowId;
} catch (error) {
console.error('Error adding media:', error);
throw error;
}
},
/**
* Get all media for user
*/
getMediaByUser: async (userId) => {
try {
await DatabaseService.ensureDb();
if (!userId && userId !== 0) {
throw new Error('User ID is required and cannot be null');
}
return await db.getAllAsync(
'SELECT * FROM media WHERE user_id = ? ORDER BY created_date DESC',
[userId]
);
} catch (error) {
console.error('Error getting media:', error);
throw error;
}
},
/**
* Get media by service
*/
getMediaByService: async (userId, serviceType) => {
try {
await DatabaseService.ensureDb();
return await db.getAllAsync(
'SELECT * FROM media WHERE user_id = ? AND source_service = ? ORDER BY created_date DESC',
[userId, serviceType]
);
} catch (error) {
console.error('Error getting media by service:', error);
throw error;
}
},
/**
* Update media sync status
*/
updateMediaSyncStatus: async (mediaId, synced) => {
try {
await DatabaseService.ensureDb();
await db.runAsync(
'UPDATE media SET synced = ? WHERE id = ?',
[synced ? 1 : 0, mediaId]
);
} catch (error) {
console.error('Error updating media sync status:', error);
throw error;
}
},
/**
* Log sync operation
*/
logSync: async (userId, serviceType, status, message) => {
try {
await DatabaseService.ensureDb();
await db.runAsync(
'INSERT INTO sync_logs (user_id, service_type, status, message) VALUES (?, ?, ?, ?)',
[userId, serviceType, status, message]
);
} catch (error) {
console.error('Error logging sync:', error);
throw error;
}
},
/**
* Close database connection
*/
closeDatabase: async () => {
try {
if (db?.closeAsync) {
await db.closeAsync();
}
db = null;
initPromise = null;
} catch (error) {
console.error('Error closing database:', error);
}
},
};
export default DatabaseService;