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:
- Email Service Integration - Reliable transactional email infrastructure
- Email Verification Flow - Ensure valid user email addresses
- Password Reset Flow - Self-service password recovery
- 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
- Background & Context
- Feature 1: Email Service Integration
- Feature 2: Email Verification Flow
- Feature 3: Password Reset Flow
- Feature 4: User Invitation System
- API Specifications
- Database Schema Changes
- Security Requirements
- Email Templates
- Integration Points
- Testing Strategy
- Implementation Plan
- Risk Assessment
- 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:
IEmailServiceinterface defined withSendEmailAsync(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.jsonandappsettings.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):
EmailVerification.html- Verification linkPasswordReset.html- Password reset linkUserInvitation.html- Tenant invitationWelcomeEmail.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.PasswordHashwith 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
UsedAttimestamp - 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:
RequireTenantOwnerorRequireTenantAdminpolicy - 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:
RequireTenantOwnerorRequireTenantAdminpolicy - 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:
RequireTenantOwnerorRequireTenantAdminpolicy - 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
UserTenantRoletable 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 beTenantOwnerorAIAgent)
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 numberpageSize(int, optional, default: 20, max: 100) - Items per pagestatus(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 IDinvitationId(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 NULLto determine if email is verified
7.3 Entity Framework Core Migrations
Migration Name: Add_EmailVerification_PasswordReset_Invitations
Migration Steps:
- Create
email_verification_tokenstable - Create
password_reset_tokenstable - Create
invitationstable - Add indexes and constraints
- 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()(notRandom()) - 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
SEC-007: HTTPS-Only Links
Priority: P0 (Critical)
Requirements:
- All email links must use
https://(neverhttp://) - Redirect HTTP to HTTPS at infrastructure level
- Set
Strict-Transport-Securityheader (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
EmailAddressAttributeand 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):
- Email verification sent
- Email verification succeeded/failed
- Password reset requested
- Password reset succeeded/failed
- Invitation created
- Invitation accepted
- Invitation canceled
- Rate limit exceeded
- 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
tenantIdfrom route matches JWTtenant_idclaim - 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:
- Mobile-First: 600px max width, responsive design
- Accessibility: High contrast, readable fonts (16px+ body text)
- Brand Consistency: ColaFlow purple gradient (#667eea to #764ba2)
- Clear CTAs: Primary action button prominently displayed
- Plain Text Fallback: Always provide plain text version
- 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):
-
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 -
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 -
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:
- Create
IEmailServiceinterface - Implement
SendGridEmailService - Implement
SmtpEmailService - Implement
MockEmailService(for tests) - Add email configuration to
appsettings.json - Register services in DI container
- Create
IEmailTemplateRendererinterface - Implement simple template renderer (string interpolation)
- Unit tests for email service
Deliverables:
IEmailServiceinterface- 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:
- Create shared email layout (
_Layout.cshtml) - Create
EmailVerification.cshtmltemplate - Create
PasswordReset.cshtmltemplate - Create
UserInvitation.cshtmltemplate - Create plain text fallbacks for all templates
- 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:
- Create
EmailVerificationTokenentity - Create
IEmailVerificationTokenRepositoryinterface + implementation - Create EF Core migration for
email_verification_tokenstable - Implement token generation and hashing logic
- Update
RegisterTenantCommandHandlerto send verification email - Create
VerifyEmailCommandand handler - Create
ResendVerificationEmailCommandand handler - Add rate limiting for resend endpoint
- Create API endpoints in
AuthController - Integration tests (6 tests)
Deliverables:
EmailVerificationTokenentity 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:
- Create
PasswordResetTokenentity - Create
IPasswordResetTokenRepositoryinterface + implementation - Create EF Core migration for
password_reset_tokenstable - Implement token generation and hashing logic
- Create
ForgotPasswordCommandand handler - Create
ResetPasswordCommandand handler - Implement password complexity validation
- Add refresh token revocation on password reset
- Add rate limiting for forgot password endpoint
- Create API endpoints in
AuthController - Integration tests (8 tests)
Deliverables:
PasswordResetTokenentity 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:
- Create
Invitationentity with status enum - Create
IInvitationRepositoryinterface + implementation - Create EF Core migration for
invitationstable - Create
InviteUserCommandand handler - Create
ListInvitationsQueryand handler - Create
AcceptInvitationCommandand handler (creates user + assigns role) - Create
CancelInvitationCommandand handler - Implement role validation (cannot invite as TenantOwner/AIAgent)
- Add duplicate invitation prevention
- Create API endpoints in
TenantUsersController(invite, list, cancel) - Create public endpoint in
AuthController(accept) - Integration tests (10 tests)
Deliverables:
Invitationentity 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:
- Unskip
RemoveUser_AsOwner_ShouldSucceed - Unskip
RemoveUser_RevokesTokens_ShouldWork - Unskip
RemoveUser_RequiresOwnerPolicy_ShouldBeEnforced - Update tests to use invitation flow for creating second user
- 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:
- Add comprehensive audit logging for all security events
- Verify HTTPS enforcement in configuration
- Add rate limiting middleware configuration
- Security review of all endpoints
- Update API documentation (Swagger/OpenAPI)
- Write Day 7 implementation summary document
- 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:
- Use reliable email provider (SendGrid with 99.9% SLA)
- Implement retry logic with exponential backoff
- Circuit breaker pattern to prevent cascading failures
- Fallback to SMTP if SendGrid fails (future enhancement)
- Email delivery monitoring and alerting (future: Sentry integration)
- 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:
- Use cryptographically secure random number generator
- Store token hashes (SHA-256), never plaintext
- Short token expiration (1h for password reset, 24h for verification)
- Token reuse prevention (mark as used)
- Rate limiting on token-based endpoints
- HTTPS enforcement (no tokens over HTTP)
- 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:
- Test migrations in staging environment first
- Create rollback migrations for all schema changes
- Use EF Core migration idempotency (can run multiple times safely)
- Backup database before migration in production
- 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:
- Implement rate limiting at multiple layers:
- Application level (in-memory cache)
- API Gateway level (future: Azure API Management)
- Email provider level (SendGrid rate limits)
- Use IP-based and email-based rate limiting
- CAPTCHA for public endpoints (future enhancement)
- 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:
- Configure SPF, DKIM, DMARC records for domain
- Use reputable email provider (SendGrid with good reputation)
- Test emails with major providers (Gmail, Outlook, Yahoo)
- Clear, non-promotional email content
- "Add to contacts" instructions in emails
- 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:
- Send verification email immediately on registration (no delay)
- Show prominent banner in app: "Please verify your email"
- Resend verification option easily accessible
- Clear email subject line: "Verify your email - ColaFlow"
- Reminder email after 24 hours (future enhancement)
- 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:
- Rate limiting: Max 20 invitations per tenant per hour
- Require email verification before user can send invitations (future)
- Monitor invitation acceptance rate per tenant (low rate = potential spam)
- "Report spam" link in invitation emails
- CAPTCHA on invitation endpoint (if abuse detected)
- 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:
- Use SendGrid (99.9% uptime SLA)
- Implement fallback to SMTP (future enhancement)
- Queue emails for retry if service unavailable
- Non-blocking email sends (user actions still succeed)
- 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:
- Confirm SendGrid status page for outage
- Enable SMTP fallback (if implemented)
- Update status page with incident details
- Communicate to users: "Email service temporarily delayed"
- 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:
- Start with SendGrid free tier (100 emails/day)
- Monitor daily email volume
- Set billing alerts at 80% of free tier
- Plan upgrade to paid tier when approaching limit
- 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: truein API responses
✅ Password Reset Flow
- Forgot password request sends reset email (without revealing email existence)
- Reset password link successfully updates password
- All refresh tokens revoked after password reset
- Used reset tokens cannot be reused
- Rate limiting prevents more than 3 reset requests per email per hour
- Password complexity requirements enforced (8+ chars, uppercase, lowercase, number, special char)
✅ User Invitation System
- Tenant owners can invite users with valid email and role
- Invitations cannot be created for TenantOwner or AIAgent roles
- Invitation emails sent immediately with correct tenant and role information
- Invited users can accept invitations and create accounts
- Accepted invitations automatically assign specified role
- Invitation acceptance marks email as verified
- Tenant owners can list all invitations with status filtering
- Tenant owners can cancel pending invitations
- Cross-tenant invitation access returns 403 Forbidden
14.2 Technical Success Criteria
✅ Testing Coverage
- 51 new tests written and passing (24 unit + 27 integration)
- 3 previously skipped tests from Day 6 now passing
- Total test suite: 97 tests with 0 failures
- Test coverage ≥90% for new business logic
- All tests run in <30 seconds
✅ Security Requirements
- All tokens stored as SHA-256 hashes (not plaintext)
- Tokens are cryptographically secure (256-bit random)
- HTTPS enforcement verified in production configuration
- Rate limiting active on all public endpoints
- Email enumeration prevention verified (forgot password, resend verification)
- Cross-tenant security validated on all invitation endpoints
- Audit logs capture all security events with IP and timestamp
✅ Database Integrity
- 3 new tables created with correct schema:
email_verification_tokens,password_reset_tokens,invitations - All indexes and constraints created successfully
- Database migration runs successfully (up and down)
- No data loss in rollback scenario
- Foreign key relationships enforce referential integrity
✅ Performance Requirements
- Email send latency <2 seconds (p95)
- Email template rendering <100ms (p95)
- API response time <200ms for all endpoints (p95)
- Database queries optimized with indexes (no full table scans)
- Test suite execution time <30 seconds
14.3 User Experience Success Criteria
✅ Email Quality
- All emails render correctly in Gmail, Outlook, Apple Mail
- Email subject lines are clear and actionable
- Primary CTA (button) is prominently displayed
- Emails are mobile-responsive (tested on iPhone and Android)
- Plain text fallback provided for all HTML emails
- Email branding consistent with ColaFlow design guidelines
✅ Error Handling
- All error messages are user-friendly and actionable
- Invalid tokens show clear expiration and resend instructions
- Rate limit errors include retry-after information
- Cross-tenant access errors don't leak sensitive information
- 500 errors are logged but show generic message to users
✅ API Documentation
- All new endpoints documented in Swagger/OpenAPI
- Request/response examples provided
- Error codes and messages documented
- Authentication requirements clearly specified
- Rate limiting policies documented
14.4 Business Success Criteria
✅ User Adoption Metrics (Track post-deployment)
- Email Verification Rate: ≥85% of users verify email within 48 hours
- Invitation Acceptance Rate: ≥70% of invitations accepted within 7 days
- Password Reset Success Rate: ≥90% of reset attempts succeed
- Email Delivery Rate: ≥99% of emails delivered successfully
✅ Support Impact Metrics (Track post-deployment)
- Password Reset Support Tickets: Reduced by ≥50% compared to manual process
- Email Verification Support Tickets: <5% of new users require support
- Invitation Issues: <2% of invitations result in support tickets
✅ System Health Metrics (Monitor continuously)
- Email Service Uptime: ≥99.5% (SendGrid SLA: 99.9%)
- API Uptime: ≥99.9% for all email/auth endpoints
- Email Send Failure Rate: <1% of total emails
- Rate Limit Hit Rate: <5% of requests (indicates abuse or legitimate high usage)
14.5 Documentation Success Criteria
✅ Technical Documentation
- Day 7 implementation summary document published
- API endpoint documentation complete (Swagger)
- Database schema changes documented
- Security audit report completed
- Email template customization guide created
✅ User Documentation
- Email verification troubleshooting guide
- Password reset user guide
- User invitation admin guide
- FAQ section updated with common issues
14.6 Deployment Readiness Checklist
✅ Pre-Deployment
- All tests passing in CI/CD pipeline
- Code review completed and approved
- Security review completed
- Database migration tested in staging
- Email deliverability tested with real accounts (Gmail, Outlook)
- SendGrid API key configured in production Key Vault
- SPF/DKIM/DMARC DNS records configured
- Rollback plan documented and tested
✅ Deployment
- Database migration executed successfully
- Application deployed without errors
- Health check endpoints return 200 OK
- Smoke tests pass (register, verify, reset, invite)
- Monitoring dashboards show normal metrics
✅ Post-Deployment
- Verify email delivery in production (send test emails)
- Monitor error logs for first 24 hours
- Check email delivery rates in SendGrid dashboard
- Verify rate limiting is working (test with high volume)
- User acceptance testing with internal team
- Stakeholder demo completed
15. Appendix
15.1 Glossary
| Term | Definition |
|---|---|
| Email Verification | Process of confirming a user owns the email address they registered with |
| Password Reset Token | Time-limited, single-use token sent via email to reset forgotten password |
| Invitation Token | Token embedded in invitation email allowing user to accept and join tenant |
| Token Hash | SHA-256 hash of token stored in database (not plaintext token) |
| Rate Limiting | Restricting number of requests per time period to prevent abuse |
| Email Enumeration | Security vulnerability where attacker can determine if email exists in system |
| Transactional Email | Automated emails triggered by user actions (not marketing emails) |
| SendGrid | Cloud-based email delivery service (SMTP alternative) |
| Base64URL | URL-safe encoding of binary data (no +, /, = characters) |
| Idempotent | Operation that produces same result if executed multiple times |
15.2 Related Documents
- Day 6 Test Report - Details on skipped tests
- Product Plan - Overall ColaFlow project vision
- M1 Sprint 2 Roadmap - Days 4-10 plan
- Security Architecture - Overall security design
- API Documentation - Complete API reference
15.3 References
Email Best Practices:
Security Standards:
- OWASP Password Storage Cheat Sheet
- OWASP Forgot Password Cheat Sheet
- OWASP Authentication Cheat Sheet
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:
- Review and approve this PRD with stakeholders
- Assign implementation to backend team
- Schedule kickoff meeting for Day 7 sprint
- Begin Phase 1: Email Service Foundation
Questions or Feedback? Contact: Product Manager Agent via ColaFlow coordination channel