-
-
Notifications
You must be signed in to change notification settings - Fork 319
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Support duplex communication * Partial implementation. Missing CSRF protection. * Prevent CSRF attacks. Return the event ID when publishing * Fix tests and some bugs * Test header auth mechanism * Improve tests * Wording
- Loading branch information
Showing
25 changed files
with
966 additions
and
473 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,15 @@ | ||
ACME_CERT_DIR= | ||
ACME_HOSTS= | ||
ADDR=:3001 | ||
PUBLISHER_JWT_KEY=!UnsecureChangeMePublisher! | ||
SUBSCRIBER_JWT_KEY=!UnsecureChangeMeSubscriber! | ||
ALLOW_ANONYMOUS=1 | ||
DEBUG=1 | ||
CORS_ALLOWED_ORIGINS= | ||
DB_PATH= | ||
ACME_HOSTS= | ||
ACME_CERT_DIR= | ||
CERT_FILE= | ||
CERT_KEY= | ||
CORS_ALLOWED_ORIGINS= | ||
DB_PATH= | ||
DEBUG=1 | ||
DEMO= | ||
JWT_KEY=!UnsecureChangeMe! | ||
LOG_FORMAT=JSON | ||
PUBLISH_ALLOWED_ORIGINS=http://localhost:3000 | ||
PUBLISHER_JWT_KEY= | ||
SUBSCRIBER_JWT_KEY= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,14 @@ | ||
<?php | ||
|
||
define('DEMO_JWT', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InN1YnNjcmliZSI6WyJmb28iLCJiYXIiXSwicHVibGlzaCI6WyJmb28iXX19.LRLvirgONK13JgacQ_VbcjySbVhkSmHy3IznH3tA9PM'); | ||
|
||
$postData = http_build_query([ | ||
'topic' => 'http://localhost:3000/demo/books/1.jsonld', | ||
'data' => json_encode(['key' => 'updated value']), | ||
]); | ||
|
||
echo file_get_contents('http://localhost:3000/publish', false, stream_context_create(['http' => [ | ||
echo file_get_contents('http://localhost:3000/hub', false, stream_context_create(['http' => [ | ||
'method' => 'POST', | ||
'header' => "Content-type: application/x-www-form-urlencoded\r\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.HB0k08BaV8KlLZ3EafCRlTDGbkd9qdznCzJQ_l8ELTU", | ||
'header' => "Content-type: application/x-www-form-urlencoded\r\nAuthorization: Bearer ".DEMO_JWT, | ||
'content' => $postData, | ||
]])); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package hub | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
|
||
jwt "github.com/dgrijalva/jwt-go" | ||
) | ||
|
||
// Claims contains Mercure's JWT claims | ||
type claims struct { | ||
Mercure mercureClaim `json:"mercure"` | ||
jwt.StandardClaims | ||
} | ||
|
||
type mercureClaim struct { | ||
Publish []string `json:"publish"` | ||
Subscribe []string `json:"subscribe"` | ||
} | ||
|
||
// Authorize validates the JWT that may be provided through an "Authorization" HTTP header or a "mercureAuthorization" cookie. | ||
// It returns the claims contained in the token if it exists and is valid, nil if no token is provided (anonymous mode), and an error if the token is not valid. | ||
func authorize(r *http.Request, jwtKey []byte, publishAllowedOrigins []string) (*claims, error) { | ||
authorizationHeaders, headerExists := r.Header["Authorization"] | ||
if headerExists { | ||
if len(authorizationHeaders) != 1 || len(authorizationHeaders[0]) < 48 || authorizationHeaders[0][:7] != "Bearer " { | ||
return nil, errors.New("Invalid \"Authorization\" HTTP header") | ||
} | ||
|
||
return validateJWT(authorizationHeaders[0][7:], jwtKey) | ||
} | ||
|
||
cookie, err := r.Cookie("mercureAuthorization") | ||
if err != nil { | ||
// Anonymous | ||
return nil, nil | ||
} | ||
|
||
// CSRF attacks cannot occurs when using safe methods | ||
if r.Method != "POST" { | ||
return validateJWT(cookie.Value, jwtKey) | ||
} | ||
|
||
origin := r.Header.Get("Origin") | ||
if origin == "" { | ||
// Try to extract the origin from the Referer, or return an error | ||
referer := r.Header.Get("Referer") | ||
if referer == "" { | ||
return nil, errors.New("An \"Origin\" or a \"Referer\" HTTP header must be present to use the cookie-based authorization mechanism") | ||
} | ||
|
||
u, err := url.Parse(referer) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
origin = fmt.Sprintf("%s://%s", u.Scheme, u.Host) | ||
} | ||
|
||
for _, allowedOrigin := range publishAllowedOrigins { | ||
if origin == allowedOrigin { | ||
return validateJWT(cookie.Value, jwtKey) | ||
} | ||
} | ||
|
||
return nil, fmt.Errorf("The origin \"%s\" is not allowed to post updates", origin) | ||
} | ||
|
||
// validateJWT validates that the provided JWT token is a valid Mercure token | ||
func validateJWT(encodedToken string, key []byte) (*claims, error) { | ||
token, err := jwt.ParseWithClaims(encodedToken, &claims{}, func(token *jwt.Token) (interface{}, error) { | ||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { | ||
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) | ||
} | ||
return key, nil | ||
}) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if claims, ok := token.Claims.(*claims); ok && token.Valid { | ||
return claims, nil | ||
} | ||
|
||
return nil, errors.New("Invalid JWT") | ||
} | ||
|
||
func authorizedTargets(claims *claims, publisher bool) (all bool, targets map[string]struct{}) { | ||
if claims == nil { | ||
return false, map[string]struct{}{} | ||
} | ||
|
||
var providedTargets []string | ||
if publisher { | ||
providedTargets = claims.Mercure.Publish | ||
} else { | ||
providedTargets = claims.Mercure.Subscribe | ||
} | ||
|
||
authorizedTargets := make(map[string]struct{}, len(providedTargets)) | ||
for _, target := range providedTargets { | ||
if target == "*" { | ||
return true, nil | ||
} | ||
|
||
authorizedTargets[target] = struct{}{} | ||
} | ||
|
||
return false, authorizedTargets | ||
} |
Oops, something went wrong.