[
  {
    "path": ".eslintignore",
    "content": "# Docs\ndocs-source\n\n# Auth0 rules (they follow a different style)\nauth0\n\n# Test data\ntestfolder\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n    env: {\n        es6: true,\n        node: true,\n        browser: true\n    },\n    extends: 'eslint:recommended',\n    parserOptions: {\n        ecmaVersion: 2019,\n        sourceType: 'module'\n    },\n    plugins: [\n        'html',\n        'svelte3'\n    ],\n    overrides: [\n        {\n            files: '**/*.svelte',\n            processor: 'svelte3/svelte3'\n        }\n    ],\n    globals: {\n        // See https://github.com/eslint/eslint/issues/11524\n        BigInt: true\n    },\n    settings: {\n        'svelte3/ignore-styles': () => true,\n        'html': {\n            'indent': 0,\n            'report-bad-indent': 'warn',\n            'html-extensions': [\n                '.html'\n            ]\n        }\n    },\n    rules: {\n        'indent': [\n            'error',\n            4,\n            {\n                SwitchCase: 1,\n                MemberExpression: 1,\n                ArrayExpression: 1,\n                ObjectExpression: 1\n            }\n        ],\n        'linebreak-style': [\n            'error',\n            'unix'\n        ],\n        'quotes': [\n            'error',\n            'single'\n        ],\n        'semi': [\n            'error',\n            'never'\n        ],\n        'quote-props': [\n            'warn',\n            'as-needed'\n        ],\n        'no-var': [\n            'error'\n        ],\n        'prefer-const': [\n            'warn'\n        ],\n        'no-unused-vars': [\n            'error',\n            {\n                args: 'none'\n            }\n        ],\n        'brace-style': [\n            'error',\n            'stroustrup',\n            {\n                allowSingleLine: false\n            }\n        ],\n        'eol-last': [\n            'error',\n            'always'\n        ],\n        'space-before-function-paren': [\n            'error',\n            {\n                anonymous: 'never',\n                named: 'never',\n                asyncArrow: 'always'\n            }\n        ],\n        'keyword-spacing': [\n            'error',\n            {\n                before: true,\n                after: true\n            }\n        ],\n        'key-spacing': [\n            'error',\n            {\n                beforeColon: false,\n                afterColon: true,\n                mode: 'strict'\n            }\n        ],\n        'comma-spacing': [\n            'error'\n        ],\n        'arrow-spacing': [\n            'error'\n        ],\n        'array-bracket-spacing': [\n            'error',\n            'never',\n            {\n                singleValue: false,\n                objectsInArrays: true,\n                arraysInArrays: true\n            }\n        ],\n        'curly': [\n            'error'\n        ],\n        'space-infix-ops': [\n            'error',\n            {\n                int32Hint: false\n            }\n        ],\n        'space-unary-ops': [\n            'error',\n            {\n                words: true,\n                nonwords: false\n            }\n        ],\n        'space-before-blocks': [\n            'error'\n        ],\n        'object-curly-spacing': [\n            'error',\n            'never'\n        ],\n        'space-in-parens': [\n            'error',\n            'never'\n        ],\n        'prefer-arrow-callback': [\n            'warn'\n        ],\n        'no-return-await': [\n            'error'\n        ],\n        'no-console': [\n            'warn'\n        ],\n        'no-nested-ternary': [\n            'error'\n        ],\n        'no-unneeded-ternary': [\n            'warn'\n        ],\n        'no-unexpected-multiline': [\n            'error'\n        ],\n        'lines-around-directive': [\n            'error',\n            'always'\n        ],\n        // Need to disable this because it causes issues with Svelte\n        'no-multiple-empty-lines': 'off',\n        'operator-linebreak': [\n            'error',\n            'after'\n        ]\n    }\n}\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: ItalyPaleAle\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: italypaleale\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: ['https://keybase.io/italypaleale']\n"
  },
  {
    "path": ".github/workflows/docs-ci.yaml",
    "content": "# Docs: CI: deploys to dev\n\n# Required secrets:\n# CF_ACCOUNT_ID: Account ID for Cloudflare Workers\n# CF_API_TOKEN: API token for Cloudflare (for the Workers CLI)\n# AZURE_STORAGE_ACCOUNT: Name of the Azure Storage Account\n# AZCOPY_SPA_APPLICATION_ID: Application ID (Client ID) for the Service Principal with access to the Azure Storage account\n# AZCOPY_SPA_CLIENT_SECRET: Client Secret for the Service Principal\n# AZCOPY_SPA_TENANT_ID: Tenant ID of the application (Service Principal)\n\nname: 'Docs: CI'\n\non:\n  push:\n    branches:\n      - master\n\njobs:\n  build-and-deploy:\n    runs-on: 'ubuntu-20.04'\n    env:\n      # Version of Hugo to use\n      HUGO_VERSION: '0.68.1'\n    steps:\n      - name: 'Check out code'\n        uses: 'actions/checkout@v2'\n\n      - name: 'Install Node.js'\n        uses: 'actions/setup-node@v1'\n        with:\n          node-version: '14.x'\n\n      - name: 'Install npm deps'\n        run: |\n          npm ci\n\n      - name: 'Install Hugo'\n        run: |\n          cd docs-source\n          mkdir -p .bin\n          cd .bin\n          echo \"Using Hugo ${HUGO_VERSION}\"\n          curl -fsSL \"https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_Linux-64bit.tar.gz\" -o hugo.tar.gz\n          tar -zxf hugo.tar.gz\n\n      - name: 'Build site'\n        run: |\n          # Build the site\n          cd docs-source\n          node generate-cli-docs.js\n          .bin/hugo\n\n      - name: 'Install azcopy and authenticate'\n        run: |\n          cd docs-source\n          mkdir -p .bin\n          curl -Ls \"https://aka.ms/downloadazcopy-v10-linux\" -o \".bin/azcopy.tar.gz\"\n          (cd .bin && tar -xvzf azcopy.tar.gz --strip 1)\n          .bin/azcopy --version\n          .bin/azcopy login --service-principal --application-id $AZCOPY_SPA_APPLICATION_ID  --tenant-id $AZCOPY_SPA_TENANT_ID\n        env:\n          # Service Principal credentials\n          AZCOPY_SPA_APPLICATION_ID: ${{ secrets.AZCOPY_SPA_APPLICATION_ID }}\n          AZCOPY_SPA_CLIENT_SECRET: ${{ secrets.AZCOPY_SPA_CLIENT_SECRET }}\n          AZCOPY_SPA_TENANT_ID: ${{ secrets.AZCOPY_SPA_TENANT_ID }}\n\n      - name: 'Upload static assets to Azure Storage'\n        run: |\n          cd docs-source\n          # Upload assets to Azure Storage\n          ./sync-assets.sh\n          # Delete the assets from disk so they're not uploaded to Cloudflare or published as artifact\n          for asset in $ASSETS; do rm -rvf \"$asset\"; done\n        env:\n          # List of assets to upload\n          ASSETS: 'public/images public/svg'\n          # Container in Azure Storage\n          CONTAINER: 'hereditas-dev'\n          # Use azcopy downloaded above\n          AZCOPYCMD: '.bin/azcopy'\n          # Storage Account name\n          AZURE_STORAGE_ACCOUNT: ${{ secrets.AZURE_STORAGE_ACCOUNT }}\n\n      - name: 'Publish docs as artifact'\n        uses: 'actions/upload-artifact@v2'\n        with:\n          name: 'docs-dev'\n          path: 'docs-source/public'\n\n      - name: 'Deploy to dev environment'\n        uses: cloudflare/wrangler-action@1.3.0\n        with:\n          apiToken: ${{ secrets.CF_API_TOKEN }}\n          workingDirectory: 'docs-source'\n        env:\n          CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}\n"
  },
  {
    "path": ".github/workflows/docs-production.yaml",
    "content": "# Docs: Deploys to production, triggered manually\n\n# Required secrets:\n# CF_ACCOUNT_ID: Account ID for Cloudflare Workers\n# CF_API_TOKEN: API token for Cloudflare (for the Workers CLI)\n# CF_ZONE_ID: Zone ID for the Cloudflare domain\n# AZURE_STORAGE_ACCOUNT: Name of the Azure Storage Account\n# AZCOPY_SPA_APPLICATION_ID: Application ID (Client ID) for the Service Principal with access to the Azure Storage account\n# AZCOPY_SPA_CLIENT_SECRET: Client Secret for the Service Principal\n# AZCOPY_SPA_TENANT_ID: Tenant ID of the application (Service Principal)\n\nname: 'Docs: Production'\n\non:\n  workflow_dispatch:\n\njobs:\n  build-and-deploy:\n    runs-on: 'ubuntu-20.04'\n    env:\n      # Version of Hugo to use\n      HUGO_VERSION: '0.68.1'\n    steps:\n      - name: 'Check out code'\n        uses: 'actions/checkout@v2'\n\n      - name: 'Install Node.js'\n        uses: 'actions/setup-node@v1'\n        with:\n          node-version: '14.x'\n\n      - name: 'Install npm deps'\n        run: |\n          npm ci\n\n      - name: 'Install Hugo'\n        run: |\n          cd docs-source\n          mkdir -p .bin\n          cd .bin\n          echo \"Using Hugo ${HUGO_VERSION}\"\n          curl -fsSL \"https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_Linux-64bit.tar.gz\" -o hugo.tar.gz\n          tar -zxf hugo.tar.gz\n\n      - name: 'Build site'\n        run: |\n          # Build the site\n          cd docs-source\n          node generate-cli-docs.js\n          .bin/hugo\n\n      - name: 'Install azcopy and authenticate'\n        run: |\n          cd docs-source\n          mkdir -p .bin\n          curl -Ls \"https://aka.ms/downloadazcopy-v10-linux\" -o \".bin/azcopy.tar.gz\"\n          (cd .bin && tar -xvzf azcopy.tar.gz --strip 1)\n          .bin/azcopy --version\n          .bin/azcopy login --service-principal --application-id $AZCOPY_SPA_APPLICATION_ID  --tenant-id $AZCOPY_SPA_TENANT_ID\n        env:\n          # Service Principal credentials\n          AZCOPY_SPA_APPLICATION_ID: ${{ secrets.AZCOPY_SPA_APPLICATION_ID }}\n          AZCOPY_SPA_CLIENT_SECRET: ${{ secrets.AZCOPY_SPA_CLIENT_SECRET }}\n          AZCOPY_SPA_TENANT_ID: ${{ secrets.AZCOPY_SPA_TENANT_ID }}\n\n      - name: 'Upload static assets to Azure Storage'\n        run: |\n          cd docs-source\n          # Upload assets to Azure Storage\n          ./sync-assets.sh\n          # Delete the assets from disk so they're not uploaded to Cloudflare or published as artifact\n          for asset in $ASSETS; do rm -rvf \"$asset\"; done\n        env:\n          # List of assets to upload\n          ASSETS: 'public/images public/svg'\n          # Container in Azure Storage\n          CONTAINER: 'hereditas-prod'\n          # Use azcopy downloaded above\n          AZCOPYCMD: '.bin/azcopy'\n          # Storage Account name\n          AZURE_STORAGE_ACCOUNT: ${{ secrets.AZURE_STORAGE_ACCOUNT }}\n\n      - name: 'Publish docs as artifact'\n        uses: 'actions/upload-artifact@v2'\n        with:\n          name: 'docs-prod'\n          path: 'docs-source/public'\n\n      - name: 'Deploy to production environment'\n        uses: cloudflare/wrangler-action@1.3.0\n        with:\n          apiToken: ${{ secrets.CF_API_TOKEN }}\n          environment: 'production'\n          workingDirectory: 'docs-source'\n        env:\n          CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}\n          CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}\n"
  },
  {
    "path": ".github/workflows/docs-staging.yaml",
    "content": "# Docs: Deploys to staging, triggered manually\n\n# Required secrets:\n# CF_ACCOUNT_ID: Account ID for Cloudflare Workers\n# CF_API_TOKEN: API token for Cloudflare (for the Workers CLI)\n# CF_ZONE_ID: Zone ID for the Cloudflare domain\n# AZURE_STORAGE_ACCOUNT: Name of the Azure Storage Account\n# AZCOPY_SPA_APPLICATION_ID: Application ID (Client ID) for the Service Principal with access to the Azure Storage account\n# AZCOPY_SPA_CLIENT_SECRET: Client Secret for the Service Principal\n# AZCOPY_SPA_TENANT_ID: Tenant ID of the application (Service Principal)\n\nname: 'Docs: Staging'\n\non:\n  workflow_dispatch:\n\njobs:\n  build-and-deploy:\n    runs-on: 'ubuntu-20.04'\n    env:\n      # Version of Hugo to use\n      HUGO_VERSION: '0.68.1'\n    steps:\n      - name: 'Check out code'\n        uses: 'actions/checkout@v2'\n\n      - name: 'Install Node.js'\n        uses: 'actions/setup-node@v1'\n        with:\n          node-version: '14.x'\n\n      - name: 'Install npm deps'\n        run: |\n          npm ci\n\n      - name: 'Install Hugo'\n        run: |\n          cd docs-source\n          mkdir -p .bin\n          cd .bin\n          echo \"Using Hugo ${HUGO_VERSION}\"\n          curl -fsSL \"https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_Linux-64bit.tar.gz\" -o hugo.tar.gz\n          tar -zxf hugo.tar.gz\n\n      - name: 'Build site'\n        run: |\n          # Build the site\n          cd docs-source\n          node generate-cli-docs.js\n          .bin/hugo\n\n      - name: 'Install azcopy and authenticate'\n        run: |\n          cd docs-source\n          mkdir -p .bin\n          curl -Ls \"https://aka.ms/downloadazcopy-v10-linux\" -o \".bin/azcopy.tar.gz\"\n          (cd .bin && tar -xvzf azcopy.tar.gz --strip 1)\n          .bin/azcopy --version\n          .bin/azcopy login --service-principal --application-id $AZCOPY_SPA_APPLICATION_ID  --tenant-id $AZCOPY_SPA_TENANT_ID\n        env:\n          # Service Principal credentials\n          AZCOPY_SPA_APPLICATION_ID: ${{ secrets.AZCOPY_SPA_APPLICATION_ID }}\n          AZCOPY_SPA_CLIENT_SECRET: ${{ secrets.AZCOPY_SPA_CLIENT_SECRET }}\n          AZCOPY_SPA_TENANT_ID: ${{ secrets.AZCOPY_SPA_TENANT_ID }}\n\n      - name: 'Upload static assets to Azure Storage'\n        run: |\n          cd docs-source\n          # Upload assets to Azure Storage\n          ./sync-assets.sh\n          # Delete the assets from disk so they're not uploaded to Cloudflare or published as artifact\n          for asset in $ASSETS; do rm -rvf \"$asset\"; done\n        env:\n          # List of assets to upload\n          ASSETS: 'public/images public/svg'\n          # Container in Azure Storage\n          CONTAINER: 'hereditas-staging'\n          # Use azcopy downloaded above\n          AZCOPYCMD: '.bin/azcopy'\n          # Storage Account name\n          AZURE_STORAGE_ACCOUNT: ${{ secrets.AZURE_STORAGE_ACCOUNT }}\n\n      - name: 'Publish docs as artifact'\n        uses: 'actions/upload-artifact@v2'\n        with:\n          name: 'docs-staging'\n          path: 'docs-source/public'\n\n      - name: 'Deploy to staging environment'\n        uses: cloudflare/wrangler-action@1.3.0\n        with:\n          apiToken: ${{ secrets.CF_API_TOKEN }}\n          environment: 'staging'\n          workingDirectory: 'docs-source'\n        env:\n          CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}\n          CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Test data\ntestfolder\n\n# OClif manifest\noclif.manifest.json\n\n\n# Created by https://www.gitignore.io/api/node,macos,linux,windows,visualstudiocode\n# Edit at https://www.gitignore.io/?templates=node,macos,linux,windows,visualstudiocode\n\n### Linux ###\n*~\n\n# temporary files which can be created if a process still has a handle open of a deleted file\n.fuse_hidden*\n\n# KDE directory preferences\n.directory\n\n# Linux trash folder which might appear on any partition or disk\n.Trash-*\n\n# .nfs files are created when an open file is removed but is still being accessed\n.nfs*\n\n### macOS ###\n# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n### Node ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# TypeScript v1 declaration files\ntypings/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n.env.test\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n\n# next.js build output\n.next\n\n# nuxt.js build output\n.nuxt\n\n# vuepress build output\n.vuepress/dist\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n### VisualStudioCode ###\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n### VisualStudioCode Patch ###\n# Ignore all local history of files\n.history\n\n### Windows ###\n# Windows thumbnail cache files\nThumbs.db\nehthumbs.db\nehthumbs_vista.db\n\n# Dump file\n*.stackdump\n\n# Folder config file\n[Dd]esktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Windows Installer files\n*.cab\n*.msi\n*.msix\n*.msm\n*.msp\n\n# Windows shortcuts\n*.lnk\n\n# End of https://www.gitignore.io/api/node,macos,linux,windows,visualstudiocode\n"
  },
  {
    "path": ".npmignore",
    "content": ".DS_Store\nassets/\n.vscode/\ndocs-source/\ntestfolder/\nazure-pipelines.yaml\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"files.exclude\": {\n        \"**/node_modules\": true,\n        \"coverage\": true,\n        \"coverage.lcov\": true\n    },\n    \"files.trimTrailingWhitespace\": true,\n    \"editor.tabSize\": 4,\n    \"files.insertFinalNewline\": true,\n    \"eslint.options\": {\n        \"configFile\": \".eslintrc.yml\"\n    },\n    \"eslint.validate\": [ \"javascript\", \"html\", \"svelte\" ]\n}\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team. All complaints will be reviewed and\ninvestigated and will result in a response that is deemed necessary and appropriate\nto the circumstances. The project team is obligated to maintain confidentiality\nwith regard to the reporter of an incident. Further details of specific enforcement\npolicies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": "LICENSE.md",
    "content": "# License\n\nCopyright © 2019-2020 Alessandro Segala @ItalyPaleAle.\n\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3.\n\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\n\n````text\n                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n````\n"
  },
  {
    "path": "README.md",
    "content": "# Hereditas\n\n[![Open in Visual Studio Code](https://open.vscode.dev/badges/open-in-vscode.svg)](https://open.vscode.dev/ItalyPaleAle/hereditas)\n[![Version](https://img.shields.io/npm/v/hereditas.svg)](https://npmjs.org/package/hereditas)\n[![Downloads/week](https://img.shields.io/npm/dw/hereditas.svg)](https://npmjs.org/package/hereditas)\n[![License](https://img.shields.io/npm/l/hereditas.svg)](https://github.com/ItalyPaleAle/hereditas/blob/master/package.json)\n\n## What happens to your digital life after you're gone?\n\n![Hereditas logo](./assets/hereditas-logo.png)\n\nHereditas, which means *inheritance* in Latin, is a static website generator that builds **fully-trustless digital legacy boxes**, where you can store information for your relatives to access in case of your sudden death or disappearance.\n\nFor example, you could use this to pass information such as passwords, cryptographic keys, cryptocurrency wallets, sensitive documents, etc.\n\n## Learn more\n\nRead the [Hereditas announcement](https://withblue.ink/2019/03/18/what-happens-to-your-digital-life-after-youre-gone-introducing-hereditas.html?utm_source=web&utm_campaign=hereditas-github) to understand more on why we need Hereditas.\n\nYou can also watch this short [intro video](https://www.youtube.com/watch?v=lZEKgB5dzQ4).\n\n## Get started and documentation\n\n❓ [**What is Hereditas**](https://hereditas.app)\n\n🚀 [**Get started guide**](https://hereditas.app/guides/get-started.html)\n\n🔐 [**Security model**](https://hereditas.app/introduction/security-model.html)\n\n📘 [**Documentation and CLI reference**](https://hereditas.app)\n\n## Screenshot\n\n![Screenshot of Hereditas 0.2](./screenshot.png)\n\n## Warning: alpha quality software\n\n**Hereditas is currently alpha quality software; use at your own risk.** While we've developed Hereditas with security always as the top priority, this software leverages a lot of cryptographic primitives under the hood. We won't release a stable (e.g. \"1.0\") version of Hereditas until we're confident that enough people and cryptography experts have audited and improved the code.\n\n**Your help is highly appreciated.** If you are an expert on security or cryptography, please help us reviewing the code and let us know what you think - including if everything looks fine, or if you found a bug.\n\nResponsible disclosure: if you believe you've found a security issue that could compromise current users of Hereditas, please [report it confidentially](https://www.npmjs.com/advisories/report?package=hereditas).\n\n## License\n\nCopyright © 2020, Alessandro Segala @ItalyPaleAle\n\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3.\n\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\n\nYou can read the full text of the license in the [LICENSE.md](./LICENSE.md) file.\n"
  },
  {
    "path": "app/components/NavBar.svelte",
    "content": "<nav class=\"bg-white fixed w-full z-10 top-0 shadow\">\n  <div class=\"w-full lg:w-3/5 container px-2 flex flex-wrap items-center justify-between my-4\">\n    <div class=\"pl-4 md:pl-0\">\n      <span class=\"flex items-center text-blue-600 text-base xl:text-xl no-underline hover:no-underline font-extrabold font-sans\">{$pageTitle}</span>\n    </div>\n  </div>\n</nav>\n\n<script>\n// Stores\nimport {pageTitle} from '../stores'\n</script>\n"
  },
  {
    "path": "app/components/PassphraseBox.svelte",
    "content": "{#await $box.fetchIndex()}\n    <p>Fetching index, please wait…</p>\n{:then response}\n    <form class=\"w-full max-w-md\" on:submit|preventDefault={handleSubmit}>\n        <div class=\"md:flex md:items-center mb-6\">\n            <div class=\"md:w-1/3\">\n                <label class=\"block md:text-right mb-1 md:mb-0 pr-4\" for=\"inputPassphrase\">\n                    Unlock passphrase:\n                </label>\n            </div>\n            <div class=\"md:w-2/3\">\n                <input class=\"bg-white appearance-none border {unlockError ? 'border-red-500' : 'border-gray-200'} rounded w-full py-2 px-4 text-gray-700 leading-tight focus:outline-none focus:bg-white focus:border-blue-500\" id=\"inputPassphrase\" bind:value={passphrase} type=\"password\" placeholder=\"•••••••\" />\n                {#if unlockError}\n                    <p class=\"text-xs text-red-500\">This passphrase isn't correct</p>\n                {/if}\n            </div>\n        </div>\n        <div class=\"md:flex md:items-center\">\n            <div class=\"md:w-1/3\"></div>\n            <div class=\"md:w-2/3\">\n                <button class=\"bg-blue-500 hover:bg-blue-700 text-white no-underline font-bold py-2 px-4 rounded\" type=\"submit\">\n                    Browse this Hereditas\n                </button>\n            </div>\n        </div>\n    </form>\n\n{:catch error}\n    <p>Error while fetching the index: {error}</p>\n{/await}\n\n<script>\n// Libs\nimport {replace} from 'svelte-spa-router'\n\n// Stores\nimport {box, hereditasProfile} from '../stores'\n\n// Props\nlet passphrase = ''\nlet unlockError = false\n\n// Form submit handler\nfunction handleSubmit() {\n    unlockError = false\n    $box.unlock(passphrase, $hereditasProfile.token)\n        .then((_) => {\n            unlockError = false\n\n            // Redirect to the list\n            replace('/list')\n        })\n        .catch((err) => {\n            // eslint-disable-next-line no-console\n            console.error('err caught', err)\n            unlockError = true\n        })\n}\n</script>\n"
  },
  {
    "path": "app/components/RequestAuthentication.svelte",
    "content": "{#if $authError}\n    <h1>Authentication error</h1>\n    <p class=\"mb-2\"><b>Error description:</b> {$authError}</p>\n    <a href={authUrl} class=\"bg-blue-500 hover:bg-blue-700 text-white no-underline font-bold py-2 px-4 rounded\">Try authenticating again</a>\n{:else}\n    <h1>Authenticate with this Hereditas box</h1>\n    <a href={authUrl} class=\"bg-blue-500 hover:bg-blue-700 text-white no-underline font-bold py-2 px-4 rounded\">Authenticate</a>\n{/if}\n\n<script>\n// Libs\nimport credentials from '../lib/Credentials'\n\n// Stores\nimport {profile, authError} from '../stores'\n\n// Generate authentication url\nlet authUrl = undefined\n$: {\n    // If we're not authenticated, request a URL\n    authUrl = $profile ? undefined : credentials.authorizationUrl()\n}\n</script>\n"
  },
  {
    "path": "app/components/UserProfile.svelte",
    "content": "<h1>Hello, {$profile.name}!</h1>\n{#if $hereditasProfile.role == 'owner'}\n    <p class=\"mb-2\">You're the owner of this Hereditas box, so you can unlock it at any time.</p>\n    <p class=\"mx-2 my-4 p-2 border border-blue-600 bg-blue-400 text-white shadow\" role=\"alert\">By logging in, you have stopped the timer for the waiting period before other users can unlock your box.</p>\n    <PassphraseBox />\n{:else}\n    {#if $hereditasProfile.token}\n        <p class=\"mb-2\">You can now access to the content of this Hereditas.</p>\n        <PassphraseBox />\n    {:else}\n        <p class=\"m-2 p-2 border border-blue-600 bg-blue-400 text-white shadow\" role=\"alert\">Thanks for requesting access. This Hereditas box will be unlocked on <b>{unlockedDate.toLocaleString().replace(/ /g, '\\xa0')}</b>. Please check later.<br/>\n        Important: if an owner signs in with their account, this Hereditas will be locked again.</p>\n    {/if}\n{/if}\n\n<script>\n// Components\nimport PassphraseBox from './PassphraseBox.svelte'\n\n// Stores\nimport {profile, hereditasProfile} from '../stores'\n\n// Unlocked date\nlet unlockedDate = new Date()\n$: {\n    // Get the date at which this Hereditas instance is unlocked\n    unlockedDate = new Date(($hereditasProfile.requestTime + $hereditasProfile.waitTime) * 1000)\n}\n</script>\n"
  },
  {
    "path": "app/layout/App.svelte",
    "content": "<Navbar />\n<div class=\"container w-full lg:w-3/5 px-2 pt-10 lg:pt-10 mt-10\">\n    <Router {routes}/>\n    <footer class=\"text-xs text-gray-600 text-center mt-8 mb-2\">Built with <a href=\"https://hereditas.app\">Hereditas</a></footer>\n</div>\n\n<script>\n// Components\nimport Navbar from '../components/Navbar.svelte'\n\n// Router and routes\nimport Router from 'svelte-spa-router'\nimport routes from '../routes'\n</script>\n"
  },
  {
    "path": "app/lib/Base64Utils.js",
    "content": "// Based on: https://github.com/danguer/blog-examples/blob/master/js/base64-binary.js\n\n/*\nCopyright (c) 2011, Daniel Guerrero\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL DANIEL GUERRERO BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n/*\n * Uses the new array typed in javascript to binary base64 encode/decode\n * at the moment just decodes a binary base64 encoded\n * into either an ArrayBuffer (decodeArrayBuffer)\n * or into an Uint8Array (decode)\n *\n * References:\n * https://developer.mozilla.org/en/JavaScript_typed_arrays/ArrayBuffer\n * https://developer.mozilla.org/en/JavaScript_typed_arrays/Uint8Array\n */\n\nconst keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='\n\n/* will return a Uint8Array type */\nexport function DecodeArrayBuffer(input) {\n    input = RemovePaddingChars(input)\n\n    const bytes = (input.length / 4) * 3\n    const ab = new ArrayBuffer(bytes)\n    Decode(input, ab)\n\n    return ab\n}\n\nfunction RemovePaddingChars(input) {\n    for (let i = 0; i < Math.min(2, input.length); i++) {\n        if (input.charAt(input.length - 1) == keyStr[64]) {\n            input = input.substring(0, input.length - 1)\n        }\n    }\n    return input\n}\n\nexport function Decode(input, arrayBuffer) {\n    input = RemovePaddingChars(input)\n\n    const bytes = parseInt((input.length / 4) * 3, 10)\n\n    let uarray\n    let chr1, chr2, chr3\n    let enc1, enc2, enc3, enc4\n    let i = 0\n    let j = 0\n\n    if (arrayBuffer) {\n        uarray = new Uint8Array(arrayBuffer)\n    }\n    else {\n        uarray = new Uint8Array(bytes)\n    }\n\n    input = input.replace(/[^A-Za-z0-9+/=]/g, '')\n\n    for (i = 0; i < bytes; i += 3) {\n        //get the 3 octects in 4 ascii chars\n        enc1 = keyStr.indexOf(input.charAt(j++))\n        enc2 = keyStr.indexOf(input.charAt(j++))\n        enc3 = keyStr.indexOf(input.charAt(j++))\n        enc4 = keyStr.indexOf(input.charAt(j++))\n\n        chr1 = (enc1 << 2) | (enc2 >> 4)\n        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2)\n        chr3 = ((enc3 & 3) << 6) | enc4\n\n        uarray[i] = chr1\n        if (enc3 != 64) {\n            uarray[i + 1] = chr2\n        }\n        if (enc4 != 64) {\n            uarray[i + 2] = chr3\n        }\n    }\n\n    return uarray\n}\n"
  },
  {
    "path": "app/lib/Box.js",
    "content": "import {Decrypt, buf2str, UnwrapKey, DeriveKeyArgon2, DeriveKeyPBKDF2} from './CryptoUtils'\nimport {DecodeArrayBuffer} from './Base64Utils'\n\n/**\n * Manages the Hereditas box\n */\nexport class Box {\n    constructor() {\n        this._masterKey = null\n        this._contents = null\n        this._indexFetchingPromise = null\n        this._encryptedIndex = null\n    }\n\n    /**\n     * Returns true if the box is unlocked\n     *\n     * @returns {boolean}\n     */\n    isUnlocked() {\n        return this._masterKey && this._contents\n    }\n\n    /**\n     * Lock the box again, removing the key and the decrypted index from memory\n     */\n    lock() {\n        this._masterKey = null\n        this._contents = null\n    }\n\n    /**\n     * Returns decrypted index\n     * @returns {Array}\n     */\n    getContents() {\n        return this.isUnlocked() ?\n            this._contents :\n            []\n    }\n\n    /**\n     * Fetches a content from the box, then decrypts it before returning.\n     *\n     * @param {Object} info - Info for the content to retrieve. Must contain the `dist` and `tag` properties.\n     * @returns {Promise<Object>} Promise that resolves with info object containing the decrypted content, as a binary ArrayBuffer in the `info.data` property, or an utf-8 encoded string in the `info.text` property (if `info.display` is \"text\" or \"html\").\n     * @async\n     */\n    fetchContent(info) {\n        // If the box is locked, return\n        if (!this.isUnlocked()) {\n            return Promise.reject('Box is locked')\n        }\n\n        // Ensure we have what we need\n        if (!info || !info.dist || !info.tag) {\n            return Promise.reject('Content not found')\n        }\n\n        // Return the promise\n        let iv = null\n        let data = null\n        return fetch(info.dist)\n            // Grab the encrypted contents as ArrayBuffer\n            .then((response) => response.arrayBuffer())\n            // Decrypt the data\n            .then((buffer) => {\n                // The first 40 bytes are the wrapped key, and the next 12 bytes are the IV\n                const wrappedKey = buffer.slice(0, 40)\n                iv = buffer.slice(40, 52)\n                data = buffer.slice(52)\n\n                // Un-wrap the key\n                return UnwrapKey(this._masterKey, wrappedKey)\n            })\n\n            .then((key) => {\n                // Get the tag\n                const tag = DecodeArrayBuffer(info.tag)\n\n                return Decrypt(key, iv, data, tag)\n                    .then((data) => {\n                        // Clone the info object\n                        info = JSON.parse(JSON.stringify(info))\n\n                        // If it's text, decode it\n                        if (info.display == 'text' || info.display == 'html') {\n                            info.text = buf2str(new Uint8Array(data))\n                        }\n                        else {\n                            info.data = data\n                        }\n                        return info\n                    })\n            })\n    }\n\n    /**\n     * Fetches the index from the box.\n     *\n     * @returns {Promise<void>} Promise that resolves (with no value) when the index has been fetched\n     * @async\n     */\n    fetchIndex() {\n        // If we have the index already, do nothing\n        if (this._encryptedIndex) {\n            return Promise.resolve()\n        }\n\n        // If we're already fetching the index, return the promise\n        if (this._indexFetchingPromise) {\n            return this._indexFetchingPromise\n        }\n\n        // Fetch the index\n        this._indexFetchingPromise = fetch('_index')\n            // Grab the contents as ArrayBuffer\n            .then((response) => response.arrayBuffer())\n            // Store the results in the object\n            .then((buffer) => {\n                // Read the data from the response\n                this._encryptedIndex = {\n                    // The first 40 bytes are the wrapped key, and the next 12 bytes are the IV\n                    wrappedKey: buffer.slice(0, 40),\n                    iv: buffer.slice(40, 52),\n                    data: buffer.slice(52)\n                }\n\n                // Request is done\n                this._indexFetchingPromise = null\n            })\n\n        // Return the promise\n        return this._indexFetchingPromise\n    }\n\n    /**\n     * Attempts to decrypt the data using the passphrase and the app token\n     *\n     * @param {string} passphrase - Passphrase typed by the user\n     * @param {string} appToken - Encryption token for the app\n     * @async\n     * @throws Throws an exception if the decryption fails, which usually means that the key/passphrase is wrong\n     */\n    unlock(passphrase, appToken) {\n        if (!passphrase || !appToken) {\n            return Promise.reject('Empty passphrase or app token')\n        }\n\n        // If we haven't fetched the index yet, return\n        if (!this._encryptedIndex) {\n            return Promise.resolve(false)\n        }\n\n        // Convert from Base64 to ArrayBuffer\n        const keySalt = DecodeArrayBuffer(process.env.KEY_SALT)\n        const indexTag = DecodeArrayBuffer(process.env.INDEX_TAG)\n\n        // Key derivation function: PBKDF2 or Argon2\n        let kdf\n        if (process.env.KEY_DERIVATION_FUNCTION == 'pbkdf2') {\n            kdf = DeriveKeyPBKDF2\n        }\n        else if (process.env.KEY_DERIVATION_FUNCTION == 'argon2') {\n            kdf = DeriveKeyArgon2\n        }\n        else {\n            throw Error('Invalid key derivation function requested')\n        }\n\n        // Try decrypting the index: this will verify the passphrase too\n        return Promise.resolve()\n            // First: derive the encryption key\n            .then(() => kdf(passphrase + appToken, keySalt))\n            .then((masterKey) => {\n                this._masterKey = masterKey\n            })\n\n            // Un-wrap the key\n            .then(() => UnwrapKey(this._masterKey, this._encryptedIndex.wrappedKey))\n\n            // Decrypt the index\n            .then((key) => Decrypt(key, this._encryptedIndex.iv, this._encryptedIndex.data, indexTag))\n            .then((data) => {\n                // Convert the buffer to string\n                const str = buf2str(new Uint8Array(data))\n\n                // Store the contents\n                this._contents = JSON.parse(str)\n            })\n\n            // Exceptions likely mean that the key/passphrase are wrong\n            .catch((err) => {\n                // eslint-disable-next-line no-console\n                console.error('Error while unlocking the box:', err)\n\n                // Ensure the box remains locked\n                this.lock()\n\n                // Bubble up\n                throw Error('Failed to unlock to box')\n            })\n    }\n}\n"
  },
  {
    "path": "app/lib/Credentials.js",
    "content": "import {RandomString} from './Utils'\nimport storage from './StorageService'\nimport IdTokenVerifier from 'idtoken-verifier'\n\n/**\n * During the authentication process we need to use nonce's to protect against certain kinds of attacks.\n */\nclass Nonce {\n    constructor() {\n        this._nonceKeyName = 'hereditas-nonce'\n        this._nonceLength = 7\n    }\n\n    /**\n     * Generates a new nonce and stores it in the session storage\n     *\n     * @returns {string} A nonce\n     */\n    generate() {\n        // Generate a nonce\n        const nonce = RandomString(this._nonceLength)\n\n        // Store the nonce in the session\n        storage.sessionStorage.setItem(this._nonceKeyName, nonce)\n\n        return nonce\n    }\n\n    /**\n     * Retrieves the last nonce from session storage\n     *\n     * @returns {string} A nonce\n     */\n    retrieve() {\n        const read = storage.sessionStorage.getItem(this._nonceKeyName)\n\n        const regExp = new RegExp('^[A-Za-z0-9_\\\\-]{' + this._nonceLength + '}$')\n        if (!read || !read.match(regExp)) {\n            return null\n        }\n\n        return read\n    }\n}\n\n/**\n * Managed the authentication flow, and validates the JWT token.\n */\nexport class Credentials {\n    constructor() {\n        this._sessionKeyName = 'hereditas-jwt'\n        this._tokenValidated = false\n        this._nonce = new Nonce()\n        this._profile = null\n    }\n\n    /**\n     * Returns the authorization URL to point users to, storing the nonce\n     *\n     * @returns {string} Authorization URL\n     */\n    authorizationUrl() {\n        // Generate a nonce\n        const nonce = this._nonce.generate()\n\n        // URL-encode the return URL\n        const appUrl = encodeURIComponent(window.location.href)\n\n        // Generate the URL\n        const authIssuer = process.env.AUTH_ISSUER\n        const authClientId = process.env.AUTH_CLIENT_ID\n        return `${authIssuer}/authorize?client_id=${authClientId}&response_type=id_token&redirect_uri=${appUrl}&scope=openid%20profile&nonce=${nonce}&response_mode=fragment`\n    }\n    /**\n     * Returns the profile object from the JWT token\n     *\n     * @returns {Object} Profile for the authenticated user\n     * @async\n     */\n    async getProfile() {\n        // If we have a pre-parsed and pre-validated profile in memory, return that\n        if (this._profile) {\n            return this._profile\n        }\n\n        // Get the token\n        const jwt = this.getToken()\n        if (!jwt) {\n            return {}\n        }\n\n        // Get the profile out of the token\n        let profile\n        try {\n            profile = await this._validateToken(jwt)\n            if (!profile) {\n                profile = {}\n            }\n            this._profile = profile\n            return profile\n        }\n        catch (e) {\n            this._profile = {}\n            throw e\n        }\n    }\n\n    /**\n     * Returns the JWT token for the session\n     *\n     * @returns {string|null} JWT Token, or null if no token\n     */\n    getToken() {\n        const read = storage.sessionStorage.getItem(this._sessionKeyName)\n        if (!read || !read.length) {\n            return null\n        }\n\n        let data\n        try {\n            data = JSON.parse(read)\n        }\n        catch (error) {\n            // eslint-disable-next-line no-console\n            console.error('Error while parsing JSON from sessionStorage', error)\n            throw Error('Could not get the token from session storage')\n        }\n\n        if (!data || !data.jwt) {\n            return null\n        }\n\n        return data.jwt\n    }\n\n    /**\n     * Stores the JWT token for the session\n     *\n     * @param {string} jwt - JWT Token\n     * @async\n     */\n    async setToken(jwt) {\n        // Delete the profile in memory\n        this._profile = null\n\n        // First, validate the token\n        const profile = await this._validateToken(jwt)\n        if (!profile) {\n            throw Error('Token validation failed')\n        }\n\n        // Store the token\n        storage.sessionStorage.setItem(this._sessionKeyName, JSON.stringify({jwt}))\n\n        // Set the profile in memory\n        this._profile = profile\n    }\n\n    /**\n     * Validates a token\n     *\n     * @param {string} jwt - JWT token to validate\n     * @returns {Promise<Object>} Extracted payload\n     * @private\n     */\n    async _validateToken(jwt) {\n        // Ensure issuer ends with /\n        const issuer = process.env.AUTH_ISSUER + (process.env.AUTH_ISSUER.charAt(process.env.AUTH_ISSUER.length - 1) != '/' ? '/' : '')\n\n        // Validate the token\n        const verifier = new IdTokenVerifier({\n            issuer,\n            audience: process.env.AUTH_CLIENT_ID\n        })\n        const payload = await new Promise((resolve, reject) => {\n            verifier.verify(jwt, this._nonce.retrieve(), (error, payload) => {\n                if (error) {\n                    // eslint-disable-next-line no-console\n                    console.error('Validation error', error)\n                    return reject('Invalid token')\n                }\n\n                // Check if the payload contains the Hereditas namespace\n                if (!payload[process.env.ID_TOKEN_NAMESPACE]) {\n                    // eslint-disable-next-line no-console\n                    console.error('Token doesn\\'t contain the Hereditas namespace')\n                    return reject('Token doesn\\'t contain the Hereditas namespace')\n                }\n\n                resolve(payload)\n            })\n        })\n\n        return payload\n    }\n}\n\n// The default export is an instance (singleton) of Credentials\nconst credentials = new Credentials()\nexport default credentials\n"
  },
  {
    "path": "app/lib/CryptoUtils.js",
    "content": "// Inspired by https://gist.github.com/tscholl2/dc7dc15dc132ea70a98e8542fefffa28#file-aes-js\n\n/**\n * Encodes a utf8 string as a byte array.\n * @param {String} str\n * @returns {Uint8Array}\n */\nexport function str2buf(str) {\n    return new TextEncoder('utf-8').encode(str)\n}\n\n/**\n * Decodes a byte array as a utf8 string.\n * @param {Uint8Array} buffer\n * @returns {String}\n */\nexport function buf2str(buffer) {\n    return new TextDecoder('utf-8').decode(buffer)\n}\n\n/**\n * Conctatenates two ArrayBuffer's\n *\n * @param {ArrayBuffer} buffer1 - First buffer\n * @param {ArrayBuffer} buffer2 - Second buffer\n * @returns {ArrayBuffer} The buffer with the data concatenated\n */\nfunction concatBuffers(buffer1, buffer2) {\n    const result = new Uint8Array(buffer1.byteLength + buffer2.byteLength)\n    result.set(new Uint8Array(buffer1), 0)\n    result.set(new Uint8Array(buffer2), buffer1.byteLength)\n    return result.buffer\n}\n\n/**\n * Given a passphrase  and a salt, this generates a crypto key\n * using Argon2.\n * @param {string} passphrase\n * @param {ArrayBuffer} salt\n * @returns {Promise<CryptoKey>}\n * @async\n */\nexport function DeriveKeyArgon2(passphrase, salt) {\n    // Import argon2 dynamically to reduce bundle size, if it's not necessary\n    const saltArr = new Uint8Array(salt)\n    return import('argon2-browser')\n        .then((argon2) => argon2.hash({\n            pass: passphrase,\n            salt: saltArr,\n            type: argon2.ArgonType.Argon2id,\n            time: process.env.ARGON2_ITERATIONS,\n            mem: process.env.ARGON2_MEMORY,\n            hashLen: 32,\n            parallelism: 1\n        }))\n        .then((res) =>\n            window.crypto.subtle.importKey(\n                'raw',\n                res.hash,\n                {name: 'AES-KW', length: 256},\n                false,\n                ['unwrapKey']\n            )\n        )\n}\n\n/**\n * Given a passphrase and a salt, this generates a crypto key\n * using `PBKDF2` with SHA-512 and N iterations.\n * @param {string} passphrase\n * @param {ArrayBuffer} salt\n * @returns {Promise<CryptoKey>}\n * @async\n */\nexport function DeriveKeyPBKDF2(passphrase, salt) {\n    return Promise.resolve()\n        .then(() =>\n            window.crypto.subtle.importKey(\n                'raw',\n                str2buf(passphrase),\n                'PBKDF2',\n                false,\n                ['deriveKey']\n            )\n        )\n        .then((k) =>\n            window.crypto.subtle.deriveKey(\n                {name: 'PBKDF2', salt, iterations: process.env.PBKDF2_ITERATIONS, hash: 'SHA-512'},\n                k,\n                {name: 'AES-KW', length: 256},\n                false,\n                ['unwrapKey']\n            )\n        )\n}\n\n/**\n * Given a key and ciphertext (in the form of a string) as given by `encrypt`,\n * this decrypts the ciphertext and returns the original plaintext\n * @param {CryptoKey} key - Encryption key\n * @param {ArrayBuffer} iv - IV\n * @param {ArrayBuffer} data - Data to decrypt\n * @param {ArrayBuffer} tag - AES-GCM tag\n * @returns {Promise<string>} Decrypted text as string\n * @async\n * @throws Throws an error if the decryption fails, likely meaning that the key was wrong.\n */\nexport function Decrypt(key, iv, data, tag) {\n    return window.crypto.subtle.decrypt(\n        {name: 'AES-GCM', iv},\n        key,\n        concatBuffers(data, tag)\n    )\n}\n\n/**\n * Unwraps a key wrapped with AES-KW (per RFC 3349)\n *\n * @param {CryptoKey} wrappingKey - Key used to wrap/unwrap the key\n * @param {ArrayBuffer} ciphertext - Wrapped key\n * @returns {Promise<CryptoKey>} Unwrapped key\n * @async\n * @throws Throws an error if the decryption fails, likely meaning that the key was wrong.\n */\nexport function UnwrapKey(wrappingKey, ciphertext) {\n    return window.crypto.subtle.unwrapKey(\n        'raw',\n        ciphertext,\n        wrappingKey,\n        {name: 'AES-KW'},\n        {name: 'AES-GCM'},\n        false,\n        ['decrypt']\n    )\n    .then((key) => {\n        return key\n    })\n}\n"
  },
  {
    "path": "app/lib/StorageService.js",
    "content": "// This module is based on https://github.com/Acanguven/StorageService/blob/master/storage.js\n// License: MIT https://github.com/Acanguven/StorageService/blob/master/LICENSE\n\n/**\n * This class allows access to localStorage and sessionStorage.\n * If they are not supported in the current browser, automatically falls back to a cookie-based storage\n */\nexport class StorageService {\n    /**\n     * Initializes the object.\n     */\n    constructor() {\n        this.localStorage = this._isSupported('localStorage') ?\n            window.localStorage :\n            new CookieStore()\n        this.sessionStorage = this._isSupported('sessionStorage') ?\n            window.sessionStorage :\n            new CookieStore(true)\n    }\n\n    /**\n     * Tests if the type of storage is supported in the current browser\n     * \n     * @param {\"localStorage\"|\"sessionStorage\"} type - Name of the storage to test\n     * @returns {boolean} True if the browser supports the kind of storage\n     * @private\n     */\n    _isSupported(type) {\n        const testKey = '__isSupported'\n        const storage = window[type]\n        try {\n            storage.setItem(testKey, '1')\n            storage.removeItem(testKey)\n            return true\n        }\n        catch (error) {\n            return false\n        }\n    }\n}\n\n/**\n * Interface that implements the protocol of localStorage/sessionStorage while keeping the data in memory.\n */\nexport class MemoryStore {\n    /**\n     * Initializes the object.\n     */\n    constructor() {\n        this._store = {}\n    }\n\n    getItem(name) {\n        return this._store[name] || null\n    }\n\n    setItem(name, value) {\n        this._store[name] = value\n    }\n\n    removeItem(name) {\n        delete this._store[name]\n    }\n}\n\n/**\n * Interface that implements the protocol of localStorage/sessionStorage while keeping the data in a cookie.\n */\nexport class CookieStore {\n    /**\n     * Initializes the object.\n     * \n     * @param {bool} isSessionStorage - True if this object is for session storage (controls cookies' expiry)\n     */\n    constructor(isSessionStorage) {\n        this._objectStore = {}\n        this._expireDate = isSessionStorage ?\n            ' path=/' :\n            ' expires=Tue, 19 Jan 2038 03:14:07 GMT path=/'\n        this._updateObject()\n    }\n\n    getItem(name) {\n        return this._objectStore[name] || null\n    }\n\n    setItem(name, value) {\n        if (!name) {\n            return\n        }\n        document.cookie = escape(name) + '=' + escape(value) + this._expireDate\n        this._updateObject()\n    }\n\n    removeItem(name) {\n        if (!name) {\n            return\n        }\n        document.cookie = escape(name) + this._expireDate\n        delete this._objectStore[name]\n    }\n\n    _updateObject() {\n        const couples = document.cookie.split(/\\s*\\s*/)\n        for (let i = 0; i < couples.length; i++) {\n            const couple = couples[i].split(/\\s*=\\s*/)\n            if (couple.length > 1) {\n                const key = unescape(couple[0])\n                this._objectStore[key] = unescape(couple[1])\n            }\n        }\n    }\n}\n\nconst storage = new StorageService()\nexport default storage\n"
  },
  {
    "path": "app/lib/Utils.js",
    "content": "/**\n * Returns a random string, useful for example as nonce.\n *\n * @param {number} length - Length of the string\n * @returns {string} Random string\n */\nexport function RandomString(length = 7) {\n    const bytes = new Uint8Array(length)\n    const random = window.crypto.getRandomValues(bytes)\n    const result = []\n    const charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-_'\n    random.forEach((c) => {\n        result.push(charset[c % charset.length])\n    })\n    return result.join('')\n}\n\n/**\n * Returns a Promise that resolves after a certain amount of time, in ms\n */\nexport function WaitPromise(time) {\n    return new Promise((resolve) => {\n        setTimeout(resolve, time || 0)\n    })\n}\n"
  },
  {
    "path": "app/main.css",
    "content": "/* Tailwind */\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n/* Default styles */\nh1 {\n    @apply text-2xl mb-2;\n}\n\nh2 {\n    @apply text-xl mb-2;\n}\n\nh3 {\n    @apply text-lg mb-2;\n}\n\na {\n    @apply text-blue-600 underline;\n}\n\n/* Rendered content */\nsection.rendered {\n    @apply my-2 mx-4 px-3 py-1 bg-white border border-blue-500;\n}\n.rendered h1, .rendered h2, .rendered h3, .rendered h4, .rendered h5, .rendered h6 {\n    @apply mt-4 mb-2;\n}\n.rendered p {\n    @apply my-2;\n}\n.rendered pre {\n    @apply mx-2;\n}\n.rendered code {\n    @apply w-full whitespace-pre-wrap text-sm;\n}\n.rendered ul {\n    @apply list-disc list-inside;\n}\n.rendered ol {\n    @apply list-decimal list-inside;\n}\n.rendered ul li, .rendered ol li {\n    @apply pl-6;\n}\n.rendered blockquote {\n    @apply italic px-6 text-sm;\n}\n"
  },
  {
    "path": "app/main.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n\n  <!-- Title -->\n  <title>Hereditas</title>\n</head>\n\n<body class=\"bg-gray-100 text-gray-900 tracking-wider leading-normal\">\n</body>\n\n</html>\n"
  },
  {
    "path": "app/main.js",
    "content": "// Style\nimport './main.css'\n\n// JavaScript modules\nimport App from './layout/App.svelte'\nimport credentials from './lib/Credentials'\nimport qs from 'qs'\nimport {Box} from './lib/Box'\n\n// Import stores\nimport {profile, hereditasProfile, box, authError} from './stores'\n\nfunction getHash() {\n    let hash = window.location.hash\n    if (hash && hash.length > 2) {\n        // Remove the leading # and / characters\n        if (hash.charAt(0) == '#') {\n            hash = hash.substr(1)\n        }\n        if (hash.charAt(0) == '/') {\n            hash = hash.substr(1)\n        }\n        const parsed = qs.parse(hash, {\n            depth: 1,\n            parameterLimit: 20,\n            ignoreQueryPrefix: true,\n        })\n\n        // Remove the information from the URL (for security, in case it contains an id_token)\n        history.replaceState(undefined, undefined, '#')\n\n        return parsed\n    }\n    else {\n        return null\n    }\n}\n\nfunction checkAuthError(hash) {\n    // Check if we have an error from the authentication server\n    if (hash && hash.error) {\n        // Check for the error type\n        if (hash.error == 'unauthorized') {\n            return hash.error_description || 'Unauthorized'\n        }\n        else {\n            return hash.error_description || hash.error\n        }\n    }\n\n    return null\n}\n\nasync function handleSession(hash) {\n    // Check if we have an id_token\n    if (hash && hash.id_token) {\n        // Validate and store the JWT\n        // If there's an error, redirect to auth page\n        try {\n            await credentials.setToken(hash.id_token)\n        }\n        catch (error) {\n            // eslint-disable-next-line no-console\n            console.error('Token error', error)\n\n            throw Error('Token error')\n        }\n    }\n\n    // If we have credentials stored, redirect the user to the authentication page\n    if (!credentials.getToken()) {\n        return false\n    }\n\n    // Get the profile\n    // If there's no session or it has expired, redirect to auth page\n    let profileData\n    try {\n        profileData = await credentials.getProfile()\n    }\n    catch (error) {\n        // eslint-disable-next-line no-console\n        console.error('Token error', error)\n\n        throw Error('Token error')\n    }\n\n    return profileData\n}\n\nconst app = (async function() {\n    let _profile\n    let _hereditasProfile = null\n    let _box = null\n\n    // Parse the hash if any\n    const hash = getHash()\n\n    // Check if we have an error from the authentication server\n    let unrecoverableError = checkAuthError(hash)\n    if (!unrecoverableError) {\n        // Load profile and check session\n        try {\n            _profile = await handleSession(hash)\n        }\n        catch (err) {\n            _profile = null\n            unrecoverableError = err\n        }\n\n        // Hereditas profile (from the profile)\n        if (_profile) {\n            // Hereditas profile (from the profile)\n            _hereditasProfile = _profile[process.env.ID_TOKEN_NAMESPACE] || {}\n\n            // Check if we have an app token\n            if (_hereditasProfile.token) {\n                try {\n                    // Create a new box and fetch the index\n                    _box = new Box()\n\n                    // Fetch the index asynchronously and do not wait for completion\n                    _box.fetchIndex()\n                }\n                catch (err) {\n                    // eslint-disable-next-line no-console\n                    console.error('Error while requesting box\\'s data', err)\n                }\n            }\n        }\n    }\n\n    // Store the profile, hereditasProfile and box into Svelte stores\n    profile.set(_profile)\n    hereditasProfile.set(_hereditasProfile)\n    box.set(_box)\n    authError.set(unrecoverableError)\n\n    // Crete a Svelte app by loading the main view\n    return new App({\n        target: document.body\n    })\n})()\n\nexport default app\n"
  },
  {
    "path": "app/postcss.config.js",
    "content": "const path = require('path')\n\nconst production = !process.env.ROLLUP_WATCH\n\nmodule.exports = {\n    plugins: [\n        require('postcss-import')(),\n        require('tailwindcss')(path.resolve(__dirname, 'tailwind.config.js')),\n        require('autoprefixer'),\n        ...(production ? [require('@fullhuman/postcss-purgecss')({\n\n            // Specify the paths to all of the template files in your project\n            content: [\n                path.resolve(__dirname, 'main.html'),\n                path.resolve(__dirname, '**/*.svelte'),\n                path.resolve(__dirname, '**/*.html'),\n            ],\n\n            // Whitelist styles that might be in the content generated from markdown\n            whitelist: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'ul', 'ol', 'li', 'strong', 'b', 'em', 'i', 'a', 'img', 'pre', 'code', 'hr', 'blockquote'],\n\n            // Include any special characters you're using in this regular expression\n            defaultExtractor: content => content.match(/[\\w-/:]+(?<!:)/g) || []\n        })] : []),\n    ],\n}\n"
  },
  {
    "path": "app/robots.txt",
    "content": "User-agent: *\nDisallow: /\n"
  },
  {
    "path": "app/routes.js",
    "content": "// Import routes\nimport UnlockView from './views/UnlockView.svelte'\nimport ListView from './views/ListView.svelte'\nimport ContentView from './views/ContentView.svelte'\n\n// Map of all routes\nexport default {\n    '/': UnlockView,\n    '/list/*': ListView,\n    '/list': ListView,\n    '/content/:element': ContentView\n}\n"
  },
  {
    "path": "app/stores.js",
    "content": "import {writable} from 'svelte/store'\n\nexport const pageTitle = writable('Hereditas')\nexport const profile = writable(null)\nexport const hereditasProfile = writable(null)\nexport const box = writable(false)\nexport const authError = writable(null)\n"
  },
  {
    "path": "app/tailwind.config.js",
    "content": "module.exports = {\n    theme: {\n        extend: {\n\n        },\n        container: {\n            center: true,\n        }\n    },\n    variants: {\n\n    },\n    plugins: [\n\n    ],\n    future: {\n        removeDeprecatedGapUtilities: true,\n    },\n}\n"
  },
  {
    "path": "app/views/ContentView.svelte",
    "content": "{#await contentPromise}\n    Loading...\n{:then content}\n    <nav aria-label=\"breadcrumb\" class=\"mb-4\">\n        <ol class=\"list-none p-0 inline-flex\">\n            <li class=\"flex items-center\">\n                <a href=\"/list/\" use:link>home</a>\n                <span class=\"px-1\">&gt;</span>\n            </li>\n            {#each content.path as path, i}\n                <li class=\"flex items-center\">\n                    <a href=\"/list/{content.path.slice(0, i + 1).join('/') + '/'}\" use:link>{path}</a>\n                    <span class=\"px-1\">&gt;</span>\n                </li>\n            {/each}\n            <li class=\"text-gray-600\" aria-current=\"page\">{content.name}</li>\n        </ol>\n    </nav>\n\n    {#if content.display == 'text'}\n        <section class=\"rendered\">\n            <pre class=\"w-full whitespace-pre-wrap\">{content.text}</pre>\n        </section>\n    {:else if content.display == 'html'}\n        <section class=\"rendered\">\n            {@html content.text}\n        </section>\n    {:else if content.display == 'image'}\n        <img src=\"{content.blob}\" alt=\"{content.name}\" class=\"img-fluid\" />\n    {:else}\n        Download: <a href=\"{content.blob}\" download=\"{content.name}\">{content.name}</a>\n    {/if}\n{:catch error}\n    Error: {error}\n{/await}\n\n\n<script>\n// Libs\nimport {link, replace} from 'svelte-spa-router'\nimport {onMount} from 'svelte'\n\n// Stores\nimport {box} from '../stores'\n\n// Props\n// Params from the route, which includes the element id\nexport let params = {}\n\n// Promise that returns the content (note the IIFE)\nconst contentPromise = (() => {\n    // Get the name of the element\n    // The element variable should be an id, a hex string of 24 chars\n    const elementId = (params && params.element) || ''\n    if (!elementId || !elementId.match(/^[0-9a-f]{24}$/)) {\n        return Promise.reject('Invalid content')\n    }\n\n    // Get info on the content\n    const contents = $box.getContents()\n\n    // Get the tag from the index\n    let element\n    for (const i in contents) {\n        if (contents[i].dist == elementId) {\n            element = contents[i]\n            break\n        }\n    }\n    if (!element) {\n        return Promise.reject('Content not found')\n    }\n\n    // Fetch the data\n    return $box.fetchContent(element)\n        .then((result) => {\n            // Build the URL for attachments and images, and get the file name\n            if (result.display != 'text' && result.display != 'html') {\n                // Convert data to a Blob\n                result.blob = URL.createObjectURL(new Blob([result.data], {type: 'octet/stream'}))\n                delete result.data\n            }\n\n            // Get the path components\n            result.path = element.path.split('/')\n            result.name = result.path.pop()\n\n            return result\n        })\n        .catch((error) => {\n            // Log the error\n            // eslint-disable-next-line no-console\n            console.error(error)\n\n            return error\n        })\n})()\n\n// Ensure that we have unlocked the box\nonMount(() => {\n    if (!$box || !$box.isUnlocked()) {\n        replace('/')\n    }\n})\n</script>\n"
  },
  {
    "path": "app/views/ListView.svelte",
    "content": "<nav aria-label=\"breadcrumb\" class=\"mb-4\">\n    <ol class=\"list-none p-0 inline-flex\">\n        {#if list.paths && list.paths.length}\n            <li class=\"flex items-center\">\n                <a href=\"/list/\" use:link>home</a>\n                <span class=\"px-1\">&gt;</span>\n            </li>\n            {#each list.paths as path, i (path)}\n                {#if i == (list.paths.length - 1)}\n                    <li class=\"text-gray-600\" aria-current=\"page\">{path}</li>\n                {:else}\n                    <li class=\"flex items-center\">\n                        <a href=\"/list/{list.paths.slice(0, i + 1).join('/') + '/'}\" use:link>{path}</a>\n                        <span class=\"px-1\">&gt;</span>\n                    </li>\n                {/if}\n            {/each}\n        {:else}\n            <li class=\"text-gray-600\" aria-current=\"page\">home</li>\n        {/if}\n    </ol>\n</nav>\n\n<div class=\"bg-white w-full max-w-lg p-4 rounded\">\n    <table class=\"table-auto w-full\">\n        <thead>\n            <tr>\n                <th scope=\"col\">Name</th>\n            </tr>\n        </thead>\n        <tbody>\n        {#each list.folders as folder (folder)}\n            <tr class=\"{(list.i++ % 2) ? 'bg-gray-100' : ''}\">\n                <td class=\"border px-4 py-2\">\n                    <a href=\"/list/{list.prefix ? list.prefix + '/' : ''}{folder}\" use:link>{folder}</a>\n                </td>\n            </tr>\n        {/each}\n        {#each list.files as file (file.dist)}\n            <tr class=\"{(list.i++ % 2) ? 'bg-gray-100' : ''}\">\n                <td class=\"border px-4 py-2\">\n                    <a href=\"/content/{file.dist}\" use:link>{file.name}</a>\n                </td>\n            </tr>\n        {/each}\n        </tbody>\n    </table>\n</div>\n\n<script>\n// Libs\nimport {link, replace} from 'svelte-spa-router'\nimport {onMount} from 'svelte'\n\n// Stores\nimport {box} from '../stores'\n\n// Props\n// Params from the route, which includes the prefix\nexport let params = {}\n\n// List of contents\nconst list = {\n    files: [],\n    folders: [],\n    paths: [],\n    prefix: '',\n    i: 0\n}\n$: {\n    // Check if we have a prefix (folder)\n    list.prefix = (params && params.wild) || ''\n    if (list.prefix.charAt(list.prefix.length - 1) == '/') {\n        list.prefix = list.prefix.substring(0, list.prefix.length - 1)\n    }\n\n    // Replace %20 with a space\n    list.prefix = list.prefix.replace(/%20/g, ' ')\n\n    // Get the list\n    const contents = $box.getContents()\n    const files = []\n    const folders = []\n    for (const i in contents) {\n        // Skip items that don't match the prefix\n        const el = contents[i]\n        if (list.prefix && el.path.substring(0, list.prefix.length) !== list.prefix) {\n            continue\n        }\n\n        // Check if it's a file or folder\n        const parts = el.path.substring(list.prefix.length)\n            .split('/') // Split paths\n            .filter((el) => !!el) // Remove empty values\n        if (parts.length > 1) {\n            // This is a folder. Check if it's in the list already\n            const folder = parts[0]\n            if (folders.indexOf(folder) < 0) {\n                folders.push(parts[0])\n            }\n        }\n        else {\n            files.push({\n                name: parts[0],\n                dist: el.dist\n            })\n        }\n    }\n    list.folders = folders\n    list.files = files\n    list.paths = list.prefix ? list.prefix.split('/') : []\n    list.i = 0\n}\n\n// Ensure that we have unlocked the box\nonMount(() => {\n    if (!$box || !$box.isUnlocked()) {\n        replace('/')\n    }\n})\n</script>\n"
  },
  {
    "path": "app/views/UnlockView.svelte",
    "content": "{#if $profile}\n    <UserProfile />\n{:else}\n    <RequestAuthentication />\n{/if}\n\n<h1 class=\"my-4\">About this page</h1>\n<section class=\"rendered mt-4\">\n  {@html welcome}\n</section>\n\n<script>\n// Libs\nimport {onMount} from 'svelte'\nimport {replace} from 'svelte-spa-router'\n\n// Components\nimport UserProfile from '../components/UserProfile.svelte'\nimport RequestAuthentication from '../components/RequestAuthentication.svelte'\n\n// Stores\nimport {box, profile} from '../stores'\n\n// Props\nexport const welcome = process.env.WELCOME_MD\n\n// If box has already been unlocked, just go straight to the list\nonMount(() => {\n    if ($box && $box.isUnlocked()) {\n        replace('/list')\n    }\n})\n</script>\n"
  },
  {
    "path": "app/webpack.config.js",
    "content": "const MiniCssExtractPlugin = require('mini-css-extract-plugin')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nconst SriPlugin = require('webpack-subresource-integrity')\nconst CopyPlugin = require('copy-webpack-plugin')\nconst {DefinePlugin} = require('webpack')\nconst path = require('path')\nconst fs = require('fs')\nconst marked = require('marked')\n\nconst mode = process.env.NODE_ENV || 'production'\nconst prod = mode === 'production'\n\nconst htmlMinifyOptions = {\n    collapseWhitespace: true,\n    conservativeCollapse: true,\n    removeComments: true,\n    collapseBooleanAttributes: true,\n    decodeEntities: true,\n    html5: true,\n    keepClosingSlash: false,\n    processConditionalComments: true,\n    removeEmptyAttributes: true\n}\n\n// Welcome content\nlet welcomeContent = ''\nif (fs.existsSync('welcome.md')) {\n    let welcomeMarkdown = fs.readFileSync('welcome.md', 'utf8')\n    // Remove the front matter, if any\n    if (welcomeMarkdown.startsWith('---')) {\n        welcomeMarkdown = welcomeMarkdown.replace(/^---$.*^---$/ms, '')\n    }\n    welcomeContent = marked(welcomeMarkdown)\n}\n\n/**\n * Returns a configuration object for webpack\n *\n * @param {Object} appParams - Params for the application\n * @returns {Object} Configuration object for webpack\n */\nfunction webpackConfig(appParams) {\n    return {\n        entry: {\n            hereditas: [path.resolve(__dirname, 'main.js')],\n        },\n        resolve: {\n            mainFields: ['svelte', 'browser', 'module', 'main'],\n            extensions: ['.mjs', '.js', '.svelte'],\n            modules: [path.resolve(__dirname, '../node_modules')]\n        },\n        resolveLoader: {\n            modules: [path.resolve(__dirname, '../node_modules')]\n        },\n        output: {\n            path: path.resolve(process.cwd(), appParams.distDir),\n            filename: '[name].[hash].js',\n            chunkFilename: '[name].[id].[hash].js',\n            crossOriginLoading: 'anonymous'\n        },\n        module: {\n            // Do not parse wasm files\n            noParse: /\\.wasm$/,\n            rules: [\n                {\n                    test: /\\.(svelte)$/,\n                    exclude: [],\n                    use: {\n                        loader: 'svelte-loader',\n                        options: {\n                            emitCss: true,\n                        }\n                    }\n                },\n                {\n                    test: /\\.css$/,\n                    use: [\n                        'style-loader',\n                        {loader: 'css-loader', options: {importLoaders: 1}},\n                        'postcss-loader',\n                    ]\n                },\n                {\n                    test: /\\.wasm$/,\n                    loaders: ['base64-loader'],\n                    type: 'javascript/auto'\n                }\n            ]\n        },\n        // Fixes for argon2-browser\n        node: {\n            __dirname: false,\n            fs: 'empty',\n            Buffer: false,\n            process: false\n        },\n        mode,\n        plugins: [\n            // Constants\n            new DefinePlugin({\n                'process.env.AUTH_ISSUER': JSON.stringify(appParams.authIssuer),\n                'process.env.AUTH_CLIENT_ID': JSON.stringify(appParams.authClientId),\n                'process.env.ID_TOKEN_NAMESPACE': JSON.stringify(appParams.idTokenNamespace),\n                'process.env.KEY_SALT': JSON.stringify(appParams.keySalt.toString('base64')),\n                'process.env.INDEX_TAG': JSON.stringify(appParams.indexTag.toString('base64')),\n                'process.env.KEY_DERIVATION_FUNCTION': JSON.stringify(appParams.kdf),\n                'process.env.PBKDF2_ITERATIONS': JSON.stringify(appParams.pbkdf2Iterations),\n                'process.env.ARGON2_ITERATIONS': JSON.stringify(appParams.argon2Iterations),\n                'process.env.ARGON2_MEMORY': JSON.stringify(appParams.argon2Memory),\n                'process.env.WELCOME_MD': JSON.stringify(welcomeContent)\n            }),\n\n            // Extract CSS\n            new MiniCssExtractPlugin({\n                filename: '[name].[hash].css'\n            }),\n\n            // Generate the index.html file\n            new HtmlWebpackPlugin({\n                filename: 'index.html',\n                template: path.resolve(__dirname, 'main.html'),\n                chunks: ['hereditas'],\n                minify: prod ? htmlMinifyOptions : false\n            }),\n\n            // Enable subresource integrity check\n            new SriPlugin({\n                hashFuncNames: ['sha384'],\n                enabled: prod,\n            }),\n\n            // Copy files\n            new CopyPlugin({\n                patterns: [\n                    {from: path.resolve(__dirname, 'robots.txt'), to: ''},\n                ]\n            }),\n        ],\n        devtool: prod ? false : 'source-map',\n        performance: {\n            // 400 KB (up from default 250 KB)\n            maxEntrypointSize: 400000\n        }\n    }\n}\n\nmodule.exports = webpackConfig\n"
  },
  {
    "path": "auth0/01-whitelist.js",
    "content": "function (user, context, callback) {\n    // Apply this rule only for Hereditas, and bypass it for other apps\n    context.clientMetadata = context.clientMetadata || {};\n    if (!context.clientMetadata.hereditas) {\n        return callback(null, user, context);\n    }\n\n    // List of authorized users\n    const whitelist = /*%ALL_USERS%*/;\n\n    // Access should only be granted to verified users.\n    if (!user.email || !user.email_verified) {\n        return callback(new UnauthorizedError('Access denied.'));\n    }\n\n    // Check if the user's email address is whitelisted\n    const userHasAccess = whitelist.some((email) => email === user.email);\n    if (!userHasAccess) {\n        return callback(new UnauthorizedError('Access denied.'));\n    }\n\n    // Continue\n    callback(null, user, context);\n}\n"
  },
  {
    "path": "auth0/02-notify.js",
    "content": "function (user, context, callback) {\n    // Apply this rule only for Hereditas, and bypass it for other apps\n    context.clientMetadata = context.clientMetadata || {};\n    if (!context.clientMetadata.hereditas) {\n        return callback(null, user, context);\n    }\n\n    // Skip if there's no webhook\n    if (!configuration || !configuration.WEBHOOK_URL || configuration.WEBHOOK_URL === '0') {\n        return callback(null, user, context);\n    }\n\n    // List of owners\n    const owners = /*%OWNERS%*/;\n\n    // Trigger the webhook\n    const role = (owners.some((email) => email === user.email)) ? 'owner' : 'user';\n    const body = {\n        value1: `New Hereditas login on ${(new Date()).toUTCString()}. User: ${user.email} (role: ${role})`,\n        value2: user.email,\n        value3: role\n    };\n    const fetch = require('node-fetch@2.6.0');\n    fetch(configuration.WEBHOOK_URL, {\n        method: 'POST',\n        body: JSON.stringify(body),\n        headers: {'Content-Type': 'application/json'}\n    })\n        // Ensure the response has a valid status code\n        .then((response) => {\n            if (response.ok) {\n                return callback(null, user, context);\n            } else {\n                return Promise.reject('Invalid response status code');\n            }\n        })\n        // Catch errors and fail (fail the login even if the notification fails to send)\n        .catch((err) => {\n            console.error(err);\n            callback(new Error('Error sending the notification'));\n        });\n}\n"
  },
  {
    "path": "auth0/03-wait-logic.js",
    "content": "function (user, context, callback) {\n    // Apply this rule only for Hereditas, and bypass it for other apps\n    context.clientMetadata = context.clientMetadata || {};\n    if (!context.clientMetadata.hereditas) {\n        return callback(null, user, context);\n    }\n\n    // List of owners\n    const owners = /*%OWNERS%*/;\n\n    // Get the Auth0 management client\n    const ManagementClient = require('auth0@2.27.0').ManagementClient;\n    const management = new ManagementClient({\n        domain: auth0.domain,\n        clientId: configuration.AUTH0_CLIENT_ID,\n        clientSecret: configuration.AUTH0_CLIENT_SECRET\n    });\n\n    // Get metadata\n    const requestTime = context.clientMetadata.requestTime ? parseInt(context.clientMetadata.requestTime, 10) : 0;\n    const waitTime = parseInt(context.clientMetadata.waitTime, 10);\n\n    // Check if the user is an owner\n    const isOwner = owners.some((email) => email === user.email);\n    if (isOwner) {\n        // Enrich the JWT with the app token\n        if (context.idToken) {\n            context.idToken['https://hereditas.app'] = {\n                role: 'owner',\n                token: configuration.APP_TOKEN,\n                requestTime: 0,\n                waitTime: waitTime\n            };\n        }\n\n        // Reset the timer if it's running\n        if (requestTime > 0) {\n            const params = {client_id: context.clientID};\n            const data = {client_metadata: {requestTime: '0'}};\n            management.clients.update(params, data, (err, client) => {\n                if (err) {\n                    console.log(err);\n                    callback(new Error('Error while updating client_metadata'));\n                }\n                else {\n                    // Continue\n                    callback(null, user, context);\n                }\n            });\n        }\n        else {\n            // Continue\n            callback(null, user, context);\n        }\n    }\n    else {\n        const now = parseInt(Date.now() / 1000, 10);\n\n        // For non-owners: first, check if the timer has been started already, and we've reached the wait time\n        if (requestTime > 0) {\n            // Enrich the JWT with the app token\n            if (context.idToken) {\n                // If the wait time has passed, add the token\n                const token = ((requestTime + waitTime) < now) ?\n                    configuration.APP_TOKEN :\n                    null;\n                // Enrich the JWT\n                context.idToken['https://hereditas.app'] = {\n                    role: 'user',\n                    token: token,\n                    requestTime: requestTime,\n                    waitTime: waitTime\n                };\n            }\n\n            // Continue\n            callback(null, user, context);\n        }\n        else {\n            // Start the timer\n            const params = {client_id: context.clientID};\n            const data = {client_metadata: {requestTime: now.toString()}};\n            management.clients.update(params, data, (err, client) => {\n                if (err) {\n                    console.log(err);\n                    callback(new Error('Error while updating client_metadata'));\n                }\n                else {\n                    // Enrich the JWT with the app token\n                    if (context.idToken) {\n                        context.idToken['https://hereditas.app'] = {\n                            role: 'user',\n                            requestTime: now,\n                            waitTime: waitTime\n                        };\n                    }\n\n                    // Continue\n                    callback(null, user, context);\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "bin/run",
    "content": "#!/usr/bin/env node\n\nrequire('@oclif/command')\n    .run()\n    .then(require('@oclif/command/flush'))\n    .catch(require('@oclif/errors/handle'))\n"
  },
  {
    "path": "bin/run.cmd",
    "content": "@echo off\n\nnode \"%~dp0\\run\" %*\n"
  },
  {
    "path": "cli/commands/auth0/sync.js",
    "content": "'use strict'\n\nconst {Command} = require('@oclif/command')\nconst Config = require('../../lib/Config')\nconst Auth0Management = require('../../lib/Auth0Management')\n\nclass Auth0SetupCommand extends Command {\n    async run() {\n        // Read the config file\n        const config = new Config('hereditas.json')\n        try {\n            await config.load()\n        }\n        catch (e) {\n            this.error(`The current directory ${process.cwd()} doesn't contain a valid Hereditas project`)\n            return this.exit(1)\n        }\n\n        // Initialize the management client\n        const auth0Management = new Auth0Management(config)\n\n        // First step: sync the app on Auth0\n        const clientId = await auth0Management.syncClient(config.get('auth0.hereditasClientId'))\n        config.set('auth0.hereditasClientId', clientId)\n\n        // Second step: create the rules\n        const ruleIds = await auth0Management.syncRules(config.get('auth0.rules'))\n        config.set('auth0.rules', ruleIds)\n\n        // Third step: create rule settings\n        await auth0Management.updateRulesConfigs()\n\n        // Save config changes\n        await config.save()\n\n        this.log('Auth0 configuration updated successfully')\n    }\n}\n\n// Command description\nAuth0SetupCommand.description = `sync the application and rules in Auth0\n\nSynchronizes the status of the resources configured in Auth0: the client (application), the rules and the rule settings.\n`\n\nmodule.exports = Auth0SetupCommand\n"
  },
  {
    "path": "cli/commands/build.js",
    "content": "'use strict'\n\nconst {Command} = require('@oclif/command')\nconst Config = require('../lib/Config')\nconst Builder = require('../lib/Builder')\nconst {cli} = require('cli-ux')\n\nclass BuildCommand extends Command {\n    async run() {\n        // Read the config file\n        const config = new Config('hereditas.json')\n        try {\n            await config.load()\n        }\n        catch (e) {\n            this.error(`The current directory ${process.cwd()} doesn't contain a valid Hereditas project`)\n            return this.exit(1)\n        }\n\n        // Make sure that we have an Auth0 client id\n        const clientId = config.get('auth0.hereditasClientId')\n        if (!clientId) {\n            this.error('The Hereditas application hasn\\'t been configured on Auth0 yet. Please run `hereditas auth0:sync` first')\n            return this.exit(1)\n        }\n\n        // Check if we have a passphrase passed as environmental variable (useful for development only)\n        let passphrase\n        if (process.env.HEREDITAS_PASSPHRASE) {\n            passphrase = process.env.HEREDITAS_PASSPHRASE\n            this.warn('Passphrase set through the HEREDITAS_PASSPHRASE environmental variable; this should be used for development only')\n        }\n        else {\n            // Ask for the user passphrase\n            passphrase = await cli.prompt('User passphrase', {type: 'mask'})\n        }\n        if (!passphrase || passphrase.length < 8) {\n            this.error('Passphrase needs to be at least 8 characters long')\n            return this.exit(1)\n        }\n\n        // Timer\n        const startTime = Date.now()\n\n        // Build the project\n        const builder = new Builder(passphrase, config)\n        await builder.build()\n\n        // Done!\n        const duration = (Date.now() - startTime) / 1000\n\n        if (!builder.hasErrors) {\n            this.log(`Finished building project in ${config.get('distDir')} (took ${duration} seconds)`)\n        }\n        else {\n            this.error(`Build failed (took ${duration} seconds)`)\n        }\n    }\n}\n\n// Command description\nBuildCommand.description = `build an Hereditas project\n\nBuild an Hereditas project in the current working directory.\n`\n\nmodule.exports = BuildCommand\n"
  },
  {
    "path": "cli/commands/init.js",
    "content": "'use strict'\n\nconst {Command, flags} = require('@oclif/command')\nconst fs = require('fs')\nconst util = require('util')\nconst process = require('process')\nconst path = require('path')\nconst Config = require('../lib/Config')\nconst {GenerateToken} = require('../lib/Crypto')\n\nclass InitCommand extends Command {\n    async run() {\n        const {flags} = this.parse(InitCommand)\n\n        // Check if the folder is empty\n        const files = await util.promisify(fs.readdir)('.')\n        if (files.length) {\n            this.error(`Directory ${process.cwd()} isn't empty; aborting`)\n            return this.exit(1)\n        }\n\n        // Get the relative paths to the folders\n        const contentDir = path.relative('', flags.content)\n        const distDir = path.relative('', flags.dist)\n\n        // Create the directories\n        const mkdirPromise = util.promisify(fs.mkdir)\n        await mkdirPromise(contentDir)\n        await mkdirPromise(distDir)\n\n        // Generate an appToken\n        const appToken = await GenerateToken(21)\n\n        // Create configuration\n        const config = new Config('hereditas.json')\n        config.create({\n            distDir: distDir,\n            contentDir: contentDir,\n            auth0: {\n                domain: flags.auth0Domain,\n                managementClientId: flags.auth0ClientId,\n                managementClientSecret: flags.auth0ClientSecret\n            },\n            urls: flags.url,\n            waitTime: 86400,\n            appToken\n        })\n        await config.save()\n\n        // Create the welcome.md file\n        const welcomeContent = `---\n# This welcome file is displayed in the box's authentication page.\n# It can be used to provide information to visitors about what this Hereditas box is, and how it can be used.\n# Note that this file is NOT ENCRYPTED, and it's accessible to the entire world; do not write anything confidential in here.\n---\n\n## What is this?\n\nSomeone (likely, a loved one) told you to come here in case they suddenly disappeared. This box contains important information about the digital life of the person that shared it with you, for example passwords, digital documents, photos, cryptocurrency wallets, etc.\n\n## How do I use this?\n\nSign in above using your existing account. You will then need to type the passphrase that you've been given.\n\nUnless you're the owner of this box, you won't immediately have access to its content, but instead you'll have to wait a certain amount of time.\n\nDuring that time, if the owner signs in here too, they will reset the timer and you will not get access to this box.\n\n## About Hereditas\n\n[Hereditas](https://hereditas.app) is an open source project to generate \"fully-trustless\" digital legacy boxes.\n`\n\n        await util.promisify(fs.writeFile)(path.relative('', 'welcome.md'), welcomeContent)\n\n        this.log('Project initialized')\n    }\n}\n\n// Command description\nInitCommand.description = `initialize a new Hereditas project in the current working directory.\n\nInitialize a new Hereditas project in the current working directory, creating the folders for the content and the generated data, as well as the \"hereditas.json\" configuration file.\n\nThe current working directory needs to be empty, or the command will raise an error.\n`\n\n// Usage example\nInitCommand.usage = `init \\\\\n   --auth0Domain \"yourdomain.auth0.com\" \\\\\n   --auth0ClientId \"...\" \\\\\n   --auth0ClientSecret \"...\" \\\\\n   --url \"https://my.testhereditas.app\"\n`\n\n// Command-line options\nInitCommand.flags = {\n    content: flags.string({\n        char: 'i',\n        description: 'path of the directory with the content',\n        default: 'content'\n    }),\n    dist: flags.string({\n        char: 'o',\n        description: 'path of the dist directory (where output is saved)',\n        default: 'dist'\n    }),\n    auth0Domain: flags.string({\n        char: 'd',\n        description: 'Auth0 domain/tenant (e.g. \"myhereditas.auth0.com\")',\n        required: true\n    }),\n    auth0ClientId: flags.string({\n        char: 'c',\n        description: 'Auth0 client ID for the management app',\n        required: true\n    }),\n    auth0ClientSecret: flags.string({\n        char: 's',\n        description: 'Auth0 client secret for the management app',\n        required: true\n    }),\n    url: flags.string({\n        char: 'u',\n        description: 'URL where the app is deployed to, used for OAuth callbacks (multiple values supported)',\n        required: true,\n        multiple: true\n    })\n}\n\nmodule.exports = InitCommand\n"
  },
  {
    "path": "cli/commands/pack.js",
    "content": "'use strict'\n\nconst {Command} = require('@oclif/command')\nconst Config = require('../lib/Config')\nconst util = require('util')\nconst path = require('path')\nconst fs = require('fs')\nconst {CleanDirectory} = require('../lib/Utils')\n\nconst execPromise = util.promisify(require('child_process').exec)\n\nclass PackCommand extends Command {\n    async run() {\n        // Read the config file\n        const config = new Config('hereditas.json')\n        try {\n            await config.load()\n        }\n        catch (e) {\n            this.error(`The current directory ${process.cwd()} doesn't contain a valid Hereditas project`)\n            return this.exit(1)\n        }\n\n        // Make sure that http://localhost:8080 is allowed as url\n        let urls = config.get('urls')\n        if (!urls || urls.indexOf('http://localhost:8080') == -1) {\n            this.error('Before you can pack a box, the URL `http://localhost:8080` must be allowed. Please run `hereditas url:add -u http://localhost:8080` (and then `hereditas auth0:sync`).')\n            return this.exit(1)\n        }\n\n        // Check that we have go installed\n        try {\n            const {stdout} = await execPromise('go version')\n            const match = stdout.match(/^go version go1\\.([0-9]+)/)\n            if (!match || !match[0] || !match[1]) {\n                throw Error('Invalid go interpreter')\n            }\n            const goVersion = parseInt(match[1], 10)\n            if (goVersion < 13) {\n                throw Error('Go 1.13 or higher is required')\n            }\n        }\n        catch (err) {\n            this.error('Go 1.13 or higher must be installed for this command to work.')\n            return this.exit(1)\n        }\n\n        // Ensure that the GOPATH and HOME are defined\n        if (!process.env.GOPATH || !process.env.HOME) {\n            this.error('Environmental variables GOPATH and HOME must be defined.')\n            return this.exit(1)\n        }\n\n        // Check that we have packr2 installed\n        try {\n            const {stdout} = await execPromise('packr2 version')\n            const match = stdout.match(/^v2/)\n            if (!match || !match[0]) {\n                throw Error('Invalid packr2 version')\n            }\n        }\n        catch (err) {\n            this.error('packr2 must be installed for this command to work.\\nSee https://github.com/gobuffalo/packr/tree/master/v2')\n            return this.exit(1)\n        }\n\n        // Check that the Hereditas box is built\n        const distDir = config.get('distDir')\n        if (!fs.existsSync(path.join(distDir, '_index'))) {\n            this.error('This Hereditas box hasn\\'t been built yet; please run `hereditas build` first.')\n            return this.exit(1)\n        }\n\n        // Create a directory for the Go app\n        // Or clean it if it exists\n        const packPath = path.relative('', 'pack.tmp')\n        if (fs.existsSync(packPath)) {\n            await CleanDirectory(packPath)\n        }\n        else {\n            fs.mkdirSync(packPath)\n        }\n\n        // Copy the Go app's files\n        ['main.go', 'go.mod', 'go.sum'].forEach((file) => {\n            fs.copyFileSync(\n                path.resolve(__dirname, '../../pack/' + file),\n                path.join(packPath, file)\n            )\n        })\n\n        // Create a symlink to distDir inside the packPath\n        fs.symlinkSync(path.join('..', distDir), path.join(packPath, 'dist'), 'dir')\n\n        // Run packr2\n        await execPromise('packr2', {\n            cwd: packPath\n        })\n\n        // Build the Go app for all archs\n        const archs = {\n            'linux-amd64': {\n                GOOS: 'linux',\n                GOARCH: 'amd64'\n            },\n            'linux-386': {\n                GOOS: 'linux',\n                GOARCH: '386'\n            },\n            'linux-arm64': {\n                GOOS: 'linux',\n                GOARCH: 'arm64'\n            },\n            'linux-armv7': {\n                GOOS: 'linux',\n                GOARCH: 'arm',\n                GOARM: '7'\n            },\n            'macos': {\n                GOOS: 'darwin',\n                GOARCH: 'amd64'\n            },\n            'win64.exe': {\n                GOOS: 'windows',\n                GOARCH: 'amd64'\n            },\n            'win32.exe': {\n                GOOS: 'windows',\n                GOARCH: '386'\n            }\n        }\n        for (const extension in archs) {\n            if (!archs.hasOwnProperty(extension)) {\n                continue\n            }\n\n            const file = 'hereditas-box-' + extension\n            this.log('Building ' + file)\n\n            // Environmental variables\n            const env = Object.assign({\n                GOPATH: process.env.GOPATH,\n                HOME: process.env.HOME,\n                CGO_ENABLED: '0',\n                GO111MODULE: 'on'\n            }, archs[extension])\n\n            await execPromise('go build -o ' + path.join('..', '_bin', file), {\n                cwd: packPath,\n                env\n            })\n        }\n\n        // Delete the temporary folder\n        await CleanDirectory(packPath)\n        fs.rmdirSync(packPath)\n\n        this.log('Done! Binaries are in the _bin folder')\n    }\n}\n\n// Command description\nPackCommand.description = `pack a box into a self-contained binary\n\nAfter building a box with Hereditas, the \\`hereditas pack\\` command allows you to generate a self-contained binary (for Windows, macOS and Linux) that contains your Hereditas box and all of its contents. Depending on your use case, this single binary might be easier to distribute.\n\nNote that this command has some pre-requisites:\n\n- You need to have the Go compiler installed\n  (version 1.13 or higher)\n- You need to have packr2 installed in your path\n  (see https://github.com/gobuffalo/packr/tree/master/v2)\n- Your Hereditas box must be already built\n  (run \\`hereditas build\\` beforehand)\n- The URL \\`http://localhost:8080\\` must be allowed for this box\n  (run \\`hereditas url:add -u http://localhost:8080\\`)\n`\n\nmodule.exports = PackCommand\n"
  },
  {
    "path": "cli/commands/regenerate-token.js",
    "content": "'use strict'\n\nconst {Command} = require('@oclif/command')\nconst Config = require('../lib/Config')\nconst {GenerateToken} = require('../lib/Crypto')\n\nclass RegenerateTokenCommand extends Command {\n    async run() {\n        // Read the config file\n        const config = new Config('hereditas.json')\n        try {\n            await config.load()\n        }\n        catch (e) {\n            this.error(`The current directory ${process.cwd()} doesn't contain a valid Hereditas project`)\n            return this.exit(1)\n        }\n\n        // Generate a new appToken and save it\n        const appToken = await GenerateToken(21)\n        config.set('appToken', appToken)\n        await config.save()\n\n        this.log('New application token saved in the configuration file')\n\n        // Notify users that they need to run the auth0:sync command\n        this.log('Info: The new application token will be used for boxes you build from now on, using `hereditas build`; it will not impact existing boxes. Additionally, remember to run `hereditas auth0:sync` to update the application token on Auth0 after deploying the new box.')\n    }\n}\n\n// Command description\nRegenerateTokenCommand.description = `regenerate the application token\n\nUpdate the \"application token\", which is part of the encryption key, in the hereditas.json config file, by generating a new random one.\n\nAfter running this command, you will need to build a new box with \\`hereditas build\\` and then synchronize the changes on Auth0 with \\`hereditas auth0:sync\\`.\n`\n\nmodule.exports = RegenerateTokenCommand\n"
  },
  {
    "path": "cli/commands/url/add.js",
    "content": "'use strict'\n\nconst {Command, flags} = require('@oclif/command')\nconst Config = require('../../lib/Config')\n\nclass UrlAddCommand extends Command {\n    async run() {\n        const {flags} = this.parse(UrlAddCommand)\n\n        // Read the config file\n        const config = new Config('hereditas.json')\n        try {\n            await config.load()\n        }\n        catch (e) {\n            this.error(`The current directory ${process.cwd()} doesn't contain a valid Hereditas project`)\n            return this.exit(1)\n        }\n\n        // Load the current list\n        let urls = config.get('urls')\n        if (!urls) {\n            urls = []\n        }\n\n        // Add all new URLs\n        for (let i = 0; i < flags.url.length; i++) {\n            if (urls.indexOf(flags.url[i]) != -1) {\n                this.log(`URL ${flags.url[i]} is already present`)\n            }\n            else {\n                // Add url\n                urls.push(flags.url[i])\n                this.log(`Added URL ${flags.url[i]}`)\n            }\n        }\n\n        // Save changes\n        config.set('urls', urls)\n        await config.save()\n        this.log('URL list updated')\n\n        // Notify users that they need to run the auth0:sync command\n        this.log('Info: The configuration has been updated locally; for changes to be effective, remember to run `hereditas auth0:sync`')\n    }\n}\n\n// Command description\nUrlAddCommand.description = `add URLs where the box is deployed to, used for OAuth callbacks\n\nAdd one or more URLs to the list of addresses where the Hereditas box is deployed to. This information is stored on Auth0 to whitelist URLs where users are redirected after a successful authentication. Note that the protocol (\\`http://\\` or \\`https://\\`) needs to match too.\n\nAfter running this command, you will need to synchronize the changes on Auth0 with \\`hereditas auth0:sync\\` (it's not necessary to re-build or re-deploy the box).\n`\n\n// Usage example\nUrlAddCommand.usage = `url:add \\\\\n   --url \"https://my.testhereditas.app\"\n`\n\n// Command-line options\nUrlAddCommand.flags = {\n    url: flags.string({\n        char: 'u',\n        description: 'URL where the box is deployed to (multiple values supported)',\n        required: true,\n        multiple: true\n    })\n}\n\nmodule.exports = UrlAddCommand\n"
  },
  {
    "path": "cli/commands/url/list.js",
    "content": "'use strict'\n\nconst {Command} = require('@oclif/command')\nconst Config = require('../../lib/Config')\n\nclass UrlListCommand extends Command {\n    async run() {\n        // Read the config file\n        const config = new Config('hereditas.json')\n        try {\n            await config.load()\n        }\n        catch (e) {\n            this.error(`The current directory ${process.cwd()} doesn't contain a valid Hereditas project`)\n            return this.exit(1)\n        }\n\n        // Load all urls\n        let urls = config.get('urls')\n        if (!urls) {\n            urls = []\n        }\n        this.log(urls.join('\\n'))\n    }\n}\n\n// Command description\nUrlListCommand.description = `list URLs where the box is deployed to\n\nShows the list of URLs where the Hereditas box is deployed to. This list is used by Auth0 to whitelist redirect URLs after users authenticate.\n`\n\nmodule.exports = UrlListCommand\n"
  },
  {
    "path": "cli/commands/url/rm.js",
    "content": "'use strict'\n\nconst {Command, flags} = require('@oclif/command')\nconst Config = require('../../lib/Config')\n\nclass UrlRmCommand extends Command {\n    async run() {\n        const {flags} = this.parse(UrlRmCommand)\n\n        // Read the config file\n        const config = new Config('hereditas.json')\n        try {\n            await config.load()\n        }\n        catch (e) {\n            this.error(`The current directory ${process.cwd()} doesn't contain a valid Hereditas project`)\n            return this.exit(1)\n        }\n\n        // Load the current list\n        let urls = config.get('urls')\n        if (!urls) {\n            urls = []\n        }\n        else {\n            // Remove urls that match\n            urls = urls.filter((el) => flags.url.indexOf(el) == -1)\n        }\n\n        if (!urls.length) {\n            this.error('Cannot remove all URLs from the list')\n            return this.exit(1)\n        }\n\n        // Save changes\n        config.set('urls', urls)\n        await config.save()\n\n        this.log('URL list updated')\n\n        // Notify users that they need to run the auth0:sync command\n        this.log('Info: The configuration has been updated locally; for changes to be effective, remember to run `hereditas auth0:sync`')\n    }\n}\n\n// Command description\nUrlRmCommand.description = `removes URL(s) from the configuration\n\nThese URLs are used by Auth0 to whitelist the pages users are redirected to after authenticating.\n\nAfter running this command, you will need to synchronize the changes on Auth0 with \\`hereditas auth0:sync\\` (it's not necessary to re-build or re-deploy the box).\n`\n\n// Usage example\nUrlRmCommand.usage = `url:rm \\\\\n   --url \"https://my.testhereditas.app\"\n`\n\n// Command-line options\nUrlRmCommand.flags = {\n    url: flags.string({\n        char: 'u',\n        description: 'URL to remove (multiple values supported)',\n        required: true,\n        multiple: true\n    })\n}\n\nmodule.exports = UrlRmCommand\n"
  },
  {
    "path": "cli/commands/user/add.js",
    "content": "'use strict'\n\nconst {Command, flags} = require('@oclif/command')\nconst Config = require('../../lib/Config')\n\nclass UserAddCommand extends Command {\n    async run() {\n        const {flags} = this.parse(UserAddCommand)\n\n        // Read the config file\n        const config = new Config('hereditas.json')\n        try {\n            await config.load()\n        }\n        catch (e) {\n            this.error(`The current directory ${process.cwd()} doesn't contain a valid Hereditas project`)\n            return this.exit(1)\n        }\n\n        // Check if the user is already in the configuration\n        let users = config.get('users')\n        if (!users) {\n            users = []\n        }\n        const added = users.some((el) => el.email == flags.email)\n        if (added) {\n            this.log(`User ${flags.email} is already authorized`)\n            return\n        }\n\n        // Add user\n        users.push({\n            email: flags.email,\n            role: flags.role\n        })\n        config.set('users', users)\n        await config.save()\n\n        this.log(`Added user ${flags.email} (role: ${flags.role})`)\n\n        // Notify users that they need to run the auth0:sync command\n        this.log('Info: The configuration has been updated locally; for changes to be effective, remember to run `hereditas auth0:sync`')\n    }\n}\n\n// Command description\nUserAddCommand.description = `add an authorized user to the box\n\nWhitelist email addresses to allow users to authenticate and access your Hereditas box. If you configure Auth0 to enable social logins (e.g. Google, Facebook and/or Microsoft accounts), users won't need to set up a new account or password, and they can authenticate with their existing social account as long as the email address matches what you've whitelisted.\n\nWhen you whitelist an email address, you can choose between the \"user\" role (the default) and the \"owner\" one. Someone with the \"owner\" role can access the data in this Hereditas box at any time (provided they have the \"user passphrase\" too), and when they authenticate, they reset any timer that might have been started by another person with the \"user\" role.\n\nAfter running this command, you will need to synchronize the changes on Auth0 with \\`hereditas auth0:sync\\` (it's not necessary to re-build or re-deploy the box).\n`\n\n// Usage example\nUserAddCommand.usage = `user:add \\\\\n   --email \"someone@example.com\"\n`\n\n// Command-line options\nUserAddCommand.flags = {\n    email: flags.string({\n        char: 'e',\n        description: 'email address of the user to whitelist',\n        required: true\n    }),\n    role: flags.string({\n        char: 'r',\n        options: ['user', 'owner'],\n        description: 'role: user or owner',\n        default: 'user'\n    })\n}\n\nmodule.exports = UserAddCommand\n"
  },
  {
    "path": "cli/commands/user/list.js",
    "content": "'use strict'\n\nconst {Command, flags} = require('@oclif/command')\nconst Config = require('../../lib/Config')\n\nclass UserListCommand extends Command {\n    async run() {\n        const {flags} = this.parse(UserListCommand)\n\n        // Read the config file\n        const config = new Config('hereditas.json')\n        try {\n            await config.load()\n        }\n        catch (e) {\n            this.error(`The current directory ${process.cwd()} doesn't contain a valid Hereditas project`)\n            return this.exit(1)\n        }\n\n        // Load all users\n        let users = config.get('users')\n        if (!users) {\n            users = []\n        }\n        const list = {owners: [], users: []}\n        for (let i = 0; i < users.length; i++) {\n            if (users[i].role == 'owner') {\n                list.owners.push(users[i].email)\n            }\n            else {\n                list.users.push(users[i].email)\n            }\n        }\n        list.owners.sort()\n        list.users.sort()\n\n        // Show list\n        if (!flags.role) {\n            this.log(`\\x1b[1mOwners:\\x1b[0m\\n  ${list.owners.join('\\n  ')}`)\n            this.log(`\\x1b[1mUsers:\\x1b[0m\\n  ${list.users.join('\\n  ')}`)\n        }\n        else {\n            this.log(list[flags.role + 's'].join('\\n'))\n        }\n    }\n}\n\n// Command description\nUserListCommand.description = `list users that are authorized to authenticate with this box\n\nPrints the list of authorized users (email adddresses) and their role.\n`\n\n// Command-line options\nUserListCommand.flags = {\n    role: flags.string({\n        char: 'r',\n        options: ['', 'user', 'owner'],\n        description: 'filter by role: user or owner (or none)',\n        default: ''\n    })\n}\n\nmodule.exports = UserListCommand\n"
  },
  {
    "path": "cli/commands/user/rm.js",
    "content": "'use strict'\n\nconst {Command, flags} = require('@oclif/command')\nconst Config = require('../../lib/Config')\n\nclass UserRmCommand extends Command {\n    async run() {\n        const {flags} = this.parse(UserRmCommand)\n\n        // Read the config file\n        const config = new Config('hereditas.json')\n        try {\n            await config.load()\n        }\n        catch (e) {\n            this.error(`The current directory ${process.cwd()} doesn't contain a valid Hereditas project`)\n            return this.exit(1)\n        }\n\n        // Load users and remove the requested one\n        let users = config.get('users')\n        if (!users) {\n            users = []\n        }\n        else {\n            users = users.filter((el) => el.email != flags.email)\n        }\n\n        // Save\n        config.set('users', users)\n        await config.save()\n\n        this.log(`Removed user ${flags.email}`)\n\n        // Notify users that they need to run the auth0:sync command\n        this.log('Info: The configuration has been updated locally; for changes to be effective, remember to run `hereditas auth0:sync`')\n    }\n}\n\n// Command description\nUserRmCommand.description = `remove an authorized user from this box\n\nRemoves an email address from the list of those authorized to authenticate with Auth0 for this Hereditas box.\n\nAfter running this command, you will need to synchronize the changes on Auth0 with \\`hereditas auth0:sync\\` (it's not necessary to re-build or re-deploy the box).\n`\n\n// Usage example\nUserRmCommand.usage = `user:rm \\\\\n   --email \"someone@example.com\"\n`\n\n// Command-line options\nUserRmCommand.flags = {\n    email: flags.string({\n        char: 'e',\n        description: 'email address of the user to remove from the whitelist',\n        required: true\n    })\n}\n\nmodule.exports = UserRmCommand\n"
  },
  {
    "path": "cli/commands/wait-time/get.js",
    "content": "'use strict'\n\nconst {Command} = require('@oclif/command')\nconst Config = require('../../lib/Config')\n\nclass WaitTimeGetCommand extends Command {\n    async run() {\n        // Read the config file\n        const config = new Config('hereditas.json')\n        try {\n            await config.load()\n        }\n        catch (e) {\n            this.error(`The current directory ${process.cwd()} doesn't contain a valid Hereditas project`)\n            return this.exit(1)\n        }\n\n        this.log(config.get('waitTime') + 's')\n    }\n}\n\n// Command description\nWaitTimeGetCommand.description = `get the current value for the wait time\n\nThis command returns the current value for the wait time, in seconds.\n\nThe wait time is the amount of time for normal users (that don't have the \"owner\" role) before they can unlock the Hereditas box. Auth0 will not provide users with the \"application token\" unless the wait time has passed since their first login, preventing them from having the information required to unlock the Hereditas box. If users with the \"owner\" role authenticate, the timer is automatically stopped.\n`\n\nmodule.exports = WaitTimeGetCommand\n"
  },
  {
    "path": "cli/commands/wait-time/set.js",
    "content": "'use strict'\n\nconst {Command, flags} = require('@oclif/command')\nconst Config = require('../../lib/Config')\n\nclass WaitTimeSetCommand extends Command {\n    async run() {\n        const {flags} = this.parse(WaitTimeSetCommand)\n\n        // Read the config file\n        const config = new Config('hereditas.json')\n        try {\n            await config.load()\n        }\n        catch (e) {\n            this.error(`The current directory ${process.cwd()} doesn't contain a valid Hereditas project`)\n            return this.exit(1)\n        }\n\n        // Get the new value\n        const time = parseInt(flags.time || '0', 10)\n        if (time < 1) {\n            this.error('Wait time must be a number greater than zero')\n            return this.exit(1)\n        }\n\n        // Save changes\n        config.set('waitTime', time)\n        await config.save()\n        this.log('Wait time updated')\n\n        // Notify users that they need to run the auth0:sync command\n        this.log('Info: The configuration has been updated locally; for changes to be effective, remember to run `hereditas auth0:sync`')\n    }\n}\n\n// Command description\nWaitTimeSetCommand.description = `configure the wait time\n\nThis command sets the wait time (in seconds) for this Hereditas box.\n\nThe wait time is the amount of time for normal users (that don't have the \"owner\" role) before they can unlock the Hereditas box. Auth0 will not provide users with the \"application token\" unless the wait time has passed since their first login, preventing them from having the information required to unlock the Hereditas box. If users with the \"owner\" role authenticate, the timer is automatically stopped.\n\nAfter running this command, you will need to synchronize the changes on Auth0 with \\`hereditas auth0:sync\\` (it's not necessary to re-build or re-deploy the box).\n`\n\n// Usage example\nWaitTimeSetCommand.usage = `wait-time:set \\\\\n   --time 86400\n`\n\n// Command-line options\nWaitTimeSetCommand.flags = {\n    time: flags.string({\n        char: 't',\n        description: 'wait time, in seconds',\n        required: true,\n    })\n}\n\nmodule.exports = WaitTimeSetCommand\n"
  },
  {
    "path": "cli/commands/webhook/get.js",
    "content": "'use strict'\n\nconst {Command} = require('@oclif/command')\nconst Config = require('../../lib/Config')\n\nclass WebhookGetCommand extends Command {\n    async run() {\n        // Read the config file\n        const config = new Config('hereditas.json')\n        try {\n            await config.load()\n        }\n        catch (e) {\n            this.error(`The current directory ${process.cwd()} doesn't contain a valid Hereditas project`)\n            return this.exit(1)\n        }\n\n        this.log(config.get('webhookUrl') || 'No webhook configured')\n    }\n}\n\n// Command description\nWebhookGetCommand.description = `get the current value for the webhook URL\n\nHereditas configures Auth0 to send a notification when someone successfully authenticates into this Hereditas box. The notification can be used as a warning that the timer has started.\n\nNotifications are sent by invoking a webhook, which can then trigger any action you desire. See the Hereditas documentation for examples and ideas on how to use this feature.\n\nIf no webhook is set, Hereditas will not send you notifications on new logins.\n`\n\nmodule.exports = WebhookGetCommand\n"
  },
  {
    "path": "cli/commands/webhook/set.js",
    "content": "'use strict'\n\nconst {Command, flags} = require('@oclif/command')\nconst Config = require('../../lib/Config')\n\nclass WebhookSetCommand extends Command {\n    async run() {\n        const {flags} = this.parse(WebhookSetCommand)\n\n        // Read the config file\n        const config = new Config('hereditas.json')\n        try {\n            await config.load()\n        }\n        catch (e) {\n            this.error(`The current directory ${process.cwd()} doesn't contain a valid Hereditas project`)\n            return this.exit(1)\n        }\n\n        // Save changes\n        config.set('webhookUrl', (flags.url === 'none') ? undefined : flags.url)\n        await config.save()\n        this.log('Webhook URL updated')\n\n        // Notify users that they need to run the auth0:sync command\n        this.log('Info: The configuration has been updated locally; for changes to be effective, remember to run `hereditas auth0:sync`')\n    }\n}\n\n// Command description\nWebhookSetCommand.description = `set the webhook URL used to notify of new logins\n\nHereditas configures Auth0 to send a notification when someone successfully authenticates into this Hereditas box. The notification can be used as a warning that the timer has started.\n\nNotifications are sent by invoking a webhook, which can then trigger any action you desire. See the Hereditas documentation for examples and ideas on how to use this feature.\n\nYou can disable notifications by setting \\`--url none\\` when invoking this command.\n\nAfter running this command, you will need to synchronize the changes on Auth0 with \\`hereditas auth0:sync\\` (it's not necessary to re-build or re-deploy the box).\n`\n\n// Usage example\nWebhookSetCommand.usage = `webhook:set \\\\\n   --url \"https://example.com/webhook/token/abc123XYZ\"\n`\n\n// Command-line options\nWebhookSetCommand.flags = {\n    url: flags.string({\n        char: 'u',\n        description: 'webhook URL; set to \"none\" to remove',\n        required: true,\n    })\n}\n\nmodule.exports = WebhookSetCommand\n"
  },
  {
    "path": "cli/index.js",
    "content": "module.exports = require('@oclif/command')\n"
  },
  {
    "path": "cli/lib/Auth0Management.js",
    "content": "'use strict'\n\nconst fs = require('fs')\nconst util = require('util')\nconst path = require('path')\n\nconst ManagementClient = require('auth0').ManagementClient\n\n/**\n * Configures Auth0 to work with Hereditas\n */\nclass Auth0Management {\n    /**\n     * Initializes the object\n     * @param {Config} config - Config object\n     */\n    constructor(config) {\n        // Ensure that the configuration has Auth0 credentials\n        const auth0Config = config.get('auth0')\n        if (!auth0Config || !auth0Config.domain || !auth0Config.managementClientId || !auth0Config.managementClientSecret) {\n            throw Error('Auth0 Management Client credentials are not present')\n        }\n\n        this._config = config\n        this._management = new ManagementClient({\n            domain: auth0Config.domain,\n            clientId: auth0Config.managementClientId,\n            clientSecret: auth0Config.managementClientSecret\n        })\n    }\n\n    /**\n     * Ensures that we have a client (application) on Auth0 whose configuration matches the desired one. If a client ID is passed, the method checks if the client exists and updates it; otherwise, it will create a new client.\n     *\n     * @param {string} [clientId] - Auth0 client (application) ID\n     * @returns {string} Client ID of the application (either new or updated)\n     */\n    async syncClient(clientId) {\n        // Check if we already have a client and it exists\n        if (clientId) {\n            // Check if it exists; if it does, update the data\n            let data\n            try {\n                data = await this.getClient(clientId)\n            }\n            catch (err) {\n                // If the exception is because the client doesn't exist, all good; otherwise, re-throw it\n                if (err.toString().match(/Not Found/i)) {\n                    data = null\n                }\n                else if (err.name && err.name == 'access_denied') {\n                    throw Error('Invalid Auth0 credentials')\n                }\n                else {\n                    throw err\n                }\n            }\n\n            // If we have an existing client, update it\n            if (data) {\n                try {\n                    await this.updateClient(clientId)\n                }\n                catch (err) {\n                    if (err.name && err.name == 'access_denied') {\n                        throw Error('Invalid Auth0 credentials')\n                    }\n                    else {\n                        throw err\n                    }\n                }\n            }\n            else {\n                clientId = undefined\n            }\n        }\n\n        // If client doesn't exist, create it\n        if (!clientId) {\n            try {\n                clientId = await this.createClient()\n            }\n            catch (err) {\n                if (err.name && err.name == 'access_denied') {\n                    throw Error('Invalid Auth0 credentials')\n                }\n                else {\n                    throw err\n                }\n            }\n        }\n\n        return clientId\n    }\n\n    /**\n     * Updates a client (application) on Auth0 so the configuration matches the desired one.\n     *\n     * @param {string} clientId - Auth0 client (application) ID\n     * @returns {string} Client ID of the updated application\n     */\n    async updateClient(clientId) {\n        const params = {\n            client_id: clientId\n        }\n        const result = await this._management.clients.update(params, this._clientConfiguration())\n        if (result) {\n            return result.client_id\n        }\n    }\n\n    /**\n     * Create the client (application) on Auth0.\n     *\n     * @returns {string} Client ID of the new application\n     */\n    async createClient() {\n        // Create the client\n        const result = await this._management.clients.create(this._clientConfiguration())\n        if (result) {\n            return result.client_id\n        }\n    }\n\n    /**\n     * Retrieve a client (application) from Auth0.\n     *\n     * @param {string} clientId - Client ID\n     */\n    async getClient(clientId) {\n        const data = await this._management.clients.get({client_id: clientId})\n        if (!data || data.client_id != clientId) {\n            return null\n        }\n        return data\n    }\n\n    /**\n     * Ensures that the rules Hereditas needs are present in Auth0, and re-creates them so they're on the last version of the configuration and code.\n     *\n     * @param {Array<string>} [ruleIds] - List of rules already created by Hereditas (if any)\n     * @returns {Array<string>} New list of rules managed by Hereditas\n     */\n    async syncRules(ruleIds) {\n        // First, check if the rules still exist\n        if (ruleIds && ruleIds.length) {\n            const rules = await this.listRules()\n            if (rules && Array.isArray(rules) && rules.length) {\n                // Delete all rules from the array that still exist\n                const promises = []\n                for (let i = 0; i < rules.length; i++) {\n                    const el = rules[i]\n                    if (!el || !el.id) {\n                        continue\n                    }\n\n                    if (ruleIds.indexOf(el.id) != -1) {\n                        promises.push(this._management.rules.delete({id: el.id}))\n                    }\n                }\n\n                // Await all requests in parallel\n                await Promise.all(promises)\n            }\n        }\n\n        // Lastly, re-create all rules and return the new IDs\n        return this.createRules()\n    }\n\n    /**\n     * List all rules\n     *\n     * @returns {Array} List of rules\n     * @async\n     */\n    listRules() {\n        return this._management.rules.getAll()\n    }\n\n    /**\n     * Create all rules required by Hereditas.\n     *\n     * @returns {Array<string>} Array with the ID of the rules, in order\n     * @async\n     */\n    async createRules() {\n        // Read all scripts\n        const readFilePromise = util.promisify(fs.readFile)\n        const scripts = await Promise.all([\n            readFilePromise(path.resolve(__dirname, '../../auth0/01-whitelist.js'), 'utf8'),\n            readFilePromise(path.resolve(__dirname, '../../auth0/02-notify.js'), 'utf8'),\n            readFilePromise(path.resolve(__dirname, '../../auth0/03-wait-logic.js'), 'utf8')\n        ])\n        const names = [\n            'Hereditas 01 - Whitelist email addresses',\n            'Hereditas 02 - Notify',\n            'Hereditas 03 - Wait logic'\n        ]\n\n        // Replacer function in scripts\n        const users = this._config.get('users') || []\n        const replacer = (script) => {\n            const vars = {\n                '/*%ALL_USERS%*/': JSON.stringify(users.map((el) => el.email)),\n                '/*%OWNERS%*/': JSON.stringify(users.filter((el) => el.role == 'owner').map((el) => el.email))\n            }\n            return script.replace(/\\/\\*%([A-Za-z0-9_]+)%\\*\\//, (token) => {\n                return vars[token]\n            })\n        }\n\n        // Create all rules, in order\n        const promises = []\n        for (let i = 0; i < 3; i++) {\n            promises.push(this._management.rules.create({\n                enabled: true,\n                stage: 'login_success',\n                order: i + 1,\n                name: names[i],\n                script: replacer(scripts[i])\n            }))\n        }\n        const results = await Promise.all(promises)\n\n        // Return the IDs of the rules\n        return results.map((el) => el.id)\n    }\n\n    /**\n     * List all rules configurations (only the keys, not values)\n     *\n     * @returns {Array} Array with all the rules configurations\n     * @async\n     */\n    listRulesConfigs() {\n        return this._management.rulesConfigs.getAll()\n    }\n\n    /**\n     * Updates all rules configurations. This creates new configurations, and overwrites existing ones.\n     *\n     * @async\n     */\n    async updateRulesConfigs() {\n        const rulesConfigs = {\n            APP_TOKEN: this._config.get('appToken'),\n            AUTH0_CLIENT_ID: this._config.get('auth0.managementClientId'),\n            AUTH0_CLIENT_SECRET: this._config.get('auth0.managementClientSecret'),\n            WEBHOOK_URL: this._config.get('webhookUrl') || '0'\n        }\n\n        // Create all rules configurations\n        const promises = []\n        for (const key in rulesConfigs) {\n            const value = rulesConfigs[key]\n            promises.push(this._management.rulesConfigs.set({key}, {value}))\n        }\n\n        await Promise.all(promises)\n    }\n\n    /**\n     * Returns the configuration object for a client (application) on Auth0.\n     *\n     * @returns {Object} Configuration object for the client (application) on Auth0\n     */\n    _clientConfiguration() {\n        return {\n            name: 'Hereditas',\n            is_first_party: true,\n            oidc_conformant: true,\n            cross_origin_auth: false,\n            description: 'This application is managed by the Hereditas CLI. For information, see https://hereditas.app',\n            logo_uri: '',\n            sso: false,\n            callbacks: this._config.get('urls'),\n            allowed_logout_urls: [],\n            allowed_clients: [],\n            client_metadata: {\n                requestTime: '0',\n                waitTime: this._config.get('waitTime') + '', // Cast as string\n                hereditas: '1'\n            },\n            allowed_origins: [],\n            jwt_configuration: {\n                alg: 'RS256',\n                lifetime_in_seconds: 1800\n            },\n            token_endpoint_auth_method: 'none',\n            app_type: 'spa',\n            grant_types: [\n                'implicit'\n            ]\n        }\n    }\n}\n\nmodule.exports = Auth0Management\n"
  },
  {
    "path": "cli/lib/Builder.js",
    "content": "'use strict'\n\nconst fs = require('fs')\nconst crypto = require('crypto')\nconst {Readable} = require('stream')\nconst util = require('util')\nconst Content = require('./Content')\nconst {CleanDirectory} = require('./Utils')\nconst path = require('path')\nconst kw = require('./aes-kw')\nconst argon2 = require('argon2-browser')\n\n// Webpack\nconst webpack = util.promisify(require('webpack'))\nconst webpackConfig = require('../../app/webpack.config')\n\n// Promisified fs.readdir, fs.stat and fs.unlink\nconst readdirPromise = util.promisify(fs.readdir)\nconst statPromise = util.promisify(fs.stat)\n\n// Promisified crypto.pbkdf2 and crypto.randomBytes\nconst pbkdf2Promise = util.promisify(crypto.pbkdf2)\nconst randomBytesPromise = util.promisify(crypto.randomBytes)\n\n/**\n * Object containing properties for a file in the content directory\n *\n * @typedef {Object} HereditasContentFile\n * @property {string} path - Path of the file (relative to the contentDir)\n * @property {number} size - File size in bytes\n * @property {string} dist - Random filename used in the dist folder\n * @property {string} tag - Authentication tag for AES-GCM\n * @property {string} processed - If the file has been pre-processed, this explains how (e.g. \"markdown\"); it's undefined otherwise\n * @property {\"text\"|\"image\"|\"attach\"} display - Configures how the file should be displayed\n */\n\n/**\n * Builds a project\n */\nclass Builder {\n    /**\n     * Initializes the object\n     * @param {string} passphrase - User passphrase\n     * @param {Config} config - Config object\n     */\n    constructor(passphrase, config) {\n        // Store config in the object\n        this._config = config\n\n        this._passphrase = passphrase\n\n        // Output\n        this.keySalt = null\n        this.indexTag = null\n        this.hasErrors = false\n    }\n\n    /**\n     * Performs a full build\n     *\n     * @async\n     */\n    async build() {\n        // Step 1: clean dist directory\n        await CleanDirectory(this._config.get('distDir'))\n\n        // Step 2: get the list of files\n        let content = await this._scanContent()\n\n        // Step 3: generate a salt for deriving the encryption key\n        // This needs to be of 64 bytes, which is the length of a SHA-512 hash\n        this.keySalt = await randomBytesPromise(64)\n\n        // Step 4: derive the master key\n        const masterKey = await this._deriveKey(this._passphrase + this._config.get('appToken'), this.keySalt)\n\n        // Step 5: encrypt all files\n        content = await this._encryptContent(masterKey, content)\n\n        // Step 6: write an (encrypted) index file\n        this.indexTag = await this._createIndex(masterKey, content)\n\n        // Step 7: build the app with webpack\n        const appParams = {\n            distDir: this._config.get('distDir'),\n            authIssuer: 'https://' + this._config.get('auth0.domain'),\n            authClientId: this._config.get('auth0.hereditasClientId'),\n            idTokenNamespace: 'https://hereditas.app',\n            indexTag: this.indexTag,\n            keySalt: this.keySalt,\n            kdf: this._config.get('kdf'),\n            pbkdf2Iterations: this._config.get('pbkdf2.iterations'),\n            argon2Iterations: this._config.get('argon2.iterations'),\n            argon2Memory: this._config.get('argon2.memory')\n        }\n        const webpackStats = await webpack(webpackConfig(appParams))\n\n        // Check if webpack compilation had errors\n        if (webpackStats.hasErrors()) {\n            const errors = webpackStats.toJson().errors\n            // eslint-disable-next-line no-console\n            console.error('\\x1b[31m\\x1b[1m' + 'WEBPACK ERRORS' + '\\x1b[0m\\n')\n            for (const i in errors) {\n                // eslint-disable-next-line no-console\n                console.error('\\x1b[31m' + errors[i] + '\\x1b[0m\\n')\n            }\n\n            this.hasErrors = true\n        }\n        if (webpackStats.hasWarnings()) {\n            const warnings = webpackStats.toJson().warnings\n            // eslint-disable-next-line no-console\n            console.warn('\\x1b[33m\\x1b[1m' + 'WEBPACK WARNINGS' + '\\x1b[0m\\n')\n            for (const i in warnings) {\n                // eslint-disable-next-line no-console\n                console.warn('\\x1b[33m' + warnings[i] + '\\x1b[0m\\n')\n            }\n        }\n    }\n\n    /**\n     * Derives a 256 bit key from the passphrase and the salt, using the preferred key derivation function.\n     * The key can be used directly for symmetric encryption.\n     *\n     * @param {string} passphrase - Passphrase for the key\n     * @param {Buffer} salt - Salt for the key\n     * @returns {Promise<Buffer>} Promise that resolves to the buffer with the key\n     * @async\n     */\n    _deriveKey(passphrase, salt) {\n        const kdf = this._config.get('kdf')\n        if (kdf == 'pbkdf2') {\n            // Using SHA-512, the result is a 512 bit key, so truncate it to 256 bit (32 bytes)\n            return pbkdf2Promise(\n                passphrase,\n                salt,\n                this._config.get('pbkdf2.iterations'),\n                32,\n                'sha512'\n            )\n        }\n        else if (kdf == 'argon2') {\n            return Promise.resolve()\n                .then(() => argon2.hash({\n                    pass: passphrase,\n                    salt: salt,\n                    type: argon2.ArgonType.Argon2id,\n                    time: this._config.get('argon2.iterations'),\n                    mem: this._config.get('argon2.memory'),\n                    hashLen: 32,\n                    parallelism: 1\n                }))\n                .then((res) => {\n                    return Buffer.from(res.hash)\n                })\n        }\n        else {\n            throw Error('Invalid key derivation function requested')\n        }\n    }\n\n    /**\n     * Creates an index file and encrypts it on disk.\n     *\n     * @param {Buffer} masterKey - Master encryption key\n     * @param {HereditasContentFile[]} content - List of content\n     * @returns {Buffer} Authentication tag\n     * @async\n     */\n    async _createIndex(masterKey, content) {\n        // Creat the index file, and convert it to a Readable Stream\n        const indexData = JSON.stringify(content)\n        const inStream = new Readable()\n        inStream._read = () => {} // _read is required, but it's a no-op\n        inStream.push(indexData, 'utf8')\n        inStream.push(null) // End\n\n        // Output stream\n        const outStream = fs.createWriteStream(path.join(this._config.get('distDir'), '_index'))\n\n        // Encrypt the index and write it, returning the tag\n        return this._encryptStream(masterKey, inStream, outStream)\n    }\n\n    /**\n     * Encrypts all the content\n     * @param {Buffer} masterKey - Master encryption key\n     * @param {HereditasContentFile[]} content - List of content\n     * @returns {HereditasContentFile[]} - List of content with the dist and tag properties set\n     * @async\n     */\n    async _encryptContent(masterKey, content) {\n        // Clone the content object\n        const result = JSON.parse(JSON.stringify(content))\n\n        // Iterate through the content and encrypt each file\n        for (const i in result) {\n            // Generate the file name for the output file (a random hex string)\n            const dist = (await randomBytesPromise(12)).toString('hex')\n\n            // Create the Readable stream to the input, and Writable stream to the output\n            const outStream = fs.createWriteStream(path.join(this._config.get('distDir'), dist))\n\n            // Pre-process the file\n            const content = new Content(result[i], this._config)\n            await content.process()\n            result[i] = content.el\n\n            // Encrypt the stream and get the tag\n            const tagBuf = await this._encryptStream(masterKey, content.inStream, outStream)\n            const tag = tagBuf.toString('base64')\n\n            // Add the dist and tag properties to the result object\n            result[i].dist = dist\n            result[i].tag = tag\n        }\n\n        return result\n    }\n\n    /**\n     * Encrypts a stream using aes-256-gcm\n     *\n     * @param {Buffer} masterKey - Master key; must be 256 bit long\n     * @param {Stream} inStream - Readable stream with the data to encrypt\n     * @param {Stream} outStream - Writable stream to pipe the data to\n     * @returns {Buffer} Authentication tag\n     * @async\n     */\n    async _encryptStream(masterKey, inStream, outStream) {\n        // Generate a key for this specific file\n        const fileKey = await randomBytesPromise(32)\n        // Generate an IV\n        const fileIV = await randomBytesPromise(12)\n\n        // Wrap the file's key with the master key, using AES-KW (RFC-3394)\n        const wrappedKey = kw.encrypt(masterKey, fileKey)\n\n        return new Promise((resolve, reject) => {\n            // Write the wrapped key and IV to the outStream, at the beginning\n            outStream.write(wrappedKey)\n            outStream.write(fileIV)\n\n            // Create the Cipher, which can be used as a stream transform too\n            const cipher = crypto.createCipheriv('aes-256-gcm', fileKey, fileIV)\n\n            // When the encryption is done, get the authentication tag\n            cipher.on('end', () => {\n                resolve(cipher.getAuthTag())\n            })\n\n            // In case of errors, throw\n            inStream.on('error', reject)\n            outStream.on('error', reject)\n\n            // Pipe the input stream through the cipher and then to the output stream\n            inStream.pipe(cipher).pipe(outStream)\n        })\n    }\n\n    /**\n     * Recursively scans the content directory, listing files\n     * @returns {HereditasContentFile[]} List of files\n     * @async\n     */\n    async _scanContent() {\n        // Will contain the final list\n        const result = []\n\n        // Recursive function that scans folders\n        const scanFolder = async (folder) => {\n            folder = folder || ''\n\n            // Scan the list of files and folders, recursively\n            const list = await readdirPromise(path.join(this._config.get('contentDir'), folder))\n            for (const e in list) {\n                const el = folder + list[e]\n\n                // Check if we need to include this path or ignore it\n                if (!includePath(el)) {\n                    continue\n                }\n\n                // Check if it's a directory\n                const stat = await statPromise(path.join(this._config.get('contentDir'), el))\n                if (!stat) {\n                    continue\n                }\n\n                // If it's a directory, scan it recursively\n                if (stat.isDirectory()) {\n                    await scanFolder(el + path.sep)\n                }\n                else {\n                    // Add the file to the list\n                    result.push({\n                        path: el,\n                        size: stat.size\n                    })\n                }\n            }\n        }\n\n        // Get the list\n        await scanFolder()\n        return result\n    }\n}\n\n// Returns true if a path should be included in the box\n// This ignores files such as operating system's metadata\nfunction includePath(str) {\n    const base = path.basename(str)\n\n    if (\n        // Linux\n        base.endsWith('~') ||\n        base == '.directory' ||\n        // macOS\n        base == '.DS_Store' ||\n        base == '.AppleDouble' ||\n        base == '.LSOverride' ||\n        base.startsWith('._') ||\n        // Windows\n        base == 'Thumbs.db' ||\n        base == 'Thumbs.db:encryptable' ||\n        base == 'desktop.ini' ||\n        base == 'Desktop.ini'\n    ) {\n        return false\n    }\n    return true\n}\n\nmodule.exports = Builder\n"
  },
  {
    "path": "cli/lib/Config.js",
    "content": "'use strict'\n\nconst fs = require('fs')\nconst util = require('util')\nconst defaultsDeep = require('lodash.defaultsdeep')\nconst cloneDeep = require('lodash.clonedeep')\nconst SMHelper = require('smhelper')\n\nconst ConfigVersion = 20190222\n\n/**\n * Authorized users\n *\n * @typedef {object} HereditasUser\n * @property {string} email - Email address\n * @property {\"user\"|\"owner\"} role - Role: \"user\" or \"owner\"\n */\n\n/**\n * Configuration dictionary for Hereditas\n *\n * @typedef {object} HereditasConfig\n * @property {number} version - Version of the configuration object\n * @property {string} contentDir - Folder containing the source content\n * @property {string} distDir - Folder where to place the compiled project\n * @property {boolean} processMarkdown - If true, enable processing of Markdown files into HTML\n * @property {object} auth0 - Auth0 configuration\n * @property {string} auth0.domain - Auth0 domain/tenant (e.g. \"myhereditas.auth0.com\")\n * @property {string} auth0.hereditasClientId - Auth0 app client ID for Hereditas\n * @property {string} auth0.managementClientId - Client ID for the Auth0 Management app\n * @property {string} auth0.managementClientSecret - Client Secret for the Auth0 Management app\n * @property {Array<string>} rules - ID of the Auth0 rules created by the Hereditas CLI\n * @property {\"pbkdf2\"|\"argon2\"} kdf - Key derivation function to use: pbkdf2 or argon2 (default)\n * @property {object} pbkdf2 - Configuration for pbkdf2\n * @property {string} pbkdf2.iterations - Number of iterations\n * @property {string} webhookUrl - URL of the webhook to trigger when a new user logs into Hereditas.\n * @property {Array<HereditasUser>} users - List of users\n * @property {string} appToken - Application token; when combined with the user passphrase, this allows deriving the encryption key\n * @property {number} waitTime - The amount of time, in seconds, to wait before Auth0 can return to users the app token\n * @property {Array<string>} urls - list of URLs where your app will be deployed to, e.g. `https://hereditas.example.com`, or `https://myname.blob.core.windows.net/hereditas`, etc; this is used for OAuth redirects.\n */\n\n/**\n * Helper class for managing Hereditas configuration\n */\nclass Config {\n    /**\n     * Initializes the object.\n     *\n     * @param {string} [filename=\"hereditas.json\"] - Name of the file on disk\n     */\n    constructor(filename) {\n        if (!filename) {\n            filename = 'hereditas.json'\n        }\n\n        this._filename = filename\n\n        // userConfig is the data read from the config file. config is that, plus defaults\n        this._userConfig = null\n        this._config = {}\n    }\n\n    /**\n     * Create a new userConfig object.\n     *\n     * Note that this doesn't save changes on disk, you must manually call `save()`.\n     *\n     * @param {HereditasConfig} initConfig - Initial configuration values\n     */\n    create(initConfig) {\n        this._userConfig = {\n            version: ConfigVersion\n        }\n        defaultsDeep(this._userConfig, initConfig)\n\n        // Update the config in memory\n        this._config = {}\n        this._defaults()\n    }\n\n    /**\n     * Reads and parses a config file, validating it.\n     *\n     * @param {string} [filename=\"hereditas.json\"] - Name of the config file to read; default is \"hereditas.json\"\n     * @throws Throws an error if the config file doesn't exist or is not a valid Hereditas config\n     */\n    async load() {\n        // Read the file\n        const configFile = await util.promisify(fs.readFile)(this._filename, 'utf8')\n        if (!configFile) {\n            throw Error('Cannot read config file')\n        }\n\n        // Parse JSON and ensure it's a valid format\n        this._userConfig = JSON.parse(configFile)\n        if (!this._validate()) {\n            throw Error('Invalid config file')\n        }\n\n        // Apply defaults\n        this._defaults()\n    }\n\n    /**\n     * Returns value for key from configuration\n     *\n     * @param {string} key - Key of the object, in \"dot notation\"\n     * @returns {*} Value of the configuration key requested (cloned)\n     */\n    get(key) {\n        let val\n\n        // If key contains a dot, we are requesting a nested object\n        if (key.indexOf('.') != -1) {\n            val = SMHelper.getDescendantProperty(this._config, key)\n        }\n        else {\n            val = this._config[key]\n        }\n\n        // Returns a clone of the object so it can't be modified\n        return cloneDeep(val)\n    }\n\n    /**\n     * Returns all config values (cloned).\n     *\n     * @returns {HereditasConfig} All configuration data\n     */\n    all() {\n        return cloneDeep(this._config)\n    }\n\n    /**\n     * Updates the value of a user config.\n     *\n     * Note: this does NOT save the changes on disk; you must invoke `save()` for that.\n     *\n     * @param {string} key - Name of the key to update, using the \"dot notation\"\n     * @param {*} value - New value\n     */\n    set(key, value) {\n        // Update the value and validate the config\n        SMHelper.updatePropertyInObject(this._userConfig, key, value)\n        if (!this._validate()) {\n            throw Error('Invalid config data')\n        }\n\n        // Update the config in memory\n        this._config = {}\n        this._defaults()\n    }\n\n    /**\n     * Save changes to user configuration to disk.\n     *\n     * @returns {Promise} Returns a promise that resolves when the changes have been committed to disk.\n     * @async\n     */\n    save() {\n        return util.promisify(fs.writeFile)(this._filename, JSON.stringify(this._userConfig, null, 2))\n    }\n\n    /**\n     * Validates a config object, ensuring that all required keys are present.\n     *\n     * @returns {boolean} Returns true on valid configuration objects\n     * @throws Throws an Error if the config file isn't valid\n     */\n    _validate() {\n        if (!this._userConfig || typeof this._userConfig != 'object' || !Object.keys(this._userConfig).length) {\n            throw Error('Invalid config file')\n        }\n        if (!this._userConfig.version) {\n            throw Error('Config file is missing required key version')\n        }\n        if (!this._userConfig.distDir) {\n            throw Error('Config file is missing required key distDir')\n        }\n        if (!this._userConfig.contentDir) {\n            throw Error('Config file is missing required key contentDir')\n        }\n        if (!this._userConfig.appToken) {\n            throw Error('Config file is missing required key appToken')\n        }\n        if (!this._userConfig.auth0 || typeof this._userConfig.auth0 != 'object' || !Object.keys(this._userConfig.auth0).length) {\n            throw Error('Config file is missing required key auth0')\n        }\n        if (!this._userConfig.auth0.domain) {\n            throw Error('Config file is missing required key auth0.domain')\n        }\n        if (!this._userConfig.urls || !Array.isArray(this._userConfig.urls) || !this._userConfig.urls.length) {\n            throw Error('Config file is missing required key urls')\n        }\n\n        return true\n    }\n\n    /**\n     * Applies default parameters to the userConfig object, and stores that into the config object\n     */\n    _defaults() {\n        defaultsDeep(this._config, this._userConfig, {\n            processMarkdown: true,\n            kdf: 'argon2',\n            pbkdf2: {\n                iterations: 100000\n            },\n            argon2: {\n                iterations: 2,\n                memory: 64 * 1024\n            }\n        })\n    }\n}\n\nmodule.exports = Config\n"
  },
  {
    "path": "cli/lib/Content.js",
    "content": "'use strict'\n\nconst fs = require('fs')\nconst {Readable} = require('stream')\nconst util = require('util')\nconst path = require('path')\n\n// Marked.js\nconst marked = util.promisify(require('marked'))\n\n// Promisified fs.readFile, fs.readdir, fs.stat and fs.unlink\nconst readFilePromise = util.promisify(fs.readFile)\n\n// List of file extensions of images\nconst imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp']\n\n/**\n * Processes content\n */\nclass Content {\n    /**\n     * Constructor\n     *\n     * @param {HereditasContentFile} el - Content to process\n     * @param {Config} config - Config object\n     */\n    constructor(el, config) {\n        this._config = config\n        this._el = el\n        this._inStream = null\n    }\n\n    /**\n     * Object with the content information, potentially modified\n     *\n     * @returns {HereditasContentFile} Content information object\n     */\n    get el() {\n        return this._el\n    }\n\n    /**\n     * Readable stream to the (optionally, pre-processed) content\n     *\n     * @returns {ReadableStream} Stream with content data\n     */\n    get inStream() {\n        return this._inStream\n    }\n\n    /**\n     * Pre-processes the content in any way necessary, e.g. converting Markdown into HTML.\n     */\n    async process() {\n        if (this._el.path.match(/\\.txt$/i)) {\n            await this._processText()\n        }\n        else if (this._el.path.match(/\\.(md|markdown)$/i)) {\n            await this._processMarkdown()\n        }\n        else {\n            await this._processBinary()\n        }\n    }\n\n    /**\n     * Processes images and other binary files\n     */\n    async _processBinary() {\n        // Just get a stream to the file on disk\n        this._inStream = fs.createReadStream(path.join(this._config.get('contentDir'), this._el.path))\n\n        // Set the display as \"image\" for images, and \"attach\" for anything else\n        const extension = this._el.path.split('.')\n            .pop()\n            .toLowerCase()\n        this._el.display = (imageExtensions.indexOf(extension) < 0) ?\n            'attach' :\n            'image'\n    }\n\n    /**\n     * Processes simple Text files\n     */\n    async _processText() {\n        // Get a stream to the file and display it as text\n        this._inStream = fs.createReadStream(path.join(this._config.get('contentDir'), this._el.path))\n        this._el.display = 'text'\n    }\n\n    /**\n     * Processes Markdown files, converting them to HTML\n     */\n    async _processMarkdown() {\n        // Check if we process Markdown into HTML\n        if (this._config.get('processMarkdown')) {\n            const markdown = await readFilePromise(path.join(this._config.get('contentDir'), this._el.path), 'utf8')\n            const html = await marked(markdown)\n\n            // Push the data into a stream\n            this._inStream = new Readable()\n            this._inStream._read = () => {} // _read is required, but it's a no-op\n            this._inStream.push(html, 'utf8')\n            this._inStream.push(null) // End\n\n            // Mark the file as pre-processed\n            this._el.processed = 'markdown'\n            this._el.display = 'html'\n            // TODO: Handle different encodings\n        }\n        else {\n            // If not processing them, treat Markdown files as simple text\n            await this._processText()\n        }\n    }\n}\n\nmodule.exports = Content\n"
  },
  {
    "path": "cli/lib/Crypto.js",
    "content": "'use strict'\n\nconst crypto = require('crypto')\nconst util = require('util')\n\n/**\n * Generates a token with `length` random bytes, and returns it as a base64-encoded string.\n *\n * @param {number} length - Number of bytes to generate (before converting to base64)\n * @returns {string} Token represented as base64-encoded string\n */\nasync function GenerateToken(length) {\n    if (!length || length < 0) {\n        length = 20\n    }\n\n    const bytes = await util.promisify(crypto.randomBytes)(length)\n    return bytes.toString('base64')\n}\n\nmodule.exports = {\n    GenerateToken\n}\n"
  },
  {
    "path": "cli/lib/Utils.js",
    "content": "const fs = require('fs')\nconst util = require('util')\nconst path = require('path')\n\nconst readdirPromise = util.promisify(fs.readdir)\nconst unlinkPromise = util.promisify(fs.unlink)\nconst rmdirPromise = util.promisify(fs.rmdir)\n\n/**\n * Deletes all files in a directory, without removing the directory itself.\n *\n * @param {string} directory - Directory to clean\n * @async\n */\nasync function CleanDirectory(directory) {\n    const files = await readdirPromise(directory)\n    return Promise.all(files.map(\n        (file) => {\n            const target = path.join(directory, file)\n            const stat = fs.lstatSync(target)\n            if (stat.isDirectory()) {\n                return CleanDirectory(target)\n                    .then(() => rmdirPromise(target))\n            }\n            return unlinkPromise(target)\n        }\n    ))\n}\n\nmodule.exports = {\n    CleanDirectory\n}\n"
  },
  {
    "path": "cli/lib/aes-kw.js",
    "content": "/**\n * This module is based on https://github.com/calvinmetcalf/aes-kw\n *\n * Copyright (C) Calvin Metcalf. Released under MIT license.\n */\n\nconst crypto = require('crypto')\nconst xor = require('buffer-xor/inplace')\nconst bufferEq = require('buffer-equal-constant-time')\n\nconst IV = Buffer.from('A6A6A6A6A6A6A6A6', 'hex')\nconst EMPTY_BUF = Buffer.alloc(0)\nfunction Encrypter(key, decipher) {\n    if (decipher) {\n        this.cipher = crypto.createDecipheriv(getCipherName(key), key, EMPTY_BUF)\n    }\n    else {\n        this.cipher = crypto.createCipheriv(getCipherName(key), key, EMPTY_BUF)\n    }\n    this.cipher.setAutoPadding(false)\n}\nEncrypter.prototype.encrypt = function(iv, buf) {\n    if (iv.length !== 8) {\n        throw new Error('invalid iv length')\n    }\n    if (buf.length !== 8) {\n        throw new Error('invalid data length')\n    }\n    this.cipher.update(iv)\n    return this.cipher.update(buf)\n}\nEncrypter.prototype.done = function() {\n    this.cipher.final()\n}\nfunction getCipherName(key) {\n    switch (key.length) {\n        case 16: return 'aes-128-ecb'\n        case 24: return 'aes-192-ecb'\n        case 32: return 'aes-256-ecb'\n    }\n}\nfunction msb(b) {\n    return b.slice(0, 8)\n}\nfunction lsb(b) {\n    return b.slice(-8)\n}\nexports.encrypt = encrypt\nfunction encrypt(key, plaintext) {\n    if (plaintext.length % 8) {\n        throw new Error('must be 64 bit increment')\n    }\n    const enc = new Encrypter(key)\n    let j = -1\n    let i, b\n    const t = Buffer.alloc(8)\n    let a = IV\n    const n = plaintext.length / 8\n    const r = createR(plaintext)\n    while (++j <= 5) {\n        i = -1\n        while (++i < n) {\n            b = enc.encrypt(a, r[i])\n            t.writeUInt32BE(0, 0)\n            t.writeUInt32BE((n * j) + i + 1, 4)\n            a = xor(msb(b), t)\n            r[i] = lsb(b)\n        }\n    }\n    enc.done()\n    return Buffer.concat([a].concat(r))\n}\nexports.decrypt = decrypt\nfunction decrypt(key, ciphertext) {\n    if (ciphertext.length % 8) {\n        throw new Error('must be 64 bit increment')\n    }\n    const enc = new Encrypter(key, true)\n    let j = 6\n    let i, b\n    const t = Buffer.alloc(8)\n    const n = ciphertext.length / 8\n    const r = createR(ciphertext)\n    let a = r[0]\n    while (--j >= 0) {\n        i = n\n        while (--i) {\n            t.writeUInt32BE(0, 0)\n            t.writeUInt32BE(((n - 1)* j) + i, 4)\n            a = xor(a, t)\n            b = enc.encrypt(a, r[i])\n            a = msb(b)\n            r[i] = lsb(b)\n        }\n    }\n    enc.done()\n    if (!bufferEq(a, IV)) {\n        throw new Error('unable to decrypt')\n    }\n    return Buffer.concat(r.slice(1))\n}\nfunction createR(buf) {\n    const n = buf.length / 8\n    const out = new Array(n)\n    let i = -1\n    while (++i < n) {\n        out[i] = buf.slice(i * 8, (i + 1) * 8)\n    }\n    return out\n}\n"
  },
  {
    "path": "docs-source/.gitignore",
    "content": "# Generated files\n/content/cli/*.md\n!/content/cli/__template.md\n/content/menu/*.md\n!/content/menu/__template.md\n/dist\n\n# Created by https://www.gitignore.io/api/hugo\n# Edit at https://www.gitignore.io/?templates=hugo\n\n### Hugo ###\n# gitginore template for Hugo projects\n# website: https://gohugo.io\n\n# generated files by hugo\n/public/\n/resources/_gen/\n\n# executable may be added to repository\nhugo.exe\nhugo.darwin\nhugo.linux\n\n# End of https://www.gitignore.io/api/hugo\n"
  },
  {
    "path": "docs-source/config.yaml",
    "content": "baseURL: \"https://hereditas.app/\"\nlanguageCode: en-us\ntitle: Hereditas\n\n# Ignore files\nignoreFiles:\n  - \"\\\\.sh$\"\n  - \"Makefile\"\n  - \"Dockerfile\"\n  - \"__template.md\"\n\n# Enable all URLs to be relative, and make them end with \".html\"\nrelativeURLs: true\ncanonifyURLs: false\nuglyurls: true\n\n# Book Theme is intended for documentation use, therefore it doesn't render taxonomy.\n# You can hide related warning with config below\ndisableKinds:\n- taxonomy\n- taxonomyTerm\n- section\n\n# Goldmark\nmarkup:\n  goldmark:\n    renderer:\n      unsafe: true\n\n# Syntax highlighting\npygmentsCodeFences: true\npygmentsStyle: \"tango\"\n\n# Google analytics\n#googleAnalytics: UA-72379106-2\n\n# Privacy\nprivacy:\n  googleAnalytics:\n    anonymizeIP: true\n  youtube:\n    privacyEnhanced: true\n\n# Theme\ntheme: book\n\n# Theme params\nparams:\n  # (Optional, default true) Show or hide table of contents globally\n  # You can also specify this parameter per page in front matter\n  BookShowToC: true\n\n  # (Optional, default none) Set leaf bundle to render as side menu\n  # When not specified file structure and weights will be used\n  BookMenuBundle: /menu\n\n  # (Optional, default docs) Specify section of content to render as menu\n  # You can also set value to \"*\" to render all sections to menu\n  BookSection: docs\n\n  # This value is duplicate of $link-color for making active link highlight in menu bundle mode\n  # BookMenuBundleActiveLinkColor: \\#004ed0\n\n  # Include JS scripts in pages. Disabled by default.\n  # - Keep side menu on same scroll position during navigation\n  BookEnableJS: false\n\n  # Set source repository location.\n  # Used for 'Last Modified' and 'Edit this page' links.\n  #BookRepo: https://github.com/ItalyPaleAle/hereditas/docs\n\n  # Enable \"Edit this page\" links for 'doc' page type.\n  # Disabled by default. Uncomment to enable. Requires 'BookRepo' param.\n  # Path must point to 'content' directory of repo.\n  #BookEditPath: edit/master/exampleSite/content\n\n  # Plausible Analytics\n  PlausibleAnalytics:\n    Domain: hereditas.app\n"
  },
  {
    "path": "docs-source/content/_index.md",
    "content": "---\ntitle: What is Hereditas\ntype: docs\n---\n\n![Hereditas logo](/images/hereditas-logo.png)\n\n# What is Hereditas\n\n**What happens to your digital life after you're gone?**\n\nHereditas, which means *inheritance* in Latin, is a static website generator that builds **fully-trustless digital legacy boxes**, where you can store information for your relatives to access in case of your sudden death or disappearance.\n\nFor example, you could use this to pass information such as passwords, cryptographic keys, cryptocurrency wallets, sensitive documents, etc.\n\n{{< youtube lZEKgB5dzQ4 >}}\n\n> Note: the video above was recorded with Hereditas 0.1. The design of the interface has been improved and made nicer in 0.2.\n\n## Why we built Hereditas\n\nCheck out the announcement [**blog post**](https://withblue.ink/2019/03/18/what-happens-to-your-digital-life-after-youre-gone-introducing-hereditas.html?utm_source=web&utm_campaign=hereditas-docs) to understand more about why we built Hereditas and why you need it too.\n\n## Design\n\nWe've designed Hereditas with three principles in mind.\n\n### Fully trustless – really\n\nWith Hereditas, you don't need to trust any person or provider. **No other person or company has standing access to your data.**\n\nAs the owner of an Hereditas box, you can nominate some authorized users by whitelisting their email address and giving them a *user passphrase*.\n\nTo prevent authorized users from having standing access to your data, however, once they log into your Hereditas box for the first time, they need to wait for a few hours or days before they can unlock the box. This gives you, the owner of the box, enough time to stop the timer, by simply logging into the same Hereditas box.\n\nFor example, if you set the waiting time to 24 hours (the default), when a relative of yours tries to log in the timer starts and Hereditas sends you a notification right away. If you've not disappeared, you can log into the same Hereditas box within 24 hours and stop the timer. Without any action from you, after the delay has passed all your relatives would be able to unlock your Hereditas box by logging in again and typing the *user passphrase*.\n\nHereditas generates digital legacy boxes that are encrypted bundles within static HTML5 applications. The encryption key is split between what you give your users and what's stored inside the authorization provider, so no company or provider has standing access to your data.\n\n### Simple for your loved ones\n\nWe designed Hereditas so it's simple to use for your loved ones, when they need to access your digital legacy box, even if they are not tech wizards. **A web browser is all they need.**\n\nAs the owner of the Hereditas box, you will provide them with the URL where they can find your box, and the *user passphrase* they need to use to unlock it. You will also whitelist their email address so they can log in with their existing accounts (e.g. Google, Facebook, Microsoft…) – no need to create new accounts for them and have new passwords around.\n\n### No costly and/or time-consuming maintenance\n\nYou don't want to rely on a solution that you'll have to keep paying and/or patching for the rest of your life (and in this case, we mean that literally).\n\n**Hereditas outputs a static HTML5 app that you can host anywhere you'd like**, for free or almost.\n\n## Open source\n\nWe made Hereditas fully open source so you can study how the app works down to every detail. We wrote the app in JavaScript, and we use Node.js for the CLI and HTML5 for the static web app. **The source code is available on GitHub at [ItalyPaleAle/hereditas](https://github.com/ItalyPaleAle/hereditas)** under GNU General Public License (GPL) version 3.0 (see [LICENSE](https://github.com/ItalyPaleAle/hereditas/tree/master/LICENSE.md)).\n\nWe happily accept contributions! Feel free to submit a Pull Request to fix bugs or add new features. Equally important, you can contribute by improving this [documentation](https://github.com/ItalyPaleAle/hereditas/tree/master/docs-source) you're reading.\n\nIf you believe you've found a security issue that could impact other people, please [report it confidentially](https://www.npmjs.com/advisories/report?package=hereditas).\n\n## Get started\n\nReady? Get started with Hereditas now!\n\n<a class=\"hereditas-button\" href=\"{{< relref \"/introduction/quickstart-video.md\" >}}\">Quickstart Video</a>\n\nOr:\n\n<a class=\"hereditas-button\" href=\"{{< relref \"/guides/get-started.md\" >}}\">Get started documentation</a>\n"
  },
  {
    "path": "docs-source/content/advanced/auth0-manual-configuration.md",
    "content": "---\ntitle: Auth0 manual configuration\ntype: docs\n---\n\n# Auth0 manual configuration\n\nHereditas uses Auth0 to authenticate users and to provide the *application token*, which is part of the string used to derive the encryption key.\n\nThis document explains the configuration that the Hereditas CLI performs when you execute the [`hereditas auth0:sync`]({{< relref \"/cli/auth0_sync.md\" >}}) command.\n\n> **Important:** this page is primarily primarily meant as reference. We recommend letting the Hereditas CLI manage the Auth0 configuration with the [`hereditas auth0:sync`]({{< relref \"/cli/auth0_sync.md\" >}}) command rather than changing settings manually.\n\n## Differences with the \"API Access\" application\n\nIn the [Auth0 setup]({{< relref \"/guides/auth0-setup.md\" >}}) article we guided you through the creation of an **API Access** app (\"Machine to Machine\") and how to get the credentials, which are used by the Hereditas CLI to configure Hereditas on Auth0, including setting up the rules, and also by the Hereditas rules on Auth0 to set timers.\n\nThis document focuses on the main \"Hereditas\" application on Auth0, which is what users will authenticate with.\n\n## Hereditas application\n\nOn Auth0, create an application of type **Single Page Application**. You can name it any way you want, but `Hereditas` is probably a good name.\n\nOnce the app is created, take note of the **Domain** and the **Client ID**. We will not need the Client Secret.\n\n### Application configuration\n\nEnsure that the application is configured with:\n\n- **Application type**: Should be \"Single Page Application\"\n- **Allowed callback URLs**: List of URLs (one per line) where your box is deployed to\n- **JWT Expiration**: Recommended to set it to a value that make sense for you, for example 1800 seconds (30 mins)\n\nUnder **Advanced Settings**, then **OAuth**:\n\n- **JsonWebToken Signature Algorithm**: Should be \"RS256\"\n- **OIDC Conformant**: Should be enabled\n\nIn the **Grant types** tab:\n\n- **Grants**: choose only \"Implicit\"\n\n### Application Metadata\n\nThe application needs to be configured with the following \"Application Metadata\" (called `client_metadata` in the Auth0 APIs):\n\n- **`hereditas`**: this is required and must be set to `1`.\n- **`requestTime`**: set this value to `0`. When users that are not owners sign in, the application rules automatically update this value with the current time (as UNIX timestamp).\n- **`waitTime`**: the amount of time, in seconds, to wait before Auth0 can return to users (non-owners) the app token. Set this value to whatever makes sense for you; `86400` (1 day) is often a good amount of time.\n\n## Rules\n\nThe `auth0` folder in the repository contains the rules that need to be configured in Auth0. Note that the order below is very important!\n\n- **Hereditas 01 - Whitelist email addresses (`01-whitelist.js`)**: This rule configures which users are allowed to authenticate, by whitelisting their email address.\n- **Hereditas 02 - Notify (`02-notify.js`)**: This rule sends a notification on all successful logins via a webhook.\n- **Hereditas 03 - Wait logic (`03-wait-logic.js`)**: This rule implements the \"wait logic\". If a non-owner users signs in, the rule starts the timer (by setting the current timestamp in the `waitTime` application metadata). After the wait is over, this same rule adds the app token to the claim. If an owner signs in, the timer is reset (and the app token is added to the claim regardless).\n\nThe scripts above contain some tokens that need to be replaced with the list of email addresses of all users or just owners.\n\n- **`/*%OWNERS%*/`** This token needs to be replaced with the JSON-encoded array of the email addresses of users who are owners.\n- **`/*%ALL_USERS%*/`** This token needs to be replaced with the JSON-encoded array of the email addresses of all users.\n\nFor example:\n\n````js\nconst whitelist = /*%ALL_USERS%*/;\nconst owners = /*%OWNERS%*/;\n// Become\nconst whitelist = [\"me@example.com\", \"someone@example.com\"];\nconst owners = [\"me@example.com\"];\n````\n\nIn the rules page, add the following settings. You will need some credentials from the \"API Access\" app, which is the \"Machine to Machine\" app created in the getting started guide.\n\n- **`APP_TOKEN`**: the application token part of the encryption key.\n- **`AUTH0_CLIENT_ID`**: Set this to the Client ID of the API Access app.\n- **`AUTH0_CLIENT_SECRET`**: Set this to the Client Secret of the API Access app.\n- **`WEBHOOK_URL`**: URL of the webhook invoked after a successful authentication (see the [Login notifications]({{< relref \"/guides/login-notifications.md\" >}})).\n"
  },
  {
    "path": "docs-source/content/advanced/building-self-contained-binaries.md",
    "content": "---\ntitle: Building self-contained binaries\ntype: docs\n---\n\n# Building self-contained binaries\n\nStarting with Hereditas 0.2, in addition to generating a set of files to be served via HTTP, you can also build a self-contained binary that can be distributed as an app without further dependencies. This binary launches a local web server, and it contains and serves all of your Hereditas box, including the (encrypted) files.\n\nThe [`hereditas pack`]({{< relref \"/cli/pack.md\" >}}) command, run inside your Hereditas working directory, automatically builds binaries for Windows (32-bit and 64-bit), macOS, and Linux (amd64, i386, arm64, armv7).\n\n## Requirements\n\nThere are a few requirements before you can run the `hereditas pack` command (the CLI will check them for you too):\n\n1. You need to have the Go compiler installed in your laptop, at least version 1.13.\n1. You need to have packr2 installed in your laptop and available in your `PATH`; you can fetch it with `go get -u github.com/gobuffalo/packr/v2/packr2` or you can get a [pre-compiled binary](https://github.com/gobuffalo/packr/releases).\n1. The URL `http://localhost:8080` must be allowed for your Hereditas box. You can do that with `hereditas url:add -u http://localhost:8080` (and then `hereditas auth0:sync`).\n1. You must have already built your Hereditas box. That is, you must have run the `hereditas build` command.\n\n## Build the binaries\n\nRun the [`hereditas pack`]({{< relref \"/cli/pack.md\" >}}) command to automatically build the binaries; this can take a couple of minutes.\n\n```sh\nhereditas pack\n```\n\nThe binaries will be placed in the `_bin` folder:\n\n```sh\n~/hereditas $ ls _bin\nhereditas-box-linux-386\nhereditas-box-linux-amd64\nhereditas-box-linux-arm64\nhereditas-box-linux-armv7\nhereditas-box-macos\nhereditas-box-win32.exe\nhereditas-box-win64.exe\n```\n\nPick the right binary for your system(s) and distribute them in any way you see fit.\n\n> **macOS and Gatekeeper:** Hereditas does not sign the macOS binary, as that requires a developer certificate from Apple. The app you compile will run in your Mac without issues, but if you distribute it to other people, Gatekeeper might refuse to run it as it's unsigned. Read more about Gatekeeper in the [Apple support site](https://support.apple.com/en-us/HT202491).\n\n## Running the self-contained app\n\nMost users will be able to open your Hereditas box by double-clicking on the binary.\n\nThe app runs in the command line, and it should automatically open a terminal if you launch it through your operating system's shell.\n\nThe app launches a web server listening on `127.0.0.1` and on port `8080`. It will then automatically open the user's default web browser (if possible) with the URL `http://localhost:8080`.\n"
  },
  {
    "path": "docs-source/content/advanced/configuration-file.md",
    "content": "---\ntitle: Configuration file\ntype: docs\n---\n\n# Configuration file\n\nEach Hereditas working folder contains a JSON configuration file called `hereditas.json`. This file is automatically generated when running the [`hereditas init`]({{< relref \"/cli/init.md\" >}}) command, and it's modified by the app itself when you run certain commands.\n\n## Contents\n\nThe structure of the document is similar to the following:\n\n````json\n{\n    \"version\": 20190222,\n    \"contentDir\": \"content\",\n    \"distDir\": \"dist\",\n    \"appToken\": \"...\",\n    \"waitTime\": 86400,\n    \"users\": [\n        {\n            \"email\": \"me@example.com\",\n            \"role\": \"owner\"\n        },\n        {\n            \"email\": \"someone@example.com\",\n            \"role\": \"user\"\n        }\n    ],\n    \"auth0\": {\n        \"domain\": \"myhereditas.auth0.com\",\n        \"managementClientId\": \"...\",\n        \"managementClientSecret\": \"...\",\n        \"hereditasClientId\": \"...\",\n        \"rules\": [\n            '...',\n            '...',\n            '...'\n        ]\n    },\n    \"urls\": [\n        \"https://my.testhereditas.app\",\n        \"https://another.testhereditas.app\"\n    ],\n    \"webhookUrl\": \"https://example.com/webhook/token/abc123XYZ\",\n    \"kdf\": \"argon2\",\n    \"argon2\": {\n        \"memory\": 65536\n    },\n    \"pbkdf2\": {\n        \"iterations\": 100000\n    },\n    \"processMarkdown\": true\n}\n````\n\n## Configuration options\n\n### Hereditas working folder options\n\nThese options configure the way the Hereditas current working folder is set up.\n\n- **`version`** (int): This represents the version of the configuration file. At present moment, this is `20190222`.\n- **`contentDir`** (string): Name of the directory, inside the Hereditas working folder, containing the documents and files to include in the Hereditas box. The content of this folder is not encrypted, as the CLI will do that automatically.<br />The default value is `content`. This value can be set during box initialization with the `--content` option for the [`hereditas init`]({{< relref \"/cli/init.md\" >}}) command.\n- **`distDir`** (string): Name of the directory, inside the Hereditas working folder, where the CLI will put the generated web app and the encrypted content.<br />The default value is `dist`. This value can be set during box initialization with the `--dist` option for the [`hereditas init`]({{< relref \"/cli/init.md\" >}}) command.\n\n### Access control basics\n\nThese options are used to configure the way Hereditas protects access to your box.\n\n- **`appToken`** (string): The *application token*, which together with the *user passphrased* is used to derive the encryption and decryption key. The application token is stored in the Hereditas configuration file and synced with Auth0. This is an important secret to protect, although by itself (without the *user passphrase*, which isn't stored anywhere) it isn't sufficient to encrypt or decrypt your data.<br/>This value is generated automatically when a new Hereditas project is initialized (using [`hereditas init`]({{< relref \"/cli/init.md\" >}})), and can be re-generated with [`hereditas regenerate-token`]({{< relref \"/cli/regenerate-token.md\" >}}). Note that changing this value will require re-building and re-deploying your box.\n- **`waitTime`** (string): Amount of time users need to wait before they can unlock an Hereditas box, in seconds. The default value is `86400`, or 24 hours.<br/>This value can be also set with [`hereditas wait-time:set`]({{< relref \"/cli/wait-time_set.md\" >}})\n- **`users`** (array of objects): Array of users that are whitelisted to use the app. Users can authenticate with Auth0 using any social account they support, e.g. Google, Facebook, Microsoft, etc; as long as the email address matches what's whitelisted in this configuration option. For more information, please refer to the documentation on [Managing users]({{< relref \"/guides/managing-users.md\" >}}). This value is an array of objects with the structure:\n    - **`users.$.email`** (string): email address of the user\n    - **`users.$.role`** (string): either `user` or `owner`\n\n### Auth0 settings and credentials\n\nThe **`auth0`** object contains settings and credentials for communicating with the Auth0 APIs. For more information, please refer to the [Auth0 Setup]({{< relref \"/guides/auth0-setup.md\" >}}) article.\n\n- **`auth0.domain`** (string): domain on Auth0, e.g. `myhereditas.auth0.com`. This is created when you sign up for Auth0, and it's globally unique.<br/>This is set with the `--auth0Domain` option for the [`hereditas init`]({{< relref \"/cli/init.md\" >}}) command.\n- **`auth0.managementClientId`** (string): Client Id (ie. public key) for the \"API Access\" application on Auth0, a \"Machine-to-Machine\" application used by the Hereditas CLI and the rules to interact with the Auth0 APIs.<br/>This is set with the `--auth0ClientId` option for the `hereditas init` command.\n- **`auth0.managementClientSecret`** (string): Client Secret (ie. private key) for the \"API Access\" application on Auth0.<br/>This is set with the `--auth0ClientSecret` option for the `hereditas init` command.\n- **`auth0.hereditasClientId`** (string): Client Id (ie. public key) for the \"Hereditas\" application on Auth0. This value is generated automatically by the Auth0 CLI, when running the [`hereditas auth0:sync`]({{< relref \"/cli/auth0_sync.md\" >}}) command. In most cases, you should not edit this Client Id manually.\n- **`auth0.rules`** (array of strings): These strings represent the Ids of the rules that the Hereditas CLI creates on Auth0. These are returned automatically by the Auth0 CLI, when running the [`hereditas auth0:sync`]({{< relref \"/cli/auth0_sync.md\" >}}) command. In most cases, you should not edit this array manually.\n\n### Deployment and webhook URLs\n\nThe Hereditas configuration file stores two separate kinds of URLs:\n\n- **`urls`** (array of strings): list of URLs where the Hereditas application is deployed to. These URLs are synced with Auth0, which uses them to whitelist callback URLs after users authenticate successfully. You should list every URL where your app might be reachable at. Note that the protocol needs to match too, so `http://example.com` and `https://example.com` are separate URLs. For more information, see the [Auth0 Setup]({{< relref \"/guides/auth0-setup.md\" >}}) and [Deploying the box]({{< relref \"/guides/deploy-box.md\" >}}) articles.<br />You need to provide (at least) one URL with the `--url` flag when running [`hereditas init`]({{< relref \"/cli/init.md\" >}}). These URLs can also be changed with CLI commands, such as [`url:add`]({{< relref \"/cli/url_add.md\" >}}) and [`url:rm`]({{< relref \"/cli/url_rm.md\" >}}).\n- **`webhookUrl`** (string): URL of the webhook that is invoked to notify owners of a successful authentication. Please refer to the [Login notifications]({{< relref \"/guides/login-notifications.md\" >}}) article for more information.<br />This value can also be set with the [`webhook:set`]({{< relref \"/cli/webhook_set.md\" >}}) command.\n\n### Advanced options\n\nThese options are set by default by Hereditas. You shouldn't change these options unless you have a good reason for that, and you're confident that you know what you're doing.\n\n- **`kdf`** (string): Key derivation function to use. Supported values are `argon2` for Argon2id (default), and `pbkdf2` for PBKDF2.\n- **`argon2`** (object): Parameters for deriving a key with Argon2.\n    - **`argon2.iterations`** (int): Iterations used by Argon2 (in Argon2id mode). Default value is 2.\n    - **`argon2.memory`** (int): Memory used by Argon2 (in Argon2id mode), in bytes. Default value is 65536 (64KB)\n- **`pbkdf2`** (object): Parameters for deriving a key with PBKDF2.\n    - **`pbkdf2.iterations`** (int): Number of iterations to use for PBKDF2. Default value is 100000 (1E+05)\n- **`processMarkdown`** (boolean): Switch to enable/disable the conversion of Markdown documents into HTML, when building the box. Default is true (enabled).\n"
  },
  {
    "path": "docs-source/content/advanced/index-file.md",
    "content": "---\ntitle: Index file\ntype: docs\n---\n\n# Index file\n\nEach Hereditas box contains an encrypted file named `_index`.\n\n## Encryption details\n\nThe index file is encrypted, just like all other files, with AES-256-GCM. The encryption key is a unique, random sequence of 32 bytes (256 bits), which is wrapped with the master key and then stored (wrapped) at the beginning of the file. Additionally, the 12-byte IV is randomly generated too and stored in the file's data right after the wrapped key.\n\nAs a result of the usage of GCM, which is an authenticated cipher, the encryption step outputs an authentication tag too. The index file's authentication tag is stored inside the JavaScript file in cleartext and it's used to certify that the index file's content are authentic.\n\n## Contents\n\nIn celartext, the index file is a JSON document listing all files inside the Hereditas box. For example:\n\n````json\n[\n    {\n       \"path\": \"hello.md\",\n       \"dist\": \"043bd2a8986b5ed805737ab8\",\n       \"size\": 248,\n       \"display\": \"html\",\n       \"tag\": \"VczD/yHW3XtcH2nNyt9Q4w==\",\n       \"processed\": \"markdown\"\n    },\n    {\n       \"path\": \"photo.jpg\",\n       \"dist\": \"122c1a87b03db8793eb90d53\",\n       \"size\": 10181034,\n       \"display\": \"image\",\n       \"tag\": \"Zsh42WN+iy05M6CaXtlhPA==\"\n    },\n    {\n       \"path\": \"folder/passwords.pdf\",\n       \"dist\": \"715f14d0479b455ed481af9f\",\n       \"size\": 60600,\n       \"display\": \"attach\",\n       \"tag\": \"CjurYwY6KeeTrmJsKxdR1A==\"\n    }\n]\n````\n\nThe JSON document is an array of objects each representing a file:\n\n- `path`: The original path of the file in the content folder\n- `dist`: Name of the encrypted file\n- `size`: The size of the original file, in bytes\n- `display`: Instructs the Hereditas web app on how to display the file. The generator determines this based on the file extension. Accepted values are:\n    - `html`: Display the content as HTML fragment inside the page (for converted Markdown files)\n    - `text`: Display the content as pre-formatted text, in a `<pre></pre>` HTML block (for text files)\n    - `image`: Display the image inline (for images)\n    - `attach`: Prompts to download the file\n- `tag`: The authentication tag for the encrypted file, as returned by the GCM cipher\n- `processed`: Contains information on how the file was pre-processed. If not present, it means the file wasn't pre-processed. Possible values:\n    - `markdown`: The Markdown file was converted to HTML\n"
  },
  {
    "path": "docs-source/content/cli/__template.md",
    "content": "---\ntitle: {{{commandName}}}\ntype: docs\n---\n\n# hereditas {{{commandName}}}\n\n{{{shortDescription}}}\n\n## Description\n\n{{{longDescription}}}\n\n{{#usage}}\n## Example usage\n\n````sh\n{{{usage}}}\n````\n{{/usage}}\n\n{{#hasFlags}}\n## Flags\n| Flag | Type | Required | Default Value | Description |\n|---|---|---|---|---|\n{{#flags}}\n|{{{name}}} | {{{type}}} | {{required}} | {{{defaultValue}}} | {{description}} |\n{{/flags}}\n{{/hasFlags}}\n"
  },
  {
    "path": "docs-source/content/guides/auth0-setup.md",
    "content": "---\ntitle: Auth0 setup\ntype: docs\n---\n\n# Auth0 setup\n\n[Auth0](https://auth0.com/) is an authentication provider built to be flexible, safe and reliable. It offers a generous free tier that is more than enough for any user of Hereditas.\n\nOn Auth0, users can authenticate using their existing social logins, including Google, Facebook, Microsoft accounts; the list of supported providers is [fairly long](https://auth0.com/docs/identityproviders). This is very convenient because it lets your users sign in with existing credentials, so you don't need to create new accounts (and passwords) for them. It also offers increased security, as providers like Microsoft, Google, Facebook (and Auth0 itself) have a powerful infrastructure to prevent and detect malicious logins (often using AI trained on millions of authentications by their users every day), and they support Multi-Factor Authentication.\n\n> **Why do we need Auth0?**\n>\n> Hereditas is a static website generator. In order to reduce the need for future maintenance and keeping operating costs down to zero (or almost), Hereditas outputs a static HTML5 web app with no server-side code at all. HTML5 apps nowadays are extremely powerful and we are able to do advanced cryptographic operations within a web browser.\n>\n> However, in order to implement the wait timer (ensuring that users need to wait a certain amount of time after their first login to unlock your box), we needed to store data in a centralized repository. Using Auth0 and splitting the encryption key between the *user passphrase* given to your users, and the *application token* stored inside the authentication provider, lets us precisely do that, while still maintaining the promise of a fully trustless platform. For more information, check out the [Security model]({{< relref \"/introduction/security-model.md\" >}}) article.\n\n## Sign up for Auth0\n\nOn the [Auth0 website](https://auth0.com/), sign up and create a new (free) account.\n\nAfter creating an account, you should automatically be redirected to the [Auth0 management portal](https://manage.auth0.com/), where you can create a new domain. Choose a name (must be universally unique) and a region, then continue the process until you've created your account and domain.\n\n![Auth0 management portal: new domain creation](/images/auth0-setup-create-domain.png)\n\n## Create the \"API Access\" application\n\nOnce you are inside the Auth0 management portal, click on the button to create a new application.\n\nThroughout this documentation, we'll name this new application \"API Access\", even though you can choose whichever name you prefer. Choose type \"Machine to Machine App\", then create the app.\n\n![Auth0 management portal: create a new Machine to Machine application](/images/auth0-setup-create-application.png)\n\nIn the next step, you need to grant this application access to the Auth0 APIs. From the dropdown menu, select \"Auth0 Management API\". Then, select **all and only** the following scopes:\n\n- read:clients\n- update:clients\n- create:clients\n- read:rules\n- update:rules\n- delete:rules\n- create:rules\n- update:rules_configs\n\n![Auth0 management portal: enable Auth0 Management APIs](/images/auth0-setup-create-application-api.png)\n\nLastly, from the Settings tab, take note of the following values, which we'll need to pass to the Hereditas CLI:\n\n- Domain\n- Client ID\n- Client Secret\n\n![Auth0 management portal: obtain credentials for API Access app](/images/auth0-setup-credentials.png)\n\n## Configure connections\n\nOne of the main benefits of using Auth0 is that it integrates with third-party identity providers such as Google, Microsoft, Facebook. This lets you skip creating new accounts for your users, so they can sign in with their existing credentials. Not only there's one less password for them to remember, but it's also safer: the external providers can support Multi-Factor Authentication, and can use advanced tools (often AI-based) to better detect hacked accounts.\n\n### Disable Username and Password authentication\n\nBy default, Auth0 offers users the possibility to create a new account specific to your app. You might want to disable that and allow social logins only. (While we recommend doing this, it's entirely optional)\n\nIn the Auth0 Management management portal, on the menu on the left side navigate to **Connections**, then **Database**.\n\nIn the row for the \"Username-Password-Authentication\" database, click on \"Settings\".\n\n![Auth0 management portal: database](/images/auth0-setup-database.png)\n\nScroll to the bottom of the page and click on the big, red button to remove the connection.\n\n![Auth0 management portal: remove the database](/images/auth0-setup-database-remove.png)\n\n### Configure social logins\n\nIn the Auth0 management portal, this time navigate to the **Connections** and then **Social** page.\n\nHere, you can configure all the social login providers, including Google, Facebook, and Microsoft.\n\n![Auth0 management portal: social logins](/images/auth0-setup-social-logins.png)\n\nEach provider has a different procedure for setting the connection up, and you can follow the Auth0 documentation for instructions.\n\nYou can enable any provider you want, and your users will be able to use anyone of them. Because Hereditas whitelists users based on their email addresses, it doesn't matter what provider they use to authenticate, as long as the email address matches.\n\n**Important:** we need providers to return users' email addresses. When you configure a new social provider, make sure that it supports sharing of users' email addresses (not all of them do, e.g. Twitter), and that the **_email_ scope is enabled** when not included in the basic info.\n\n## Next step: Login notifications\n\nIn the next step, we'll configure a webhook to send notifications when users sign into your Hereditas box.\n\n<a class=\"hereditas-button\" href=\"{{< relref \"/guides/login-notifications.md\" >}}\">Login notifications</a>\n"
  },
  {
    "path": "docs-source/content/guides/build-static-web-app.md",
    "content": "---\ntitle: Build the static web app\ntype: docs\n---\n\n# Build the static web app\n\nIn the previous step we created an Hereditas box, and now we're finally ready to build the static web app.\n\n## Build the web app\n\nYou're finally ready to build the static web app, using the [`hereditas build`]({{< relref \"/cli/build.md\" >}}) command:\n\n````sh\nhereditas build\n````\n\nThis will ask you to **type the _user passphrase_**, which needs to be at least 8 characters long. You can choose any passphrase you'd like, but a good practice is to use a bunch of words in your native language. If you're interested in the subject, check out [XKCD 936](https://www.explainxkcd.com/wiki/index.php/936:_Password_Strength).\n\nOnce the command is done, you'll see your generated files in the `dist` folder:\n\n````text\n~/hereditas $ ls content\nhello.md\nphoto.webp\nsubfolder\ntext.txt\ntulips.jpg\n\n~/hereditas $ hereditas build\nUser passphrase: ***********\nFinished building project in dist (took 3.895 seconds)\n\n~/hereditas $ ls dist\n1.1.9a2b99a07d39e25b4b7f.js\n24ec53f0e99728db2f471caf\n35bc79f07f20c003532724bf\n430c96dc23ccca5eb4227508\n_index\nd0160be2ee0f1479367b325c\nd9f2eb6a0f34382c36d2a116\nhereditas.9a2b99a07d39e25b4b7f.css\nhereditas.9a2b99a07d39e25b4b7f.js\nindex.html\nrobots.txt\n````\n\n> The Hereditas CLI uses [webpack](https://webpack.js.org/) behind the scenes to generate your static app. By default, the JavaScript code is bundled and minified. If you're looking at modifying the Hereditas source and want to skip the minification (for much faster builds) and include a sourcemap, you can call `NODE_ENV=development hereditas build` instead.\n\n## Next step: Managing users\n\nWe're almost there. Before you can actually deploy your box (or test it locally), we need to configure the list of users who can unlock it.\n\n<a class=\"hereditas-button\" href=\"{{< relref \"/guides/managing-users.md\" >}}\">Managing users</a>\n"
  },
  {
    "path": "docs-source/content/guides/create-box.md",
    "content": "---\ntitle: Create the box\ntype: docs\n---\n\n# Create the box\n\nAfter gathering all the content you want to encrypt, setting up our \"API Access\" application on Auth0, and configuring a webhook endpoints to send notifications, we can now create a box on our laptop. This will be our \"working directory\".\n\n## Initialize a working directory\n\nCreate a **new, empty folder** on your laptop. Open a terminal inside that folder, then run:\n\n````sh\nhereditas init \\\n   --auth0Domain \"yourdomain.auth0.com\" \\\n   --auth0ClientId \"...\" \\\n   --auth0ClientSecret \"...\" \\\n   --url \"http://localhost:5000\"\n````\n\nYou'll need to pass some options to the command above:\n\n- `--auth0Domain` is your domain on Auth0, created in the previous step\n- Set `--auth0ClientId` and `--auth0ClientSecret` to the Client Id and Client Secret for the \"API Access\" app you just created in Auth0\n- `--url` is the URL where the app will be deployed to. We'll be testing locally before deploying the app, so for now you might just want to keep this to `http://localhost:5000`. We can always change this later, without having to re-build the Hereditas box.\n\nAfter running the command, you'll see that your folder now contains three objects:\n\n````text\n~/hereditas $ ls\ncontent\ndist\nhereditas.json\nwelcome.md\n````\n\n- The `content` folder is where you store the data you wish to encrypt\n- The `dist` folder will contain the generated web app\n- The `hereditas.json` file contains the configuration for the Hereditas box\n- The `welcome.md` file contains a welcome message that is displayed in the login page; this file is not encrypted.\n\n> In most cases you will not need to manually edit the `hereditas.json` configuration file, as you can use the Hereditas CLI to change the most common options. However, you can find the full reference for the configuration file in the [Configuration file]({{< relref \"/advanced/configuration-file.md\" >}}) article.\n\n## Content\n\nPlace all the content you want to encrypt in the `content` folder. You can store any kind of file in this folder and sub-folders. The [Get started]({{< relref \"/guides/get-started.md\" >}}#step-zero-gather-all-content) article has some suggestions on what kind of content to store.\n\nMarkdown documents are automatically converted to HTML chunks, so that's a great way to include information. However, at present Hereditas web apps do not support hyperlinks, images or videos in Markdown or HTML files linking to other content within the box.\n\n### Welcome file\n\nAs mentioned above, Hereditas generates a `welcome.md` file and pre-populates it with some default content.\n\nThe welcome file is displayed in the authentication page, and you can use it to provide some information about what your Hereditas box is, and how it can be used.\n\nNote that the welcome file is **not encrypted**, so do not store any confidential information in there!\n\n## Set the webhook URL\n\nWe need to set the URL of the webhook we created in the previous step. We can use [`hereditas webhook:set`]({{< relref \"/cli/webhook_set.md\" >}}) for that, replacing the URL below with yours:\n\n````sh\nhereditas webhook:set --url \"https://maker.ifttt.com/trigger/hereditas_auth/with/key/123abc456def\"\n````\n\n## Synchronize changes on Auth0\n\nAt this point, let's create the Hereditas application and rules on Auth0, which will also give us the required Client Id.\n\nThe Hereditas CLI has a built-in command [`hereditas auth0:sync`]({{< relref \"/cli/auth0_sync.md\" >}}) that manages the application, configuration and rules inside Auth0, in a fully-automated way. So, syncing the changes is as simple as running:\n\n````sh\nhereditas auth0:sync\n````\n\nThe command above will create the application and the rules on Auth0, and make sure that everything is configured correctly. As we'll see in the next steps, you will need to re-run that command after making certain configuration changes.\n\n## Next step: Build the static web app\n\nWe're finally ready to use the Hereditas CLI to build our static app! Follow the instructions in the next article for how:\n\n<a class=\"hereditas-button\" href=\"{{< relref \"/guides/build-static-web-app.md\" >}}\">Build the static web app</a>\n\n"
  },
  {
    "path": "docs-source/content/guides/deploy-box.md",
    "content": "---\ntitle: Deploy the box\ntype: docs\n---\n\n# Deploy the box\n\nIn this last step, we're finally ready to deploy the static web app!\n\nIn the [Build the static web app]({{< relref \"/guides/build-static-web-app.md\" >}}) article you used the Hereditas CLI to generate the box, which is a static, HTML5 web application. The generated files are in the `dist` folder. It's now time to take those files and make them accessible to others.\n\n## Sync changes with Auth0\n\nIn case you haven't done it already in the previous step, run the [`hereditas auth0:sync`]({{< relref \"/cli/auth0_sync.md\" >}}) command to ensure that the Hereditas application and rules are propertly configured on Auth0 (it's safe to run this command as often as you'd like).\n\n````sh\nhereditas auth0:sync\n````\n\n## Testing locally\n\nBefore deploying your app, you can test it running on your laptop with a local server. There are multiple options to run a local server; a simple one is:\n\n````sh\nnpx serve dist -n\n````\n\nThis will serve all files in the `dist` directory at the URL `http://localhost:5000`, which you can open with any web browser.\n\nKeep in mind that the URL and port must be whitelisted in the Hereditas app and Auth0. In the previous step, we did whitelist `http://localhost:5000` when running [`hereditas init`]({{< relref \"/cli/init.md\" >}}), so we're good for now. If you use a local server listening on another port, however, you'll have to allow that URL too – see the [managing deployment URLs](#managing-deployment-urls) section below.\n\n## Choosing where to host your box\n\nYour box is just a static HTML5 web app, with HTML, JavaScript and CSS files, plus a bunch of encrypted documents. You can deploy it on any service capable of serving HTML5 apps via HTTP(S).\n\nBecause all of your data is encrypted, Hereditas boxes are designed to be deployed on publicly-accessible endpoints too, safely.\n\nGood solutions include [Azure Blob Storage](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website), or [AWS S3](https://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html). Any provider that supports static website hosting should work; this service is often free, or very inexpensive.\n\nWhile possible, we do not recommend deploying Hereditas on a VPS (*what would happen if your credit card got canceled and your services stopped?*), nor inside a server in your home (*would your relatives know how to access it from within your LAN? what if your landlord disconnected your servers, would people know how to rebuild your network?*). Ultimately, however, it's up to you, your specific situation, and to the trust you put in the technical skills of your loved ones.\n\n## Managing deployment URLs\n\nAfter you've decided where to deploy your app to, you need to whitelist the URL where it will be reachable at. This is necessary because after a successful authentication, Auth0 will redirect users only to URLs you specifically whitelist, for security reasons.\n\nYou can manage the list of allowed URLs using the Hereditas CLI, with the commands:\n\n- [`hereditas url:add`]({{< relref \"/cli/url_add.md\" >}})\n- [`hereditas url:list`]({{< relref \"/cli/url_list.md\" >}})\n- [`hereditas url:rm`]({{< relref \"/cli/url_rm.md\" >}})\n\nFor example, let's whitelist `https://myhereditas.example.com` and remove the localhost one we added earlier:\n\n````sh\nhereditas url:add --url \"https://myhereditas.example.com\"\nhereditas url:rm --url \"http://localhost:5000\"\n\n# Verify the list\nhereditas url:list\n````\n\nAfter making changes to the list of URLs, sync them with Auth0 so they become effective (you don't need to re-build the Hereditas box, however):\n\n````sh\nhereditas auth0:sync\n````\n\n## Examples\n\nHere's an example on how to deploy your box to Azure Storage.\n\n### Deploy to Azure Storage\n\nAzure Storage is an object storage provider that offers static website hosting too. You pay for how much data you store, at a rate that starts at less than $0.02 per GB per month.\n\nIn order to deploy to Azure Storage, you'll need:\n\n- An Azure account. You can get it [for free](https://azure.com/free) if you don't have one already.\n- The Azure CLI installed on your laptop. Installation instructions are in the [official documentation](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest).\n\nStart by logging into Azure and creating an Azure Storage Account:\n\n````sh\n# Log into azure\n# After running this, follow the instructions to log in via a web browser\naz login\n\n# Create a Resource Group and a Storage Account\n# The storage account name must be universally unique\n# For a list of locations available, type:\n# az account list-locations --query \"[].{Region:name}\" --out table\nAZURE_STORAGE_ACCOUNT=\"myhereditas\"\nAZURE_RESOURCE_GROUP=\"Hereditas\"\nAZURE_LOCATION=\"eastus2\"\naz group create \\\n    --name \"$AZURE_RESOURCE_GROUP\" \\\n    --location \"$AZURE_LOCATION\"\naz storage account create \\\n    --name \"$AZURE_STORAGE_ACCOUNT\" \\\n    --resource-group \"$AZURE_RESOURCE_GROUP\" \\\n    --location \"$AZURE_LOCATION\" \\\n    --sku Standard_LRS \\\n    --kind StorageV2\n````\n\nAt this point, enable static website hosting for your Storage Account, and retrieve the URL with:\n\n````sh\naz storage blob service-properties update \\\n    --account-name \"$AZURE_STORAGE_ACCOUNT\" \\\n    --static-website \\\n    --404-document \"404.html\" \\\n    --index-document \"index.html\"\naz storage account show \\\n    --name \"$AZURE_STORAGE_ACCOUNT\" \\\n    --resource-group \"$AZURE_RESOURCE_GROUP\" \\\n    --query \"primaryEndpoints.web\" \\\n    --output tsv\n# Result will be something similar to:\n# https://myhereditas.z20.web.core.windows.net/\n````\n\nWe can now upload all files from the `dist` folder into the Storage Account, in the `$web` container:\n\n````sh\naz storage blob upload-batch \\\n    --source dist \\\n    --destination \"\\$web\" \\\n    --account-name \"$AZURE_STORAGE_ACCOUNT\"\n````\n\nIn the last step, we need to whitelist the website's URL with Auth0:\n\n````sh\n# Replace the URL with yours\nhereditas url:add --url \"https://myhereditas.z20.web.core.windows.net\"\nhereditas auth0:sync\n````\n\nDone! You can now go to `https://myhereditas.z20.web.core.windows.net` and use your Hereditas box.\n\n![Hereditas deployed to Azure Storage](/images/deploy-box-azure-done.png)\n\n## Share the information with your relatives\n\nAt this point, you have all the information you need to give to your relatives, for usage in case you disappear. Send them a letter, or an email, or anything else that works for you.\n\nMake sure to include:\n\n1. An explanation of what this Hereditas box is, and what information they can find in there.\n2. The URL they need to type\n3. The *user passphrase*\n4. The name of the account they need to use to sign in (the email address)\n\nThis is all and only the information they need to use Hereditas.\n"
  },
  {
    "path": "docs-source/content/guides/get-started.md",
    "content": "---\ntitle: Get started\ntype: docs\n---\n\n# Get started\n\n## Prerequisites\n\nIn order to use Hereditas, you will need [Node.js](https://nodejs.org/en/download/) 10 or higher installed on your laptop.\n\nYou will also need three services; we'll guide you through the creation of those in the next steps.\n\n1. A (free) [Auth0](https://auth0.com/) account. This is used by Hereditas to ensure that only authorized users can access your data, and only after a certain amount of time after the first request. (But don't worry: Auth0 and their developers have no way to access your data)\n2. A webhook that you can use to send you notifications when users log into your Hereditas box, so you know when the unlock timer starts and it gives you a chance to stop it. There are multiple options for that, including [IFTTT](https://ifttt.com/) (free), or more advanced solutions like Microsoft Flow, Azure Functions, AWS Lambda, etc.\n3. A place where to host static HTML5 apps (HTML, JavaScript, CSS files, plus your encrypted content) serving it over HTTP(S).\n\n## Install the Hereditas CLI\n\nYou can install the Hereditas CLI on your machine from NPM, by running:\n\n````sh\nnpm install --global hereditas\n\n# Verify it's installed with\nhereditas --version\n````\n\n> Note: If you prefer not to install the CLI as a global package, you can always invoke it using NPX, for example with `npx hereditas --version`. However, each command invocation will take significantly longer as NPX needs to restore all dependencies.\n\n## Step zero: gather all content\n\nThis is the least technical of all the steps, but by far the most important one.\n\nAs the owner of an Hereditas box, you start by assembling all the content you want to include in your digital legacy box. For example, text/Markdown documents, images, and other files.\n\nThings you might want to include:\n\n* The passwords to access your laptop and your phone/tablet/watch/etc.\n* The recovery key for your password manager, for example iCloud Keychain, 1Password, LastPass, KeePass, etc.\n* How to access your private photos on an encrypted drive or cloud storage.\n* Useful encryption keys, inclduing keys for your cryptocurrency wallets.\n* Or, just a nice letter.\n\nThis step is very personal, and Hereditas gives you total flexibility to decide what to include in your box.\n\nWhile it would technically be possible, we recommend that you don't store large amount of data, or data that changes frequently, inside an Hereditas box. In fact, every time you change any information, you'd have to encrypt and publish again the entire box, which can be very time-consuming, and could lead to your box containing outdated information.\n\nFor example, rather than including gigabytes of photos, we recommend that you store them in a safe place (encrypted drive, cloud storage, etc) and use your Hereditas box to explain how to retrieve them. Similarly, instead of including every single password (which can change frequently), just put the recovery key of your password manager.\n\n## Next step: Auth0 setup\n\nAfter you've installed the Hereditas CLI and gathered all the content, you're ready to go to the next step and configure a new Auth0 account.\n\n<a class=\"hereditas-button\" href=\"{{< relref \"/guides/auth0-setup.md\" >}}\">Auth0 setup</a>\n"
  },
  {
    "path": "docs-source/content/guides/login-notifications.md",
    "content": "---\ntitle: Login notifications\ntype: docs\n---\n\n# Login notifications\n\nAs the owner of an Hereditas box, you'll want to be notified when someone signs into your box, to potentially block unauthorized attempts. For example, you can choose to receive a text message, or an email, etc.\n\nHereditas uses [webhooks](https://codeburst.io/what-are-webhooks-b04ec2bf9ca2) for this, which are just POST requests to an external HTTPS endpoint (make sure you use HTTPS, and not HTTP!).\n\n## Notification webhook\n\nHereditas sends a webhook to the URL you provide, as a POST request with the following JSON body:\n\n````json\n{\n    \"value1\": \"Full notification, e.g. 'New Hereditas login on Fri, 08 Mar 2019 12:01:10 GMT. User: user@example.com (role: user)'\",\n    \"value2\": \"email address of user, e.g. user@example.com\",\n    \"value3\": \"role, either owner or user\"\n}\n````\n\nYou can point the webhook to whatever service you'd like to use. The next sections will show some common examples.\n\n### Using IFTTT\n\n[IFTTT](https://ifttt.com/), or \"IF This Then That\", is a free service that lets you \"connect\" multiple APIs and actions.\n\nAfter enabling the [webhook service](https://ifttt.com/maker_webhooks), you'll get a private key. The URL you need to use is:\n\n````text\nhttps://maker.ifttt.com/trigger/{event}/with/key/{key}\n````\n\nReplace `{event}` with an event name (e.g. `hereditas_auth`) and `{key}` with your IFTTT Webhook key (so messages are sent to yourself). For example:\n\n````text\nhttps://maker.ifttt.com/trigger/hereditas_auth/with/key/123abc456def\n````\n\nNote down your webhook URL, as we'll need it soon.\n\nYou can then configure your IFTTT applet to perform any action as a consequence of this. For example, you could send yourself an email, a message on Telegram, or a notification on Slack (or turn the lights red in your home, etc!).\n\nIf you send yourself a message, you can use `{{value1}}` as a pre-made mesasge, or you can write whatever body you prefer. As example:\n\n````text\n{{Value2}} (role: {{Value3}}) just logged into your Hereditas box at {{OccurredAt}}!\n````\n\n## Next step: Create the box\n\nWe now have all the information we need to create the Hereditas box in our laptop and start putting content in there.\n\n<a class=\"hereditas-button\" href=\"{{< relref \"/guides/create-box.md\" >}}\">Create the box</a>\n"
  },
  {
    "path": "docs-source/content/guides/managing-users.md",
    "content": "---\ntitle: Managing users\ntype: docs\n---\n\n# Managing users\n\nYou can use the Hereditas CLI to add or remove authorized users and owners.\n\n## Roles\n\nHereditas users can have one of two roles:\n\n- **user**: This is the normal user role. When someone with a *user* role signs into your Hereditas box, they are not immediately able to unlock it. Instead, a successful authentication of someone with a *user* role will start the timer, and after a certain delay (e.g. 24 hours, or as you configured it), users can sign in again and this time they'll be able to unlock the box (as long as they know the *user passphrase*).\n- **owner**: When an *owner* successfully signs in, two things happen. First, they are always returned the *application token* by Auth0, so they can unlock the Hereditas box at any time (as long as they know the *user passphrase*). Second, an *owner* logging in always stops any running timer. This prevents those with a *user* role from accessing your data when you don't want them to.\n\n## Add or remove authorized users\n\nYou can easily manage users with the Hereditas CLI.\n\nTo **add users**, use the [`hereditas user:add`]({{< relref \"/cli/user_add.md\" >}}) command.\n\n````sh\nhereditas user:add --email \"someone@example.com\"\n````\n\nBy default, all users are given the role *user*. To add an *owner*, use the `--role owner` option:\n\n````sh\nhereditas user:add --email \"owner@example.com\" --role owner\n````\n\nYou can **list users** who have access to your Hereditas box, and their roles, with the [`hereditas user:list`]({{< relref \"/cli/user_list.md\" >}}) command.\n\n````sh\nhereditas user:list\n````\n\nLastly, you can **remove users** with the [`hereditas user:rm`]({{< relref \"/cli/user_rm.md\" >}}) command.\n\n````sh\nhereditas user:rm --email \"someone@example.com\"\n````\n\n## Synchronize changes on Auth0\n\nThe commands above save the changes in the local `hereditas.json` configuration file only.\n\nIn order for changes like adding/removing users (and others including changing the wait time, the webhook URL, or re-generating the application token) to be effective, you need to synchronize them with Auth0.\n\nWe can use again the [`hereditas auth0:sync`]({{< relref \"/cli/auth0_sync.md\" >}}) command, which will synchronize all changes in Auth0, updating our rules and application configuration, in a fully-automated way.\n\n````sh\nhereditas auth0:sync\n````\n\n## Next step: Deploy the box\n\nWe're almost there! Ready to test the box locally and then deploy it, so your users can access it when needed.\n\n<a class=\"hereditas-button\" href=\"{{< relref \"/guides/deploy-box.md\" >}}\">Deploy the box</a>\n"
  },
  {
    "path": "docs-source/content/introduction/quickstart-video.md",
    "content": "---\ntitle: Quickstart video\ntype: docs\n---\n\n# Quickstart video\n\nThis quickstart video shows you how to get an Hereditas box configured, built and deployed to the cloud, in just a few minutes.\n\n{{< youtube iGgza7AK7ow >}}\n\n> Note: the quickstart video was recorded with Hereditas 0.1. All instructions remain the same for Hereditas 0.2, but the user interface looks better now.\n\n## Get started documentation\n\nFor more information, check out our Get Started documentation.\n\n<a class=\"hereditas-button\" href=\"{{< relref \"/guides/get-started.md\" >}}\">Get started with Hereditas</a>\n"
  },
  {
    "path": "docs-source/content/introduction/security-model.md",
    "content": "---\ntitle: Security model\ntype: docs\n---\n\n# Security model\n\nThis document explains in technical details how Hereditas ensures that your data is protected.\n\n## Data encryption\n\nHereditas, as a static site generator, encrypts all of your sensitive data with AES-256 in [Galois/Counter Mode (CGM)](https://en.wikipedia.org/wiki/Galois/Counter_Mode), a symmetric cryptographic algorithm that is industry-standard for encrypting data. GCM is an authenticated encryption algorithm, designed to provide both data authenticity and confidentiality.\n\nEach file is encrypted with a unique, random 256-bit key (32 bytes), which is wrapped with a master key (read more below). The wrapped key (40 bytes-long after wrapping) is stored at the beginning of the file.\n\nAdditionally, each file is encrypted with a unique, random IV of 12 bytes, which is stored in clear text at the beginning of each file, right after the wrapped key.\n\nEncrypted files are given random names so attackers cannot gather information about the name and type of each file.\n\n## Index file\n\nEach Hereditas box also contains an `_index` file, which is encrypted just like each data file. The file's key is 256-bit, unique and randomly-generated, and it's wrapped with the master key. The wrapped key and the random, unique 12-byte IV are stored at the beginning of the file.\n\nWhen in cleartext, the index file is a JSON document that contains the original file name, the id of the object stored in the Hereditas box, the authentication tag (as returned by AES-GCM), and a few more details. For more information on the index file, see the [Index file]({{< relref \"/advanced/index-file.md\" >}}) article in the advanced section.\n\n## Master key\n\nEach file inside the box is encrypted with a unique key that is wrapped with a master key. You can read the next section for details on the key wrapping algorithm used.\n\nThe master key is itself derived from the *user passphrase* and the *application token*.\n\nThe **user passphrase** is set while running [`hereditas build`]({{< relref \"/cli/build.md\" >}}). Users need to type it in the Hereditas app before they can unlock the box. The owner can choose any passphrase they want, as long as it's longer than 8 characters. This is not stored anywhere, but you should communicate it (in a safe way) to your loved ones.\n\nThe **application token** is unique to each Hereditas box and stored in the `hereditas.json` configuration file. By default, Hereditas generates it when the [`hereditas init`]({{< relref \"/cli/init.md\" >}}) command is executed, and the token can be re-generated with [`hereditas regenerate-token`]({{< relref \"/cli/regenerate-token.md\" >}}). The CLI creates the application token by getting 21 random bytes with Node.js [`crypto.randomBytes()`](https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback), then encoding them as base64. The application token is stored inside Auth0 as a [\"rule configuration\"](https://auth0.com/docs/rules/guides/configuration), and it's passed to the web app in the JWT token for users that are logged in when they're ready to unlock the box (see below).\n\nThe **master key** is derived from the concatenated string (user passphrase + application token) using a key derivation function. Hereditas supports two strong, industry-standard key derivation functions:\n\n- [Argon2](https://en.wikipedia.org/wiki/Argon2) is the default since version 0.2, and it uses the Argon2id variant. Argon2id can use a configurable amount of memory, which can be set in the `hereditas.json` config file; the default is 64 MB.\n- [PBKDF2](https://en.wikipedia.org/wiki/PBKDF2), which is based on SHA-512 and supports a configurable number of iterations. The default is 100,000 iterations, and it can be configured with the `hereditas.json` config file.\n\nBoth key derivation functions generate a 256-bit key.\n\nArgon2 is the default because it is known to provide better resistance against GPU-based brute force attacks. However, while support for PBKDF2 is available natively in browser thanks to the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API), Argon2 uses an [external module](https://github.com/antelle/argon2-browser) based on WebAssembly.\n\nHereditas uses a salt with the key derivation function (both Argon2 and PBKDF2) that is re-generated on each new build of the box (ie. each time you run [`hereditas build`]({{< relref \"/cli/build.md\" >}})), and it's stored inside the app's JavaScript file in cleartext.\n\n## Key wrapping\n\nAfter deriving the master key, Hereditas can use that to wrap and un-wrap the key used to encrypt each file.\n\nAs mentioned above, each file's key is a unique, randomly-generated sequence of 32 bytes (256 bits), suitable for AES-256-GCM. This key is wrapped and then stored at the beginning of each file.\n\nFile keys are wrapped using the AES-KW algorithm, as defined in RFC 3394. The master key is used as wrapping key.\n\n## Unlocking the box\n\nIn order to unlock the box, users need to complete two steps:\n\n1. Authenticate with Auth0. This lets authorized users get the *application token* if appropriate.\n2. Type the *user passphrase*.\n\nWhen users open the Hereditas web app, they are redirected to Auth0 to authenticate themselves.\n\n- Only users whose email address is explicitly whitelisted (using [`hereditas user:add`]({{< relref \"/cli/user_add.md\" >}})) are allowed to log in. Users can authenticate with any social profile they want (anything supported by Auth0, eg. Google, Facebook, Microsoft accounts, etc), as long as the email address returned by the provider is included in the whitelist.\n- Users can have two roles: *owner* and *user*.\n- When an **owner** authenticates, Auth0 includes the *application token* in the JWT token every time. Owners who also know the *user passphrase* can unlock their Hereditas boxes any time they want.\n- When a normal **user** (ie. non-owner) authenticates the first time, Auth0 sets the time of the login in the Client Application setting, but does not return the *application token*.\n- After a configurable amount of time, e.g. 24 hours, users (non-owners) can authenticate again, and this time Auth0 will include the *application token* in the JWT token. The wait time can be configured with [`hereditas wait-time:set`]({{< relref \"/cli/wait-time_set.md\" >}}). Users can then unlock the Hereditas box if they also know the *user passphrase*.\n- If an *owner* authenticates at any time, Auth0 resets any active timer, preventing other users to unlock the Hereditas box when the owner is still around.\n\n## Trustless\n\nThe model above is what allows Hereditas to be fully trustless:\n\n1. Users who are in possession of the *user passphrase* cannot unlock Hereditas boxes without the *application token*, even if they have full access to the encrypted files.\n2. Auth0 stores only the *application token* and has no knowledge of the *user passphrase*. So, a malicious actor who managed to extract the *application token* from Auth0 would not be able to unlock the Hereditas box.\n3. Users need to wait a certain amount of time before they're allowed to unlock Hereditas boxes, and owners can stop the timer by logging in themselves. This guarantees that ill-intentioned users won't be able to unlock Hereditas boxes until you're around.\n"
  },
  {
    "path": "docs-source/content/menu/__template.md",
    "content": "---\nheadless: true\n---\n{{! Set Mustache delimeters to ASP-style tags (this is a Mustache comment) }}\n{{=<% %>=}}\n\n* **Introduction**\n  * [What is Hereditas]({{< relref \"/\" >}})\n  * [Quickstart video]({{< relref \"/introduction/quickstart-video.md\" >}})\n  * [Security model]({{< relref \"/introduction/security-model.md\" >}})\n* **Guides**\n  * [Get started]({{< relref \"/guides/get-started.md\" >}})\n  * [Auth0 setup]({{< relref \"/guides/auth0-setup.md\" >}})\n  * [Login notifications]({{< relref \"/guides/login-notifications.md\" >}})\n  * [Create the box]({{< relref \"/guides/create-box.md\" >}})\n  * [Build the static web app]({{< relref \"/guides/build-static-web-app.md\" >}})\n  * [Managing users]({{< relref \"/guides/managing-users.md\" >}})\n  * [Deploy the box]({{< relref \"/guides/deploy-box.md\" >}})\n* **CLI Reference**\n<%# index %>\n  * [<% name %>]({{< relref \"/cli/<% path %>\" >}})\n<%/ index %>\n* **Advanced topics**\n  * [Building self-contained binaries]({{< relref \"/advanced/building-self-contained-binaries.md\" >}})\n  * [Auth0 manual configuration]({{< relref \"/advanced/auth0-manual-configuration.md\" >}})\n  * [Configuration file]({{< relref \"/advanced/configuration-file.md\" >}})\n  * [Index file]({{< relref \"/advanced/index-file.md\" >}})\n* **Other**\n  * [GitHub project page](https://github.com/ItalyPaleAle/hereditas)\n"
  },
  {
    "path": "docs-source/generate-cli-docs.js",
    "content": "'use strict'\n\nconst Mustache = require('mustache')\nconst fs = require('fs')\nconst util = require('util')\n\n// Promisified functions\nconst readFilePromise = util.promisify(fs.readFile)\nconst readdirPromise = util.promisify(fs.readdir)\nconst statPromise = util.promisify(fs.stat)\nconst writeFilePromise = util.promisify(fs.writeFile)\n\n// Scan a directory recursively and get the file names\nconst scanFolder = async (base, folder, result) => {\n    result = result || []\n    folder = folder || ''\n\n    // Scan the list of files and folders, recursively\n    const list = await readdirPromise(base + folder)\n    for (const e in list) {\n        const el = base + folder + list[e]\n\n        // Check if it's a directory\n        const stat = await statPromise(el)\n        if (!stat) {\n            continue\n        }\n\n        // If it's a directory, scan it recursively\n        if (stat.isDirectory()) {\n            await scanFolder(base, folder + list[e] + '/', result)\n        }\n        // Get only JavaScript files\n        else if (el.substr(-3) === '.js') {\n            // Add the file to the list\n            result.push(folder + list[e])\n        }\n    }\n\n    return result\n}\n\nconst commandsPath = __dirname + '/../cli/commands/'\nconst docTemplateFile = __dirname + '/content/cli/__template.md'\nconst menuTemplateFile = __dirname + '/content/menu/__template.md'\nconst docDestinationPath = __dirname + '/content/cli/'\nconst menuDestinationPath = __dirname + '/content/menu/index.md'\n\n// Main entrypoint\n;(async function generateCliDocs() {\n    // Load the templates\n    const docTemplate = await readFilePromise(docTemplateFile, 'utf8')\n    Mustache.parse(docTemplate)\n\n    // Load the list of CLI commands\n    const commands = await scanFolder(commandsPath)\n\n    // Generate the documentation file for all commands\n    const promises = commands.map(async (file) => {\n        // Command name is derived from the file name\n        const commandName = file.replace('/', ':').slice(0, -3)\n\n        // Import the class\n        const command = require(commandsPath + file)\n\n        // Description: first line is the short one, and second line is the long one\n        const description = command.description.trim()\n        const [shortDescription, ...parts] = description.split('\\n')\n        const longDescription = parts.join('\\n').trim()\n\n        // Usage\n        const usage = (command.usage) ?\n            'hereditas ' + command.usage.trim() :\n            'hereditas ' + commandName\n\n        // Flags\n        const flags = []\n        if (command.flags) {\n            // Iterate through the flags\n            for (const key in command.flags) {\n                if (!command.flags.hasOwnProperty(key) || !command.flags[key]) {\n                    continue\n                }\n                const flag = command.flags[key]\n\n                if (flag.type !== 'option') {\n                    // eslint-disable-next-line no-console\n                    console.warn('Skipping flag with type != \"option\"')\n                    continue\n                }\n\n                // Required\n                const required = flag.required ? '✓' : ''\n\n                // Flag name and character\n                let name = ['--' + key]\n                if (flag.char) {\n                    name.unshift('-' + flag.char)\n                }\n                name = '`' + name.join('`<br/>`') + '`'\n\n                // Type (including options)\n                const type = (flag.options) ?\n                    '`\"' + flag.options.join('\"`, `\"') + '\"`' :\n                    'string'\n\n                const defaultValue = (flag.default) ?\n                    '`\"' + flag.default + '\"`' :\n                    'none'\n\n                // Add the flag\n                flags.push({\n                    name,\n                    description: flag.description,\n                    required,\n                    defaultValue,\n                    type\n                })\n            }\n        }\n\n        // Build the documentation file\n        const params = {\n            commandName,\n            shortDescription,\n            longDescription,\n            usage,\n            hasFlags: !!flags.length,\n            flags\n        }\n        const rendered = Mustache.render(docTemplate, params)\n        const outfileName = commandName.replace(':', '_') + '.md'\n        await writeFilePromise(docDestinationPath + outfileName, rendered)\n\n        // Return the name of the command and the file, which will be used for the index\n        return {\n            name: commandName,\n            path: outfileName\n        }\n    })\n    const index = await Promise.all(promises)\n\n    // Use the index to build the menu\n    const menuTemplate = await readFilePromise(menuTemplateFile, 'utf8')\n    const menuRendered = Mustache.render(menuTemplate, {\n        index\n    })\n    await writeFilePromise(menuDestinationPath, menuRendered)\n})()\n"
  },
  {
    "path": "docs-source/sync-assets.sh",
    "content": "#!/bin/sh\n\nset -eu\n\n# \"azcopy\" command, defaults to searching for it in the PATH\n: \"${AZCOPYCMD:=$(which azcopy)}\"\necho \"Using azcopy in $AZCOPYCMD\"\n\n# Ensure azcopy is installed\n\"$AZCOPYCMD\" --version\n\n# Check required env vars: $ASSETS, $CONTAINER, $AZURE_STORAGE_ACCOUNT\nif [ -z \"$ASSETS\" ]; then\n    echo \"\\$ASSETS is empty\"\n    exit 1\nfi\n\nif [ -z \"$CONTAINER\" ]; then\n    echo \"\\$CONTAINER is empty\"\n    exit 1\nfi\n\nif [ -z \"$AZURE_STORAGE_ACCOUNT\" ]; then\n    echo \"\\$AZURE_STORAGE_ACCOUNT is empty\"\n    exit 1\nfi\n\necho \"Syncing with: https://${AZURE_STORAGE_ACCOUNT}.blob.core.windows.net/${CONTAINER}\"\n\n# Check that all folders specified in $ASSETS exist\nfor asset in $ASSETS; do\n    # Ensure the asset exists and it's a folder\n    if [ ! -d \"$asset\" ]; then\n        echo \"$asset doesn't exist or it's not a folder\"\n        exit 2\n    fi\ndone\n\n# Sync all folders\nfor asset in $ASSETS; do\n    # Sync the folder\n    \"$AZCOPYCMD\" sync \"$asset\" https://${AZURE_STORAGE_ACCOUNT}.blob.core.windows.net/${CONTAINER}/${asset} --recursive --delete-destination=true\ndone\n"
  },
  {
    "path": "docs-source/themes/book/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2018 Alex Shpak\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "docs-source/themes/book/README.md",
    "content": "# Hugo Book Theme\n[![Build Status](https://travis-ci.org/alex-shpak/hugo-book.svg?branch=master)](https://travis-ci.org/alex-shpak/hugo-book)\n### [Hugo](https://gohugo.io) documentation theme as simple as plain book\n\n![Screenshot](https://github.com/alex-shpak/hugo-book/blob/master/images/screenshot.png)\n\n\n## Features\n* Clean simple design\n* Mobile friendly\n* Customizable\n* Designed to not interfere with other layouts\n* Zero initial configuration\n\n## Requirements\n* Hugo 0.43 or higher\n* Hugo extended version, read more [here](https://gohugo.io/news/0.43-relnotes/)\n\n## Installation\nNavigate to your hugo website root and run:\n```\ngit submodule add https://github.com/alex-shpak/hugo-book themes/book\n```\n\nThen run hugo (or set `theme: book` in configuration file)\n```\nhugo server --theme book\n```\n\n## Menu\n### File tree menu (default)\nBy default theme will render pages from `content/docs` section as menu in a tree structure.  \nYou can set `title` and `weight` in front matter of pages to adjust order and titles in menu.\n\n### Leaf bundle menu\nYou can also use leaf bundle and content of it's `index.md` as \nmenu.\n\nGiven you have this file structure\n```\n├── content\n│   ├── docs\n│   │   ├── page-one.md\n│   │   └── page-two.md\n│   └── posts\n│       ├── post-one.md\n│       └── post-two.md\n```\n\nCreate file `content/docs/menu/index.md` with content\n```md\n---\nheadless: true\n---\n\n- [Book Example](/docs/)\n  - [Page One](/docs/page-one)\n  - [Page Two](/docs/page-two)\n- [Blog](/posts)\n```\n\nAnd Enable it by settings `BookMenuBundle: /docs/menu` in Site configuration\n\n- [Example menu](https://github.com/alex-shpak/hugo-book/blob/master/exampleSite/content/menu/index.md)\n- [Example config file](https://github.com/alex-shpak/hugo-book/blob/master/exampleSite/config.yml)\n- [Leaf bundles](https://gohugo.io/content-management/page-bundles/)\n\n## Blog\nSimple blog supported for section `posts`\n\n## Configuration\n### Site Configuration\nThere are few configuration options you can add to your `config.yml|json|toml` file\n```yaml\n# (Optional) Set this to true if you use captial letters in file names\ndisablePathToLower: true\n\n# (Optional) Set this to true to enable 'Last Modified by' date and git author\n#  information on 'doc' type pages.\nenableGitInfo: true\n\n# (Warnings) Theme is intended for documentation use, there for it doesn't render taxonomy.\n# You can hide related warning with config below\ndisableKinds: [\"taxonomy\", \"taxonomyTerm\"]\n\nparams:\n  # (Optional, default true) Show or hide table of contents globally\n  # You can also specify this parameter per page in front matter\n  BookShowToC: true\n\n  # (Optional, default none) Set leaf bundle to render as side menu\n  # When not specified file structure and weights will be used\n  BookMenuBundle: /menu\n\n  # (Optional, default docs) Specify section of content to render as menu\n  # You can also set value to \"*\" to render all sections to menu\n  BookSection: docs\n\n  # This value is duplicate of $link-color for making active link highlight in menu bundle mode\n  # BookMenuBundleActiveLinkColor: \\#004ed0\n\n  # Include JS scripts in pages. Disabled by default.\n  # - Keep side menu on same scroll position during navigation\n  BookEnableJS: true\n\n  # Set source repository location.\n  # Used for 'Last Modified' and 'Edit this page' links.\n  BookRepo: https://github.com/alex-shpak/hugo-book\n\n  # Enable \"Edit this page\" links for 'doc' page type.\n  # Disabled by default. Uncomment to enable. Requires 'BookRepo' param.\n  # Path must point to 'content' directory of repo.\n  BookEditPath: edit/master/exampleSite/content\n```\n\n### Page Configuration\nYou can specify additional params per page in front matter\n```yaml\n---\n# Set type to 'docs' if you want to render page outside of configured section or if you render section other than 'docs'\ntype: docs\n\n# Set page weight to re-arrange items in file-tree menu (if BookMenuBundle not set)\nweight: 10\n\n# (Optional) Set to mark page as flat section in file-tree menu (if BookMenuBundle not set)\nbookFlatSection: true\n\n# (Optional) Set to hide table of contents, overrides global value\nbookShowToC: false\n---\n```\n\n### Partials\nThere are few empty partials you can override in `layouts/partials/`\n\n| Partial                                          | Placement                               |\n| --                                               | --                                      |\n| `layouts/partials/docs/inject/head.html`         | Before closing `<head>` tag             |\n| `layouts/partials/docs/inject/body.html`         | Before closing `<body>` tag             |\n| `layouts/partials/docs/inject/menu-before.html`  | At the beginning of `<nav>` menu block  |\n| `layouts/partials/docs/inject/menu-after.html`   | At the end of `<nav>` menu block        |\n\n\n## Contributing\nContributions are welcome and I will review and consider pull requests.  \nPrimary goals are:\n - Keep it simple\n - Keep minimal (or zero) default configuration\n - Avoid interference with user-defined layouts\n\nFeel free to open issue if you missing some configuration or customization option.\n\n## License\n[MIT](LICENSE)\n"
  },
  {
    "path": "docs-source/themes/book/archetypes/docs.md",
    "content": "---\ntitle: \"{{ .Name | humanize | title }}\"\nweight: 1\n---\n"
  },
  {
    "path": "docs-source/themes/book/assets/_markdown.scss",
    "content": "@import 'variables';\n\n$block-border-radius: 0.15rem;\n\n.markdown {\n  line-height: 1.7;\n\n  > :first-child {\n    margin-top: 0;\n    line-height: 1em;\n  }\n\n  h1, h2, h3, h4, h5 {\n    font-weight: 400;\n    line-height: 1.25;\n  }\n\n  b, optgroup, strong {\n    font-weight: 700;\n  }\n\n  a {\n    text-decoration: none;\n\n    &:hover {\n      text-decoration: underline;\n    }\n  }\n\n  code {\n    font-family: 'Oxygen Mono', monospace;\n  }\n\n  p code {\n    padding: 0 $padding-4;\n    background: $gray-200;\n    border-radius: $block-border-radius;\n  }\n\n  pre {\n    padding: $padding-16;\n    background: $gray-100;\n    border-radius: $block-border-radius;\n    font-size: $font-size-14;\n    overflow-x: auto;\n  }\n\n  blockquote {\n    border-left: $padding-1*2 solid $gray-300;\n    margin: 0;\n    padding: $padding-1 $padding-16;\n\n    :first-child { margin-top: 0; }\n    :last-child { margin-bottom: 0; }\n  }\n\n  table tr {\n    font-size: $font-size-14;\n    td, th {\n      padding: $padding-8;\n    }\n  }\n\n  table {\n    tr {\n      td, th {\n        border-bottom: 1px solid $gray-200;\n        border-right: 1px solid $gray-200;\n\n        &:last-child {\n          border-right: none;\n        }\n      }\n\n      &:last-child {\n        td {\n          border-bottom: none;\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs-source/themes/book/assets/_utils.scss",
    "content": ".flex {\n  display: flex;\n}\n\n.justify-start {\n  justify-content: flex-start;\n}\n\n.justify-end {\n  justify-content: flex-end;\n}\n\n.justify-center {\n  justify-content: center;\n}\n\n.justify-between {\n  justify-content: space-between;\n}\n\n.align-center {\n  align-items: center;\n}\n\n.mx-auto {\n  margin: 0 auto;\n}\n\n.mr-auto {\n  margin-right: auto;\n}\n\n.hide {\n  display: none;\n}\n\n@mixin fixed {\n  position: fixed;\n  top: 0;\n  bottom: 0;\n  overflow-x: hidden;\n  overflow-y: auto;\n}\n\n@mixin dark-links {\n  a {\n    color: $nav-link-color;\n  }\n\n  a.active {\n    color: $color-link;\n  }\n}"
  },
  {
    "path": "docs-source/themes/book/assets/_variables.scss",
    "content": "$padding-1: 1px;\n$padding-4: 0.25rem;\n$padding-8: 0.5rem;\n$padding-16: 1rem;\n\n$font-size-base: 16px;\n$font-size-12: 0.75rem;\n$font-size-14: 0.875rem;\n$font-size-16: 1rem;\n\n// Grayscale\n$white: #ffffff;\n$gray-100: #f8f9fa;\n$gray-200: #e9ecef;\n$gray-300: #dee2e6;\n$gray-400: #ced4da;\n$gray-500: #adb5bd;\n$gray-600: #868e96;\n$gray-700: #495057;\n$gray-800: #343a40;\n$gray-900: #212529;\n$black: #000;\n\n$color-link: #004ed0;\n$color-visited-link: #8440f1;\n\n$color-button: #007bff;\n$color-button-active: #0062cc;\n\n$body-background: white;\n$body-font-color: $gray-800;\n$body-font-weight: 400;\n$body-min-width: 25rem;\n\n$nav-background: $body-background;\n$nav-link-color: $gray-800;\n\n$header-height: 3.5rem;\n$menu-width: 18rem;\n$toc-width: 14rem;\n\n$container-min-width: $body-min-width;\n$container-max-width: 80rem;\n\n$sm-breakpoint: $menu-width + $body-min-width;\n$md-breakpoint: $sm-breakpoint + $toc-width;\n"
  },
  {
    "path": "docs-source/themes/book/assets/book.scss",
    "content": "@import \"variables\";\n@import \"markdown\";\n@import \"utils\";\n\nhtml {\n  font-size: $font-size-base;\n  letter-spacing: 0.33px;\n  scroll-behavior: smooth;\n}\n\nhtml,\nbody {\n  min-width: $body-min-width;\n  overflow-x: hidden;\n}\n\nbody {\n  color: $body-font-color;\n  background: $body-background;\n\n  font-family: \"Oxygen\", sans-serif;\n  font-weight: $body-font-weight;\n\n  text-rendering: optimizeLegibility;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n\n  box-sizing: border-box;\n  * {\n    box-sizing: inherit;\n  }\n}\n\nh1,\nh2,\nh3,\nh4,\nh5 {\n  font-weight: 400;\n}\n\na {\n  text-decoration: none;\n  color: $color-link;\n}\n\nimg {\n  vertical-align: middle;\n}\n\naside nav ul {\n  padding: 0;\n  margin: 0;\n  list-style: none;\n\n  li {\n    margin: 1em 0;\n  }\n\n  a {\n    display: block;\n  }\n\n  a:hover {\n    opacity: .5;\n  }\n\n  ul {\n    padding-left: $padding-16;\n  }\n}\n\nul.pagination {\n  display: flex;\n  justify-content: center;\n\n  .page-item a {\n    padding: $padding-16;\n  }\n}\n\ncode {\n  background-color: $gray-200;\n}\n\n// Reset code inside code blocks\n.highlight {\n  code {\n    background: none;\n  }\n}\n\n.container {\n  min-width: $container-min-width;\n  max-width: $container-max-width;\n  margin: 0 auto;\n}\n\n.book-brand {\n  margin-top: 0;\n}\n\n.book-menu {\n  flex: 0 0 $menu-width;\n  font-size: $font-size-14;\n\n  nav {\n    width: $menu-width;\n    padding: $padding-16;\n\n    @include fixed;\n  }\n\n  @include dark-links;\n}\n\n.book-page {\n  min-width: $body-min-width;\n  padding: $padding-16;\n\n  // Make images responsive\n  img {\n    max-width: 100%;\n    height: auto;\n  }\n}\n\n.book-header {\n  margin-bottom: $padding-16;\n  display: none;\n}\n\n.book-toc {\n  flex: 0 0 $toc-width;\n  font-size: $font-size-12;\n\n  nav {\n    width: $toc-width;\n    padding: $padding-16;\n\n    @include fixed;\n  }\n\n  nav > ul > li {\n    margin: 0;\n  }\n}\n\n.book-git-footer {\n  display: flex;\n  margin-top: $padding-16;\n  font-size: $font-size-14;\n  align-items: baseline;\n\n  img {\n    width: $font-size-14;\n    vertical-align: bottom;\n  }\n}\n\n.book-posts {\n  min-width: $body-min-width;\n  max-width: $sm-breakpoint;\n  padding: $padding-16;\n\n  article {\n    padding-bottom: $padding-16;\n  }\n}\n\n// Responsive styles\naside nav,\n.book-page,\n.book-posts,\n.markdown {\n  transition: 0.2s ease-in-out;\n  transition-property: transform, margin-left, opacity;\n  will-change: transform, margin-left;\n}\n\n@media screen and (max-width: $md-breakpoint) {\n  .book-toc {\n    display: none;\n  }\n}\n\n@media screen and (max-width: $sm-breakpoint) {\n  .book-menu {\n    margin-left: -$menu-width;\n  }\n\n  .book-header {\n    display: flex;\n  }\n\n  #menu-control:checked + main {\n    .book-menu nav,\n    .book-page,\n    .book-posts {\n      transform: translateX($menu-width);\n    }\n\n    .book-header label {\n      transform: rotate(90deg);\n    }\n\n    .markdown {\n      opacity: 0.25;\n    }\n  }\n}\n\n// Extra space for big screens\n@media screen and (min-width: $container-max-width) {\n  .book-page,\n  .book-menu nav,\n  .book-toc nav {\n    padding: $padding-16 * 2 $padding-16;\n  }\n}\n\n// Custom styles\na.hereditas-button {\n  padding: .375rem .75rem;\n  line-height: 1.5;\n  border-radius: .25rem;\n  display: inline-block;\n  color: $white;\n  background-color: $color-button;\n  border: 1px solid $color-button;\n  transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;\n\n  &:hover {\n    text-decoration: none;\n    background-color: $color-button-active;\n    border-color: $color-button-active;\n  }\n}\n"
  },
  {
    "path": "docs-source/themes/book/layouts/404.html",
    "content": "<!DOCTYPE html>\n{{- partial \"docs/shared\" -}}\n<html>\n\n<head>\n  {{ partial \"docs/html-head\" . }}\n  {{ partial \"docs/inject/head\" . }}\n</head>\n\n<body>\n  <main class=\"flex justify-center\">\n      <h1>404 Not Found</h1>\n  </main>\n\n  {{ partial \"docs/inject/body\" . }}\n</body>\n\n</html>\n"
  },
  {
    "path": "docs-source/themes/book/layouts/docs/baseof.html",
    "content": "<!DOCTYPE html>\n{{- partial \"docs/shared\" -}}\n<html>\n\n<head>\n  {{ partial \"docs/html-head\" . }}\n  {{ partial \"docs/inject/head\" . }}\n</head>\n\n<body>\n  <input type=\"checkbox\" style=\"display: none\" id=\"menu-control\" />\n  <main class=\"container flex\">\n\n    <aside class=\"fixed book-menu\">\n      {{ partial \"docs/menu\" . }}\n    </aside>\n\n    <div class=\"book-page\">\n      {{ partial \"docs/mobile-header\" . }}\n      {{ template \"main\" . }}\n      {{ partial \"docs/git-footer\" . }}\n    </div>\n\n    {{ template \"toc\" . }}\n  </main>\n\n  {{ partial \"docs/inject/body\" . }}\n</body>\n\n</html>\n"
  },
  {
    "path": "docs-source/themes/book/layouts/docs/list.html",
    "content": "{{ define \"main\" }}\n<article class=\"markdown\">\n  {{- .Content -}}\n</article>\n{{ end }}\n\n{{ define \"toc\" }}\n  {{ partial \"docs/toc\" . }}\n{{ end }}\n"
  },
  {
    "path": "docs-source/themes/book/layouts/docs/single.html",
    "content": "{{ define \"main\" }}\n<article class=\"markdown\">\n  {{- .Content -}}\n</article>\n{{ end }}\n\n{{ define \"toc\" }}\n  {{ partial \"docs/toc\" . }}\n{{ end }}\n"
  },
  {
    "path": "docs-source/themes/book/layouts/partials/docs/brand.html",
    "content": "<h2 class=\"book-brand\">\n  <a href=\"{{ .Site.BaseURL }}\">{{ .Site.Title }}</a>\n</h2>"
  },
  {
    "path": "docs-source/themes/book/layouts/partials/docs/git-footer.html",
    "content": "{{ if or .GitInfo .Site.Params.BookEditPath }}\n<div class=\"align-center book-git-footer {{ if not .GitInfo }}justify-end{{ else }}justify-between{{ end }}\">\n  {{ with .GitInfo }}\n  <div>\n    <a href=\"{{ $.Site.Params.BookRepo }}/commit/{{ .Hash }}\" title='Last modified {{ .AuthorDate.Local.Format \"January 2, 2006 15:04 MST\" }} by {{ .AuthorName }}' target=\"_blank\" rel=\"noopener\">\n      <img src=\"{{ \"svg/code-merge.svg\" | relURL }}\" /> {{ .AuthorDate.Local.Format \"Last Modified Jan 2, 2006\" }}\n    </a>\n  </div>\n  {{ end }}\n  {{ with .Site.Params.BookEditPath }}\n  <div>\n    <a href=\"{{ $.Site.Params.BookRepo }}/{{ . }}/{{ $.File.Path }}\" target=\"_blank\" rel=\"noopener\">\n      <img src=\"{{ \"svg/code-fork.svg\" | relURL }}\" /> Edit this page\n    </a>\n  </div>\n  {{ end }}\n</div>\n{{ end }}\n"
  },
  {
    "path": "docs-source/themes/book/layouts/partials/docs/html-head.html",
    "content": "<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>{{- template \"title\" . }} | {{ .Site.Title -}}</title>\n\n<link href=\"https://fonts.googleapis.com/css?family=Oxygen|Oxygen+Mono:300,400,700\" rel=\"stylesheet\">\n<link rel=\"stylesheet\" href=\"{{ \"normalize.min.css\" | relURL }}\">\n{{ $styles := resources.Get \"book.scss\" | resources.ToCSS | resources.Minify | resources.Fingerprint }}\n<link rel=\"stylesheet\" href=\"{{ $styles.RelPermalink }}\">\n\n{{ \"<!--\" | safeHTML }}\nMade with Book Theme\nhttps://github.com/alex-shpak/hugo-book\n{{ \"-->\" | safeHTML }}"
  },
  {
    "path": "docs-source/themes/book/layouts/partials/docs/inject/body.html",
    "content": "{{ if eq hugo.Environment \"production\" }}\n  {{ with .Site.Params.PlausibleAnalytics }}\n<script async defer data-domain=\"{{ .Domain }}\" data-api=\"/pls/api/event\" src=\"/pls/index.d81135.js\"></script>\n  {{ end }}\n{{ end }}\n"
  },
  {
    "path": "docs-source/themes/book/layouts/partials/docs/inject/head.html",
    "content": ""
  },
  {
    "path": "docs-source/themes/book/layouts/partials/docs/inject/menu-after.html",
    "content": ""
  },
  {
    "path": "docs-source/themes/book/layouts/partials/docs/inject/menu-before.html",
    "content": ""
  },
  {
    "path": "docs-source/themes/book/layouts/partials/docs/menu-bundle.html",
    "content": "{{- template \"hrefhack\" . -}}\n{{ with .Site.GetPage .Site.Params.BookMenuBundle }}\n  {{- .Content -}}\n{{ end }}\n{{ if .Site.Params.BookEnableJS }}\n  {{- template \"jsmenu\" . -}}\n{{ end }}"
  },
  {
    "path": "docs-source/themes/book/layouts/partials/docs/menu-filetree.html",
    "content": "<!-- Put configured sections list to .Scratch -->\n{{ template \"book-get-root-section\" . }} \n\n{{- range .Scratch.Get \"BookSections\" -}}\n  {{ template \"book-section\" (dict \"Section\" . \"CurrentPage\" $.Permalink) }}\n{{- end -}}\n\n{{ define \"book-section\" }} <!-- Single section of menu (recursive) -->\n  <ul>\n    {{ range .Section.Sections }}\n    <li {{- if .Params.bookflatsection}} class=\"flat-section\" {{ end }}>\n      {{- if .Content -}}\n        {{ template \"book-page-link\" (dict \"Page\" . \"CurrentPage\" $.CurrentPage) }}\n      {{- else -}}\n        {{- template \"title\" . -}}\n      {{- end -}}\n\n      {{ template \"book-section\" (dict \"Section\" . \"CurrentPage\" $.CurrentPage) }}\n    </li>\n    {{ end }}\n\n    {{ range .Section.Pages }}\n    <li>\n      {{ template \"book-page-link\" (dict \"Page\" . \"CurrentPage\" $.CurrentPage) }}\n    </li>\n    {{ end }}\n  </ul>\n{{ end }}\n\n{{ define \"book-page-link\" }}\n{{- with .Page -}}\n<a href=\"{{ .RelPermalink }}\" {{- if eq $.CurrentPage .Permalink }} class=\"active\"{{ end }}>\n  {{- template \"title\" . -}}\n</a>\n{{- end -}}\n{{ end }}\n\n{{ define \"book-get-root-section\" }}\n<!-- Complex logic to guess page title without .Title specified -->\n  {{ $bookSection := default \"docs\" .Site.Params.BookSection  }}\n  {{ if eq $bookSection \"*\" }}\n    {{ .Scratch.Set \"BookSections\" .Site.Sections }}\n  {{ else }}\n    {{ $bookSections := where .Site.Sections \"Section\" $bookSection }}\n    {{ .Scratch.Set \"BookSections\" $bookSections }}\n  {{ end }}\n{{ end }}"
  },
  {
    "path": "docs-source/themes/book/layouts/partials/docs/menu.html",
    "content": "<nav role=\"navigation\">\n{{ partial \"docs/brand\" . }}\n{{ partial \"docs/inject/menu-before\" . }}\n\n{{ if .Site.Params.BookMenuBundle }}\n    {{ partial \"docs/menu-bundle\" . }}\n{{ else }}\n    {{ partial \"docs/menu-filetree\" . }}\n{{ end }}\n\n{{ partial \"docs/inject/menu-after\" . }}\n</nav>"
  },
  {
    "path": "docs-source/themes/book/layouts/partials/docs/mobile-header.html",
    "content": "<header class=\"align-center justify-between book-header\">\n  <label for=\"menu-control\">\n    <img src=\"{{ \"svg/menu.svg\" | relURL }}\" />\n  </label>\n  <strong>{{- template \"title\" . }}</strong>\n</header>\n"
  },
  {
    "path": "docs-source/themes/book/layouts/partials/docs/shared.html",
    "content": "{{/*These templates contains some more complex logic and shared between partials*/}}\n{{- define \"title\" -}}\n  {{- if .Pages -}}\n    {{ $sections := split (trim .Dir \"/\") \"/\" }}\n    {{ $title := index ($sections | last 1) 0 | humanize | title }}\n    {{- default $title .Title -}}\n  {{- else -}}\n    {{ $title :=  .File | humanize | title }}\n    {{- default $title .Title -}}\n  {{- end -}}\n{{- end -}}\n\n{{- define \"hrefhack\" -}}\n  {{ $attrEq := \"$=\" }}\n  {{ $attrVal := .RelPermalink }}\n  {{ if eq .RelPermalink \"/\" }}\n    {{ $attrEq = \"=\" }}\n    {{ $attrVal = .Permalink }}\n  {{ end }}\n\n  <style>\n  nav ul a[href{{ $attrEq }}\"{{ $attrVal }}\"] {\n      color: {{ default \"#004ed0\" .Site.Params.BookMenuBundleActiveLinkColor }};\n  }\n  </style>\n{{- end -}}\n\n{{- define \"jsmenu\" -}}\n<script>\n(function() {\n  var menu = document.querySelector('aside.book-menu nav')\n  addEventListener('beforeunload', function(event) {\n    localStorage.setItem('menu.scrollTop', menu.scrollTop)\n  });\n\n  menu.scrollTop = localStorage.getItem('menu.scrollTop')\n})()\n</script>\n{{- end -}}"
  },
  {
    "path": "docs-source/themes/book/layouts/partials/docs/toc.html",
    "content": "{{ $showToC := default (default true .Site.Params.BookShowToC) .Params.bookshowtoc }}\n  {{ if and ($showToC) (.Page.TableOfContents) }}\n  <aside class=\"book-toc fixed\">\n    {{ .Page.TableOfContents }}\n  </aside>\n{{ end }}\n"
  },
  {
    "path": "docs-source/themes/book/layouts/posts/baseof.html",
    "content": "<!DOCTYPE html>\n{{- partial \"docs/shared\" -}}\n<html>\n\n<head>\n  {{ partial \"docs/html-head\" . }}\n  {{ partial \"docs/inject/head\" . }}\n</head>\n\n<body>\n  <input type=\"checkbox\" style=\"display: none\" id=\"menu-control\" />\n  <main class=\"container flex\">\n    <aside class=\"fixed book-menu\">\n      {{ partial \"docs/menu\" . }}\n    </aside>\n\n    <div class=\"mr-auto book-posts\">\n      {{ partial \"docs/mobile-header\" . }}\n      {{ template \"main\" . }}\n    </div>\n  </main>\n\n\n  {{ partial \"docs/inject/body\" . }}\n</body>\n\n</html>\n"
  },
  {
    "path": "docs-source/themes/book/layouts/posts/list.html",
    "content": "{{ define \"main\" }}\n  {{ $paginator := .Paginate (where .Pages \"Params.hidden\" \"ne\" true) }}\n  {{ range sort .Paginator.Pages }}\n  <article>\n    <h2>\n      <a href=\"{{ .RelPermalink }}\">{{ .Title }}</a>\n    </h2>\n    <h5>\n      <strong>{{ .Date.Format \"January 2, 2006\" }}</strong>\n    </h5>\n    <p class=\"markdown\">\n      {{- .Summary -}}\n      {{ if .Truncated }}\n        <a href=\"{{ .RelPermalink }}\">...</a>\n      {{ end }}\n    </p>\n  </article>\n  {{ end }}\n  {{ template \"_internal/pagination.html\" . }}\n{{ end }}\n"
  },
  {
    "path": "docs-source/themes/book/layouts/posts/single.html",
    "content": "{{ define \"main\" }}\n<header>\n  <h1>{{ .Title }}</h1>\n  <h5>\n    <strong>{{ .Date.Format \"January 2, 2006\" }}</strong>\n  </h5>\n</header>\n<article class=\"markdown\">\n  {{- .Content -}}\n</article>\n{{ end }}\n"
  },
  {
    "path": "docs-source/themes/book/source",
    "content": "https://github.com/alex-shpak/hugo-book/tree/fdc6fdd2de1bcb5a891fd3e76c7d4cacb596ee09"
  },
  {
    "path": "docs-source/themes/book/theme.toml",
    "content": "# theme.toml template for a Hugo theme\n# See https://github.com/gohugoio/hugoThemes#themetoml for an example\n\nname = \"Book\"\nlicense = \"MIT\"\nlicenselink = \"https://github.com/alex-shpak/hugo-book/blob/master/LICENSE\"\ndescription = \"Hugo documentation theme as simple as plain book\"\nhomepage = \"https://github.com/alex-shpak/hugo-book\"\ntags = [\"responsive\", \"clean\", \"documentation\", \"docs\", \"flexbox\"]\nfeatures = []\nmin_version = \"0.43\"\n\n[author]\n  name = \"Alex Shpak\"\n  homepage = \"https://github.com/alex-shpak/\"\n"
  },
  {
    "path": "docs-source/workers-site/.cargo-ok",
    "content": ""
  },
  {
    "path": "docs-source/workers-site/.gitignore",
    "content": "node_modules\nworker\n"
  },
  {
    "path": "docs-source/workers-site/assets.js",
    "content": "export default [\n    {\n        match: /^\\/images\\/(.*?)$/,\n        storagePath: '/public/images/$1',\n        // Cache in the edge for 3 months\n        edgeTTL: 86400 * 90,\n        // Cache in the browser for 2 weeks\n        browserTTL: 86400 * 14,\n        // Not immutable\n        immutable: false\n    },\n    {\n        match: /^\\/svg\\/(.*?)$/,\n        storagePath: '/public/svg/$1',\n        // Cache in the edge for 3 months\n        edgeTTL: 86400 * 90,\n        // Cache in the browser for 1 month\n        browserTTL: 86400 * 30,\n        // Not immutable\n        immutable: false\n    },\n]\n"
  },
  {
    "path": "docs-source/workers-site/cache-config.js",
    "content": "// Configure how to cache files from KV\n\n// Match by path\nexport const cachePaths = [\n    {\n        // JS and CSS files\n        // They have a unique hash in the file name\n        match: /^\\/(js|css)\\/(.*?)$/,\n        // Cache in the edge for 6 months\n        edgeTTL: 86400 * 180,\n        // Cache in the browser for 6 months\n        browserTTL: 86400 * 180,\n        // Not immutable\n        immutable: false\n    }\n]\n\n// Default\nexport const cacheDefault = {\n    // Cache in the edge for 5 minutes\n    edgeTTL: 300,\n    // Cache in the browser for 10 minutes\n    browserTTL: 600,\n    // Not immutable\n    immutable: false\n}\n\n/**\n * Returns the cache settings for a given URL.\n * @param {string} url - URL of the original request\n * @returns {CacheOpts}\n */\nexport function cacheSettings(urlStr) {\n    // Convert to an URL object\n    const url = new URL(urlStr)\n    if (!url || !url.host) {\n        return null\n    }\n\n    // Check if there are special cache settings for this URL\n    for (let i = 0; i < cachePaths.length; i++) {\n        const e = cachePaths[i]\n        if (!e || !e.match) {\n            continue\n        }\n        const match = url.pathname.match(e.match)\n        if (!match) {\n            continue\n        }\n\n        // Return the request URL and caching options\n        return {\n            edgeTTL: e.edgeTTL,\n            browserTTL: e.browserTTL,\n            immutable: !!e.immutable\n        }\n    }\n\n    return cacheDefault\n}\n\n/**\n * @typedef CacheOpts\n * @type {Object}\n * @property {number} [edgeTTL]\n * @property {number} [browserTTL]\n * @property {boolean} [immutable]\n */\n"
  },
  {
    "path": "docs-source/workers-site/index.js",
    "content": "import {getAssetFromKV} from '@cloudflare/kv-asset-handler'\nimport assets from './assets'\nimport {cacheSettings} from './cache-config'\n\n/* global STORAGE_ACCOUNT, STORAGE_CONTAINER, DOMAINS, PLAUSIBLE_ANALYTICS */\n\n/**\n * The DEBUG flag will do two things that help during development:\n * 1. we will skip caching on the edge, which makes it easier to debug\n * 2. we will return an error message on exception in your Response rather than the default 404.html page\n */\nconst DEBUG = false\n\naddEventListener('fetch', (event) => {\n    try {\n        event.respondWith(handleEvent(event))\n    }\n    catch (e) {\n        if (DEBUG) {\n            return event.respondWith(\n                new Response(e.message || e.toString(), {\n                    status: 500,\n                }),\n            )\n        }\n        event.respondWith(new Response('Internal Error', {status: 500}))\n    }\n})\n\n/**\n * Handles requests coming in from the client\n * @param {Event} event\n * @returns {Promise<Response>} Response object\n */\nasync function handleEvent(event) {\n    const reqUrl = new URL(event.request.url)\n\n    // Check if the URL points to a static asset on Azure Storage\n    const useAsset = isAsset(reqUrl)\n    if (useAsset) {\n        return requestAsset(useAsset)\n    }\n\n    // Handle proxy for Plausible if enabled (if PLAUSIBLE_ANALYTICS contains the URL of the Plausible server, with https prefix)\n    // 1. Proxy and cache the script (from /pls/index.*.js to ${PLAUSIBLE_ANALYTICS}/js/plausible.outbound-links.js)\n    // 2. Proxy (no cache) the message sending the request (from /pls/(event|error) to ${PLAUSIBLE_ANALYTICS}/api/(event|error))\n    // Check if the URL is for the Plausible Analytics script\n    const path = reqUrl.pathname\n    if (PLAUSIBLE_ANALYTICS && path) {\n        // Script\n        if (/^\\/pls\\/index(\\.[a-fA-F0-9]{1,6})?\\.js$/.test(path)) {\n            // Request the asset and modify the response to add padding\n            return requestAsset(\n                {\n                    url: PLAUSIBLE_ANALYTICS + '/js/plausible.outbound-links.js',\n                    // Cache in the edge for a day and in the browser for 12 hours\n                    edgeTTL: 86400,\n                    browserTTL: 43200\n                },\n                async (response) => {\n                    // Get the body's text and add padding\n                    let text = await response.text()\n                    const num = Math.floor(Math.random() * 100000)\n                    if (Math.random() < 0.5) {\n                        text += `\\n;'` + num + `'`\n                    } else {\n                        text = `'` + num + `';\\n` + text\n                    }\n                    return text\n                }\n            )\n        }\n\n        // APIs\n        if (path.startsWith('/pls/api')) {\n            // Clone the request but change the URL\n            console.log(PLAUSIBLE_ANALYTICS + path.slice(4))\n            const newReq = new Request(\n                PLAUSIBLE_ANALYTICS + path.slice(4),\n                new Request(event.request, {})\n            )\n\n            // Set the X-Forwarded-For header\n            // Cloudflare automatically adds X-Real-IP and CF-Connecting-IP (and X-Forwarded-Proto), but we need X-Forwarded-For too\n            // First, check if the request had an X-Forwarded-For already\n            if (!newReq.headers.get('X-Forwarded-For')) {\n                // Fallback to CF-Connecting-IP if available\n                // Lastly, X-Real-IP\n                if (newReq.headers.get('CF-Connecting-IP')) {\n                    newReq.headers.set('X-Forwarded-For', newReq.headers.get('CF-Connecting-IP'))\n                } else if (newReq.headers.get('X-Real-IP')) {\n                    newReq.headers.set('X-Forwarded-For', newReq.headers.get('X-Real-IP'))\n                }\n            }\n\n            // Need to remove all Cloudflare headers (starting with cf-) and the Host and Cookie headers, or the request will fail\n            newReq.headers.delete('Host')\n            newReq.headers.delete('Cookie')\n            for (const key of newReq.headers.keys()) {\n                if (key.startsWith('cf-')) {\n                    newReq.headers.delete(key)\n                }\n            }\n\n            // Make the request\n            return fetch(newReq)\n        }\n    }\n\n    // Request from the KV\n    return requestFromKV(event)\n}\n\n/**\n * Loads the response from the Workers KV\n * @param {Event} event\n * @returns {Promise<Response>} Response object\n */\nasync function requestFromKV(event) {\n    // Get cache settings for this file\n    const cacheOpts = cacheSettings(event.request.url)\n    // Options for the request from the KV\n    /** @type {import('@cloudflare/kv-asset-handler').Options} */\n    const options = {\n        // Set custom caching options\n        cacheControl: {\n            // Add the options\n            ...cacheOpts,\n            // Use Cloudflare cache\n            bypassCache: false,\n        }\n    }\n    if (DEBUG) {\n        // Disable caching while in debug mode\n        options.cacheControl = {\n            bypassCache: true,\n            browserTTL: null,\n        }\n    }\n\n    try {\n        const response = await getAssetFromKV(event, options)\n\n        // Set the Cache-Control header for the browser\n        setCacheHeader(response.status, response.headers, cacheOpts)\n\n        // Set security headers\n        setSecurityHeaders(response.headers)\n\n        return response\n    }\n    catch (e) {\n        // If an error is thrown try to serve the asset at 404.html\n        if (!DEBUG) {\n            try {\n                const notFoundResponse = await getAssetFromKV(event, {\n                    mapRequestToAsset: req => new Request((new URL(req.url).origin) + '/404.html', req),\n                })\n\n                return new Response(notFoundResponse.body, {...notFoundResponse, status: 404})\n            }\n            // eslint-disable-next-line no-empty\n            catch (e) {}\n        }\n\n        return new Response(e.message || e.toString(), {status: 500})\n    }\n}\n\n/**\n * Requests an asset, optionally caching it in the edge. It also sets the correct headers in the response.\n * @param {object} useAsset\n * @param {(response: Response) => Promise<string>} [modifyBody] Optional method that can modify the response's body\n * @returns {Response} A Response object\n */\nasync function requestAsset(useAsset, modifyBody) {\n    // Caching options\n    const cfOpts = {}\n    if (useAsset.edgeTTL) {\n        // Cache everything, even if the response has no TTL\n        cfOpts.cacheEverything = true\n        cfOpts.cacheTtlByStatus = {\n            '200-299': useAsset.edgeTTL,\n            404: 3,\n            '500-599': 0\n        }\n    }\n\n    // Return a fetch invocation (promise) that retrieves data from the origin\n    let response = await fetch(useAsset.url, {\n        cf: cfOpts\n    })\n\n    // See if we want to modify the response's body\n    let body = response.body\n    if (modifyBody) {\n        body = await modifyBody(response)\n    }\n\n    // Reconstruct the Response object to make its headers mutable\n    response = new Response(body, response)\n\n    // Delete all Azure Storage headers (x-ms-*)\n    for (const key of response.headers.keys()) {\n        if (key.startsWith('x-ms-')) {\n            response.headers.delete(key)\n        }\n    }\n\n    // Set the Cache-Control header for the browser\n    setCacheHeader(response.status, response.headers, useAsset)\n\n    // Set security headers\n    setSecurityHeaders(response.headers)\n\n    // Return the data we requested (and cached)\n    return response\n}\n\n/**\n * Sets the Cache-Control header for the browser if needed\n * @param {number} statusCode\n * @param {Headers} headers\n * @param {CacheOpts} cacheOpts\n */\nfunction setCacheHeader(statusCode, headers, cacheOpts) {\n    if (statusCode >= 200 && statusCode <= 299 && cacheOpts.browserTTL) {\n        let val = 'public,max-age=' + cacheOpts.browserTTL\n        if (cacheOpts.immutable) {\n            val += ',immutable'\n        }\n        headers.set('Cache-Control', val)\n    } else {\n        headers.delete('Cache-Control')\n    }\n}\n\n/**\n * Sets some security headers\n * @param {Headers} headers\n */\nfunction setSecurityHeaders(headers) {\n    // Opt out of FLoC\n    let policy = headers.get('Permissions-Policy')\n    policy = (policy ? policy + '; ' : '') + 'interest-cohort=()'\n    headers.set('Permissions-Policy', policy)\n\n    // Referrer policy\n    headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')\n\n    // Set CSP (and X-Frame-Options) for HTML pages only\n    const ct = headers.get('Content-Type')\n    if (ct && ct == 'text/html' || ct.startsWith('text/html;')) {\n        // Allow using in frames on the same origin only\n        headers.set('X-Frame-Options', 'SAMEORIGIN')\n    }\n}\n\n/**\n * Check if the requested URL corresponds to an asset in Azure Storage\n * @param {URL} url - URL of the original request\n */\nfunction isAsset(url) {\n    for (let i = 0; i < assets.length; i++) {\n        const e = assets[i]\n        if (!e || !e.match) {\n            continue\n        }\n        const match = url.pathname.match(e.match)\n        if (!match) {\n            continue\n        }\n\n        // New request URL\n        const assetUrl = 'https://' + STORAGE_ACCOUNT + '.blob.core.windows.net/' + STORAGE_CONTAINER + e.storagePath.replace(/\\$([1-9][0-9]*)/g, (m) => {\n            const index = parseInt(m.substr(1), 10)\n            return match[index] || ''\n        })\n\n        // Return the request URL and caching options\n        return {\n            url: assetUrl,\n            edgeTTL: e.edgeTTL,\n            browserTTL: e.browserTTL,\n            immutable: !!e.immutable\n        }\n    }\n\n    return false\n}\n"
  },
  {
    "path": "docs-source/workers-site/package.json",
    "content": "{\n  \"private\": true,\n  \"name\": \"worker\",\n  \"version\": \"1.0.0\",\n  \"description\": \"A template for kick starting a Cloudflare Workers project\",\n  \"main\": \"index.js\",\n  \"author\": \"Ashley Lewis <ashleymichal@gmail.com>\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@cloudflare/kv-asset-handler\": \"^0.1.3\"\n  }\n}\n"
  },
  {
    "path": "docs-source/wrangler.toml",
    "content": "compatibility_date = \"2021-11-08\"\ntype = \"webpack\"\nname = \"hereditas-dev\"\n# Dev environment is deployed to a workers.dev domain\nworkers_dev = true\n# Use CF_ACCOUNT_ID instead\n#account_id = \"\"\n# Use CF_ZONE_ID instead\n#zone_id = \"\"\nvars = {STORAGE_CONTAINER = \"hereditas-dev\", DOMAINS = \"hereditas-dev.italypaleale.workers.dev\"}\n# These variables need to be set as secrets: PLAUSIBLE_ANALYTICS (except for dev), STORAGE_ACCOUNT\n\n[site]\nbucket = \"./public\"\nentry-point = \"workers-site\"\n\n[env.staging]\nname = \"hereditas-staging\"\nworkers_dev = true\nroute = \"staging.hereditas.app/*\"\nvars = {STORAGE_CONTAINER = \"hereditas-staging\", DOMAINS = \"staging.hereditas.app\"}\n\n[env.production]\nname = \"hereditas\"\nworkers_dev = true\nroute = \"hereditas.app/*\"\nvars = {STORAGE_CONTAINER = \"hereditas-prod\", DOMAINS = \"hereditas.app\"}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"hereditas\",\n  \"version\": \"0.2.2\",\n  \"author\": \"Alessandro Segala @ItalyPaleAle\",\n  \"bin\": {\n    \"hereditas\": \"./bin/run\"\n  },\n  \"bugs\": \"https://github.com/ItalyPaleAle/hereditas/issues\",\n  \"dependencies\": {\n    \"@fullhuman/postcss-purgecss\": \"2.3.0\",\n    \"@oclif/command\": \"1.8.0\",\n    \"@oclif/config\": \"1.17.0\",\n    \"@oclif/plugin-help\": \"3.2.0\",\n    \"argon2-browser\": \"1.14.0\",\n    \"auth0\": \"2.27.1\",\n    \"autoprefixer\": \"9.8.6\",\n    \"base64-loader\": \"1.0.0\",\n    \"buffer-equal-constant-time\": \"1.0.1\",\n    \"buffer-xor\": \"2.0.2\",\n    \"cli-ux\": \"5.4.10\",\n    \"copy-webpack-plugin\": \"6.0.3\",\n    \"css-loader\": \"4.2.1\",\n    \"html-webpack-plugin\": \"4.3.0\",\n    \"idtoken-verifier\": \"2.0.3\",\n    \"lodash.clonedeep\": \"4.5.0\",\n    \"lodash.defaultsdeep\": \"4.6.1\",\n    \"marked\": \"1.1.1\",\n    \"mini-css-extract-plugin\": \"0.10.0\",\n    \"path-to-regexp\": \"6.1.0\",\n    \"postcss\": \"7.0.32\",\n    \"postcss-import\": \"12.0.1\",\n    \"postcss-loader\": \"3.0.0\",\n    \"qs\": \"6.9.4\",\n    \"smhelper\": \"1.2.4\",\n    \"style-loader\": \"1.2.1\",\n    \"svelte\": \"3.24.1\",\n    \"svelte-loader\": \"2.13.6\",\n    \"svelte-spa-router\": \"2.2.0\",\n    \"tailwindcss\": \"1.7.1\",\n    \"webpack\": \"4.44.1\",\n    \"webpack-subresource-integrity\": \"1.4.1\"\n  },\n  \"devDependencies\": {\n    \"@oclif/dev-cli\": \"1.22.2\",\n    \"eslint\": \"7.7.0\",\n    \"eslint-plugin-html\": \"6.0.3\",\n    \"eslint-plugin-svelte3\": \"2.7.3\",\n    \"globby\": \"11.0.1\",\n    \"mustache\": \"4.0.1\",\n    \"serve\": \"11.3.2\"\n  },\n  \"engines\": {\n    \"node\": \">=10.0.0\"\n  },\n  \"files\": [\n    \"/app\",\n    \"/auth0\",\n    \"/bin\",\n    \"/cli\",\n    \"/npm-shrinkwrap.json\",\n    \"/oclif.manifest.json\",\n    \"/vendor\"\n  ],\n  \"homepage\": \"https://github.com/ItalyPaleAle/hereditas\",\n  \"keywords\": [\n    \"digital legacy\",\n    \"cli\",\n    \"generator\"\n  ],\n  \"license\": \"GPL-3.0-only\",\n  \"main\": \"cli/index.js\",\n  \"oclif\": {\n    \"commands\": \"./cli/commands\",\n    \"bin\": \"hereditas\",\n    \"plugins\": [\n      \"@oclif/plugin-help\"\n    ],\n    \"topics\": {\n      \"auth0\": {\n        \"description\": \"interact with Auth0 to configure the Hereditas client\"\n      },\n      \"url\": {\n        \"description\": \"manage URLs where the Hereditas box will be deployed, which are used for OAuth callbacks\"\n      },\n      \"user\": {\n        \"description\": \"manage the list of owners and users of the Hereditas box\"\n      },\n      \"wait-time\": {\n        \"description\": \"manage the waitTime setting of the hereditas.json file\"\n      },\n      \"webhook\": {\n        \"description\": \"manage the webhook setting of the hereditas.json file\"\n      }\n    }\n  },\n  \"repository\": \"ItalyPaleAle/hereditas\",\n  \"scripts\": {\n    \"postpack\": \"rm -f oclif.manifest.json\",\n    \"eslint\": \"npx eslint -c .eslintrc.js --ext .js,.svelte,.html .\",\n    \"prepack\": \"oclif-dev manifest\",\n    \"test\": \"echo NO TESTS\"\n  }\n}\n"
  }
]