mirror of
https://github.com/aljazceru/khatru.git
synced 2026-02-01 19:24:19 +01:00
86 lines
3.3 KiB
Markdown
86 lines
3.3 KiB
Markdown
---
|
|
outline: deep
|
|
---
|
|
|
|
# NIP-42 `AUTH`
|
|
|
|
`khatru` supports [NIP-42](https://nips.nostr.com/42) out of the box. The functionality is exposed in the following ways.
|
|
|
|
## Sending arbitrary `AUTH` challenges
|
|
|
|
At any time you can send an `AUTH` message to a client that is making a request.
|
|
|
|
It makes sense to give the user the option to authenticate right after they establish a connection, for example, when you have a relay that works differently depending on whether the user is authenticated or not.
|
|
|
|
```go
|
|
relay := khatru.NewRelay()
|
|
|
|
relay.OnConnect = append(relay.OnConnect, func(ctx context.Context) {
|
|
khatru.RequestAuth(ctx)
|
|
})
|
|
```
|
|
|
|
This will send a NIP-42 `AUTH` challenge message to the client so it will have the option to authenticate itself whenever it wants to.
|
|
|
|
## Signaling to the client that a specific query requires an authenticated user
|
|
|
|
If on `RejectFilter` or `RejectEvent` you prefix the message with `auth-required: `, that will automatically send an `AUTH` message before a `CLOSED` or `OK` with that prefix, such that the client will immediately be able to know it must authenticate to proceed and will already have the challenge required for that, so they can immediately replay the request.
|
|
|
|
```go
|
|
relay.RejectFilter = append(relay.RejectFilter, func(ctx context.Context, filter nostr.Filter) (bool, string) {
|
|
return true, "auth-required: this query requires you to be authenticated"
|
|
})
|
|
relay.RejectEvent = append(relay.RejectEvent, func(ctx context.Context, event *nostr.Event) (bool, string) {
|
|
return true, "auth-required: publishing this event requires authentication"
|
|
})
|
|
```
|
|
|
|
## Reading the auth status of a client
|
|
|
|
After a client is authenticated and opens a new subscription with `REQ` or sends a new event with `EVENT`, you'll be able to read the public key they're authenticated with.
|
|
|
|
```go
|
|
relay.RejectFilter = append(relay.RejectFilter, func(ctx context.Context, filter nostr.Filter) (bool, string) {
|
|
authenticatedUser := khatru.GetAuthed(ctx)
|
|
})
|
|
```
|
|
|
|
## Telling an authenticated user they're still not allowed to do something
|
|
|
|
If the user is authenticated but still not allowed (because some specific filters or events are only accessible to some specific users) you can reply on `RejectFilter` or `RejectEvent` with a message prefixed with `"restricted: "` to make that clear to clients.
|
|
|
|
```go
|
|
relay.RejectFilter = append(relay.RejectFilter, func(ctx context.Context, filter nostr.Filter) (bool, string) {
|
|
authenticatedUser := khatru.GetAuthed(ctx)
|
|
|
|
if slices.Contain(authorizedUsers, authenticatedUser) {
|
|
return false
|
|
} else {
|
|
return true, "restricted: you're not a member of the privileged group that can read that stuff"
|
|
}
|
|
})
|
|
```
|
|
|
|
## Reacting to a successful authentication
|
|
|
|
Each `khatru.WebSocket` object has an `.Authed` channel that is closed whenever that connection performs a successful authentication.
|
|
|
|
You can use that to emulate a listener for these events in case you want to keep track of who is authenticating in real time and not only check it when they request for something.
|
|
|
|
```go
|
|
relay.OnConnect = append(relay.OnConnect,
|
|
khatru.RequestAuth,
|
|
func(ctx context.Context) {
|
|
go func(ctx context.Context) {
|
|
conn := khatru.GetConnection(ctx)
|
|
select {
|
|
case <-ctx.Done():
|
|
fmt.Println("connection closed")
|
|
case <-conn.Authed:
|
|
fmt.Println("authenticated as", conn.AuthedPublicKey)
|
|
}
|
|
}(ctx)
|
|
},
|
|
)
|
|
```
|