npm package: @insyde/auth-sdk
Description: Authentication SDK for insyde.one consumer apps
Latest version: 2.2.0

# @insyde/auth-sdk

Authentication and authorization middleware for Express apps consuming JWTs issued by [auth.insyde.one](https://auth.insyde.one).

## Install

```bash
npm install @insyde/auth-sdk
```

Peer dependencies: `express` (>=4), `jsonwebtoken` (>=9).

## Usage

### authenticate

Validates the JWT from the `Authorization: Bearer <token>` header, verifies `aud` (audience) and `iss` (issuer), and populates `req.auth`.

```typescript
import { authenticate } from '@insyde/auth-sdk';

app.use(authenticate({
  jwtSecret: process.env.JWT_SECRET!,
  audience: 'stage.insyde.one',
  // issuer: 'auth.insyde.one', // optional, defaults to 'auth.insyde.one'
}));
```

#### Options

| Option | Type | Required | Default | Description |
|--------|------|----------|---------|-------------|
| `jwtSecret` | `string` | Yes | — | Shared HS256 secret (same as auth-service) |
| `audience` | `string` | Yes | — | Expected `aud` claim (your product's audience) |
| `issuer` | `string` | No | `'auth.insyde.one'` | Expected `iss` claim |

After authentication, `req.auth` contains:

```typescript
interface AuthPayload {
  userId: string;
  tenantId: string;
  productId: string;
  roles: string[];
  permissions: string[];
  authzRev: number;
  globalRole?: 'super_admin';
}
```

### requirePermission

Checks that the authenticated user has **all** of the specified permissions. Supports hierarchy — `post.edit.any` satisfies a check for `post.edit`.

```typescript
import { requirePermission } from '@insyde/auth-sdk';

router.put('/posts/:id', requirePermission('post.edit'), handler);
router.delete('/posts/:id', requirePermission('post.delete', 'post.edit'), handler);
```

`super_admin` users bypass all permission checks.

### requirePermissionWithOwnership

Checks `<resource>.<action>.any` first, then falls back to `<resource>.<action>.own` by comparing the resource owner with `req.auth.userId`.

```typescript
import { requirePermissionWithOwnership } from '@insyde/auth-sdk';

router.put('/posts/:id',
  requirePermissionWithOwnership('post', 'edit', async (req) => {
    const post = await Post.findById(req.params.id);
    return post?.authorId?.toString();
  }),
  handler,
);
```

## i18n / Translation Keys

Since v2.0.0, all error responses use **translation keys** instead of hardcoded strings. Your app is responsible for mapping these keys to user-facing messages (e.g., via i18n middleware).

| Key | HTTP Status | Description |
|-----|-------------|-------------|
| `auth.token.not_provided` | 401 | Missing or invalid `Authorization` header |
| `auth.token.invalid_or_expired` | 401 | JWT verification failed (expired, bad signature, wrong audience/issuer) |
| `auth.permission.insufficient` | 403 | User lacks required permission(s) |

### Example: mapping keys to messages

```typescript
// i18n middleware (after auth SDK middlewares)
const messages: Record<string, Record<string, string>> = {
  'pt-BR': {
    'auth.token.not_provided': 'Token não fornecido.',
    'auth.token.invalid_or_expired': 'Token inválido ou expirado.',
    'auth.permission.insufficient': 'Permissão insuficiente.',
  },
  en: {
    'auth.token.not_provided': 'Token not provided.',
    'auth.token.invalid_or_expired': 'Invalid or expired token.',
    'auth.permission.insufficient': 'Insufficient permission.',
  },
};
```

### Migrating from v1.x

In v1.x, error responses contained Portuguese strings:

```diff
- { "message": "Token não fornecido." }
+ { "message": "auth.token.not_provided" }

- { "message": "Token inválido ou expirado." }
+ { "message": "auth.token.invalid_or_expired" }

- { "message": "Não autenticado." }
+ { "message": "auth.token.not_provided" }

- { "message": "Permissão insuficiente." }
+ { "message": "auth.permission.insufficient" }
```

If your frontend checks `response.data.message` against the old Portuguese strings, update those checks to use the new keys.

## License

Private — insyde.one internal use only.