Assist_Design/docs/_archive/refactoring/auth-schema-improvements.md

271 lines
7.4 KiB
Markdown
Raw Normal View History

# Auth Schema Improvements - Explanation
## Problems Identified and Fixed
### 1. **`z.unknown()` Type Safety Issue** ❌ → ✅
**Problem:**
```typescript
// BEFORE - NO TYPE SAFETY
export const signupResultSchema = z.object({
user: z.unknown(), // ❌ Loses all type information and validation
tokens: authTokensSchema,
});
```
**Why it was wrong:**
- `z.unknown()` provides **zero validation** at runtime
- TypeScript can't infer proper types from it
- Defeats the purpose of schema-first architecture
- Any object could pass validation, even malformed data
**Solution:**
```typescript
// AFTER - FULL TYPE SAFETY
export const userProfileSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
role: z.enum(["USER", "ADMIN"]),
// ... all fields properly typed
});
export const signupResultSchema = z.object({
user: userProfileSchema, // ✅ Full validation and type inference
tokens: authTokensSchema,
});
```
**Benefits:**
- Runtime validation of user data structure
- Proper TypeScript type inference
- Catches invalid data early
- Self-documenting API contract
---
### 2. **Email Validation Issues** ❌ → ✅
**Problem:**
The `z.string().email()` in `checkPasswordNeededResponseSchema` was correct, but using `z.unknown()` elsewhere meant emails weren't being validated in user objects.
**Solution:**
```typescript
export const userProfileSchema = z.object({
email: z.string().email(), // ✅ Validates email format
// ...
});
```
Now all user objects have their emails properly validated.
---
### 3. **UserProfile Alias Confusion** 🤔 → ✅
**Problem:**
```typescript
export type UserProfile = AuthenticatedUser;
```
This created confusion about why we have two names for the same thing.
**Solution - Added Clear Documentation:**
```typescript
/**
* UserProfile type alias
*
* Note: This is an alias for backward compatibility.
* Both types represent the same thing: a complete user profile with auth state.
*
* Architecture:
* - PortalUser: Only auth state from portal DB (id, email, role, emailVerified, etc.)
* - CustomerProfile: Profile data from WHMCS (firstname, lastname, address, etc.)
* - AuthenticatedUser/UserProfile: CustomerProfile + auth state = complete user
*/
export type UserProfile = AuthenticatedUser;
```
**Why the alias exists:**
- **Backward compatibility**: Code may use either name
- **Domain language**: "UserProfile" is more business-friendly than "AuthenticatedUser"
- **Convention**: Many authentication libraries use "UserProfile"
---
## Architecture Explanation
### Three-Layer User Model
```
┌─────────────────────────────────────────────┐
│ AuthenticatedUser / UserProfile │
│ (Complete User) │
│ ┌────────────────────────────────────────┐ │
│ │ CustomerProfile (WHMCS) │ │
│ │ - firstname, lastname │ │
│ │ - address, phone │ │
│ │ - language, currency │ │
│ └────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────┐ │
│ │ PortalUser (Portal DB) │ │
│ │ - id, email │ │
│ │ - role, emailVerified, mfaEnabled │ │
│ │ - lastLoginAt │ │
│ └────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
```
### 1. **PortalUser** (Portal Database)
- **Purpose**: Authentication state only
- **Source**: Portal's Prisma database
- **Fields**: id, email, role, emailVerified, mfaEnabled, lastLoginAt
- **Schema**: `portalUserSchema`
- **Use Cases**: JWT validation, token refresh, auth checks
### 2. **CustomerProfile** (WHMCS)
- **Purpose**: Business profile data
- **Source**: WHMCS API (single source of truth for profile data)
- **Fields**: firstname, lastname, address, phone, language, currency
- **Schema**: Interface only (external system)
- **Use Cases**: Displaying user info, profile updates
### 3. **AuthenticatedUser / UserProfile** (Combined)
- **Purpose**: Complete user representation
- **Source**: Portal DB + WHMCS (merged)
- **Fields**: All fields from PortalUser + CustomerProfile
- **Schema**: `userProfileSchema` (for validation)
- **Use Cases**: API responses, full user context
---
## Why This Matters
### Before (with `z.unknown()`):
```typescript
// Could pass completely invalid data ❌
const badData = {
user: { totally: "wrong", structure: true },
tokens: {
/* ... */
},
};
// Would validate successfully! ❌
```
### After (with proper schema):
```typescript
// Validates structure correctly ✅
const goodData = {
user: {
id: "uuid-here",
email: "valid@email.com",
role: "USER",
// ... all required fields
},
tokens: {
/* ... */
},
};
// Validates ✅
const badData = {
user: { wrong: "structure" },
tokens: {
/* ... */
},
};
// Throws validation error ✅
```
---
## Implementation Details
### Schema Definition Location
The `userProfileSchema` is defined **before** it's used:
```typescript
// 1. First, define the schema
export const userProfileSchema = z.object({
// All fields defined
});
// 2. Then use it in response schemas
export const authResponseSchema = z.object({
user: userProfileSchema, // ✅ Now defined
tokens: authTokensSchema,
});
```
### Type Inference
TypeScript now properly infers types:
```typescript
// Type is inferred from schema
type InferredUser = z.infer<typeof userProfileSchema>;
// Results in proper type:
// {
// id: string;
// email: string;
// role: "USER" | "ADMIN";
// emailVerified: boolean;
// // ... etc
// }
```
---
## Migration Impact
### ⚠️ Potential Breaking Change (Security Improvement)
Auth endpoints now return **session metadata** instead of **token strings**:
- **Before**: `{ user, tokens: { accessToken, refreshToken, expiresAt, refreshExpiresAt } }`
- **After**: `{ user, session: { expiresAt, refreshExpiresAt } }`
Token strings are delivered via **httpOnly cookies** only.
### ✅ Improved Type Safety
- Better autocomplete in IDEs
- Compile-time error checking
- Runtime validation of API responses
### ✅ Better Documentation
- Schema serves as living documentation
- Clear separation of concerns
- Self-describing data structures
---
## Summary
| Issue | Before | After |
| ------------------- | ---------------- | ---------------------- |
| User validation | `z.unknown()` ❌ | `userProfileSchema` ✅ |
| Type safety | None | Full |
| Runtime validation | None | Complete |
| Documentation | Unclear | Self-documenting |
| Email validation | Partial | Complete |
| UserProfile clarity | Confusing | Documented |
**Result**: Proper schema-first architecture with full type safety and validation! 🎉