npm package: @nya-account/node-sdk
Description: Official Node.js SDK for Nya Account SSO — OAuth 2.1 / OIDC client with PKCE, JWT verification, and Express middleware
Author: Neko Along
Latest version: 2.0.3

# @nya-account/node-sdk

Official Node.js SDK for [Nya Account](https://account.lolinya.net) SSO system.

Provides a complete OAuth 2.1 / OIDC client with PKCE, JWT verification, and Express middleware.

## Installation

```bash
npm install @nya-account/node-sdk
# or
pnpm add @nya-account/node-sdk
# or
yarn add @nya-account/node-sdk
```

## Quick Start

```typescript
import { NyaAccountClient } from '@nya-account/node-sdk'

const client = new NyaAccountClient({
    // See https://account.lolinya.net/docs/developer/service-endpoints#integration-endpoints
    issuer: 'https://account-api.edge.lolinya.net',
    clientId: 'my-app',
    clientSecret: 'my-secret'
})

// Create authorization URL (with PKCE)
const { url, codeVerifier, state } = await client.createAuthorizationUrl({
    redirectUri: 'https://myapp.com/callback',
    scope: 'openid profile email'
})

// Exchange code for tokens
const tokens = await client.exchangeCode({
    code: callbackCode,
    redirectUri: 'https://myapp.com/callback',
    codeVerifier
})

// Get user info
const userInfo = await client.getUserInfo(tokens.accessToken)

// Revoke refresh token on logout
await client.revokeToken(tokens.refreshToken, { tokenTypeHint: 'refresh_token' })

// Build RP-initiated logout URL
const logoutUrl = await client.createEndSessionUrl({
    idTokenHint: tokens.idToken,
    postLogoutRedirectUri: 'https://myapp.com/logout/callback',
    state: 'logout-csrf-state'
})
```

## Express Middleware

```typescript
import express from 'express'
import { NyaAccountClient } from '@nya-account/node-sdk'
import { getAuth } from '@nya-account/node-sdk/express'

const app = express()
const client = new NyaAccountClient({
    issuer: 'https://account-api.edge.lolinya.net',
    clientId: 'my-app',
    clientSecret: 'my-secret'
})

// Protect all /api routes
app.use('/api', client.authenticate())

app.get('/api/me', (req, res) => {
    const auth = getAuth(req)
    res.json({ userId: auth?.sub, scopes: auth?.scope })
})

// Require specific scopes
app.get(
    '/api/profile',
    client.authenticate(),
    client.requireScopes('profile'),
    (req, res) => {
        const auth = getAuth(req)
        res.json({ name: auth?.sub })
    }
)

// Use introspection for sensitive operations
app.post('/api/sensitive', client.authenticate({ strategy: 'introspection' }), handler)
```

## Configuration

| Option              | Type             | Default                                  | Description                                                                                                                                                        |
| ------------------- | ---------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `issuer`            | `string`         | `'https://account-api.edge.lolinya.net'` | SSO service URL (Issuer URL). See [Service Endpoints](https://account.lolinya.net/docs/developer/service-endpoints#integration-endpoints) for available endpoints. |
| `clientId`          | `string`         | _required_                               | OAuth client ID                                                                                                                                                    |
| `clientSecret`      | `string`         | _required_                               | OAuth client secret                                                                                                                                                |
| `timeout`           | `number`         | `10000`                                  | HTTP request timeout in milliseconds                                                                                                                               |
| `discoveryCacheTtl` | `number`         | `3600000`                                | Discovery document cache TTL in milliseconds (default: 1 hour)                                                                                                     |
| `endpoints`         | `EndpointConfig` | —                                        | Explicitly specify endpoint URLs (auto-discovered via OIDC Discovery if omitted)                                                                                   |

## API Reference

### `NyaAccountClient`

#### Authorization

- **`createAuthorizationUrl(options)`** — Create an OAuth authorization URL with PKCE
- **`pushAuthorizationRequest(options)`** — Push authorization request to PAR endpoint (RFC 9126)
- **`createAuthorizationUrlWithPar(options)`** — Create authorization URL using PAR `request_uri`

#### Token Operations

- **`exchangeCode(options)`** — Exchange an authorization code for tokens
- **`refreshToken(refreshToken)`** — Refresh an Access Token
- **`revokeToken(token, options?)`** — Revoke a token (RFC 7009)
- **`introspectToken(token, options?)`** — Token introspection (RFC 7662)

#### User Info

- **`getUserInfo(accessToken)`** — Get user info via OIDC UserInfo endpoint

#### JWT Verification

- **`verifyAccessToken(token, options?)`** — Locally verify a JWT Access Token (RFC 9068)
- **`verifyIdToken(token, options?)`** — Locally verify an OIDC ID Token

#### Express Middleware

- **`authenticate(options?)`** — Middleware to verify Bearer Token (`local` or `introspection` strategy)
- **`requireScopes(...scopes)`** — Middleware to validate token scopes

#### Cache

- **`discover()`** — Fetch OIDC Discovery document (cached with TTL)
- **`clearCache()`** — Clear Discovery and JWT verifier cache

#### OIDC Logout

- **`createEndSessionUrl(options?)`** — Create OIDC RP-initiated logout URL (`end_session_endpoint`)

### Express Helpers

Available from `@nya-account/node-sdk/express`:

- **`getAuth(req)`** — Retrieve the verified Access Token payload from a request
- **`extractBearerToken(req)`** — Extract Bearer token from the Authorization header
- **`sendOAuthError(res, statusCode, error, errorDescription)`** — Send an OAuth-standard error response

### PKCE Utilities

- **`generatePkce()`** — Generate a code_verifier and code_challenge pair
- **`generateCodeVerifier()`** — Generate a PKCE code_verifier
- **`generateCodeChallenge(codeVerifier)`** — Generate an S256 code_challenge

## Error Handling

The SDK provides typed error classes:

```typescript
import {
    NyaAccountError, // Base error class
    OAuthError, // OAuth protocol errors from the server
    TokenVerificationError, // JWT verification failures
    DiscoveryError // OIDC Discovery failures
} from '@nya-account/node-sdk'

try {
    await client.verifyAccessToken(token)
} catch (error) {
    if (error instanceof TokenVerificationError) {
        console.log(error.code) // 'token_verification_failed'
        console.log(error.description) // Human-readable description
    }
}
```

## Requirements

- Node.js >= 20.0.0
- Express 4.x or 5.x (optional, for middleware features)

## License

[MIT](./LICENSE)