# Day 7 Product Requirements Document # Email Service & User Management **Version**: 1.0 **Date**: 2025-11-03 **Sprint**: M1 Sprint 2 - Day 7 **Author**: Product Manager Agent **Status**: Ready for Implementation --- ## Executive Summary Day 7 completes the core authentication and user management foundation by adding: 1. **Email Service Integration** - Reliable transactional email infrastructure 2. **Email Verification Flow** - Ensure valid user email addresses 3. **Password Reset Flow** - Self-service password recovery 4. **User Invitation System** - Team member onboarding These features are critical for: - Unblocking 3 skipped integration tests (user removal scenarios) - Enabling multi-user tenant functionality - Completing enterprise-ready authentication flows - Meeting security and compliance standards --- ## Table of Contents 1. [Background & Context](#1-background--context) 2. [Feature 1: Email Service Integration](#2-feature-1-email-service-integration) 3. [Feature 2: Email Verification Flow](#3-feature-2-email-verification-flow) 4. [Feature 3: Password Reset Flow](#4-feature-3-password-reset-flow) 5. [Feature 4: User Invitation System](#5-feature-4-user-invitation-system) 6. [API Specifications](#6-api-specifications) 7. [Database Schema Changes](#7-database-schema-changes) 8. [Security Requirements](#8-security-requirements) 9. [Email Templates](#9-email-templates) 10. [Integration Points](#10-integration-points) 11. [Testing Strategy](#11-testing-strategy) 12. [Implementation Plan](#12-implementation-plan) 13. [Risk Assessment](#13-risk-assessment) 14. [Success Criteria](#14-success-criteria) --- ## 1. Background & Context ### 1.1 Current State (Days 0-6) **Completed Features**: - Multi-tenant architecture with tenant isolation - JWT authentication with refresh tokens - RBAC system with 5 roles (TenantOwner, TenantAdmin, Developer, Guest, AIAgent) - Role Management API with cross-tenant security - Domain Events infrastructure **Limitations**: - No email notifications for registration or login - No email verification (security gap) - No password reset mechanism (user lockout risk) - Single-user tenants only (cannot invite team members) - 3 integration tests skipped due to missing invitation feature ### 1.2 Business Drivers **User Pain Points**: - "I registered but can't invite my team" - Blocks team collaboration - "I forgot my password and I'm locked out" - Support burden - "Are these emails valid?" - Email bounces, spam issues - "How do I know if someone registered with my company email?" - Security concern **Business Impact**: - **Without email verification**: ~30% fake/invalid email addresses (industry average) - **Without password reset**: 15-20% support tickets for password issues - **Without user invitation**: Single-user limitation blocks 80% of enterprise use cases - **Without email service**: Cannot send critical security notifications ### 1.3 Success Metrics | Metric | Target | Measurement | |--------|--------|-------------| | Email delivery rate | >99% | SendGrid/SMTP logs | | Email verification rate | >85% | Verified users / Total registrations | | Password reset success rate | >90% | Successful resets / Attempts | | Invitation acceptance rate | >70% | Accepted / Sent invitations | | Test coverage | 100% of skipped tests passing | Integration test suite | | Support ticket reduction | -50% for password issues | Support ticket tracking | --- ## 2. Feature 1: Email Service Integration ### 2.1 Overview Implement a reliable, configurable email service for sending transactional emails (verification, password reset, invitations, notifications). ### 2.2 Technology Decision: SendGrid vs SMTP #### Recommendation: **Hybrid Approach with SendGrid Priority** **Primary**: SendGrid (for production) - Industry-standard 99.9% delivery rate - Built-in analytics and bounce handling - Rate limiting and spam prevention - Email validation API - Managed infrastructure (no SMTP server maintenance) - Free tier: 100 emails/day (sufficient for MVP) **Fallback**: SMTP (for development and self-hosted deployments) - No external dependencies - Works in air-gapped environments - Free for self-hosted email servers - Suitable for development/testing with tools like MailHog **Implementation**: Abstraction layer with strategy pattern ### 2.3 Requirements #### FR-EMAIL-001: Email Service Abstraction **Priority**: P0 (Must Have) **Description**: Create abstraction layer supporting multiple email providers **Acceptance Criteria**: - [ ] `IEmailService` interface defined with `SendEmailAsync(EmailMessage message)` method - [ ] SendGrid implementation (`SendGridEmailService`) - [ ] SMTP implementation (`SmtpEmailService`) - [ ] Provider selection via configuration (`appsettings.json`) - [ ] Graceful fallback if primary provider fails - [ ] All email sends are logged (INFO level) **User Story**: ``` As a system administrator, I want to configure email providers without code changes, So that I can use SendGrid in production and SMTP in development. ``` #### FR-EMAIL-002: Configuration Management **Priority**: P0 (Must Have) **Description**: Environment-based email configuration **Acceptance Criteria**: - [ ] Configuration in `appsettings.json` and `appsettings.Development.json` - [ ] SendGrid API key stored in User Secrets (development) and Azure Key Vault (production) - [ ] SMTP settings: host, port, username, password, enableSSL - [ ] Email template base path configurable - [ ] From address and display name configurable - [ ] Provider selection: `SendGrid`, `Smtp`, `Mock` (for tests) **Configuration Example**: ```json { "EmailSettings": { "Provider": "SendGrid", "FromAddress": "noreply@colaflow.io", "FromName": "ColaFlow", "SendGrid": { "ApiKey": "stored-in-user-secrets-or-keyvault" }, "Smtp": { "Host": "smtp.gmail.com", "Port": 587, "Username": "user@example.com", "Password": "stored-in-user-secrets", "EnableSsl": true }, "TemplateBasePath": "EmailTemplates" } } ``` #### FR-EMAIL-003: Email Template System **Priority**: P0 (Must Have) **Description**: Reusable HTML email templates with placeholders **Acceptance Criteria**: - [ ] Template engine for HTML emails (using C# string interpolation or Razor) - [ ] Shared layout template with ColaFlow branding - [ ] Template variables: `{{userName}}`, `{{tenantName}}`, `{{verificationUrl}}`, etc. - [ ] Plain text fallback for all templates - [ ] Templates stored in `EmailTemplates/` folder - [ ] Template rendering service: `IEmailTemplateRenderer` **Templates Required** (see section 9 for details): 1. `EmailVerification.html` - Verification link 2. `PasswordReset.html` - Password reset link 3. `UserInvitation.html` - Tenant invitation 4. `WelcomeEmail.html` - Post-verification welcome (optional) #### FR-EMAIL-004: Development Mode Email Preview **Priority**: P1 (Should Have) **Description**: Preview emails in development without sending **Acceptance Criteria**: - [ ] In development, emails are logged to console with full HTML - [ ] Optional: Save emails to `temp/emails/` folder for manual inspection - [ ] Mock email service for integration tests (no actual sends) - [ ] Configuration flag: `EmailSettings:SaveEmailsToFile` (true in development) **User Story**: ``` As a developer, I want to preview email templates locally, So that I can verify styling and content before deploying. ``` #### FR-EMAIL-005: Rate Limiting & Error Handling **Priority**: P0 (Must Have) **Description**: Prevent abuse and handle failures gracefully **Acceptance Criteria**: - [ ] Rate limiting: Max 5 emails per user per hour (configurable) - [ ] Retry logic for transient failures (3 attempts with exponential backoff) - [ ] Circuit breaker pattern for email provider outages - [ ] Email send failures logged as WARN (not ERROR to avoid alert fatigue) - [ ] Graceful degradation: If email fails, user is informed but operation succeeds - [ ] Dead letter queue for failed emails (future: background retry job) **Business Rule**: Email delivery is non-blocking. If email fails, the user action (e.g., registration) still succeeds, but user is notified that email may be delayed. ### 2.4 Technical Architecture ``` ┌──────────────────────────────────────┐ │ Application Layer (Commands) │ │ - RegisterTenant │ │ - ForgotPassword │ │ - InviteUser │ └──────────────┬───────────────────────┘ │ Calls ┌──────────────▼───────────────────────┐ │ IEmailService (Abstraction) │ │ + SendEmailAsync(EmailMessage) │ └──────────────┬───────────────────────┘ │ Implemented by ┌───────┴────────┐ │ │ ┌──────▼──────┐ ┌──────▼──────┐ │ SendGrid │ │ SMTP │ │ Service │ │ Service │ └─────────────┘ └─────────────┘ ┌──────────────────────────────────────┐ │ IEmailTemplateRenderer │ │ + RenderTemplateAsync(name, data) │ └──────────────────────────────────────┘ ``` ### 2.5 Non-Functional Requirements | Requirement | Target | Priority | |-------------|--------|----------| | Email send latency | <2 seconds | P0 | | Template rendering time | <100ms | P1 | | Delivery rate (SendGrid) | >99% | P0 | | Rate limiting | 5 emails/user/hour | P0 | | Log retention | 30 days | P1 | --- ## 3. Feature 2: Email Verification Flow ### 3.1 Overview Ensure users own the email addresses they register with by requiring email verification. ### 3.2 User Journey ``` 1. User registers tenant ↓ 2. System creates user account (status: Active, emailVerified: false) ↓ 3. System generates verification token (24h expiry) ↓ 4. System sends verification email with link ↓ 5. User clicks link → redirected to verification endpoint ↓ 6. System validates token → marks email as verified ↓ 7. User redirected to dashboard with success message ``` ### 3.3 Requirements #### FR-VERIFY-001: Generate Verification Token **Priority**: P0 (Must Have) **Acceptance Criteria**: - [ ] Token generated on registration (in `RegisterTenantCommandHandler`) - [ ] Token is cryptographically random (256-bit, URL-safe) - [ ] Token hash stored in database (not plaintext) - [ ] Token expires after 24 hours - [ ] One active token per user (new token invalidates old) - [ ] Token linked to user ID and email address **Technical Implementation**: ```csharp var token = GenerateSecureToken(); // 256-bit random var tokenHash = HashToken(token); // SHA-256 var emailVerificationToken = new EmailVerificationToken { UserId = user.Id, TokenHash = tokenHash, Email = user.Email.Value, ExpiresAt = DateTime.UtcNow.AddHours(24), CreatedAt = DateTime.UtcNow }; ``` #### FR-VERIFY-002: Send Verification Email **Priority**: P0 (Must Have) **Acceptance Criteria**: - [ ] Email sent immediately after registration - [ ] Email contains verification link: `https://app.colaflow.io/verify-email?token={token}` - [ ] Link includes tenant slug for context - [ ] Email template uses user's full name and tenant name - [ ] Email includes "resend" instructions if link expired - [ ] Non-blocking: Registration succeeds even if email fails **User Story**: ``` As a new user, I want to receive a verification email after registration, So that I can verify my email address and access all features. ``` #### FR-VERIFY-003: Verify Email Endpoint **Priority**: P0 (Must Have) **Acceptance Criteria**: - [ ] Endpoint: `POST /api/auth/verify-email` - [ ] Request body: `{ "token": "..." }` - [ ] Validates token existence and expiration - [ ] Compares token hash with stored hash - [ ] Sets `User.EmailVerifiedAt = DateTime.UtcNow` - [ ] Returns success response with redirect URL - [ ] Invalid/expired token returns 400 with clear error message - [ ] Already verified email returns 200 (idempotent) **Error Messages**: - "Verification token is invalid or expired. Please request a new verification email." - "Email already verified. You can log in now." - "Verification token not found." #### FR-VERIFY-004: Resend Verification Email **Priority**: P0 (Must Have) **Acceptance Criteria**: - [ ] Endpoint: `POST /api/auth/resend-verification` - [ ] Request body: `{ "tenantSlug": "...", "email": "..." }` - [ ] Rate limited: Max 3 resends per hour per email - [ ] Generates new token (invalidates old) - [ ] Returns 200 even if email doesn't exist (prevent enumeration) - [ ] Logs resend attempts for security monitoring **User Story**: ``` As a user who didn't receive the verification email, I want to request a new verification email, So that I can complete the verification process. ``` #### FR-VERIFY-005: Unverified User Restrictions (Future) **Priority**: P2 (Nice to Have, Day 7 Optional) **Business Decision Required**: Should unverified users be able to log in? **Option A (Recommended)**: Allow login, restrict features - Unverified users can log in and view dashboard - Banner message: "Please verify your email to invite team members" - User invitation disabled until email verified - Project creation limited to 1 project **Option B**: Block login until verified - Login returns 403: "Please verify your email before logging in" - Stricter security, but higher support burden **Recommendation**: Option A for Day 7 (better UX, lower support burden) ### 3.4 Business Rules | Rule ID | Rule | Priority | |---------|------|----------| | BR-VERIFY-001 | Token expires after 24 hours | P0 | | BR-VERIFY-002 | Only one active token per user | P0 | | BR-VERIFY-003 | Verification is idempotent (can verify multiple times) | P0 | | BR-VERIFY-004 | Resend limited to 3 times per hour | P0 | | BR-VERIFY-005 | Email verification is optional for login (Day 7) | P1 | | BR-VERIFY-006 | Future: User invitation requires verified email | P2 | ### 3.5 Security Considerations - **Token Hashing**: Store SHA-256 hash, not plaintext token - **URL Encoding**: Token must be URL-safe (base64url) - **Expiration**: Enforce 24-hour expiration - **Rate Limiting**: Prevent spam via resend endpoint - **Email Enumeration**: Don't reveal if email exists in resend response - **HTTPS Only**: Verification links must use HTTPS --- ## 4. Feature 3: Password Reset Flow ### 4.1 Overview Allow users to securely reset forgotten passwords via email. ### 4.2 User Journey ``` 1. User clicks "Forgot Password" on login page ↓ 2. User enters tenant slug + email ↓ 3. System generates reset token (1h expiry) ↓ 4. System sends reset email with link ↓ 5. User clicks link → redirected to reset form ↓ 6. User enters new password (validated) ↓ 7. System validates token → updates password ↓ 8. System invalidates all refresh tokens ↓ 9. User redirected to login with success message ``` ### 4.3 Requirements #### FR-RESET-001: Forgot Password Endpoint **Priority**: P0 (Must Have) **Acceptance Criteria**: - [ ] Endpoint: `POST /api/auth/forgot-password` - [ ] Request body: `{ "tenantSlug": "...", "email": "..." }` - [ ] Validates tenant and email existence (in background, no revelation) - [ ] Generates reset token (256-bit, URL-safe) - [ ] Stores token hash with 1-hour expiration - [ ] Sends reset email with link - [ ] Returns 200 regardless of email existence (prevent enumeration) - [ ] Rate limited: Max 3 requests per email per hour - [ ] Logs all reset requests for security audit **Response** (always 200, never reveal if email exists): ```json { "message": "If an account exists with this email, a password reset link has been sent." } ``` **User Story**: ``` As a user who forgot my password, I want to request a password reset link, So that I can regain access to my account. ``` #### FR-RESET-002: Send Password Reset Email **Priority**: P0 (Must Have) **Acceptance Criteria**: - [ ] Email sent only if user exists and is active - [ ] Email contains reset link: `https://app.colaflow.io/reset-password?token={token}` - [ ] Link expires in 1 hour - [ ] Email warns: "If you didn't request this, ignore this email" - [ ] Email template uses user's full name - [ ] Link includes tenant slug for UX #### FR-RESET-003: Reset Password Endpoint **Priority**: P0 (Must Have) **Acceptance Criteria**: - [ ] Endpoint: `POST /api/auth/reset-password` - [ ] Request body: `{ "token": "...", "newPassword": "..." }` - [ ] Validates token existence and expiration - [ ] Validates new password complexity (see FR-RESET-005) - [ ] Compares token hash with stored hash - [ ] Updates `User.PasswordHash` with new hashed password - [ ] Sets `PasswordResetToken.UsedAt = DateTime.UtcNow` - [ ] Invalidates all user's refresh tokens (force re-login) - [ ] Marks token as used (cannot reuse) - [ ] Returns 200 with success message - [ ] Invalid/expired token returns 400 **Error Messages**: - "Password reset token is invalid or expired. Please request a new one." - "Password has already been reset with this token." - "New password does not meet complexity requirements." #### FR-RESET-004: Token Invalidation on Use **Priority**: P0 (Must Have) **Acceptance Criteria**: - [ ] Used tokens marked with `UsedAt` timestamp - [ ] Used tokens cannot be reused (return 400) - [ ] New reset request invalidates previous unused tokens - [ ] Expired tokens automatically cleaned up (future: background job) **Business Rule**: Only one active reset token per user. Requesting new reset invalidates old unused tokens. #### FR-RESET-005: Password Complexity Requirements **Priority**: P0 (Must Have) **Acceptance Criteria**: - [ ] Minimum 8 characters - [ ] At least 1 uppercase letter - [ ] At least 1 lowercase letter - [ ] At least 1 number - [ ] At least 1 special character (`!@#$%^&*()_+-=[]{}|;:,.<>?`) - [ ] Cannot be same as old password (compare hashes) - [ ] Clear validation error messages **Validation Error Response**: ```json { "errors": { "newPassword": [ "Password must be at least 8 characters long", "Password must contain at least one uppercase letter", "Password cannot be the same as your current password" ] } } ``` #### FR-RESET-006: Refresh Token Revocation **Priority**: P0 (Must Have) **Acceptance Criteria**: - [ ] On successful password reset, invalidate all user's refresh tokens - [ ] User forced to log in again with new password - [ ] Security measure: Ensures attacker with old tokens loses access **Security Rationale**: If a password reset was triggered due to compromise, we must invalidate all existing sessions. ### 4.4 Business Rules | Rule ID | Rule | Priority | |---------|------|----------| | BR-RESET-001 | Reset token expires after 1 hour | P0 | | BR-RESET-002 | Max 3 reset requests per email per hour | P0 | | BR-RESET-003 | Used tokens cannot be reused | P0 | | BR-RESET-004 | New reset invalidates old unused tokens | P0 | | BR-RESET-005 | All refresh tokens revoked on password reset | P0 | | BR-RESET-006 | Password reset requires valid email verification (future) | P2 | ### 4.5 Security Considerations - **Token Hashing**: Store SHA-256 hash, not plaintext - **Short Expiration**: 1 hour to minimize attack window - **Rate Limiting**: Prevent brute force and abuse - **Email Enumeration**: Never reveal if email exists - **HTTPS Only**: Reset links must use HTTPS - **Token Reuse Prevention**: Mark tokens as used - **Session Invalidation**: Revoke all refresh tokens on reset - **Audit Logging**: Log all reset attempts with IP and user agent --- ## 5. Feature 4: User Invitation System ### 5.1 Overview Enable tenant owners/admins to invite team members to their tenant. ### 5.2 User Journey ``` 1. Tenant owner/admin clicks "Invite User" ↓ 2. Owner enters email + selects role ↓ 3. System validates email format and role ↓ 4. System generates invitation token (7 days expiry) ↓ 5. System sends invitation email with link ↓ 6. Invited user clicks link → redirected to accept page ↓ 7. User enters full name + password ↓ 8. System creates user account + assigns role ↓ 9. User redirected to dashboard ``` ### 5.3 Requirements #### FR-INVITE-001: Create Invitation Endpoint **Priority**: P0 (Must Have) **Acceptance Criteria**: - [ ] Endpoint: `POST /api/tenants/{tenantId}/invitations` - [ ] Authorization: `RequireTenantOwner` or `RequireTenantAdmin` policy - [ ] Request body: `{ "email": "...", "role": "Developer" }` - [ ] Validates tenant ownership (cross-tenant check) - [ ] Validates email format - [ ] Validates role (cannot invite as TenantOwner or AIAgent) - [ ] Prevents duplicate invitations (same email + tenant) - [ ] Generates invitation token (256-bit, URL-safe) - [ ] Stores invitation with 7-day expiration - [ ] Sends invitation email - [ ] Returns invitation details **Validation Rules**: - Email must be valid format - Role must be one of: TenantAdmin, Developer, Guest - Cannot invite existing tenant members - Cannot invite with invalid role **Response** (201 Created): ```json { "id": "uuid", "tenantId": "uuid", "email": "user@example.com", "role": "Developer", "status": "Pending", "invitedBy": "uuid", "invitedAt": "2025-11-03T10:00:00Z", "expiresAt": "2025-11-10T10:00:00Z" } ``` **User Story**: ``` As a tenant owner, I want to invite team members to my tenant, So that they can collaborate on projects. ``` #### FR-INVITE-002: List Invitations Endpoint **Priority**: P0 (Must Have) **Acceptance Criteria**: - [ ] Endpoint: `GET /api/tenants/{tenantId}/invitations` - [ ] Authorization: `RequireTenantOwner` or `RequireTenantAdmin` policy - [ ] Validates tenant ownership - [ ] Supports pagination: `?pageNumber=1&pageSize=20` - [ ] Supports filtering by status: `?status=Pending` - [ ] Returns list of invitations with metadata - [ ] Includes inviter's name for context **Response**: ```json { "items": [ { "id": "uuid", "email": "user@example.com", "role": "Developer", "status": "Pending", "invitedBy": { "id": "uuid", "fullName": "John Doe" }, "invitedAt": "2025-11-03T10:00:00Z", "expiresAt": "2025-11-10T10:00:00Z" } ], "pageNumber": 1, "pageSize": 20, "totalCount": 5, "totalPages": 1 } ``` #### FR-INVITE-003: Send Invitation Email **Priority**: P0 (Must Have) **Acceptance Criteria**: - [ ] Email sent immediately after invitation creation - [ ] Email contains acceptance link: `https://app.colaflow.io/accept-invitation?token={token}` - [ ] Email includes tenant name, inviter name, and assigned role - [ ] Email includes expiration date (7 days) - [ ] Email has clear call-to-action button - [ ] Link includes tenant slug for UX **Email Content Example**: ``` Subject: You're invited to join [Tenant Name] on ColaFlow Hi there, [Inviter Name] has invited you to join [Tenant Name] on ColaFlow as a [Role]. [Accept Invitation Button] This invitation will expire on [Expiration Date]. If you didn't expect this invitation, you can safely ignore this email. ``` #### FR-INVITE-004: Accept Invitation Endpoint **Priority**: P0 (Must Have) **Acceptance Criteria**: - [ ] Endpoint: `POST /api/invitations/accept` - [ ] Public endpoint (no authentication required) - [ ] Request body: `{ "token": "...", "fullName": "...", "password": "..." }` - [ ] Validates token existence and expiration - [ ] Validates invitation status (must be Pending) - [ ] Validates password complexity (same as registration) - [ ] Creates new user account in invited tenant - [ ] Assigns role from invitation - [ ] Marks invitation as Accepted with timestamp - [ ] Sends welcome email (optional) - [ ] Returns access token + refresh token (auto-login) **Response** (200 OK): ```json { "user": { "id": "uuid", "tenantId": "uuid", "email": "user@example.com", "fullName": "Jane Doe", "role": "Developer" }, "accessToken": "jwt-token", "refreshToken": "refresh-token" } ``` **Error Cases**: - Invitation expired → 400: "This invitation has expired. Please request a new one." - Invitation already accepted → 400: "This invitation has already been used." - Token invalid → 400: "Invalid invitation token." - Email already registered in tenant → 400: "An account with this email already exists in this tenant." #### FR-INVITE-005: Cancel Invitation Endpoint **Priority**: P1 (Should Have) **Acceptance Criteria**: - [ ] Endpoint: `DELETE /api/tenants/{tenantId}/invitations/{invitationId}` - [ ] Authorization: `RequireTenantOwner` or `RequireTenantAdmin` policy - [ ] Validates tenant ownership - [ ] Validates invitation belongs to tenant - [ ] Only pending invitations can be canceled - [ ] Marks invitation as Canceled (soft delete) - [ ] Returns 204 No Content **User Story**: ``` As a tenant owner, I want to cancel a pending invitation, So that the invitee can no longer accept it if I invited the wrong person. ``` #### FR-INVITE-006: Resend Invitation **Priority**: P2 (Nice to Have, Day 7 Optional) **Acceptance Criteria**: - [ ] Endpoint: `POST /api/tenants/{tenantId}/invitations/{invitationId}/resend` - [ ] Generates new token (invalidates old) - [ ] Extends expiration by 7 days from now - [ ] Resends invitation email - [ ] Rate limited: Max 3 resends per invitation ### 5.4 Business Rules | Rule ID | Rule | Priority | |---------|------|----------| | BR-INVITE-001 | Invitation expires after 7 days | P0 | | BR-INVITE-002 | Only TenantOwner and TenantAdmin can invite | P0 | | BR-INVITE-003 | Cannot invite as TenantOwner or AIAgent | P0 | | BR-INVITE-004 | Cannot invite existing tenant members | P0 | | BR-INVITE-005 | One active invitation per email per tenant | P0 | | BR-INVITE-006 | Accepting invitation auto-creates user account | P0 | | BR-INVITE-007 | Users can belong to multiple tenants (future) | P2 | ### 5.5 Multi-Tenant Invitation Handling (Future) **Day 7 Scope**: User can only belong to one tenant (simplification). **Future Enhancement** (M2+): - User can accept invitations to multiple tenants - On login, user selects which tenant to access - `UserTenantRole` table already supports this (user_id + tenant_id + role) **Day 7 Implementation**: Check if user email exists globally. If yes, reject invitation with error: "This email is already registered. Multi-tenant users are coming soon!" ### 5.6 Security Considerations - **Token Hashing**: Store SHA-256 hash - **Role Validation**: Prevent privilege escalation (cannot invite as TenantOwner) - **Cross-Tenant Check**: Ensure inviter belongs to tenant - **Email Verification**: Invitation acceptance verifies email ownership - **Rate Limiting**: Prevent invitation spam - **Expiration**: 7-day expiration balances security and UX - **Audit Logging**: Log all invitation actions (create, accept, cancel) --- ## 6. API Specifications ### 6.1 Email Verification Endpoints #### `POST /api/auth/verify-email` **Description**: Verify user's email address with token. **Authorization**: None (public) **Request Body**: ```json { "token": "base64url-encoded-token" } ``` **Responses**: **200 OK** - Email verified successfully: ```json { "message": "Email verified successfully. You can now log in.", "redirectUrl": "/login" } ``` **400 Bad Request** - Invalid or expired token: ```json { "error": "Verification token is invalid or expired.", "code": "INVALID_TOKEN" } ``` **200 OK** - Email already verified (idempotent): ```json { "message": "Email already verified.", "redirectUrl": "/dashboard" } ``` --- #### `POST /api/auth/resend-verification` **Description**: Resend email verification email. **Authorization**: None (public) **Request Body**: ```json { "tenantSlug": "acme-corp", "email": "user@example.com" } ``` **Responses**: **200 OK** - Always returns success (prevent email enumeration): ```json { "message": "If an account exists, a verification email has been sent." } ``` **429 Too Many Requests** - Rate limit exceeded: ```json { "error": "Too many verification email requests. Please try again later.", "retryAfter": 3600 } ``` --- ### 6.2 Password Reset Endpoints #### `POST /api/auth/forgot-password` **Description**: Request password reset email. **Authorization**: None (public) **Request Body**: ```json { "tenantSlug": "acme-corp", "email": "user@example.com" } ``` **Responses**: **200 OK** - Always returns success (prevent email enumeration): ```json { "message": "If an account exists, a password reset email has been sent." } ``` **429 Too Many Requests** - Rate limit exceeded: ```json { "error": "Too many password reset requests. Please try again in 1 hour.", "retryAfter": 3600 } ``` --- #### `POST /api/auth/reset-password` **Description**: Reset password with token. **Authorization**: None (public) **Request Body**: ```json { "token": "base64url-encoded-token", "newPassword": "SecureP@ssw0rd" } ``` **Responses**: **200 OK** - Password reset successfully: ```json { "message": "Password reset successfully. You can now log in with your new password.", "redirectUrl": "/login" } ``` **400 Bad Request** - Invalid or expired token: ```json { "error": "Password reset token is invalid or expired.", "code": "INVALID_TOKEN" } ``` **400 Bad Request** - Password complexity requirements not met: ```json { "errors": { "newPassword": [ "Password must be at least 8 characters long", "Password must contain at least one uppercase letter" ] } } ``` **400 Bad Request** - Token already used: ```json { "error": "This password reset link has already been used.", "code": "TOKEN_ALREADY_USED" } ``` --- ### 6.3 User Invitation Endpoints #### `POST /api/tenants/{tenantId}/invitations` **Description**: Invite a user to join tenant. **Authorization**: `RequireTenantOwner` or `RequireTenantAdmin` **Path Parameters**: - `tenantId` (Guid) - Target tenant ID **Request Body**: ```json { "email": "newuser@example.com", "role": "Developer" } ``` **Validation**: - Email: Valid email format - Role: One of `TenantAdmin`, `Developer`, `Guest` (cannot be `TenantOwner` or `AIAgent`) **Responses**: **201 Created** - Invitation created: ```json { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "tenantId": "1fa85f64-5717-4562-b3fc-2c963f66afa6", "email": "newuser@example.com", "role": "Developer", "status": "Pending", "invitedBy": { "id": "2fa85f64-5717-4562-b3fc-2c963f66afa6", "fullName": "John Doe" }, "invitedAt": "2025-11-03T10:00:00Z", "expiresAt": "2025-11-10T10:00:00Z", "acceptedAt": null } ``` **400 Bad Request** - Invalid role: ```json { "errors": { "role": ["Role must be one of: TenantAdmin, Developer, Guest"] } } ``` **400 Bad Request** - User already invited: ```json { "error": "An active invitation for this email already exists.", "code": "DUPLICATE_INVITATION" } ``` **400 Bad Request** - User already member: ```json { "error": "A user with this email is already a member of this tenant.", "code": "USER_ALREADY_EXISTS" } ``` **403 Forbidden** - Cross-tenant access: ```json { "error": "Access denied: You can only manage invitations in your own tenant." } ``` --- #### `GET /api/tenants/{tenantId}/invitations` **Description**: List all invitations for a tenant. **Authorization**: `RequireTenantOwner` or `RequireTenantAdmin` **Path Parameters**: - `tenantId` (Guid) - Target tenant ID **Query Parameters**: - `pageNumber` (int, optional, default: 1) - Page number - `pageSize` (int, optional, default: 20, max: 100) - Items per page - `status` (string, optional) - Filter by status: `Pending`, `Accepted`, `Expired`, `Canceled` **Responses**: **200 OK**: ```json { "items": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "email": "user1@example.com", "role": "Developer", "status": "Pending", "invitedBy": { "id": "2fa85f64-5717-4562-b3fc-2c963f66afa6", "fullName": "John Doe" }, "invitedAt": "2025-11-03T10:00:00Z", "expiresAt": "2025-11-10T10:00:00Z", "acceptedAt": null }, { "id": "4fa85f64-5717-4562-b3fc-2c963f66afa6", "email": "user2@example.com", "role": "Guest", "status": "Accepted", "invitedBy": { "id": "2fa85f64-5717-4562-b3fc-2c963f66afa6", "fullName": "John Doe" }, "invitedAt": "2025-11-01T08:00:00Z", "expiresAt": "2025-11-08T08:00:00Z", "acceptedAt": "2025-11-01T09:30:00Z" } ], "pageNumber": 1, "pageSize": 20, "totalCount": 2, "totalPages": 1 } ``` **403 Forbidden** - Cross-tenant access: ```json { "error": "Access denied: You can only view invitations in your own tenant." } ``` --- #### `POST /api/invitations/accept` **Description**: Accept an invitation and create user account. **Authorization**: None (public) **Request Body**: ```json { "token": "base64url-encoded-token", "fullName": "Jane Doe", "password": "SecureP@ssw0rd" } ``` **Validation**: - fullName: 2-100 characters - password: Password complexity requirements (8+ chars, uppercase, lowercase, number, special char) **Responses**: **200 OK** - Invitation accepted, user created: ```json { "user": { "id": "5fa85f64-5717-4562-b3fc-2c963f66afa6", "tenantId": "1fa85f64-5717-4562-b3fc-2c963f66afa6", "email": "newuser@example.com", "fullName": "Jane Doe", "role": "Developer", "status": "Active", "isEmailVerified": true, "createdAt": "2025-11-03T11:00:00Z" }, "tenant": { "id": "1fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "Acme Corp", "slug": "acme-corp" }, "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refreshToken": "base64-encoded-refresh-token" } ``` **400 Bad Request** - Invalid token: ```json { "error": "Invalid or expired invitation token.", "code": "INVALID_INVITATION" } ``` **400 Bad Request** - Invitation expired: ```json { "error": "This invitation has expired. Please request a new one from your team admin.", "code": "INVITATION_EXPIRED" } ``` **400 Bad Request** - Invitation already accepted: ```json { "error": "This invitation has already been used.", "code": "INVITATION_ALREADY_USED" } ``` **400 Bad Request** - Password validation failed: ```json { "errors": { "password": [ "Password must be at least 8 characters long", "Password must contain at least one special character" ] } } ``` --- #### `DELETE /api/tenants/{tenantId}/invitations/{invitationId}` **Description**: Cancel a pending invitation. **Authorization**: `RequireTenantOwner` or `RequireTenantAdmin` **Path Parameters**: - `tenantId` (Guid) - Target tenant ID - `invitationId` (Guid) - Invitation ID **Responses**: **204 No Content** - Invitation canceled successfully **400 Bad Request** - Invitation not pending: ```json { "error": "Only pending invitations can be canceled.", "code": "INVITATION_NOT_PENDING" } ``` **403 Forbidden** - Cross-tenant access: ```json { "error": "Access denied: You can only cancel invitations in your own tenant." } ``` **404 Not Found** - Invitation not found: ```json { "error": "Invitation not found.", "code": "INVITATION_NOT_FOUND" } ``` --- ## 7. Database Schema Changes ### 7.1 New Tables #### `email_verification_tokens` **Purpose**: Store email verification tokens for new users. ```sql CREATE TABLE email_verification_tokens ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, email VARCHAR(255) NOT NULL, -- For validation token_hash VARCHAR(64) NOT NULL UNIQUE, -- SHA-256 hash expires_at TIMESTAMP NOT NULL, verified_at TIMESTAMP NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW(), INDEX idx_user_id (user_id), INDEX idx_token_hash (token_hash), INDEX idx_expires_at (expires_at) ); COMMENT ON TABLE email_verification_tokens IS 'Email verification tokens for user registration'; COMMENT ON COLUMN email_verification_tokens.token_hash IS 'SHA-256 hash of verification token (not plaintext)'; COMMENT ON COLUMN email_verification_tokens.verified_at IS 'Timestamp when email was verified (NULL if not verified)'; ``` **Indexes**: - Primary key on `id` - Index on `user_id` (for user lookup) - Unique index on `token_hash` (for token validation) - Index on `expires_at` (for cleanup queries) **Business Rules**: - One active token per user (enforce in application layer) - Tokens expire after 24 hours - Verified tokens kept for audit (not deleted) --- #### `password_reset_tokens` **Purpose**: Store password reset tokens for forgot password flow. ```sql CREATE TABLE password_reset_tokens ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash VARCHAR(64) NOT NULL UNIQUE, -- SHA-256 hash expires_at TIMESTAMP NOT NULL, used_at TIMESTAMP NULL, ip_address VARCHAR(45) NULL, -- IPv4 or IPv6 user_agent VARCHAR(500) NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW(), INDEX idx_user_id (user_id), INDEX idx_token_hash (token_hash), INDEX idx_expires_at (expires_at) ); COMMENT ON TABLE password_reset_tokens IS 'Password reset tokens for forgot password flow'; COMMENT ON COLUMN password_reset_tokens.token_hash IS 'SHA-256 hash of reset token (not plaintext)'; COMMENT ON COLUMN password_reset_tokens.used_at IS 'Timestamp when token was used (NULL if not used)'; ``` **Indexes**: - Primary key on `id` - Index on `user_id` (for user lookup) - Unique index on `token_hash` (for token validation) - Index on `expires_at` (for cleanup queries) **Business Rules**: - Tokens expire after 1 hour - Used tokens cannot be reused (`used_at` != NULL) - New reset request invalidates old unused tokens --- #### `invitations` **Purpose**: Store user invitations to tenants. ```sql CREATE TABLE invitations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, email VARCHAR(255) NOT NULL, role VARCHAR(50) NOT NULL, -- TenantAdmin, Developer, Guest token_hash VARCHAR(64) NOT NULL UNIQUE, -- SHA-256 hash status VARCHAR(20) NOT NULL DEFAULT 'Pending', -- Pending, Accepted, Expired, Canceled invited_by_user_id UUID NOT NULL REFERENCES users(id), accepted_by_user_id UUID NULL REFERENCES users(id), invited_at TIMESTAMP NOT NULL DEFAULT NOW(), accepted_at TIMESTAMP NULL, expires_at TIMESTAMP NOT NULL, canceled_at TIMESTAMP NULL, INDEX idx_tenant_id (tenant_id), INDEX idx_email (email), INDEX idx_token_hash (token_hash), INDEX idx_status (status), INDEX idx_expires_at (expires_at), CONSTRAINT chk_role CHECK (role IN ('TenantAdmin', 'Developer', 'Guest')), CONSTRAINT chk_status CHECK (status IN ('Pending', 'Accepted', 'Expired', 'Canceled')), CONSTRAINT uq_tenant_email_pending UNIQUE (tenant_id, email, status) WHERE status = 'Pending' ); COMMENT ON TABLE invitations IS 'User invitations to tenants'; COMMENT ON COLUMN invitations.token_hash IS 'SHA-256 hash of invitation token (not plaintext)'; COMMENT ON COLUMN invitations.status IS 'Invitation lifecycle status'; COMMENT ON CONSTRAINT uq_tenant_email_pending ON invitations IS 'Prevent duplicate pending invitations for same email in same tenant'; ``` **Indexes**: - Primary key on `id` - Index on `tenant_id` (for tenant lookup) - Index on `email` (for duplicate check) - Unique index on `token_hash` (for token validation) - Index on `status` (for filtering) - Partial unique index on `(tenant_id, email, status)` where status = 'Pending' (prevent duplicates) **Business Rules**: - Cannot have multiple pending invitations for same email in same tenant - Invitations expire after 7 days - Accepted invitations create user account and assign role --- ### 7.2 Modified Tables #### `users` Table Changes **No schema changes required**. Existing columns support email verification: ```sql -- Existing columns (no changes needed) email_verified_at TIMESTAMP NULL -- NULL = not verified, NOT NULL = verified ``` **Usage**: - Set `email_verified_at = NOW()` when email verification succeeds - Check `email_verified_at IS NOT NULL` to determine if email is verified --- ### 7.3 Entity Framework Core Migrations **Migration Name**: `Add_EmailVerification_PasswordReset_Invitations` **Migration Steps**: 1. Create `email_verification_tokens` table 2. Create `password_reset_tokens` table 3. Create `invitations` table 4. Add indexes and constraints 5. Seed initial data (none required) **Rollback Strategy**: - Drop tables in reverse order - No data migration needed (new feature) --- ### 7.4 Database Cleanup Jobs (Future) **Not in Day 7 scope**, but document for future: ```sql -- Delete expired email verification tokens (older than 30 days) DELETE FROM email_verification_tokens WHERE expires_at < NOW() - INTERVAL '30 days'; -- Delete used password reset tokens (older than 30 days) DELETE FROM password_reset_tokens WHERE used_at IS NOT NULL AND used_at < NOW() - INTERVAL '30 days'; -- Mark expired invitations as Expired UPDATE invitations SET status = 'Expired' WHERE status = 'Pending' AND expires_at < NOW(); ``` **Future**: Implement background job (Hangfire or similar) to run cleanup daily. --- ## 8. Security Requirements ### 8.1 Token Security #### SEC-001: Cryptographically Secure Token Generation **Priority**: P0 (Critical) **Requirements**: - Use `RandomNumberGenerator.Create()` (not `Random()`) - Generate 256-bit (32-byte) tokens - Encode as Base64URL for URL safety - Never log tokens in plaintext **Implementation**: ```csharp public static string GenerateSecureToken() { var randomBytes = new byte[32]; // 256 bits using (var rng = RandomNumberGenerator.Create()) { rng.GetBytes(randomBytes); } return Convert.ToBase64String(randomBytes) .TrimEnd('=') .Replace('+', '-') .Replace('/', '_'); // Base64URL encoding } ``` #### SEC-002: Token Hashing in Database **Priority**: P0 (Critical) **Requirements**: - Store SHA-256 hash, never plaintext token - Hash before database insert - Compare hashes during validation **Implementation**: ```csharp public static string HashToken(string token) { using (var sha256 = SHA256.Create()) { var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(token)); return Convert.ToBase64String(hashBytes); } } ``` ### 8.2 Rate Limiting #### SEC-003: Email Verification Rate Limits **Priority**: P0 (Critical) **Limits**: - Resend verification: 3 requests per email per hour - Verify email: 10 attempts per IP per minute (prevent brute force) **Implementation**: Use ASP.NET Core Rate Limiting middleware or in-memory cache #### SEC-004: Password Reset Rate Limits **Priority**: P0 (Critical) **Limits**: - Forgot password: 3 requests per email per hour - Reset password: 5 attempts per IP per minute (prevent brute force) **Error Response**: 429 Too Many Requests with `Retry-After` header #### SEC-005: Invitation Rate Limits **Priority**: P1 (High) **Limits**: - Create invitation: 20 invitations per tenant per hour - Accept invitation: 5 attempts per token per hour (prevent brute force) ### 8.3 Email Enumeration Prevention #### SEC-006: Never Reveal Email Existence **Priority**: P0 (Critical) **Requirements**: - Forgot password: Always return 200 OK, never reveal if email exists - Resend verification: Always return 200 OK, never reveal if email exists - Invitation: Return 400 "User already exists" only to authenticated tenant admins **Example**: ```csharp // ✅ CORRECT: Prevent enumeration [HttpPost("forgot-password")] public async Task ForgotPassword([FromBody] ForgotPasswordRequest request) { // Process in background, don't wait _ = _emailService.SendPasswordResetEmailIfUserExists(request.Email); // Always return same response return Ok(new { message = "If an account exists, a reset email has been sent." }); } // ❌ WRONG: Reveals if email exists [HttpPost("forgot-password")] public async Task ForgotPassword([FromBody] ForgotPasswordRequest request) { var user = await _userRepository.GetByEmail(request.Email); if (user == null) return NotFound("Email not found"); // ❌ Enumeration vulnerability! await _emailService.SendPasswordResetEmail(user); return Ok(); } ``` ### 8.4 HTTPS Enforcement #### SEC-007: HTTPS-Only Links **Priority**: P0 (Critical) **Requirements**: - All email links must use `https://` (never `http://`) - Redirect HTTP to HTTPS at infrastructure level - Set `Strict-Transport-Security` header (HSTS) **Configuration**: ```csharp app.UseHttpsRedirection(); app.UseHsts(); // Enforce HTTPS for 1 year ``` ### 8.5 Input Validation #### SEC-008: Email Validation **Priority**: P0 (Critical) **Requirements**: - Validate email format using `EmailAddressAttribute` and regex - Normalize emails to lowercase - Trim whitespace - Max length: 255 characters - Reject disposable email domains (future enhancement) **Validation**: ```csharp [EmailAddress] [MaxLength(255)] public string Email { get; set; } ``` #### SEC-009: Password Complexity Validation **Priority**: P0 (Critical) **Requirements** (repeated for emphasis): - Minimum 8 characters - At least 1 uppercase letter - At least 1 lowercase letter - At least 1 number - At least 1 special character - Max length: 128 characters (prevent DoS via bcrypt) **Implementation**: Use `DataAnnotations` + custom validator ### 8.6 Session Security #### SEC-010: Refresh Token Revocation on Password Reset **Priority**: P0 (Critical) **Requirements**: - On successful password reset, invalidate all user's refresh tokens - Force re-login on all devices - Prevent attacker with stolen tokens from maintaining access **Implementation**: ```csharp // In ResetPasswordCommandHandler await _refreshTokenService.RevokeAllUserTokensAsync(user.Id, cancellationToken); ``` ### 8.7 Audit Logging #### SEC-011: Security Event Logging **Priority**: P0 (Critical) **Events to Log** (with IP address, user agent, timestamp): 1. Email verification sent 2. Email verification succeeded/failed 3. Password reset requested 4. Password reset succeeded/failed 5. Invitation created 6. Invitation accepted 7. Invitation canceled 8. Rate limit exceeded 9. Invalid token attempts **Log Format**: ```json { "timestamp": "2025-11-03T10:00:00Z", "event": "PasswordResetRequested", "email": "user@example.com", "tenantSlug": "acme-corp", "ipAddress": "192.168.1.1", "userAgent": "Mozilla/5.0...", "success": true } ``` ### 8.8 Cross-Tenant Security #### SEC-012: Tenant Isolation in Invitations **Priority**: P0 (Critical) **Requirements**: - Validate `tenantId` from route matches JWT `tenant_id` claim - Users can only invite to their own tenant - Users can only view invitations for their own tenant - Return 403 Forbidden for cross-tenant access attempts **Implementation** (reuse pattern from Day 6): ```csharp var userTenantId = Guid.Parse(User.FindFirst("tenant_id")?.Value); if (userTenantId != tenantId) return StatusCode(403, new { error = "Access denied: Cross-tenant access not allowed" }); ``` --- ## 9. Email Templates ### 9.1 Template Architecture **Template Engine**: C# String Interpolation or Razor Pages (recommend Razor for complex templates) **Template Structure**: ``` EmailTemplates/ ├── _Layout.cshtml # Shared layout with branding ├── EmailVerification.cshtml ├── PasswordReset.cshtml ├── UserInvitation.cshtml └── WelcomeEmail.cshtml (optional) ``` **Shared Layout** (`_Layout.cshtml`): ```html @ViewBag.Subject - ColaFlow

🧠 ColaFlow

@RenderBody()
``` ### 9.2 Email Verification Template **File**: `EmailTemplates/EmailVerification.cshtml` **Subject**: "Verify your email address - ColaFlow" **Template**: ```html @{ ViewBag.Subject = "Verify your email address"; }

Welcome to ColaFlow, @Model.FullName!

Thank you for registering with @Model.TenantName on ColaFlow.

Please verify your email address by clicking the button below:

Verify Email Address

Or copy and paste this link into your browser:

@Model.VerificationUrl

This link will expire in 24 hours.

If you didn't create an account, you can safely ignore this email.

Best regards,
The ColaFlow Team

``` **Model**: ```csharp public class EmailVerificationModel { public string FullName { get; set; } public string TenantName { get; set; } public string VerificationUrl { get; set; } // https://app.colaflow.io/verify-email?token=... } ``` **Plain Text Fallback**: ``` Welcome to ColaFlow, [FullName]! Thank you for registering with [TenantName] on ColaFlow. Please verify your email address by clicking this link: [VerificationUrl] This link will expire in 24 hours. If you didn't create an account, you can safely ignore this email. Best regards, The ColaFlow Team --- © 2025 ColaFlow. This is an automated email. Please do not reply. ``` ### 9.3 Password Reset Template **File**: `EmailTemplates/PasswordReset.cshtml` **Subject**: "Reset your password - ColaFlow" **Template**: ```html @{ ViewBag.Subject = "Reset your password"; }

Password Reset Request

Hi @Model.FullName,

We received a request to reset the password for your ColaFlow account (@Model.TenantName).

Click the button below to reset your password:

Reset Password

Or copy and paste this link into your browser:

@Model.ResetUrl

This link will expire in 1 hour.

⚠️ Security Notice: If you didn't request a password reset, please ignore this email and ensure your account is secure.

Best regards,
The ColaFlow Team

``` **Model**: ```csharp public class PasswordResetModel { public string FullName { get; set; } public string TenantName { get; set; } public string ResetUrl { get; set; } // https://app.colaflow.io/reset-password?token=... } ``` ### 9.4 User Invitation Template **File**: `EmailTemplates/UserInvitation.cshtml` **Subject**: "You're invited to join [TenantName] on ColaFlow" **Template**: ```html @{ ViewBag.Subject = $"You're invited to join {Model.TenantName} on ColaFlow"; }

You've been invited! 🎉

Hi there,

@Model.InviterName has invited you to join @Model.TenantName on ColaFlow as a @Model.Role.

ColaFlow is an AI-powered project management platform that helps teams collaborate more effectively.

Click the button below to accept the invitation and create your account:

Accept Invitation

Or copy and paste this link into your browser:

@Model.AcceptUrl

This invitation will expire on @Model.ExpiresAt.ToString("MMMM dd, yyyy").

Your Role: @Model.RoleDescription

If you didn't expect this invitation, you can safely ignore this email.

Best regards,
The ColaFlow Team

``` **Model**: ```csharp public class UserInvitationModel { public string TenantName { get; set; } public string InviterName { get; set; } public string Role { get; set; } // "Developer" public string RoleDescription { get; set; } // "Developers can create and manage projects..." public string AcceptUrl { get; set; } // https://app.colaflow.io/accept-invitation?token=... public DateTime ExpiresAt { get; set; } } ``` **Role Descriptions**: - **TenantAdmin**: "Admins can manage team members, view all projects, and configure tenant settings." - **Developer**: "Developers can create and manage projects, tasks, and collaborate with the team." - **Guest**: "Guests have read-only access to assigned projects and can leave comments." ### 9.5 Template Rendering Service **Interface**: ```csharp public interface IEmailTemplateRenderer { Task RenderHtmlAsync(string templateName, TModel model); Task RenderPlainTextAsync(string templateName, TModel model); } ``` **Implementation** (Razor Pages or simple string interpolation for Day 7): ```csharp public class EmailTemplateRenderer : IEmailTemplateRenderer { private readonly string _templateBasePath; public EmailTemplateRenderer(IConfiguration configuration) { _templateBasePath = configuration["EmailSettings:TemplateBasePath"] ?? "EmailTemplates"; } public async Task RenderHtmlAsync(string templateName, TModel model) { var templatePath = Path.Combine(_templateBasePath, $"{templateName}.cshtml"); // Use RazorEngine or simple file read + string.Replace for Day 7 // For simplicity, use string interpolation initially var template = await File.ReadAllTextAsync(templatePath); return RenderTemplate(template, model); } private string RenderTemplate(string template, TModel model) { // Simple placeholder replacement for Day 7 // Future: Use RazorEngine for complex logic var properties = typeof(TModel).GetProperties(); foreach (var prop in properties) { var value = prop.GetValue(model)?.ToString() ?? ""; template = template.Replace($"@Model.{prop.Name}", value); } return template; } } ``` ### 9.6 Email Styling Guidelines **Design Principles**: 1. **Mobile-First**: 600px max width, responsive design 2. **Accessibility**: High contrast, readable fonts (16px+ body text) 3. **Brand Consistency**: ColaFlow purple gradient (#667eea to #764ba2) 4. **Clear CTAs**: Primary action button prominently displayed 5. **Plain Text Fallback**: Always provide plain text version 6. **No External Images**: Embed logos as data URIs or use text-based branding **Testing**: - Preview in Gmail, Outlook, Apple Mail - Use tools like Litmus or Email on Acid (future) - Test dark mode compatibility --- ## 10. Integration Points ### 10.1 Registration Flow Enhancement **Current Flow** (`RegisterTenantCommandHandler`): ``` 1. Validate slug uniqueness 2. Create tenant 3. Create admin user 4. Assign TenantOwner role 5. Generate JWT tokens 6. Return result ``` **Day 7 Enhancement**: ``` 1. Validate slug uniqueness 2. Create tenant 3. Create admin user (with emailVerified = false) ✨ NEW 4. Assign TenantOwner role 5. Generate email verification token ✨ NEW 6. Send verification email (non-blocking) ✨ NEW 7. Generate JWT tokens 8. Return result + verification status ✨ NEW ``` **Code Changes**: ```csharp // In RegisterTenantCommandHandler.Handle() // After creating admin user var adminUser = User.CreateLocal( TenantId.Create(tenant.Id), Email.Create(request.AdminEmail), hashedPassword, FullName.Create(request.AdminFullName)); await _userRepository.AddAsync(adminUser, cancellationToken); // ✨ NEW: Generate verification token var verificationToken = _tokenGenerator.GenerateSecureToken(); var verificationTokenHash = _tokenGenerator.HashToken(verificationToken); var emailVerificationToken = new EmailVerificationToken { UserId = adminUser.Id, Email = adminUser.Email.Value, TokenHash = verificationTokenHash, ExpiresAt = DateTime.UtcNow.AddHours(24), CreatedAt = DateTime.UtcNow }; await _emailVerificationTokenRepository.AddAsync(emailVerificationToken, cancellationToken); // ✨ NEW: Send verification email (non-blocking) _ = _emailService.SendEmailVerificationAsync(new EmailVerificationModel { FullName = adminUser.FullName.Value, TenantName = tenant.Name.Value, VerificationUrl = $"{_configuration["AppSettings:WebAppUrl"]}/verify-email?token={verificationToken}" }); // Continue with existing logic (assign role, generate tokens, etc.) ``` **Response Changes**: ```json { "tenant": { ... }, "user": { "id": "...", "email": "...", "isEmailVerified": false, // ✨ NEW: Always false on registration ... }, "accessToken": "...", "refreshToken": "...", "verificationEmailSent": true // ✨ NEW: Indicates email was sent } ``` ### 10.2 Login Flow Enhancement **Current Flow** (`LoginCommandHandler`): ``` 1. Find tenant 2. Find user 3. Verify password 4. Get user's role 5. Generate JWT tokens 6. Return result ``` **Day 7 Enhancement** (Optional, P2 Priority): ``` 1. Find tenant 2. Find user 3. Verify password 4. Check email verification status ✨ NEW (optional) 5. Get user's role 6. Generate JWT tokens 7. Return result + verification warning ✨ NEW ``` **Code Changes** (Optional for Day 7): ```csharp // In LoginCommandHandler.Handle() // After verifying password if (!user.EmailVerifiedAt.HasValue) { // Option A: Allow login, return warning (recommended for Day 7) // No code change needed, just return verification status in response // Option B: Block login (future enhancement) // throw new UnauthorizedAccessException("Please verify your email before logging in."); } // Continue with existing logic ``` **Response Changes**: ```json { "user": { "id": "...", "email": "...", "isEmailVerified": false, // ✨ Frontend shows banner if false ... }, "tenant": { ... }, "accessToken": "...", "refreshToken": "...", "emailVerificationRequired": false // ✨ NEW: true if blocking login (future) } ``` ### 10.3 Role Management API Integration **Current**: Role assignment only via RegisterTenant and manual admin actions. **Day 7 Enhancement**: Role assignment via invitation acceptance. **Integration Point**: `AcceptInvitationCommandHandler` ```csharp // In AcceptInvitationCommandHandler.Handle() // 1. Validate invitation token var invitation = await _invitationRepository.GetByTokenHashAsync(tokenHash, cancellationToken); if (invitation == null || invitation.ExpiresAt < DateTime.UtcNow) throw new InvalidOperationException("Invalid or expired invitation"); // 2. Create user account var user = User.CreateLocal( TenantId.Create(invitation.TenantId), Email.Create(invitation.Email), hashedPassword, FullName.Create(request.FullName)); user.EmailVerifiedAt = DateTime.UtcNow; // ✨ Email verified via invitation acceptance await _userRepository.AddAsync(user, cancellationToken); // 3. Assign role from invitation var userTenantRole = UserTenantRole.Create( UserId.Create(user.Id), TenantId.Create(invitation.TenantId), Enum.Parse(invitation.Role)); // ✨ Role from invitation await _userTenantRoleRepository.AddAsync(userTenantRole, cancellationToken); // 4. Mark invitation as accepted invitation.Status = InvitationStatus.Accepted; invitation.AcceptedAt = DateTime.UtcNow; invitation.AcceptedByUserId = user.Id; await _invitationRepository.UpdateAsync(invitation, cancellationToken); // 5. Generate tokens and return ``` ### 10.4 Domain Events Integration **Day 7 Domain Events** (using existing infrastructure from Day 6): #### `EmailVerificationRequestedEvent` ```csharp public class EmailVerificationRequestedEvent : DomainEvent { public Guid UserId { get; } public string Email { get; } public string VerificationToken { get; } public EmailVerificationRequestedEvent(Guid userId, string email, string token) { UserId = userId; Email = email; VerificationToken = token; } } ``` **Handler**: Send verification email (async) #### `EmailVerifiedEvent` ```csharp public class EmailVerifiedEvent : DomainEvent { public Guid UserId { get; } public DateTime VerifiedAt { get; } public EmailVerifiedEvent(Guid userId, DateTime verifiedAt) { UserId = userId; VerifiedAt = verifiedAt; } } ``` **Handler**: Log event, potentially send welcome email #### `PasswordResetRequestedEvent` ```csharp public class PasswordResetRequestedEvent : DomainEvent { public Guid UserId { get; } public string IpAddress { get; } public DateTime RequestedAt { get; } public PasswordResetRequestedEvent(Guid userId, string ipAddress) { UserId = userId; IpAddress = ipAddress; RequestedAt = DateTime.UtcNow; } } ``` **Handler**: Send reset email, log security event #### `UserInvitedEvent` ```csharp public class UserInvitedEvent : DomainEvent { public Guid InvitationId { get; } public Guid TenantId { get; } public string Email { get; } public string Role { get; } public Guid InvitedByUserId { get; } public UserInvitedEvent(Guid invitationId, Guid tenantId, string email, string role, Guid invitedBy) { InvitationId = invitationId; TenantId = tenantId; Email = email; Role = role; InvitedByUserId = invitedBy; } } ``` **Handler**: Send invitation email #### `InvitationAcceptedEvent` ```csharp public class InvitationAcceptedEvent : DomainEvent { public Guid InvitationId { get; } public Guid UserId { get; } public Guid TenantId { get; } public DateTime AcceptedAt { get; } public InvitationAcceptedEvent(Guid invitationId, Guid userId, Guid tenantId) { InvitationId = invitationId; UserId = userId; TenantId = tenantId; AcceptedAt = DateTime.UtcNow; } } ``` **Handler**: Log event, notify inviter (future), send welcome email --- ## 11. Testing Strategy ### 11.1 Unit Tests **Coverage Target**: 90%+ for business logic #### Email Service Tests ```csharp // Tests/Modules/Identity/ColaFlow.Modules.Identity.UnitTests/Services/EmailServiceTests.cs public class SendGridEmailServiceTests { [Fact] public async Task SendEmailAsync_WithValidInput_ShouldSucceed() { // Arrange: Mock SendGrid client // Act: Send email // Assert: SendGrid API called with correct parameters } [Fact] public async Task SendEmailAsync_WithInvalidApiKey_ShouldThrowException() { // Arrange: Invalid API key // Act & Assert: Should throw UnauthorizedAccessException } } ``` #### Token Generation Tests ```csharp public class SecureTokenGeneratorTests { [Fact] public void GenerateSecureToken_ShouldReturnUniqueTokens() { // Generate 1000 tokens, ensure all unique } [Fact] public void GenerateSecureToken_ShouldBeUrlSafe() { // Assert: No '+', '/', '=' characters } [Fact] public void HashToken_ShouldBeIdempotent() { // Same token should produce same hash } } ``` #### Password Validation Tests ```csharp public class PasswordValidatorTests { [Theory] [InlineData("short", false)] // Too short [InlineData("nouppercase1!", false)] // No uppercase [InlineData("NOLOWERCASE1!", false)] // No lowercase [InlineData("NoNumbers!", false)] // No numbers [InlineData("NoSpecialChar1", false)] // No special char [InlineData("ValidP@ssw0rd", true)] // Valid public void ValidatePassword_ShouldEnforceComplexity(string password, bool expected) { var result = PasswordValidator.Validate(password); Assert.Equal(expected, result.IsValid); } } ``` ### 11.2 Integration Tests **Coverage Target**: All API endpoints + critical flows #### Email Verification Flow Tests ```csharp // Tests/Modules/Identity/ColaFlow.Modules.Identity.IntegrationTests/EmailVerificationTests.cs public class EmailVerificationFlowTests : IClassFixture { [Fact] public async Task VerifyEmail_WithValidToken_ShouldMarkEmailAsVerified() { // Arrange: Register tenant (generates verification token) var registerResponse = await RegisterTenant(); var token = GetVerificationTokenFromEmail(); // Mock email capture // Act: Verify email var response = await _client.PostAsJsonAsync("/api/auth/verify-email", new { token }); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var user = await GetUser(registerResponse.User.Id); user.EmailVerifiedAt.Should().NotBeNull(); } [Fact] public async Task VerifyEmail_WithExpiredToken_ShouldFail() { // Arrange: Create expired token (manually set ExpiresAt in past) // Act: Attempt verification // Assert: 400 Bad Request } [Fact] public async Task ResendVerification_ShouldInvalidateOldToken() { // Arrange: Register + get initial token // Act: Resend verification // Assert: Old token no longer works, new token works } [Fact] public async Task ResendVerification_ShouldRespectRateLimit() { // Arrange: Register tenant // Act: Resend 4 times quickly // Assert: 4th request returns 429 Too Many Requests } } ``` #### Password Reset Flow Tests ```csharp public class PasswordResetFlowTests : IClassFixture { [Fact] public async Task ForgotPassword_WithValidEmail_ShouldSendResetEmail() { // Arrange: Create user // Act: Request password reset // Assert: 200 OK, email sent (verify with mock email service) } [Fact] public async Task ForgotPassword_WithNonexistentEmail_ShouldNotRevealExistence() { // Act: Request reset for nonexistent email // Assert: 200 OK (same response as valid email) } [Fact] public async Task ResetPassword_WithValidToken_ShouldUpdatePassword() { // Arrange: Request password reset, get token // Act: Reset password with new password // Assert: Password updated, can login with new password } [Fact] public async Task ResetPassword_ShouldRevokeRefreshTokens() { // Arrange: Login (get refresh token), request password reset // Act: Reset password // Assert: Old refresh token no longer works } [Fact] public async Task ResetPassword_WithUsedToken_ShouldFail() { // Arrange: Reset password once // Act: Attempt to reuse same token // Assert: 400 Bad Request } } ``` #### User Invitation Flow Tests ```csharp public class UserInvitationFlowTests : IClassFixture { [Fact] public async Task InviteUser_AsOwner_ShouldCreateInvitation() { // Arrange: Register tenant as owner // Act: Invite user with Developer role // Assert: 201 Created, invitation stored, email sent } [Fact] public async Task InviteUser_WithInvalidRole_ShouldFail() { // Act: Attempt to invite as TenantOwner or AIAgent // Assert: 400 Bad Request } [Fact] public async Task InviteUser_AsGuest_ShouldFail() { // Arrange: Create guest user // Act: Attempt to invite // Assert: 403 Forbidden } [Fact] public async Task AcceptInvitation_WithValidToken_ShouldCreateUser() { // Arrange: Owner invites user, get invitation token // Act: Accept invitation with name + password // Assert: User created, role assigned, email verified, logged in } [Fact] public async Task AcceptInvitation_WithExpiredToken_ShouldFail() { // Arrange: Create invitation with past expiration // Act: Attempt to accept // Assert: 400 Bad Request } [Fact] public async Task ListInvitations_AsOwner_ShouldReturnTenantInvitations() { // Arrange: Owner creates 3 invitations // Act: List invitations // Assert: Returns 3 invitations with correct data } [Fact] public async Task CancelInvitation_AsOwner_ShouldMarkAsCanceled() { // Arrange: Create invitation // Act: Cancel invitation // Assert: Status = Canceled, token no longer works } [Fact] public async Task InviteUser_CrossTenant_ShouldFail() { // Arrange: Owner of Tenant A // Act: Attempt to invite to Tenant B // Assert: 403 Forbidden } } ``` ### 11.3 Unblocking Skipped Tests **Day 6 Skipped Tests** (from test report): 1. `RemoveUser_AsOwner_ShouldSucceed` - **UNBLOCKED by Invitation System** ```csharp // Now testable: // 1. Owner invites user // 2. User accepts invitation // 3. Owner removes user // 4. Assert: User removed, tokens revoked ``` 2. `RemoveUser_RevokesTokens_ShouldWork` - **UNBLOCKED by Invitation System** ```csharp // Now testable: // 1. Invite user, user accepts and logs in (gets refresh token) // 2. Owner removes user // 3. Assert: User's refresh tokens revoked ``` 3. `RemoveUser_RequiresOwnerPolicy_ShouldBeEnforced` - **UNBLOCKED by Invitation System** ```csharp // Now testable: // 1. Invite user as Developer // 2. Developer attempts to remove another user // 3. Assert: 403 Forbidden ``` **Day 7 Test Deliverable**: Unskip these 3 tests and verify they pass. ### 11.4 Test Data Management **Test Email Capture** (for integration tests): ```csharp // Mock email service that captures emails instead of sending public class MockEmailService : IEmailService { public List SentEmails { get; } = new(); public Task SendEmailAsync(EmailMessage message) { SentEmails.Add(message); return Task.CompletedTask; } } // Usage in tests var emailService = _testServer.Services.GetRequiredService() as MockEmailService; var lastEmail = emailService.SentEmails.Last(); var verificationToken = ExtractTokenFromEmailBody(lastEmail.Body); ``` **Test Token Extraction**: ```csharp private string ExtractTokenFromEmailBody(string emailBody) { // Extract token from URL in email body var match = Regex.Match(emailBody, @"token=([a-zA-Z0-9_-]+)"); return match.Groups[1].Value; } ``` ### 11.5 Test Coverage Summary | Feature | Unit Tests | Integration Tests | Total | |---------|-----------|-------------------|-------| | Email Service | 5 | 3 | 8 | | Email Verification | 4 | 6 | 10 | | Password Reset | 5 | 8 | 13 | | User Invitation | 6 | 10 | 16 | | Token Generation | 4 | - | 4 | | **Total** | **24** | **27** | **51** | **Day 7 Test Goal**: 51 new tests + 3 unskipped tests = **54 total new/updated tests** --- ## 12. Implementation Plan ### 12.1 Implementation Phases #### Phase 1: Email Service Foundation (Day 7.1 - 4 hours) **Priority**: P0 - Foundation for all other features **Tasks**: 1. Create `IEmailService` interface 2. Implement `SendGridEmailService` 3. Implement `SmtpEmailService` 4. Implement `MockEmailService` (for tests) 5. Add email configuration to `appsettings.json` 6. Register services in DI container 7. Create `IEmailTemplateRenderer` interface 8. Implement simple template renderer (string interpolation) 9. Unit tests for email service **Deliverables**: - [ ] `IEmailService` interface - [ ] 3 email service implementations - [ ] Configuration setup - [ ] Template rendering infrastructure - [ ] 8 unit tests **Dependencies**: None (foundation) **Risk**: Low (well-defined requirements) --- #### Phase 2: Email Templates (Day 7.2 - 2 hours) **Priority**: P0 - Required for all email features **Tasks**: 1. Create shared email layout (`_Layout.cshtml`) 2. Create `EmailVerification.cshtml` template 3. Create `PasswordReset.cshtml` template 4. Create `UserInvitation.cshtml` template 5. Create plain text fallbacks for all templates 6. Test template rendering with sample data **Deliverables**: - [ ] 4 HTML email templates - [ ] 4 plain text templates - [ ] Template preview tool (optional) **Dependencies**: Phase 1 (template renderer) **Risk**: Low (static content) --- #### Phase 3: Email Verification (Day 7.3 - 6 hours) **Priority**: P0 - Security requirement **Tasks**: 1. Create `EmailVerificationToken` entity 2. Create `IEmailVerificationTokenRepository` interface + implementation 3. Create EF Core migration for `email_verification_tokens` table 4. Implement token generation and hashing logic 5. Update `RegisterTenantCommandHandler` to send verification email 6. Create `VerifyEmailCommand` and handler 7. Create `ResendVerificationEmailCommand` and handler 8. Add rate limiting for resend endpoint 9. Create API endpoints in `AuthController` 10. Integration tests (6 tests) **Deliverables**: - [ ] `EmailVerificationToken` entity and repository - [ ] Database migration - [ ] 2 commands + handlers - [ ] 2 API endpoints - [ ] 10 integration tests **Dependencies**: Phase 1, Phase 2 **Risk**: Medium (complex flow, security critical) --- #### Phase 4: Password Reset (Day 7.4 - 6 hours) **Priority**: P0 - Critical user experience **Tasks**: 1. Create `PasswordResetToken` entity 2. Create `IPasswordResetTokenRepository` interface + implementation 3. Create EF Core migration for `password_reset_tokens` table 4. Implement token generation and hashing logic 5. Create `ForgotPasswordCommand` and handler 6. Create `ResetPasswordCommand` and handler 7. Implement password complexity validation 8. Add refresh token revocation on password reset 9. Add rate limiting for forgot password endpoint 10. Create API endpoints in `AuthController` 11. Integration tests (8 tests) **Deliverables**: - [ ] `PasswordResetToken` entity and repository - [ ] Database migration - [ ] 2 commands + handlers - [ ] 2 API endpoints - [ ] Password validator - [ ] 13 integration tests **Dependencies**: Phase 1, Phase 2 **Risk**: Medium (security critical, token revocation) --- #### Phase 5: User Invitation System (Day 7.5 - 8 hours) **Priority**: P0 - Unblocks multi-user testing **Tasks**: 1. Create `Invitation` entity with status enum 2. Create `IInvitationRepository` interface + implementation 3. Create EF Core migration for `invitations` table 4. Create `InviteUserCommand` and handler 5. Create `ListInvitationsQuery` and handler 6. Create `AcceptInvitationCommand` and handler (creates user + assigns role) 7. Create `CancelInvitationCommand` and handler 8. Implement role validation (cannot invite as TenantOwner/AIAgent) 9. Add duplicate invitation prevention 10. Create API endpoints in `TenantUsersController` (invite, list, cancel) 11. Create public endpoint in `AuthController` (accept) 12. Integration tests (10 tests) **Deliverables**: - [ ] `Invitation` entity and repository - [ ] Database migration - [ ] 4 commands + handlers - [ ] 4 API endpoints - [ ] 16 integration tests **Dependencies**: Phase 1, Phase 2, Phase 3 (user creation logic) **Risk**: High (complex flow, multi-tenant concerns, role assignment) --- #### Phase 6: Unskip Day 6 Tests (Day 7.6 - 2 hours) **Priority**: P0 - Test coverage requirement **Tasks**: 1. Unskip `RemoveUser_AsOwner_ShouldSucceed` 2. Unskip `RemoveUser_RevokesTokens_ShouldWork` 3. Unskip `RemoveUser_RequiresOwnerPolicy_ShouldBeEnforced` 4. Update tests to use invitation flow for creating second user 5. Verify all 3 tests pass **Deliverables**: - [ ] 3 previously skipped tests now passing - [ ] Test report updated **Dependencies**: Phase 5 (invitation system) **Risk**: Low (tests already written, just need invitation infrastructure) --- #### Phase 7: Security Hardening & Documentation (Day 7.7 - 2 hours) **Priority**: P1 - Production readiness **Tasks**: 1. Add comprehensive audit logging for all security events 2. Verify HTTPS enforcement in configuration 3. Add rate limiting middleware configuration 4. Security review of all endpoints 5. Update API documentation (Swagger/OpenAPI) 6. Write Day 7 implementation summary document 7. Update project README with new features **Deliverables**: - [ ] Audit logging for all email/auth events - [ ] Rate limiting configuration - [ ] Security audit checklist - [ ] Updated API documentation - [ ] Day 7 implementation summary **Dependencies**: All previous phases **Risk**: Low (documentation and configuration) --- ### 12.2 Implementation Schedule **Total Estimated Time**: 30 hours (3.75 developer days) | Phase | Duration | Start | End | Developer | |-------|----------|-------|-----|-----------| | Phase 1: Email Service | 4 hours | Day 7.0h | Day 7.4h | Backend | | Phase 2: Email Templates | 2 hours | Day 7.4h | Day 7.6h | Backend | | Phase 3: Email Verification | 6 hours | Day 7.6h | Day 7.12h | Backend | | Phase 4: Password Reset | 6 hours | Day 7.12h | Day 7.18h | Backend | | Phase 5: User Invitation | 8 hours | Day 7.18h | Day 7.26h | Backend | | Phase 6: Unskip Tests | 2 hours | Day 7.26h | Day 7.28h | QA/Backend | | Phase 7: Security & Docs | 2 hours | Day 7.28h | Day 7.30h | Backend/PM | **Recommended Schedule**: 4 working days (7.5 hours/day) with buffer for unexpected issues **Parallel Work Opportunities**: - Phases 3 and 4 can be developed in parallel (different developers) - Phase 2 (templates) can be done by frontend developer or designer --- ### 12.3 Implementation Dependencies ``` Phase 1 (Email Service) ↓ Phase 2 (Email Templates) ↓ ┌─────────────┬─────────────┐ │ │ │ Phase 3 Phase 4 (Email (Password Verification) Reset) │ │ │ └─────────────┴─────────────┘ ↓ Phase 5 (User Invitation) ↓ Phase 6 (Unskip Tests) ↓ Phase 7 (Security & Docs) ``` **Critical Path**: Phase 1 → Phase 2 → Phase 5 → Phase 6 (required for test unblocking) **Optional Path**: Phase 3, Phase 4 (can be deferred to Day 8 if time-constrained, but not recommended) --- ### 12.4 Definition of Done Each phase is considered complete when: **Phase Completion Criteria**: - [ ] All code written and reviewed - [ ] Unit tests written and passing (if applicable) - [ ] Integration tests written and passing - [ ] Code coverage ≥90% for business logic - [ ] API documentation updated - [ ] Security review completed - [ ] No compiler warnings or errors - [ ] Database migrations tested (up and down) - [ ] Manual testing completed (happy path + error cases) **Day 7 Completion Criteria**: - [ ] All 4 features implemented and tested - [ ] 51 new tests written and passing - [ ] 3 skipped tests from Day 6 now passing - [ ] Total test count: 97 tests (46 from Days 4-6 + 51 new) - [ ] Email delivery working in development (verified manually) - [ ] Security audit checklist 100% complete - [ ] Day 7 implementation summary document published - [ ] Demo prepared for stakeholder review --- ## 13. Risk Assessment ### 13.1 Technical Risks #### RISK-001: Email Delivery Failures **Severity**: HIGH **Probability**: MEDIUM **Impact**: Users cannot verify emails or reset passwords **Mitigation**: 1. Use reliable email provider (SendGrid with 99.9% SLA) 2. Implement retry logic with exponential backoff 3. Circuit breaker pattern to prevent cascading failures 4. Fallback to SMTP if SendGrid fails (future enhancement) 5. Email delivery monitoring and alerting (future: Sentry integration) 6. Non-blocking email sends (user action succeeds even if email fails) **Rollback Plan**: - If email service completely fails, disable email features temporarily - Users can still register/login (email verification is optional for Day 7) - Manual password reset via admin console (future feature) **Owner**: Backend Team --- #### RISK-002: Token Security Vulnerabilities **Severity**: CRITICAL **Probability**: LOW **Impact**: Account takeover, unauthorized access **Mitigation**: 1. Use cryptographically secure random number generator 2. Store token hashes (SHA-256), never plaintext 3. Short token expiration (1h for password reset, 24h for verification) 4. Token reuse prevention (mark as used) 5. Rate limiting on token-based endpoints 6. HTTPS enforcement (no tokens over HTTP) 7. Security audit of token generation and validation logic **Detection**: - Audit logs for suspicious token activity - Monitor failed verification attempts - Alert on high volume of token generation from single IP **Rollback Plan**: - If vulnerability discovered, immediately revoke all outstanding tokens - Notify affected users via email (if email service is working) - Force password reset for all users (last resort) **Owner**: Security Team / Backend Team --- #### RISK-003: Database Migration Failures **Severity**: MEDIUM **Probability**: LOW **Impact**: Deployment blocked, downtime **Mitigation**: 1. Test migrations in staging environment first 2. Create rollback migrations for all schema changes 3. Use EF Core migration idempotency (can run multiple times safely) 4. Backup database before migration in production 5. Blue-green deployment strategy (future) **Rollback Plan**: ```bash # If migration fails, rollback: dotnet ef database update ``` **Owner**: DevOps Team / Backend Team --- #### RISK-004: Rate Limiting Bypass **Severity**: MEDIUM **Probability**: MEDIUM **Impact**: Email spam, DoS attacks, abuse **Mitigation**: 1. Implement rate limiting at multiple layers: - Application level (in-memory cache) - API Gateway level (future: Azure API Management) - Email provider level (SendGrid rate limits) 2. Use IP-based and email-based rate limiting 3. CAPTCHA for public endpoints (future enhancement) 4. Monitor for suspicious activity patterns **Detection**: - Logs showing rate limit exceeded - Metrics on email send volume per tenant - Alert on anomalies (e.g., 100 invitations in 1 minute) **Rollback Plan**: - Temporarily increase rate limits if legitimate traffic is blocked - Ban abusive IP addresses at firewall level - Disable user invitation endpoint if under attack (preserve other features) **Owner**: Backend Team / Security Team --- ### 13.2 Business Risks #### RISK-005: Email Deliverability Issues **Severity**: HIGH **Probability**: MEDIUM **Impact**: Low email verification rate, user frustration, support burden **Causes**: - Emails marked as spam by email providers - SPF/DKIM/DMARC not configured properly - Email content triggers spam filters - Disposable email addresses (not supported) **Mitigation**: 1. Configure SPF, DKIM, DMARC records for domain 2. Use reputable email provider (SendGrid with good reputation) 3. Test emails with major providers (Gmail, Outlook, Yahoo) 4. Clear, non-promotional email content 5. "Add to contacts" instructions in emails 6. Monitor email bounce rates and spam reports **Metrics to Monitor**: - Email delivery rate (target: >99%) - Email open rate (target: >60%) - Spam complaint rate (target: <0.1%) - Bounce rate (target: <5%) **Contingency**: - If deliverability drops, switch to alternative email provider - Provide manual verification option via support ticket - Add "didn't receive email?" troubleshooting guide **Owner**: Product Manager / DevOps Team --- #### RISK-006: User Adoption of Email Verification **Severity**: MEDIUM **Probability**: MEDIUM **Impact**: Many unverified users, potential data quality issues **Causes**: - Users forget to verify - Verification email goes to spam - Poor UX around verification flow **Mitigation**: 1. Send verification email immediately on registration (no delay) 2. Show prominent banner in app: "Please verify your email" 3. Resend verification option easily accessible 4. Clear email subject line: "Verify your email - ColaFlow" 5. Reminder email after 24 hours (future enhancement) 6. Block critical features until verified (future: user invitation requires verification) **Metrics to Monitor**: - Email verification rate (target: >85% within 48h) - Time to verification (target: <1 hour median) - Resend requests per user (target: <0.5 average) **Contingency**: - If verification rate <70%, investigate email deliverability - A/B test different email templates - Consider SMS verification as alternative (future) **Owner**: Product Manager / UX Team --- #### RISK-007: Invitation Spam and Abuse **Severity**: MEDIUM **Probability**: LOW **Impact**: Brand reputation damage, email blacklisting **Scenarios**: - Malicious user invites random emails to spam them - Competitor invites our customers to confuse them - Automated bot creates accounts and sends mass invitations **Mitigation**: 1. Rate limiting: Max 20 invitations per tenant per hour 2. Require email verification before user can send invitations (future) 3. Monitor invitation acceptance rate per tenant (low rate = potential spam) 4. "Report spam" link in invitation emails 5. CAPTCHA on invitation endpoint (if abuse detected) 6. Tenant suspension for repeated abuse **Detection**: - Invitation acceptance rate <10% (flag for review) - High volume of invitations from new tenant - Spam reports from recipients **Response**: - Suspend tenant pending investigation - Invalidate all pending invitations from tenant - Notify sender that abuse was detected - Require additional verification to reactivate **Owner**: Product Manager / Security Team --- ### 13.3 Operational Risks #### RISK-008: Email Service Outage (SendGrid Down) **Severity**: MEDIUM **Probability**: LOW **Impact**: No emails sent, users cannot verify or reset passwords **Mitigation**: 1. Use SendGrid (99.9% uptime SLA) 2. Implement fallback to SMTP (future enhancement) 3. Queue emails for retry if service unavailable 4. Non-blocking email sends (user actions still succeed) 5. Status page to inform users of email service issues **Detection**: - Monitor SendGrid API health endpoint - Alert on consecutive email send failures - Track email send success rate metric **Response Plan**: 1. Confirm SendGrid status page for outage 2. Enable SMTP fallback (if implemented) 3. Update status page with incident details 4. Communicate to users: "Email service temporarily delayed" 5. Queue failed emails for retry when service recovers **Recovery**: - Retry all queued emails when service recovers - Verify email delivery success rate returns to normal - Post-incident review to prevent recurrence **Owner**: DevOps Team --- #### RISK-009: High Email Costs **Severity**: LOW **Probability**: MEDIUM **Impact**: Unexpected infrastructure costs **Scenario**: High user growth leads to email volume exceeding free tier (100 emails/day) **Mitigation**: 1. Start with SendGrid free tier (100 emails/day) 2. Monitor daily email volume 3. Set billing alerts at 80% of free tier 4. Plan upgrade to paid tier when approaching limit 5. Optimize email frequency (avoid unnecessary emails) **Cost Projections**: - Free tier: 100 emails/day = 3000/month (sufficient for 500 active users) - Essentials plan: $19.95/month for 50,000 emails (sufficient for 8,000 users) - Pro plan: $89.95/month for 100,000 emails **Contingency**: - If costs exceed budget, reduce email frequency (e.g., no reminder emails) - Negotiate volume pricing with SendGrid - Switch to self-hosted SMTP (trade-off: lower deliverability) **Owner**: Product Manager / Finance Team --- ### 13.4 Risk Summary Matrix | Risk ID | Risk | Severity | Probability | Mitigation Status | Owner | |---------|------|----------|-------------|-------------------|-------| | RISK-001 | Email Delivery Failures | HIGH | MEDIUM | ✅ Mitigated | Backend Team | | RISK-002 | Token Security Vulnerabilities | CRITICAL | LOW | ✅ Mitigated | Security Team | | RISK-003 | Database Migration Failures | MEDIUM | LOW | ✅ Mitigated | DevOps Team | | RISK-004 | Rate Limiting Bypass | MEDIUM | MEDIUM | ✅ Mitigated | Backend Team | | RISK-005 | Email Deliverability Issues | HIGH | MEDIUM | ⚠️ Monitor | Product Manager | | RISK-006 | Low Email Verification Rate | MEDIUM | MEDIUM | ⚠️ Monitor | Product Manager | | RISK-007 | Invitation Spam/Abuse | MEDIUM | LOW | ✅ Mitigated | Security Team | | RISK-008 | Email Service Outage | MEDIUM | LOW | ⚠️ Partial | DevOps Team | | RISK-009 | High Email Costs | LOW | MEDIUM | ✅ Mitigated | Product Manager | **Legend**: - ✅ Mitigated: Controls in place, low residual risk - ⚠️ Monitor: Requires ongoing monitoring and metrics - ❌ Open: Mitigation needed (none for Day 7) --- ## 14. Success Criteria ### 14.1 Functional Success Criteria #### ✅ Email Service Integration - [ ] SendGrid email service sends emails successfully in production - [ ] SMTP email service sends emails successfully in development - [ ] Mock email service captures emails in integration tests - [ ] Email templates render correctly with dynamic data - [ ] Configuration supports multiple providers without code changes #### ✅ Email Verification Flow - [ ] New users receive verification email within 5 seconds of registration - [ ] Verification link successfully verifies email and updates database - [ ] Expired tokens return appropriate error message - [ ] Resend verification email generates new token and invalidates old one - [ ] Rate limiting prevents more than 3 resend requests per hour - [ ] Verified users show `isEmailVerified: true` in API responses #### ✅ Password Reset Flow - [ ] Forgot password request sends reset email (without revealing email existence) - [ ] Reset password link successfully updates password - [ ] All refresh tokens revoked after password reset - [ ] Used reset tokens cannot be reused - [ ] Rate limiting prevents more than 3 reset requests per email per hour - [ ] Password complexity requirements enforced (8+ chars, uppercase, lowercase, number, special char) #### ✅ User Invitation System - [ ] Tenant owners can invite users with valid email and role - [ ] Invitations cannot be created for TenantOwner or AIAgent roles - [ ] Invitation emails sent immediately with correct tenant and role information - [ ] Invited users can accept invitations and create accounts - [ ] Accepted invitations automatically assign specified role - [ ] Invitation acceptance marks email as verified - [ ] Tenant owners can list all invitations with status filtering - [ ] Tenant owners can cancel pending invitations - [ ] Cross-tenant invitation access returns 403 Forbidden ### 14.2 Technical Success Criteria #### ✅ Testing Coverage - [ ] 51 new tests written and passing (24 unit + 27 integration) - [ ] 3 previously skipped tests from Day 6 now passing - [ ] Total test suite: 97 tests with 0 failures - [ ] Test coverage ≥90% for new business logic - [ ] All tests run in <30 seconds #### ✅ Security Requirements - [ ] All tokens stored as SHA-256 hashes (not plaintext) - [ ] Tokens are cryptographically secure (256-bit random) - [ ] HTTPS enforcement verified in production configuration - [ ] Rate limiting active on all public endpoints - [ ] Email enumeration prevention verified (forgot password, resend verification) - [ ] Cross-tenant security validated on all invitation endpoints - [ ] Audit logs capture all security events with IP and timestamp #### ✅ Database Integrity - [ ] 3 new tables created with correct schema: `email_verification_tokens`, `password_reset_tokens`, `invitations` - [ ] All indexes and constraints created successfully - [ ] Database migration runs successfully (up and down) - [ ] No data loss in rollback scenario - [ ] Foreign key relationships enforce referential integrity #### ✅ Performance Requirements - [ ] Email send latency <2 seconds (p95) - [ ] Email template rendering <100ms (p95) - [ ] API response time <200ms for all endpoints (p95) - [ ] Database queries optimized with indexes (no full table scans) - [ ] Test suite execution time <30 seconds ### 14.3 User Experience Success Criteria #### ✅ Email Quality - [ ] All emails render correctly in Gmail, Outlook, Apple Mail - [ ] Email subject lines are clear and actionable - [ ] Primary CTA (button) is prominently displayed - [ ] Emails are mobile-responsive (tested on iPhone and Android) - [ ] Plain text fallback provided for all HTML emails - [ ] Email branding consistent with ColaFlow design guidelines #### ✅ Error Handling - [ ] All error messages are user-friendly and actionable - [ ] Invalid tokens show clear expiration and resend instructions - [ ] Rate limit errors include retry-after information - [ ] Cross-tenant access errors don't leak sensitive information - [ ] 500 errors are logged but show generic message to users #### ✅ API Documentation - [ ] All new endpoints documented in Swagger/OpenAPI - [ ] Request/response examples provided - [ ] Error codes and messages documented - [ ] Authentication requirements clearly specified - [ ] Rate limiting policies documented ### 14.4 Business Success Criteria #### ✅ User Adoption Metrics (Track post-deployment) - **Email Verification Rate**: ≥85% of users verify email within 48 hours - **Invitation Acceptance Rate**: ≥70% of invitations accepted within 7 days - **Password Reset Success Rate**: ≥90% of reset attempts succeed - **Email Delivery Rate**: ≥99% of emails delivered successfully #### ✅ Support Impact Metrics (Track post-deployment) - **Password Reset Support Tickets**: Reduced by ≥50% compared to manual process - **Email Verification Support Tickets**: <5% of new users require support - **Invitation Issues**: <2% of invitations result in support tickets #### ✅ System Health Metrics (Monitor continuously) - **Email Service Uptime**: ≥99.5% (SendGrid SLA: 99.9%) - **API Uptime**: ≥99.9% for all email/auth endpoints - **Email Send Failure Rate**: <1% of total emails - **Rate Limit Hit Rate**: <5% of requests (indicates abuse or legitimate high usage) ### 14.5 Documentation Success Criteria #### ✅ Technical Documentation - [ ] Day 7 implementation summary document published - [ ] API endpoint documentation complete (Swagger) - [ ] Database schema changes documented - [ ] Security audit report completed - [ ] Email template customization guide created #### ✅ User Documentation - [ ] Email verification troubleshooting guide - [ ] Password reset user guide - [ ] User invitation admin guide - [ ] FAQ section updated with common issues ### 14.6 Deployment Readiness Checklist #### ✅ Pre-Deployment - [ ] All tests passing in CI/CD pipeline - [ ] Code review completed and approved - [ ] Security review completed - [ ] Database migration tested in staging - [ ] Email deliverability tested with real accounts (Gmail, Outlook) - [ ] SendGrid API key configured in production Key Vault - [ ] SPF/DKIM/DMARC DNS records configured - [ ] Rollback plan documented and tested #### ✅ Deployment - [ ] Database migration executed successfully - [ ] Application deployed without errors - [ ] Health check endpoints return 200 OK - [ ] Smoke tests pass (register, verify, reset, invite) - [ ] Monitoring dashboards show normal metrics #### ✅ Post-Deployment - [ ] Verify email delivery in production (send test emails) - [ ] Monitor error logs for first 24 hours - [ ] Check email delivery rates in SendGrid dashboard - [ ] Verify rate limiting is working (test with high volume) - [ ] User acceptance testing with internal team - [ ] Stakeholder demo completed --- ## 15. Appendix ### 15.1 Glossary | Term | Definition | |------|------------| | **Email Verification** | Process of confirming a user owns the email address they registered with | | **Password Reset Token** | Time-limited, single-use token sent via email to reset forgotten password | | **Invitation Token** | Token embedded in invitation email allowing user to accept and join tenant | | **Token Hash** | SHA-256 hash of token stored in database (not plaintext token) | | **Rate Limiting** | Restricting number of requests per time period to prevent abuse | | **Email Enumeration** | Security vulnerability where attacker can determine if email exists in system | | **Transactional Email** | Automated emails triggered by user actions (not marketing emails) | | **SendGrid** | Cloud-based email delivery service (SMTP alternative) | | **Base64URL** | URL-safe encoding of binary data (no +, /, = characters) | | **Idempotent** | Operation that produces same result if executed multiple times | ### 15.2 Related Documents - [Day 6 Test Report](DAY6-TEST-REPORT.md) - Details on skipped tests - [Product Plan](../product.md) - Overall ColaFlow project vision - [M1 Sprint 2 Roadmap](M1-SPRINT-2-ROADMAP.md) - Days 4-10 plan - [Security Architecture](SECURITY-ARCHITECTURE.md) - Overall security design - [API Documentation](../docs/API.md) - Complete API reference ### 15.3 References **Email Best Practices**: - [SendGrid Email Best Practices](https://docs.sendgrid.com/ui/sending-email/email-best-practices) - [OWASP Email Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Email_Security_Cheat_Sheet.html) - [Gmail Sender Guidelines](https://support.google.com/mail/answer/81126) **Security Standards**: - [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html) - [OWASP Forgot Password Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Forgot_Password_Cheat_Sheet.html) - [OWASP Authentication Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html) **Technology Documentation**: - [SendGrid .NET SDK](https://github.com/sendgrid/sendgrid-csharp) - [ASP.NET Core Rate Limiting](https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit) - [Entity Framework Core Migrations](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/) ### 15.4 Change Log | Version | Date | Author | Changes | |---------|------|--------|---------| | 1.0 | 2025-11-03 | Product Manager Agent | Initial PRD for Day 7 | --- **End of Document** **Total Pages**: 45+ **Word Count**: ~15,000 words **Estimated Reading Time**: 60 minutes **Next Steps**: 1. Review and approve this PRD with stakeholders 2. Assign implementation to backend team 3. Schedule kickoff meeting for Day 7 sprint 4. Begin Phase 1: Email Service Foundation **Questions or Feedback?** Contact: Product Manager Agent via ColaFlow coordination channel