diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..63d8f63 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ + + +# editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..991fc74 --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +# GitHub pages site +_gh_pages +_site +.ruby-version +.jekyll-cache + +# Numerous always-ignore extensions +*.diff +*.err +*.orig +*.log +*.rej +*.swo +*.swp +*.zip +*.vi +*~ + +# OS or Editor folders +.DS_Store +._* +Thumbs.db +.cache +.project +.settings +.tmproj +*.esproj +nbproject +*.sublime-project +*.sublime-workspace +.idea +.editorconfig + +# Komodo +*.komodoproject +.komodotools + +# grunt-html-validation +validation-status.json +validation-report.json + +# Folders to ignore +node_modules +bower_components diff --git a/404.md b/404.md new file mode 100644 index 0000000..c19a547 --- /dev/null +++ b/404.md @@ -0,0 +1,20 @@ +--- +layout: default +title: "404: Page not found" +permalink: 404.html +--- + + + +# 404: Page not found +Sorry, we've misplaced that URL or it's pointing to something that doesn't exist. [Head back home]({{ site.url }}) to try finding it again. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..af1b0ec --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,9 @@ +# Released under MIT License + +Copyright (c) 2014 Mark Otto. + +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. \ No newline at end of file diff --git a/README.md b/README.md index 9e0f23f..5f054f7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,22 @@ -# aranya-docs +# Aranya Docs -This repository contains documentation for the Aranya project. +This repository contains documentation for the Aranya project [website](https://aranya-project.github.io/aranya-docs/). Specs and documentation are written in [Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax), utilizing YAML [Front Matter](https://jekyllrb.com/docs/front-matter/) and then deployed to Github Pages using [Jekyll](https://jekyllrb.com/). We used the Jekyll theme from [lanyon](https://github.com/poole/lanyon) as the basis of the site. + +## Adding new pages +When you want to add a new spec or documentation page, you will create a new Markdown file in the `/docs` directory. This file will rely on [Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) for it's styling and hierarchy and [Front Matter](https://jekyllrb.com/docs/front-matter/) to communicate it processing information to Jekyll. + +The key Front Matter information that every file needs is +- `layout`: Layout tells Jekyll this is a `page` that should be included in the menu. +- `title`: Title tells Jekyll the value to use in the menu. +- `permalink`: Permalink tells Jekyll the value to use in the page slug. + +``` +--- +layout: page +title: "document-title" +permalink: /"url-slug"/ +--- +``` + +## Deploying the site +Deploying is easy, just merge a PR to the `main` branch. The documentation repo is configured to use Github's built in branch push actions to trigger builds and deploys to GH pages. The target branch and directory can be configured in the GH Pages settings section of the repo, if you need to test a deployment. Just note however, we don't have a proper staging environment, so these deployments will go live to the production github.io site. diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..59fe115 --- /dev/null +++ b/_config.yml @@ -0,0 +1,19 @@ +# Setup +title: Aranya Documentation +tagline: 'An overview of the Aranya project' +description: 'Aranya is an access governance and secure data exchange platform for organizations to control their critical data and services.' +url: https://aranya-project.github.io/aranya-docs +project-repo: https://github.com/aranya-project/aranya +baseurl: '' +permalink: pretty + +# Excludes +exclude: + - .editorconfig + - .git + - .jekyll-cache + - Gemfile + - Gemfile.lock + - LICENSE.md + - README.md + diff --git a/_includes/head.html b/_includes/head.html new file mode 100644 index 0000000..04a6570 --- /dev/null +++ b/_includes/head.html @@ -0,0 +1,53 @@ + + + + + + + + + + {% if page.title == "Home" %} + {{ site.title }} · {{ site.tagline }} + {% else %} + {{ page.title }} · {{ site.title }} + {% endif %} + + + {% if page.url and site.baseurl %} + + {% endif %} + + + + + + + + + + {% if site.google_analytics_id %} + + {% endif %} + + {% if page.mermaid %} + {% include mermaid.html %} + {% endif %} + diff --git a/_includes/mermaid.html b/_includes/mermaid.html new file mode 100644 index 0000000..2ac8128 --- /dev/null +++ b/_includes/mermaid.html @@ -0,0 +1,3 @@ + diff --git a/_includes/sidebar.html b/_includes/sidebar.html new file mode 100644 index 0000000..a7d3645 --- /dev/null +++ b/_includes/sidebar.html @@ -0,0 +1,64 @@ + + + + + + + diff --git a/_layouts/default.html b/_layouts/default.html new file mode 100644 index 0000000..4f369a1 --- /dev/null +++ b/_layouts/default.html @@ -0,0 +1,39 @@ + + + + + {% include head.html %} + + {% include sidebar.html %} + +
+
+
+

+ {{ site.title }} + {{ site.tagline }} +

+
+
+ +
+ {{ content }} +
+
+ + + + diff --git a/_layouts/page.html b/_layouts/page.html new file mode 100644 index 0000000..30d57e4 --- /dev/null +++ b/_layouts/page.html @@ -0,0 +1,19 @@ +--- +layout: default +--- + + + +
+ {{ content }} +
diff --git a/_layouts/post.html b/_layouts/post.html new file mode 100644 index 0000000..b49d30a --- /dev/null +++ b/_layouts/post.html @@ -0,0 +1,39 @@ +--- +layout: default +--- + + + +
+

{{ page.title }}

+ {{ page.date | date_to_string }} + {{ content }} +
+ +{% if site.related_posts.size >= 1 %} + +{% endif %} diff --git a/assets/images/aranya-logo-white.png b/assets/images/aranya-logo-white.png new file mode 100644 index 0000000..c46e48d Binary files /dev/null and b/assets/images/aranya-logo-white.png differ diff --git a/assets/images/icon-github-48.png b/assets/images/icon-github-48.png new file mode 100644 index 0000000..8fa82e5 Binary files /dev/null and b/assets/images/icon-github-48.png differ diff --git a/docs/images/new_daemon_arch_block.png b/assets/images/new_daemon_arch_block.png similarity index 100% rename from docs/images/new_daemon_arch_block.png rename to assets/images/new_daemon_arch_block.png diff --git a/docs/images/new_daemon_arch_block_detail.png b/assets/images/new_daemon_arch_block_detail.png similarity index 100% rename from docs/images/new_daemon_arch_block_detail.png rename to assets/images/new_daemon_arch_block_detail.png diff --git a/docs/images/overview-image1.png b/assets/images/overview-image1.png similarity index 100% rename from docs/images/overview-image1.png rename to assets/images/overview-image1.png diff --git a/docs/images/overview-image2.png b/assets/images/overview-image2.png similarity index 100% rename from docs/images/overview-image2.png rename to assets/images/overview-image2.png diff --git a/docs/images/overview-image3.png b/assets/images/overview-image3.png similarity index 100% rename from docs/images/overview-image3.png rename to assets/images/overview-image3.png diff --git a/docs/images/overview-image4.png b/assets/images/overview-image4.png similarity index 100% rename from docs/images/overview-image4.png rename to assets/images/overview-image4.png diff --git a/docs/images/overview-image5.png b/assets/images/overview-image5.png similarity index 100% rename from docs/images/overview-image5.png rename to assets/images/overview-image5.png diff --git a/docs/images/overview-image6.png b/assets/images/overview-image6.png similarity index 100% rename from docs/images/overview-image6.png rename to assets/images/overview-image6.png diff --git a/docs/images/overview-image7.png b/assets/images/overview-image7.png similarity index 100% rename from docs/images/overview-image7.png rename to assets/images/overview-image7.png diff --git a/assets/images/spideroak-logo-white.png b/assets/images/spideroak-logo-white.png new file mode 100644 index 0000000..20a3fc1 Binary files /dev/null and b/assets/images/spideroak-logo-white.png differ diff --git a/atom.xml b/atom.xml new file mode 100644 index 0000000..207890a --- /dev/null +++ b/atom.xml @@ -0,0 +1,40 @@ +--- +layout: null +--- + + + + + + + {{ site.title }} + + + {{ site.time | date_to_xmlschema }} + {{ site.url }} + + {{ site.author.name }} + {{ site.author.email }} + + + {% for post in site.posts %} + + {{ post.title }} + + {{ post.date | date_to_xmlschema }} + {{ site.url }}{{ site.baseurl }}{{ post.id }} + {{ post.content | xml_escape }} + + {% endfor %} + + diff --git a/docs/afc-crypto.md b/docs/afc-crypto.md index 251e7a1..049f57f 100644 --- a/docs/afc-crypto.md +++ b/docs/afc-crypto.md @@ -1,6 +1,12 @@ +--- +layout: page +title: AFC Cryptography +permalink: "/afc-cryptography/" +--- + # AFC Cryptography -# Overview +## Overview Aranya Fast Channels (AFC) is a low latency, high throughput encryption engine that uses Aranya for key management and @@ -14,7 +20,7 @@ Encryption is scoped to a particular _channel_, which supports one-to-one communication in either a unidirectional or bidirectional manner. -# Notation +## Notation - `"abc"`: A byte string containing the UTF-8 characters between the double quotation marks (`"`). @@ -29,7 +35,7 @@ bidirectional manner. - `ALG_Op(...)`: A cryptographic algorithm routine. E.g., `AEAD_Seal(...)`, `HPKE_OneShotSeal(...)`, etc. -# Design +## Design Conceptually, AFC implements this interface: @@ -54,12 +60,12 @@ ensuring that both channel users meet some specified criteria. > **Note**: For performance reasons, users and labels are mapped > to 32-bit integers. -## Bidirectional Channels +### Bidirectional Channels Bidirectional channels allow both users to encrypt and decrypt data. Generally speaking, they're the default channel type. -### Cryptography +#### Cryptography Each bidirectional channel has two unique symmetric [AEAD] keys, (k1, k2), called the _ChannelKeys_. One side of the channel uses @@ -68,7 +74,7 @@ k2 for encryption and k1 for decryption. The key used for encryption is referred to as the _SealKey_ and the key used for decryption is referred to as the _OpenKey_. -#### Key Derivation +##### Key Derivation ChannelKeys are derived using HPKE's Secret Export API. @@ -76,7 +82,7 @@ For domain separation purposes, the key derivation scheme includes both UserIDs. Additionally, in order to prevent duplicate ChannelKeys (from a buggy CSPRNG), it mixes in the ID of the command that created the channel. (Command IDs are assumed -to be unique; for more information, see the [Aranya spec](aranya-beta.md).) +to be unique; for more information, see the [Aranya spec]({{ site.url }}/aranya-beta).) The key derivation scheme is as follows: @@ -145,18 +151,18 @@ fn DecryptChannelKeys(enc, us, peer, parent_cmd_id, label) { } ``` -## Unidirectional Channels +### Unidirectional Channels Unidirectional channels allow one user to encrypt and one user to decrypt. -### Cryptography +#### Cryptography Each unidirectional channel has one unique symmetric [AEAD] key. The side that encrypts calls this the _SealOnlyKey_ and the side that decrypts calls this the _OpenOnlyKey_. -#### Key Derivation +##### Key Derivation The SealOnlyKey/OpenOnlyKey is derived using HPKE's Secret Export API. @@ -165,7 +171,7 @@ For domain separation purposes, the key derivation scheme includes both UserIDs. Additionally, in order to prevent duplicate keys (from a buggy CSPRNG), it mixes in the ID of the command that created the channel. (Command IDs are assumed to be -unique; for more information, see the [Aranya spec](aranya-beta.md).) +unique; for more information, see the [Aranya spec]({{ site.url }}/aranya-beta).) The key derivation scheme is as follows: @@ -232,12 +238,12 @@ fn DecryptOpenOnlyKey(enc, us, peer, parent_cmd_id, label) { } ``` -## Cryptography +### Cryptography Outside of key derivation, the remaining cryptography is identical for both channel types. -### Message Encryption +#### Message Encryption AFC encrypts each message with a uniformly random nonce generated by a CSPRNG. @@ -280,16 +286,16 @@ fn Open(user, label, ciphertext) { } ``` -### Key Usage +#### Key Usage Each encryption key must not be used more than allowed by the underlying AEAD (i.e., it should respect the AEAD's lifetime). **The current specification does not require AFC to track how much a particular key is used. This will change in the future.** -### Algorithms +#### Algorithms -#### AEAD +##### AEAD Briefly, [AEAD] encryption is a construction with four inputs: @@ -318,14 +324,14 @@ include [AES-256-GCM], [ChaCha20-Poly1305], and [Ascon]. It is highly recommended to use a nonce misuse-resistant AEAD, like [AES-GCM-SIV]. -### Committing AEAD +#### Committing AEAD A _committing_ AEAD is an AEAD that binds the authenticator to one or more of the AEAD inputs. For more information, see [Efficient Schemes for Committing Authenticated Encryption][Bellare]. -#### KDF +##### KDF An extract-then-expand Key Derivation Function (KDF) as formally defined in section 3 of [HKDF]. @@ -340,7 +346,7 @@ The KDF must: > from passwords. In other words, it does not need to be a "slow" > KDF like PBKDF2. -#### HPKE +##### HPKE Hybrid Public Key Encryption (HPKE) per [RFC 9180]. diff --git a/docs/afc.md b/docs/afc.md index b75b644..c2dd9f2 100644 --- a/docs/afc.md +++ b/docs/afc.md @@ -1,3 +1,9 @@ +--- +layout: page +title: Aranya Fast Channels +permalink: "/afc/" +--- + # Aranya Fast Channels Data Routing For Aranya Beta V2 ## Overview diff --git a/docs/aranya-beta.md b/docs/aranya-beta.md index d32d0e5..b64e470 100644 --- a/docs/aranya-beta.md +++ b/docs/aranya-beta.md @@ -1,19 +1,19 @@ --- -title: Aranya Spec V1 -taxonomy: - category: docs +layout: page +title: Aranya Beta +permalink: "/aranya-beta/" --- -# Aranya V1 +# Aranya Beta ## Introduction -A product specification for version 1 of the standalone Aranya daemon. The goal +A product specification of the standalone Aranya daemon. The goal is to provide a commercial off the shelf solution for integrating Aranya. Customers -should be able to download the Aranya daemon, setup a team, and begin using Aranya. +should be able to download the Aranya daemon, setup a team, and begin using Aranya. Primary Goals: -1. Provide a low friction solution customers can use to better secure their infrastructure. +1. Provide a low friction solution customers can use to better secure their infrastructure. 2. Easily setup a team and onboard users. 3. Implement a default policy that works well enough for a wide variety of situations that customers can relate to. @@ -64,12 +64,12 @@ Both subsystems are limited to a single KeyBundle per process to limit the possi leaking device keys. This still enables the daemon to participate in multiple teams by using the same keybundle for every team. -![daemon subsystems](images/new_daemon_arch_block.png) +daemon subsystems -![daemon subsystems with additional detail](images/new_daemon_arch_block_detail.png) +daemon subsystems with additional detail For V1, we will initially design for the C API. The Rust client library is a bonus that -we gain from the Rust -> C compilation. The C API should include autogenerated docs. +we gain from the Rust -> C compilation. The C API should include autogenerated docs. **Config** @@ -88,7 +88,7 @@ The "client API" is the top level component and contains local operations such a enabling/disabling syncing or initializing a new device. The Access Control Plane is the top level control plane, enabling IDAM operations -and other on-graph operations. The Access Control Plane is used to manage keys, +and other on-graph operations. The Access Control Plane is used to manage keys, address assignment, roles, and labels as set out in the policy. For the beta, the AFC Plane will be build to provide a simple API for sending @@ -116,8 +116,8 @@ Component structure: The client APIs are local only API endpoints that do not create commands on the graph. They are mostly used to manage the local state. -- InitDevice() -> device_id - init a device if not exists, generate keys, -create the API instance, etc. +- InitDevice() -> device_id - init a device if not exists, generate keys, +create the API instance, etc. - GetKeyBundle() -> keybundle - returns the current device's public key bundle. - GetDeviceId() -> device_id - returns the device's device ID. - AddTeam(team_id) -> bool - add an existing team to the local device store. Not an Aranya action/command. @@ -125,7 +125,7 @@ create the API instance, etc. Aranya action/command. - SerializeKeyBundle(scheme, keybundle) -> bytes - serialize a keybundle to a given format/scheme. Ideally, this can be used to serialize to either human readable or -machine readable formats. +machine readable formats. - DeserializeKeyBundle(scheme, bytes) -> keybundle - desrialize a keybundle from a given format/scheme. - SerializeId(scheme, id) -> bytes - serialize an ID to a given format/scheme. @@ -168,8 +168,8 @@ returns the device ID of the device that joined using that code. - Join(server address, Invite Code, Device Key) -> Result - join a team using an invite code, returns the team_id of the team that was joined. -The goal of this onboarding API is to simplify the process of onboarding a -user/device by providing an invite code instead of passing a KeyBundle. +The goal of this onboarding API is to simplify the process of onboarding a +user/device by providing an invite code instead of passing a KeyBundle. ### IDAM Control Plane API @@ -184,7 +184,7 @@ the author as the owner. - RemoveDevice(team_id, device_id) - remove a device from the team - AssignRole(team_id, device_id, role) -> bool - assign a role to a device - RevokeRole(team_id, device_id, role) -> bool - remove a role from a device -- AssignNetworkName(team_id, device_id, net_identifier) -> bool - associate a network address to a device +- AssignNetworkName(team_id, device_id, net_identifier) -> bool - associate a network address to a device for use with AFC. If the address already exists for this device, it is replaced with the new address. Capable of resolving addresses via DNS, required to be statically mapped to IPv4. For use with CreateChannel and receiving messages. Can take either DNS name or IPv4. MVP @@ -280,7 +280,7 @@ and behavior of each API call. Most likely, this will take the form of a doxygen web page. Developers can use this to look up language agnostic functions for operating the client API. The documentation should also include tutorials and a quickstart to get developers up and running with Aranya as soon as possible. Documentation -should also be provided for the daemon so that developers and sysadmins can +should also be provided for the daemon so that developers and sysadmins can understand the requirements and operations of the daemon. diff --git a/docs/aranya-overview.md b/docs/aranya-overview.md index a5fd1d4..10b4112 100644 --- a/docs/aranya-overview.md +++ b/docs/aranya-overview.md @@ -1,3 +1,11 @@ +--- +layout: page +title: Overview +permalink: "/overview/" +tags: [Mermaid] +mermaid: true +--- + # ARANYA OVERVIEW **Table of Contents** @@ -127,7 +135,7 @@ To ensure that participating entities are acting over a common authoritative sta Other core components of our system that enable the control plane and data plane functionalities are detailed below and can be seen in Figure 1 below. -A diagram of a software application Description automatically generated +A diagram of a software application Description automatically generated _Figure 1: System Architecture Overview Diagram_ @@ -183,7 +191,7 @@ All endpoints participating in the graph will receive all updates to the graph r The general workflow for exchanging control plane commands on-graph across two endpoints can be seen below in Figure 2. This workflow assumes a policy has been written and validated for all actions desired in the architecture. -A diagram of a diagram Description automatically generated +A diagram of a diagram Description automatically generated _Figure 2: General On-Graph Workflow_ @@ -223,7 +231,13 @@ Policy evaluation in Aranya relies on the set of facts stored in the fact databa To call an Action, the entity will follow the following process: -A diagram of a command Description automatically generated +
+flowchart TB + A(Entity) -- Calls an action --> B(Action) -- Action issues Command --> C(Command) -- Command is evaluated by the policy --> D(Policy) + D -- Command is accepted --> E(Accepted) + D -- Command is rejected --> F(Rejected) + E -- Command is stored on graph and (potentially) updates FactDB -->H(Graph) +
_Figure 3: Calling an Action Workflow_ @@ -231,7 +245,16 @@ _Figure 3: Calling an Action Workflow_ To sync with a peer or other entity using Aranya, the entity will follow the following process: -A diagram of a system Description automatically generated +
+flowchart TB + I(Entity) -- Sync with peer --> J(Sync) + J -- Sync sends new command --> K(Peer) + K -- Command is evaluated by the policy --> L(Policy) + L -- Command is accepted --> M(Accepted) + L -- Command is rejected --> N(Recalled) + M --> O(Graph) + N --> O +
_Figure 4: Syncing with a Peer Workflow_ @@ -253,7 +276,7 @@ A channel is used to group together a fixed number of devices based on specific Once the command is validated, the crypto engine generates an encryption key associated with the entity and exposes it through shared memory. If the channel is specified as unidirectional, the entity creating the channel is only assigned an encryption key. If the channel is bidirectional, the entity will also be assigned a decryption key. Aranya stores the key(s) in its own database and associates the key or key pair with this specific channel for this specific entity. After the channel creator's keys have been assigned, a "create channel" command is sent to the specified receiver. Like the process for the initial sender entity, the command is processed by the receiver's associated policy and the crypto engine generates a decryption key (if unidirectional), or encryption/decryption keys (if bidirectional). After the sender and receiver have both processed the "create channel" command, they are free to send and receive messages over their new channel and no further messages will be processed by their policy. -A diagram of a system Description automatically generated +A diagram of a system Description automatically generated _Figure 5: Workflow when creating a Channel_ @@ -263,7 +286,7 @@ To send data over the channel, an entity will prepare the bytes to submit to the While channels are one-to-one, a policy may define rules for an entity to send messages to multiple other entities over individual channels. This is facilitated by topic labels, which are defined in a policy and act on the permission system. A label is assigned to entities that want to communicate under a specific topic and a channel can only be created for entities assigned to that same topic. Labels cannot be used to send a message to more than one entity as they are specifically used by policy to allow two entities to talk to each other using that label (if both points have that label assigned to them). -A blue rectangular sign with white text Description automatically generated +A blue rectangular sign with white text Description automatically generated _Figure 6: Workflow to Send Data on a Channel_ @@ -287,7 +310,7 @@ Aranya provides interfaces for secure peer-to-peer data exchange, guaranteeing d Figure 7 below outlines the data flow between two endpoints, both with an Aranya instance and an application which will utilize the data. The two instances will leverage any transport that has been configured between the endpoints to exchange data via either the sync protocol (on-graph) or a high-throughput data exchange (off-graph), both defined below. -A diagram of a data transfer Description automatically generated +A diagram of a data transfer Description automatically generated _Figure 7: Endpoint Integration Overview Diagram_ diff --git a/docs/aranya-sessions-note.md b/docs/aranya-sessions-note.md index b3d4254..e29bfb7 100644 --- a/docs/aranya-sessions-note.md +++ b/docs/aranya-sessions-note.md @@ -1,10 +1,12 @@ --- -title: Aranya Sessions Design Note -taxonomy: - category: docs +layout: page +title: Aranya Sessions +permalink: "/aranya-sessions/" --- -## Rational +# Aranya Sessions Design Note + +## Rational We would like to allow off graph data to be evaluated by policy at some perspective in the graph. This will be useful for session oriented APIs such as AFC and the light-weight-protocol. These off graph protocols allow us to leverage the strong trust built in the graph at a lower cost than storing the data on graph. Storing on graph is costly especially for data that is not relevant to all nodes because the graph is distributed to all nodes. For example, AFC supports point to point communication and only the two end points require the session data. @@ -13,15 +15,15 @@ These off graph protocols allow us to leverage the strong trust built in the gra Any data which effects the persisted fact database must be on the graph, therefore while we can update the facts in a session it is not possible to persist those changes. ## Design -At the highest level an Aranya Sessions is just perspectives which can not be committed. +At the highest level an Aranya Sessions is just perspectives which can not be committed. In addition, the order of commands is defined by the order that they are added to the perspective instead of graph ordering. Because of this commands added to a Session Perspective need not have a parent field, though for simplicity our initial implementation may retain this feature. To make use of this Session Perspective some additional changes to the API will be required. ### Add Command -The Aranya Client currently can only accept new commands via `sync_receive()` or by calling `action()`. This leaves us with no easy way to add commands from a remote device to a session perspective. To fix this we should add an `add_command()` method to the Aranya client that attempts to add a command to a session perspective. +The Aranya Client currently can only accept new commands via `sync_receive()` or by calling `action()`. This leaves us with no easy way to add commands from a remote device to a session perspective. To fix this we should add an `add_command()` method to the Aranya client that attempts to add a command to a session perspective. ### Session Sinks -Other Aranya Client APIs take `Sink` which contains a call back to return `Effects`, Session Sinks must also contain a call back to return commands. This is strictly required for `Actions` on a session as any generated commands will not be added to the graph. It will also be useful for indicating success for adding commands received from remote parties. +Other Aranya Client APIs take `Sink` which contains a call back to return `Effects`, Session Sinks must also contain a call back to return commands. This is strictly required for `Actions` on a session as any generated commands will not be added to the graph. It will also be useful for indicating success for adding commands received from remote parties. diff --git a/docs/graph.md b/docs/graph.md index d96d7e4..41b04dd 100644 --- a/docs/graph.md +++ b/docs/graph.md @@ -1,9 +1,11 @@ --- +layout: page title: Graph Auth -taxonomy: - category: docs +permalink: "/graph-auth/" --- +# Graph + Disclaimer: this document is somewhat out-of-date and incomplete. ## Introduction @@ -78,7 +80,7 @@ A state in Aranya is a set of Commands. For example if we had the command instan Given a deterministic function which provides a total ordering over any set of Commands this approach can be used to build a distributed system which supports eventual consistency. -Most blockchains in productions use an ordering approach that is provided by a partial deterministic ordering augmented by a Validator, a miner in blockchain parlance, which extends the deterministic partial order into a total order. +Most blockchains in productions use an ordering approach that is provided by a partial deterministic ordering augmented by a Validator, a miner in blockchain parlance, which extends the deterministic partial order into a total order. In this approach Validators have large amounts of freedom over the total order. For financial transactions between mutual distrusting parties, this is workable as the prevention of double spending requires a global consistent state and the freedom of the miners to produce different orderings can largely be ignored. (Though Miner Extractable Value, MEV, is still an issue that is exacerbated by this flexibility in ordering.) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..d0df436 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,31 @@ +--- +layout: page +title: Home +permalink: "/" +--- + +# What is Aranya? +Aranya is an access governance and secure data exchange platform for organizations to control their critical data and services. Access governance is a mechanism to define, enforce, and maintain the set of rules and procedures to secure your system’s behaviors. Data governance is a more narrowed approach for applying the same mechanism to specifically address access permissions of all data in the system. + +## What can Aranya do? +Aranya is useful anywhere you have data that needs to be reconciled, sent securely, or restricted. Possible uses could be sending data between IoT devices, files or messages between users, encryption keys between computer systems or information between embedded devices. Anywhere you have a need for permission management, such as Identity and Access Management (IdAM). Aranya gives you the ability to apply access controls over stored or shared resources all in one place. + +- [Aranya Overview](overview): The Aranya Overview discussed Aranya at a high level. This would be a great place to start. + +- [Aranya Beta](aranya-beta): Aranya Beta is the product specification of the standalone Aranya daemon. + +- [Aranya Fast Channels](afc): Aranya Fast Channels is a way for application data to be sent over the network to other peers using cryptography to ensure security. + +- [Aranya Fast Channels Cryptography](afc-cryptography): Discusses the design of the cryptography used in Aranya Fast Channels. + +- [Aranya Sessions](aranya-sessions): Aranya Sessions allow off graph data to be evaluated by the policy. + +- [Policy Language v1](policy-language-v1): The v1 spec for the policy language discusses the initial release of our policy language. + +- [Policy Language v2](policy-language-v2): The v2 spec outlines changes made to the v1 spec. + +- [Sync](sync): Sync discusses our approach to syncing commands across graphs. + +- [Open Sync Requests](open-sync-requests): Open Sync Requests discusses our pushed based approach to syncing. + +- [Aranya Model](aranya-model): The Aranya Model is a simple tool for simulating or testing the Aranya runtime under different scenarios. diff --git a/docs/model.md b/docs/model.md index 7ebf0d7..8d2b0a0 100644 --- a/docs/model.md +++ b/docs/model.md @@ -1,8 +1,13 @@ --- +layout: page title: Aranya Model -taxonomy: - category: docs +permalink: "/aranya-model/" +tags: [Mermaid] +mermaid: true --- + +# Aranya Model + ## Introduction When simulating or testing Aranya we need a model that can be used to test the behavior of Aranya under different scenarios. The Aranya Model is the answer to this need. @@ -14,7 +19,7 @@ The APIs use simple user generated segregate Proxy IDs for the public client and ## Client State Each client in the model can be thought of as an instance of Aranya Client State or the state of its graph. To configure an Aranya client, we need to look at its component parts. -```mermaid +
flowchart A(Policy Document) --> B(Policy Parser) B --> C(Policy Compiler) @@ -26,12 +31,12 @@ Each client in the model can be thought of as an instance of Aranya Client State F --> I(Policy Engine) I --> K(Client State) J(Storage Provider) --> K -``` +
By far the biggest portion of the Client State configuration is the VMPolicy. The VMPolicy is made up of the Policy VM, Crypto Engine, and Policy FFI configuration. The Policy VM will take a compiled policy module and convert it into an executable policy which we can evaluate commands against. Let's look at how Aranya takes a raw policy markdown document and processes it into the Policy VM. ### Policy document -To execute actions in the model, we need to write a custom policy [see policy-v1](policy-v1.md). Policies are markdown files that outline the accepted actions that can be issued and the corresponding commands that will be generated. Successful commands will emit effects and/or write facts to the graph. The database or storage mechanism used by the factDB will be an implementation detail. The following is a basic overview of the parts that make up a policy. +To execute actions in the model, we need to write a custom policy [see policy-v1]({{ site.url }}/policy-language-v1). Policies are markdown files that outline the accepted actions that can be issued and the corresponding commands that will be generated. Successful commands will emit effects and/or write facts to the graph. The database or storage mechanism used by the factDB will be an implementation detail. The following is a basic overview of the parts that make up a policy. #### Policy vocabulary *Action*: An action is a user generated function defined in the policy language that can emit commands. Actions create new commands and insert them into the local database. When new commands arrive (from either local creation, or synced from other nodes), the policy for those commands is evaluated, which may produce fact changes and effects. Actions can be thought of as providing a contract (along with effects) to the application which is implemented by the policy. The commands in actions are atomic in nature, only succeeding if all the command policy checks succeed. @@ -75,23 +80,24 @@ The client state keeps track of the state of the graph in the runtime client. Th ## Model Client -```mermaid +
flowchart A(Client State) --> B(Model Client) C(Public Keys) --> B -``` +
+ Once we have the required configurations to build the client state, we can construct a model client. A model client is constructed using the client factory, which holds the client state configuration. When the `add_client` method is called on the model instance, it will create and add a model client to the model `clients` collection. ### Public Keys When key bundles are created, they are limited in scope to the factory in which they are instantiated. We need to use the public key portion of the bundle to interact with various parts of the policy. Because of this we store them as part of the model client. ## Runtime Model -```mermaid +
flowchart A(clients) --> B(RuntimeModel) C(storage_ids) --> B D(client_factory) --> B -``` +
Once we have the model client established, we have the pieces we need to construct the runtime model. The model consists of a collection of constructed Model Clients, Storage IDs, and our Client Factory function. diff --git a/docs/open-sync.md b/docs/open-sync.md index 8ab3dbf..655aaeb 100644 --- a/docs/open-sync.md +++ b/docs/open-sync.md @@ -1,18 +1,20 @@ --- +layout: page title: Open Sync Requests -taxonomy: - category: docs +permalink: "/open-sync-requests/" --- +# Open Sync Requests + ## Problem We currently sync by polling. This can be very inefficient when new commands -are infrequent. We want to allow for pushing new commands to peers. This +are infrequent. We want to allow for pushing new commands to peers. This will significantly reduce the overhead of syncing. ## Design -Syncing needs to work over many network protocols. We don't want to rely on +Syncing needs to work over many network protocols. We don't want to rely on anything beyond being able to send a payload of bytes. So the different sync calls will be distinguished by an enum value. @@ -23,7 +25,7 @@ pub enum SyncType { Poll { request: SyncRequestMessage, }, - // Subscribes the peer to receive push syncs from this peer. Calling this + // Subscribes the peer to receive push syncs from this peer. Calling this // again will update remain_open and max_bytes for this peer. Subscribe { // The number of seconds the sync request will remain open. @@ -47,7 +49,7 @@ pub enum SyncTypeError { } ``` -### Subscribe +### Subscribe The following data is stored for each open subscription. Each peer can have at most one open subscription. @@ -56,10 +58,10 @@ most one open subscription. struct Subscription { // The time to close the request. The subscription should be closed when the // time is greater than the close time. - // Calculated by adding remain open seconds to the time when the + // Calculated by adding remain open seconds to the time when the // request was made. close_time: SystemTime - // The number of remaining bytes to send. Every time a Push request is + // The number of remaining bytes to send. Every time a Push request is // sent this will be updated with the number of bytes sent. remaining_bytes: u64, } @@ -68,8 +70,8 @@ struct Subscription { const MAXIMUM_OPEN_REQUESTS: usize = 128 ``` -Open subscriptions will be stored in a `heapless::FnvIndexMap` with a maximum size of -`MAXIMUM_OPEN_REQUESTS`. If there are more than `MAXIMUM_OPEN_REQUESTS`, new requests +Open subscriptions will be stored in a `heapless::FnvIndexMap` with a maximum size of +`MAXIMUM_OPEN_REQUESTS`. If there are more than `MAXIMUM_OPEN_REQUESTS`, new requests will be ignored, and a `MaximumSubscriptionExceeded` error will be returned. ### Unsubscribe @@ -78,12 +80,12 @@ Closes the open subscription by removing it from the subscriptions map. ### Push -Every time a new command is committed to the graph a push will be sent to -each peer with an open request, and the bytes sent and stored heads for the +Every time a new command is committed to the graph a push will be sent to +each peer with an open request, and the bytes sent and stored heads for the peer will be updated based on the commands that were sent. -A callback will be added to `ClientState` that is called after -`ClientState::action` and `ClientState::commit`. This callback will be +A callback will be added to `ClientState` that is called after +`ClientState::action` and `ClientState::commit`. This callback will be responsible for sending the `Push` to all subscribed peers. ### Quic Syncer @@ -94,7 +96,7 @@ responsible for sending the `Push` to all subscribed peers. `run_syncer` will match the `SyncType` and route the requests. -`SyncType::Poll` will be routed to the existing `handle_connection` function. +`SyncType::Poll` will be routed to the existing `handle_connection` function. `SyncType::Subscribe` will add or update `open_requests`. `SyncType::Unsubscribe` will remove a key from `open_subscriptions` if it exists. -`SyncType::Push` will call `syncer.receive` with the provided commands. +`SyncType::Push` will call `syncer.receive` with the provided commands. diff --git a/docs/policy-v1.md b/docs/policy-v1.md index a00b7b5..ad5b851 100644 --- a/docs/policy-v1.md +++ b/docs/policy-v1.md @@ -1,9 +1,11 @@ --- +layout: page title: Policy Language v1 -taxonomy: - category: docs +permalink: "/policy-language-v1/" --- +# Policy Language v1 + ## File format The policy document is a Markdown document with YAML front matter (as diff --git a/docs/policy-v2.md b/docs/policy-v2.md index f7fa75a..2e9b687 100644 --- a/docs/policy-v2.md +++ b/docs/policy-v2.md @@ -1,10 +1,12 @@ --- +layout: page title: Policy Language v2 -taxonomy: - category: docs +permalink: "/policy-language-v2/" --- -All elements of [Policy Language v1](policy-v1.md) not modified by this document are unchanged. +# Policy Language v2 + +All elements of [Policy Language v1]({{ site.url }}/policy-language-v1) not modified by this document are unchanged. ## File Format @@ -254,4 +256,4 @@ struct Bar { action frob(x Bar) { let f = x substruct Foo } -``` \ No newline at end of file +``` diff --git a/docs/skiplist.md b/docs/skiplist.md index 103c1c4..6bd6632 100644 --- a/docs/skiplist.md +++ b/docs/skiplist.md @@ -1,16 +1,18 @@ --- -title: Skip List Spec -taxonomy: - category: docs +layout: page +title: Skip List +permalink: "/skip-list/" --- +# Skip List + ## Max Cut -The maximum cut of a command is the maximum number of steps it takes to get from a command to the root. The init command is the root and has a max cut of 0. The max cut of a command is one +The maximum cut of a command is the maximum number of steps it takes to get from a command to the root. The init command is the root and has a max cut of 0. The max cut of a command is one more than the maximum max cut of the parent commands. -Here are the max cuts for the Init command, a command with a single parent, and a -merge command with two parents. +Here are the max cuts for the Init command, a command with a single parent, and a +merge command with two parents. ``` Init: 0 @@ -21,24 +23,24 @@ Merge(l, r): max(max_cut(l), max_cut(r)) + 1 ## Skip List A skip list allows O(m log n) average complexity for finding commands. -Where m is the average number of branches at a given max cut and n is the -number of segments in the graph. Typically m should be less than 2. +Where m is the average number of branches at a given max cut and n is the +number of segments in the graph. Typically m should be less than 2. Each segment will have skips to segments further towards the root of the -graph. A skip is a reference to a segment. When a segment is created we will -randomly generate three different max cuts that are less than the new segment's -max cut. The segment will then have skips to segments with matching max cuts. -This will usually result in three skips, but it can be less if two different -max cuts reference the same segment. +graph. A skip is a reference to a segment. When a segment is created we will +randomly generate three different max cuts that are less than the new segment's +max cut. The segment will then have skips to segments with matching max cuts. +This will usually result in three skips, but it can be less if two different +max cuts reference the same segment. A skip can't jump into a branch. If this was allowed we'd have to backtrack -to find missed branches. A skip can jump back to the last merge -command and it can jump over the branches to a common ancestor. In the -following graph segment Segment(I, J) could jump to Segment(A, B, C), but it -could not jump to Segment(D, E, F), or Segment(G, H). Segment(D, E, F) could +to find missed branches. A skip can jump back to the last merge +command and it can jump over the branches to a common ancestor. In the +following graph segment Segment(I, J) could jump to Segment(A, B, C), but it +could not jump to Segment(D, E, F), or Segment(G, H). Segment(D, E, F) could jump to Segment(A, B, C). -Each merge command will have a skip to the last common ancestor. So Segment(I, J) +Each merge command will have a skip to the last common ancestor. So Segment(I, J) will have a skip to Segment(A, B, C). ### Example @@ -46,7 +48,7 @@ will have a skip to Segment(A, B, C). ``` D(3) - E(4) - F(5) / \ -A(0) - B(1) - C(2) I(6) - J(7) +A(0) - B(1) - C(2) I(6) - J(7) \ / G(3) - H(4) ``` @@ -70,21 +72,21 @@ Segment { }, ] }, -``` +``` ### Search algorithm -Search will start at the head of the graph. If the max cut of the command is within -the head segment that segment will be checked. If the command is found it will be -returned. If the command is not found, the skip segment with lowest low_max_cut that is +Search will start at the head of the graph. If the max cut of the command is within +the head segment that segment will be checked. If the command is found it will be +returned. If the command is not found, the skip segment with lowest low_max_cut that is >= to the command's max cut will be checked. If there is no skip segment to check the segments parent will be checked. If a merge command is reached both -parent segments will be added to the list of heads. The algorithm will then be +parent segments will be added to the list of heads. The algorithm will then be repeated with all of the added heads. This will result in quickly skipping deeply into the graph. When the search gets -close and can no longer skip it'll fall back to checking segments and if a -segment is reached whose low_max_cut is less than the commands max cut, then the -search will back up and continue from a previous head. +close and can no longer skip it'll fall back to checking segments and if a +segment is reached whose low_max_cut is less than the commands max cut, then the +search will back up and continue from a previous head. ```rust fn locate(id: Id, max_cut: usize) -> Some(Command) { @@ -113,4 +115,4 @@ fn locate(id: Id, max_cut: usize) -> Some(Command) { } None } -``` \ No newline at end of file +``` diff --git a/docs/sync.md b/docs/sync.md index 5281eb3..5248a0c 100644 --- a/docs/sync.md +++ b/docs/sync.md @@ -1,9 +1,11 @@ --- +layout: page title: Sync -taxonomy: - category: docs +permalink: "/sync/" --- +# Sync + ## Problem We want to minimize the size of sync requests while not sending @@ -231,7 +233,7 @@ this command will always be sent. No commands will be sent which are ancestors of the stored command. The last known command will be the command with the highest max cut that we -know the peer has. We will update the last known command when receiving a sync +know the peer has. We will update the last known command when receiving a sync request or a sync response. The last known commands will be stored in memory. And can be recreated after diff --git a/public/css/lanyon.css b/public/css/lanyon.css new file mode 100644 index 0000000..b0b42b8 --- /dev/null +++ b/public/css/lanyon.css @@ -0,0 +1,558 @@ +/* + * ___ + * /\_ \ + * \//\ \ __ ___ __ __ ___ ___ + * \ \ \ /'__`\ /' _ `\/\ \/\ \ / __`\ /' _ `\ + * \_\ \_/\ \_\.\_/\ \/\ \ \ \_\ \/\ \_\ \/\ \/\ \ + * /\____\ \__/.\_\ \_\ \_\/`____ \ \____/\ \_\ \_\ + * \/____/\/__/\/_/\/_/\/_/`/___/> \/___/ \/_/\/_/ + * /\___/ + * \/__/ + * + * Designed, built, and released under MIT license by @mdo. Learn more at + * https://github.com/poole/lanyon. + */ + + +/* + * Contents + * + * Global resets + * Masthead + * Sidebar + * Slide effect + * Posts and pages + * Pagination + * Reverse layout + * Themes + */ + + +/* + * Global resets + * + * Update the foundational and global aspects of the page. + */ + +/* Prevent scroll on narrow devices */ +html, +body { + overflow-x: hidden; +} + +html { + font-family: "PT Serif", Georgia, "Times New Roman", serif; +} + +h1, h2, h3, h4, h5, h6 { + font-family: "PT Sans", Helvetica, Arial, sans-serif; + font-weight: 400; + color: #313131; + letter-spacing: -.025rem; +} + + +/* + * Wrapper + * + * The wrapper is used to position site content when the sidebar is toggled. We + * use an outter wrap to position the sidebar without interferring with the + * regular page content. + */ + +.wrap { + position: relative; + width: 100%; +} + + +/* + * Container + * + * Center the page content. + */ + +.container { + max-width: 28rem; +} +@media (min-width: 38em) { + .container { + max-width: 32rem; + } +} +@media (min-width: 56em) { + .container { + max-width: 38rem; + } +} + + +/* + * Masthead + * + * Super small header above the content for site name and short description. + */ + +.masthead { + padding-top: 1rem; + padding-bottom: 1rem; + margin-bottom: 3rem; + border-bottom: 1px solid #eee; +} +.masthead-title { + margin-top: 0; + margin-bottom: 0; + color: #505050; +} +.masthead-title a { + color: #505050; +} +.masthead-title small { + font-size: 75%; + font-weight: 400; + color: #c0c0c0; + letter-spacing: 0; +} + +@media (max-width: 48em) { + .masthead-title { + text-align: center; + } + .masthead-title small { + display: none; + } +} + + +/* + * Sidebar + * + * The sidebar is the drawer, the item we are toggling with our handy hamburger + * button in the corner of the page. + * + * This particular sidebar implementation was inspired by Chris Coyier's + * "Offcanvas Menu with CSS Target" article, and the checkbox variation from the + * comments by a reader. It modifies both implementations to continue using the + * checkbox (no change in URL means no polluted browser history), but this uses + * `position` for the menu to avoid some potential content reflow issues. + * + * Source: http://css-tricks.com/off-canvas-menu-with-css-target/#comment-207504 + */ + +/* Style and "hide" the sidebar */ +.sidebar { + position: fixed; + top: 0; + bottom: 0; + left: -14rem; + width: 14rem; + visibility: hidden; + overflow-y: auto; + font-family: "PT Sans", Helvetica, Arial, sans-serif; + font-size: .875rem; /* 15px */ + color: rgba(255,255,255,.6); + background-color: #202020; + -webkit-transition: all .3s ease-in-out; + transition: all .3s ease-in-out; +} +@media (min-width: 30em) { + .sidebar { + font-size: .75rem; /* 14px */ + } +} + +/* Sidebar content */ +.sidebar a { + font-weight: normal; + color: #fff; +} +.sidebar-item { + padding: 1rem; +} +.sidebar-item p:last-child { + margin-bottom: 0; +} + +/* Sidebar nav */ +.sidebar-nav { + border-bottom: 1px solid rgba(255,255,255,.1); +} +.sidebar-nav-item { + display: block; + padding: .5rem 1rem; + border-top: 1px solid rgba(255,255,255,.1); +} +.sidebar-nav-item.active, +a.sidebar-nav-item:hover, +a.sidebar-nav-item:focus { + text-decoration: none; + background-color: rgba(255,255,255,.1); + border-color: transparent; +} + +@media (min-width: 48em) { + .sidebar-item { + padding: 1.5rem; + } + .sidebar-nav-item { + padding-left: 1.5rem; + padding-right: 1.5rem; + } +} + +/* Hide the sidebar checkbox that we toggle with `.sidebar-toggle` */ +.sidebar-checkbox { + position: absolute; + opacity: 0; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +/* Style the `label` that we use to target the `.sidebar-checkbox` */ +.sidebar-toggle { + position: absolute; + top: .8rem; + left: 1rem; + display: flex; + align-items: center; + padding: .25rem .75rem; + color: #505050; + background-color: #fff; + border-radius: .25rem; + cursor: pointer; +} + +.sidebar-toggle::before { + display: inline-block; + width: 32px; + height: 32px; + content: ""; + background: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' fill='%23555' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' d='M2.5 11.5A.5.5 0 013 11h10a.5.5 0 010 1H3a.5.5 0 01-.5-.5zm0-4A.5.5 0 013 7h10a.5.5 0 010 1H3a.5.5 0 01-.5-.5zm0-4A.5.5 0 013 3h10a.5.5 0 010 1H3a.5.5 0 01-.5-.5z' clip-rule='evenodd'/%3E%3C/svg%3E") no-repeat; +} + +.sidebar-toggle:active, +#sidebar-checkbox:focus ~ .sidebar-toggle, +#sidebar-checkbox:checked ~ .sidebar-toggle { + color: #fff; + background-color: #555; +} + +.sidebar-toggle:active:before, +#sidebar-checkbox:focus ~ .sidebar-toggle::before, +#sidebar-checkbox:checked ~ .sidebar-toggle::before { + background: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' d='M2.5 11.5A.5.5 0 013 11h10a.5.5 0 010 1H3a.5.5 0 01-.5-.5zm0-4A.5.5 0 013 7h10a.5.5 0 010 1H3a.5.5 0 01-.5-.5zm0-4A.5.5 0 013 3h10a.5.5 0 010 1H3a.5.5 0 01-.5-.5z' clip-rule='evenodd'/%3E%3C/svg%3E") no-repeat; +} + +@media (min-width: 30.1em) { + .sidebar-toggle { + position: fixed; + } +} + +@media print { + .sidebar-toggle { + display: none; + } +} + +/* Slide effect + * + * Handle the sliding effects of the sidebar and content in one spot, seperate + * from the default styles. + * + * As an a heads up, we don't use `transform: translate3d()` here because when + * mixed with `position: fixed;` for the sidebar toggle, it creates a new + * containing block. Put simply, the fixed sidebar toggle behaves like + * `position: absolute;` when transformed. + * + * Read more about it at http://meyerweb.com/eric/thoughts/2011/09/12/. + */ + +.wrap, +.sidebar, +.sidebar-toggle { + -webkit-backface-visibility: hidden; + -ms-backface-visibility: hidden; + backface-visibility: hidden; +} +.wrap, +.sidebar-toggle { + -webkit-transition: -webkit-transform .3s ease-in-out; + transition: transform .3s ease-in-out; +} + +#sidebar-checkbox:checked + .sidebar { + z-index: 10; + visibility: visible; +} +#sidebar-checkbox:checked ~ .sidebar, +#sidebar-checkbox:checked ~ .wrap, +#sidebar-checkbox:checked ~ .sidebar-toggle { + -webkit-transform: translateX(14rem); + -ms-transform: translateX(14rem); + transform: translateX(14rem); +} + + +/* + * Posts and pages + * + * Each post is wrapped in `.post` and is used on default and post layouts. Each + * page is wrapped in `.page` and is only used on the page layout. + */ + +.page, +.post { + margin-bottom: 4em; +} + +/* Blog post or page title */ +.page-title, +.post-title, +.post-title a { + color: #303030; +} +.page-title, +.post-title { + margin-top: 0; +} + +/* Meta data line below post title */ +.post-date { + display: block; + margin-top: -.5rem; + margin-bottom: 1rem; + color: #9a9a9a; +} + +/* Related posts */ +.related { + padding-top: 2rem; + padding-bottom: 2rem; + border-top: 1px solid #eee; +} +.related-posts { + padding-left: 0; + list-style: none; +} +.related-posts h3 { + margin-top: 0; +} +.related-posts li small { + font-size: 75%; + color: #999; +} +.related-posts li a:hover { + color: #268bd2; + text-decoration: none; +} +.related-posts li a:hover small { + color: inherit; +} + + +/* + * Pagination + * + * Super lightweight (HTML-wise) blog pagination. `span`s are provide for when + * there are no more previous or next posts to show. + */ + +.pagination { + overflow: hidden; /* clearfix */ + margin-left: -1rem; + margin-right: -1rem; + font-family: "PT Sans", Helvetica, Arial, sans-serif; + color: #ccc; + text-align: center; +} + +/* Pagination items can be `span`s or `a`s */ +.pagination-item { + display: block; + padding: 1rem; + border: 1px solid #eee; +} +.pagination-item:first-child { + margin-bottom: -1px; +} + +/* Only provide a hover state for linked pagination items */ +a.pagination-item:hover { + background-color: #f5f5f5; +} + +@media (min-width: 30em) { + .pagination { + margin: 3rem 0; + } + .pagination-item { + float: left; + width: 50%; + } + .pagination-item:first-child { + margin-bottom: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + } + .pagination-item:last-child { + margin-left: -1px; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + } +} + + +/* + * Reverse layout + * + * Flip the orientation of the page by placing the `.sidebar` and sidebar toggle + * on the right side. + */ + +.layout-reverse .sidebar { + left: auto; + right: -14rem; +} +.layout-reverse .sidebar-toggle { + left: auto; + right: 1rem; +} + +.layout-reverse #sidebar-checkbox:checked ~ .sidebar, +.layout-reverse #sidebar-checkbox:checked ~ .wrap, +.layout-reverse #sidebar-checkbox:checked ~ .sidebar-toggle { + -webkit-transform: translateX(-14rem); + -ms-transform: translateX(-14rem); + transform: translateX(-14rem); +} + + +/* + * Themes + * + * Apply custom color schemes by adding the appropriate class to the `body`. + * Based on colors from Base16: http://chriskempson.github.io/base16/#default. + */ + +/* Red */ +.theme-base-08 .sidebar, +.theme-base-08 .sidebar-toggle:active, +.theme-base-08 #sidebar-checkbox:checked ~ .sidebar-toggle { + background-color: #ac4142; +} +.theme-base-08 .container a, +.theme-base-08 .sidebar-toggle, +.theme-base-08 .related-posts li a:hover { + color: #ac4142; +} + +/* Orange */ +.theme-base-09 .sidebar, +.theme-base-09 .sidebar-toggle:active, +.theme-base-09 #sidebar-checkbox:checked ~ .sidebar-toggle { + background-color: #d28445; +} +.theme-base-09 .container a, +.theme-base-09 .sidebar-toggle, +.theme-base-09 .related-posts li a:hover { + color: #d28445; +} + +/* Yellow */ +.theme-base-0a .sidebar, +.theme-base-0a .sidebar-toggle:active, +.theme-base-0a #sidebar-checkbox:checked ~ .sidebar-toggle { + background-color: #f4bf75; +} +.theme-base-0a .container a, +.theme-base-0a .sidebar-toggle, +.theme-base-0a .related-posts li a:hover { + color: #f4bf75; +} + +/* Green */ +.theme-base-0b .sidebar, +.theme-base-0b .sidebar-toggle:active, +.theme-base-0b #sidebar-checkbox:checked ~ .sidebar-toggle { + background-color: #90a959; +} +.theme-base-0b .container a, +.theme-base-0b .sidebar-toggle, +.theme-base-0b .related-posts li a:hover { + color: #90a959; +} + +/* Cyan */ +.theme-base-0c .sidebar, +.theme-base-0c .sidebar-toggle:active, +.theme-base-0c #sidebar-checkbox:checked ~ .sidebar-toggle { + background-color: #75b5aa; +} +.theme-base-0c .container a, +.theme-base-0c .sidebar-toggle, +.theme-base-0c .related-posts li a:hover { + color: #75b5aa; +} + +/* Blue */ +.theme-base-0d .sidebar, +.theme-base-0d .sidebar-toggle:active, +.theme-base-0d #sidebar-checkbox:checked ~ .sidebar-toggle { + background-color: #6a9fb5; +} +.theme-base-0d .container a, +.theme-base-0d .sidebar-toggle, +.theme-base-0d .related-posts li a:hover { + color: #6a9fb5; +} + +/* Magenta */ +.theme-base-0e .sidebar, +.theme-base-0e .sidebar-toggle:active, +.theme-base-0e #sidebar-checkbox:checked ~ .sidebar-toggle { + background-color: #aa759f; +} +.theme-base-0e .container a, +.theme-base-0e .sidebar-toggle, +.theme-base-0e .related-posts li a:hover { + color: #aa759f; +} + +/* Brown */ +.theme-base-0f .sidebar, +.theme-base-0f .sidebar-toggle:active, +.theme-base-0f #sidebar-checkbox:checked ~ .sidebar-toggle { + background-color: #8f5536; +} +.theme-base-0f .container a, +.theme-base-0f .sidebar-toggle, +.theme-base-0f .related-posts li a:hover { + color: #8f5536; +} + + +/* + * Overlay sidebar + * + * Make the sidebar content overlay the viewport content instead of pushing it + * aside when toggled. + */ + +.sidebar-overlay #sidebar-checkbox:checked ~ .wrap { + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); +} +.sidebar-overlay #sidebar-checkbox:checked ~ .sidebar-toggle { + box-shadow: 0 0 0 .25rem #fff; +} +.sidebar-overlay #sidebar-checkbox:checked ~ .sidebar { + box-shadow: .25rem 0 .5rem rgba(0,0,0,.1); +} + +/* Only one tweak for a reverse layout */ +.layout-reverse.sidebar-overlay #sidebar-checkbox:checked ~ .sidebar { + box-shadow: -.25rem 0 .5rem rgba(0,0,0,.1); +} diff --git a/public/css/poole.css b/public/css/poole.css new file mode 100644 index 0000000..8ec27e7 --- /dev/null +++ b/public/css/poole.css @@ -0,0 +1,430 @@ +/* + * ___ + * /\_ \ + * _____ ___ ___\//\ \ __ + * /\ '__`\ / __`\ / __`\\ \ \ /'__`\ + * \ \ \_\ \/\ \_\ \/\ \_\ \\_\ \_/\ __/ + * \ \ ,__/\ \____/\ \____//\____\ \____\ + * \ \ \/ \/___/ \/___/ \/____/\/____/ + * \ \_\ + * \/_/ + * + * Designed, built, and released under MIT license by @mdo. Learn more at + * https://github.com/poole/poole. + */ + + +/* + * Contents + * + * Body resets + * Custom type + * Messages + * Container + * Masthead + * Posts and pages + * Pagination + * Reverse layout + * Themes + */ + + +/* + * Body resets + * + * Update the foundational and global aspects of the page. + */ + +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; +} + +html { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 1.5; +} +@media (min-width: 38em) { + html { + font-size: 20px; + } +} + +body { + color: #515151; + background-color: #fff; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +/* No `:visited` state is required by default (browsers will use `a`) */ +a { + color: #268bd2; + text-decoration: none; +} +a strong { + color: inherit; +} +/* `:focus` is linked to `:hover` for basic accessibility */ +a:hover, +a:focus { + text-decoration: underline; +} + +/* Headings */ +h1, h2, h3, h4, h5, h6 { + margin-bottom: .5rem; + font-weight: bold; + line-height: 1.25; + color: #313131; + text-rendering: optimizeLegibility; +} +h1 { + font-size: 2rem; +} +h2 { + margin-top: 1rem; + font-size: 1.5rem; +} +h3 { + margin-top: 1.5rem; + font-size: 1.25rem; +} +h4, h5, h6 { + margin-top: 1rem; + font-size: 1rem; +} + +/* Body text */ +p { + margin-top: 0; + margin-bottom: 1rem; +} + +strong { + color: #303030; +} + + +/* Lists */ +ul, ol, dl { + margin-top: 0; + margin-bottom: 1rem; +} + +dt { + font-weight: bold; +} +dd { + margin-bottom: .5rem; +} + +/* Misc */ +hr { + position: relative; + margin: 1.5rem 0; + border: 0; + border-top: 1px solid #eee; + border-bottom: 1px solid #fff; +} + +abbr { + font-size: 85%; + font-weight: bold; + color: #555; + text-transform: uppercase; +} +abbr[title] { + cursor: help; + border-bottom: 1px dotted #e5e5e5; +} + +/* Code */ +code, +pre { + font-family: Menlo, Monaco, "Courier New", monospace; +} +code { + padding: .25em .5em; + font-size: 85%; + color: #bf616a; + background-color: #f9f9f9; + border-radius: 3px; +} +pre { + display: block; + margin-top: 0; + margin-bottom: 1rem; + padding: 1rem; + font-size: .8rem; + line-height: 1.4; + white-space: pre; + white-space: pre-wrap; + word-break: break-all; + word-wrap: break-word; + background-color: #f9f9f9; +} +pre code { + padding: 0; + font-size: 100%; + color: inherit; + background-color: transparent; +} + +/* Pygments via Jekyll */ +.highlight { + margin-bottom: 1rem; + border-radius: 4px; +} +.highlight pre { + margin-bottom: 0; +} + +/* Gist via GitHub Pages */ +.gist .gist-file { + font-family: Menlo, Monaco, "Courier New", monospace !important; +} +.gist .markdown-body { + padding: 15px; +} +.gist pre { + padding: 0; + background-color: transparent; +} +.gist .gist-file .gist-data { + font-size: .8rem !important; + line-height: 1.4; +} +.gist code { + padding: 0; + color: inherit; + background-color: transparent; + border-radius: 0; +} + +/* Quotes */ +blockquote { + padding: .5rem 1rem; + margin: .8rem 0; + color: #7a7a7a; + border-left: .25rem solid #e5e5e5; +} +blockquote p:last-child { + margin-bottom: 0; +} +@media (min-width: 30em) { + blockquote { + padding-right: 5rem; + padding-left: 1.25rem; + } +} + +img { + display: block; + max-width: 100%; + margin: 0 0 1rem; + border-radius: 5px; +} + +/* Tables */ +table { + margin-bottom: 1rem; + width: 100%; + border: 1px solid #e5e5e5; + border-collapse: collapse; +} +td, +th { + padding: .25rem .5rem; + border: 1px solid #e5e5e5; +} +tbody tr:nth-child(odd) td, +tbody tr:nth-child(odd) th { + background-color: #f9f9f9; +} + + +/* + * Custom type + * + * Extend paragraphs with `.lead` for larger introductory text. + */ + +.lead { + font-size: 1.25rem; + font-weight: 300; +} + + +/* + * Messages + * + * Show alert messages to users. You may add it to single elements like a `

`, + * or to a parent if there are multiple elements to show. + */ + +.message { + margin-bottom: 1rem; + padding: 1rem; + color: #717171; + background-color: #f9f9f9; +} + + +/* + * Container + * + * Center the page content. + */ + +.container { + max-width: 38rem; + padding-left: 1rem; + padding-right: 1rem; + margin-left: auto; + margin-right: auto; +} + + +/* + * Masthead + * + * Super small header above the content for site name and short description. + */ + +.masthead { + padding-top: 1rem; + padding-bottom: 1rem; + margin-bottom: 3rem; +} +.masthead-title { + margin-top: 0; + margin-bottom: 0; + color: #505050; +} +.masthead-title a { + color: #505050; +} +.masthead-title small { + font-size: 75%; + font-weight: 400; + color: #c0c0c0; + letter-spacing: 0; +} + + +/* + * Posts and pages + * + * Each post is wrapped in `.post` and is used on default and post layouts. Each + * page is wrapped in `.page` and is only used on the page layout. + */ + +.page, +.post { + margin-bottom: 4em; +} + +/* Blog post or page title */ +.page-title, +.post-title, +.post-title a { + color: #303030; +} +.page-title, +.post-title { + margin-top: 0; +} + +/* Meta data line below post title */ +.post-date { + display: block; + margin-top: -.5rem; + margin-bottom: 1rem; + color: #9a9a9a; +} + +/* Related posts */ +.related { + padding-top: 2rem; + padding-bottom: 2rem; + border-top: 1px solid #eee; +} +.related-posts { + padding-left: 0; + list-style: none; +} +.related-posts h3 { + margin-top: 0; +} +.related-posts li small { + font-size: 75%; + color: #999; +} +.related-posts li a:hover { + color: #268bd2; + text-decoration: none; +} +.related-posts li a:hover small { + color: inherit; +} + + +/* + * Pagination + * + * Super lightweight (HTML-wise) blog pagination. `span`s are provide for when + * there are no more previous or next posts to show. + */ + +.pagination { + overflow: hidden; /* clearfix */ + margin-left: -1rem; + margin-right: -1rem; + font-family: "PT Sans", Helvetica, Arial, sans-serif; + color: #ccc; + text-align: center; +} + +/* Pagination items can be `span`s or `a`s */ +.pagination-item { + display: block; + padding: 1rem; + border: 1px solid #eee; +} +.pagination-item:first-child { + margin-bottom: -1px; +} + +/* Only provide a hover state for linked pagination items */ +a.pagination-item:hover { + background-color: #f5f5f5; +} + +@media (min-width: 30em) { + .pagination { + margin: 3rem 0; + } + .pagination-item { + float: left; + width: 50%; + } + .pagination-item:first-child { + margin-bottom: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + } + .pagination-item:last-child { + margin-left: -1px; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + } +} diff --git a/public/css/spideroak.css b/public/css/spideroak.css new file mode 100644 index 0000000..fab6c93 --- /dev/null +++ b/public/css/spideroak.css @@ -0,0 +1,87 @@ + +.masthead { + padding-top: 1.25rem; + padding-bottom: 1.25rem; +} + +@media (min-width: 38em) { + .sidebar-toggle { + top: 1rem; + } +} +.sidebar { + padding-top: 0.75rem; +} +.sidebar .sidebar-nav { + border-bottom-width: 0; + border-top: 1px solid rgba(255,255,255,.1); + padding-bottom: 1rem; +} +@media (min-width: 38em) { + .sidebar .sidebar-nav { + padding-bottom: 1.5rem; + } +} +.sidebar .sidebar-nav-item { + border-bottom: 1px solid rgba(255,255,255,.1); +} +.sidebar .sidebar-item { + padding: 0 1rem 1rem; +} +@media (min-width: 48em) { + .sidebar .sidebar-item { + padding: 0 1.5rem 1.5rem; + } +} + +.sidebar .sidebar-item.logo-wrapper { + padding-bottom: 1rem; +} +.sidebar .sidebar-item.logo-wrapper .logo { + margin: 0; + max-width: 10rem; +} +.sidebar .logo-wrapper.sponsored p { + margin-bottom: 0.5rem; +} +.sidebar .logo-wrapper.sponsored .logo { + max-width: 75%; +} + +.sidebar .nav-icon { + margin-bottom: 0; + width: 2rem; +} +@media (min-width: 38em) { + .sidebar .nav-icon { + width: 2.2rem; + } +} + +@media (min-width: 80em) { + .container { + max-width: 55rem; + } +} +@media (min-width: 100em) { + .container { + max-width: 70rem; + } +} +.doc-image { + margin: 0 auto 1rem; + max-width: 50rem; + width: 100%; +} +@media (min-width: 38em) { + .doc-image { + margin-bottom: 1.5rem; + } +} +.mermaid svg { + margin: 0 auto; + display: block; +} +.mermaid-wrapper { + margin-bottom: 1rem; +} diff --git a/public/css/syntax.css b/public/css/syntax.css new file mode 100644 index 0000000..6a367cf --- /dev/null +++ b/public/css/syntax.css @@ -0,0 +1,77 @@ +/* +Released under MIT License + +Copyright (c) 2014 Mark Otto. + +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. +*/ + +.highlight .hll { background-color: #ffc; } +.highlight .c { color: #999; } /* Comment */ +.highlight .err { color: #a00; background-color: #faa } /* Error */ +.highlight .k { color: #069; } /* Keyword */ +.highlight .o { color: #555 } /* Operator */ +.highlight .cm { color: #09f; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #099 } /* Comment.Preproc */ +.highlight .c1 { color: #999; } /* Comment.Single */ +.highlight .cs { color: #999; } /* Comment.Special */ +.highlight .gd { background-color: #fcc; border: 1px solid #c00 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #f00 } /* Generic.Error */ +.highlight .gh { color: #030; } /* Generic.Heading */ +.highlight .gi { background-color: #cfc; border: 1px solid #0c0 } /* Generic.Inserted */ +.highlight .go { color: #aaa } /* Generic.Output */ +.highlight .gp { color: #009; } /* Generic.Prompt */ +.highlight .gs { } /* Generic.Strong */ +.highlight .gu { color: #030; } /* Generic.Subheading */ +.highlight .gt { color: #9c6 } /* Generic.Traceback */ +.highlight .kc { color: #069; } /* Keyword.Constant */ +.highlight .kd { color: #069; } /* Keyword.Declaration */ +.highlight .kn { color: #069; } /* Keyword.Namespace */ +.highlight .kp { color: #069 } /* Keyword.Pseudo */ +.highlight .kr { color: #069; } /* Keyword.Reserved */ +.highlight .kt { color: #078; } /* Keyword.Type */ +.highlight .m { color: #f60 } /* Literal.Number */ +.highlight .s { color: #d44950 } /* Literal.String */ +.highlight .na { color: #4f9fcf } /* Name.Attribute */ +.highlight .nb { color: #366 } /* Name.Builtin */ +.highlight .nc { color: #0a8; } /* Name.Class */ +.highlight .no { color: #360 } /* Name.Constant */ +.highlight .nd { color: #99f } /* Name.Decorator */ +.highlight .ni { color: #999; } /* Name.Entity */ +.highlight .ne { color: #c00; } /* Name.Exception */ +.highlight .nf { color: #c0f } /* Name.Function */ +.highlight .nl { color: #99f } /* Name.Label */ +.highlight .nn { color: #0cf; } /* Name.Namespace */ +.highlight .nt { color: #2f6f9f; } /* Name.Tag */ +.highlight .nv { color: #033 } /* Name.Variable */ +.highlight .ow { color: #000; } /* Operator.Word */ +.highlight .w { color: #bbb } /* Text.Whitespace */ +.highlight .mf { color: #f60 } /* Literal.Number.Float */ +.highlight .mh { color: #f60 } /* Literal.Number.Hex */ +.highlight .mi { color: #f60 } /* Literal.Number.Integer */ +.highlight .mo { color: #f60 } /* Literal.Number.Oct */ +.highlight .sb { color: #c30 } /* Literal.String.Backtick */ +.highlight .sc { color: #c30 } /* Literal.String.Char */ +.highlight .sd { color: #c30; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #c30 } /* Literal.String.Double */ +.highlight .se { color: #c30; } /* Literal.String.Escape */ +.highlight .sh { color: #c30 } /* Literal.String.Heredoc */ +.highlight .si { color: #a00 } /* Literal.String.Interpol */ +.highlight .sx { color: #c30 } /* Literal.String.Other */ +.highlight .sr { color: #3aa } /* Literal.String.Regex */ +.highlight .s1 { color: #c30 } /* Literal.String.Single */ +.highlight .ss { color: #fc3 } /* Literal.String.Symbol */ +.highlight .bp { color: #366 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #033 } /* Name.Variable.Class */ +.highlight .vg { color: #033 } /* Name.Variable.Global */ +.highlight .vi { color: #033 } /* Name.Variable.Instance */ +.highlight .il { color: #f60 } /* Literal.Number.Integer.Long */ + +.css .o, +.css .o + .nt, +.css .nt + .nt { color: #999; } diff --git a/public/js/script.js b/public/js/script.js new file mode 100644 index 0000000..357fa10 --- /dev/null +++ b/public/js/script.js @@ -0,0 +1,35 @@ +/* +* Released under MIT License +* +* Copyright (c) 2014 Mark Otto. +* +* 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. +*/ + +(function (document) { + var toggle = document.querySelector(".sidebar-toggle"); + var sidebar = document.querySelector("#sidebar"); + var checkbox = document.querySelector("#sidebar-checkbox"); + + document.addEventListener( + "click", + function (e) { + var target = e.target; + if (sidebar.contains(target) || target === checkbox) { + // Do nothing is clicking in the sidebar. + return; + } else if (target === toggle) { + // Toggle menu when menu toggle clicked. + checkbox.checked = !checkbox.checked; + } else if (checkbox.checked) { + // Close the menu if the document body is clicked. + checkbox.checked = false; + } + }, + false + ); +})(document);