Skip to content

Commit

Permalink
Update the spec and the docs to use /.well-known/mercure
Browse files Browse the repository at this point in the history
  • Loading branch information
dunglas committed Nov 7, 2019
1 parent f3b4867 commit af76086
Show file tree
Hide file tree
Showing 17 changed files with 133 additions and 83 deletions.
8 changes: 4 additions & 4 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ Subscribing to updates from a web browser or any other platform supporting [Serv
```javascript
// The subscriber subscribes to updates for the https://example.com/users/dunglas topic
// and to any topic matching https://example.com/books/{id}
const url = new URL('https://example.com/hub');
const url = new URL('https://example.com/.well-known/mercure');
url.searchParams.append('topic', 'https://example.com/books/{id}');
url.searchParams.append('topic', 'https://example.com/users/dunglas');
// The URL class is a convenient way to generate URLs such as https://example.com/hub?topic=https://example.com/books/{id}&topic=https://example.com/users/dunglas
// The URL class is a convenient way to generate URLs such as https://example.com/.well-known/mercure?topic=https://example.com/books/{id}&topic=https://example.com/users/dunglas

const eventSource = new EventSource(url);

Expand All @@ -44,7 +44,7 @@ Also optionally, the hub URL can be automatically discovered:
Here is a snippet to extract the URL of the hub from the `Link` HTTP header.

```javascript
fetch('https://example.com/books/1') // Has this header `Link: <https://example.com/hub>; rel="mercure"`
fetch('https://example.com/books/1') // Has this header `Link: <https://example.com/.well-known/mercure>; rel="mercure"`
.then(response => {
// Extract the hub URL from the Link header
const hubUrl = response.headers.get('Link').match(/<([^>]+)>;\s+rel=(?:mercure|"[^"]*mercure[^"]*")/)[1];
Expand Down Expand Up @@ -79,7 +79,7 @@ const postData = querystring.stringify({
const req = https.request({
hostname: 'example.com',
port: '443',
path: '/hub',
path: '/.well-known/mercure',
method: 'POST',
headers: {
Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InN1YnNjcmliZSI6WyJmb28iLCJiYXIiXSwicHVibGlzaCI6WyJmb28iXX19.afLx2f2ut3YgNVFStCx95Zm_UND1mZJ69OenXaDuZL8',
Expand Down
4 changes: 2 additions & 2 deletions docs/hub/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ If you omit this variable, the server will be exposed using a not encrypted HTTP

When the server is up and running, the following endpoints are available:

* `POST https://example.com/hub`: to publish updates
* `GET https://example.com/hub`: to subscribe to updates
* `POST https://example.com/.well-known/mercure`: to publish updates
* `GET https://example.com/.well-known/mercure`: to subscribe to updates

See [the protocol](spec/mercure.md) for further informations.

Expand Down
2 changes: 1 addition & 1 deletion docs/spec/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Because they are delivery agnostic, Mercure plays particularly well with [GraphQL's subscriptions](https://facebook.github.io/graphql/draft/#sec-Subscription).

In response to the subscription query, the GraphQL server may return a corresponding topic URL.
The client can then subscribe to the Mercure's event stream corresponding to this subscription by creating a new `EventSource` with an URL like `https://example.com/hub?topic=https://example.com/subscriptions/<subscription-id>` as parameter.
The client can then subscribe to the Mercure's event stream corresponding to this subscription by creating a new `EventSource` with an URL like `https://example.com/.well-known/mercure?topic=https://example.com/subscriptions/<subscription-id>` as parameter.

Updates for the given subscription can then be sent from the GraphQL server to the clients through the Mercure hub (in the `data` property of the server-sent event).

Expand Down
8 changes: 4 additions & 4 deletions examples/chat-python-flask/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
Deploy on Heroku:
heroku login
heroku config:set HUB_URL=https://demo.mercure.rocks/hub
heroku config:set HUB_URL=https://demo.mercure.rocks/.well-known/mercure
heroku config:set COOKIE_DOMAIN=.mercure.rocks
git subtree push --prefix examples/chat-python-flask heroku master
Environment variables:
JWT_KEY: the JWT key to use (must be shared with the Mercure hub)
HUB_URL: the URL of the Mercure hub (default: http://localhost:3000/hub)
HUB_URL: the URL of the Mercure hub (default: http://localhost:3000/.well-known/mercure)
TOPIC: the topic to use (default: http://example.com/chat)
TARGET: the target to use (default: chan)
COOKIE_DOMAIN: the cookie domain (default: None)
Expand All @@ -39,12 +39,12 @@ def chat():
algorithm='HS256'
)

hub_url = os.environ.get('HUB_URL', 'http://localhost:3000/hub')
hub_url = os.environ.get('HUB_URL', 'http://localhost:3000/.well-known/mercure')
topic = os.environ.get('TOPIC', 'http://example.com/chat')

resp = make_response(render_template('chat.html', config={
'hubURL': hub_url, 'topic': topic}))
resp.set_cookie('mercureAuthorization', token, httponly=True, path='/hub',
resp.set_cookie('mercureAuthorization', token, httponly=True, path='/.well-known/mercure',
samesite="strict", domain=os.environ.get('COOKIE_DOMAIN', None), secure=request.is_secure) # Force secure to True for real apps

return resp
2 changes: 1 addition & 1 deletion examples/publisher-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const postData = querystring.stringify({
const req = http.request({
hostname: 'localhost',
port: '3000',
path: '/hub',
path: '/.well-known/mercure',
method: 'POST',
headers: {
Authorization: `Bearer ${demoJwt}`,
Expand Down
2 changes: 1 addition & 1 deletion examples/publisher-php.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
'data' => json_encode(['key' => 'updated value']),
]);

echo file_get_contents('http://localhost:3000/hub', false, stream_context_create(['http' => [
echo file_get_contents('http://localhost:3000/.well-known/mercure', false, stream_context_create(['http' => [
'method' => 'POST',
'header' => "Content-type: application/x-www-form-urlencoded\r\nAuthorization: Bearer ".DEMO_JWT,
'content' => $postData,
Expand Down
2 changes: 1 addition & 1 deletion examples/publisher-ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InN1YnNjcmliZSI6WyJmb28iLCJiYXIiXSwicHVibGlzaCI6WyJmb28iXX19.afLx2f2ut3YgNVFStCx95Zm_UND1mZJ69OenXaDuZL8'

Net::HTTP.start('localhost', 3000) do |http|
req = Net::HTTP::Post.new('/hub')
req = Net::HTTP::Post.new('/.well-known/mercure')
req['Authorization'] = "Bearer #{token}"
req.form_data = {
topic: 'http://localhost:3000/demo/books/1.jsonld',
Expand Down
62 changes: 31 additions & 31 deletions hub/authorization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ var hmacSigningMethod = jwt.GetSigningMethod("HS256")
var rsaSigningMethod = jwt.GetSigningMethod("RS256")

func TestAuthorizeMultipleAuthorizationHeader(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.Header.Add("Authorization", validEmptyHeader)
r.Header.Add("Authorization", validEmptyHeader)

Expand All @@ -54,7 +54,7 @@ func TestAuthorizeMultipleAuthorizationHeader(t *testing.T) {
}

func TestAuthorizeMultipleAuthorizationHeaderRsa(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.Header.Add("Authorization", validEmptyHeaderRsa)
r.Header.Add("Authorization", validEmptyHeaderRsa)

Expand All @@ -64,7 +64,7 @@ func TestAuthorizeMultipleAuthorizationHeaderRsa(t *testing.T) {
}

func TestAuthorizeAuthorizationHeaderTooShort(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.Header.Add("Authorization", "Bearer x")

claims, err := authorize(r, []byte{}, hmacSigningMethod, []string{})
Expand All @@ -73,7 +73,7 @@ func TestAuthorizeAuthorizationHeaderTooShort(t *testing.T) {
}

func TestAuthorizeAuthorizationHeaderNoBearer(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.Header.Add("Authorization", "Greater "+validEmptyHeader)

claims, err := authorize(r, []byte{}, hmacSigningMethod, []string{})
Expand All @@ -82,7 +82,7 @@ func TestAuthorizeAuthorizationHeaderNoBearer(t *testing.T) {
}

func TestAuthorizeAuthorizationHeaderNoBearerRsa(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.Header.Add("Authorization", "Greater "+validEmptyHeaderRsa)

claims, err := authorize(r, []byte{}, rsaSigningMethod, []string{})
Expand All @@ -91,7 +91,7 @@ func TestAuthorizeAuthorizationHeaderNoBearerRsa(t *testing.T) {
}

func TestAuthorizeAuthorizationHeaderInvalidAlg(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.Header.Add("Authorization", "Bearer "+createDummyNoneSignedJWT())

claims, err := authorize(r, []byte{}, hmacSigningMethod, []string{})
Expand All @@ -100,7 +100,7 @@ func TestAuthorizeAuthorizationHeaderInvalidAlg(t *testing.T) {
}

func TestAuthorizeAuthorizationHeaderInvalidKey(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.Header.Add("Authorization", "Bearer "+validEmptyHeader)

claims, err := authorize(r, []byte{}, hmacSigningMethod, []string{})
Expand All @@ -109,7 +109,7 @@ func TestAuthorizeAuthorizationHeaderInvalidKey(t *testing.T) {
}

func TestAuthorizeAuthorizationHeaderInvalidKeyRsa(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.Header.Add("Authorization", "Bearer "+validEmptyHeaderRsa)

claims, err := authorize(r, []byte{}, rsaSigningMethod, []string{})
Expand All @@ -118,7 +118,7 @@ func TestAuthorizeAuthorizationHeaderInvalidKeyRsa(t *testing.T) {
}

func TestAuthorizeAuthorizationHeaderNoContent(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.Header.Add("Authorization", "Bearer "+validEmptyHeader)

claims, err := authorize(r, []byte("!ChangeMe!"), hmacSigningMethod, []string{})
Expand All @@ -128,7 +128,7 @@ func TestAuthorizeAuthorizationHeaderNoContent(t *testing.T) {
}

func TestAuthorizeAuthorizationHeaderNoContentRsa(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.Header.Add("Authorization", "Bearer "+validEmptyHeaderRsa)

claims, err := authorize(r, []byte(publicKeyRsa), rsaSigningMethod, []string{})
Expand All @@ -138,7 +138,7 @@ func TestAuthorizeAuthorizationHeaderNoContentRsa(t *testing.T) {
}

func TestAuthorizeAuthorizationHeader(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.Header.Add("Authorization", "Bearer "+validFullHeader)

claims, err := authorize(r, []byte("!ChangeMe!"), hmacSigningMethod, []string{})
Expand All @@ -148,7 +148,7 @@ func TestAuthorizeAuthorizationHeader(t *testing.T) {
}

func TestAuthorizeAuthorizationHeaderRsa(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.Header.Add("Authorization", "Bearer "+validFullHeaderRsa)

claims, err := authorize(r, []byte(publicKeyRsa), rsaSigningMethod, []string{})
Expand All @@ -158,7 +158,7 @@ func TestAuthorizeAuthorizationHeaderRsa(t *testing.T) {
}

func TestAuthorizeAuthorizationHeaderWrongAlgorithm(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.Header.Add("Authorization", "Bearer "+validFullHeaderRsa)

claims, err := authorize(r, []byte(publicKeyRsa), nil, []string{})
Expand All @@ -167,7 +167,7 @@ func TestAuthorizeAuthorizationHeaderWrongAlgorithm(t *testing.T) {
}

func TestAuthorizeCookieInvalidAlg(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.AddCookie(&http.Cookie{Name: "mercureAuthorization", Value: createDummyNoneSignedJWT()})

claims, err := authorize(r, []byte{}, hmacSigningMethod, []string{})
Expand All @@ -176,7 +176,7 @@ func TestAuthorizeCookieInvalidAlg(t *testing.T) {
}

func TestAuthorizeCookieInvalidKey(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.AddCookie(&http.Cookie{Name: "mercureAuthorization", Value: validEmptyHeader})

claims, err := authorize(r, []byte{}, hmacSigningMethod, []string{})
Expand All @@ -185,7 +185,7 @@ func TestAuthorizeCookieInvalidKey(t *testing.T) {
}

func TestAuthorizeCookieEmptyKeyRsa(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.AddCookie(&http.Cookie{Name: "mercureAuthorization", Value: validEmptyHeaderRsa})

claims, err := authorize(r, []byte{}, rsaSigningMethod, []string{})
Expand All @@ -194,7 +194,7 @@ func TestAuthorizeCookieEmptyKeyRsa(t *testing.T) {
}

func TestAuthorizeCookieInvalidKeyRsa(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.AddCookie(&http.Cookie{Name: "mercureAuthorization", Value: validEmptyHeaderRsa})

claims, err := authorize(r, []byte(privateKeyRsa), rsaSigningMethod, []string{})
Expand All @@ -203,7 +203,7 @@ func TestAuthorizeCookieInvalidKeyRsa(t *testing.T) {
}

func TestAuthorizeCookieNoContent(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.AddCookie(&http.Cookie{Name: "mercureAuthorization", Value: validEmptyHeader})

claims, err := authorize(r, []byte("!ChangeMe!"), hmacSigningMethod, []string{})
Expand All @@ -213,7 +213,7 @@ func TestAuthorizeCookieNoContent(t *testing.T) {
}

func TestAuthorizeCookieNoContentRsa(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.AddCookie(&http.Cookie{Name: "mercureAuthorization", Value: validEmptyHeaderRsa})

claims, err := authorize(r, []byte(publicKeyRsa), rsaSigningMethod, []string{})
Expand All @@ -223,7 +223,7 @@ func TestAuthorizeCookieNoContentRsa(t *testing.T) {
}

func TestAuthorizeCookie(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.AddCookie(&http.Cookie{Name: "mercureAuthorization", Value: validFullHeader})

claims, err := authorize(r, []byte("!ChangeMe!"), hmacSigningMethod, []string{})
Expand All @@ -233,7 +233,7 @@ func TestAuthorizeCookie(t *testing.T) {
}

func TestAuthorizeCookieRsa(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.com/hub", nil)
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.AddCookie(&http.Cookie{Name: "mercureAuthorization", Value: validFullHeaderRsa})

claims, err := authorize(r, []byte(publicKeyRsa), rsaSigningMethod, []string{})
Expand All @@ -243,7 +243,7 @@ func TestAuthorizeCookieRsa(t *testing.T) {
}

func TestAuthorizeCookieNoOriginNoReferer(t *testing.T) {
r, _ := http.NewRequest("POST", "http://example.com/hub", nil)
r, _ := http.NewRequest("POST", defaultHubURL, nil)
r.AddCookie(&http.Cookie{Name: "mercureAuthorization", Value: validFullHeader})

claims, err := authorize(r, []byte("!ChangeMe!"), hmacSigningMethod, []string{})
Expand All @@ -252,7 +252,7 @@ func TestAuthorizeCookieNoOriginNoReferer(t *testing.T) {
}

func TestAuthorizeCookieNoOriginNoRefererRsa(t *testing.T) {
r, _ := http.NewRequest("POST", "http://example.com/hub", nil)
r, _ := http.NewRequest("POST", defaultHubURL, nil)
r.AddCookie(&http.Cookie{Name: "mercureAuthorization", Value: validFullHeaderRsa})

claims, err := authorize(r, []byte(publicKeyRsa), rsaSigningMethod, []string{})
Expand All @@ -261,7 +261,7 @@ func TestAuthorizeCookieNoOriginNoRefererRsa(t *testing.T) {
}

func TestAuthorizeCookieOriginNotAllowed(t *testing.T) {
r, _ := http.NewRequest("POST", "http://example.com/hub", nil)
r, _ := http.NewRequest("POST", defaultHubURL, nil)
r.Header.Add("Origin", "http://example.com")
r.AddCookie(&http.Cookie{Name: "mercureAuthorization", Value: validFullHeader})

Expand All @@ -271,7 +271,7 @@ func TestAuthorizeCookieOriginNotAllowed(t *testing.T) {
}

func TestAuthorizeCookieOriginNotAllowedRsa(t *testing.T) {
r, _ := http.NewRequest("POST", "http://example.com/hub", nil)
r, _ := http.NewRequest("POST", defaultHubURL, nil)
r.Header.Add("Origin", "http://example.com")
r.AddCookie(&http.Cookie{Name: "mercureAuthorization", Value: validFullHeaderRsa})

Expand All @@ -281,7 +281,7 @@ func TestAuthorizeCookieOriginNotAllowedRsa(t *testing.T) {
}

func TestAuthorizeCookieRefererNotAllowed(t *testing.T) {
r, _ := http.NewRequest("POST", "http://example.com/hub", nil)
r, _ := http.NewRequest("POST", defaultHubURL, nil)
r.Header.Add("Referer", "http://example.com/foo/bar")
r.AddCookie(&http.Cookie{Name: "mercureAuthorization", Value: validFullHeader})

Expand All @@ -291,7 +291,7 @@ func TestAuthorizeCookieRefererNotAllowed(t *testing.T) {
}

func TestAuthorizeCookieRefererNotAllowedRsa(t *testing.T) {
r, _ := http.NewRequest("POST", "http://example.com/hub", nil)
r, _ := http.NewRequest("POST", defaultHubURL, nil)
r.Header.Add("Referer", "http://example.com/foo/bar")
r.AddCookie(&http.Cookie{Name: "mercureAuthorization", Value: validFullHeaderRsa})

Expand All @@ -301,7 +301,7 @@ func TestAuthorizeCookieRefererNotAllowedRsa(t *testing.T) {
}

func TestAuthorizeCookieInvalidReferer(t *testing.T) {
r, _ := http.NewRequest("POST", "http://example.com/hub", nil)
r, _ := http.NewRequest("POST", defaultHubURL, nil)
r.Header.Add("Referer", "http://192.168.0.%31/")
r.AddCookie(&http.Cookie{Name: "mercureAuthorization", Value: validFullHeader})

Expand All @@ -311,7 +311,7 @@ func TestAuthorizeCookieInvalidReferer(t *testing.T) {
}

func TestAuthorizeCookieInvalidRefererRsa(t *testing.T) {
r, _ := http.NewRequest("POST", "http://example.com/hub", nil)
r, _ := http.NewRequest("POST", defaultHubURL, nil)
r.Header.Add("Referer", "http://192.168.0.%31/")
r.AddCookie(&http.Cookie{Name: "mercureAuthorization", Value: validFullHeaderRsa})

Expand All @@ -321,7 +321,7 @@ func TestAuthorizeCookieInvalidRefererRsa(t *testing.T) {
}

func TestAuthorizeCookieOriginHasPriority(t *testing.T) {
r, _ := http.NewRequest("POST", "http://example.com/hub", nil)
r, _ := http.NewRequest("POST", defaultHubURL, nil)
r.Header.Add("Origin", "http://example.net")
r.Header.Add("Referer", "http://example.com")
r.AddCookie(&http.Cookie{Name: "mercureAuthorization", Value: validFullHeader})
Expand All @@ -333,7 +333,7 @@ func TestAuthorizeCookieOriginHasPriority(t *testing.T) {
}

func TestAuthorizeCookieOriginHasPriorityRsa(t *testing.T) {
r, _ := http.NewRequest("POST", "http://example.com/hub", nil)
r, _ := http.NewRequest("POST", defaultHubURL, nil)
r.Header.Add("Origin", "http://example.net")
r.Header.Add("Referer", "http://example.com")
r.AddCookie(&http.Cookie{Name: "mercureAuthorization", Value: validFullHeaderRsa})
Expand Down
2 changes: 1 addition & 1 deletion hub/demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func Demo(w http.ResponseWriter, r *http.Request) {

header := w.Header()
// Several Link headers are set on purpose to allow testing advanced discovery mechanism
header.Add("Link", "</hub>; rel=\"mercure\"")
header.Add("Link", "<"+defaultHubURL+">; rel=\"mercure\"")
header.Add("Link", fmt.Sprintf("<%s>; rel=\"self\"", url))
if mimeType != "" {
header.Set("Content-Type", mimeType)
Expand Down
Loading

0 comments on commit af76086

Please sign in to comment.