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,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 = {};