You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
300 lines
13 KiB
300 lines
13 KiB
Feature: NIP-05 Mapping Nostr Keys to DNS-based Internet Identifiers |
|
As a Nostr client |
|
I want to map Nostr public keys to DNS-based internet identifiers |
|
So that users can be identified by human-readable email-like addresses |
|
|
|
Background: |
|
Given the newsroom application is running |
|
And I have a valid Nostr keypair |
|
|
|
# ===== Verification Flow ===== |
|
|
|
Scenario: Successful verification of NIP-05 identifier |
|
Given I see a kind 0 event with pubkey "b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9" |
|
And the event content includes nip05 identifier "bob@example.com" |
|
When I split the identifier into local part "bob" and domain "example.com" |
|
And I make a GET request to "https://example.com/.well-known/nostr.json?name=bob" |
|
And the response contains: |
|
"""json |
|
{ |
|
"names": { |
|
"bob": "b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9" |
|
} |
|
} |
|
""" |
|
Then the pubkey should match the one in the names mapping |
|
And the NIP-05 identifier should be marked as valid |
|
And the identifier should be displayed to the user |
|
|
|
Scenario: Verification with relay information |
|
Given I see a kind 0 event with pubkey "b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9" |
|
And the event content includes nip05 identifier "bob@example.com" |
|
When I make a GET request to "https://example.com/.well-known/nostr.json?name=bob" |
|
And the response contains relay information: |
|
"""json |
|
{ |
|
"names": { |
|
"bob": "b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9" |
|
}, |
|
"relays": { |
|
"b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9": [ |
|
"wss://relay.example.com", |
|
"wss://relay2.example.com" |
|
] |
|
} |
|
} |
|
""" |
|
Then the pubkey should match the one in the names mapping |
|
And the client should learn the user's preferred relays |
|
And the relay list should be saved for future connections |
|
|
|
# ===== User Discovery ===== |
|
|
|
Scenario: Finding a user by NIP-05 identifier |
|
Given a user wants to find "bob@example.com" |
|
When I fetch "https://example.com/.well-known/nostr.json?name=bob" |
|
And the response contains pubkey "b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9" |
|
Then I should fetch the kind 0 event for that pubkey |
|
And verify it has a matching nip05 field "bob@example.com" |
|
And suggest the user profile to the searcher |
|
|
|
Scenario: User search box implementation |
|
Given a search box is available in the client |
|
When a user types "bob@example.com" |
|
And the client recognizes the email-like format |
|
Then the client should perform NIP-05 lookup |
|
And retrieve the associated pubkey |
|
And display the user profile in search results |
|
|
|
# ===== Validation Rules ===== |
|
|
|
Scenario: Valid local-part characters |
|
Given I have identifiers with various local parts |
|
When I validate "<identifier>" |
|
Then it should be "<valid_or_invalid>" |
|
|
|
Examples: |
|
| identifier | valid_or_invalid | |
|
| bob@example.com | valid | |
|
| alice_123@example.com | valid | |
|
| user-name@example.com | valid | |
|
| test.user@example.com | valid | |
|
| User@Example.com | valid | |
|
| bob+tag@example.com | invalid | |
|
| bob space@example.com | invalid | |
|
| bob#hash@example.com | invalid | |
|
|
|
Scenario: Case-insensitive local-part matching |
|
Given I have identifier "Bob@Example.com" |
|
When I make a request to the well-known endpoint |
|
Then the query should be normalized to lowercase |
|
And "bob" should match "Bob" in the response |
|
|
|
# ===== Public Key Format ===== |
|
|
|
Scenario: Public keys must be in hex format |
|
Given I fetch "https://example.com/.well-known/nostr.json?name=bob" |
|
When the response contains pubkey "b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9" |
|
Then the pubkey should be in hex format |
|
And the pubkey should NOT be in npub format |
|
And the client should accept it for verification |
|
|
|
Scenario: Rejecting npub format in well-known response |
|
Given I fetch "https://example.com/.well-known/nostr.json?name=bob" |
|
When the response contains pubkey starting with "npub1" |
|
Then the client should reject the identifier as invalid |
|
And display an error about incorrect key format |
|
|
|
# ===== Root Domain Identifier ===== |
|
|
|
Scenario: Displaying root identifier without redundancy |
|
Given I see a kind 0 event with nip05 identifier "_@bob.com" |
|
When the client processes the identifier |
|
Then it should display as "bob.com" only |
|
And treat it as the root identifier for the domain |
|
|
|
Scenario: Regular identifier display |
|
Given I see a kind 0 event with nip05 identifier "bob@bob.com" |
|
When the client processes the identifier |
|
Then it should display as "bob@bob.com" |
|
And not apply root identifier special handling |
|
|
|
# ===== Multiple NIP-05 Identifiers ===== |
|
|
|
Scenario: User with multiple NIP-05 identifiers in tags |
|
Given I see a kind 0 event with pubkey "b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9" |
|
And the event has a tag ["nip05", "bob@example.com", "bob@business.com", "bob@personal.org"] |
|
When the client processes the metadata |
|
Then all three identifiers should be collected into an array |
|
And each identifier should be verified independently |
|
And all verified identifiers should be displayed as badges |
|
|
|
Scenario: Displaying multiple verified NIP-05 badges |
|
Given I see a kind 0 event with multiple nip05 identifiers |
|
And "bob@example.com" verification succeeds |
|
And "bob@business.com" verification succeeds |
|
And "bob@personal.org" verification fails |
|
When the profile is displayed |
|
Then I should see a badge for "bob@example.com" |
|
And I should see a badge for "bob@business.com" |
|
And I should NOT see a badge for "bob@personal.org" |
|
|
|
Scenario: Multiple NIP-05 from tags override content field |
|
Given I see a kind 0 event with content field nip05 "old@example.com" |
|
And the event has a tag ["nip05", "bob@example.com", "bob@business.com"] |
|
When the client processes the metadata |
|
Then the nip05 field should be ["bob@example.com", "bob@business.com"] |
|
And "old@example.com" should be ignored |
|
And tags take priority over content |
|
|
|
Scenario: Single NIP-05 in content is converted to array |
|
Given I see a kind 0 event with only content field nip05 "bob@example.com" |
|
And the event has no nip05 tags |
|
When the client processes the metadata |
|
Then the nip05 field should be converted to array ["bob@example.com"] |
|
And it should be displayed consistently with multi-value cases |
|
|
|
Scenario: Duplicate NIP-05 values are removed |
|
Given I see a kind 0 event with tag ["nip05", "bob@example.com", "bob@example.com", "bob@business.com"] |
|
When the client processes the metadata |
|
Then duplicates should be removed |
|
And the nip05 field should contain ["bob@example.com", "bob@business.com"] |
|
And each identifier should only be verified once |
|
|
|
Scenario: Multiple lightning addresses in tags |
|
Given I see a kind 0 event with tag ["lud16", "bob@getalby.com", "bob@primal.net", "bob@wallet.com"] |
|
When the client processes the metadata |
|
Then all three addresses should be collected into an array |
|
And the profile about page should display "Lightning Addresses" (plural) |
|
And all addresses should be listed |
|
|
|
Scenario: Single lightning address displays singular form |
|
Given I see a kind 0 event with tag ["lud16", "bob@getalby.com"] |
|
When the client processes the metadata |
|
Then the lud16 field should be array ["bob@getalby.com"] |
|
And the profile about page should display "Lightning Address" (singular) |
|
|
|
# ===== Following and Key Management ===== |
|
|
|
Scenario: Client must follow public keys, not identifiers |
|
Given I find that "bob@bob.com" has pubkey "abc...def" |
|
When the user follows this profile |
|
Then the client should store a reference to pubkey "abc...def" |
|
And NOT store a primary reference to "bob@bob.com" |
|
And use the NIP-05 identifier only for display |
|
|
|
Scenario: Handling identifier changes |
|
Given I am following pubkey "abc...def" displayed as "bob@bob.com" |
|
When "https://bob.com/.well-known/nostr.json?name=bob" starts returning pubkey "1d2...e3f" |
|
Then the client should continue following "abc...def" |
|
And stop displaying "bob@bob.com" for that user |
|
And mark the NIP-05 identifier as invalid |
|
|
|
# ===== Verification Failures ===== |
|
|
|
Scenario: Pubkey mismatch in verification |
|
Given I see a kind 0 event with pubkey "abc...def" |
|
And the event content includes nip05 identifier "bob@example.com" |
|
When I fetch "https://example.com/.well-known/nostr.json?name=bob" |
|
And the response contains a different pubkey "xyz...123" |
|
Then the verification should fail |
|
And the NIP-05 identifier should not be displayed |
|
And the user should be shown without verification |
|
|
|
Scenario: Well-known endpoint not found |
|
Given I see a kind 0 event with nip05 identifier "bob@example.com" |
|
When I fetch "https://example.com/.well-known/nostr.json?name=bob" |
|
And the response is 404 Not Found |
|
Then the verification should fail |
|
And the identifier should be marked as unverified |
|
|
|
Scenario: Network timeout during verification |
|
Given I see a kind 0 event with nip05 identifier "bob@example.com" |
|
When I fetch "https://example.com/.well-known/nostr.json?name=bob" |
|
And the request times out |
|
Then the verification should fail gracefully |
|
And the client should retry after a reasonable delay |
|
And display the user without verification in the meantime |
|
|
|
# ===== CORS Support ===== |
|
|
|
Scenario: Successful CORS-enabled request from JavaScript app |
|
Given a JavaScript Nostr app is running in a browser |
|
When I fetch "https://example.com/.well-known/nostr.json?name=bob" |
|
And the response includes header "Access-Control-Allow-Origin: *" |
|
Then the JavaScript app should successfully receive the response |
|
And complete the verification process |
|
|
|
Scenario: CORS policy blocking JavaScript request |
|
Given a JavaScript Nostr app is running in a browser |
|
When I fetch "https://example.com/.well-known/nostr.json?name=bob" |
|
And the server does not include CORS headers |
|
Then the browser should block the request |
|
And the app should see it as a network failure |
|
And recommend the user check their server's CORS policy |
|
|
|
# ===== Security Constraints ===== |
|
|
|
Scenario: Rejecting HTTP redirects |
|
Given I fetch "https://example.com/.well-known/nostr.json?name=bob" |
|
When the server responds with a 301 or 302 redirect |
|
Then the client MUST ignore the redirect |
|
And the verification should fail |
|
And the identifier should be marked as invalid |
|
|
|
Scenario: Following redirect attempt should be blocked |
|
Given I fetch "https://example.com/.well-known/nostr.json?name=bob" |
|
When the server responds with redirect to "https://malicious.com/nostr.json" |
|
Then the client MUST NOT follow the redirect |
|
And MUST NOT make a request to the redirected URL |
|
And treat this as a verification failure |
|
|
|
# ===== Dynamic vs Static Server Support ===== |
|
|
|
Scenario: Dynamic server with query string support |
|
Given the server generates JSON on-demand |
|
When I request "https://example.com/.well-known/nostr.json?name=bob" |
|
Then the server should return data specific to "bob" |
|
And optionally include relay information |
|
When I request "https://example.com/.well-known/nostr.json?name=alice" |
|
Then the server should return different data for "alice" |
|
|
|
Scenario: Static server with multiple names |
|
Given the server has a static nostr.json file |
|
When I request "https://example.com/.well-known/nostr.json?name=bob" |
|
Then the response should contain multiple names: |
|
"""json |
|
{ |
|
"names": { |
|
"bob": "b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9", |
|
"alice": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" |
|
} |
|
} |
|
""" |
|
And the client should extract only the "bob" mapping |
|
|
|
# ===== Edge Cases ===== |
|
|
|
Scenario: Empty response from well-known endpoint |
|
Given I fetch "https://example.com/.well-known/nostr.json?name=bob" |
|
When the response is empty or malformed JSON |
|
Then the verification should fail |
|
And log the error for debugging |
|
|
|
Scenario: Missing names field in response |
|
Given I fetch "https://example.com/.well-known/nostr.json?name=bob" |
|
When the response is valid JSON but missing the "names" field |
|
Then the verification should fail |
|
And the identifier should not be displayed |
|
|
|
Scenario: Name not found in response |
|
Given I fetch "https://example.com/.well-known/nostr.json?name=bob" |
|
When the response contains names but not "bob" |
|
Then the verification should fail |
|
And the identifier should be marked as invalid |
|
|
|
Scenario: Invalid hex format in response |
|
Given I fetch "https://example.com/.well-known/nostr.json?name=bob" |
|
When the response contains pubkey with invalid hex characters |
|
Then the verification should fail |
|
And log a format error
|
|
|