First commit
This commit is contained in:
7
packages/core/analytics/package.json
Normal file
7
packages/core/analytics/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "@core/analytics",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "src/index.ts",
|
||||
"nx": { "tags": ["scope:core","type:infra"] }
|
||||
}
|
||||
1
packages/core/analytics/src/index.ts
Normal file
1
packages/core/analytics/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const AnalyticsCore = {};
|
||||
7
packages/core/bff-client/package.json
Normal file
7
packages/core/bff-client/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "@core/bff-client",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "src/index.ts",
|
||||
"nx": { "tags": ["scope:core","type:infra"] }
|
||||
}
|
||||
1
packages/core/bff-client/src/index.ts
Normal file
1
packages/core/bff-client/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const BFFClient = {};
|
||||
7
packages/core/consent/package.json
Normal file
7
packages/core/consent/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "@core/consent",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "src/index.ts",
|
||||
"nx": { "tags": ["scope:core","type:infra"] }
|
||||
}
|
||||
1
packages/core/consent/src/index.ts
Normal file
1
packages/core/consent/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const ConsentCore = {};
|
||||
7
packages/core/policy-client/package.json
Normal file
7
packages/core/policy-client/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "@core/policy-client",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "src/index.ts",
|
||||
"nx": { "tags": ["scope:core","type:infra"] }
|
||||
}
|
||||
1
packages/core/policy-client/src/index.ts
Normal file
1
packages/core/policy-client/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const PolicyClient = {};
|
||||
7
packages/core/runtime/package.json
Normal file
7
packages/core/runtime/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "@core/runtime",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "src/index.ts",
|
||||
"nx": { "tags": ["scope:core","type:infra"] }
|
||||
}
|
||||
1
packages/core/runtime/src/index.ts
Normal file
1
packages/core/runtime/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const RuntimeCore = {};
|
||||
24
packages/core/security/package.json
Normal file
24
packages/core/security/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@core/security",
|
||||
"version": "1.0.0",
|
||||
"description": "Core security SDK for hardware-backed cryptography, DPoP, and contextual encryption",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"scripts": {
|
||||
"build": "nx build",
|
||||
"test": "nx test",
|
||||
"lint": "nx lint",
|
||||
"typecheck": "nx typecheck"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-native-keychain": "^8.2.0",
|
||||
"react-native-crypto-js": "^1.0.0",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/uuid": "^9.0.7"
|
||||
},
|
||||
"nx": {
|
||||
"tags": ["scope:core", "type:infra", "platform:rn"]
|
||||
}
|
||||
}
|
||||
144
packages/core/security/src/DPoPService.ts
Normal file
144
packages/core/security/src/DPoPService.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import type { DPoPProof } from './types';
|
||||
|
||||
// Note: This requires a custom Native Module (NativeCryptoModule) capable of
|
||||
// generating ECC keys in the Secure Enclave and signing data with them.
|
||||
// This is a placeholder implementation that would need native module integration.
|
||||
|
||||
const DPOP_KEY_ALIAS = 'lynkedup.dpop.key.v1';
|
||||
|
||||
/**
|
||||
* DPoP (Demonstrating Proof-of-Possession) Service
|
||||
* Implements RFC 9449 for sender-constrained access tokens
|
||||
*
|
||||
* Features:
|
||||
* - Hardware-backed ECC key generation
|
||||
* - JWT signing with private key in secure enclave
|
||||
* - Request binding via HTTP method and URI
|
||||
*/
|
||||
export class DPoPService {
|
||||
private publicKey: string | null = null;
|
||||
|
||||
/**
|
||||
* Initialize DPoP key pair in secure enclave
|
||||
* Returns the public key JWK
|
||||
*/
|
||||
async initializeKeyPair(): Promise<string> {
|
||||
if (this.publicKey) return this.publicKey;
|
||||
|
||||
// Check if key exists natively using the alias, otherwise generate it
|
||||
// This would call into a native module
|
||||
let pubKey = await this.getPublicKeyFromNative(DPOP_KEY_ALIAS);
|
||||
|
||||
if (!pubKey) {
|
||||
pubKey = await this.generateHardwareECCKeyPair(DPOP_KEY_ALIAS);
|
||||
}
|
||||
|
||||
this.publicKey = pubKey;
|
||||
return pubKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a DPoP proof for HTTP request
|
||||
* @param method HTTP method (GET, POST, etc.)
|
||||
* @param uri Full request URI
|
||||
* @param accessToken Optional access token to bind to request
|
||||
*/
|
||||
async signProof(method: string, uri: string, accessToken?: string): Promise<string> {
|
||||
if (!this.publicKey) {
|
||||
await this.initializeKeyPair();
|
||||
}
|
||||
|
||||
// Construct the DPoP JWT payload (htu, htm, jti, ath)
|
||||
const payload = {
|
||||
htm: method, // HTTP Method
|
||||
htu: uri, // HTTP URI
|
||||
jti: uuidv4(), // Unique identifier for this proof
|
||||
iat: Math.floor(Date.now() / 1000), // Issued at time
|
||||
// Include hash of access token (ath) if present
|
||||
ath: accessToken ? this.base64url(this.sha256(accessToken)) : undefined,
|
||||
};
|
||||
|
||||
// Sign the JWT using the hardware-backed private key via the native module
|
||||
const signedJwt = await this.signJWTWithNative(payload, DPOP_KEY_ALIAS);
|
||||
|
||||
return signedJwt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign arbitrary data with DPoP key
|
||||
*/
|
||||
async signData(data: string): Promise<string> {
|
||||
if (!this.publicKey) {
|
||||
await this.initializeKeyPair();
|
||||
}
|
||||
|
||||
const signaturePayload = {
|
||||
data,
|
||||
timestamp: Date.now(),
|
||||
nonce: uuidv4()
|
||||
};
|
||||
|
||||
return this.signJWTWithNative(signaturePayload, DPOP_KEY_ALIAS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current public key
|
||||
*/
|
||||
getPublicKey(): string | null {
|
||||
return this.publicKey;
|
||||
}
|
||||
|
||||
// Native module integration methods (would be implemented via bridge)
|
||||
private async getPublicKeyFromNative(keyAlias: string): Promise<string | null> {
|
||||
// This would call into a native module
|
||||
// Example: return NativeCryptoModule.getPublicKey(keyAlias);
|
||||
console.warn('Native module integration required for getPublicKeyFromNative');
|
||||
return null;
|
||||
}
|
||||
|
||||
private async generateHardwareECCKeyPair(keyAlias: string): Promise<string> {
|
||||
// This would call into a native module to generate ECC key in secure enclave
|
||||
// Example: return NativeCryptoModule.generateHardwareECCKeyPair(keyAlias);
|
||||
console.warn('Native module integration required for generateHardwareECCKeyPair');
|
||||
|
||||
// Mock implementation for development
|
||||
return JSON.stringify({
|
||||
kty: 'EC',
|
||||
crv: 'P-256',
|
||||
x: 'mock-x-coordinate',
|
||||
y: 'mock-y-coordinate',
|
||||
use: 'sig',
|
||||
kid: keyAlias
|
||||
});
|
||||
}
|
||||
|
||||
private async signJWTWithNative(payload: any, keyAlias: string): Promise<string> {
|
||||
// This would call into a native module to sign JWT with hardware key
|
||||
// Example: return NativeCryptoModule.signJWT(payload, keyAlias);
|
||||
console.warn('Native module integration required for signJWTWithNative');
|
||||
|
||||
// Mock implementation for development
|
||||
const header = { alg: 'ES256', typ: 'dpop+jwt', jwk: JSON.parse(this.publicKey!) };
|
||||
const encodedHeader = this.base64url(JSON.stringify(header));
|
||||
const encodedPayload = this.base64url(JSON.stringify(payload));
|
||||
const signature = 'mock-signature'; // Would be actual signature from secure enclave
|
||||
|
||||
return `${encodedHeader}.${encodedPayload}.${signature}`;
|
||||
}
|
||||
|
||||
// Utility methods
|
||||
private base64url(str: string): string {
|
||||
return Buffer.from(str)
|
||||
.toString('base64')
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=/g, '');
|
||||
}
|
||||
|
||||
private sha256(str: string): string {
|
||||
// This would use a proper crypto library
|
||||
console.warn('Proper SHA256 implementation required');
|
||||
return 'mock-hash';
|
||||
}
|
||||
}
|
||||
165
packages/core/security/src/SecurityCore.ts
Normal file
165
packages/core/security/src/SecurityCore.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { DPoPService } from './DPoPService';
|
||||
import { CryptoService } from './CryptoService';
|
||||
import { KeyManagementService } from './KeyManagementService';
|
||||
import type { SecurityConfig, EncryptionResult, KeyReference } from './types';
|
||||
|
||||
/**
|
||||
* Core Security SDK
|
||||
* Provides hardware-backed cryptography, DPoP implementation, and contextual encryption
|
||||
*
|
||||
* Key Features (per FRD):
|
||||
* - F.SC.001: Cryptographic utilities (AES-GCM envelope encryption)
|
||||
* - F.SC.002: Hardware-backed key storage (Keychain/StrongBox)
|
||||
* - F.SC.003: Certificate Pinning logic
|
||||
* - F.SC.004: DPoP proof generation
|
||||
* - F.SC.005: Organizational Data Encryption Keys (DEKs) management
|
||||
* - F.SC.006: Secure wipe/cryptographic erasure
|
||||
* - F.SC.007: Device attestation integration
|
||||
*/
|
||||
export class SecurityCore {
|
||||
private config: SecurityConfig;
|
||||
private dpopService: DPoPService;
|
||||
private cryptoService: CryptoService;
|
||||
private keyManagement: KeyManagementService;
|
||||
private initialized = false;
|
||||
|
||||
constructor(config: SecurityConfig) {
|
||||
this.config = config;
|
||||
this.dpopService = new DPoPService();
|
||||
this.cryptoService = new CryptoService();
|
||||
this.keyManagement = new KeyManagementService(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the Security Core
|
||||
* Must be called before any other operations
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
if (this.initialized) return;
|
||||
|
||||
await this.keyManagement.initialize();
|
||||
await this.dpopService.initializeKeyPair();
|
||||
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* F.SC.004: Generate DPoP proof for request authentication
|
||||
*/
|
||||
async signDPoPProof(method: string, uri: string, accessToken?: string): Promise<string> {
|
||||
this.ensureInitialized();
|
||||
return this.dpopService.signProof(method, uri, accessToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* F.SC.001: Encrypt data using organizational DEK (envelope encryption)
|
||||
*/
|
||||
async encryptForOrganization(data: string, orgId: string): Promise<EncryptionResult> {
|
||||
this.ensureInitialized();
|
||||
|
||||
// Get or create organizational DEK
|
||||
const orgKeyRef = await this.keyManagement.getOrganizationalKey(orgId);
|
||||
|
||||
// Generate random file encryption key
|
||||
const fileKey = await this.cryptoService.generateKey();
|
||||
|
||||
// Encrypt data with file key
|
||||
const encryptedData = await this.cryptoService.encrypt(data, fileKey);
|
||||
|
||||
// Wrap file key with organizational DEK
|
||||
const wrappedKey = await this.cryptoService.wrapKey(fileKey, orgKeyRef.keyId);
|
||||
|
||||
return {
|
||||
encryptedData: encryptedData.ciphertext,
|
||||
keyReference: {
|
||||
keyId: wrappedKey,
|
||||
orgId,
|
||||
keyType: 'dek',
|
||||
createdAt: new Date().toISOString()
|
||||
},
|
||||
iv: encryptedData.iv,
|
||||
authTag: encryptedData.authTag
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt data using organizational DEK
|
||||
*/
|
||||
async decryptForOrganization(encryptedData: string, keyReference: KeyReference): Promise<string> {
|
||||
this.ensureInitialized();
|
||||
|
||||
if (!keyReference.orgId) {
|
||||
throw new Error('Organization ID required for decryption');
|
||||
}
|
||||
|
||||
// Get organizational DEK
|
||||
const orgKeyRef = await this.keyManagement.getOrganizationalKey(keyReference.orgId);
|
||||
|
||||
// Unwrap file key
|
||||
const fileKey = await this.cryptoService.unwrapKey(keyReference.keyId, orgKeyRef.keyId);
|
||||
|
||||
// Decrypt data
|
||||
return this.cryptoService.decrypt(encryptedData, fileKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* F.SC.002: Get database master key from secure storage
|
||||
*/
|
||||
async getDatabaseMasterKey(): Promise<string> {
|
||||
this.ensureInitialized();
|
||||
return this.keyManagement.getDatabaseMasterKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* F.SC.005: Store authentication tokens securely
|
||||
*/
|
||||
async storeAuthTokens(accessToken: string, refreshToken: string): Promise<void> {
|
||||
this.ensureInitialized();
|
||||
await this.keyManagement.storeSecureValue('auth.access_token', accessToken);
|
||||
await this.keyManagement.storeSecureValue('auth.refresh_token', refreshToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stored authentication tokens
|
||||
*/
|
||||
async getAuthTokens(): Promise<{ accessToken?: string; refreshToken?: string }> {
|
||||
this.ensureInitialized();
|
||||
|
||||
const [accessToken, refreshToken] = await Promise.all([
|
||||
this.keyManagement.getSecureValue('auth.access_token'),
|
||||
this.keyManagement.getSecureValue('auth.refresh_token')
|
||||
]);
|
||||
|
||||
return { accessToken, refreshToken };
|
||||
}
|
||||
|
||||
/**
|
||||
* F.SC.006: Cryptographic erasure - delete organizational keys
|
||||
*/
|
||||
async performCryptographicErasure(orgId: string): Promise<void> {
|
||||
this.ensureInitialized();
|
||||
await this.keyManagement.deleteOrganizationalKeys(orgId);
|
||||
}
|
||||
|
||||
/**
|
||||
* F.SC.006: Complete secure wipe - delete all keys
|
||||
*/
|
||||
async performSecureWipe(): Promise<void> {
|
||||
this.ensureInitialized();
|
||||
await this.keyManagement.secureWipe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign arbitrary data with identity key
|
||||
*/
|
||||
async signData(data: string): Promise<string> {
|
||||
this.ensureInitialized();
|
||||
return this.dpopService.signData(data);
|
||||
}
|
||||
|
||||
private ensureInitialized(): void {
|
||||
if (!this.initialized) {
|
||||
throw new Error('SecurityCore must be initialized before use');
|
||||
}
|
||||
}
|
||||
}
|
||||
11
packages/core/security/src/index.ts
Normal file
11
packages/core/security/src/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export { SecurityCore } from './SecurityCore';
|
||||
export { DPoPService } from './DPoPService';
|
||||
export { CryptoService } from './CryptoService';
|
||||
export { KeyManagementService } from './KeyManagementService';
|
||||
export type {
|
||||
SecurityConfig,
|
||||
DPoPProof,
|
||||
EncryptionResult,
|
||||
KeyReference,
|
||||
SecurityLevel
|
||||
} from './types';
|
||||
39
packages/core/security/src/types.ts
Normal file
39
packages/core/security/src/types.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
export interface SecurityConfig {
|
||||
enableHardwareBackedStorage: boolean;
|
||||
requireSecureEnclave: boolean;
|
||||
enableCertificatePinning: boolean;
|
||||
allowDebugging: boolean;
|
||||
}
|
||||
|
||||
export interface DPoPProof {
|
||||
jwt: string;
|
||||
publicKey: string;
|
||||
algorithm: string;
|
||||
}
|
||||
|
||||
export interface EncryptionResult {
|
||||
encryptedData: string;
|
||||
keyReference: KeyReference;
|
||||
iv: string;
|
||||
authTag: string;
|
||||
}
|
||||
|
||||
export interface KeyReference {
|
||||
keyId: string;
|
||||
orgId?: string;
|
||||
keyType: 'master' | 'dek' | 'dpop' | 'identity';
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export enum SecurityLevel {
|
||||
SOFTWARE = 'software',
|
||||
SECURE_HARDWARE = 'secure_hardware',
|
||||
SECURE_ENCLAVE = 'secure_enclave'
|
||||
}
|
||||
|
||||
export interface AttestationResult {
|
||||
status: 'VALID' | 'INVALID' | 'UNKNOWN';
|
||||
deviceCheck?: any;
|
||||
playIntegrity?: any;
|
||||
timestamp: string;
|
||||
}
|
||||
26
packages/core/storage/package.json
Normal file
26
packages/core/storage/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "@core/storage",
|
||||
"version": "1.0.0",
|
||||
"description": "Core storage SDK for encrypted RxDB/SQLCipher database management",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"scripts": {
|
||||
"build": "nx build",
|
||||
"test": "nx test",
|
||||
"lint": "nx lint",
|
||||
"typecheck": "nx typecheck"
|
||||
},
|
||||
"dependencies": {
|
||||
"rxdb": "^15.0.0",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@core/security": "workspace:*"
|
||||
},
|
||||
"nx": {
|
||||
"tags": ["scope:core", "type:infra", "platform:rn"]
|
||||
}
|
||||
}
|
||||
220
packages/core/storage/src/StorageCore.ts
Normal file
220
packages/core/storage/src/StorageCore.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
import type { SecurityCore } from '@core/security';
|
||||
import { DatabaseProvider } from './DatabaseProvider';
|
||||
import { SchemaRegistry } from './SchemaRegistry';
|
||||
import type { StorageConfig, CollectionSchema, DatabaseInstance } from './types';
|
||||
|
||||
/**
|
||||
* Core Storage SDK
|
||||
* Provides encrypted local database management with RxDB/SQLCipher
|
||||
*
|
||||
* Key Features (per FRD):
|
||||
* - F.STC.001: Abstraction layer over RxDB/SQLite with SQLCipher encryption
|
||||
* - F.STC.002: Schema registration for feature SDKs
|
||||
* - F.STC.003: Conflict resolution strategies (CRDT configuration, LWW handling)
|
||||
* - F.STC.004: Encrypted file vault for local media storage
|
||||
* - F.STC.005: Automated database schema migration
|
||||
* - F.STC.006: Incremental encryption re-keying
|
||||
* - F.STC.007: Selective purge mechanisms
|
||||
*/
|
||||
export class StorageCore {
|
||||
private config: StorageConfig;
|
||||
private securityCore: SecurityCore;
|
||||
private databaseProvider: DatabaseProvider;
|
||||
private schemaRegistry: SchemaRegistry;
|
||||
private initialized = false;
|
||||
|
||||
constructor(config: StorageConfig, securityCore: SecurityCore) {
|
||||
this.config = config;
|
||||
this.securityCore = securityCore;
|
||||
this.databaseProvider = new DatabaseProvider(config, securityCore);
|
||||
this.schemaRegistry = new SchemaRegistry();
|
||||
}
|
||||
|
||||
/**
|
||||
* F.STC.001: Initialize the storage layer with encryption
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
if (this.initialized) return;
|
||||
|
||||
await this.databaseProvider.initialize();
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* F.STC.002: Register schema for feature SDKs
|
||||
*/
|
||||
registerSchema(collectionName: string, schema: CollectionSchema): void {
|
||||
this.schemaRegistry.register(collectionName, schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database instance
|
||||
*/
|
||||
getDatabase(): DatabaseInstance {
|
||||
this.ensureInitialized();
|
||||
return this.databaseProvider.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* F.STC.002: Create collection with registered schema
|
||||
*/
|
||||
async createCollection(name: string): Promise<void> {
|
||||
this.ensureInitialized();
|
||||
|
||||
const schema = this.schemaRegistry.getSchema(name);
|
||||
if (!schema) {
|
||||
throw new Error(`Schema not registered for collection: ${name}`);
|
||||
}
|
||||
|
||||
await this.databaseProvider.createCollection(name, schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* F.STC.004: Store encrypted file
|
||||
*/
|
||||
async storeEncryptedFile(
|
||||
filename: string,
|
||||
data: Buffer,
|
||||
mimeType: string,
|
||||
orgId?: string
|
||||
): Promise<string> {
|
||||
this.ensureInitialized();
|
||||
|
||||
// Encrypt file content
|
||||
const encryptResult = orgId
|
||||
? await this.securityCore.encryptForOrganization(data.toString('base64'), orgId)
|
||||
: await this.encryptFileLocally(data);
|
||||
|
||||
// Store encrypted file to filesystem
|
||||
const encryptedPath = await this.writeEncryptedFile(filename, encryptResult.encryptedData);
|
||||
|
||||
// Store metadata in database
|
||||
const fileId = this.generateFileId();
|
||||
const metadata = {
|
||||
id: fileId,
|
||||
filename,
|
||||
mimeType,
|
||||
size: data.length,
|
||||
encryptedPath,
|
||||
keyReference: JSON.stringify(encryptResult.keyReference),
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
const db = this.getDatabase();
|
||||
await db.collections.fileVault.insert(metadata);
|
||||
|
||||
return fileId;
|
||||
}
|
||||
|
||||
/**
|
||||
* F.STC.004: Retrieve encrypted file
|
||||
*/
|
||||
async retrieveEncryptedFile(fileId: string, orgId?: string): Promise<Buffer> {
|
||||
this.ensureInitialized();
|
||||
|
||||
const db = this.getDatabase();
|
||||
const metadata = await db.collections.fileVault.findOne(fileId).exec();
|
||||
|
||||
if (!metadata) {
|
||||
throw new Error(`File not found: ${fileId}`);
|
||||
}
|
||||
|
||||
// Read encrypted file
|
||||
const encryptedData = await this.readEncryptedFile(metadata.encryptedPath);
|
||||
const keyReference = JSON.parse(metadata.keyReference);
|
||||
|
||||
// Decrypt file content
|
||||
const decryptedData = orgId
|
||||
? await this.securityCore.decryptForOrganization(encryptedData, keyReference)
|
||||
: await this.decryptFileLocally(encryptedData, keyReference);
|
||||
|
||||
return Buffer.from(decryptedData, 'base64');
|
||||
}
|
||||
|
||||
/**
|
||||
* F.STC.007: Selective purge by organization
|
||||
*/
|
||||
async purgeOrganizationData(orgId: string): Promise<void> {
|
||||
this.ensureInitialized();
|
||||
|
||||
const db = this.getDatabase();
|
||||
|
||||
// Find all collections and purge org-specific data
|
||||
for (const [collectionName, collection] of Object.entries(db.collections)) {
|
||||
// Remove documents with matching orgId
|
||||
await collection.find({ orgId }).remove();
|
||||
}
|
||||
|
||||
// Purge encrypted files belonging to organization
|
||||
const orgFiles = await db.collections.fileVault
|
||||
.find()
|
||||
.where('keyReference')
|
||||
.regex(new RegExp(orgId))
|
||||
.exec();
|
||||
|
||||
for (const file of orgFiles) {
|
||||
await this.deleteEncryptedFile(file.encryptedPath);
|
||||
await file.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* F.STC.006: Re-encrypt data with new keys
|
||||
*/
|
||||
async reEncryptData(orgId: string, newKeyReference: any): Promise<void> {
|
||||
this.ensureInitialized();
|
||||
|
||||
// This would implement re-encryption of existing data
|
||||
// when organizational keys are rotated
|
||||
console.warn('Re-encryption implementation needed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sync status for collections
|
||||
*/
|
||||
getSyncStatus(collectionName: string): any {
|
||||
this.ensureInitialized();
|
||||
|
||||
const db = this.getDatabase();
|
||||
return db.collections[collectionName]?.find({ syncStatus: 'PENDING' });
|
||||
}
|
||||
|
||||
private async encryptFileLocally(data: Buffer): Promise<any> {
|
||||
// Implementation would use local encryption for non-org files
|
||||
throw new Error('Local file encryption not implemented');
|
||||
}
|
||||
|
||||
private async decryptFileLocally(encryptedData: string, keyReference: any): Promise<string> {
|
||||
// Implementation would use local decryption for non-org files
|
||||
throw new Error('Local file decryption not implemented');
|
||||
}
|
||||
|
||||
private async writeEncryptedFile(filename: string, encryptedData: string): Promise<string> {
|
||||
// Implementation would write to secure app documents directory
|
||||
const path = `/secure/${this.generateFileId()}_${filename}`;
|
||||
console.warn('File system integration needed');
|
||||
return path;
|
||||
}
|
||||
|
||||
private async readEncryptedFile(path: string): Promise<string> {
|
||||
// Implementation would read from secure app documents directory
|
||||
console.warn('File system integration needed');
|
||||
return 'mock-encrypted-data';
|
||||
}
|
||||
|
||||
private async deleteEncryptedFile(path: string): Promise<void> {
|
||||
// Implementation would securely delete file
|
||||
console.warn('Secure file deletion needed');
|
||||
}
|
||||
|
||||
private generateFileId(): string {
|
||||
return `file_${Date.now()}_${Math.random().toString(36).substring(2)}`;
|
||||
}
|
||||
|
||||
private ensureInitialized(): void {
|
||||
if (!this.initialized) {
|
||||
throw new Error('StorageCore must be initialized before use');
|
||||
}
|
||||
}
|
||||
}
|
||||
9
packages/core/storage/src/index.ts
Normal file
9
packages/core/storage/src/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export { StorageCore } from './StorageCore';
|
||||
export { DatabaseProvider } from './DatabaseProvider';
|
||||
export { SchemaRegistry } from './SchemaRegistry';
|
||||
export type {
|
||||
StorageConfig,
|
||||
CollectionSchema,
|
||||
MigrationStrategy,
|
||||
ConflictResolutionStrategy
|
||||
} from './types';
|
||||
42
packages/core/storage/src/types.ts
Normal file
42
packages/core/storage/src/types.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import type { RxDatabase, RxCollection } from 'rxdb';
|
||||
|
||||
export interface StorageConfig {
|
||||
databaseName: string;
|
||||
enableEncryption: boolean;
|
||||
enableCRDT: boolean;
|
||||
migrationStrategy: 'drop' | 'migrate';
|
||||
}
|
||||
|
||||
export interface CollectionSchema {
|
||||
version: number;
|
||||
title: string;
|
||||
type: 'object';
|
||||
properties: Record<string, any>;
|
||||
required?: string[];
|
||||
indexes?: string[];
|
||||
migrationStrategies?: Record<number, MigrationStrategy>;
|
||||
conflictResolution?: ConflictResolutionStrategy;
|
||||
}
|
||||
|
||||
export type MigrationStrategy = (oldDoc: any) => any;
|
||||
|
||||
export interface ConflictResolutionStrategy {
|
||||
type: 'crdt' | 'lww' | 'custom';
|
||||
resolver?: (conflicts: any[]) => any;
|
||||
}
|
||||
|
||||
export interface DatabaseInstance {
|
||||
database: RxDatabase;
|
||||
collections: Record<string, RxCollection>;
|
||||
}
|
||||
|
||||
export interface EncryptedFileMetadata {
|
||||
id: string;
|
||||
filename: string;
|
||||
mimeType: string;
|
||||
size: number;
|
||||
encryptedPath: string;
|
||||
keyReference: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
7
packages/core/sync/package.json
Normal file
7
packages/core/sync/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "@core/sync",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "src/index.ts",
|
||||
"nx": { "tags": ["scope:core","type:infra"] }
|
||||
}
|
||||
3
packages/core/sync/src/index.ts
Normal file
3
packages/core/sync/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const SyncCore = {
|
||||
// placeholder sync core
|
||||
};
|
||||
7
packages/core/trust/package.json
Normal file
7
packages/core/trust/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "@core/trust",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "src/index.ts",
|
||||
"nx": { "tags": ["scope:core","type:infra"] }
|
||||
}
|
||||
1
packages/core/trust/src/index.ts
Normal file
1
packages/core/trust/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const TrustCore = {};
|
||||
Reference in New Issue
Block a user