# IAM Service .NET 10

> Identity and Access Management Service built with .NET 10, ASP.NET Core Identity, and **Duende IdentityServer** following DDD, CQRS, and Clean Architecture patterns.

## Overview

This service provides OAuth2/OpenID Connect authentication and authorization:

- **OAuth2/OIDC Server** - Duende IdentityServer for token management
- **User Management** - Registration, profile, soft-delete
- **Role-Based Access Control** - User roles and permissions
- **Token Management** - Access (15 min), Refresh (7 days) tokens
- **Email Verification** - SMTP-based email confirmation
- **Two-Factor Authentication (2FA)** - TOTP with QR code setup
- **Social Login** - Google and Facebook OAuth integration
- **CQRS Pattern** - MediatR for Commands/Queries
- **Clean Architecture** - Domain, Infrastructure, API layers

## Tech Stack

| Technology | Purpose |
|------------|---------|
| .NET 10 | Runtime |
| ASP.NET Core Identity | User/Role management |
| Duende IdentityServer | OAuth2/OIDC server |
| EF Core + PostgreSQL | Data persistence |
| Redis | Distributed caching |
| MediatR | CQRS pattern |
| FluentValidation | Request validation |
| Serilog | Structured logging |

## Quick Start

### 1. Prerequisites

- .NET SDK 10.0.101+
- Docker (for PostgreSQL)

### 2. Configure Environment

```bash
cp .env.example .env
# Edit DATABASE_URL, JWT_SECRET in .env
```

### 3. Run with Docker

```bash
docker-compose up -d
```

Service available at: `http://localhost:5001`

### 4. Run Locally

```bash
dotnet restore
dotnet build
dotnet run --project src/IamService.API
```

## Database Migrations

### Prerequisites

```bash
# Install EF Core tools (one-time)
dotnet tool install --global dotnet-ef
```

### Create Migration

```bash
dotnet ef migrations add <MigrationName> \
  --project src/IamService.Infrastructure \
  --startup-project src/IamService.API
```

### Apply Migration

```bash
dotnet ef database update \
  --project src/IamService.Infrastructure \
  --startup-project src/IamService.API
```

### Neon Database Setup

1. Create database on [Neon Console](https://console.neon.tech)
2. Update `appsettings.Development.json` with connection string
3. Run: `dotnet ef database update ...`

## API Endpoints

### Authorization Policies

> **Note:** All API endpoints require authentication (Bearer JWT Token).  
> Some endpoints require specific roles as shown below.

| Policy | Required Role | Applied To |
|--------|---------------|------------|
| `RequireSuperAdmin` | SuperAdmin | PAM (Privileged Access Management) |
| `RequireAdmin` | Admin, SuperAdmin | User/Role/Group/Organization management |
| `RequireAuditor` | Auditor, Admin, SuperAdmin | Audit logs, Compliance reports |
| `OwnerOrAdmin` | Owner or Admin | User self-service profile management |

**Authorization by Controller:**

| Controller | Policy | Description |
|------------|--------|-------------|
| Users (GET /users, DELETE) | RequireAdmin | List users, delete user |
| Users (GET/PUT /{id}) | OwnerOrAdmin | User access own profile or Admin access any |
| Roles | RequireAdmin | Role management |
| Organizations | RequireAdmin | Organization management |
| Groups | RequireAdmin | Group management |
| Access Requests | RequireAdmin | Access request workflow |
| Access Reviews | RequireAdmin | Periodic access review |
| Privileged Access | RequireSuperAdmin | PAM - most sensitive |
| Audit | RequireAuditor | View audit logs |
| Compliance | RequireAuditor | Compliance reports |
| Verifications | RequireAdmin | Identity verification |

### Authentication (`/api/v1/auth`)

| Method | Endpoint | Description | Auth |
|--------|----------|-------------|------|
| `POST` | `/api/v1/auth/register` | Register new user | ❌ |
| `POST` | `/connect/token` | OAuth2 token endpoint | ❌ |
| `POST` | `/api/v1/auth/change-password` | Change password | ✅ |
| `POST` | `/api/v1/auth/logout` | Logout (revoke tokens) | ✅ |

### Email Verification (`/api/v1/auth`)

| Method | Endpoint | Description | Auth |
|--------|----------|-------------|------|
| `POST` | `/api/v1/auth/send-verification-email` | Send email verification link | ✅ |
| `POST` | `/api/v1/auth/confirm-email` | Confirm email with token | ❌ |

### Two-Factor Authentication (`/api/v1/auth/2fa`)

| Method | Endpoint | Description | Auth |
|--------|----------|-------------|------|
| `POST` | `/api/v1/auth/2fa/enable` | Enable 2FA (get QR code) | ✅ |
| `POST` | `/api/v1/auth/2fa/verify` | Verify TOTP code & activate | ✅ |
| `POST` | `/api/v1/auth/2fa/disable` | Disable 2FA | ✅ |

### Social Login (`/api/v1/auth`)

| Method | Endpoint | Description | Auth |
|--------|----------|-------------|------|
| `GET` | `/api/v1/auth/external-login/{provider}` | Initiate OAuth flow (Google/Facebook) | ❌ |
| `GET` | `/api/v1/auth/external-callback` | Handle OAuth callback | ❌ |
| `GET` | `/api/v1/auth/linked-accounts` | Get linked OAuth providers | ✅ |

### User Management (`/api/v1/users`)

| Method | Endpoint | Description | Auth |
|--------|----------|-------------|------|
| `GET` | `/api/v1/users` | List users (paginated) | ✅ |
| `GET` | `/api/v1/users/me` | Get current user | ✅ |
| `GET` | `/api/v1/users/{id}` | Get user by ID | ✅ |
| `PUT` | `/api/v1/users/{id}` | Update user | ✅ |
| `DELETE` | `/api/v1/users/{id}` | Delete user (soft) | ✅ |
| `GET` | `/api/v1/users/{id}/roles` | Get user's assigned roles | ✅ |
| `GET` | `/api/v1/users/{id}/permissions` | Get user's effective permissions | ✅ |

### User Profiles (`/api/v1/users/{userId}/profile`)

| Method | Endpoint | Description | Auth |
|--------|----------|-------------|------|
| `GET` | `/api/v1/users/{userId}/profile` | Get user profile | ✅ |
| `PUT` | `/api/v1/users/{userId}/profile` | Update user profile | ✅ |
| `PUT` | `/api/v1/users/{userId}/profile/attributes/{key}` | Set profile attribute | ✅ |

### Roles Management (`/api/v1/roles`)

| Method | Endpoint | Description | Auth |
|--------|----------|-------------|------|
| `GET` | `/api/v1/roles` | List roles (paginated) | ✅ |
| `GET` | `/api/v1/roles/{id}` | Get role by ID | ✅ |
| `POST` | `/api/v1/roles` | Create new role | ✅ |
| `PUT` | `/api/v1/roles/{id}` | Update role | ✅ |
| `DELETE` | `/api/v1/roles/{id}` | Delete role | ✅ |
| `POST` | `/api/v1/roles/users/{userId}` | Assign role to user | ✅ |
| `DELETE` | `/api/v1/roles/users/{userId}/{roleName}` | Remove role from user | ✅ |

### Identity Verifications (`/api/v1/verifications`)

| Method | Endpoint | Description | Auth |
|--------|----------|-------------|------|
| `POST` | `/api/v1/verifications/phone` | Request phone verification | ✅ |
| `POST` | `/api/v1/verifications/email` | Request email verification | ✅ |
| `POST` | `/api/v1/verifications/{id}/confirm` | Confirm verification with code | ✅ |

### Health Endpoints

| Endpoint | Purpose |
|----------|---------|
| `/health` | Full health status |
| `/health/live` | Liveness probe |
| `/health/ready` | Readiness probe |

### Organizations (`/api/v1/organizations`) - Phase 2

| Method | Endpoint | Description | Auth |
|--------|----------|-------------|------|
| `GET` | `/api/v1/organizations/{id}` | Get organization by ID | ✅ |
| `GET` | `/api/v1/organizations/slug/{slug}` | Get organization by slug | ✅ |
| `POST` | `/api/v1/organizations` | Create organization | ✅ |
| `PUT` | `/api/v1/organizations/{id}` | Update organization | ✅ |
| `DELETE` | `/api/v1/organizations/{id}` | Archive organization | ✅ |
| `GET` | `/api/v1/organizations/{id}/hierarchy` | Get hierarchy | ✅ |
| `GET` | `/api/v1/organizations/{id}/children` | Get child orgs | ✅ |

### Groups (`/api/v1/groups`) - Phase 2

| Method | Endpoint | Description | Auth |
|--------|----------|-------------|------|
| `GET` | `/api/v1/groups` | List groups | ✅ |
| `GET` | `/api/v1/groups/{id}` | Get group by ID | ✅ |
| `POST` | `/api/v1/groups` | Create group | ✅ |
| `DELETE` | `/api/v1/groups/{id}` | Delete group | ✅ |
| `POST` | `/api/v1/groups/{id}/members` | Add member | ✅ |
| `DELETE` | `/api/v1/groups/{id}/members/{userId}` | Remove member | ✅ |

### Access Requests (`/api/v1/access-requests`) - Phase 3A

| Method | Endpoint | Description | Auth |
|--------|----------|-------------|------|
| `POST` | `/api/v1/access-requests` | Create access request | ✅ |
| `GET` | `/api/v1/access-requests` | List requests | ✅ |
| `GET` | `/api/v1/access-requests/{id}` | Get request by ID | ✅ |
| `POST` | `/api/v1/access-requests/{id}/submit` | Submit request | ✅ |
| `POST` | `/api/v1/access-requests/{id}/approve` | Approve | ✅ |
| `POST` | `/api/v1/access-requests/{id}/reject` | Reject | ✅ |
| `DELETE` | `/api/v1/access-requests/{id}` | Cancel request | ✅ |
| `GET` | `/api/v1/access-requests/pending` | Pending requests | ✅ |

### Access Reviews (`/api/v1/access-reviews`) - Phase 3B

| Method | Endpoint | Description | Auth |
|--------|----------|-------------|------|
| `POST` | `/api/v1/access-reviews` | Create access review | ✅ |
| `GET` | `/api/v1/access-reviews/{id}` | Get review by ID | ✅ |
| `POST` | `/api/v1/access-reviews/{id}/items` | Add item | ✅ |
| `POST` | `/api/v1/access-reviews/{id}/start` | Start review | ✅ |
| `POST` | `/api/v1/access-reviews/{id}/items/{itemId}/review` | Certify/Revoke | ✅ |
| `POST` | `/api/v1/access-reviews/{id}/complete` | Complete | ✅ |

### Privileged Access (`/api/v1/privileged-access`) - Phase 3B PAM

| Method | Endpoint | Description | Auth |
|--------|----------|-------------|------|
| `POST` | `/api/v1/privileged-access/request` | Request JIT access | ✅ |
| `GET` | `/api/v1/privileged-access/active` | Active grants | ✅ |
| `POST` | `/api/v1/privileged-access/{id}/revoke` | Revoke access | ✅ |

### Audit (`/api/v1/audit`) - Phase 4A

| Method | Endpoint | Description | Auth |
|--------|----------|-------------|------|
| `GET` | `/api/v1/audit/logs` | Get audit logs (filtered) | ✅ |

### Compliance (`/api/v1/compliance`) - Phase 4A

| Method | Endpoint | Description | Auth |
|--------|----------|-------------|------|
| `POST` | `/api/v1/compliance/reports` | Generate report | ✅ |
| `GET` | `/api/v1/compliance/reports` | List reports | ✅ |
| `GET` | `/api/v1/compliance/reports/{id}` | Report detail | ✅ |
| `POST` | `/api/v1/compliance/reports/{id}/complete` | Complete report | ✅ |
| `GET` | `/api/v1/compliance/violations` | Unresolved violations | ✅ |

## Authentication Flow

### Step 1: Register a New User

```bash
curl -X POST http://localhost:5001/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "password": "Password123!",
    "firstName": "John",
    "lastName": "Doe",
    "phoneNumber": "+84901234567"
  }'
```

**Response:**
```json
{
  "success": true,
  "data": {
    "userId": "8083b412-5040-4b74-baf0-5cb709861957",
    "email": "user@example.com",
    "fullName": "John Doe"
  },
  "error": null,
  "pagination": null
}
```

### Step 2: Login (Password Grant)

```bash
curl -X POST http://localhost:5001/connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password" \
  -d "client_id=password-client" \
  -d "client_secret=password-client-secret" \
  -d "username=user@example.com" \
  -d "password=Password123!" \
  -d "scope=openid profile email api offline_access"
```

**Response:**
```json
{
  "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjlGMzk1RDQzMDIz...",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "5B8FB8189575241FF63C32ED2D0D492B32DA533B...",
  "scope": "api email offline_access openid profile"
}
```

### Step 3: Use Access Token

```bash
curl http://localhost:5001/api/v1/users/me \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."
```

### Step 4: Refresh Token

```bash
curl -X POST http://localhost:5001/connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=eyJhbGciOiJSUzI1NiIs..."
```

### Step 5: Logout

```bash
curl -X POST http://localhost:5001/api/v1/auth/logout \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

## Client Credentials (Service-to-Service)

For service-to-service authentication without user context:

```bash
curl -X POST http://localhost:5001/connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=goodgo-service" \
  -d "client_secret=service-secret" \
  -d "scope=api"
```

### Supported OAuth2 Grant Types

| Grant Type | Use Case | Requires User |
|------------|----------|---------------|
| `password` | User login from trusted apps | Yes |
| `refresh_token` | Token renewal | No (uses refresh token) |
| `client_credentials` | Service-to-service | No |

## Email Verification

### Send Verification Email

```bash
curl -X POST http://localhost:5001/api/v1/auth/send-verification-email \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com"}'
```

**Response:**
```json
{
  "success": true,
  "data": {
    "success": true,
    "message": "Verification email sent successfully."
  },
  "error": null,
  "pagination": null
}
```

### Confirm Email

```bash
curl -X POST http://localhost:5001/api/v1/auth/confirm-email \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "token": "confirmation-token-from-email"}'
```

## Two-Factor Authentication (2FA)

### Enable 2FA

```bash
curl -X POST http://localhost:5001/api/v1/auth/2fa/enable \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**Response:**
```json
{
  "success": true,
  "data": {
    "qrCodeBase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAooAAAKK...",
    "manualEntryKey": "3R36NBSHHFGRTZLMNSOH5Q2FPETPRYMP",
    "recoveryCodes": [
      "KEJF-MPAY",
      "URMF-YRVX",
      "GPK4-G9WX",
      "LS34-FNP8",
      "WRM0-WAML",
      "QY5W-VVER",
      "AIEU-GLD6",
      "FDJL-3HNY",
      "J1MC-JXXT",
      "8GVA-OAAM"
    ]
  },
  "error": null,
  "pagination": null
}
```

### Verify 2FA Code

```bash
curl -X POST http://localhost:5001/api/v1/auth/2fa/verify \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"code": "123456"}'
```

### Disable 2FA

```bash
curl -X POST http://localhost:5001/api/v1/auth/2fa/disable \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"code": "123456"}'
```

## Social Login

### Initiate OAuth Flow

Redirect user to:
```
GET http://localhost:5001/api/v1/auth/external-login/Google?returnUrl=http://your-app/callback
GET http://localhost:5001/api/v1/auth/external-login/Facebook?returnUrl=http://your-app/callback
```

### Get Linked Accounts

```bash
curl http://localhost:5001/api/v1/auth/linked-accounts \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**Response:**
```json
{
  "success": true,
  "data": {
    "linkedProviders": [
      {"provider": "Google", "providerDisplayName": "Google"},
      {"provider": "Facebook", "providerDisplayName": "Facebook"}
    ]
  }
}
```

## Configuration

### Environment Variables

| Variable | Description | Required |
|----------|-------------|----------|
| `ASPNETCORE_ENVIRONMENT` | Environment | No (default: Development) |
| `DATABASE_URL` | PostgreSQL connection | Yes |
| `JWT_SECRET` | JWT signing secret (32+ chars) | Yes |
| `REDIS_HOST` | Redis server host | No (default: localhost) |
| `REDIS_PORT` | Redis server port | No (default: 6379) |
| `REDIS_PASSWORD` | Redis password | No |
| `REDIS_DATABASE` | Redis database number | No (default: 0) |

### Token Lifetimes

| Token | Lifetime |
|-------|----------|
| Access Token | 15 minutes |
| Refresh Token | 7 days |

## Redis Caching

The service uses Redis for distributed caching with the `ICacheService` interface.

### Configuration

Add Redis settings in `appsettings.json`:

```json
{
  "Redis": {
    "Host": "localhost",
    "Port": 6379,
    "Password": "",
    "Database": 0,
    "ConnectTimeout": 5000,
    "SyncTimeout": 5000
  }
}
```

Or use environment variables:

```bash
REDIS_HOST=your-redis-host
REDIS_PORT=6379
REDIS_PASSWORD=your-password
REDIS_DATABASE=0
```

### ICacheService Interface

```csharp
public interface ICacheService
{
    // Basic operations
    Task<T?> GetAsync<T>(string key);
    Task SetAsync<T>(string key, T value, TimeSpan? expiration = null);
    Task RemoveAsync(string key);
    Task<bool> ExistsAsync(string key);
    
    // Get or create pattern
    Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null);
    
    // Token blacklist support
    Task BlacklistAsync(string key, TimeSpan expiration);
    Task<bool> IsBlacklistedAsync(string key);
}
```

### Usage Examples

**Basic Get/Set:**
```csharp
// Inject ICacheService
public class MyService
{
    private readonly ICacheService _cache;
    
    public MyService(ICacheService cache) => _cache = cache;
    
    public async Task<User?> GetUser(string userId)
    {
        return await _cache.GetAsync<User>($"user:{userId}");
    }
    
    public async Task CacheUser(User user)
    {
        await _cache.SetAsync($"user:{user.Id}", user, TimeSpan.FromMinutes(15));
    }
}
```

**Get or Set Pattern (Cache-Aside):**
```csharp
public async Task<User> GetUserById(string userId)
{
    return await _cache.GetOrSetAsync(
        $"user:{userId}",
        async () => await _repository.GetByIdAsync(userId),
        TimeSpan.FromMinutes(15)
    );
}
```

**Token Blacklisting (for Logout):**
```csharp
public async Task Logout(string tokenId)
{
    // Blacklist the refresh token for its remaining lifetime
    await _cache.BlacklistAsync($"token:{tokenId}", TimeSpan.FromDays(7));
}

public async Task<bool> IsTokenRevoked(string tokenId)
{
    return await _cache.IsBlacklistedAsync($"token:{tokenId}");
}

### Password Policy

- Minimum 8 characters
- Requires: uppercase, lowercase, digit, special character

## Project Structure

```
iam-service-net/
├── src/
│   ├── IamService.API/              # Controllers, CQRS
│   │   ├── Controllers/             # AuthController, UsersController
│   │   └── Application/             # Commands, Queries, Validations
│   ├── IamService.Domain/           # Domain entities
│   │   ├── AggregatesModel/         # UserAggregate, RoleAggregate
│   │   ├── Events/                  # Domain events
│   │   └── Exceptions/              # Domain exceptions
│   └── IamService.Infrastructure/   # Data access
│       ├── IamServiceContext.cs     # DbContext with Identity
│       └── Repositories/            # Repository implementations
├── tests/
│   ├── IamService.UnitTests/
│   └── IamService.FunctionalTests/
├── docs/
│   ├── en/                          # English documentation
│   └── vi/                          # Vietnamese documentation
├── Dockerfile
└── docker-compose.yml
```

## Swagger UI

After running the service, access Swagger UI at:
- **Local**: http://localhost:5001/swagger
- **Docker**: http://localhost/api/v1/iam/swagger

## Testing

```bash
# Run all tests
dotnet test

# Run with coverage
dotnet test /p:CollectCoverage=true
```

## Docker

```bash
# Build image
docker build -t goodgo/iam-service:latest .

# Run container
docker run -p 5001:8080 --env-file .env goodgo/iam-service:latest
```

## Resources

- [Duende IdentityServer Documentation](https://docs.duendesoftware.com/identityserver/v7/)
- [ASP.NET Core Identity](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity)
- [OAuth2 Specification](https://oauth.net/2/)
- [eShopOnContainers](https://github.com/dotnet-architecture/eShopOnContainers)

## License

Proprietary - GoodGo Platform
