-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
38 changed files
with
965 additions
and
0 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
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
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,9 @@ | ||
root = true | ||
|
||
[*.cr] | ||
charset = utf-8 | ||
end_of_line = lf | ||
insert_final_newline = true | ||
indent_style = space | ||
indent_size = 2 | ||
trim_trailing_whitespace = true |
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,9 @@ | ||
/docs/ | ||
/lib/ | ||
/bin/ | ||
/.shards/ | ||
*.dwarf | ||
|
||
# Libraries don't need dependency lock | ||
# Dependencies will be locked in applications that use them | ||
/shard.lock |
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,7 @@ | ||
# Changelog | ||
|
||
## [0.1.0] - YYYY-MM-DD | ||
|
||
_Initial release._ | ||
|
||
[0.1.0]: https://github.com/athena-framework/mercure/releases/tag/v0.1.0 |
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,3 @@ | ||
# Contributing | ||
|
||
This repository is a read-only mirror. Please refer the [main Athena repository](https://github.com/athena-framework/athena/blob/master/CONTRIBUTING.md) on how to start contributing. |
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,21 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2024 George Dietrich | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. |
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,29 @@ | ||
# Mercure | ||
|
||
[![Common Changelog](https://common-changelog.org/badge.svg)](https://common-changelog.org) | ||
[![CI](https://github.com/athena-framework/athena/workflows/CI/badge.svg)](https://github.com/athena-framework/athena/actions/workflows/ci.yml) | ||
[![Latest release](https://img.shields.io/github/release/athena-framework/mercure.svg)](https://github.com/athena-framework/mercure/releases) | ||
|
||
Allows easily pushing updates to web browsers and other HTTP clients using the Mercure protocol. | ||
|
||
## Installation | ||
|
||
1. Add the dependency to your `shard.yml`: | ||
|
||
```yaml | ||
dependencies: | ||
athena-mercure: | ||
github: athena-framework/mercure | ||
version: ~> 0.1.0 | ||
``` | ||
2. Run `shards install` | ||
|
||
## Documentation | ||
|
||
If using the component on its own, checkout the [API documentation](https://athenaframework.org/Mercure). | ||
If using the component as part of Athena, also checkout the [external documentation](https://athenaframework.org/components/mercure). | ||
|
||
## Contributing | ||
|
||
Read the general [Contributing Guide](./CONTRIBUTING.md) for information on how to get started. |
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,22 @@ | ||
name: athena-mercure | ||
|
||
version: 0.1.0 | ||
|
||
crystal: '>= 0.36.0' | ||
|
||
license: MIT | ||
|
||
repository: https://github.com/athena-framework/mercure | ||
|
||
documentation: https://athenaframework.org/Mercure | ||
|
||
description: | | ||
Allows easily pushing updates to web browsers and other HTTP clients using the Mercure protocol. | ||
authors: | ||
- George Dietrich <[email protected]> | ||
|
||
dependencies: | ||
jwt: | ||
github: crystal-community/jwt | ||
version: ~> 1.6 |
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,187 @@ | ||
require "./spec_helper" | ||
|
||
# @[ASPEC::TestCase::Focus] | ||
struct AuthorizationTest < ASPEC::TestCase | ||
def test_jwt_lifetime : Nil | ||
registry = AMC::Hub::Registry.new(AMC::Spec::MockHub.new( | ||
"https://example.com/.well-known/mercure", | ||
AMC::TokenProvider::Static.new("JWT"), | ||
token_factory: AMC::TokenFactory::JWT.new("looooooooooooongenoughtestsecret", jwt_lifetime: 4000) | ||
) { "ID" }) | ||
|
||
authorization = AMC::Authorization.new registry | ||
cookie = authorization.create_cookie HTTP::Request.new("GET", "https://example.com", headers: HTTP::Headers{"host" => "example.com"}) | ||
|
||
payload, _ = JWT.decode(cookie.value, verify: false, validate: false) | ||
payload["exp"].as_i?.should be_a Int32 | ||
end | ||
|
||
def test_set_cookie_zero_expiration : Nil | ||
token_factory = AMC::Spec::AssertingTokenFactory.new( | ||
"JWT", | ||
["foo"], | ||
["bar"], | ||
{"x-foo" => "baz"}, | ||
) | ||
|
||
registry = AMC::Hub::Registry.new(AMC::Spec::MockHub.new( | ||
"https://example.com/.well-known/mercure", | ||
AMC::TokenProvider::Static.new("JWT"), | ||
token_factory: token_factory | ||
) { "ID" }) | ||
|
||
request = HTTP::Request.new("GET", "https://example.com", headers: HTTP::Headers{"host" => "example.com"}) | ||
response = HTTP::Server::Response.new IO::Memory.new | ||
|
||
authorization = AMC::Authorization.new registry, Time::Span.zero, :lax | ||
authorization.set_cookie request, response, ["foo"], ["bar"], {"x-foo" => "baz"} | ||
token_factory.called?.should be_true | ||
|
||
cookie = response.cookies.first | ||
cookie.max_age.should eq Time::Span.zero | ||
cookie.value.should_not be_empty | ||
cookie.samesite.try &.lax?.should be_true | ||
end | ||
|
||
def test_set_cookie_default_expiration : Nil | ||
token_factory = AMC::Spec::AssertingTokenFactory.new( | ||
"JWT", | ||
["foo"], | ||
["bar"], | ||
{"x-foo" => "baz"}, | ||
) | ||
|
||
registry = AMC::Hub::Registry.new(AMC::Spec::MockHub.new( | ||
"https://example.com/.well-known/mercure", | ||
AMC::TokenProvider::Static.new("JWT"), | ||
token_factory: token_factory | ||
) { "ID" }) | ||
|
||
request = HTTP::Request.new("GET", "https://example.com", headers: HTTP::Headers{"host" => "example.com"}) | ||
response = HTTP::Server::Response.new IO::Memory.new | ||
|
||
authorization = AMC::Authorization.new registry, cookie_samesite: :lax | ||
authorization.set_cookie request, response, ["foo"], ["bar"], {"x-foo" => "baz"} | ||
token_factory.called?.should be_true | ||
|
||
cookie = response.cookies.first | ||
cookie.max_age.should eq 1.hour | ||
cookie.value.should_not be_nil | ||
cookie.samesite.try &.lax?.should be_true | ||
end | ||
|
||
def test_clear_cookie : Nil | ||
token_factory = AMC::Spec::AssertingTokenFactory.new("JWT") | ||
|
||
registry = AMC::Hub::Registry.new(AMC::Spec::MockHub.new( | ||
"https://example.com/.well-known/mercure", | ||
AMC::TokenProvider::Static.new("JWT"), | ||
token_factory: token_factory | ||
) { "ID" }) | ||
|
||
request = HTTP::Request.new("GET", "https://example.com", headers: HTTP::Headers{"host" => "example.com"}) | ||
response = HTTP::Server::Response.new IO::Memory.new | ||
|
||
authorization = AMC::Authorization.new registry | ||
authorization.clear_cookie request, response | ||
|
||
cookie = response.cookies.first | ||
cookie.value.should be_empty | ||
cookie.max_age.should eq 1.second | ||
end | ||
|
||
@[DataProvider("applicable_cookie_domains")] | ||
def test_applicable_cookie_domains(expected : String?, hub_url : String, request_url : String) : Nil | ||
registry = AMC::Hub::Registry.new(AMC::Spec::MockHub.new( | ||
hub_url, | ||
AMC::TokenProvider::Static.new("JWT"), | ||
token_factory: AMC::TokenFactory::JWT.new("looooooooooooongenoughtestsecret", jwt_lifetime: 4000) | ||
) { "ID" }) | ||
|
||
uri = URI.parse request_url | ||
request = HTTP::Request.new("GET", uri.path, headers: HTTP::Headers{"host" => uri.hostname || ""}) | ||
|
||
authorization = AMC::Authorization.new registry | ||
|
||
cookie = authorization.create_cookie request | ||
cookie.domain.should eq expected | ||
end | ||
|
||
def applicable_cookie_domains : Tuple | ||
{ | ||
{".example.com", "https://foo.bar.baz.example.com", "https://foo.bar.baz.qux.example.com"}, | ||
{".foo.bar.baz.example.com", "https://mercure.foo.bar.baz.example.com", "https://app.foo.bar.baz.example.com"}, | ||
{"example.com", "https://demo.example.com", "https://example.com"}, | ||
{".example.com", "https://mercure.example.com", "https://app.example.com"}, | ||
{".example.com", "https://example.com/.well-known/mercure", "https://app.example.com"}, | ||
{nil, "https://example.com/.well-known/mercure", "https://example.com"}, | ||
} | ||
end | ||
|
||
@[DataProvider("nonapplicable_cookie_domains")] | ||
def test_nonapplicable_cookie_domains(hub_url : String, request_url : String) : Nil | ||
registry = AMC::Hub::Registry.new(AMC::Spec::MockHub.new( | ||
hub_url, | ||
AMC::TokenProvider::Static.new("JWT"), | ||
token_factory: AMC::TokenFactory::JWT.new("looooooooooooongenoughtestsecret", jwt_lifetime: 4000) | ||
) { "ID" }) | ||
|
||
uri = URI.parse request_url | ||
request = HTTP::Request.new("GET", uri.path, headers: HTTP::Headers{"host" => uri.hostname || ""}) | ||
|
||
authorization = AMC::Authorization.new registry | ||
|
||
expect_raises AMC::Exceptions::InvalidArgument, "Unable to create authorization cookie for a hub on the different second-level domain" do | ||
authorization.create_cookie request | ||
end | ||
end | ||
|
||
def nonapplicable_cookie_domains : Tuple | ||
{ | ||
{"https://demo.mercure.com", "https://example.com"}, | ||
{"https://mercure.internal.com", "https://external.com"}, | ||
} | ||
end | ||
|
||
def test_set_multiple_cookies : Nil | ||
registry = AMC::Hub::Registry.new(AMC::Spec::MockHub.new( | ||
"https://example.com/.well-known/mercure", | ||
AMC::TokenProvider::Static.new("JWT"), | ||
token_factory: AMC::TokenFactory::JWT.new("looooooooooooongenoughtestsecret", jwt_lifetime: 4000) | ||
) { "ID" }) | ||
|
||
request = HTTP::Request.new("GET", "https://example.com", headers: HTTP::Headers{"host" => "example.com"}) | ||
response = HTTP::Server::Response.new IO::Memory.new | ||
|
||
authorization = AMC::Authorization.new registry | ||
|
||
expect_raises AMC::Exceptions::Runtime, "The 'mercureAuthorization' cookie for the 'default hub' has already been set. You cannot set it two times during the same request." do | ||
authorization.set_cookie request, response | ||
authorization.clear_cookie request, response | ||
end | ||
end | ||
|
||
def test_nil_cookie_topics : Nil | ||
token_factory = AMC::Spec::AssertingTokenFactory.new( | ||
"JWT", | ||
nil, | ||
nil, | ||
{"x-foo" => "baz"}, | ||
) | ||
|
||
registry = AMC::Hub::Registry.new(AMC::Spec::MockHub.new( | ||
"https://example.com/.well-known/mercure", | ||
AMC::TokenProvider::Static.new("JWT"), | ||
token_factory: token_factory | ||
) { "ID" }) | ||
|
||
request = HTTP::Request.new("GET", "https://example.com", headers: HTTP::Headers{"host" => "example.com"}) | ||
response = HTTP::Server::Response.new IO::Memory.new | ||
|
||
authorization = AMC::Authorization.new registry | ||
authorization.set_cookie request, response, nil, nil, {"x-foo" => "baz"} | ||
|
||
cookie = response.cookies.first | ||
cookie.value.should_not be_empty | ||
end | ||
end |
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,35 @@ | ||
require "./spec_helper" | ||
|
||
describe AMC::Discovery do | ||
it "preflight request" do | ||
registry = AMC::Hub::Registry.new(AMC::Spec::MockHub.new( | ||
"https://example.com/.well-known/mercure", | ||
AMC::TokenProvider::Static.new("JWT"), | ||
token_factory: AMC::TokenFactory::JWT.new("looooooooooooongenoughtestsecret", jwt_lifetime: 4000) | ||
) { "ID" }) | ||
|
||
request = HTTP::Request.new("OPTIONS", "/", headers: HTTP::Headers{"access-control-request-method" => "GET"}) | ||
response = HTTP::Server::Response.new IO::Memory.new | ||
|
||
discovery = AMC::Discovery.new registry | ||
discovery.add_link request, response | ||
|
||
response.headers["link"]?.should be_nil | ||
end | ||
|
||
it "non-preflight request" do | ||
registry = AMC::Hub::Registry.new(AMC::Spec::MockHub.new( | ||
"https://example.com/.well-known/mercure", | ||
AMC::TokenProvider::Static.new("JWT"), | ||
token_factory: AMC::TokenFactory::JWT.new("looooooooooooongenoughtestsecret", jwt_lifetime: 4000) | ||
) { "ID" }) | ||
|
||
request = HTTP::Request.new("POST", "/") | ||
response = HTTP::Server::Response.new IO::Memory.new | ||
|
||
discovery = AMC::Discovery.new registry | ||
discovery.add_link request, response | ||
|
||
response.headers.get("link").should eq ["<https://example.com/.well-known/mercure>; rel=\"mercure\""] | ||
end | ||
end |
Oops, something went wrong.