Skip to content

Commit

Permalink
Add AUTH relay request and response
Browse files Browse the repository at this point in the history
  • Loading branch information
tyiu committed May 2, 2024
1 parent 594a242 commit 5f5770a
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 2 deletions.
11 changes: 10 additions & 1 deletion Sources/NostrSDK/EventKind.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,13 @@ public enum EventKind: RawRepresentable, CaseIterable, Codable, Equatable, Hasha
///
/// See [NIP-51](https://github.com/nostr-protocol/nips/blob/master/51.md#standard-lists)
case bookmarksList


/// This kind of event provides a way for clients to authenticate to relays by signing an ephemeral event.
/// This kind is not meant to be published or queried.
///
/// See [NIP-42](https://github.com/nostr-protocol/nips/blob/master/42.md).
case authentication

/// This kind of event is for long-form texxt content, generally referred to as "articles" or "blog posts".
///
/// See [NIP-23](https://github.com/nostr-protocol/nips/blob/master/23.md).
Expand Down Expand Up @@ -112,6 +118,7 @@ public enum EventKind: RawRepresentable, CaseIterable, Codable, Equatable, Hasha
.report,
.muteList,
.bookmarksList,
.authentication,
.longformContent,
.dateBasedCalendarEvent,
.timeBasedCalendarEvent,
Expand Down Expand Up @@ -141,6 +148,7 @@ public enum EventKind: RawRepresentable, CaseIterable, Codable, Equatable, Hasha
case .report: return 1984
case .muteList: return 10000
case .bookmarksList: return 10003
case .authentication: return 22242
case .longformContent: return 30023
case .dateBasedCalendarEvent: return 31922
case .timeBasedCalendarEvent: return 31923
Expand All @@ -164,6 +172,7 @@ public enum EventKind: RawRepresentable, CaseIterable, Codable, Equatable, Hasha
case .report: return ReportEvent.self
case .muteList: return MuteListEvent.self
case .bookmarksList: return BookmarksListEvent.self
case .authentication: return AuthenticationEvent.self
case .longformContent: return LongformContentEvent.self
case .dateBasedCalendarEvent: return DateBasedCalendarEvent.self
case .timeBasedCalendarEvent: return TimeBasedCalendarEvent.self
Expand Down
55 changes: 55 additions & 0 deletions Sources/NostrSDK/Events/AuthenticationEvent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// AuthenticationEvent.swift
//
//
// Created by Terry Yiu on 5/1/24.
//

import Foundation

/// An event that provides a way for clients to authenticate to relays.
/// This kind is not meant to be published or queried.
///
/// See [NIP-42](https://github.com/nostr-protocol/nips/blob/master/42.md).
public final class AuthenticationEvent: NostrEvent, RelayProviding, RelayURLValidating {
public required init(from decoder: Decoder) throws {
try super.init(from: decoder)
}

@available(*, unavailable, message: "This initializer is unavailable for this class.")
override init(kind: EventKind, content: String, tags: [Tag] = [], createdAt: Int64 = Int64(Date.now.timeIntervalSince1970), signedBy keypair: Keypair) throws {
try super.init(kind: kind, content: content, tags: tags, createdAt: createdAt, signedBy: keypair)
}

init(content: String, tags: [Tag] = [], createdAt: Int64 = Int64(Date.now.timeIntervalSince1970), signedBy keypair: Keypair) throws {
try super.init(kind: .authentication, content: content, tags: tags, createdAt: createdAt, signedBy: keypair)
}

/// The relay URL where this event authenticates to it.
public var relayURL: URL? {
guard let relayURLString = firstValueForRawTagName("relay") else {
return nil
}

return try? validateRelayURLString(relayURLString)
}

/// The challenge string as received from the relay.
public var challenge: String? {
firstValueForRawTagName("challenge")
}
}

public extension EventCreating {

func authenticate(relayURL: URL, challenge: String, signedBy keypair: Keypair) throws -> AuthenticationEvent {
let validatedRelayURL = try RelayURLValidator.shared.validateRelayURL(relayURL)

let tags: [Tag] = [
Tag(name: "relay", value: validatedRelayURL.absoluteString),
Tag(name: "challenge", value: challenge)
]

return try AuthenticationEvent(content: "", tags: tags, signedBy: keypair)
}
}
3 changes: 3 additions & 0 deletions Sources/NostrSDK/RelayRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ enum RelayRequest {
case event(NostrEvent)
case request(subscriptionId: String, filter: Filter)
case close(subscriptionId: String)
case auth(AuthenticationEvent)
case count(subscriptionId: String, filter: Filter)

var encoded: String? {
Expand All @@ -22,6 +23,8 @@ enum RelayRequest {
payload = [AnyEncodable("REQ"), AnyEncodable(subscriptionId), AnyEncodable(filter)]
case .close(let subscriptionId):
payload = [AnyEncodable("CLOSE"), AnyEncodable(subscriptionId)]
case .auth(let event):
payload = [AnyEncodable("AUTH"), AnyEncodable(event)]
case .count(let subscriptionId, let filter):
payload = [AnyEncodable("COUNT"), AnyEncodable(subscriptionId), AnyEncodable(filter)]
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/NostrSDK/RelayResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ enum RelayResponse: Decodable {
case rateLimited = "rate-limited"
case invalid
case error
case authRequired = "auth-required"
case restricted
}

case event(subscriptionId: String, event: NostrEvent)
Expand Down
41 changes: 41 additions & 0 deletions Tests/NostrSDKTests/Events/AuthenticationEventTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// AuthenticationEventTests.swift
//
//
// Created by Terry Yiu on 5/2/24.
//

@testable import NostrSDK
import XCTest

final class AuthenticationEventTests: XCTestCase, EventCreating, EventVerifying, FixtureLoading {

func testCreateAuthenticationEvent() throws {
let relayURL = try XCTUnwrap(URL(string: "wss://relay.example.com/"))
let event = try authenticate(relayURL: relayURL, challenge: "some-challenge-string", signedBy: Keypair.test)

XCTAssertEqual(event.kind, .authentication)
XCTAssertEqual(event.relayURL, relayURL)
XCTAssertEqual(event.challenge, "some-challenge-string")

try verifyEvent(event)
}

func testDecodeAuthenticationEvent() throws {
let event: AuthenticationEvent = try decodeFixture(filename: "authentication_event")

XCTAssertEqual(event.id, "adb599bc2f6b4cf97d927de2cb36829326c86013e0a6e8f51159f80938a5c246")
XCTAssertEqual(event.pubkey, "9947f9659dd80c3682402b612f5447e28249997fb3709500c32a585eb0977340")
XCTAssertEqual(event.createdAt, 1714625219)
XCTAssertEqual(event.kind, .authentication)

let expectedTags: [Tag] = [
Tag(name: "relay", value: "wss://relay.example.com/"),
Tag(name: "challenge", value: "some-challenge-string")
]
XCTAssertEqual(event.tags, expectedTags)
XCTAssertEqual(event.content, "")
XCTAssertEqual(event.signature, "b27181e0b72872c463ac75ebf3ad2c2502696d81551bbae6b2a391c67614daf2e321eb5ab724b04520b26fcf7a4c9823fefdb47b10d66c088db44162ba9c1291")
}

}
1 change: 1 addition & 0 deletions Tests/NostrSDKTests/Fixtures/auth_request.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["AUTH",{"id":"adb599bc2f6b4cf97d927de2cb36829326c86013e0a6e8f51159f80938a5c246","pubkey":"9947f9659dd80c3682402b612f5447e28249997fb3709500c32a585eb0977340","created_at":1714625219,"kind":22242,"tags":[["relay","wss://relay.example.com/"],["challenge","some-challenge-string"]],"content":"","sig":"b27181e0b72872c463ac75ebf3ad2c2502696d81551bbae6b2a391c67614daf2e321eb5ab724b04520b26fcf7a4c9823fefdb47b10d66c088db44162ba9c1291"}]
12 changes: 12 additions & 0 deletions Tests/NostrSDKTests/Fixtures/authentication_event.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"id": "adb599bc2f6b4cf97d927de2cb36829326c86013e0a6e8f51159f80938a5c246",
"pubkey": "9947f9659dd80c3682402b612f5447e28249997fb3709500c32a585eb0977340",
"created_at": 1714625219,
"kind": 22242,
"tags": [
["relay", "wss://relay.example.com/"],
["challenge", "some-challenge-string"]
],
"content": "",
"sig": "b27181e0b72872c463ac75ebf3ad2c2502696d81551bbae6b2a391c67614daf2e321eb5ab724b04520b26fcf7a4c9823fefdb47b10d66c088db44162ba9c1291"
}
10 changes: 9 additions & 1 deletion Tests/NostrSDKTests/RelayRequestEncodingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
@testable import NostrSDK
import XCTest

final class RelayRequestEncodingTests: XCTestCase, FixtureLoading, JSONTesting {
final class RelayRequestEncodingTests: XCTestCase, EventCreating, FixtureLoading, JSONTesting {

func testEncodeEvent() throws {
let eventTag = Tag.event("93930d65435d49db723499335473920795e7f13c45600dcfad922135cf44bd63")
Expand Down Expand Up @@ -50,6 +50,14 @@ final class RelayRequestEncodingTests: XCTestCase, FixtureLoading, JSONTesting {
XCTAssertEqual(request.encoded, expected)
}

func testEncodeAuth() throws {
let authenticationEvent: AuthenticationEvent = try decodeFixture(filename: "authentication_event")
let request = try XCTUnwrap(RelayRequest.auth(authenticationEvent))
let expected = try loadFixtureString("auth_request")

XCTAssertTrue(areEquivalentJSONArrayStrings(request.encoded, expected))
}

func testEncodeCount() throws {
let filter = Filter(ids: nil,
authors: ["some-pubkey"],
Expand Down

0 comments on commit 5f5770a

Please sign in to comment.