Skip to content

Setting up the Relay Management API

NIP-86 specifies a set of RPC methods for managing the boring aspects of relays, such as whitelisting or banning users, banning individual events, banning IPs and so on.

All khatru.Relay instances expose a field ManagementAPI with a RelayManagementAPI instance inside, which can be used for creating handlers for each of the RPC methods.

There is also a generic RejectAPICall which is a slice of functions that will be called before any RPC method, if they exist and, if any of them returns true, the request will be rejected.

The most basic implementation of a RejectAPICall handler would be one that checks the public key of the caller with a hardcoded public key of the relay owner:

go
var owner = "<my-own-pubkey>"
var allowedPubkeys = make([]string, 0, 10)

func main () {
	relay := khatru.NewRelay()

	relay.ManagementAPI.RejectAPICall = append(relay.ManagementAPI.RejectAPICall,
		func(ctx context.Context, mp nip86.MethodParams) (reject bool, msg string) {
			user := khatru.GetAuthed(ctx)
			if user != owner {
				return true, "go away, intruder"
			}
			return false, ""
		}
	)

	relay.ManagementAPI.AllowPubKey = func(ctx context.Context, pubkey string, reason string) error {
		allowedPubkeys = append(allowedPubkeys, pubkey)
		return nil
	}
	relay.ManagementAPI.BanPubKey = func(ctx context.Context, pubkey string, reason string) error {
		idx := slices.Index(allowedPubkeys, pubkey)
		if idx == -1 {
			return fmt.Errorf("pubkey already not allowed")
		}
		allowedPubkeys = slices.Delete(allowedPubkeys, idx, idx+1)
	}
}

You can also not provide any RejectAPICall handler and do the approval specifically on each RPC handler.

In the following example any current member can include any other pubkey, and anyone who was added before is able to remove any pubkey that was added afterwards (not a very good idea, but serves as an example).

go
var allowedPubkeys = []string{"<my-own-pubkey>"}

func main () {
	relay := khatru.NewRelay()

	relay.ManagementAPI.AllowPubKey = func(ctx context.Context, pubkey string, reason string) error {
		caller := khatru.GetAuthed(ctx)

		if slices.Contains(allowedPubkeys, caller) {
			allowedPubkeys = append(allowedPubkeys, pubkey)
			return nil
		}

		return fmt.Errorf("you're not authorized")
	}
	relay.ManagementAPI.BanPubKey = func(ctx context.Context, pubkey string, reason string) error {
		caller := khatru.GetAuthed(ctx)

		callerIdx := slices.Index(allowedPubkeys, caller)
		if callerIdx == -1 {
			return fmt.Errorf("you're not even allowed here")
		}

		targetIdx := slices.Index(allowedPubkeys, pubkey)
		if targetIdx < callerIdx {
			// target is a bigger OG than the caller, so it has bigger influence and can't be removed
			return fmt.Errorf("you're less powerful than the pubkey you're trying to remove")
		}

		// allow deletion since the target came after the caller
		allowedPubkeys = slices.Delete(allowedPubkeys, targetIdx, targetIdx+1)
		return nil
	}
}
~