npm package: @mntu/nestjs-matrix-federation
Description: A NestJS module for Matrix Federation API authentication (sign and verify requests using homeserver signing keys).
Author: mntu
Latest version: 1.0.1

# nestjs-matrix-federation

A NestJS module for **Matrix Federation API authentication**.  
It provides helpers to **sign outbound federation requests** and **verify inbound signatures** using Matrix homeserver signing keys.

## ✨ Features

- Load **homeserver signing key** (`homeserver.signing.key`) from file
- Generate **public/private key pairs** for federation
- `signRequest` → sign outbound federation requests with Ed25519
- `verifySignature` → validate inbound requests against the origin’s published keys
- `MatrixFederationGuard` for inbound verification

## 📦 Installation

```bash
npm install nestjs-matrix-federation
```

## ⚙️ Setup

### 1. Prepare your signing key

The signing key file must be in the same format as Synapse:

```
ed25519 <key_id> <base64_seed>
```

### 2. Import the module

```ts
import { Module } from '@nestjs/common';
import { MatrixFederationModule } from 'nestjs-matrix-federation';

@Module({
  imports: [
    MatrixFederationModule.forRoot({
      serverName: 'my.example.com',
      signingKeyPath: '/data/homeserver.signing.key',
      dns: {
        override: {
          homeserver: [
            { name: 'my.example.com', value: 'https://override.example.com' }
          ],
        },
      },
    }),
  ],
})
export class AppModule {}
```

## 🔑 Usage

### Sign an outbound request

```ts
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { MatrixFederationService } from 'nestjs-matrix-federation';

@Injectable()
export class ExampleService {
  constructor(
    private readonly http: HttpService,
    private readonly matrix: MatrixFederationService,
  ) {}

  async send() {
    const { auth } = this.matrix.signRequest({
      method: 'PUT',
      uri: '/_matrix/federation/v1/send/12345',
      destination: 'remote.example.com',
      body: { hello: 'world' },
    });

    await this.http.put(
      'https://remote.example.com/_matrix/federation/v1/send/12345',
      { hello: 'world' },
      { headers: { Authorization: auth } },
    ).toPromise();
  }
}
```

### Verify Inbound Request (Manual)

When another homeserver calls your federation API (e.g. `/_matrix/federation/v1/send/txn`),
you need to verify the Authorization header before processing.

```ts
import { Controller, Post, Req, Res } from '@nestjs/common';
import { MatrixFederationService } from 'nestjs-matrix-federation';
import { Request, Response } from 'express';

@Controller('_matrix/federation/v1')
export class FederationController {
  constructor(private readonly federation: MatrixFederationService) {}

  @Post('send/:txnId')
  async handleTxn(@Req() req: Request, @Res() res: Response) {
    const authHeader = req.headers['authorization'] as string;

    // Example header:
    // X-Matrix origin=remote.example.org,key="ed25519:a_Kvlj",sig="AbCdEf..."

    const match = authHeader.match(/origin=(.*?),key="(.*?)",sig="(.*?)"/);
    if (!match) {
      return res.status(401).json({ error: 'Missing or invalid auth header' });
    }

    const [, origin, keyId, sigBase64] = match;

    const ok = await this.federation.verifySignature({
      origin,
      keyId,
      sigBase64,
      method: req.method,
      uri: req.originalUrl,
      destination: 'my.example.com',
      body: req.body,
    });

    if (!ok) {
      return res.status(403).json({ error: 'Signature verification failed' });
    }

    // ✅ Request verified, handle transaction
    return res.json({ ok: true });
  }
}
```

###  Verify Inbound Request (Guard)

Use the provided `MatrixFederationGuard` to protect federation endpoints.

```ts
import { Controller, Post, UseGuards, Req } from '@nestjs/common';
import { MatrixFederationGuard } from 'nestjs-matrix-federation';
import { Request } from 'express';

@Controller('_matrix/federation/v1')
export class FederationController {
  @UseGuards(MatrixFederationGuard)
  @Post('send/:txnId')
  async handleTxn(@Req() req: Request) {
    // If you reach here the signature is valid
    return { ok: true };
  }
}
```

## 🧪 Testing

Run unit tests:

```bash
npm test
```

Unit tests cover:
- Key loader
- Signing requests
- Verifying signatures
- Middleware behavior

## 📜 License

MIT