154 lines
4.8 KiB
Markdown
154 lines
4.8 KiB
Markdown
# Microsoft OAuth Token Management Analysis
|
|
|
|
## Current Implementation
|
|
|
|
### Token Storage Locations
|
|
|
|
1. **Redis Cache** (Primary for OAuth tokens)
|
|
- **Location**: `lib/redis.ts` → `cacheEmailCredentials()`
|
|
- **TTL**: 24 hours (`TTL.CREDENTIALS = 60 * 60 * 24`)
|
|
- **Stored**: `accessToken`, `refreshToken`, `tokenExpiry`, `useOAuth`
|
|
- **Key Format**: `email:credentials:${userId}:${accountId}`
|
|
|
|
2. **Prisma Database** (Schema has fields but NOT used for OAuth tokens)
|
|
- **Location**: `prisma/schema.prisma` → `MailCredentials` model
|
|
- **Fields Available**: `refresh_token`, `access_token`, `token_expiry`, `use_oauth`
|
|
- **Current Status**: ❌ **Tokens are NOT saved to Prisma** (only Redis)
|
|
- **Code Comment**: "OAuth fields don't exist" (but they DO exist in schema!)
|
|
|
|
### Token Refresh Flow
|
|
|
|
**Location**: `lib/services/token-refresh.ts` → `ensureFreshToken()`
|
|
|
|
1. Checks Redis for credentials
|
|
2. Validates token expiry (5-minute buffer)
|
|
3. Refreshes token if needed via Microsoft API
|
|
4. **Updates Redis only** (not Prisma)
|
|
5. Returns new access token
|
|
|
|
### Issues Identified
|
|
|
|
#### 🔴 Critical Issue #1: Refresh Tokens Not Persisted to Database
|
|
|
|
**Problem**:
|
|
- Refresh tokens are only stored in Redis with 24-hour TTL
|
|
- If Redis is cleared, restarted, or TTL expires, refresh tokens are **permanently lost**
|
|
- Microsoft refresh tokens can last up to **90 days** (or indefinitely with `offline_access` scope)
|
|
- Users would need to re-authenticate if Redis data is lost
|
|
|
|
**Impact**:
|
|
- ❌ Not viable for long-term production use
|
|
- ❌ Data loss risk on Redis restarts
|
|
- ❌ No backup/recovery mechanism
|
|
|
|
#### 🟡 Issue #2: Token Refresh Doesn't Update Database
|
|
|
|
**Problem**:
|
|
- When tokens are refreshed, only Redis is updated
|
|
- Prisma database still has old/expired tokens (if any)
|
|
- Schema has the fields but they're never populated
|
|
|
|
**Impact**:
|
|
- ⚠️ Inconsistency between Redis and Database
|
|
- ⚠️ Can't recover from Redis cache loss
|
|
|
|
#### 🟡 Issue #3: Missing Refresh Token in Logs
|
|
|
|
From your logs:
|
|
```
|
|
hasRefreshToken: false
|
|
```
|
|
|
|
This suggests the refresh token might not be properly saved or retrieved.
|
|
|
|
### Microsoft OAuth Token Lifespan
|
|
|
|
- **Access Token**: ~1 hour (3600 seconds)
|
|
- **Refresh Token**: Up to 90 days (with `offline_access` scope)
|
|
- **Token Refresh**: Returns new access token, may return new refresh token
|
|
|
|
### Required Scopes
|
|
|
|
Current implementation uses:
|
|
```typescript
|
|
const REQUIRED_SCOPES = [
|
|
'offline_access', // ✅ Required for long-lived refresh tokens
|
|
'https://outlook.office.com/IMAP.AccessAsUser.All',
|
|
'https://outlook.office.com/SMTP.Send'
|
|
].join(' ');
|
|
```
|
|
|
|
✅ `offline_access` is included - this is correct for long-term use.
|
|
|
|
## Recommendations
|
|
|
|
### ✅ Fix #1: Persist Refresh Tokens to Prisma
|
|
|
|
**Why**: Refresh tokens are critical for long-term access and should be persisted to database.
|
|
|
|
**Implementation**:
|
|
1. Save `refresh_token` to Prisma `MailCredentials.refresh_token` field
|
|
2. Update `token_expiry` when tokens are refreshed
|
|
3. Keep access tokens in Redis (short-lived, can be regenerated)
|
|
4. Use Prisma as source of truth for refresh tokens
|
|
|
|
### ✅ Fix #2: Update Database on Token Refresh
|
|
|
|
**Why**: Keep database in sync with refreshed tokens.
|
|
|
|
**Implementation**:
|
|
1. After refreshing tokens, update Prisma `MailCredentials` record
|
|
2. Update `access_token` and `token_expiry` fields
|
|
3. Update `refresh_token` if Microsoft returns a new one
|
|
|
|
### ✅ Fix #3: Fallback to Database if Redis Missing
|
|
|
|
**Why**: Recover from Redis cache loss.
|
|
|
|
**Implementation**:
|
|
1. If Redis cache is empty, check Prisma for refresh token
|
|
2. Use Prisma refresh token to get new access token
|
|
3. Re-populate Redis cache
|
|
|
|
## Long-Term Viability Assessment
|
|
|
|
### Current State: ⚠️ **NOT VIABLE** for long-term production
|
|
|
|
**Reasons**:
|
|
1. ❌ Refresh tokens only in volatile Redis cache
|
|
2. ❌ No persistence mechanism
|
|
3. ❌ Risk of data loss on Redis restart
|
|
4. ❌ No recovery mechanism
|
|
|
|
### After Fixes: ✅ **VIABLE** for long-term production
|
|
|
|
**With recommended fixes**:
|
|
1. ✅ Refresh tokens persisted to database
|
|
2. ✅ Redis used for fast access token retrieval
|
|
3. ✅ Database as source of truth
|
|
4. ✅ Recovery mechanism in place
|
|
|
|
## Token Storage Strategy (Recommended)
|
|
|
|
### Access Tokens
|
|
- **Storage**: Redis (fast, short-lived)
|
|
- **TTL**: 1 hour (matches Microsoft token expiry)
|
|
- **Purpose**: Fast IMAP/SMTP authentication
|
|
|
|
### Refresh Tokens
|
|
- **Storage**: Prisma Database (persistent, long-term)
|
|
- **TTL**: None (stored indefinitely until revoked)
|
|
- **Purpose**: Long-term access, token renewal
|
|
|
|
### Token Expiry
|
|
- **Storage**: Both Redis and Prisma
|
|
- **Purpose**: Know when to refresh tokens
|
|
|
|
## Implementation Priority
|
|
|
|
1. **HIGH**: Persist refresh tokens to Prisma
|
|
2. **HIGH**: Update Prisma on token refresh
|
|
3. **MEDIUM**: Add fallback to database if Redis missing
|
|
4. **LOW**: Add token encryption at rest (if required by compliance)
|
|
|