# Universal Auth-CheChe Integration Guide & AI Cheatsheet

This universal guide serves as an operational reference and AI prompt source for integrating with **auth-cheche** (`auth_api`) across all AI tools (ChatGPT, GitHub Copilot, Windsurf, Cursor, Claude, Antigravity) and development environments (Express.js, NestJS, Go, React, Flutter).

---

## 1. System Architecture & Core Concepts

- **System Role**: `auth-cheche` is the central identity management, user authentication, device session tracking, and service authorization microservice for the CheChe platform.
- **Shared Secret Architecture**: All downstream microservices share the same `APP_AUTH_TOKEN` secret key to verify HS256 JWT signatures without needing to hit `auth-cheche` on every HTTP request.
- **Base URLs**:
  - Local Dev: `http://localhost:5000/v1` or `http://localhost:5003/v1`
  - Staging / Prod: `https://auth.cheche.et/v1`
- **Mobile Number Invariant**: Mobile numbers must be in Ethiopian format: `+251XXXXXXXXX` (e.g. `+251912345678`).

---

## 2. JWT Specification & Claims Schema

### Secret Configuration
Set `APP_AUTH_TOKEN` in the environment of every microservice that validates user JWTs.

### Token TTL Lifetimes
- **Access Token (`token_type: "access"`)**: Valid for **3 days** (`3 * 24h`). Used for API authorization.
- **Refresh Token (`token_type: "refresh"`)**: Valid for **7 days** (`7 * 24h`). Used solely at `POST /v1/auth/refresh`.

### JWT Claims Payload
```json
{
  "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "firstname": "Abebe",
  "middlename": "Bikila",
  "lastname": "Tola",
  "mobile": "+251912345678",
  "service_ids": [
    "c8a14b3d-7a6b-4e8c-9a1d-2e3f4a5b6c7d",
    "f47ac10b-58cc-4372-a567-0e02b2c3d4e5"
  ],
  "token_type": "access",
  "auth_role": "super_admin",
  "session_id": "sess-98765-abcd",
  "iss": "auth_api",
  "sub": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "exp": 1740000000,
  "iat": 1739740800
}
```

---

## 3. Express.js (Node.js & TypeScript) Integration Guide

### Express Middleware (`authCheCheMiddleware.ts`)

```typescript
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';

export interface CheCheJwtClaims {
  user_id: string;
  firstname: string;
  middlename?: string;
  lastname: string;
  mobile: string;
  service_ids?: string[];
  token_type: 'access' | 'refresh';
  auth_role?: string;
  session_id?: string;
  exp: number;
  iat: number;
}

export interface AuthenticatedRequest extends Request {
  user?: CheCheJwtClaims;
  userId?: string;
}

export const authCheCheMiddleware = (requiredServiceId?: string) => {
  return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
    const authHeader = req.headers.authorization;

    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      return res.status(401).json({ error: 'Authorization header missing or invalid format' });
    }

    const token = authHeader.split(' ')[1];
    const secret = process.env.APP_AUTH_TOKEN;

    if (!secret) {
      console.error('FATAL: APP_AUTH_TOKEN environment variable not set');
      return res.status(500).json({ error: 'Internal server authentication configuration error' });
    }

    try {
      const decoded = jwt.verify(token, secret) as CheCheJwtClaims;

      if (decoded.token_type !== 'access') {
        return res.status(403).json({ error: 'Invalid token type provided, expected access token' });
      }

      if (requiredServiceId) {
        const userServices = decoded.service_ids || [];
        if (!userServices.includes(requiredServiceId)) {
          return res.status(403).json({ error: 'User is not authorized to access this service' });
        }
      }

      req.user = decoded;
      req.userId = decoded.user_id;
      next();
    } catch (err: any) {
      if (err.name === 'TokenExpiredError') {
        return res.status(401).json({ error: 'Token has expired' });
      }
      return res.status(401).json({ error: 'Invalid authentication token' });
    }
  };
};
```

---

## 4. Frontend Client Integration (React / Next.js / Mobile)

Axios HTTP Client with automatic token refresh queue and device metadata:

```typescript
import axios from 'axios';

const AUTH_API_URL = process.env.NEXT_PUBLIC_AUTH_API_URL || 'https://auth.cheche.et/v1';

export const authClient = axios.create({
  baseURL: AUTH_API_URL,
  headers: {
    'Content-Type': 'application/json',
  },
});

authClient.interceptors.request.use((config) => {
  if (typeof window !== 'undefined') {
    const token = localStorage.getItem('access_token');
    if (token && config.headers) {
      config.headers.Authorization = `Bearer ${token}`;
    }
  }
  return config;
});

let isRefreshing = false;
let failedQueue: Array<{ resolve: (token: string) => void; reject: (err: any) => void }> = [];

const processQueue = (error: any, token: string | null = null) => {
  failedQueue.forEach((prom) => {
    if (error) {
      prom.reject(error);
    } else if (token) {
      prom.resolve(token);
    }
  });
  failedQueue = [];
};

authClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;

    if (error.response?.status === 401 && !originalRequest._retry) {
      if (isRefreshing) {
        return new Promise((resolve, reject) => {
          failedQueue.push({ resolve, reject });
        })
          .then((token) => {
            originalRequest.headers.Authorization = `Bearer ${token}`;
            return authClient(originalRequest);
          })
          .catch((err) => Promise.reject(err));
      }

      originalRequest._retry = true;
      isRefreshing = true;

      try {
        const refreshToken = localStorage.getItem('refresh_token');
        if (!refreshToken) throw new Error('No refresh token available');

        const { data } = await axios.post(`${AUTH_API_URL}/auth/refresh`, {
          refresh_token: refreshToken,
        });

        const newAccessToken = data.access_token;
        localStorage.setItem('access_token', newAccessToken);

        authClient.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`;
        processQueue(null, newAccessToken);
        return authClient(originalRequest);
      } catch (refreshErr) {
        processQueue(refreshErr, null);
        localStorage.removeItem('access_token');
        localStorage.removeItem('refresh_token');
        if (typeof window !== 'undefined') window.location.href = '/login';
        return Promise.reject(refreshErr);
      } finally {
        isRefreshing = false;
      }
    }
    return Promise.reject(error);
  }
);
```

---

## 5. Complete API Endpoint Specifications (`/v1`)

### Dual Auth Sign-In (PIN + Password) — *Primary Auth Flow*
- **Path**: `POST /v1/users/signin/pin-password`
- **Query Parameter**: `service_id` (optional UUID)
- **Request Body**:
  ```json
  {
    "mobile": "+251912345678",
    "pin": "123456",
    "password": "mySecurePassword123",
    "device_id": "device-uuid-99",
    "device_name": "iPhone 15 Pro",
    "platform": "ios"
  }
  ```
- **Response (200 OK)**:
  ```json
  {
    "message": "Signin successful",
    "access_token": "eyJhbGciOi...",
    "refresh_token": "eyJhbGciOi...",
    "service_ids": ["c8a14b3d-7a6b-4e8c-9a1d-2e3f4a5b6c7d"],
    "service_id": "c8a14b3d-7a6b-4e8c-9a1d-2e3f4a5b6c7d",
    "session_id": "sess-123456"
  }
  ```

---

## 6. Error Handling Matrix & Rate Limiting

- **Standard Error**: `{"error": "description"}`
- **OTP Error**: `{"response": "description"}`
- **HTTP 429 Rate Limit**: `{"message": "Too many requests"}` (5 req/s per IP global limit; 3 req/min for Send-OTP)
