TAJMAC Auth

AI Coding Instructions

Structured integration guide for AI coding assistants (Claude Code, GitHub Copilot, Cursor, etc.)

Download raw text

Copy the content below into your AI assistant's context window, or fetch the raw version from /docs/ai-instructions/raw in your CI or script.

# TAJMAC Auth — AI Integration Guide

## What is TAJMAC Auth?
TAJMAC Auth is a self-hosted identity platform providing complete authentication infrastructure.

- REST API server on port 3002 (Hono / Bun)
- Admin portal on port 3003 (Next.js)
- Hosted login UI on port 3004 (Next.js)
- Database: PostgreSQL with schema `tajmac_auth`

## npm / package

There is no published SDK yet. Integrate by calling the REST API directly.

## Key API endpoints

### Authentication (public — no auth required)

```
POST /api/v1/auth/sign-in/email
Body: { email, password, appSlug }
Response: sets tajmac.session HttpOnly cookie

POST /api/v1/auth/sign-up/email
Body: { email, password, name, appSlug }
Response: sets tajmac.session HttpOnly cookie

GET /api/v1/auth/session
Cookie: tajmac.session=<token>
Response: { user: { id, email, name, emailVerified }, session: { id, expiresAt } }

POST /api/v1/auth/sign-out
Cookie: tajmac.session=<token>
Response: 200, clears cookie

POST /api/v1/auth/magic-link/send
Body: { email, appSlug }
Response: { ok: true }

GET /api/v1/auth/magic-link/verify?token=<token>
Response: sets tajmac.session cookie, redirects

POST /api/v1/auth/forgot-password
Body: { email, appSlug }
Response: { ok: true }

POST /api/v1/auth/reset-password
Body: { token, password }
Response: { ok: true }
```

### Portal management (requires tajmac.session cookie — portal admin only)

```
GET    /api/v1/portal/apps
POST   /api/v1/portal/apps
GET    /api/v1/portal/apps/:slug
PATCH  /api/v1/portal/apps/:slug
DELETE /api/v1/portal/apps/:slug

GET    /api/v1/portal/admin/users
GET    /api/v1/portal/admin/stats

GET    /api/v1/portal/organizations
POST   /api/v1/portal/organizations
GET    /api/v1/portal/organizations/:id
PATCH  /api/v1/portal/organizations/:id
DELETE /api/v1/portal/organizations/:id

GET    /api/v1/portal/permissions/roles
POST   /api/v1/portal/permissions/roles
POST   /api/v1/portal/permissions/check

GET    /api/v1/portal/admin/flags
POST   /api/v1/portal/admin/flags

GET    /api/v1/portal/admin/api-keys
POST   /api/v1/portal/admin/api-keys
DELETE /api/v1/portal/admin/api-keys/:id

GET    /api/v1/portal/oauth-clients
POST   /api/v1/portal/oauth-clients
```

### OIDC Provider

```
GET /.well-known/openid-configuration
GET /.well-known/jwks.json
GET /oidc/auth        (authorization endpoint)
POST /oidc/token      (token endpoint)
GET /oidc/me          (userinfo endpoint)
```

## Session validation

```typescript
// In Next.js middleware or API route (server-side)
const sessionToken = req.cookies.get("tajmac.session")?.value;
if (!sessionToken) return; // not authenticated

const res = await fetch(`${process.env.TAJMAC_AUTH_URL}/api/v1/auth/session`, {
  headers: { Cookie: `tajmac.session=${sessionToken}` },
});
if (!res.ok) return; // invalid or expired session

const { user, session } = await res.json();
// user.id, user.email, user.name
```

## Sign-in (server-side proxy pattern)

```typescript
// Next.js API route: app/api/auth/sign-in/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
  const body = await req.json();

  const upstream = await fetch(
    `${process.env.TAJMAC_AUTH_URL}/api/v1/auth/sign-in/email`,
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ ...body, appSlug: process.env.TAJMAC_APP_SLUG }),
    },
  );

  const json = await upstream.json();
  const res = NextResponse.json(json, { status: upstream.status });

  // Forward Set-Cookie so session cookie lands on your domain
  upstream.headers.forEach((value, key) => {
    if (key.toLowerCase() === "set-cookie") {
      res.headers.append("Set-Cookie", value);
    }
  });

  return res;
}
```

## Environment variables required

```bash
# Your app's .env.local
TAJMAC_AUTH_URL=http://localhost:3002          # auth server URL
TAJMAC_APP_SLUG=my-app                         # slug of your registered app
# Optional: internal URL (faster in same network)
TAJMAC_AUTH_INTERNAL_URL=http://auth-server:3002
```

## Common patterns

### Next.js middleware (protect all dashboard routes)

```typescript
// middleware.ts
import { NextRequest, NextResponse } from "next/server";

export async function middleware(req: NextRequest) {
  const session = req.cookies.get("tajmac.session")?.value;

  if (!session) {
    return NextResponse.redirect(new URL("/login", req.url));
  }

  // Optional: validate session on every request (adds latency)
  // const res = await fetch(...)

  return NextResponse.next();
}

export const config = {
  matcher: ["/dashboard/:path*", "/profile/:path*"],
};
```

### React hook (client component)

```typescript
"use client";
import { useState, useEffect } from "react";

export function useSession() {
  const [session, setSession] = useState<{ user: { id: string; email: string; name: string } } | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch("/api/auth/session")
      .then(r => r.ok ? r.json() : null)
      .then(setSession)
      .catch(() => setSession(null))
      .finally(() => setLoading(false));
  }, []);

  return { session, loading };
}
```

### Express.js middleware

```typescript
import type { Request, Response, NextFunction } from "express";

export async function requireAuth(req: Request, res: Response, next: NextFunction) {
  const session = req.cookies["tajmac.session"];
  if (!session) return res.status(401).json({ error: "Unauthorized" });

  const r = await fetch(`${process.env.TAJMAC_AUTH_URL}/api/v1/auth/session`, {
    headers: { Cookie: `tajmac.session=${session}` },
  }).catch(() => null);

  if (!r?.ok) return res.status(401).json({ error: "Invalid session" });

  const data = await r.json();
  (req as Request & { user: unknown }).user = data.user;
  next();
}
```

## Important notes

1. The session cookie is HttpOnly — you cannot read it in JavaScript. Always use server-side session validation.
2. Each app has a unique `appSlug`. Sign-in requests MUST include the correct `appSlug` for the app.
3. The portal app slug is "portal" — never sign in to the portal app from your application.
4. API calls to `/api/v1/portal/*` require a portal admin session. Use API keys for programmatic access.
5. TAJMAC Auth does NOT use JWTs for sessions — it uses 256-bit random tokens stored in the database.