Skip to content

Commit

Permalink
feat: wot (#363)
Browse files Browse the repository at this point in the history
  • Loading branch information
CodyTseng authored Sep 7, 2024
1 parent 5f86bb5 commit b640e71
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 7 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ If you'd like to help me test the reliability of this relay implementation, you

## Extra Features

### WoT (Web of Trust)

If you want to enable the WoT feature, you need to set the following environment variables:

- `WOT_TRUST_ANCHOR_PUBKEY`: The public key of the trust anchor. Trust anchor is the root of the trust net.
- `WOT_TRUST_DEPTH`: The depth of the trust net. If the trust depth is 1, the trust net will include the trust anchor and the trust anchor's following users. If the trust depth is 2, the trust net will include the trust anchor, the trust anchor's following users, and the trust anchor's following users' following users. Now the maximum trust depth is 2.
- `WOT_FETCH_FOLLOW_LIST_FROM`: Comma-separated list of relay URLs to fetch follow list from (e.g., WOT_FETCH_FOLLOW_LIST_FROM=wss://nostr-relay.app,wss://relay.damus.io). This environment variable is optional. The relay will always fetch the follow list from the local database first.

### RESTful API

You can see the API documentation at `/api` endpoint.
Expand Down
17 changes: 16 additions & 1 deletion example.env
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,19 @@ DATABASE_MAX_CONNECTIONS=50
# TOP_MESSAGE_HANDLING_ENABLED=true

# Optional: Enable/disable auth message handling (default: true)
# AUTH_MESSAGE_HANDLING_ENABLED=true
# AUTH_MESSAGE_HANDLING_ENABLED=true

# Optional: The anchor public key for the Web of Trust. If set, the relay will enable WoT (default: )
WOT_TRUST_ANCHOR_PUBKEY=

# Optional: The depth of the Web of Trust. 1 means trust anchor and anchor's following, 2 means trust anchor, anchor's following, and anchor's following's following (default: 0)
WOT_TRUST_DEPTH=

# Optional: The refresh interval for the Web of Trust in milliseconds (default: 3600000 = 1 hour)
WOT_REFRESH_INTERVAL=

# Optional: Comma-separated list of relay URLs to fetch follow list from (default: )
WOT_FETCH_FOLLOW_LIST_FROM=

# Optional: JSON string of filters to skip WoT Guard (default: )
WOT_SKIP_FILTERS=
18 changes: 15 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@nostr-relay/common": "^0.0.28",
"@nostr-relay/core": "^0.0.28",
"@nostr-relay/validator": "^0.0.28",
"@nostr-relay/wot-guard": "^0.0.2",
"another-nestjs-ws-adapter": "^10.1.0",
"hbs": "^4.2.0",
"helmet": "^7.1.0",
Expand Down Expand Up @@ -107,4 +108,4 @@
"testEnvironment": "node",
"maxWorkers": 1
}
}
}
10 changes: 10 additions & 0 deletions src/config/config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ describe('config', () => {
CLOSE_MESSAGE_HANDLING_ENABLED: 'false',
TOP_MESSAGE_HANDLING_ENABLED: 'false',
AUTH_MESSAGE_HANDLING_ENABLED: 'false',
WOT_TRUST_ANCHOR_PUBKEY:
'8125b911ed0e94dbe3008a0be48cfe5cd0c0b05923cfff917ae7e87da8400883',
WOT_TRUST_DEPTH: '2',
WOT_FETCH_FOLLOW_LIST_FROM: 'wss://relay.damus.io,wss://nos.lol',
WOT_SKIP_FILTERS: '[{"kinds":[2333]}]',
}),
).toEqual({
HOSTNAME: 'localhost',
Expand Down Expand Up @@ -69,6 +74,11 @@ describe('config', () => {
CLOSE_MESSAGE_HANDLING_ENABLED: false,
TOP_MESSAGE_HANDLING_ENABLED: false,
AUTH_MESSAGE_HANDLING_ENABLED: false,
WOT_TRUST_ANCHOR_PUBKEY:
'8125b911ed0e94dbe3008a0be48cfe5cd0c0b05923cfff917ae7e87da8400883',
WOT_TRUST_DEPTH: 2,
WOT_FETCH_FOLLOW_LIST_FROM: ['wss://relay.damus.io', 'wss://nos.lol'],
WOT_SKIP_FILTERS: [{ kinds: [2333] }],
});
});
});
2 changes: 2 additions & 0 deletions src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { meiliSearchConfig } from './meili-search.config';
import { messageHandlingConfig } from './message-handling.config';
import { relayInfoConfig } from './relay-info.config';
import { throttlerConfig } from './throttler.config';
import { wotConfig } from './wot.config';

export function config() {
const env = validateEnvironment(process.env);
Expand All @@ -22,6 +23,7 @@ export function config() {
throttler: throttlerConfig(env),
cache: cacheConfig(env),
messageHandling: messageHandlingConfig(env),
wot: wotConfig(env),
};
}
export type Config = ReturnType<typeof config>;
24 changes: 24 additions & 0 deletions src/config/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,30 @@ export const EnvironmentSchema = z.object({
AUTH_MESSAGE_HANDLING_ENABLED: z
.preprocess((enabled: string) => enabled === 'true', z.boolean())
.optional(),

WOT_TRUST_ANCHOR_PUBKEY: z
.string()
.regex(/^[0-9a-f]{64}$/)
.optional(),
WOT_TRUST_DEPTH: z
.preprocess(
(depth: string) => parseInt(depth),
z.number().positive().int().max(2),
)
.optional(),
WOT_REFRESH_INTERVAL: z.number().positive().int().optional(),
WOT_FETCH_FOLLOW_LIST_FROM: z
.preprocess(
(str: string) => str.split(',').map((item) => item.trim()),
z.array(z.string().regex(/^wss?:\/\/.+/)),
)
.optional(),
WOT_SKIP_FILTERS: z
.preprocess(
(str: string) => JSON.parse(str),
z.array(z.any()), // TODO: add filter schema
)
.optional(),
});
export type Environment = z.infer<typeof EnvironmentSchema>;

Expand Down
11 changes: 11 additions & 0 deletions src/config/wot.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Environment } from './environment';

export function wotConfig(env: Environment) {
return {
trustAnchorPubkey: env.WOT_TRUST_ANCHOR_PUBKEY,
trustDepth: env.WOT_TRUST_DEPTH ?? 0,
refreshInterval: env.WOT_REFRESH_INTERVAL,
fetchFollowListFrom: env.WOT_FETCH_FOLLOW_LIST_FROM,
skipFilters: env.WOT_SKIP_FILTERS,
};
}
24 changes: 22 additions & 2 deletions src/nostr/services/nostr-relay.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Injectable } from '@nestjs/common';
import { Injectable, OnApplicationBootstrap } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Event, Filter } from '@nostr-relay/common';
import { createOutgoingNoticeMessage, NostrRelay } from '@nostr-relay/core';
import { Validator } from '@nostr-relay/validator';
import { WotGuard } from '@nostr-relay/wot-guard';
import { InjectPinoLogger, PinoLogger } from 'nestjs-pino';
import { Config } from 'src/config';
import { MessageHandlingConfig } from 'src/config/message-handling.config';
Expand All @@ -14,10 +15,11 @@ import { MetricService } from './metric.service';
import { NostrRelayLogger } from './nostr-relay-logger.service';

@Injectable()
export class NostrRelayService {
export class NostrRelayService implements OnApplicationBootstrap {
private readonly relay: NostrRelay;
private readonly messageHandlingConfig: MessageHandlingConfig;
private readonly validator: Validator;
private readonly wotGuardPlugin: WotGuard;

constructor(
@InjectPinoLogger(NostrRelayService.name)
Expand All @@ -31,6 +33,7 @@ export class NostrRelayService {
const hostname = configService.get('hostname');
const limitConfig = configService.get('limit', { infer: true });
const cacheConfig = configService.get('cache', { infer: true });
const wotConfig = configService.get('wot', { infer: true });
this.messageHandlingConfig = configService.get('messageHandling', {
infer: true,
});
Expand All @@ -42,6 +45,23 @@ export class NostrRelayService {
});
this.validator = new Validator();
this.relay.register(accessControlPlugin);

if (wotConfig.trustAnchorPubkey) {
this.wotGuardPlugin = new WotGuard({
trustAnchorPubkey: wotConfig.trustAnchorPubkey,
trustDepth: wotConfig.trustDepth,
refreshInterval: wotConfig.refreshInterval,
relayUrls: wotConfig.fetchFollowListFrom,
skipFilters: wotConfig.skipFilters,
logger: nostrRelayLogger,
eventRepository,
});
this.relay.register(this.wotGuardPlugin);
}
}

async onApplicationBootstrap() {
await this.wotGuardPlugin?.init();
}

handleConnection(client: WebSocket) {
Expand Down

0 comments on commit b640e71

Please sign in to comment.