Initial commit: Custom Start Page application with authentication and DynamoDB storage

This commit is contained in:
2026-02-18 22:06:43 -05:00
commit 7175ff14ba
47 changed files with 7592 additions and 0 deletions

View File

@@ -0,0 +1,165 @@
# Task 2.1 Implementation: User Data Model and DynamoDB Users Table
## Overview
This task implements the User data model and DynamoDB Users table with full CRUD operations.
## Files Created
### 1. User Model (`internal/models/user.go`)
- Defines the `User` struct with all required fields:
- `ID` (user_id): Unique identifier (UUID)
- `Email`: User's email address
- `OAuthProvider`: OAuth provider name (e.g., "google", "github")
- `OAuthID`: OAuth provider's user ID
- `CreatedAt`: Timestamp when user was created
- `UpdatedAt`: Timestamp when user was last updated
### 2. DynamoDB Client (`internal/storage/dynamodb.go`)
- `NewDynamoDBClient`: Creates a new DynamoDB client with optional endpoint override
- `CreateUsersTable`: Creates the Users table with proper schema
- Partition Key: `user_id` (String)
- Billing Mode: Pay-per-request (on-demand)
- Waits for table to be active before returning
### 3. User Storage Layer (`internal/storage/user_storage.go`)
Implements all CRUD operations:
- `CreateUser`: Creates a new user with auto-generated UUID
- `GetUserByID`: Retrieves a user by their ID
- `GetUserByOAuth`: Retrieves a user by OAuth provider and ID
- `UpdateUser`: Updates an existing user (auto-updates UpdatedAt)
- `DeleteUser`: Deletes a user by their ID
### 4. Unit Tests (`internal/storage/user_storage_test.go`)
Comprehensive test coverage:
- `TestCreateUser`: Verifies user creation with all fields
- `TestGetUserByID`: Tests user retrieval by ID
- `TestGetUserByID_NotFound`: Tests error handling for non-existent users
- `TestGetUserByOAuth`: Tests user retrieval by OAuth credentials
- `TestGetUserByOAuth_NotFound`: Tests error handling for OAuth lookup
- `TestUpdateUser`: Verifies user updates and timestamp changes
- `TestDeleteUser`: Tests user deletion
- `TestCreateUser_MultipleUsers`: Verifies multiple users can be created with unique IDs
### 5. Database Initialization (`cmd/init-db/main.go`)
Standalone CLI tool to initialize DynamoDB tables:
```bash
go run cmd/init-db/main.go -endpoint http://localhost:8000
```
### 6. Table Initialization Helper (`cmd/server/init_tables.go`)
Helper function for the main server to initialize tables on startup.
## DynamoDB Schema
### Users Table
```
Table Name: Users
Partition Key: user_id (String)
Billing Mode: Pay-per-request
Attributes:
- user_id: String (UUID)
- email: String
- oauth_provider: String
- oauth_id: String
- created_at: Number (Unix timestamp in nanoseconds)
- updated_at: Number (Unix timestamp in nanoseconds)
```
## Dependencies Added
- `github.com/aws/aws-sdk-go-v2`: AWS SDK for Go v2
- `github.com/aws/aws-sdk-go-v2/config`: AWS configuration
- `github.com/aws/aws-sdk-go-v2/service/dynamodb`: DynamoDB service client
- `github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue`: DynamoDB attribute marshaling
- `github.com/google/uuid`: UUID generation
## Running Tests
### Prerequisites
1. Start DynamoDB Local:
```bash
make db-start
```
Or manually:
```bash
docker-compose up -d dynamodb-local
```
2. Set environment variable (optional):
```bash
export DYNAMODB_ENDPOINT=http://localhost:8000
```
### Run Tests
```bash
make test
# or
go test -v ./internal/storage/...
```
## Usage Example
```go
package main
import (
"context"
"log"
"custom-start-page/internal/storage"
)
func main() {
ctx := context.Background()
// Create DynamoDB client
db, err := storage.NewDynamoDBClient(ctx, "http://localhost:8000")
if err != nil {
log.Fatal(err)
}
// Create Users table
if err := db.CreateUsersTable(ctx); err != nil {
log.Fatal(err)
}
// Create user storage
userStorage := storage.NewUserStorage(db)
// Create a new user
user, err := userStorage.CreateUser(ctx, "user@example.com", "google", "google123")
if err != nil {
log.Fatal(err)
}
log.Printf("Created user: %+v", user)
// Retrieve user by ID
retrievedUser, err := userStorage.GetUserByID(ctx, user.ID)
if err != nil {
log.Fatal(err)
}
log.Printf("Retrieved user: %+v", retrievedUser)
}
```
## Notes
### OAuth Lookup Performance
The `GetUserByOAuth` method currently uses a Scan operation since there's no GSI for `oauth_provider` + `oauth_id`. For production use with many users, consider adding a GSI:
- Partition Key: `oauth_provider`
- Sort Key: `oauth_id`
This would change the query from O(n) scan to O(1) lookup.
### Timestamp Storage
Timestamps are stored as Go `time.Time` which DynamoDB marshals as Unix timestamps in nanoseconds. This provides high precision for audit trails.
## Requirements Validated
- ✓ Requirement 1.1: User authentication foundation
- ✓ Requirement 1.3: OAuth user creation and retrieval
- ✓ Requirement 8.7: User data association
## Next Steps
Task 2.2 will implement the OAuth service using this User model to handle Google OAuth authentication.

View File

@@ -0,0 +1,160 @@
# Task 2.2: OAuth Service Implementation
## Overview
This document describes the implementation of the OAuth service with Google provider support for the Custom Start Page application.
## Components Implemented
### 1. OAuth Service (`internal/auth/oauth.go`)
- **Purpose**: Manages OAuth authentication flows
- **Key Methods**:
- `InitiateOAuth(provider string)`: Generates OAuth redirect URL with CSRF protection
- `HandleOAuthCallback(ctx, provider, code, state)`: Exchanges authorization code for access token
- `GetGoogleConfig()`: Returns OAuth configuration for accessing user info
- **Features**:
- CSRF protection using state tokens
- Support for Google OAuth (extensible for other providers)
- Secure state token generation using crypto/rand
### 2. State Store (`internal/auth/state_store.go`)
- **Purpose**: Manages OAuth state tokens for CSRF protection
- **Implementation**: In-memory store with automatic cleanup
- **Key Methods**:
- `Set(state, expiry)`: Stores state token with expiration
- `Validate(state)`: Checks if state token is valid and not expired
- `Delete(state)`: Removes state token after use
- **Features**:
- Automatic cleanup of expired tokens every 5 minutes
- Thread-safe with mutex protection
- 10-minute token expiry for security
### 3. User Service (`internal/auth/user_service.go`)
- **Purpose**: Handles user creation and retrieval from OAuth providers
- **Key Methods**:
- `GetOrCreateUserFromGoogle(ctx, token, config)`: Fetches user info and creates/updates user
- `fetchGoogleUserInfo(ctx, token, config)`: Retrieves user data from Google API
- **Features**:
- Automatic user creation on first login
- Email verification check
- Updates last login timestamp
### 4. Session Store (`internal/auth/session_store.go`)
- **Purpose**: Manages user sessions using cookies
- **Implementation**: Cookie-based sessions using gorilla/sessions
- **Key Methods**:
- `CreateSession(w, r, userID)`: Creates authenticated session
- `GetUserID(r)`: Retrieves user ID from session
- `DestroySession(w, r)`: Logs out user
- **Features**:
- Secure, HTTP-only cookies
- Configurable session expiry (default: 7 days)
- SameSite protection
### 5. User Repository (`internal/storage/user_repository.go`)
- **Purpose**: Handles user data persistence in DynamoDB
- **Key Methods**:
- `Create(ctx, user)`: Creates new user
- `GetByID(ctx, userID)`: Retrieves user by ID
- `GetByOAuthID(ctx, provider, oauthID)`: Finds user by OAuth credentials
- `Update(ctx, user)`: Updates existing user
- **Note**: Currently uses Scan for OAuth lookup; consider adding GSI in production
### 6. Auth Handler (`internal/handlers/auth_handler.go`)
- **Purpose**: HTTP handlers for authentication endpoints
- **Endpoints**:
- `GET /auth/oauth/{provider}`: Initiates OAuth flow
- `GET /auth/callback/{provider}`: Handles OAuth callback
- `POST /logout`: Logs out user
- `GET /login`: Displays login page
- **Features**:
- Error handling with user-friendly messages
- Automatic redirect to dashboard on success
- Session management integration
## HTTP Endpoints
### OAuth Flow
1. **Initiate OAuth**: `GET /auth/oauth/google`
- Generates state token
- Redirects to Google OAuth consent screen
2. **OAuth Callback**: `GET /auth/callback/google?code=...&state=...`
- Validates state token (CSRF protection)
- Exchanges code for access token
- Fetches user info from Google
- Creates or retrieves user account
- Creates session
- Redirects to dashboard
3. **Logout**: `POST /logout`
- Destroys session
- Redirects to login page
4. **Login Page**: `GET /login`
- Displays OAuth provider buttons
- Shows error messages if authentication failed
## Configuration
Required environment variables:
```bash
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GOOGLE_REDIRECT_URL=http://localhost:8080/auth/callback/google
SESSION_SECRET=change-me-in-production
SESSION_MAX_AGE=604800 # 7 days in seconds
```
## Security Features
1. **CSRF Protection**: State tokens prevent cross-site request forgery
2. **Secure Sessions**: HTTP-only, secure cookies with SameSite protection
3. **Email Verification**: Only verified Google emails are accepted
4. **Token Expiry**: State tokens expire after 10 minutes
5. **Cryptographic Randomness**: State tokens use crypto/rand for security
## Testing
Comprehensive test coverage includes:
- OAuth service initialization and callback handling
- State store operations (set, validate, delete, expiry)
- Session store operations (create, retrieve, destroy)
- Error handling for invalid states and codes
- Concurrent state management
Run tests:
```bash
go test ./internal/auth/... -v
```
## Integration with Main Server
The OAuth service is integrated into `cmd/server/main.go`:
- Initializes DynamoDB client and creates Users table
- Sets up OAuth service with Google configuration
- Creates session store with configured secret
- Registers auth handlers for OAuth endpoints
- Protects dashboard route with session check
## Requirements Validated
This implementation addresses the following requirements:
- **1.2**: OAuth provider selection triggers redirect
- **1.3**: Successful OAuth creates or retrieves user
- **1.5**: Google OAuth support
- **1.6**: Additional OAuth providers can be configured (extensible design)
## Future Enhancements
1. **Production State Store**: Replace in-memory store with Redis or DynamoDB for multi-server deployments
2. **OAuth Provider GSI**: Add Global Secondary Index on oauth_provider + oauth_id for efficient user lookup
3. **Additional Providers**: Add GitHub, Microsoft, and other OAuth providers
4. **Rate Limiting**: Add rate limiting on OAuth endpoints
5. **Audit Logging**: Log authentication events for security monitoring
## Dependencies Added
- `golang.org/x/oauth2`: OAuth 2.0 client library
- `golang.org/x/oauth2/google`: Google OAuth provider
- `github.com/gorilla/sessions`: Session management
- `github.com/gorilla/securecookie`: Secure cookie encoding (dependency of sessions)

View File

@@ -0,0 +1,162 @@
# Task 3.1 Implementation: DynamoDB Storage Service Wrapper
## Overview
Enhanced the existing DynamoDB client from Task 2.1 with retry logic, connection pooling, transaction support, and batch operations.
## Implementation Details
### 1. Enhanced Client Configuration
- **Retry Strategy**: Configured with exponential backoff and jitter
- Max attempts: 5
- Max backoff: 20 seconds
- Prevents thundering herd with jitter
- **Connection Pooling**: Uses AWS SDK's default HTTP client with built-in connection pooling
### 2. Transaction Support
Implemented `TransactWriteItems` method for ACID transactions:
- Supports multiple write operations in a single atomic transaction
- Automatic retry on transient failures
- Proper error handling and wrapping
### 3. Batch Operations
Implemented two batch operation methods:
#### BatchGetItems
- Retrieves multiple items in a single request
- Automatically retries unprocessed keys with exponential backoff
- Merges results from retry attempts
- Max 5 retry attempts with increasing backoff (100ms → 20s)
#### BatchWriteItems
- Writes multiple items in a single request
- Automatically retries unprocessed items with exponential backoff
- Max 5 retry attempts with increasing backoff (100ms → 20s)
### 4. Standard Operations
Wrapped standard DynamoDB operations with automatic retry:
- `PutItem` - Put a single item
- `GetItem` - Get a single item
- `UpdateItem` - Update a single item
- `DeleteItem` - Delete a single item
- `Query` - Query items
All operations include proper error handling and wrapping.
### 5. Comprehensive Testing
Created `dynamodb_test.go` with tests for:
- Client initialization
- Transaction operations
- Batch get operations
- Batch write operations
- Put and get operations
- Update operations
- Delete operations
- Query operations
Tests include:
- Automatic skip when DynamoDB is not available
- Helper function for test setup with dummy AWS credentials
- Table creation and cleanup helpers
- Verification of all operations
## Files Modified/Created
### Modified
- `internal/storage/dynamodb.go` - Enhanced with retry logic, transactions, and batch operations
### Created
- `internal/storage/dynamodb_test.go` - Comprehensive test suite
- `internal/storage/README.md` - Documentation for the storage service
- `docs/task-3.1-implementation.md` - This implementation document
## Requirements Addressed
**Requirement 8.1**: Immediate persistence with reliable operations
**Requirement 8.8**: Efficient scaling through batch operations and connection pooling
**Design**: Retry logic with exponential backoff
**Design**: Transaction support (TransactWrite)
**Design**: Batch operations (BatchGet, BatchWrite)
**Design**: Connection pooling
## Testing
### Running Tests
```bash
# Start DynamoDB Local
docker-compose up -d
# Run all storage tests
go test -v ./internal/storage
# Run specific tests
go test -v ./internal/storage -run TestTransactWriteItems
```
### Test Coverage
- ✅ Client initialization with retry configuration
- ✅ Transaction writes with multiple items
- ✅ Batch get with multiple items
- ✅ Batch write with multiple items
- ✅ Single item operations (Put, Get, Update, Delete)
- ✅ Query operations
- ✅ Graceful skip when DynamoDB unavailable
## Usage Example
```go
// Create client
ctx := context.Background()
client, err := storage.NewDynamoDBClient(ctx, "http://localhost:8000")
if err != nil {
log.Fatal(err)
}
// Transaction example
err = client.TransactWriteItems(ctx, &dynamodb.TransactWriteItemsInput{
TransactItems: []types.TransactWriteItem{
{
Put: &types.Put{
TableName: aws.String("MyTable"),
Item: map[string]types.AttributeValue{
"id": &types.AttributeValueMemberS{Value: "item1"},
},
},
},
},
})
// Batch write example
err = client.BatchWriteItems(ctx, &dynamodb.BatchWriteItemInput{
RequestItems: map[string][]types.WriteRequest{
"MyTable": {
{
PutRequest: &types.PutRequest{
Item: map[string]types.AttributeValue{
"id": &types.AttributeValueMemberS{Value: "item1"},
},
},
},
},
},
})
// Batch get example
output, err := client.BatchGetItems(ctx, &dynamodb.BatchGetItemInput{
RequestItems: map[string]types.KeysAndAttributes{
"MyTable": {
Keys: []map[string]types.AttributeValue{
{"id": &types.AttributeValueMemberS{Value: "item1"}},
},
},
},
})
```
## Next Steps
This enhanced storage service is now ready to be used by:
- Task 3.2: Data model implementations
- Task 3.3: Table schema creation
- All future tasks requiring DynamoDB operations
The retry logic, transaction support, and batch operations provide a solid foundation for building scalable, reliable data access patterns.