First commit

This commit is contained in:
Sachin
2025-12-16 22:26:18 +05:30
commit 03ed187ebe
122 changed files with 68601 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
{
"name": "@core/analytics",
"version": "1.0.0",
"private": true,
"main": "src/index.ts",
"nx": { "tags": ["scope:core","type:infra"] }
}

View File

@@ -0,0 +1 @@
export const AnalyticsCore = {};

View 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"] }
}

View File

@@ -0,0 +1 @@
export const BFFClient = {};

View File

@@ -0,0 +1,7 @@
{
"name": "@core/consent",
"version": "1.0.0",
"private": true,
"main": "src/index.ts",
"nx": { "tags": ["scope:core","type:infra"] }
}

View File

@@ -0,0 +1 @@
export const ConsentCore = {};

View 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"] }
}

View File

@@ -0,0 +1 @@
export const PolicyClient = {};

View File

@@ -0,0 +1,7 @@
{
"name": "@core/runtime",
"version": "1.0.0",
"private": true,
"main": "src/index.ts",
"nx": { "tags": ["scope:core","type:infra"] }
}

View File

@@ -0,0 +1 @@
export const RuntimeCore = {};

View 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"]
}
}

View 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';
}
}

View 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');
}
}
}

View 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';

View 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;
}

View 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"]
}
}

View 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');
}
}
}

View 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';

View 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;
}

View File

@@ -0,0 +1,7 @@
{
"name": "@core/sync",
"version": "1.0.0",
"private": true,
"main": "src/index.ts",
"nx": { "tags": ["scope:core","type:infra"] }
}

View File

@@ -0,0 +1,3 @@
export const SyncCore = {
// placeholder sync core
};

View File

@@ -0,0 +1,7 @@
{
"name": "@core/trust",
"version": "1.0.0",
"private": true,
"main": "src/index.ts",
"nx": { "tags": ["scope:core","type:infra"] }
}

View File

@@ -0,0 +1 @@
export const TrustCore = {};

View File

@@ -0,0 +1,279 @@
# @feature/auth Playbook
## 1. Overview
Handles Authentication (OTP, Magic Link, Federated), Session Management, and Biometrics with risk-adaptive security.
**Key Features (per FRD):**
- F.ID.001: Login, Registration, and Forgot Password flows
- F.ID.002: Configurable verification (OTP, Email, Magic Links)
- F.ID.003: Secure session token storage and rotation
- F.ID.004: Device biometrics integration (FaceID/TouchID/Android Biometrics)
- F.ID.005: Configurable "Remember Me" duration
- F.ID.006: Enhanced Magic Link security with nonce and expiry
- F.ID.007: Federated Login connectors (Google, Apple)
## 2. Setup & Dependencies
**Required Core SDKs:**
- `@core/security` - DPoP key generation and signing
- `@core/trust` - Runtime Risk Score; triggers Step-Up MFA if score is high
- `@core/policy` - Risk-adaptive policy checks
- `@core/storage` - Encrypted session storage
**Installation:**
```bash
# Install auth feature layers
pnpm add @feature/auth/domain @feature/auth/data @feature/auth/ui-rn
# Peer dependencies (automatically resolved in monorepo)
# @core/security @core/trust @core/policy @core/storage
```
## 3. Core Workflow: DPoP Login and Risk Assessment
```mermaid
sequenceDiagram
participant App
participant Auth as @feature/auth
participant Trust as @core/trust
participant Security as @core/security
participant Policy as @core/policy
participant BFF
App->>Auth: login(credentials)
Auth->>Trust: calculateRiskScore()
Trust-->>Auth: { score: 45, signals: {...} }
Auth->>Security: initializeDPoP()
Security-->>Auth: publicKey
Auth->>BFF: exchangeCredentials(creds, pubKey)
BFF-->>Auth: session + tokens
alt Risk Score > Threshold
Auth->>Policy: check('step-up-mfa', context)
Policy-->>Auth: requires_verification: true
Auth-->>App: { success: true, requiresStepUp: true }
else Low Risk
Auth-->>App: { success: true, session }
end
```
## 4. Integration Points
### @core/security Integration
```typescript
// DPoP key generation and signing
const publicKey = await this.authRepository.initializeDPoP();
const signedProof = await this.securityCore.signDPoPProof('POST', '/auth/login', accessToken);
```
### @core/trust Integration
```typescript
// Risk assessment for adaptive authentication
const { score, signals } = await this.trustRepository.calculateRiskScore();
const requiresStepUp = this.shouldRequireStepUp(score, signals);
```
### @core/policy Integration
```typescript
// Risk-adaptive policy checks
const allowed = await this.policyClient.check('login', 'User', {
riskScore: 60,
deviceSignals: signals
});
```
## 5. Policy Enforcement Examples
**Risk-Adaptive Login:**
```typescript
// Low risk (score < 50): Standard login
await policyClient.check('login', 'User', { riskScore: 35 }); // → true
// Medium risk (50-75): Email verification
await policyClient.check('login', 'User', { riskScore: 65 }); // → requires email MFA
// High risk (75+): OTP + Device attestation
await policyClient.check('login', 'User', { riskScore: 85 }); // → requires OTP + attestation
```
**Organization Access:**
```typescript
// Check organization membership with role-based permissions
await policyClient.check('access-org', 'Organization', {
orgId: 'org-123',
userRole: 'member',
riskScore: 40
});
```
## 6. API Usage Examples
### Basic Login Flow
```typescript
import { LoginUseCase } from '@feature/auth/domain';
import { AuthRepository } from '@feature/auth/data';
const authRepo = new AuthRepository(securityCore, bffClient);
const trustRepo = new TrustRepository(trustCore);
const loginUseCase = new LoginUseCase(authRepo, trustRepo, config);
const result = await loginUseCase.execute({
identifier: 'user@example.com',
password: 'secure_password',
deviceId: 'device-uuid'
});
if (result.success) {
if (result.requiresStepUp) {
// Handle step-up MFA flow
console.log('Additional verification required:', result.verificationMethod);
} else {
// Login successful
console.log('User session:', result.session);
}
}
```
### Biometric Authentication
```typescript
import { BiometricUseCase } from '@feature/auth/domain';
const biometricUseCase = new BiometricUseCase(authRepo, biometricConfig);
const result = await biometricUseCase.authenticate({
promptMessage: 'Authenticate to access LynkedUp',
fallbackToPassword: true
});
```
### Magic Link Flow
```typescript
// Generate magic link
const magicLink = await authRepo.generateMagicLink('user@example.com');
// Verify magic link (typically called from deep link handler)
const result = await loginUseCase.executeWithMagicLink(token, nonce);
```
## 7. Configuration
```typescript
const authConfig: AuthConfig = {
requireEmailVerification: true,
requirePhoneVerification: false,
biometrics: {
enabled: true,
fallbackToPassword: true,
promptMessage: 'Authenticate with LynkedUp'
},
rememberMeDays: 30,
maxLoginAttempts: 5,
lockoutDurationMinutes: 15
};
```
## 8. Error Handling
```typescript
// Standard error codes returned by AuthResult
switch (result.errorCode) {
case 'INVALID_CREDENTIALS':
// Handle invalid login
break;
case 'ACCOUNT_LOCKED':
// Handle account lockout
break;
case 'VERIFICATION_REQUIRED':
// Handle unverified account
break;
case 'DEVICE_NOT_TRUSTED':
// Handle untrusted device
break;
case 'POLICY_VIOLATION':
// Handle policy-based denial
break;
}
```
## 9. Testing Strategy
### Unit Tests
```typescript
// Domain layer testing (LoginUseCase)
describe('LoginUseCase', () => {
it('should require step-up for high risk score', async () => {
mockTrustRepo.calculateRiskScore.mockResolvedValue({ score: 85, signals: {} });
const result = await loginUseCase.execute(validCredentials);
expect(result.requiresStepUp).toBe(true);
});
});
```
### Contract Tests
```typescript
// Data layer testing against mocked BFF
import { setupServer } from 'msw/node';
import { graphql } from 'msw';
const server = setupServer(
graphql.mutation('Login', (req, res, ctx) => {
return res(ctx.data({
login: {
accessToken: 'mock-token',
user: mockUser
}
}));
})
);
```
## 10. Security Notes
**Critical Security Requirements:**
- Private keys MUST remain in the Secure Enclave/TEE
- Magic links MUST enforce nonce and expiry (see F.ID.006)
- DPoP proofs MUST be bound to specific HTTP requests
- Session tokens MUST be stored in hardware-backed keychain
- Step-up MFA triggers MUST be policy-driven, not hardcoded
**Risk Signals Handling:**
- Root/Jailbreak detection → Immediate step-up required
- Device attestation failure → Block access + notify admin
- Geolocation deviation → Email verification required
- New device → SMS OTP required
## 11. Troubleshooting
**Common Issues:**
1. **"SecurityCore must be initialized"**
- Ensure `securityCore.initialize()` is called before auth operations
2. **"DPoP key generation failed"**
- Verify hardware-backed storage is available
- Check device security settings (passcode/biometrics enabled)
3. **"Policy evaluation failed"**
- Verify policy bundles are cached locally
- Check network connectivity for policy updates
4. **Biometric authentication unavailable**
- Verify device biometric enrollment
- Check app permissions for biometric access
## 12. Performance Considerations
- **Key Operations**: DPoP key generation (one-time, ~100ms)
- **Risk Assessment**: Device evaluation (~50-200ms depending on signals)
- **Policy Evaluation**: Local cache lookup (~1-5ms)
- **Session Storage**: Keychain operations (~10-50ms)
**Optimization Tips:**
- Cache risk assessment results for 5-10 minutes
- Pre-warm DPoP keys during app initialization
- Batch policy evaluations when possible

View File

@@ -0,0 +1,6 @@
{
"name": "@feature/auth/data",
"version": "1.0.0",
"private": true,
"main": "src/index.ts"
}

View File

@@ -0,0 +1 @@
export const AuthData = {};

View File

@@ -0,0 +1,22 @@
{
"name": "@feature/auth/domain",
"version": "1.0.0",
"description": "Authentication domain logic - pure business rules and use cases",
"main": "./src/index.ts",
"types": "./src/index.ts",
"scripts": {
"build": "nx build",
"test": "nx test",
"lint": "nx lint",
"typecheck": "nx typecheck"
},
"dependencies": {
"uuid": "^9.0.1"
},
"devDependencies": {
"@types/uuid": "^9.0.7"
},
"nx": {
"tags": ["scope:feature", "type:domain", "platform:shared"]
}
}

View File

@@ -0,0 +1,53 @@
export interface User {
id: string;
email: string;
phoneNumber?: string;
firstName: string;
lastName: string;
isEmailVerified: boolean;
isPhoneVerified: boolean;
createdAt: string;
lastLoginAt?: string;
profilePictureUrl?: string;
organizations: UserOrganization[];
preferences: UserPreferences;
}
export interface UserOrganization {
orgId: string;
orgName: string;
role: string;
permissions: string[];
joinedAt: string;
status: 'active' | 'suspended' | 'pending';
}
export interface UserPreferences {
language: string;
timezone: string;
theme: 'light' | 'dark' | 'system';
notifications: NotificationPreferences;
}
export interface NotificationPreferences {
email: boolean;
push: boolean;
sms: boolean;
marketing: boolean;
}
export interface AuthResult {
success: boolean;
session?: any;
requiresVerification?: boolean;
verificationMethod?: VerificationMethod;
errorCode?: string;
errorMessage?: string;
nextSteps?: string[];
}
export type VerificationMethod =
| { type: 'otp'; target: string }
| { type: 'email'; target: string }
| { type: 'biometric' }
| { type: 'magic_link'; target: string };

View File

@@ -0,0 +1,20 @@
export const AuthDomain = {};
import { LoginUseCase } from './usecases/LoginUseCase';
import { RegistrationUseCase } from './usecases/RegistrationUseCase';
import { SessionUseCase } from './usecases/SessionUseCase';
import { BiometricUseCase } from './usecases/BiometricUseCase';
export { LoginUseCase, RegistrationUseCase, SessionUseCase, BiometricUseCase };
export type {
IAuthRepository,
ITrustRepository,
Credentials,
UserSession,
BiometricConfig,
AuthConfig
} from './interfaces';
export type {
User,
AuthResult,
VerificationMethod
} from './entities';

View File

@@ -0,0 +1,76 @@
import type { User, AuthResult } from './entities';
/**
* Repository interface that the data layer must implement
* Following clean architecture principles - domain defines the contract
*/
export interface IAuthRepository {
// F.ID.004: DPoP initialization
initializeDPoP(): Promise<string>;
// F.ID.001: Login flows
exchangeCredentialsForToken(creds: Credentials, pubKey: string): Promise<UserSession>;
// F.ID.002: Verification flows
sendOTPVerification(phoneNumber: string): Promise<void>;
verifyOTP(phoneNumber: string, code: string): Promise<boolean>;
sendEmailVerification(email: string): Promise<void>;
verifyEmail(email: string, token: string): Promise<boolean>;
// Magic Links (F.ID.006 - Enhanced security)
generateMagicLink(email: string): Promise<string>;
verifyMagicLink(token: string, nonce: string): Promise<UserSession>;
// F.ID.007: Federated login
authenticateWithProvider(provider: 'google' | 'apple', token: string): Promise<UserSession>;
// Session management
storeSession(session: UserSession): Promise<void>;
getSession(): Promise<UserSession | null>;
refreshSession(refreshToken: string): Promise<UserSession>;
revokeSession(): Promise<void>;
// F.ID.005: Remember me functionality
enableRememberMe(duration: number): Promise<void>;
disableRememberMe(): Promise<void>;
}
/**
* Trust/Risk assessment interface
*/
export interface ITrustRepository {
calculateRiskScore(): Promise<{ score: number; signals: Record<string, any> }>;
performDeviceAttestation(): Promise<boolean>;
}
export interface Credentials {
identifier: string; // email or phone
password?: string;
biometricSignature?: string;
deviceId: string;
}
export interface UserSession {
userId: string;
accessToken: string;
refreshToken: string;
expiresAt: string;
user: User;
riskScore?: number;
requiresStepUp?: boolean;
}
export interface BiometricConfig {
enabled: boolean;
fallbackToPassword: boolean;
promptMessage: string;
}
export interface AuthConfig {
requireEmailVerification: boolean;
requirePhoneVerification: boolean;
biometrics: BiometricConfig;
rememberMeDays: number;
maxLoginAttempts: number;
lockoutDurationMinutes: number;
}

View File

@@ -0,0 +1,5 @@
export class BiometricUseCase {
async verify(signature: string) {
return false;
}
}

View File

@@ -0,0 +1,177 @@
import type { IAuthRepository, ITrustRepository, Credentials, UserSession, AuthConfig } from '../interfaces';
import type { AuthResult } from '../entities';
/**
* Login Use Case - Pure business logic
* Orchestrates the login flow including risk assessment and DPoP initialization
*
* Features implemented (per FRD):
* - F.ID.001: Login flows
* - F.ID.004: DPoP integration
* - Risk-adaptive authentication
* - Step-up MFA based on risk score
*/
export class LoginUseCase {
constructor(
private authRepository: IAuthRepository,
private trustRepository: ITrustRepository,
private config: AuthConfig
) {}
/**
* Execute login flow with risk assessment
*/
async execute(credentials: Credentials): Promise<AuthResult> {
try {
// 1. Risk Assessment (F.TR.004 from Trust SDK)
const { score: riskScore, signals } = await this.trustRepository.calculateRiskScore();
// 2. Initialize DPoP (F.ID.004 / F.SC.004)
const publicKey = await this.authRepository.initializeDPoP();
// 3. Attempt authentication
const session = await this.authRepository.exchangeCredentialsForToken(credentials, publicKey);
// 4. Enhance session with risk information
const enhancedSession: UserSession = {
...session,
riskScore,
requiresStepUp: this.shouldRequireStepUp(riskScore, signals)
};
// 5. Store session
await this.authRepository.storeSession(enhancedSession);
// 6. Check if step-up authentication is required
if (enhancedSession.requiresStepUp) {
return {
success: true,
session: enhancedSession,
requiresVerification: true,
verificationMethod: this.determineStepUpMethod(signals),
nextSteps: ['Complete additional verification to proceed']
};
}
// 7. Enable Remember Me if configured
if (this.config.rememberMeDays > 0) {
await this.authRepository.enableRememberMe(this.config.rememberMeDays);
}
return {
success: true,
session: enhancedSession
};
} catch (error) {
return this.handleLoginError(error);
}
}
/**
* Biometric login flow
*/
async executeWithBiometrics(biometricSignature: string, deviceId: string): Promise<AuthResult> {
const credentials: Credentials = {
identifier: 'biometric',
biometricSignature,
deviceId
};
return this.execute(credentials);
}
/**
* Magic link login flow (F.ID.006)
*/
async executeWithMagicLink(token: string, nonce: string): Promise<AuthResult> {
try {
const session = await this.authRepository.verifyMagicLink(token, nonce);
await this.authRepository.storeSession(session);
return {
success: true,
session
};
} catch (error) {
return this.handleLoginError(error);
}
}
/**
* Federated login flow (F.ID.007)
*/
async executeWithProvider(provider: 'google' | 'apple', token: string): Promise<AuthResult> {
try {
const session = await this.authRepository.authenticateWithProvider(provider, token);
await this.authRepository.storeSession(session);
return {
success: true,
session
};
} catch (error) {
return this.handleLoginError(error);
}
}
private shouldRequireStepUp(riskScore: number, signals: Record<string, any>): boolean {
// Risk-based step-up logic
if (riskScore > 75) return true;
if (signals.isRooted || signals.isEmulator) return true;
if (signals.attestationFailed) return true;
if (signals.geoDeviation && riskScore > 50) return true;
return false;
}
private determineStepUpMethod(signals: Record<string, any>) {
// Determine the most appropriate step-up method based on risk signals
if (signals.suspiciousLocation) {
return { type: 'otp' as const, target: 'phone' };
}
if (signals.newDevice) {
return { type: 'email' as const, target: 'email' };
}
// Default to biometric if available
return { type: 'biometric' as const };
}
private handleLoginError(error: any): AuthResult {
console.error('Login failed:', error);
// Map specific errors to user-friendly messages
if (error.code === 'INVALID_CREDENTIALS') {
return {
success: false,
errorCode: 'INVALID_CREDENTIALS',
errorMessage: 'Invalid email or password'
};
}
if (error.code === 'ACCOUNT_LOCKED') {
return {
success: false,
errorCode: 'ACCOUNT_LOCKED',
errorMessage: 'Account is temporarily locked due to multiple failed attempts'
};
}
if (error.code === 'VERIFICATION_REQUIRED') {
return {
success: false,
requiresVerification: true,
verificationMethod: error.verificationMethod,
errorMessage: 'Account verification required'
};
}
return {
success: false,
errorCode: 'UNKNOWN_ERROR',
errorMessage: 'An unexpected error occurred'
};
}
}

View File

@@ -0,0 +1,5 @@
export class RegistrationUseCase {
async execute(payload: any) {
return { success: true };
}
}

View File

@@ -0,0 +1,5 @@
export class SessionUseCase {
async getCurrent(sessionId: string) {
return null;
}
}

View File

@@ -0,0 +1,6 @@
{
"name": "@feature/auth/ui-rn",
"version": "1.0.0",
"private": true,
"main": "src/index.ts"
}

View File

@@ -0,0 +1 @@
export const AuthUI = {};

View File

@@ -0,0 +1,6 @@
{
"name": "@feature/location/domain",
"version": "1.0.0",
"private": true,
"main": "src/index.ts"
}

View File

@@ -0,0 +1 @@
export const LocationDomain = {};

View File

@@ -0,0 +1,6 @@
{
"name": "@feature/location/ui-rn",
"version": "1.0.0",
"private": true,
"main": "src/index.ts"
}

View File

@@ -0,0 +1 @@
export const LocationUI = {};

View File

@@ -0,0 +1,6 @@
{
"name": "@feature/media/domain",
"version": "1.0.0",
"private": true,
"main": "src/index.ts"
}

View File

@@ -0,0 +1 @@
export const MediaDomain = {};

View File

@@ -0,0 +1,6 @@
{
"name": "@feature/media/ui-rn",
"version": "1.0.0",
"private": true,
"main": "src/index.ts"
}

View File

@@ -0,0 +1 @@
export const MediaUI = {};

View File

@@ -0,0 +1,6 @@
{
"name": "@feature/memberships/domain",
"version": "1.0.0",
"private": true,
"main": "src/index.ts"
}

View File

@@ -0,0 +1 @@
export const MembershipsDomain = {};

View File

@@ -0,0 +1,6 @@
{
"name": "@feature/memberships/ui-rn",
"version": "1.0.0",
"private": true,
"main": "src/index.ts"
}

View File

@@ -0,0 +1 @@
export const MembershipsUI = {};

View File

@@ -0,0 +1,6 @@
{
"name": "@feature/messaging/data",
"version": "1.0.0",
"private": true,
"main": "src/index.ts"
}

View File

@@ -0,0 +1 @@
export const MessagingData = {};

View File

@@ -0,0 +1,6 @@
{
"name": "@feature/messaging/domain",
"version": "1.0.0",
"private": true,
"main": "src/index.ts"
}

View File

@@ -0,0 +1 @@
export const MessagingDomain = {};

View File

@@ -0,0 +1,6 @@
{
"name": "@feature/messaging/ui-rn",
"version": "1.0.0",
"private": true,
"main": "src/index.ts"
}

View File

@@ -0,0 +1 @@
export const MessagingUI = {};

View File

@@ -0,0 +1,6 @@
{
"name": "@feature/notes/domain",
"version": "1.0.0",
"private": true,
"main": "src/index.ts"
}

View File

@@ -0,0 +1 @@
export const NotesDomain = {};

View File

@@ -0,0 +1,6 @@
{
"name": "@feature/notes/ui-rn",
"version": "1.0.0",
"private": true,
"main": "src/index.ts"
}

View File

@@ -0,0 +1 @@
export const NotesUI = {};

View File

@@ -0,0 +1,6 @@
{
"name": "@feature/tasks/data",
"version": "1.0.0",
"private": true,
"main": "src/index.ts"
}

View File

@@ -0,0 +1 @@
export const TasksData = {};

View File

@@ -0,0 +1,6 @@
{
"name": "@feature/tasks/domain",
"version": "1.0.0",
"private": true,
"main": "src/index.ts"
}

View File

@@ -0,0 +1 @@
export const TasksDomain = {};

View File

@@ -0,0 +1,6 @@
{
"name": "@feature/tasks/ui-rn",
"version": "1.0.0",
"private": true,
"main": "src/index.ts"
}

View File

@@ -0,0 +1 @@
export const TasksUI = {};

View File

@@ -0,0 +1,6 @@
{
"name": "@shared/config",
"version": "1.0.0",
"private": true,
"main": "src/index.ts"
}

View File

@@ -0,0 +1 @@
export const sharedConfig = {};

View File

@@ -0,0 +1,6 @@
{
"name": "@shared/types",
"version": "1.0.0",
"private": true,
"main": "src/index.ts"
}

View File

@@ -0,0 +1 @@
export type AnyRecord = Record<string, any>;

View File

@@ -0,0 +1,6 @@
{
"name": "@shared/ui-kit",
"version": "1.0.0",
"private": true,
"main": "src/index.ts"
}

View File

@@ -0,0 +1 @@
export const UIKIT = {};