# Authentication Flow

**Document Version:** 1.0
**Last Updated:** 2026-01-10
**Status:** Production LIVE
**Flow Type:** User Authentication & Session Management

---

## Overview

This document describes the **complete authentication flow** for the Norda Biznes Partner application, covering:

- **User Registration** with email verification
- **Login** with session management
- **Email Verification** process
- **Password Reset** flow
- **Session Management** and cookies
- **Authorization Levels** and access control

**Key Technology:**
- **Authentication Framework:** Flask-Login
- **Password Hashing:** PBKDF2:SHA256 (werkzeug.security)
- **Session Storage:** Server-side session cookies
- **Email Delivery:** Microsoft Graph API (OAuth 2.0)
- **CSRF Protection:** Flask-WTF

**Security Features:**
- Email verification required before login
- Secure session cookies (HttpOnly, Secure, SameSite=Lax)
- CSRF protection on all forms
- Password strength requirements
- Input sanitization against XSS
- Rate limiting on authentication endpoints

---

## 1. User Registration Flow

### 1.1 Registration Sequence Diagram

```mermaid
sequenceDiagram
    actor User
    participant Browser
    participant Flask as Flask App<br/>(app.py)
    participant DB as PostgreSQL<br/>(users table)
    participant EmailSvc as Email Service<br/>(email_service.py)
    participant MSGraph as Microsoft Graph API

    User->>Browser: Navigate to /register
    Browser->>Flask: GET /register
    Flask->>Browser: Render register.html

    User->>Browser: Fill form (email, password, name, company_nip)
    Browser->>Flask: POST /register

    Note over Flask: CSRF token validated (automatic)
    Note over Flask: Input sanitization

    Flask->>Flask: validate_email(email)
    Flask->>Flask: validate_password(password)<br/>(8+ chars, uppercase, lowercase, digit)
    Flask->>Flask: validate NIP format (10 digits)

    Flask->>DB: SELECT * FROM users WHERE email = ?
    DB->>Flask: Check if exists

    alt Email already exists
        Flask->>Browser: Flash "Email już jest zarejestrowany"
        Browser->>User: Show error message
    else Email available
        Flask->>DB: SELECT * FROM companies WHERE nip = ? AND status = 'active'
        DB->>Flask: Company data (if NORDA member)

        Flask->>Flask: generate_password_hash(password)<br/>(PBKDF2:SHA256)
        Flask->>Flask: secrets.token_urlsafe(32)<br/>(verification token)
        Flask->>Flask: Calculate token expiry<br/>(now + 24 hours)

        Flask->>DB: INSERT INTO users<br/>(email, password_hash, name,<br/>verification_token, is_verified=FALSE,<br/>company_id, is_norda_member)
        DB->>Flask: User created (id)

        Flask->>EmailSvc: send_verification_email(email, token)
        EmailSvc->>MSGraph: POST /users/noreply@nordabiznes.pl/sendMail<br/>(OAuth 2.0 + Bearer token)
        MSGraph->>EmailSvc: 202 Accepted

        Flask->>Browser: Flash "Sprawdź email"<br/>Redirect to /login
        Browser->>User: Show success message
    end
```

### 1.2 Registration Implementation Details

**Route:** `POST /register`
**File:** `app.py` (lines ~3077-3183)
**Rate Limit:** 5 requests per hour per IP

**Input Fields:**
- `email` (required, max 255 chars)
- `password` (required, 8+ chars with complexity requirements)
- `name` (required, max 255 chars)
- `company_nip` (required, 10 digits)

**Validation Steps:**
1. **Email validation:** Regex pattern check
2. **Password validation:**
   - Minimum 8 characters
   - At least 1 uppercase letter
   - At least 1 lowercase letter
   - At least 1 digit
3. **NIP validation:** Must be exactly 10 digits
4. **Company membership check:** NIP lookup in `companies` table

**Database Operations:**
```sql
-- Check email uniqueness
SELECT * FROM users WHERE email = ?;

-- Verify company membership
SELECT * FROM companies WHERE nip = ? AND status = 'active';

-- Create user account
INSERT INTO users (
    email, password_hash, name, company_nip,
    verification_token, verification_token_expires,
    is_verified, is_norda_member, company_id,
    created_at
) VALUES (?, ?, ?, ?, ?, ?, FALSE, ?, ?, NOW());
```

**Security Measures:**
- **CSRF Protection:** Automatic via Flask-WTF
- **Input Sanitization:** `sanitize_input()` strips HTML/malicious patterns
- **Password Hashing:** PBKDF2:SHA256 (werkzeug default)
- **Rate Limiting:** 5 attempts/hour via Flask-Limiter
- **XSS Prevention:** All user inputs sanitized

**Email Verification Token:**
- Generated via `secrets.token_urlsafe(32)` (256-bit entropy)
- Stored in `users.verification_token` column
- Expires after 24 hours
- Single-use (cleared after verification)

---

## 2. Email Verification Flow

### 2.1 Email Verification Sequence Diagram

```mermaid
sequenceDiagram
    actor User
    participant Email as Email Client
    participant Browser
    participant Flask as Flask App<br/>(app.py)
    participant DB as PostgreSQL

    Note over User,Email: User receives verification email

    User->>Email: Open verification email
    Email->>Browser: Click link:<br/>https://nordabiznes.pl/verify-email/<token>

    Browser->>Flask: GET /verify-email/<token>

    Flask->>Flask: Extract token from URL
    Flask->>DB: SELECT * FROM users<br/>WHERE verification_token = ?<br/>AND verification_token_expires > NOW()<br/>AND is_active = TRUE

    alt Token valid and not expired
        DB->>Flask: User found

        alt User already verified
            Flask->>Browser: Flash "Email został już zweryfikowany"<br/>Redirect to /login
        else User not verified yet
            Flask->>DB: UPDATE users SET<br/>is_verified = TRUE,<br/>verified_at = NOW(),<br/>verification_token = NULL,<br/>verification_token_expires = NULL<br/>WHERE id = ?

            DB->>Flask: Update successful

            Flask->>Browser: Flash "Email zweryfikowany!<br/>Możesz się teraz zalogować"<br/>Redirect to /login
            Browser->>User: Show success message
        end

    else Token invalid or expired
        Flask->>Browser: Flash "Link weryfikacyjny<br/>jest nieprawidłowy lub wygasł"<br/>Redirect to /login
        Browser->>User: Show error message
    end
```

### 2.2 Email Verification Implementation Details

**Route:** `GET /verify-email/<token>`
**File:** `app.py` (lines ~3369-3405)
**Rate Limit:** None (public endpoint)

**Verification Logic:**
```python
# Query user by token
user = db.query(User).filter(
    User.verification_token == token,
    User.verification_token_expires > datetime.now(),
    User.is_active == True
).first()

if user and not user.is_verified:
    user.is_verified = True
    user.verified_at = datetime.now()
    user.verification_token = None
    user.verification_token_expires = None
    db.commit()
```

**Token Expiry:**
- Verification tokens expire after **24 hours**
- Expired tokens cannot be used
- Users can request new verification email via `/resend-verification`

**Database Schema (users table):**
```sql
verification_token VARCHAR(255) NULL
verification_token_expires TIMESTAMP NULL
is_verified BOOLEAN DEFAULT FALSE
verified_at TIMESTAMP NULL
```

---

## 3. Login Flow

### 3.1 Login Sequence Diagram

```mermaid
sequenceDiagram
    actor User
    participant Browser
    participant Flask as Flask App<br/>(app.py)
    participant FlaskLogin as Flask-Login
    participant DB as PostgreSQL
    participant Session as Session Cookie

    User->>Browser: Navigate to /login
    Browser->>Flask: GET /login
    Flask->>Browser: Render login.html with CSRF token

    User->>Browser: Enter email & password<br/>(optional: remember me)
    Browser->>Flask: POST /login<br/>(email, password, remember, csrf_token)

    Note over Flask: CSRF token validated

    Flask->>DB: SELECT * FROM users WHERE email = ?

    alt User not found
        DB->>Flask: No user
        Flask->>Browser: Flash "Nieprawidłowy email lub hasło"
        Browser->>User: Show error
    else User found
        DB->>Flask: User data

        Flask->>Flask: check_password_hash(<br/>user.password_hash,<br/>password<br/>)

        alt Password invalid
            Flask->>Browser: Flash "Nieprawidłowy email lub hasło"
            Browser->>User: Show error
        else Password valid
            alt User not active
                Flask->>Browser: Flash "Konto zostało dezaktywowane"
                Browser->>User: Show error
            else User not verified
                Flask->>Browser: Flash "Musisz potwierdzić adres email"
                Browser->>User: Show error with resend link
            else User active and verified
                Flask->>FlaskLogin: login_user(user, remember=remember)
                FlaskLogin->>Session: Set session cookie<br/>(secure, httponly, samesite=Lax)
                Session->>Browser: Store session cookie

                Flask->>DB: UPDATE users SET last_login = NOW() WHERE id = ?
                DB->>Flask: Update successful

                Flask->>Browser: Redirect to /dashboard<br/>(or 'next' URL if specified)
                Browser->>User: Show dashboard
            end
        end
    end
```

### 3.2 Login Implementation Details

**Route:** `POST /login`
**File:** `app.py` (lines ~3184-3240)
**Rate Limit:**
- **Development:** 1000 requests per hour
- **Production:** 5 requests per hour per IP

**Login Validation Steps:**
1. Check email exists in database
2. Verify password hash matches
3. Check `is_active = TRUE`
4. Require `is_verified = TRUE`
5. Create session via Flask-Login
6. Update `last_login` timestamp

**Session Configuration:**
```python
# app.py session settings (lines ~161-168)
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY')
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)
app.config['SESSION_COOKIE_SECURE'] = True  # HTTPS only in production
app.config['SESSION_COOKIE_HTTPONLY'] = True  # No JS access
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'  # CSRF protection
```

**Flask-Login Configuration:**
```python
# app.py Flask-Login setup (lines ~192-196)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
login_manager.login_message = 'Zaloguj się, aby uzyskać dostęp do tej strony.'
```

**User Loader Function:**
```python
@login_manager.user_loader
def load_user(user_id):
    """Load user from database for Flask-Login"""
    db = SessionLocal()
    try:
        return db.query(User).get(int(user_id))
    except:
        return None
    finally:
        db.close()
```

**Remember Me Functionality:**
- When enabled: Session cookie persists for **7 days**
- When disabled: Session cookie expires when browser closes
- Implemented via Flask-Login's `login_user(user, remember=True/False)`

**Next URL Redirect:**
```python
# Prevent open redirect vulnerability
next_page = request.args.get('next')
if next_page and not next_page.startswith('/'):
    next_page = None
return redirect(next_page or url_for('dashboard'))
```

---

## 4. Session Management

### 4.1 Session Lifecycle

```mermaid
stateDiagram-v2
    [*] --> Anonymous: User visits site
    Anonymous --> Authenticated: Successful login
    Authenticated --> Authenticated: Normal activity
    Authenticated --> Anonymous: Logout
    Authenticated --> Anonymous: Session expires (7 days)
    Authenticated --> Anonymous: User deactivated
    Anonymous --> [*]
```

### 4.2 Session Cookie Details

**Cookie Name:** `session` (Flask default)
**Storage:** Server-side (encrypted session data)
**Attributes:**
- `Secure` = True (HTTPS only in production)
- `HttpOnly` = True (prevents XSS cookie theft)
- `SameSite` = Lax (CSRF protection)
- `Max-Age` = 7 days (with remember me)

**Session Data Stored:**
- User ID (for user loader)
- CSRF token (automatic via Flask-WTF)
- Login timestamp
- Remember me flag

**Session Security:**
- Session cookie is **signed** with `SECRET_KEY`
- Session data is **encrypted** (Flask built-in)
- Session is **regenerated** on login (prevents session fixation)
- Session is **cleared** on logout

---

## 5. Logout Flow

### 5.1 Logout Sequence Diagram

```mermaid
sequenceDiagram
    actor User
    participant Browser
    participant Flask as Flask App
    participant FlaskLogin as Flask-Login
    participant Session as Session Cookie

    User->>Browser: Click "Wyloguj" button
    Browser->>Flask: GET /logout

    Note over Flask: @login_required decorator<br/>verifies user is authenticated

    Flask->>FlaskLogin: logout_user()
    FlaskLogin->>Session: Clear session data
    Session->>Browser: Delete session cookie

    Flask->>Browser: Flash "Wylogowano pomyślnie"<br/>Redirect to /
    Browser->>User: Show homepage (logged out)
```

### 5.2 Logout Implementation Details

**Route:** `GET /logout`
**File:** `app.py` (lines ~3242-3248)
**Authentication:** Required (`@login_required`)

**Implementation:**
```python
@app.route('/logout')
@login_required
def logout():
    """User logout"""
    logout_user()  # Flask-Login clears session
    flash('Wylogowano pomyślnie.', 'success')
    return redirect(url_for('index'))
```

**What Happens on Logout:**
1. Flask-Login calls `logout_user()`
2. Session cookie is deleted
3. User object is removed from `current_user`
4. Browser redirected to homepage
5. All subsequent requests are anonymous

---

## 6. Password Reset Flow

### 6.1 Password Reset Sequence Diagram

```mermaid
sequenceDiagram
    actor User
    participant Browser
    participant Flask as Flask App
    participant DB as PostgreSQL
    participant EmailSvc as Email Service
    participant MSGraph as Microsoft Graph API

    Note over User,Browser: Phase 1: Request Reset

    User->>Browser: Navigate to /forgot-password
    Browser->>Flask: GET /forgot-password
    Flask->>Browser: Render forgot_password.html

    User->>Browser: Enter email address
    Browser->>Flask: POST /forgot-password (email)

    Flask->>Flask: validate_email(email)
    Flask->>DB: SELECT * FROM users<br/>WHERE email = ? AND is_active = TRUE

    alt User found
        DB->>Flask: User data

        Flask->>Flask: secrets.token_urlsafe(32)<br/>(generate reset token)
        Flask->>Flask: Calculate expiry (now + 1 hour)

        Flask->>DB: UPDATE users SET<br/>reset_token = ?,<br/>reset_token_expires = ?<br/>WHERE email = ?
        DB->>Flask: Update successful

        Flask->>EmailSvc: send_password_reset_email(email, token)
        EmailSvc->>MSGraph: POST /users/noreply@nordabiznes.pl/sendMail
        MSGraph->>EmailSvc: 202 Accepted

        Flask->>Browser: Flash "Sprawdź email"<br/>Redirect to /login
        Browser->>User: Show message

    else User not found
        Note over Flask: Still show success message<br/>(prevent email enumeration)
        Flask->>Browser: Flash "Sprawdź email"<br/>Redirect to /login
        Browser->>User: Show message
    end

    Note over User,Browser: Phase 2: Reset Password

    User->>Browser: Click link in email:<br/>https://nordabiznes.pl/reset-password/<token>
    Browser->>Flask: GET /reset-password/<token>

    Flask->>DB: SELECT * FROM users<br/>WHERE reset_token = ?<br/>AND reset_token_expires > NOW()

    alt Token valid
        DB->>Flask: User found
        Flask->>Browser: Render reset_password.html<br/>(password form)

        User->>Browser: Enter new password (twice)
        Browser->>Flask: POST /reset-password/<token><br/>(new_password, confirm_password)

        Flask->>Flask: validate_password(new_password)
        Flask->>Flask: Check passwords match
        Flask->>Flask: generate_password_hash(new_password)

        Flask->>DB: UPDATE users SET<br/>password_hash = ?,<br/>reset_token = NULL,<br/>reset_token_expires = NULL<br/>WHERE reset_token = ?
        DB->>Flask: Update successful

        Flask->>Browser: Flash "Hasło zostało zmienione"<br/>Redirect to /login
        Browser->>User: Show success message

    else Token invalid or expired
        Flask->>Browser: Flash "Link resetowania<br/>jest nieprawidłowy lub wygasł"<br/>Redirect to /forgot-password
        Browser->>User: Show error
    end
```

### 6.2 Password Reset Implementation Details

**Routes:**
- `POST /forgot-password` - Request reset
- `GET /reset-password/<token>` - Show reset form
- `POST /reset-password/<token>` - Process new password

**Files:** `app.py` (lines ~3251-3368)
**Rate Limit:** 5 requests per hour per IP

**Reset Token Properties:**
- Generated via `secrets.token_urlsafe(32)` (256-bit entropy)
- Expires after **1 hour**
- Single-use (cleared after successful reset)
- Stored in `users.reset_token` column

**Database Schema:**
```sql
reset_token VARCHAR(255) NULL
reset_token_expires TIMESTAMP NULL
```

**Security Considerations:**
- Reset tokens expire after 1 hour
- Tokens are cleared after use
- Password strength validation applied
- Email enumeration prevented (always show success message)
- Rate limiting prevents brute force

---

## 7. Authorization & Access Control

### 7.1 Authorization Levels

```mermaid
graph TB
    subgraph "Authorization Hierarchy"
        Public[👥 Public<br/>Anonymous Users]
        Auth[🔐 Authenticated<br/>Logged-in Users]
        Member[👔 NORDA Members<br/>is_norda_member=TRUE]
        Admin[👨‍💼 Administrators<br/>is_admin=TRUE]

        Public --> Auth
        Auth --> Member
        Member --> Admin
    end

    subgraph "Access Permissions"
        PublicRoutes["Public Routes:<br/>/, /search, /company/*,<br/>/audit/*, /api/companies"]
        AuthRoutes["Authenticated Routes:<br/>/dashboard, /chat,<br/>/forum/*, /wiadomosci/*,<br/>/kalendarz/*, /tablica/*"]
        AdminRoutes["Admin Routes:<br/>/admin/*, /api/*/audit"]
    end

    Public --> PublicRoutes
    Auth --> AuthRoutes
    Admin --> AdminRoutes
```

### 7.2 Route Protection Decorators

**Public Access (No decorator):**
```python
@app.route('/')
def index():
    """Public company directory"""
    # No authentication required
    return render_template('index.html')
```

**Authenticated Users Only:**
```python
@app.route('/dashboard')
@login_required
def dashboard():
    """User dashboard - requires login"""
    # current_user is automatically available
    return render_template('dashboard.html', user=current_user)
```

**Admin Only (Custom check):**
```python
@app.route('/admin/users')
@login_required
def admin_users():
    """Admin user management"""
    if not current_user.is_admin:
        flash('Brak uprawnień administratora.', 'error')
        return redirect(url_for('index'))

    # Admin logic here
    return render_template('admin/users.html')
```

### 7.3 Access Control Matrix

| Route Category | Public | Authenticated | NORDA Member | Admin |
|---------------|--------|---------------|--------------|-------|
| `/` (Company directory) | ✅ | ✅ | ✅ | ✅ |
| `/search` | ✅ | ✅ | ✅ | ✅ |
| `/company/<slug>` | ✅ | ✅ | ✅ | ✅ |
| `/audit/*/<slug>` | ✅ | ✅ | ✅ | ✅ |
| `/api/companies` | ✅ | ✅ | ✅ | ✅ |
| `/register`, `/login` | ✅ | ❌ | ❌ | ❌ |
| `/dashboard` | ❌ | ✅ | ✅ | ✅ |
| `/chat` | ❌ | ✅ | ✅ | ✅ |
| `/forum/*` | ❌ | ✅ | ✅ | ✅ |
| `/wiadomosci/*` | ❌ | ✅ | ✅ | ✅ |
| `/kalendarz/*` | ❌ | ✅ | ✅ | ✅ |
| `/tablica/*` | ❌ | ✅ | ✅ | ✅ |
| `/admin/*` | ❌ | ❌ | ❌ | ✅ |
| `/api/*/audit` | ❌ | ❌ | ❌ | ✅ |

**Legend:**
- ✅ Access granted
- ❌ Access denied (redirect to login or show error)

---

## 8. User Model Database Schema

### 8.1 Users Table Structure

```sql
CREATE TABLE users (
    -- Primary Key
    id SERIAL PRIMARY KEY,

    -- Authentication
    email VARCHAR(255) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,

    -- Profile
    name VARCHAR(255),
    phone VARCHAR(50),

    -- Company Association
    company_nip VARCHAR(10),
    company_id INTEGER REFERENCES companies(id),

    -- Status Flags
    is_active BOOLEAN DEFAULT TRUE,
    is_verified BOOLEAN DEFAULT FALSE,
    is_admin BOOLEAN DEFAULT FALSE,
    is_norda_member BOOLEAN DEFAULT FALSE,

    -- Timestamps
    created_at TIMESTAMP DEFAULT NOW(),
    last_login TIMESTAMP,
    verified_at TIMESTAMP,

    -- Email Verification
    verification_token VARCHAR(255),
    verification_token_expires TIMESTAMP,

    -- Password Reset
    reset_token VARCHAR(255),
    reset_token_expires TIMESTAMP,

    -- Indexes
    INDEX idx_users_email (email),
    INDEX idx_users_company_id (company_id)
);
```

### 8.2 User Model (SQLAlchemy)

**File:** `database.py` (lines ~119-158)

```python
class User(Base, UserMixin):
    """User accounts with Flask-Login integration"""
    __tablename__ = 'users'

    # Authentication fields
    id = Column(Integer, primary_key=True)
    email = Column(String(255), unique=True, nullable=False, index=True)
    password_hash = Column(String(255), nullable=False)

    # Profile
    name = Column(String(255))
    company_nip = Column(String(10))
    company_id = Column(Integer, ForeignKey('companies.id'), nullable=True)
    phone = Column(String(50))

    # Status
    is_active = Column(Boolean, default=True)
    is_verified = Column(Boolean, default=False)
    is_admin = Column(Boolean, default=False)
    is_norda_member = Column(Boolean, default=False)

    # Timestamps
    created_at = Column(DateTime, default=datetime.now)
    last_login = Column(DateTime)
    verified_at = Column(DateTime)

    # Verification token
    verification_token = Column(String(255))
    verification_token_expires = Column(DateTime)

    # Password reset token
    reset_token = Column(String(255))
    reset_token_expires = Column(DateTime)

    # Relationships
    company = relationship('Company', backref='users', lazy='joined')
    conversations = relationship('AIChatConversation', back_populates='user')
    forum_topics = relationship('ForumTopic', back_populates='author')
    forum_replies = relationship('ForumReply', back_populates='author')
```

**UserMixin Methods (Flask-Login):**
- `is_authenticated` - Always True for logged-in users
- `is_active` - Returns `self.is_active`
- `is_anonymous` - Always False for logged-in users
- `get_id()` - Returns `str(self.id)` for session storage

---

## 9. Security Features Summary

### 9.1 Security Measures Implemented

| Feature | Implementation | Protection Against |
|---------|----------------|---------------------|
| **CSRF Protection** | Flask-WTF automatic tokens | Cross-Site Request Forgery |
| **Password Hashing** | PBKDF2:SHA256 (werkzeug) | Rainbow table attacks |
| **Secure Cookies** | HttpOnly, Secure, SameSite=Lax | XSS cookie theft, CSRF |
| **Email Verification** | Required before login | Fake accounts |
| **Input Sanitization** | `sanitize_input()` function | XSS attacks |
| **Rate Limiting** | Flask-Limiter (5 req/hour) | Brute force attacks |
| **Session Regeneration** | Flask-Login automatic | Session fixation |
| **Token Expiry** | 24h (verify), 1h (reset) | Token replay attacks |
| **Open Redirect Prevention** | Next URL validation | Phishing attacks |
| **Password Strength** | 8+ chars, complexity rules | Weak passwords |

### 9.2 Security Best Practices

**Implemented:**
✅ HTTPS enforced in production
✅ Password hashing with secure algorithm
✅ CSRF protection on all forms
✅ Session cookies with security flags
✅ Email verification required
✅ Rate limiting on auth endpoints
✅ Input sanitization
✅ Token expiry
✅ Open redirect prevention

**Potential Improvements:**
⚠️ Add account lockout after N failed attempts
⚠️ Implement 2FA (TOTP) for admins
⚠️ Add password history (prevent reuse)
⚠️ Log authentication events for auditing
⚠️ Add CAPTCHA on registration
⚠️ Implement session timeout (idle)
⚠️ Add IP-based rate limiting

---

## 10. Testing Accounts (Production)

### 10.1 Test Users

**IMPORTANT:** Use only these accounts for testing production features.

| Account | Email | Role | Purpose |
|---------|-------|------|---------|
| Test User | `test@nordabiznes.pl` | Regular User | Test user-level features |
| Test Admin | `testadmin@nordabiznes.pl` | Administrator | Test admin features |

**Test Account Credentials:**
- Password stored in CLAUDE.md (do not commit to repository)
- Accounts are pre-verified (`is_verified = TRUE`)
- Test Admin has `is_admin = TRUE` flag

**Usage:**
- Always use test accounts for production testing
- Never modify real user accounts for testing
- Test authentication flows, authorization, session management

---

## 11. Related Documentation

### 11.1 Architecture Documents

- [System Context Diagram](../01-system-context.md) - External actors and integrations
- [Container Diagram](../02-container-diagram.md) - Flask app and session storage
- [Flask Components](../04-flask-components.md) - Authentication routes and decorators
- [Database Schema](../05-database-schema.md) - Users table and relationships

### 11.2 Code Files

**Authentication Routes:**
- `app.py` lines ~3077-3500 (register, login, logout, verify, reset)

**User Model:**
- `database.py` lines ~119-158 (User model with Flask-Login)

**Email Service:**
- `email_service.py` (Microsoft Graph email sending)

**Security Utilities:**
- `app.py` - `sanitize_input()`, `validate_email()`, `validate_password()`

### 11.3 External Dependencies

- **Flask-Login:** User session management
- **Flask-WTF:** CSRF protection
- **Flask-Limiter:** Rate limiting
- **werkzeug.security:** Password hashing
- **secrets:** Cryptographic token generation

---

## 12. Maintenance & Updates

### 12.1 When to Update This Document

Update this document when:
- Authentication flow changes (new steps, validation)
- Session management changes (cookie settings, expiry)
- Security measures are added/modified
- New authorization levels are introduced
- User model schema changes

### 12.2 Verification Checklist

When updating authentication flow:
- [ ] Test registration with valid/invalid data
- [ ] Test email verification with valid/expired tokens
- [ ] Test login with correct/incorrect credentials
- [ ] Test session persistence (remember me)
- [ ] Test logout clears session
- [ ] Test password reset flow end-to-end
- [ ] Verify CSRF protection is active
- [ ] Verify rate limiting works
- [ ] Test authorization levels (public, user, admin)
- [ ] Verify security headers on cookies

### 12.3 Security Audit Checklist

Periodic security audit:
- [ ] Review password hashing algorithm (current: PBKDF2:SHA256)
- [ ] Check token expiry times (verify: 24h, reset: 1h)
- [ ] Verify session cookie security flags
- [ ] Review rate limiting thresholds
- [ ] Check for SQL injection vulnerabilities
- [ ] Test XSS prevention in inputs
- [ ] Verify CSRF protection coverage
- [ ] Review authentication logs for anomalies
- [ ] Test open redirect prevention
- [ ] Verify email verification enforcement

---

**Document End**

*This document is maintained as part of the Norda Biznes Partner architecture documentation. For questions or updates, contact the development team.*
