# `@sonordev/site-kit/admin-auth` — Sign in with Sonor

Put a site's own admin area behind a Sonor login. Whoever can open the
project in Sonor (its owners and members, the managing agency, platform
admins) can sign in; nobody else can. There are no passwords on the site.

## Wiring

```ts
// lib/sonor-sso.ts
import { createSonorSso } from '@sonordev/site-kit/admin-auth'

export const sso = createSonorSso({
  paths: '/admin',            // or ['/admin', '/staff'] for several areas
  // loginPath: '/admin/login' (default: `${paths[0]}/login`)
  // adminEmails: process.env.ADMIN_EMAILS,
})
```

```ts
// proxy.ts
import { createProxy } from '@sonordev/site-kit/proxy'
import { sso } from '@/lib/sonor-sso'

export default createProxy({ before: sso.gate, redirects: true, securityHeaders: true })
```

```ts
// app/api/auth/callback/route.ts
import { sso } from '@/lib/sonor-sso'
export const GET = sso.handleCallback
```

```ts
// app/api/auth/logout/route.ts
import { sso } from '@/lib/sonor-sso'
export const GET = sso.handleLogout
export const POST = sso.handleLogout
```

```tsx
// app/admin/login/page.tsx (server component)
import { SSO_ERROR_MESSAGES } from '@sonordev/site-kit/admin-auth'
import { sso } from '@/lib/sonor-sso'

export default async function Login({ searchParams }) {
  const { error, return_to } = await searchParams
  let href: string | null = null
  try { href = await sso.getLoginUrl({ returnTo: return_to }) } catch {}
  return (
    <>
      {error && <p>{SSO_ERROR_MESSAGES[error] ?? error}</p>}
      {href ? <a href={href}>Sign in with Sonor</a> : <p>Sign-in is unavailable.</p>}
    </>
  )
}
```

```ts
// Every API route and server action that touches admin data
const session = await sso.getSession()
if (!session) return new Response('Unauthorized', { status: 401 })
```

JSON endpoints can be guarded in the middleware too: pass
`apiPaths: ['/api/admin']` and include them in the matcher. They get a
401 JSON response instead of a redirect.

## Env

| Var                    |                                                                         |
| ---------------------- | ----------------------------------------------------------------------- |
| `SONOR_API_KEY`        | Already on every site. Identifies the project and verifies tokens.      |
| `SONOR_SESSION_SECRET` | 32+ random chars (`openssl rand -hex 32`). Rotate to sign everyone out. |
| `NEXT_PUBLIC_SITE_URL` | The site's public origin, for the callback URL.                         |

The callback route has to be reachable at `callbackPath` (default
`/api/auth/callback`) on `NEXT_PUBLIC_SITE_URL`.

## Rules

- **The gate doesn't cover `/api/*`.** The standard middleware matcher
  excludes it. Check `getSession()` in every API route.
- **Don't trust identity from headers, query strings or bodies.**
  `getSession()` reads the httpOnly cookie and verifies its signature on
  every call; that's the only source.
- **Moving a site onto this module:** pass its existing `cookieName` and
  `secretEnv` so current sessions stay valid.
