proposal: migrate to external API authentication
Create OpenSpec proposal for migrating from local database authentication to external API authentication using Microsoft Azure AD. Changes proposed: - Replace local username/password auth with external API - Integrate with https://pj-auth-api.vercel.app/api/auth/login - Use Azure AD tokens instead of local JWT - Display user 'name' from API response in UI - Maintain backward compatibility with feature flag Benefits: - Single Sign-On (SSO) capability - Leverage enterprise identity management - Reduce local user management overhead - Consistent authentication across applications Database changes: - Add external_user_id for Azure AD user mapping - Add display_name for UI display - Keep existing schema for rollback capability Implementation includes: - Detailed migration plan with phased rollout - Comprehensive task list for implementation - Test script for API validation - Risk assessment and mitigation strategies 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,171 @@
|
|||||||
|
# Change: Migrate to External API Authentication
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
The current local database authentication system has several limitations:
|
||||||
|
- User credentials are managed locally, requiring manual user creation and password management
|
||||||
|
- No centralized authentication with enterprise identity systems
|
||||||
|
- Cannot leverage existing enterprise authentication infrastructure (e.g., Microsoft Azure AD)
|
||||||
|
- No single sign-on (SSO) capability
|
||||||
|
- Increased maintenance overhead for user management
|
||||||
|
|
||||||
|
By migrating to the external API authentication service at https://pj-auth-api.vercel.app, the system will:
|
||||||
|
- Integrate with enterprise Microsoft Azure AD authentication
|
||||||
|
- Enable single sign-on (SSO) for users
|
||||||
|
- Eliminate local password management
|
||||||
|
- Leverage existing enterprise user management and security policies
|
||||||
|
- Reduce maintenance overhead
|
||||||
|
- Provide consistent authentication across multiple applications
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
### Authentication Flow
|
||||||
|
- **Current**: Local database authentication using username/password stored in MySQL
|
||||||
|
- **New**: External API authentication via POST to `https://pj-auth-api.vercel.app/api/auth/login`
|
||||||
|
- **Token Management**: Use JWT tokens from external API instead of locally generated tokens
|
||||||
|
- **User Display**: Use `name` field from API response for user display instead of local username
|
||||||
|
|
||||||
|
### API Integration
|
||||||
|
**Endpoint**: `POST https://pj-auth-api.vercel.app/api/auth/login`
|
||||||
|
|
||||||
|
**Request Format**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "user@domain.com",
|
||||||
|
"password": "user_password"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Success Response (200)**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "認證成功",
|
||||||
|
"data": {
|
||||||
|
"access_token": "eyJ0eXAiOiJKV1Q...",
|
||||||
|
"id_token": "eyJ0eXAiOiJKV1Q...",
|
||||||
|
"expires_in": 4999,
|
||||||
|
"token_type": "Bearer",
|
||||||
|
"userInfo": {
|
||||||
|
"id": "42cf0b98-f598-47dd-ae2a-f33803f87d41",
|
||||||
|
"name": "ymirliu 劉念萱",
|
||||||
|
"email": "ymirliu@panjit.com.tw",
|
||||||
|
"jobTitle": null,
|
||||||
|
"officeLocation": "高雄",
|
||||||
|
"businessPhones": ["1580"]
|
||||||
|
},
|
||||||
|
"issuedAt": "2025-11-14T07:09:15.203Z",
|
||||||
|
"expiresAt": "2025-11-14T08:32:34.203Z"
|
||||||
|
},
|
||||||
|
"timestamp": "2025-11-14T07:09:15.203Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Failure Response (401)**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"error": "用戶名或密碼錯誤",
|
||||||
|
"code": "INVALID_CREDENTIALS",
|
||||||
|
"timestamp": "2025-11-14T07:10:02.585Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Schema Changes
|
||||||
|
- **users table modifications**:
|
||||||
|
- Remove/deprecate `hashed_password` column (keep for rollback)
|
||||||
|
- Add `external_user_id` (VARCHAR 255) - Store Azure AD user ID
|
||||||
|
- Add `display_name` (VARCHAR 255) - Store user display name from API
|
||||||
|
- Add `azure_email` (VARCHAR 255) - Store Azure AD email
|
||||||
|
- Add `last_token_refresh` (DATETIME) - Track token refresh timing
|
||||||
|
- Keep `username` for backward compatibility (can be email)
|
||||||
|
|
||||||
|
### Session Management
|
||||||
|
- Store external API tokens in session/cache instead of local JWT
|
||||||
|
- Implement token refresh mechanism based on `expires_in` field
|
||||||
|
- Use `expiresAt` timestamp for token expiration validation
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
### Affected Capabilities
|
||||||
|
- `authentication`: Complete replacement of authentication mechanism
|
||||||
|
- `user-management`: Simplified to read-only user information from external API
|
||||||
|
- `session-management`: Modified to handle external tokens
|
||||||
|
|
||||||
|
### Affected Code
|
||||||
|
- **Backend Authentication**:
|
||||||
|
- `backend/app/api/v1/endpoints/auth.py`: Replace login logic with external API call
|
||||||
|
- `backend/app/core/security.py`: Modify token validation to use external tokens
|
||||||
|
- `backend/app/core/auth.py`: Update authentication dependencies
|
||||||
|
- `backend/app/services/auth_service.py`: New service for external API integration
|
||||||
|
|
||||||
|
- **Database Models**:
|
||||||
|
- `backend/app/models/user.py`: Update User model with new fields
|
||||||
|
- `backend/alembic/versions/`: New migration for schema changes
|
||||||
|
|
||||||
|
- **Frontend**:
|
||||||
|
- `frontend/src/services/authService.ts`: Update to handle new token format
|
||||||
|
- `frontend/src/stores/authStore.ts`: Modify to store/display user info from API
|
||||||
|
- `frontend/src/components/Header.tsx`: Display `name` field instead of username
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
- Add `httpx` or `aiohttp` for async HTTP requests to external API (already present)
|
||||||
|
- No new package dependencies required
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
- New environment variables:
|
||||||
|
- `EXTERNAL_AUTH_API_URL` = "https://pj-auth-api.vercel.app"
|
||||||
|
- `EXTERNAL_AUTH_ENDPOINT` = "/api/auth/login"
|
||||||
|
- `EXTERNAL_AUTH_TIMEOUT` = 30 (seconds)
|
||||||
|
- `USE_EXTERNAL_AUTH` = true (feature flag for gradual rollout)
|
||||||
|
- `TOKEN_REFRESH_BUFFER` = 300 (refresh tokens 5 minutes before expiry)
|
||||||
|
|
||||||
|
### Security Considerations
|
||||||
|
- HTTPS required for all authentication requests
|
||||||
|
- Token storage must be secure (HTTPOnly cookies or secure session storage)
|
||||||
|
- Implement rate limiting for authentication attempts
|
||||||
|
- Log all authentication events for audit trail
|
||||||
|
- Validate SSL certificates for external API calls
|
||||||
|
- Handle network failures gracefully with appropriate error messages
|
||||||
|
|
||||||
|
### Rollback Strategy
|
||||||
|
- Keep existing authentication code with feature flag
|
||||||
|
- Maintain password column in database (don't drop immediately)
|
||||||
|
- Implement dual authentication mode during transition:
|
||||||
|
- If `USE_EXTERNAL_AUTH=true`: Use external API
|
||||||
|
- If `USE_EXTERNAL_AUTH=false`: Use local authentication
|
||||||
|
- Provide migration script to sync existing users with external system
|
||||||
|
|
||||||
|
### Migration Plan
|
||||||
|
1. **Phase 1**: Implement external API authentication alongside existing system
|
||||||
|
2. **Phase 2**: Test with subset of users (based on domain or user flag)
|
||||||
|
3. **Phase 3**: Gradual rollout to all users
|
||||||
|
4. **Phase 4**: Deprecate local authentication (keep code for emergency)
|
||||||
|
5. **Phase 5**: Remove local authentication code (after stable period)
|
||||||
|
|
||||||
|
## Risks and Mitigations
|
||||||
|
|
||||||
|
### Risks
|
||||||
|
1. **External API Unavailability**: Authentication service downtime blocks all logins
|
||||||
|
- *Mitigation*: Implement fallback to local auth, cache tokens, implement retry logic
|
||||||
|
|
||||||
|
2. **Token Expiration Handling**: Users may be logged out unexpectedly
|
||||||
|
- *Mitigation*: Implement automatic token refresh before expiration
|
||||||
|
|
||||||
|
3. **Network Latency**: Slower authentication due to external API calls
|
||||||
|
- *Mitigation*: Implement proper timeout handling, async requests, response caching
|
||||||
|
|
||||||
|
4. **Data Consistency**: User information mismatch between local DB and external system
|
||||||
|
- *Mitigation*: Regular sync jobs, use external system as single source of truth
|
||||||
|
|
||||||
|
5. **Breaking Change**: Existing sessions will be invalidated
|
||||||
|
- *Mitigation*: Provide migration window, clear communication to users
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
- All users can authenticate via external API
|
||||||
|
- Authentication response time < 2 seconds (95th percentile)
|
||||||
|
- Zero data loss during migration
|
||||||
|
- Automatic token refresh works without user intervention
|
||||||
|
- Proper error messages for all failure scenarios
|
||||||
|
- Audit logs capture all authentication events
|
||||||
|
- Rollback procedure tested and documented
|
||||||
180
openspec/changes/migrate-to-external-api-authentication/tasks.md
Normal file
180
openspec/changes/migrate-to-external-api-authentication/tasks.md
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
# Implementation Tasks
|
||||||
|
|
||||||
|
## 1. Database Schema Updates
|
||||||
|
- [ ] 1.1 Create database migration script
|
||||||
|
- Add `external_user_id` column (VARCHAR 255)
|
||||||
|
- Add `display_name` column (VARCHAR 255)
|
||||||
|
- Add `azure_email` column (VARCHAR 255)
|
||||||
|
- Add `last_token_refresh` column (DATETIME)
|
||||||
|
- Mark `hashed_password` as nullable (for gradual migration)
|
||||||
|
- [ ] 1.2 Update User model
|
||||||
|
- Add new fields to SQLAlchemy model
|
||||||
|
- Update model relationships if needed
|
||||||
|
- Add migration version with Alembic
|
||||||
|
- [ ] 1.3 Create user sync mechanism
|
||||||
|
- Script to map existing users to external IDs
|
||||||
|
- Handle users without external accounts
|
||||||
|
- Backup existing user data
|
||||||
|
|
||||||
|
## 2. Configuration Management
|
||||||
|
- [ ] 2.1 Update environment configuration
|
||||||
|
- Add `EXTERNAL_AUTH_API_URL` to `.env.local`
|
||||||
|
- Add `EXTERNAL_AUTH_ENDPOINT` configuration
|
||||||
|
- Add `EXTERNAL_AUTH_TIMEOUT` setting
|
||||||
|
- Add `USE_EXTERNAL_AUTH` feature flag
|
||||||
|
- Add `TOKEN_REFRESH_BUFFER` setting
|
||||||
|
- [ ] 2.2 Update Settings class
|
||||||
|
- Add external auth settings to `backend/app/core/config.py`
|
||||||
|
- Add validation for new configuration values
|
||||||
|
- Implement feature flag logic
|
||||||
|
|
||||||
|
## 3. External API Integration Service
|
||||||
|
- [ ] 3.1 Create auth API client
|
||||||
|
- Implement `backend/app/services/external_auth_service.py`
|
||||||
|
- Create async HTTP client for API calls
|
||||||
|
- Implement request/response models
|
||||||
|
- Add proper error handling and logging
|
||||||
|
- [ ] 3.2 Implement authentication methods
|
||||||
|
- `authenticate_user()` - Call external API
|
||||||
|
- `validate_token()` - Verify token validity
|
||||||
|
- `refresh_token()` - Handle token refresh
|
||||||
|
- `get_user_info()` - Fetch user details
|
||||||
|
- [ ] 3.3 Add resilience patterns
|
||||||
|
- Implement retry logic with exponential backoff
|
||||||
|
- Add circuit breaker pattern
|
||||||
|
- Implement timeout handling
|
||||||
|
- Add fallback mechanisms
|
||||||
|
|
||||||
|
## 4. Backend Authentication Updates
|
||||||
|
- [ ] 4.1 Modify login endpoint
|
||||||
|
- Update `backend/app/api/v1/endpoints/auth.py`
|
||||||
|
- Route to external API based on feature flag
|
||||||
|
- Handle both authentication modes during transition
|
||||||
|
- Return appropriate token format
|
||||||
|
- [ ] 4.2 Update token validation
|
||||||
|
- Modify `backend/app/core/security.py`
|
||||||
|
- Support both local and external tokens
|
||||||
|
- Implement token type detection
|
||||||
|
- Update JWT validation logic
|
||||||
|
- [ ] 4.3 Update authentication dependencies
|
||||||
|
- Modify `backend/app/core/auth.py`
|
||||||
|
- Update `get_current_user()` dependency
|
||||||
|
- Handle external user information
|
||||||
|
- Implement proper user context
|
||||||
|
|
||||||
|
## 5. Session and Token Management
|
||||||
|
- [ ] 5.1 Implement token storage
|
||||||
|
- Store external tokens securely
|
||||||
|
- Implement token encryption at rest
|
||||||
|
- Handle multiple token types (access, ID, refresh)
|
||||||
|
- [ ] 5.2 Create token refresh mechanism
|
||||||
|
- Background task for token refresh
|
||||||
|
- Refresh tokens before expiration
|
||||||
|
- Update stored tokens atomically
|
||||||
|
- Handle refresh failures gracefully
|
||||||
|
- [ ] 5.3 Session invalidation
|
||||||
|
- Clear tokens on logout
|
||||||
|
- Handle token revocation
|
||||||
|
- Implement session timeout
|
||||||
|
|
||||||
|
## 6. Frontend Updates
|
||||||
|
- [ ] 6.1 Update authentication service
|
||||||
|
- Modify `frontend/src/services/authService.ts`
|
||||||
|
- Handle new token format
|
||||||
|
- Store user display information
|
||||||
|
- Implement token refresh on client side
|
||||||
|
- [ ] 6.2 Update auth store
|
||||||
|
- Modify `frontend/src/stores/authStore.ts`
|
||||||
|
- Store external user information
|
||||||
|
- Update user display logic
|
||||||
|
- Handle token expiration
|
||||||
|
- [ ] 6.3 Update UI components
|
||||||
|
- Modify `frontend/src/components/Header.tsx`
|
||||||
|
- Display user `name` instead of username
|
||||||
|
- Show additional user information
|
||||||
|
- Update login form if needed
|
||||||
|
- [ ] 6.4 Error handling
|
||||||
|
- Handle external API errors
|
||||||
|
- Display appropriate error messages
|
||||||
|
- Implement retry UI for failures
|
||||||
|
- Add loading states
|
||||||
|
|
||||||
|
## 7. Testing
|
||||||
|
- [ ] 7.1 Unit tests
|
||||||
|
- Test external auth service
|
||||||
|
- Test token validation
|
||||||
|
- Test user information mapping
|
||||||
|
- Test error scenarios
|
||||||
|
- [ ] 7.2 Integration tests
|
||||||
|
- Test full authentication flow
|
||||||
|
- Test token refresh mechanism
|
||||||
|
- Test fallback scenarios
|
||||||
|
- Test feature flag switching
|
||||||
|
- [ ] 7.3 Load testing
|
||||||
|
- Test external API response times
|
||||||
|
- Test system under high authentication load
|
||||||
|
- Measure impact on performance
|
||||||
|
- [ ] 7.4 Security testing
|
||||||
|
- Test token security
|
||||||
|
- Verify HTTPS enforcement
|
||||||
|
- Test rate limiting
|
||||||
|
- Validate error message security
|
||||||
|
|
||||||
|
## 8. Migration Execution
|
||||||
|
- [ ] 8.1 Pre-migration preparation
|
||||||
|
- Backup database
|
||||||
|
- Document rollback procedure
|
||||||
|
- Prepare user communication
|
||||||
|
- Set up monitoring
|
||||||
|
- [ ] 8.2 Staged rollout
|
||||||
|
- Enable for test users first
|
||||||
|
- Monitor for issues
|
||||||
|
- Gradually increase user percentage
|
||||||
|
- Collect feedback
|
||||||
|
- [ ] 8.3 Post-migration validation
|
||||||
|
- Verify all users can login
|
||||||
|
- Check audit logs
|
||||||
|
- Monitor error rates
|
||||||
|
- Validate performance metrics
|
||||||
|
|
||||||
|
## 9. Documentation
|
||||||
|
- [ ] 9.1 Technical documentation
|
||||||
|
- Update API documentation
|
||||||
|
- Document authentication flow
|
||||||
|
- Update deployment guide
|
||||||
|
- Create troubleshooting guide
|
||||||
|
- [ ] 9.2 User documentation
|
||||||
|
- Update login instructions
|
||||||
|
- Document new features
|
||||||
|
- Create FAQ for common issues
|
||||||
|
- [ ] 9.3 Operations documentation
|
||||||
|
- Document monitoring points
|
||||||
|
- Create runbook for issues
|
||||||
|
- Document rollback procedure
|
||||||
|
|
||||||
|
## 10. Monitoring and Observability
|
||||||
|
- [ ] 10.1 Add monitoring metrics
|
||||||
|
- Authentication success/failure rates
|
||||||
|
- External API response times
|
||||||
|
- Token refresh success rate
|
||||||
|
- Error rate monitoring
|
||||||
|
- [ ] 10.2 Implement logging
|
||||||
|
- Log all authentication attempts
|
||||||
|
- Log external API calls
|
||||||
|
- Log token operations
|
||||||
|
- Structured logging for analysis
|
||||||
|
- [ ] 10.3 Create alerts
|
||||||
|
- Alert on high failure rates
|
||||||
|
- Alert on external API unavailability
|
||||||
|
- Alert on token refresh failures
|
||||||
|
- Alert on unusual patterns
|
||||||
|
|
||||||
|
## 11. Cleanup (Post-Stabilization)
|
||||||
|
- [ ] 11.1 Remove legacy code
|
||||||
|
- Remove local authentication code (after stable period)
|
||||||
|
- Remove unused database columns
|
||||||
|
- Clean up configuration
|
||||||
|
- [ ] 11.2 Optimize performance
|
||||||
|
- Implement caching where appropriate
|
||||||
|
- Optimize database queries
|
||||||
|
- Review and optimize API calls
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Proof of Concept: External API Authentication Test
|
||||||
|
Tests the external authentication API at https://pj-auth-api.vercel.app
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
import httpx
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class UserInfo(BaseModel):
|
||||||
|
"""User information from external API"""
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
email: str
|
||||||
|
job_title: Optional[str] = Field(None, alias="jobTitle")
|
||||||
|
office_location: Optional[str] = Field(None, alias="officeLocation")
|
||||||
|
business_phones: list[str] = Field(default_factory=list, alias="businessPhones")
|
||||||
|
|
||||||
|
|
||||||
|
class AuthSuccessData(BaseModel):
|
||||||
|
"""Successful authentication response data"""
|
||||||
|
access_token: str
|
||||||
|
id_token: str
|
||||||
|
expires_in: int
|
||||||
|
token_type: str
|
||||||
|
user_info: UserInfo = Field(alias="userInfo")
|
||||||
|
issued_at: str = Field(alias="issuedAt")
|
||||||
|
expires_at: str = Field(alias="expiresAt")
|
||||||
|
|
||||||
|
|
||||||
|
class AuthSuccessResponse(BaseModel):
|
||||||
|
"""Successful authentication response"""
|
||||||
|
success: bool
|
||||||
|
message: str
|
||||||
|
data: AuthSuccessData
|
||||||
|
timestamp: str
|
||||||
|
|
||||||
|
|
||||||
|
class AuthErrorResponse(BaseModel):
|
||||||
|
"""Failed authentication response"""
|
||||||
|
success: bool
|
||||||
|
error: str
|
||||||
|
code: str
|
||||||
|
timestamp: str
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalAuthClient:
|
||||||
|
"""Client for external authentication API"""
|
||||||
|
|
||||||
|
def __init__(self, base_url: str = "https://pj-auth-api.vercel.app", timeout: int = 30):
|
||||||
|
self.base_url = base_url
|
||||||
|
self.timeout = timeout
|
||||||
|
self.endpoint = "/api/auth/login"
|
||||||
|
|
||||||
|
async def authenticate(self, username: str, password: str) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Authenticate user with external API
|
||||||
|
|
||||||
|
Args:
|
||||||
|
username: User email/username
|
||||||
|
password: User password
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Authentication result dictionary
|
||||||
|
"""
|
||||||
|
url = f"{self.base_url}{self.endpoint}"
|
||||||
|
|
||||||
|
print(f"ℹ Endpoint: POST {url}")
|
||||||
|
print(f"ℹ Username: {username}")
|
||||||
|
print(f"ℹ Timestamp: {datetime.now().isoformat()}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
try:
|
||||||
|
# Make authentication request
|
||||||
|
start_time = datetime.now()
|
||||||
|
response = await client.post(
|
||||||
|
url,
|
||||||
|
json={"username": username, "password": password},
|
||||||
|
timeout=self.timeout
|
||||||
|
)
|
||||||
|
elapsed = (datetime.now() - start_time).total_seconds()
|
||||||
|
|
||||||
|
# Print response details
|
||||||
|
print("Response Details:")
|
||||||
|
print(f" Status Code: {response.status_code}")
|
||||||
|
print(f" Response Time: {elapsed:.3f}s")
|
||||||
|
print(f" Content-Type: {response.headers.get('content-type', 'N/A')}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Parse response
|
||||||
|
response_data = response.json()
|
||||||
|
print("Response Body:")
|
||||||
|
print(json.dumps(response_data, indent=2, ensure_ascii=False))
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Handle success/failure
|
||||||
|
if response.status_code == 200:
|
||||||
|
auth_response = AuthSuccessResponse(**response_data)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"status_code": response.status_code,
|
||||||
|
"data": auth_response.dict(),
|
||||||
|
"user_display_name": auth_response.data.user_info.name,
|
||||||
|
"user_email": auth_response.data.user_info.email,
|
||||||
|
"token": auth_response.data.access_token,
|
||||||
|
"expires_in": auth_response.data.expires_in,
|
||||||
|
"expires_at": auth_response.data.expires_at
|
||||||
|
}
|
||||||
|
elif response.status_code == 401:
|
||||||
|
error_response = AuthErrorResponse(**response_data)
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"status_code": response.status_code,
|
||||||
|
"error": error_response.error,
|
||||||
|
"code": error_response.code
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"status_code": response.status_code,
|
||||||
|
"error": f"Unexpected status code: {response.status_code}",
|
||||||
|
"response": response_data
|
||||||
|
}
|
||||||
|
|
||||||
|
except httpx.TimeoutException:
|
||||||
|
print(f"❌ Request timeout after {self.timeout} seconds")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "Request timeout",
|
||||||
|
"code": "TIMEOUT"
|
||||||
|
}
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
print(f"❌ Request error: {e}")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e),
|
||||||
|
"code": "REQUEST_ERROR"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Unexpected error: {e}")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e),
|
||||||
|
"code": "UNKNOWN_ERROR"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_authentication():
|
||||||
|
"""Test authentication with different scenarios"""
|
||||||
|
client = ExternalAuthClient()
|
||||||
|
|
||||||
|
# Test scenarios
|
||||||
|
test_cases = [
|
||||||
|
{
|
||||||
|
"name": "Valid Credentials (Example)",
|
||||||
|
"username": "ymirliu@panjit.com.tw",
|
||||||
|
"password": "correct_password", # Replace with actual password for testing
|
||||||
|
"expected": "success"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Invalid Credentials",
|
||||||
|
"username": "test@example.com",
|
||||||
|
"password": "wrong_password",
|
||||||
|
"expected": "failure"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, test_case in enumerate(test_cases, 1):
|
||||||
|
print(f"{'='*60}")
|
||||||
|
print(f"Test Case {i}: {test_case['name']}")
|
||||||
|
print(f"{'='*60}")
|
||||||
|
|
||||||
|
result = await client.authenticate(
|
||||||
|
username=test_case["username"],
|
||||||
|
password=test_case["password"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Analyze result
|
||||||
|
print("\nAnalysis:")
|
||||||
|
if result["success"]:
|
||||||
|
print("✅ Authentication successful")
|
||||||
|
print(f" User: {result.get('user_display_name', 'N/A')}")
|
||||||
|
print(f" Email: {result.get('user_email', 'N/A')}")
|
||||||
|
print(f" Token expires in: {result.get('expires_in', 0)} seconds")
|
||||||
|
print(f" Expires at: {result.get('expires_at', 'N/A')}")
|
||||||
|
else:
|
||||||
|
print("❌ Authentication failed")
|
||||||
|
print(f" Error: {result.get('error', 'Unknown error')}")
|
||||||
|
print(f" Code: {result.get('code', 'N/A')}")
|
||||||
|
|
||||||
|
print("\n")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_token_validation():
|
||||||
|
"""Test token validation and refresh logic"""
|
||||||
|
# This would be implemented when we have a valid token
|
||||||
|
print("Token validation test - To be implemented with actual tokens")
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point"""
|
||||||
|
print("External Authentication API Test")
|
||||||
|
print("================================\n")
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
asyncio.run(test_authentication())
|
||||||
|
|
||||||
|
print("\nTest completed!")
|
||||||
|
print("\nNotes for implementation:")
|
||||||
|
print("1. Use httpx for async HTTP requests (already in requirements)")
|
||||||
|
print("2. Store tokens securely (consider encryption)")
|
||||||
|
print("3. Implement automatic token refresh before expiration")
|
||||||
|
print("4. Handle network failures with retry logic")
|
||||||
|
print("5. Map external user ID to local user records")
|
||||||
|
print("6. Display user 'name' field in UI instead of username")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user