Files
ColaFlow/colaflow-api/DAY7-PRD.md
Yaojia Wang a220e5d5d7
Some checks failed
Code Coverage / Generate Coverage Report (push) Has been cancelled
Tests / Run Tests (9.0.x) (push) Has been cancelled
Tests / Docker Build Test (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Refactor
2025-11-03 21:02:14 +01:00

98 KiB

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
  2. Feature 1: Email Service Integration
  3. Feature 2: Email Verification Flow
  4. Feature 3: Password Reset Flow
  5. Feature 4: User Invitation System
  6. API Specifications
  7. Database Schema Changes
  8. Security Requirements
  9. Email Templates
  10. Integration Points
  11. Testing Strategy
  12. Implementation Plan
  13. Risk Assessment
  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:

{
  "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:

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):

{
  "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:

{
  "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):

{
  "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:

{
  "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):

{
  "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:

{
  "token": "base64url-encoded-token"
}

Responses:

200 OK - Email verified successfully:

{
  "message": "Email verified successfully. You can now log in.",
  "redirectUrl": "/login"
}

400 Bad Request - Invalid or expired token:

{
  "error": "Verification token is invalid or expired.",
  "code": "INVALID_TOKEN"
}

200 OK - Email already verified (idempotent):

{
  "message": "Email already verified.",
  "redirectUrl": "/dashboard"
}

POST /api/auth/resend-verification

Description: Resend email verification email.

Authorization: None (public)

Request Body:

{
  "tenantSlug": "acme-corp",
  "email": "user@example.com"
}

Responses:

200 OK - Always returns success (prevent email enumeration):

{
  "message": "If an account exists, a verification email has been sent."
}

429 Too Many Requests - Rate limit exceeded:

{
  "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:

{
  "tenantSlug": "acme-corp",
  "email": "user@example.com"
}

Responses:

200 OK - Always returns success (prevent email enumeration):

{
  "message": "If an account exists, a password reset email has been sent."
}

429 Too Many Requests - Rate limit exceeded:

{
  "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:

{
  "token": "base64url-encoded-token",
  "newPassword": "SecureP@ssw0rd"
}

Responses:

200 OK - Password reset successfully:

{
  "message": "Password reset successfully. You can now log in with your new password.",
  "redirectUrl": "/login"
}

400 Bad Request - Invalid or expired token:

{
  "error": "Password reset token is invalid or expired.",
  "code": "INVALID_TOKEN"
}

400 Bad Request - Password complexity requirements not met:

{
  "errors": {
    "newPassword": [
      "Password must be at least 8 characters long",
      "Password must contain at least one uppercase letter"
    ]
  }
}

400 Bad Request - Token already used:

{
  "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:

{
  "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:

{
  "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:

{
  "errors": {
    "role": ["Role must be one of: TenantAdmin, Developer, Guest"]
  }
}

400 Bad Request - User already invited:

{
  "error": "An active invitation for this email already exists.",
  "code": "DUPLICATE_INVITATION"
}

400 Bad Request - User already member:

{
  "error": "A user with this email is already a member of this tenant.",
  "code": "USER_ALREADY_EXISTS"
}

403 Forbidden - Cross-tenant access:

{
  "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:

{
  "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:

{
  "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:

{
  "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:

{
  "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:

{
  "error": "Invalid or expired invitation token.",
  "code": "INVALID_INVITATION"
}

400 Bad Request - Invitation expired:

{
  "error": "This invitation has expired. Please request a new one from your team admin.",
  "code": "INVITATION_EXPIRED"
}

400 Bad Request - Invitation already accepted:

{
  "error": "This invitation has already been used.",
  "code": "INVITATION_ALREADY_USED"
}

400 Bad Request - Password validation failed:

{
  "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:

{
  "error": "Only pending invitations can be canceled.",
  "code": "INVITATION_NOT_PENDING"
}

403 Forbidden - Cross-tenant access:

{
  "error": "Access denied: You can only cancel invitations in your own tenant."
}

404 Not Found - Invitation not found:

{
  "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.

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.

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.

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:

-- 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:

-- 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:

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:

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:

// ✅ CORRECT: Prevent enumeration
[HttpPost("forgot-password")]
public async Task<IActionResult> 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<IActionResult> 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

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:

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:

[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:

// 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:

{
  "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):

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):

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Subject - ColaFlow</title>
    <style>
        body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
        .container { max-width: 600px; margin: 0 auto; padding: 20px; }
        .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                  color: white; padding: 30px; text-align: center; }
        .content { background: white; padding: 30px; }
        .button { display: inline-block; padding: 12px 30px; background: #667eea;
                  color: white; text-decoration: none; border-radius: 5px; }
        .footer { text-align: center; padding: 20px; color: #777; font-size: 12px; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>🧠 ColaFlow</h1>
        </div>
        <div class="content">
            @RenderBody()
        </div>
        <div class="footer">
            <p>© 2025 ColaFlow. All rights reserved.</p>
            <p>This is an automated email. Please do not reply.</p>
        </div>
    </div>
</body>
</html>

9.2 Email Verification Template

File: EmailTemplates/EmailVerification.cshtml

Subject: "Verify your email address - ColaFlow"

Template:

@{
    ViewBag.Subject = "Verify your email address";
}

<h2>Welcome to ColaFlow, @Model.FullName!</h2>

<p>Thank you for registering with <strong>@Model.TenantName</strong> on ColaFlow.</p>

<p>Please verify your email address by clicking the button below:</p>

<p style="text-align: center; margin: 30px 0;">
    <a href="@Model.VerificationUrl" class="button">Verify Email Address</a>
</p>

<p>Or copy and paste this link into your browser:</p>
<p style="word-break: break-all; color: #667eea;">@Model.VerificationUrl</p>

<p><strong>This link will expire in 24 hours.</strong></p>

<p>If you didn't create an account, you can safely ignore this email.</p>

<p>Best regards,<br>The ColaFlow Team</p>

Model:

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:

@{
    ViewBag.Subject = "Reset your password";
}

<h2>Password Reset Request</h2>

<p>Hi @Model.FullName,</p>

<p>We received a request to reset the password for your ColaFlow account
   (<strong>@Model.TenantName</strong>).</p>

<p>Click the button below to reset your password:</p>

<p style="text-align: center; margin: 30px 0;">
    <a href="@Model.ResetUrl" class="button">Reset Password</a>
</p>

<p>Or copy and paste this link into your browser:</p>
<p style="word-break: break-all; color: #667eea;">@Model.ResetUrl</p>

<p><strong>This link will expire in 1 hour.</strong></p>

<p style="background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin: 20px 0;">
    ⚠️ <strong>Security Notice:</strong> If you didn't request a password reset,
    please ignore this email and ensure your account is secure.
</p>

<p>Best regards,<br>The ColaFlow Team</p>

Model:

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:

@{
    ViewBag.Subject = $"You're invited to join {Model.TenantName} on ColaFlow";
}

<h2>You've been invited! 🎉</h2>

<p>Hi there,</p>

<p><strong>@Model.InviterName</strong> has invited you to join
   <strong>@Model.TenantName</strong> on ColaFlow as a <strong>@Model.Role</strong>.</p>

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

<p>Click the button below to accept the invitation and create your account:</p>

<p style="text-align: center; margin: 30px 0;">
    <a href="@Model.AcceptUrl" class="button">Accept Invitation</a>
</p>

<p>Or copy and paste this link into your browser:</p>
<p style="word-break: break-all; color: #667eea;">@Model.AcceptUrl</p>

<p><strong>This invitation will expire on @Model.ExpiresAt.ToString("MMMM dd, yyyy").</strong></p>

<div style="background: #e3f2fd; border-left: 4px solid #2196f3; padding: 15px; margin: 20px 0;">
    <strong>Your Role:</strong> @Model.RoleDescription
</div>

<p>If you didn't expect this invitation, you can safely ignore this email.</p>

<p>Best regards,<br>The ColaFlow Team</p>

Model:

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:

public interface IEmailTemplateRenderer
{
    Task<string> RenderHtmlAsync<TModel>(string templateName, TModel model);
    Task<string> RenderPlainTextAsync<TModel>(string templateName, TModel model);
}

Implementation (Razor Pages or simple string interpolation for Day 7):

public class EmailTemplateRenderer : IEmailTemplateRenderer
{
    private readonly string _templateBasePath;

    public EmailTemplateRenderer(IConfiguration configuration)
    {
        _templateBasePath = configuration["EmailSettings:TemplateBasePath"] ?? "EmailTemplates";
    }

    public async Task<string> RenderHtmlAsync<TModel>(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<TModel>(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:

// 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:

{
  "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):

// 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:

{
  "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

// 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<TenantRole>(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

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

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

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

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

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

// 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

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

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

// Tests/Modules/Identity/ColaFlow.Modules.Identity.IntegrationTests/EmailVerificationTests.cs

public class EmailVerificationFlowTests : IClassFixture<DatabaseFixture>
{
    [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

public class PasswordResetFlowTests : IClassFixture<DatabaseFixture>
{
    [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

public class UserInvitationFlowTests : IClassFixture<DatabaseFixture>
{
    [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

    // 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

    // 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

    // 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):

// Mock email service that captures emails instead of sending
public class MockEmailService : IEmailService
{
    public List<EmailMessage> SentEmails { get; } = new();

    public Task SendEmailAsync(EmailMessage message)
    {
        SentEmails.Add(message);
        return Task.CompletedTask;
    }
}

// Usage in tests
var emailService = _testServer.Services.GetRequiredService<IEmailService>() as MockEmailService;
var lastEmail = emailService.SentEmails.Last();
var verificationToken = ExtractTokenFromEmailBody(lastEmail.Body);

Test Token Extraction:

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:

# If migration fails, rollback:
dotnet ef database update <PreviousMigrationName>

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.3 References

Email Best Practices:

Security Standards:

Technology Documentation:

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