├── .dockerignore ├── apps ├── consumer │ ├── public │ │ └── .gitkeep │ ├── views │ │ ├── error.jade │ │ ├── layout.jade │ │ ├── articles │ │ │ ├── oauth2.jade │ │ │ └── subject.jade │ │ └── index.jade │ ├── Dockerfile │ ├── package.json │ ├── bin │ │ └── www │ ├── app.js │ ├── routes │ │ └── index.js │ └── package-lock.json └── resource-server │ ├── public │ └── .gitkeep │ ├── views │ ├── index.jade │ ├── layout.jade │ └── error.jade │ ├── Dockerfile │ ├── package.json │ ├── routes │ ├── oathkeeper.js │ ├── introspect.js │ └── keto.js │ ├── app.js │ └── bin │ └── www ├── cypress ├── .dockerignore ├── .gitignore ├── fixtures │ └── example.json ├── Dockerfile ├── integration │ ├── common.js │ └── full-stack │ │ ├── spec_00_setup.js │ │ └── spec_fullstack.js ├── plugins │ └── index.js └── support │ ├── index.js │ └── commands.js ├── .gitignore ├── cypress.json ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── support.md │ ├── feature_request.md │ └── bug_report.md ├── stale.yml ├── PULL_REQUEST_TEMPLATE.md └── adopters │ ├── tw.svg │ ├── 3R-horiz.svg │ ├── ordermygear.svg │ ├── tulip.svg │ ├── arduino.svg │ ├── hootsuite.svg │ ├── kyma.svg │ ├── spiribo.svg │ ├── allmyfunds.svg │ ├── segment.svg │ └── raspi.svg ├── .gitattributes ├── full-stack ├── config │ ├── keto │ │ ├── roles │ │ │ ├── user.json │ │ │ └── admin.json │ │ └── policies │ │ │ ├── blog-peter.json │ │ │ ├── blog-foobar.json │ │ │ ├── policy-by-role-user.json │ │ │ └── policy-by-role-admin.json │ ├── hydra │ │ └── clients │ │ │ ├── oathkeeper.json │ │ │ ├── example-auth-code-flow.json │ │ │ └── consumer-app.json │ └── oathkeeper │ │ └── rules │ │ └── resource-server.json ├── supervisord.conf ├── Dockerfile ├── README.md └── docker-compose.yml ├── hydra-bc └── README.md ├── hydra └── README.md ├── scripts ├── services │ ├── oathkeeper.sh │ ├── hydra.sh │ └── keto.sh ├── helper │ ├── getid.sh │ ├── envsubstfiles.sh │ └── retry.sh ├── supervisor-stdout.py ├── configure.sh └── supervisor-watchdog.py ├── package.json ├── SECURITY.md ├── Makefile ├── .circleci └── config.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── README.md └── LICENSE /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /apps/consumer/public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/resource-server/public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cypress/.dockerignore: -------------------------------------------------------------------------------- 1 | videos/ 2 | images/ -------------------------------------------------------------------------------- /cypress/.gitignore: -------------------------------------------------------------------------------- 1 | screenshots/ 2 | videos/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | vendor/ 3 | results/ -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "chromeWebSecurity": false 3 | } 4 | -------------------------------------------------------------------------------- /apps/resource-server/views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= title 5 | p Welcome to #{title} 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | # github: 4 | patreon: _ory 5 | open_collective: ory 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Never do crlf translation of bash scripts 2 | *.sh -crlf 3 | apps/* linguist-vendored 4 | cypress/* linguist-vendored 5 | -------------------------------------------------------------------------------- /full-stack/config/keto/roles/user.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id":"user", 3 | "members": [ 4 | "a@a.com", 5 | "b@b.com" 6 | ] 7 | }] 8 | -------------------------------------------------------------------------------- /apps/resource-server/views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | body 6 | block content 7 | -------------------------------------------------------------------------------- /apps/consumer/views/error.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /full-stack/config/keto/roles/admin.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id":"admin", 3 | "members": [ 4 | "foo@bar.com", 5 | "bar@foo.com" 6 | ] 7 | }] 8 | -------------------------------------------------------------------------------- /apps/resource-server/views/error.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /hydra-bc/README.md: -------------------------------------------------------------------------------- 1 | # Backwards Compatible Template for ORY Hydra < 1.0.0 2 | 3 | This example has been removed due to lack of community feedback and engagement, and high maintenance cost. -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } -------------------------------------------------------------------------------- /hydra/README.md: -------------------------------------------------------------------------------- 1 | # ORY Hydra and User Login & Consent Reference Implementation 2 | 3 | This example has [moved to the ORY Hydra Developer Guide](https://www.ory.sh/docs/guides/master/hydra/1-tutorial/). 4 | -------------------------------------------------------------------------------- /apps/consumer/views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | 3 | html 4 | head 5 | title= title 6 | style 7 | | pre { width: 100%; overflow-x: auto } 8 | body 9 | block content 10 | -------------------------------------------------------------------------------- /cypress/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM cypress/base:10 2 | 3 | WORKDIR /app 4 | 5 | ADD package.json /app/package.json 6 | ADD package-lock.json /app/package-lock.json 7 | 8 | RUN npm ci 9 | 10 | ADD cypress.json cypress.json 11 | ADD cypress cypress 12 | 13 | CMD ["/bin/sh"] 14 | -------------------------------------------------------------------------------- /full-stack/config/keto/policies/blog-peter.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id": "${KETO_RESOURCE_PREFIX}policies:peter-blog", 3 | "description": "This is another example policy which works together with the example consumer and resource server app.", 4 | "subjects": ["peter"], 5 | "effect": "allow", 6 | "resources": ["blog:posts:2"], 7 | "actions": ["blog:read"] 8 | }] 9 | -------------------------------------------------------------------------------- /full-stack/config/keto/policies/blog-foobar.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id": "${KETO_RESOURCE_PREFIX}policies:foobar-blog", 3 | "description": "This is another example policy which works together with the example consumer and resource server app.", 4 | "subjects": ["foo@bar.com"], 5 | "effect": "allow", 6 | "resources": ["blog:posts:1"], 7 | "actions": ["blog:read"] 8 | }] -------------------------------------------------------------------------------- /scripts/services/oathkeeper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | url=$1 6 | path=$2 7 | rule_path="$path/rules/*.json" 8 | 9 | echo "Importing oathkeeper rules from $rule_path..." 10 | for filename in $rule_path; do 11 | (set -x; oathkeeper rules import --endpoint "${url}" ${filename}) 12 | done 13 | echo "Imported oathkeeper rules from $rule_path!" 14 | -------------------------------------------------------------------------------- /full-stack/config/hydra/clients/oathkeeper.json: -------------------------------------------------------------------------------- 1 | { 2 | "client_id": "${HYDRA_SUBJECT_PREFIX}${OATHKEEPER_HYDRA_CLIENT_ID}", 3 | "description": "ORY Oathkeeper uses this client to perform OAuth 2.0 Token Introspection at ORY Hydra.", 4 | "client_secret": "${OATHKEEPER_HYDRA_CLIENT_SECRET}", 5 | "scope": "", 6 | "grant_types": ["client_credentials"], 7 | "response_types": ["token"] 8 | } 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support request 3 | about: 4 | Please use our forums (community.ory.sh) or the chat (ory.sh/chat) to ask for 5 | support 6 | --- 7 | 8 | Please use issues only to file potential bugs or request features. For 9 | everything else please go to the [ORY Community](https://community.ory.sh/) or 10 | join the [ORY Chat](https://www.ory.sh/chat). 11 | -------------------------------------------------------------------------------- /full-stack/config/keto/policies/policy-by-role-user.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id": "${KETO_RESOURCE_PREFIX}policies:policy:by:role:user", 3 | "description": "This is another example policy which works together with the example consumer and resource server app.", 4 | "subjects": ["user"], 5 | "effect": "allow", 6 | "resources": [ 7 | "/", 8 | "/home" 9 | ], 10 | "actions": ["get"] 11 | }] 12 | -------------------------------------------------------------------------------- /full-stack/config/keto/policies/policy-by-role-admin.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id": "${KETO_RESOURCE_PREFIX}policies:policy:by:role:admin", 3 | "description": "This is another example policy which works together with the example consumer and resource server app.", 4 | "subjects": ["admin"], 5 | "effect": "allow", 6 | "resources": [ 7 | "/", 8 | "/home", 9 | "/charts" 10 | ], 11 | "actions": ["get", "post", "put", "delete"] 12 | }] 13 | -------------------------------------------------------------------------------- /scripts/helper/getid.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | function getid { 6 | filename=$1 7 | content=$(cat $filename | sed 's/\n|\r//') 8 | echo $content | grep '"id":' | sed 's/.\+"id":\s\+"\([^"]*\)".\+/\1/g' 9 | } 10 | 11 | function getclientid { 12 | filename=$1 13 | content=$(cat $filename | sed 's/\n|\r//') 14 | echo $content | grep '"client_id":' | sed 's/.\+"client_id":\s\+"\([^"]*\)".\+/\1/g' 15 | } 16 | -------------------------------------------------------------------------------- /apps/consumer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10.4.0-alpine 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 8 | # where available (npm@5+) 9 | COPY package*.json ./ 10 | 11 | RUN npm install 12 | # If you are building your code for production 13 | # RUN npm install --only=production 14 | 15 | # Bundle app source 16 | COPY . . 17 | 18 | EXPOSE 8080 19 | CMD [ "npm", "start" ] 20 | -------------------------------------------------------------------------------- /apps/resource-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10.4.0-alpine 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 8 | # where available (npm@5+) 9 | COPY package*.json ./ 10 | 11 | RUN npm install 12 | # If you are building your code for production 13 | # RUN npm install --only=production 14 | 15 | # Bundle app source 16 | COPY . . 17 | 18 | EXPOSE 8080 19 | CMD [ "npm", "start" ] -------------------------------------------------------------------------------- /full-stack/config/hydra/clients/example-auth-code-flow.json: -------------------------------------------------------------------------------- 1 | { 2 | "client_id": "example-auth-code", 3 | "description": "This client can be used to perform (exemplarily) the OAuth 2.0 Authorize Code Flow.", 4 | "client_secret": "secret", 5 | "scope": "openid offline", 6 | "grant_types": [ 7 | "authorization_code", 8 | "refresh_token" 9 | ], 10 | "response_types": [ 11 | "code", 12 | "id_token" 13 | ], 14 | "redirect_uris": [ 15 | "http://localhost:5555/callback", 16 | "http://127.0.0.1:5555/callback" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /scripts/helper/envsubstfiles.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script expands environment variables in all files of a directory 4 | # ./envsubst.sh path/to/config/files/* 5 | 6 | set -euo pipefail 7 | 8 | function envsubstfiles { 9 | usepath=$1 10 | echo "Running envsubst in path $usepath" 11 | for filename in $usepath; do 12 | echo "Substituting environment variables in file $filename" 13 | (set -x; envsubst < ${filename} > ${filename}.env) 14 | (set -x; rm ${filename}) 15 | (set -x; mv ${filename}.env ${filename}) 16 | done 17 | } 18 | -------------------------------------------------------------------------------- /apps/consumer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "consumer", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "^1.18.3", 10 | "cookie-parser": "~1.4.3", 11 | "debug": "~2.2.0", 12 | "express": "^4.16.3", 13 | "express-session": "^1.15.6", 14 | "jade": "~1.11.0", 15 | "morgan": "^1.9.0", 16 | "node-fetch": "^2.2.0", 17 | "passport": "^0.4.0", 18 | "passport-oauth2": "^1.4.0", 19 | "passport-oauth2-refresh": "^1.1.0", 20 | "serve-favicon": "^2.5.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /apps/resource-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "resource-server", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.15.1", 10 | "cookie-parser": "~1.4.3", 11 | "debug": "~2.2.0", 12 | "express": "^4.16.3", 13 | "express-basic-auth": "^1.1.5", 14 | "express-jwt": "^5.3.1", 15 | "jade": "~1.11.0", 16 | "jwks-rsa": "^1.3.0", 17 | "morgan": "~1.7.0", 18 | "node-fetch": "^2.2.0", 19 | "serve-favicon": "~2.3.0", 20 | "simple-oauth2": "^1.6.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /scripts/services/hydra.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | source "$( dirname "${BASH_SOURCE[0]}" )/../helper/getid.sh" 6 | 7 | path=$1 8 | client_path="$path/clients/*.json" 9 | 10 | echo "Deleting clients in $client_path..." 11 | for filename in $client_path; do 12 | id=$(getclientid $filename) 13 | (set -x; hydra clients delete $id || true) 14 | done 15 | echo "Deleted all clients in $client_path!" 16 | 17 | echo "Importing clients in $client_path..." 18 | for filename in $client_path; do 19 | (set -x; hydra clients import $filename) 20 | done 21 | echo "Imported all clients in $client_path!" 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | --- 5 | 6 | **Is your feature request related to a problem? Please describe.** A clear and 7 | concise description of what the problem is. Ex. I'm always frustrated when [...] 8 | 9 | **Describe the solution you'd like** A clear and concise description of what you 10 | want to happen. 11 | 12 | **Describe alternatives you've considered** A clear and concise description of 13 | any alternative solutions or features you've considered. 14 | 15 | **Additional context** Add any other context or screenshots about the feature 16 | request here. 17 | -------------------------------------------------------------------------------- /full-stack/config/hydra/clients/consumer-app.json: -------------------------------------------------------------------------------- 1 | { 2 | "client_id": "consumer-app", 3 | "description": "This client will be used by the exemplary consumer application.", 4 | "client_secret": "consumer-secret", 5 | "scope": "openid offline articles.read", 6 | "grant_types": [ 7 | "authorization_code", 8 | "refresh_token" 9 | ], 10 | "response_types": [ 11 | "code", 12 | "id_token" 13 | ], 14 | "token_endpoint_auth_method": "client_secret_post", 15 | "redirect_uris": [ 16 | "http://localhost:4477/auth/callback", 17 | "http://consumer.localhost:4477/auth/callback", 18 | "http://127.0.0.1:4477/auth/callback" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ory-examples", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "test:watch": "cypress open cypress/integration/full-stack/*", 6 | "test:full-stack": "cypress run --spec cypress/integration/full-stack/*", 7 | "wait": "wait-on" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/ory/examples.git" 12 | }, 13 | "author": "", 14 | "bugs": { 15 | "url": "https://github.com/ory/examples/issues" 16 | }, 17 | "homepage": "https://github.com/ory/examples#readme", 18 | "devDependencies": { 19 | "cypress": "3.1.5" 20 | }, 21 | "dependencies": { 22 | "wait-on": "^2.1.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /scripts/helper/retry.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function backoff { 4 | local max_attempts=${ATTEMPTS-5} 5 | local timeout=${TIMEOUT-1} 6 | local attempt=0 7 | local exitCode=0 8 | 9 | while (( $attempt < $max_attempts )) 10 | do 11 | set +e 12 | "$@" 13 | exitCode=$? 14 | set -e 15 | 16 | if [[ $exitCode == 0 ]] 17 | then 18 | break 19 | fi 20 | 21 | echo "Failure! Retrying in $timeout.." 1>&2 22 | sleep $timeout 23 | attempt=$(( attempt + 1 )) 24 | timeout=$(( timeout * 2 )) 25 | done 26 | 27 | if [[ $exitCode != 0 ]] 28 | then 29 | echo "You've failed me for the last time! ($@)" 1>&2 30 | fi 31 | 32 | return $exitCode 33 | } 34 | -------------------------------------------------------------------------------- /cypress/integration/common.js: -------------------------------------------------------------------------------- 1 | export const checkApi = (url, key, items) => fetch(url) 2 | .then((res) => res.json()) 3 | .then((body) => Promise.resolve(body.map((b) => b[key]).filter((id) => items.indexOf(id) === -1))) 4 | 5 | export const urls = { 6 | hydraAdmin: `http://${Cypress.env('HYDRA_ADMIN_HOST') || 'localhost'}:${Cypress.env('HYDRA_ADMIN_PORT') || 4445}`, 7 | oathkeeperApi: `http://${Cypress.env('OATHKEEPER_API_HOST') || 'localhost'}:${Cypress.env('OATHKEEPER_API_PORT') || 4456}`, 8 | keto: `http://${Cypress.env('KETO_HOST') || 'localhost'}:${Cypress.env('KETO_PORT') || 4466}`, 9 | consumer: `http://${Cypress.env('CONSUMER_HOST')|| 'localhost'}:${Cypress.env('CONSUMER_PORT') || 4477}` 10 | } 11 | -------------------------------------------------------------------------------- /apps/resource-server/routes/oathkeeper.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const jwt = require('express-jwt'); 3 | const router = express.Router(); 4 | const jwksRsa = require('jwks-rsa'); 5 | 6 | // For infos check https://github.com/auth0/node-jwks-rsa/tree/master/examples/express-demo 7 | const client = jwksRsa.expressJwtSecret({ 8 | cache: true, 9 | rateLimit: true, 10 | jwksUri: process.env.OATHKEEPER_KEY_URL, 11 | algorithms: ['RS256'] 12 | }); 13 | 14 | router.get('/', jwt({ secret: client }), function (req, res, next) { 15 | res.json({ message: 'Congratulations, you gained access to this endpoint!', tokenClaims: req.user }); 16 | }); 17 | 18 | /* GET home page. */ 19 | module.exports = router; 20 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | module.exports = (on, config) => { 15 | // `on` is used to hook into various events Cypress emits 16 | // `config` is the resolved Cypress config 17 | } 18 | -------------------------------------------------------------------------------- /full-stack/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | stdout_logfile=/dev/stdout 3 | nodaemon=true 4 | user=root 5 | stdout_maxbytes=0 6 | 7 | [eventlistener:supervisord-watchdog] 8 | command=/usr/bin/python /scripts/supervisord-watchdog.py 9 | events=PROCESS_STATE_FATAL 10 | 11 | [eventlistener:stdout] 12 | command = supervisor_stdout 13 | buffer_size = 100 14 | events = PROCESS_LOG 15 | result_handler = supervisor_stdout:event_handler 16 | 17 | [program:configure] 18 | # environment= 19 | command=/bin/bash /scripts/configure.sh 20 | stdout_logfile=/dev/stdout 21 | stdout_events_enabled=true 22 | stderr_events_enabled=true 23 | redirect_stderr=true 24 | stdout_logfile_maxbytes = 0 25 | stderr_logfile_maxbytes = 0 26 | startretries = 5 27 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | --- 5 | 6 | **Describe the bug** 7 | 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | 12 | Steps to reproduce the behavior: 13 | 14 | 18 | 19 | **Expected behavior** 20 | 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Expected behavior** 24 | 25 | A clear and concise description of what you expected to happen. 26 | 27 | **Environment** 28 | 29 | - Version: v1.2.3, git sha hash 30 | - Environment: Debian, Docker, ... 31 | 32 | **Additional context** 33 | 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /cypress/integration/full-stack/spec_00_setup.js: -------------------------------------------------------------------------------- 1 | import { checkApi, urls } from "../common"; 2 | 3 | describe('environment', () => { 4 | it('should be able to fetch all the data from hydra', () => { 5 | expect(checkApi(`${urls.hydraAdmin}/clients`, 'client_id', ['subjects:hydra:clients:oathkeeper-client', 'consumer-app', 'example-auth-code', 'example-auth-code'])).to.be.empty 6 | }) 7 | it('should be able to fetch all the data from oathkeeper', () => { 8 | expect(checkApi(`${urls.oathkeeperApi}/rules`, 'id', ['resources:oathkeeper:rules:resource-server-oathkeeper'])).to.be.empty 9 | }) 10 | it('should be able to fetch all the data from keto', () => { 11 | expect(checkApi(`${urls.keto}/engines/acp/ory/exact/policies`, 'id', ['resources:keto:policies:peter-blog'])).to.be.empty 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /full-stack/config/oathkeeper/rules/resource-server.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "${OATHKEEPER_RESOURCE_PREFIX}rules:resource-server-oathkeeper", 4 | "upstream": { 5 | "url": "${RESOURCE_SERVER_URL}/" 6 | }, 7 | "match": { 8 | "url": "${OATHKEEPER_PROXY_URL}/oathkeeper", 9 | "methods": [ 10 | "GET" 11 | ] 12 | }, 13 | "authenticators": [ 14 | { 15 | "handler": "oauth2_introspection", 16 | "config": { 17 | "required_scope": [ 18 | "articles.read" 19 | ] 20 | } 21 | } 22 | ], 23 | "authorizer": { 24 | "handler": "keto_engine_acp_ory", 25 | "config": { 26 | "required_action": "blog:read", 27 | "required_resource": "blog:posts:1", 28 | "flavor": "exact" 29 | } 30 | }, 31 | "credentials_issuer": { 32 | "handler": "id_token", 33 | "config": { 34 | "aud": [] 35 | } 36 | } 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /scripts/supervisor-stdout.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | def write_stdout(s): 4 | sys.stdout.write(s) 5 | sys.stdout.flush() 6 | 7 | def write_stderr(s): 8 | sys.stderr.write(s) 9 | sys.stderr.flush() 10 | 11 | def main(): 12 | while 1: 13 | write_stdout('READY\n') # transition from ACKNOWLEDGED to READY 14 | line = sys.stdin.readline() # read header line from stdin 15 | headers = dict([ x.split(':') for x in line.split() ]) 16 | data = sys.stdin.read(int(headers['len'])) # read the event payload 17 | write_stdout('RESULT %s\n%s'%(len(data), data)) # transition from READY to ACKNOWLEDGED 18 | 19 | def event_handler(event, response): 20 | line, data = response.split('\n', 1) 21 | headers = dict([ x.split(':') for x in line.split() ]) 22 | lines = data.split('\n') 23 | prefix = '%s %s | '%(headers['processname'], headers['channel']) 24 | print '\n'.join([ prefix + l for l in lines ]) 25 | 26 | if __name__ == '__main__': 27 | main() 28 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | - [Security Policy](#security-policy) 6 | - [Supported Versions](#supported-versions) 7 | - [Reporting a Vulnerability](#reporting-a-vulnerability) 8 | 9 | 10 | 11 | # Security Policy 12 | 13 | ## Supported Versions 14 | 15 | We release patches for security vulnerabilities. Which versions are eligible 16 | receiving such patches depend on the CVSS v3.0 Rating: 17 | 18 | | CVSS v3.0 | Supported Versions | 19 | | --------- | ----------------------------------------- | 20 | | 9.0-10.0 | Releases within the previous three months | 21 | | 4.0-8.9 | Most recent release | 22 | 23 | ## Reporting a Vulnerability 24 | 25 | Please report (suspected) security vulnerabilities to 26 | **[security@ory.sh](mailto:security@ory.sh)**. You will receive a response from 27 | us within 48 hours. If the issue is confirmed, we will release a patch as soon 28 | as possible depending on complexity but historically within a few days. 29 | -------------------------------------------------------------------------------- /full-stack/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG HYDRA_VERSION 2 | ARG KETO_VERSION 3 | ARG OATHKEEPER_VERSION 4 | 5 | FROM oryd/hydra:$HYDRA_VERSION 6 | FROM oryd/keto:$KETO_VERSION 7 | FROM oryd/oathkeeper:$OATHKEEPER_VERSION 8 | 9 | FROM alpine:3.7 10 | 11 | ENV BUILD_DEPS="gettext" \ 12 | RUNTIME_DEPS="libintl python3 py-pip supervisor bash curl" 13 | 14 | RUN set -x && \ 15 | apk add --no-cache --update $RUNTIME_DEPS && \ 16 | apk add --virtual build_deps $BUILD_DEPS && \ 17 | cp /usr/bin/envsubst /usr/local/bin/envsubst && \ 18 | apk del build_deps 19 | 20 | RUN pip install --upgrade pip 21 | # RUN apk add --no-cache supervisor bash curl 22 | 23 | COPY --from=0 /usr/bin/hydra /usr/bin/hydra 24 | COPY --from=1 /usr/bin/keto /usr/bin/keto 25 | COPY --from=2 /usr/bin/oathkeeper /usr/bin/oathkeeper 26 | 27 | RUN pip install supervisor-stdout 28 | 29 | # The context of docker-compose is `../` which is why we need to cd back into full-stack 30 | ADD ./full-stack/config /config 31 | ADD ./full-stack/supervisord.conf /etc/supervisor/conf.d/supervisord.conf 32 | # But it allows us to include the scripts directly 33 | ADD ./scripts /scripts 34 | 35 | ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] 36 | -------------------------------------------------------------------------------- /scripts/services/keto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | source "$( dirname "${BASH_SOURCE[0]}" )/../helper/getid.sh" 6 | 7 | url=$1 8 | path=$2 9 | role_path="$path/roles/*.json" 10 | 11 | echo "Deleting roles in $role_path..." 12 | for filename in $role_path; do 13 | id=$(getid $filename) 14 | (set -x; keto engines acp ory roles delete --endpoint $url exact $id || true) 15 | done 16 | echo "Deleted all roles in $role_path!" 17 | 18 | echo "Importing roles in $role_path..." 19 | for filename in $role_path; do 20 | (set -x; keto engines acp ory roles import --endpoint $url exact $filename) 21 | done 22 | echo "Imported all roles in $role_path!" 23 | 24 | url=$1 25 | path=$2 26 | policy_path="$path/policies/*.json" 27 | 28 | echo "Deleting policies in $policy_path..." 29 | for filename in $policy_path; do 30 | id=$(getid $filename) 31 | (set -x; keto engines acp ory policies delete --endpoint $url exact $id || true) 32 | done 33 | echo "Deleted all policies in $policy_path!" 34 | 35 | echo "Importing policies in $policy_path..." 36 | for filename in $policy_path; do 37 | (set -x; keto engines acp ory policies import --endpoint $url exact $filename) 38 | done 39 | echo "Imported all policies in $policy_path!" 40 | -------------------------------------------------------------------------------- /scripts/configure.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | cd "$( dirname "${BASH_SOURCE[0]}" )/.." 6 | 7 | echo "Sourcing helpers..." 8 | source /scripts/helper/retry.sh 9 | source /scripts/helper/envsubstfiles.sh 10 | source /scripts/helper/getid.sh 11 | 12 | echo "Substituting environment variables" 13 | if [ -d "/config/hydra/clients/" ]; then 14 | envsubstfiles "/config/hydra/clients/*.json" 15 | fi 16 | if [ -d "/config/keto/policies/" ]; then 17 | envsubstfiles "/config/keto/policies/*.json" 18 | fi 19 | if [ -d "/config/oathkeeper/rules/" ]; then 20 | envsubstfiles "/config/oathkeeper/rules/*.json" 21 | fi 22 | 23 | echo "Executing bootstrap scripts..." 24 | 25 | hydra_url=${HYDRA_URL:=undefined} 26 | hydra_admin_url=${HYDRA_ADMIN_URL:=undefined} 27 | oathkeeper_url=${OATHKEEPER_API_URL:=undefined} 28 | keto_url=${KETO_URL:=undefined} 29 | 30 | export HYDRA_URL=${hydra_url%/}/ 31 | export HYDRA_ADMIN_URL=${hydra_admin_url%/}/ 32 | 33 | if [ -d "/config/hydra" ]; then 34 | backoff /scripts/services/hydra.sh "/config/hydra" 35 | fi 36 | 37 | if [ -d "/config/oathkeeper" ]; then 38 | backoff /scripts/services/oathkeeper.sh ${oathkeeper_url%/}/ "/config/oathkeeper" 39 | fi 40 | 41 | if [ -d "/config/keto" ]; then 42 | backoff /scripts/services/keto.sh ${keto_url%/}/ "/config/keto" 43 | fi 44 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 1 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 2 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: stale 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | Thank you for your contribution, we value it! However, this issue 14 | has been marked as stale because it has not had recent activity, or 15 | because it does not comply with our contribution guidelines. If it 16 | is the latter, please follow the instructions from the comment above 17 | to comply with our guidelines. 18 | It will be closed if no further activity occurs. Thank you for your 19 | contributions. 20 | # Comment to post when closing a stale issue. Set to `false` to disable 21 | closeComment: false 22 | 23 | # Set to true to ignore issues in a project (defaults to false) 24 | exemptProjects: false 25 | 26 | # Set to true to ignore issues in a milestone (defaults to false) 27 | exemptMilestones: false 28 | 29 | # Set to true to ignore issues with an assignee (defaults to false) 30 | exemptAssignees: false 31 | 32 | onlyLabels: 33 | - needs-context 34 | -------------------------------------------------------------------------------- /apps/consumer/views/articles/oauth2.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | h1= pageTitle 5 | p 6 | a(href="/") Go back 7 | 8 | p 9 | | This endpoint makes several requests to the backend ("resource server"). You can see the results below! 10 | 11 | hr 12 | div 13 | h2 Request with a valid Access Token 14 | p 15 | | You already performed an OAuth 2.0 Token. Therefore, this consumer application has an access token (it's #{accessToken}). 16 | | We're making a request to the backend and are including the token in the HTTP request header: 17 | h3 CURL Equivalent 18 | pre 19 | code curl -H "Authorization: bearer #{accessToken}" #{url} 20 | h3 HTTP Response Body 21 | pre#with 22 | code #{valid.body} 23 | hr 24 | div 25 | h2 Request without an Access Token 26 | p 27 | | This request will be done without an access token included. The result will be a 401 error. 28 | h3 CURL Equivalent 29 | pre 30 | code curl #{url} 31 | h3 HTTP Response Body 32 | pre#without 33 | code #{empty.body} 34 | hr 35 | div 36 | h2 Request with an invalid Access Token 37 | p 38 | | This request uses a random and invalid access token. The result will be a 401 error. 39 | h3 CURL Equivalent 40 | pre 41 | code curl -h "Authorization: invalid-token" #{url} 42 | h3 HTTP Response Body 43 | pre#invalid 44 | code #{invalid.body} 45 | hr 46 | -------------------------------------------------------------------------------- /apps/consumer/views/articles/subject.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | h1= pageTitle 5 | p 6 | a(href="/") Go back 7 | 8 | p 9 | | This endpoint makes several requests to the backend ("resource server"). You can see the results below! 10 | 11 | hr 12 | div 13 | h2 Request as user peter 14 | p 15 | | This consumer application makes a request with user #{peter.auth.username} and password #{peter.auth.password} 16 | | We're making a request to the backend and are including the HTTP Basic Authorization header: 17 | h3 CURL Equivalent 18 | pre 19 | code curl -u #{peter.auth.username}:#{peter.auth.password} #{url} 20 | h3 HTTP Response Body 21 | pre 22 | code#peter #{peter.body} 23 | hr 24 | div 25 | h2 Request as user bob 26 | p 27 | | This consumer application makes a request with user #{bob.auth.username} and password #{bob.auth.password} 28 | | However, bob is not allowed to access this resource, so the request will be denied: 29 | h3 CURL Equivalent 30 | pre 31 | code curl -u #{bob.auth.username}:#{bob.auth.password} #{url} 32 | h3 HTTP Response Body 33 | pre 34 | code#bob #{bob.body} 35 | hr 36 | div 37 | h2 Request as no user 38 | p 39 | | This consumer application makes a request without the HTTP Basic Authorization header: 40 | h3 CURL Equivalent 41 | pre 42 | code curl #{url} 43 | h3 HTTP Response Body 44 | pre 45 | code#empty #{empty.body} 46 | hr -------------------------------------------------------------------------------- /apps/resource-server/routes/introspect.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const fetch = require('node-fetch') 4 | const url = require('url') 5 | const qs = require('querystring') 6 | 7 | const introspect = (req, res, next) => { 8 | const body = qs.stringify({ token: req.get('Authorization').replace(/bearer\s/gi, '') }) 9 | return fetch(process.env.OAUTH2_INTROSPECT_URL, { 10 | headers: { 11 | 'Content-Type': 'application/x-www-form-urlencoded', 12 | 'Content-Length': body.length 13 | }, 14 | method: 'POST', body 15 | }) 16 | .then(res => res.ok ? res.json() : Promise.reject(new Error(res.statusText))) 17 | .then(body => { 18 | if (!body.active) { 19 | return next(new Error('Bearer token is not active')) 20 | } else if (body.token_type && body.token_type !== 'access_token') { 21 | // ORY Hydra also returns the token type (access_token or refresh_token). Other server's don't do that 22 | // but it will help us to make sure to only accept access tokens here, not refresh tokens 23 | return next(new Error('Bearer token is not an access token')) 24 | } 25 | 26 | req.user = body 27 | next() 28 | }) 29 | .catch(err => next(err)) 30 | } 31 | 32 | router.get('/', 33 | introspect, 34 | (req, res, next) => { 35 | res.json({ 36 | title: 'What an incredible blog post!', 37 | content: 'This blog post is so interesting, wow! By the way, you have full privileges to read this content as the request has been authorized. Isn\'t that just great? We\'ve even included the user data from the request here! Amazing!', 38 | author: 'Aeneas Rekkas', 39 | user: req.user 40 | }) 41 | }) 42 | 43 | module.exports = router 44 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DOCKER_VERSION := $(shell docker --version 2>/dev/null) 2 | DOCKER_COMPOSE_VERSION := $(shell docker-compose --version 2>/dev/null) 3 | 4 | ENV_BROWSER_HYDRA_HOST ?= localhost 5 | ENV_BROWSER_CONSUMER_HOST ?= localhost 6 | ENV_BROWSER_IDP_HOST ?= localhost 7 | ENV_BROWSER_OATHKEEPER_PROXY_HOST ?= localhost 8 | 9 | ENV_HYDRA_VERSION ?= v1.0.9 10 | ENV_KETO_VERSION ?= v0.2.2-sandbox_oryOS.10 11 | ENV_OATHKEEPER_VESRION ?= v0.14.2_oryOS.10 12 | ENV_LOGIN_CONSENT_VERSION ?= v1.0.8 13 | 14 | all: 15 | ifndef DOCKER_VERSION 16 | $(error "command docker is not available, please install Docker") 17 | endif 18 | ifndef DOCKER_COMPOSE_VERSION 19 | $(error "command docker-compose is not available, please install Docker") 20 | endif 21 | 22 | export LOGIN_CONSENT_VERSION=${ENV_LOGIN_CONSENT_VERSION} 23 | export HYDRA_VERSION=${ENV_HYDRA_VERSION} 24 | export OATHKEEPER_VERSION=${ENV_OATHKEEPER_VESRION} 25 | export KETO_VERSION=${ENV_KETO_VERSION} 26 | 27 | export BROWSER_HYDRA_HOST=${ENV_BROWSER_HYDRA_HOST} 28 | export BROWSER_CONSUMER_HOST=${ENV_BROWSER_CONSUMER_HOST} 29 | export BROWSER_IDP_HOST=${ENV_BROWSER_IDP_HOST} 30 | export BROWSER_OATHKEEPER_PROXY_HOST=${ENV_BROWSER_OATHKEEPER_PROXY_HOST} 31 | 32 | build-dev: 33 | docker build -t oryd/hydra:dev ${GOPATH}/src/github.com/ory/hydra/ 34 | docker build -t oryd/oathkeeper:dev ${GOPATH}/src/github.com/ory/oathkeeper/ 35 | docker build -t oryd/keto:dev ${GOPATH}/src/github.com/ory/keto/ 36 | 37 | ### 38 | 39 | start-full-stack: 40 | cd full-stack; docker-compose up --build -d 41 | 42 | restart-full-stack: 43 | cd full-stack; docker-compose restart 44 | 45 | rm-full-stack: 46 | cd full-stack; docker-compose kill 47 | cd full-stack; docker-compose rm -f 48 | 49 | reset-full-stack: rm-full-stack start-full-stack 50 | -------------------------------------------------------------------------------- /apps/consumer/views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | p 5 | | This is the front page of the consumer app. There are several example included in this app: 6 | ul 7 | if (backends.oathkeeper) 8 | li 9 | a#oathkeeper(href="/articles/secure-backend-with-ory-oathkeeper") 10 | code /articles/secure-backend-with-ory-oathkeeper 11 | | : Opening this article will first request an OAuth 2.0 Access Token from you and then make requests 12 | | to the backend ("resource server") which is protected with ORY Oathkeeper. The backend will return 13 | | some data this app then renders. 14 | if (backends.introspect) 15 | li 16 | a#introspection(href="/articles/secure-backend-with-oauth2-token-introspection") 17 | code /articles/secure-backend-with-oauth2-token-introspection 18 | | : Opening this article will first request an OAuth 2.0 Access Token from you and then make requests 19 | | to the backend ("resource server"). The backend performs the OAuth 2.0 Token Introspection flow to validate 20 | | the access token and, if the token is valid, will return some data. 21 | if (backends.warden.subject) 22 | li 23 | a#keto(href="/articles/secure-backend-with-ory-keto") 24 | code /articles/secure-backend-with-ory-keto 25 | | : Opening this article will make a request to the backend ("resource server"). The resource server 26 | | uses the ORY Keto API to check if the user (taken from the query, e.g. /articles/secure-backend-with-ory-keto?user=alice) 27 | | is allowed to perform this action. 28 | -------------------------------------------------------------------------------- /scripts/supervisor-watchdog.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import logging 4 | import subprocess 5 | import time 6 | 7 | from supervisor.childutils import listener 8 | 9 | def main(args): 10 | logging.basicConfig(stream=sys.stderr, level=logging.DEBUG, format='%(asctime)s %(levelname)s %(filename)s: %(message)s') 11 | logger = logging.getLogger("supervisord-watchdog") 12 | debug_mode = True if 'DEBUG' in os.environ else False 13 | 14 | while True: 15 | logger.info("Listening for events...") 16 | headers, body = listener.wait(sys.stdin, sys.stdout) 17 | body = dict([pair.split(":") for pair in body.split(" ")]) 18 | 19 | logger.debug("Headers: %r", repr(headers)) 20 | logger.debug("Body: %r", repr(body)) 21 | logger.debug("Args: %r", repr(args)) 22 | 23 | if debug_mode: continue 24 | 25 | try: 26 | if headers["eventname"] == "PROCESS_STATE_FATAL": 27 | logger.info("Process entered FATAL state...") 28 | if not args or body["processname"] in args: 29 | logger.error("Killing off supervisord instance ...") 30 | res = subprocess.call(["/bin/kill", "-15", "1"], stdout=sys.stderr) 31 | logger.info("Sent TERM signal to init process") 32 | time.sleep( 5 ) 33 | logger.critical("Why am I still alive? Send KILL to all processes...") 34 | res = subprocess.call(["/bin/kill", "-9", "-1"], stdout=sys.stderr) 35 | except Exception as e: 36 | logger.critical("Unexpected Exception: %s", str(e)) 37 | listener.fail(sys.stdout) 38 | exit(1) 39 | else: 40 | listener.ok(sys.stdout) 41 | 42 | if __name__ == '__main__': 43 | main(sys.argv[1:]) 44 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:10.9 11 | 12 | # Specify service dependencies here if necessary 13 | # CircleCI maintains a library of pre-built images 14 | # documented at https://circleci.com/docs/2.0/circleci-images/ 15 | # - image: circleci/mongo:3.4.4 16 | 17 | working_directory: ~/examples 18 | 19 | steps: 20 | - checkout 21 | - setup_remote_docker: 22 | version: 17.10.0-ce 23 | - run: make BROWSER_HYDRA_HOST=hydra BROWSER_CONSUMER_HOST=consumer.localhost BROWSER_IDP_HOST=identity-provider BROWSER_OATHKEEPER_PROXY_HOST=oathkeeper-proxy start-full-stack 24 | - run: docker build -t cypress-runner -f cypress/Dockerfile . 25 | - run: docker run --rm -it --network full-stack_intranet cypress-runner npm run wait http-get://hydra:4445/health/ready 26 | - run: docker run --rm -it --network full-stack_intranet cypress-runner npm run wait http-get://oathkeeper-api:4456/health/ready 27 | # - run: docker run --rm -it --network full-stack_intranet cypress-runner npm run wait http-get://oathkeeper-proxy:4455/ 28 | - run: docker run --rm -it --network full-stack_intranet cypress-runner npm run wait http-get://keto:4466/health/ready 29 | - run: docker run --rm -it --network full-stack_intranet cypress-runner npm run wait http-get://consumer.localhost:4477/ 30 | - run: sleep 30 31 | - run: docker run -e CYPRESS_OATHKEEPER_API_HOST=oathkeeper-api -e CYPRESS_KETO_HOST=keto -e CYPRESS_CONSUMER_HOST=consumer.localhost -e CYPRESS_HYDRA_ADMIN_HOST=hydra -it --network full-stack_intranet cypress-runner npm run test:full-stack 32 | -------------------------------------------------------------------------------- /apps/resource-server/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const favicon = require('serve-favicon'); 4 | const logger = require('morgan'); 5 | const cookieParser = require('cookie-parser'); 6 | const bodyParser = require('body-parser'); 7 | 8 | const oathkeeper = require('./routes/oathkeeper'); 9 | const introspect = require('./routes/introspect'); 10 | const keto = require('./routes/keto'); 11 | 12 | const app = express(); 13 | 14 | // view engine setup 15 | app.set('views', path.join(__dirname, 'views')); 16 | app.set('view engine', 'jade'); 17 | 18 | // uncomment after placing your favicon in /public 19 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 20 | app.use(logger('dev')); 21 | app.use(bodyParser.json()); 22 | app.use(bodyParser.urlencoded({ extended: false })); 23 | app.use(cookieParser()); 24 | app.use(express.static(path.join(__dirname, 'public'))); 25 | 26 | app.use('/oathkeeper', oathkeeper); 27 | app.use('/introspect', introspect); 28 | app.use('/keto', keto); 29 | 30 | // catch 404 and forward to error handler 31 | app.use(function (req, res, next) { 32 | const err = new Error('Not Found'); 33 | err.status = 404; 34 | next(err); 35 | }); 36 | 37 | // error handlers 38 | 39 | // development error handler 40 | // will print stacktrace 41 | if (app.get('env') === 'development') { 42 | app.use(function (err, req, res, next) { 43 | console.error(err) 44 | res.status(err.status || 500); 45 | res.render('error', { 46 | message: err.message, 47 | error: err 48 | }); 49 | }); 50 | } 51 | 52 | // production error handler 53 | // no stacktraces leaked to user 54 | app.use(function (err, req, res, next) { 55 | console.error(err) 56 | res.status(err.status || 500); 57 | res.render('error', { 58 | message: err.message, 59 | error: {} 60 | }); 61 | }); 62 | 63 | 64 | module.exports = app; 65 | -------------------------------------------------------------------------------- /apps/resource-server/routes/keto.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const fetch = require('node-fetch') 4 | const basicAuth = require('express-basic-auth') 5 | 6 | const wardenSubject = ({ resource, action }) => (req, res, next) => { 7 | return fetch(process.env.KETO_URL + '/engines/acp/ory/exact/allowed', { 8 | headers: { 9 | 'Content-Type': 'application/json' 10 | }, 11 | method: 'POST', 12 | body: JSON.stringify({ resource, action, subject: req.auth.user }) 13 | }) 14 | .then(res => res.ok ? res.json() : Promise.reject(new Error(res.statusText))) 15 | .then(body => { 16 | if (!body.allowed) { 17 | return next(new Error('Request was not allowed')) 18 | } 19 | 20 | req.user = body 21 | next() 22 | }) 23 | .catch(err => next(err)) 24 | } 25 | 26 | router.get('/subject', 27 | // Let's add basic auth here 28 | basicAuth({ 29 | users: { 30 | 'peter': 'password1', 31 | 'bob': 'password2' 32 | }, 33 | unauthorizedResponse: (req) => req.auth ? ('Credentials ' + req.auth.user + ':' + req.auth.password + ' rejected') : 'No credentials provided' 34 | }), 35 | // This middleware takes the username from the basic auth to perform the warden request. 36 | // 37 | // We tell the warden that the resource is blog:posts:2 and the action blog:read. 38 | wardenSubject({ 39 | resource: 'blog:posts:2', 40 | action: 'blog:read' 41 | }), 42 | (req, res, next) => { 43 | res.json({ 44 | title: 'What an incredible blog post!', 45 | content: 'This blog post is so interesting, wow! By the way, you have full privileges to read this content as the request has been authorized. Isn\'t that just great? We\'ve even included the user data from the request here! Amazing!', 46 | author: 'Aeneas Rekkas', 47 | user: req.auth.user 48 | }) 49 | }) 50 | 51 | module.exports = router 52 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Related issue 2 | 3 | 13 | 14 | ## Proposed changes 15 | 16 | 19 | 20 | ## Checklist 21 | 22 | 26 | 27 | - [ ] I have read the [contributing guidelines](../blob/master/CONTRIBUTING.md) 28 | - [ ] I have read the [security policy](../security/policy) 29 | - [ ] I confirm that this pull request does not address a security 30 | vulnerability. If this pull request addresses a security vulnerability, I 31 | confirm that I got green light (please contact 32 | [security@ory.sh](mailto:security@ory.sh)) from the maintainers to push 33 | the changes. 34 | - [ ] I have added tests that prove my fix is effective or that my feature works 35 | - [ ] I have added necessary documentation within the code base (if appropriate) 36 | 37 | ## Further comments 38 | 39 | 43 | -------------------------------------------------------------------------------- /apps/resource-server/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('resource-server:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /apps/consumer/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('consumer:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | console.log('Listening on http://localhost:' + addr.port); 90 | } 91 | -------------------------------------------------------------------------------- /apps/consumer/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const path = require('path') 3 | const favicon = require('serve-favicon') 4 | const logger = require('morgan') 5 | const cookieParser = require('cookie-parser') 6 | const bodyParser = require('body-parser') 7 | const passport = require('passport') 8 | const session = require('express-session') 9 | 10 | const routes = require('./routes/index') 11 | 12 | const app = express() 13 | 14 | // view engine setup 15 | app.set('views', path.join(__dirname, 'views')) 16 | app.set('view engine', 'jade') 17 | 18 | // uncomment after placing your favicon in /public 19 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))) 20 | app.use(logger('dev')) 21 | app.use(bodyParser.json()) 22 | app.use(bodyParser.urlencoded({ extended: false })) 23 | app.use(cookieParser()) 24 | app.use(express.static(path.join(__dirname, 'public'))) 25 | 26 | // These are middlewares required by passport js 27 | app.use(session({ secret: 'keyboard cat', resave: false, saveUninitialized: true })) 28 | app.use(passport.initialize()) 29 | app.use(passport.session()) 30 | 31 | // Redirects from 127.0.0.1 to localhost which prevents https://github.com/ory/examples/issues/17 32 | app.use((req, res, next) => { 33 | if (req.hostname === '127.0.0.1') { 34 | res.redirect(req.protocol + '://' + req.get('Host').replace('127.0.0.1', 'localhost') + req.originalUrl, 302) 35 | return 36 | } 37 | next() 38 | }) 39 | 40 | app.use('/', routes) 41 | 42 | // catch 404 and forward to error handler 43 | app.use(function (req, res, next) { 44 | const err = new Error('Not Found') 45 | err.status = 404 46 | next(err) 47 | }) 48 | 49 | // error handlers 50 | 51 | // development error handler 52 | // will print stacktrace 53 | if (app.get('env') === 'development') { 54 | app.use(function (err, req, res, next) { 55 | console.error(err) 56 | res.status(err.status || 500) 57 | res.render('error', { 58 | message: err.message, 59 | error: err 60 | }) 61 | }) 62 | } 63 | 64 | // production error handler 65 | // no stacktraces leaked to user 66 | app.use(function (err, req, res, next) { 67 | console.error(err) 68 | res.status(err.status || 500) 69 | res.render('error', { 70 | message: err.message, 71 | error: {} 72 | }) 73 | }) 74 | 75 | 76 | module.exports = app 77 | -------------------------------------------------------------------------------- /cypress/integration/full-stack/spec_fullstack.js: -------------------------------------------------------------------------------- 1 | import { urls } from '../common' 2 | 3 | describe('full-stack', () => { 4 | 5 | it('completes the oathkeeper flow', () => { 6 | cy.clearCookies() 7 | cy.visit(urls.consumer) 8 | 9 | cy.get('#oathkeeper').click() 10 | 11 | cy.get('input[type="email"]').type("foo@bar.com") 12 | cy.get('input[type="password"]').type("foobar") 13 | cy.get('input[type="submit"]').click() 14 | 15 | cy.get('input[type="checkbox"]').click({ multiple: true }) 16 | cy.get('#remember').click() 17 | cy.get('input[value="Allow access"]').click() 18 | 19 | cy.get('#with').should('to.contain', `"message": "Congratulations, you gained access to this endpoint!",`) 20 | cy.get('#without').should('to.contain', `{"error":{"code":401,"status":"Unauthorized","message":"Access credentials are invalid"}}`) 21 | cy.get('#invalid').should('to.contain', `{"error":{"code":403,"status":"Forbidden","reason":"Access token introspection says token is not active","message":"Access credentials are not sufficient to access this resource"}}`) 22 | }) 23 | 24 | it('completes the introspection flow', () => { 25 | cy.clearCookies() 26 | cy.visit(urls.consumer) 27 | 28 | cy.get('#introspection').click() 29 | 30 | cy.get('input[type="email"]').type("foo@bar.com") 31 | cy.get('input[type="password"]').type("foobar") 32 | cy.get('input[type="submit"]').click() 33 | 34 | cy.get('input[type="checkbox"]').click({ multiple: true }) 35 | 36 | // We don't want to remember consent or re-running test will fail 37 | cy.get('#remember').click() 38 | cy.get('input[value="Allow access"]').click() 39 | 40 | cy.get('#with').should('to.contain', `"title": "What an incredible blog post!",`) 41 | cy.get('#without').should('to.contain', `

Bearer token is not active

`) 42 | cy.get('#invalid').should('to.contain', `

Bearer token is not active

`) 43 | }) 44 | 45 | it('completes the keto simple flow', () => { 46 | cy.clearCookies() 47 | cy.visit(urls.consumer) 48 | 49 | cy.get('#keto').click() 50 | 51 | cy.get('#peter').should('to.contain', `"title": "What an incredible blog post!",`) 52 | cy.get('#bob').should('to.contain', `

Request was not allowed

`) 53 | cy.get('#empty').should('to.contain', `No credentials provided`) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /.github/adopters/tw.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.github/adopters/3R-horiz.svg: -------------------------------------------------------------------------------- 1 | 4 | 6 | 14 | 21 | 29 | 36 | 39 | 41 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /.github/adopters/ordermygear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ORDER 22 | MY 23 | GEAR 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hi@ory.am. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /full-stack/README.md: -------------------------------------------------------------------------------- 1 | # Full Stack 2 | 3 | This boots up all services (ORY Oathkeeper, ORY Hydra, ORY Keto) and creates the respective database schemas. It also 4 | boots the exemplary consumer application and resource server. 5 | 6 | ## Running the Example 7 | 8 | Run this example with: 9 | 10 | ``` 11 | $ cd ..; make start-full-stack 12 | ``` 13 | 14 | Please be patient. In the background the system will boot a PostgreSQL database, execute SQL migrations for two services, then create 15 | several configuration items. This might take up to 5 minutes, depending on your system. While you wait or when having trouble, you may want 16 | to check `docker logs hydra-bc_postgresd_1`, `docker logs hydra-bc_keto-migrate_1`, `docker logs hydra-bc_oathkeeper-migrate_1`, `docker logs hydra-bc_hydra-migrate_1`, 17 | and `docker logs hydra-bc_services_1`. 18 | 19 | Once you are confident that everything is loaded (you're not seeing any error messages), try to run: 20 | 21 | ``` 22 | $ curl http://localhost:4445/clients 23 | $ curl http://localhost:4456/rules 24 | $ curl http://localhost:4466/engines/acp/ory/exact/policies 25 | $ curl http://localhost:4466/engines/acp/ory/exact/roles 26 | ``` 27 | 28 | You should see the preconfigured settings and no errors. 29 | 30 | To perform the OAuth 2 Authorize Code Flow, install ORY Hydra >= 1.0.0 locally and run: 31 | 32 | ``` 33 | $ hydra token user --client-id example-auth-code --client-secret secret --endpoint http://localhost:4444 --port 5555 34 | ``` 35 | 36 | Next, you should open [http://localhost:4477](http://localhost:4477) and check out the different examples. 37 | 38 | ## Architecture 39 | 40 | This example has three docker containers: 41 | 42 | * A PostgreSQL database for ORY Hydra, ORY Keto, ORY Oathkeeper. 43 | * Our reference [login and consent provider](https://github.com/ory/hydra-login-consent-node) exposed at port `3000`. 44 | * `hydra serve all --dangerous-force-http` which is exposed directly (without access control) at port `4444` an dport `4445`. 45 | * `oathkeeper serve proxy` which is exposed at port `4455`. 46 | * `oathkeeper serve api` exposed at port `4456`. This endpoint lets you manage ORY Oathkeeper if you need to. Be aware 47 | that this service is not configured to use the database. Every time you restart the container, you will have to redo 48 | all changes made. 49 | * `keto serve` exposed at port `4466` without access control. 50 | * A script that loads all configuration items from the `./config` directory and imports ORY Hydra OAuth 2.0 Clients, ORY Keto Access Control Policies, and 51 | ORY Oathkeeper Access Rules to each respective service. 52 | * The exemplary consumer application on port `4477` 53 | * The exemplary resource server on port `4478` 54 | 55 | If you intend to run a system based on this example in production, be aware that none of the ports (except the Oathkeeper Proxy) 56 | should be exposed directly to the open internet, as some of the endpoint expose administrative features. 57 | 58 | ## Configuration 59 | 60 | This set up loads several configuration files located in [./config](./config): 61 | 62 | * OAuth 2.0 Clients for ORY Hydra: 63 | * `consumer-app.json`: This client allows the [consumer app](../apps/consumer) to request an OAuth 2.0 Access Token 64 | from the end user. 65 | * `example-auth-code-flow.json`: This client allows you to run the `hydra token user --client-id example-auth-code --client-secret secret --endpoint http://localhost:4444` command. 66 | * `introspection-app.json`: This client allows the resource server to perform the OAuth 2.0 Token Introspection flow. 67 | * `oathkeeper.json`: This client allows ORY Oathkeeper to perform the OAuth 2.0 Token Introspection flow. 68 | * `keto.json`: This client allows ORY Keto to perform the OAuth 2.0 Token Introspection flow. 69 | * Access Control Policies for ORY Keto: 70 | * `blog-foobar.json`: This policy allows the user `foo@bar.com` (this is the user from the exemplary login provider) to 71 | perform `blog:read` on resource `blog:posts:1`. The goal of this policy is to show how the ORY Keto Warden API works 72 | when implemented at the [resource server](../apps/resource-server). 73 | * `blog-peter.json`: This is another policy for an exemplary user `peter` and is also used at the exemplary [resource server](../apps/resource-server). 74 | * Access Rules for ORY Oathkeeper: 75 | * `resource-server.json`: This access rule defines how the [`/oathkeeper` endpoint of the resource server](../apps/resource-server/routes/oathkeeper.js) 76 | is being accessed. 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to ORY Examples 2 | 3 | 4 | 5 | 6 | 7 | - [Introduction](#introduction) 8 | - [Contributing Code](#contributing-code) 9 | - [Disclosing vulnerabilities](#disclosing-vulnerabilities) 10 | - [Code Style](#code-style) 11 | - [Pull request procedure](#pull-request-procedure) 12 | - [Communication](#communication) 13 | - [Conduct](#conduct) 14 | 15 | 16 | 17 | ## Introduction 18 | 19 | Please note: We take ORY Examples's security and our users' trust very 20 | seriously. If you believe you have found a security issue in ORY Examples, 21 | please responsibly disclose by contacting us at hi@ory.sh. 22 | 23 | First: if you're unsure or afraid of anything, just ask or submit the issue or 24 | pull request anyways. You won't be yelled at for giving it your best effort. The 25 | worst that can happen is that you'll be politely asked to change something. We 26 | appreciate any sort of contributions, and don't want a wall of rules to get in 27 | the way of that. 28 | 29 | That said, if you want to ensure that a pull request is likely to be merged, 30 | talk to us! You can find out our thoughts and ensure that your contribution 31 | won't clash or be obviated by ORY Examples's normal direction. A great way to 32 | do this is via the [ORY Community](https://community.ory.sh/) or join the 33 | [ORY Chat](https://www.ory.sh/chat). 34 | 35 | ## Contributing Code 36 | 37 | Unless you are fixing a known bug, we **strongly** recommend discussing it with 38 | the core team via a GitHub issue or [in our chat](https://www.ory.sh/chat) 39 | before getting started to ensure your work is consistent with ORY Examples's 40 | roadmap and architecture. 41 | 42 | All contributions are made via pull request. Note that **all patches from all 43 | contributors get reviewed**. After a pull request is made other contributors 44 | will offer feedback, and if the patch passes review a maintainer will accept it 45 | with a comment. When pull requests fail testing, authors are expected to update 46 | their pull requests to address the failures until the tests pass and the pull 47 | request merges successfully. 48 | 49 | At least one review from a maintainer is required for all patches (even patches 50 | from maintainers). 51 | 52 | Reviewers should leave a "LGTM" comment once they are satisfied with the patch. 53 | If the patch was submitted by a maintainer with write access, the pull request 54 | should be merged by the submitter after review. 55 | 56 | ## Disclosing vulnerabilities 57 | 58 | Please disclose vulnerabilities exclusively to [hi@ory.am](mailto:hi@ory.am). Do 59 | not use GitHub issues. 60 | 61 | ## Code Style 62 | 63 | Please follow these guidelines when formatting source code: 64 | 65 | - Go code should match the output of `gofmt -s` 66 | 67 | ## Pull request procedure 68 | 69 | To make a pull request, you will need a GitHub account; if you are unclear on 70 | this process, see GitHub's documentation on 71 | [forking](https://help.github.com/articles/fork-a-repo) and 72 | [pull requests](https://help.github.com/articles/using-pull-requests). Pull 73 | requests should be targeted at the `master` branch. Before creating a pull 74 | request, go through this checklist: 75 | 76 | 1. Create a feature branch off of `master` so that changes do not get mixed up. 77 | 1. [Rebase](http://git-scm.com/book/en/Git-Branching-Rebasing) your local 78 | changes against the `master` branch. 79 | 1. Run the full project test suite with the `go test ./...` (or equivalent) 80 | command and confirm that it passes. 81 | 1. Run `gofmt -s` (if the project is written in Go). 82 | 1. Ensure that each commit has a subsystem prefix (ex: `controller:`). 83 | 84 | Pull requests will be treated as "review requests," and maintainers will give 85 | feedback on the style and substance of the patch. 86 | 87 | Normally, all pull requests must include tests that test your change. 88 | Occasionally, a change will be very difficult to test for. In those cases, 89 | please include a note in your commit message explaining why. 90 | 91 | ## Communication 92 | 93 | We use [Slack](https://www.ory.sh/chat). You are welcome to drop in and ask 94 | questions, discuss bugs, etc. 95 | 96 | ## Conduct 97 | 98 | Whether you are a regular contributor or a newcomer, we care about making this 99 | community a safe place for you and we've got your back. 100 | 101 | - We are committed to providing a friendly, safe and welcoming environment for 102 | all, regardless of gender, sexual orientation, disability, ethnicity, 103 | religion, or similar personal characteristic. 104 | - Please avoid using nicknames that might detract from a friendly, safe and 105 | welcoming environment for all. 106 | - Be kind and courteous. There is no need to be mean or rude. 107 | - We will exclude you from interaction if you insult, demean or harass anyone. 108 | In particular, we do not tolerate behavior that excludes people in socially 109 | marginalized groups. 110 | - Private harassment is also unacceptable. No matter who you are, if you feel 111 | you have been or are being harassed or made uncomfortable by a community 112 | member, please contact one of the channel ops or a member of the ORY 113 | Examples core team immediately. 114 | - Likewise any spamming, trolling, flaming, baiting or other attention-stealing 115 | behaviour is not welcome. 116 | 117 | We welcome discussion about creating a welcoming, safe, and productive 118 | environment for the community. If you have any questions, feedback, or concerns 119 | [please let us know](https://www.ory.sh/chat). 120 | -------------------------------------------------------------------------------- /.github/adopters/tulip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.github/adopters/arduino.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.github/adopters/hootsuite.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /apps/consumer/routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const refresh = require('passport-oauth2-refresh') 4 | const fetch = require('node-fetch') 5 | 6 | const passport = require('passport') 7 | const OAuth2Strategy = require('passport-oauth2') 8 | 9 | // We'll use Passort.js to request OAuth 2.0 Access tokens 10 | // 11 | // For more info, go to: 12 | // 13 | // - http://www.passportjs.org/ 14 | // - https://github.com/jaredhanson/passport-oauth2 15 | // - https://github.com/fiznool/passport-oauth2-refresh 16 | // 17 | // You can obviously use any OAuth 2.0 Library you want. Passport.js is quite popular in the Node ecosystem. 18 | passport.use('oauth2', new OAuth2Strategy({ 19 | authorizationURL: process.env.OAUTH2_AUTH_URL, 20 | tokenURL: process.env.OAUTH2_TOKEN_URL, 21 | clientID: process.env.OAUTH2_CLIENT_ID, 22 | clientSecret: process.env.OAUTH2_CLIENT_SECRET, 23 | callbackURL: process.env.OAUTH2_REDIRECT_URL, 24 | state: true, 25 | scope: ['offline', 'openid', 'articles.read'] 26 | }, 27 | (accessToken, refreshToken, profile, cb) => cb(null, { accessToken, profile }) 28 | )) 29 | passport.use('refresh', refresh) 30 | 31 | passport.serializeUser((user, done) => { 32 | done(null, JSON.stringify(user)) 33 | }) 34 | 35 | passport.deserializeUser((user, done) => { 36 | done(null, JSON.parse(user)) 37 | }) 38 | // End of Passport.js configuration 39 | 40 | // This configuration contains where each backend is located. If a backend is empty, we will not show the link to it on 41 | // the front page. 42 | // 43 | // This is useful because we're using this example app in a couple of different configurations. 44 | const backends = { 45 | oathkeeper: process.env.BACKEND_OATHKEEPER_URL, 46 | warden: { 47 | subject: process.env.BACKEND_WARDEN_SUBJECT_URL, 48 | }, 49 | introspect: process.env.BACKEND_INTROSPECT_URL 50 | } 51 | 52 | // This is a middleware that checks if the user is authenticated. It also remembers the URL so it can be used to 53 | // redirect to it after the user authenticated. 54 | const checkAuthentication = (req, res, next) => { 55 | // The `isAuthenticated` is available because of Passport.js 56 | if (!req.isAuthenticated()) { 57 | req.session.redirectTo = req.url 58 | res.redirect('/auth') 59 | return 60 | } 61 | next() 62 | } 63 | 64 | // A small helper function to make a request to the backend. It includes a bearer token in the request header. 65 | const makeBearerRequest = (url, authorization, response, next) => fetch(url, { 66 | headers: { Authorization: 'bearer ' + authorization } 67 | }).then((res) => res.ok ? res.json() : res.text()) 68 | .then((body) => { 69 | response.body = typeof body === 'string' ? body : JSON.stringify(body, null, 2) 70 | }) 71 | .catch(err => next(err)) 72 | 73 | const makeBasicRequest = (url, { username, password }, response, next) => fetch(url, { 74 | headers: { 75 | Authorization: 76 | (username + password).length ? 77 | 'basic ' + Buffer.from(username + ":" + password).toString('base64') : 78 | '' 79 | } 80 | }).then((res) => res.ok ? res.json() : res.text()) 81 | .then((body) => { 82 | response.body = typeof body === 'string' ? body : JSON.stringify(body, null, 2) 83 | }) 84 | .catch(err => next(err)) 85 | 86 | // This shows the home page. 87 | router.get('/', (req, res, next) => { 88 | res.render('index', { backends }) 89 | }) 90 | 91 | // This route makes several requests to the resource server. The accessed URL at the resource server is protected 92 | // using the OAuth 2.0 Token Introspection protocol. 93 | router.get('/articles/secure-backend-with-oauth2-token-introspection', 94 | checkAuthentication, 95 | async (req, res, next) => { 96 | const data = { 97 | pageTitle: 'This endpoint makes requests to a server secured using OAuth 2.0 Token Introspection', 98 | accessToken: req.user.accessToken, backends, 99 | valid: { body: '' }, invalid: { body: '' }, empty: { body: '' }, 100 | url: backends.introspect 101 | } 102 | 103 | // Let's make a request to the backend with the access token 104 | await makeBearerRequest(backends.introspect, req.user.accessToken, data.valid, next) 105 | 106 | // Let's make a request without a token 107 | await makeBearerRequest(backends.introspect, '', data.empty, next) 108 | 109 | // Let's make a request without a random (invalid) JSON Web Token 110 | await makeBearerRequest(backends.introspect, 'invalid-token', data.invalid, next) 111 | 112 | res.render('articles/oauth2', data) 113 | }) 114 | 115 | router.get('/articles/secure-backend-with-ory-oathkeeper', 116 | checkAuthentication, 117 | async (req, res, next) => { 118 | const data = { 119 | pageTitle: 'This endpoint makes requests to a server secured using ORY Oathkeeper', 120 | accessToken: req.user.accessToken, backends, 121 | valid: { body: '' }, invalid: { body: '' }, empty: { body: '' }, 122 | url: backends.oathkeeper 123 | } 124 | 125 | // Let's make a request to the backend with the access token 126 | await makeBearerRequest(backends.oathkeeper, req.user.accessToken, data.valid, next) 127 | 128 | // Let's make a request without a token 129 | await makeBearerRequest(backends.oathkeeper, '', data.empty, next) 130 | 131 | // Let's make a request without a random (invalid) bearer token 132 | await makeBearerRequest(backends.oathkeeper, 'invalid-token', data.invalid, next) 133 | 134 | res.render('articles/oauth2', data) 135 | }) 136 | 137 | // This route makes several requests to the resource server. The accessed URL at the resource server is protected 138 | // using the ORY Keto Warden API - and more specifically the Warden Subject Authorization. 139 | router.get('/articles/secure-backend-with-ory-keto', 140 | // Authentication is not required here because we will use basic authentication in the requests. 141 | // checkAuthentication, 142 | async (req, res, next) => { 143 | const data = { 144 | pageTitle: 'This endpoint makes requests to a server secured using HTTP Basic Auth and ORY Keto', 145 | backends, 146 | peter: { body: '', auth: { username: 'peter', password: 'password1' } }, 147 | bob: { body: '', auth: { username: 'bob', password: 'password2' } }, 148 | empty: { body: '', auth: { username: '', password: '' } }, 149 | url: backends.warden.subject 150 | } 151 | 152 | // Let's make a request as user peter 153 | await makeBasicRequest(backends.warden.subject, data.peter.auth, data.peter, next) 154 | 155 | // Let's make a request as user bob 156 | await makeBasicRequest(backends.warden.subject, data.bob.auth, data.bob, next) 157 | 158 | // Let's make a request without any username/password 159 | await makeBasicRequest(backends.warden.subject, data.empty.auth, data.empty, next) 160 | 161 | res.render('articles/subject', data) 162 | }) 163 | 164 | // This endpoint initializes the OAuth2 request 165 | router.get('/auth', passport.authenticate('oauth2')) 166 | 167 | // This endpoint handles OAuth2 requests (exchanges code for token) 168 | router.get('/auth/callback', passport.authenticate('oauth2'), (req, res, next) => { 169 | // After success, redirect to the page we came from originally 170 | res.redirect(req.session.redirectTo) 171 | }) 172 | 173 | module.exports = router 174 | -------------------------------------------------------------------------------- /.github/adopters/kyma.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 21 | -------------------------------------------------------------------------------- /.github/adopters/spiribo.svg: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ORY Ecosystem Deployment Examples 2 | 3 | [![CircleCI](https://circleci.com/gh/ory/examples.svg?style=shield)](https://circleci.com/gh/ory/examples) 4 | 5 | This repository contains deployment examples and templates for the ORY Ecosystem. This repository does not contain 6 | examples for the [ORY Editor](https://github.com/ory/editor), but ORY Hydra, ORY Oathkeeper, and ORY Keto. 7 | 8 | 9 | 10 | 11 | 12 | - [Overview](#overview) 13 | - [Scripts](#scripts) 14 | - [Examples](#examples) 15 | - [Apps](#apps) 16 | - [Resource Server](#resource-server) 17 | - [Consumer Application](#consumer-application) 18 | - [Development](#development) 19 | 20 | 21 | 22 | ## Overview 23 | 24 | Each example typically consists of these parts: 25 | 26 | - `docker-compose.yml`: The definition for docker-compose. 27 | - `supervisord.conf`: Configuration for `supervisord` which runs multiple services at once in one Docker container. 28 | - `config`: Contains configuration items (typically JSON files) for OAuth 2.0 Clients, Access Control Policies, and so on. 29 | - `Dockerfile`: A customized Dockerfile that is capable of running `supervisord` as well as each service. 30 | 31 | Please be aware that **you can't run multiple examples at once as ports will clash**. Use `make rm-` to 32 | kill and remove the containers of a running example before starting up another one. 33 | 34 | ### Scripts 35 | 36 | We wrote several helper scripts capable of: 37 | 38 | - Substituting environment variables in JSON files 39 | - Retrying statements on failure 40 | - Importing JSON files to the respective services (ORY Hydra, ORY Keto, ORY Oathkeeper) 41 | 42 | You will encounter several environment variables in each `docker-compose.yml` file. These are either used for the 43 | services directly (e.g. `HYDRA_DATABASE_URL`) or are used for variable substitution in the configuration files 44 | (e.g. `HYDRA_SUBJECT_PREFIX`). 45 | 46 | Typically, environment variables are prefixed with the service name they are used for - so `HYDRA_DATABASE_URL` is the 47 | `DATABASE_URL` environment variable for ORY Hydra. We use variable substitution in the `supervisord.conf` file to achieve that. 48 | 49 | ## Examples 50 | 51 | This repository provides several examples. Each example is documented in detail in the example's README. 52 | 53 | * [Full Stack](./full-stack): This example sets up all ORY services, the exemplary User Login and Consent 54 | Application, the exemplary OAuth 2.0 [Consumer Application](#consumer-application), and an exemplary [Resource Server](#resource-server) 55 | as well as example policies and OAuth 2.0 Clients. 56 | * [Basic ORY Hydra setup](./hydra): This example sets up ORY Hydra and our exemplary User Login and Consent Application. 57 | It is the minimal required set up for ORY Hydra which you can use to start experimenting. 58 | * [Backwards-compatible template](./hydra-bc): This example provides a Docker Image that offers a backwards compatible 59 | (for versions 0.11.0 / 0.10.0) experience by combining ORY Oathkeeper, ORY Keto, and ORY Hydra in the same Docker Image. 60 | 61 | ## Apps 62 | 63 | This repository contains two exemplary applications, both written in NodeJS with Express. The idea here is to show you the different ways you can 64 | authorize requests on both sides (consumer, resource server) and shows the difference in approaches of protecting your services 65 | with ORY Keto, ORY Oathkeeper, ORY Hydra, or any combination of the three. 66 | 67 | The application's code has been documented, and we encourage you to read it. Please note that almost all SDKs used (like 68 | Passport.js) are built on open standards such as OAuth 2.0. If you do not write applications in NodeJS you will be able 69 | to find SDKs with similar functionality in other languages. 70 | 71 | Please note that the code is making use of some [ES6 features](oauth2.jade), such as arrow functions, as well as 72 | async/await. Additionally, don't be fooled by ~100 Lines of Code. We packed everything in one file so you have a better 73 | time navigating the source code. The most interesting files will be the ones contained in the `routes` directory. 74 | All other files are either boilerplate ExpressJS or HTML views, with minimal changes to the ExpressJS middleware 75 | in each respective `./app.js` file. 76 | 77 | ### Resource Server 78 | 79 | A resource server is an application that, for example, exposes a CRUD API for modifying blog articles. 80 | Resource servers are usually protected - you don't want a hacker to be able to delete all your blog articles - 81 | and require valid credentials (authentication) as well as a certain permission (e.g. alice is allowed to modify this article) 82 | in order to execute the action. 83 | 84 | There are different types of credentials (Cookie, JSON Web Token, OAuth 2.0 Access Token, ...) that can be used to protect 85 | a resource server. Therefore, the [resource server](./apps/resource-server) has several different routes: 86 | 87 | * [/introspect](./apps/resource-server/routes/introspect.js): This route requires that an OAuth 2.0 Access Token 88 | is included in the HTTP header ([`Authorization: bearer `](https://tools.ietf.org/html/rfc6750)) and uses the 89 | [OAuth 2.0 Token Introspection](https://tools.ietf.org/html/rfc7662) flow to validate the token. 90 | * [/oathkeeper](./apps/resource-server/routes/oathkeeper.js): This route also accepts a bearer token 91 | ([`Authorization: bearer `](https://tools.ietf.org/html/rfc6750)) but this time it has to be a JSON Web Token 92 | signed by ORY Oathkeeper. 93 | * [/warden/](./apps/resource-server/routes/warden.js): This route uses the ORY Keto Warden API to check if a request 94 | is allowed to perform the request. It consists of two subroutes: 95 | * `/warden/access-token`: This endpoint requires an OAuth 2.0 Access Token in the HTTP header 96 | ([`Authorization: bearer `](https://tools.ietf.org/html/rfc6750)) and checks if the token's subject is allowed 97 | to perform the requested action using ORY Keto. 98 | * `/warden/subject`: This endpoint requires HTTP Basic Auth (`Authorization: basic ...`) and checks if the 99 | provided credentials match the username/password pairs (`peter:password1`, `bob:password2`) 100 | and if so, asks the ORY Keto Warden API if the user (e.g. `peter`, `bob`, `alice`) is allowed to perform the action. 101 | 102 | ### Consumer Application 103 | 104 | The [consumer application](./apps/consumer) is a web server that fetches data from the backend ("resource server") 105 | and displays it. In this particular case, the application makes requests to different [Resource Server](#resource-server) endpoints. 106 | 107 | The consumer application has several routes (e.g. `/articles/secure-backend-with-oauth2-token-introspection`) which use 108 | different endpoints at the [Resource Server](#resource-server). The idea here is to show you the different ways you can 109 | authorize requests on both sides (consumer, resource server). 110 | 111 | Some endpoints in the consumer application require a valid OAuth 2.0 Access Token from the user. When accessing one 112 | of those endpoints, you will be redirected to ORY Hydra and asked to login in and grant the application the required 113 | scopes. Make sure to **select all scopes** or the examples might not work. 114 | 115 | ## Development 116 | 117 | In case you wish to develop one of the projects and test them out with the examples here, first build the docker images 118 | for each project: 119 | 120 | ``` 121 | docker build -t oryd/hydra:dev $GOPATH/src/github.com/ory/hydra/ 122 | docker build -t oryd/oathkeeper:dev $GOPATH/src/github.com/ory/oathkeeper/ 123 | docker build -t oryd/keto:dev $GOPATH/src/github.com/ory/keto/ 124 | ``` 125 | 126 | then run Docker Compose in the example you would wish to test and set the version tags to `dev`: 127 | 128 | ``` 129 | $ cd some/example 130 | $ LOGIN_CONSENT_VERSION=v1.0.0-beta.2 HYDRA_VERSION=dev KETO_VERSION=dev OATHKEEPER_VERSION=dev docker-compose up --build -d 131 | ``` 132 | -------------------------------------------------------------------------------- /.github/adopters/allmyfunds.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /full-stack/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | 5 | hydra-migrate: 6 | image: oryd/hydra:$HYDRA_VERSION 7 | links: 8 | - postgresd:postgresd 9 | networks: 10 | - intranet 11 | environment: 12 | - LOG_LEVEL=debug 13 | - HYDRA_SYSTEM_SECRET=youReallyNeedToChangeThis 14 | command: 15 | migrate sql --yes postgres://dbuser:secret@postgresd:5432/accesscontroldb?sslmode=disable 16 | restart: on-failure 17 | 18 | keto-migrate: 19 | image: oryd/keto:$KETO_VERSION 20 | links: 21 | - postgresd:postgresd 22 | environment: 23 | - LOG_LEVEL=debug 24 | networks: 25 | - intranet 26 | command: 27 | migrate sql postgres://dbuser:secret@postgresd:5432/accesscontroldb?sslmode=disable 28 | restart: on-failure 29 | 30 | oathkeeper-migrate: 31 | image: oryd/oathkeeper:$OATHKEEPER_VERSION 32 | links: 33 | - postgresd:postgresd 34 | environment: 35 | - LOG_LEVEL=debug 36 | networks: 37 | - intranet 38 | command: 39 | migrate sql postgres://dbuser:secret@postgresd:5432/accesscontroldb?sslmode=disable 40 | restart: on-failure 41 | 42 | hydra: 43 | image: oryd/hydra:$HYDRA_VERSION 44 | links: 45 | - postgresd:postgresd 46 | ports: 47 | - "4444:4444" 48 | - "4445:4445" 49 | depends_on: 50 | - hydra-migrate 51 | command: 52 | serve all --dangerous-force-http 53 | networks: 54 | - intranet 55 | environment: 56 | - CORS_ENABLED=true 57 | - CORS_ALLOWED_METHODS=POST,GET,PUT,DELETE 58 | - LOG_LEVEL=debug 59 | - SYSTEM_SECRET=youReallyNeedToChangeThis 60 | - DATABASE_URL=postgres://dbuser:secret@postgresd:5432/accesscontroldb?sslmode=disable 61 | - OAUTH2_CONSENT_URL=http://$BROWSER_IDP_HOST:4488/consent 62 | - OAUTH2_LOGIN_URL=http://$BROWSER_IDP_HOST:4488/login 63 | - OAUTH2_ISSUER_URL=http://$BROWSER_HYDRA_HOST:4444 64 | - OAUTH2_SHARE_ERROR_DEBUG=1 65 | restart: on-failure 66 | 67 | oathkeeper-proxy: 68 | image: oryd/oathkeeper:$OATHKEEPER_VERSION 69 | links: 70 | - postgresd:postgresd 71 | ports: 72 | - "4455:4455" 73 | depends_on: 74 | - oathkeeper-api 75 | - hydra 76 | - keto 77 | command: 78 | serve proxy 79 | networks: 80 | - intranet 81 | environment: 82 | - LOG_LEVEL=debug 83 | - PORT=4455 84 | - ISSUER_URL=http://$BROWSER_OATHKEEPER_PROXY_HOST:4455/ 85 | - OATHKEEPER_API_URL=http://oathkeeper-api:4456 86 | - CREDENTIALS_ISSUER_ID_TOKEN_ALGORITHM=ory-hydra 87 | - CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_JWK_SET_ID=resources:hydra:jwk:oathkeeper 88 | - CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_ADMIN_URL=http://hydra:4445 89 | - CREDENTIALS_ISSUER_ID_TOKEN_LIFESPAN=1h 90 | - CREDENTIALS_ISSUER_ID_TOKEN_ISSUER=http://oathkeeper-api:4456 91 | - CREDENTIALS_ISSUER_ID_TOKEN_JWK_REFRESH_INTERVAL=30m 92 | - AUTHORIZER_KETO_URL=http://keto:4466 93 | - AUTHENTICATOR_ANONYMOUS_USERNAME=anonymous 94 | - AUTHENTICATOR_OAUTH2_INTROSPECTION_URL=http://hydra:4445/oauth2/introspect 95 | - AUTHENTICATOR_OAUTH2_CLIENT_CREDENTIALS_TOKEN_URL=http://hydra:4444/oauth2/token 96 | restart: on-failure 97 | 98 | oathkeeper-api: 99 | image: oryd/oathkeeper:$OATHKEEPER_VERSION 100 | links: 101 | - postgresd:postgresd 102 | ports: 103 | - "4456:4456" 104 | depends_on: 105 | - hydra-migrate 106 | command: 107 | serve api 108 | networks: 109 | - intranet 110 | environment: 111 | - CORS_ENABLED=true 112 | - CORS_ALLOWED_METHODS=POST,GET,PUT,DELETE 113 | - LOG_LEVEL=debug 114 | - PORT=4456 115 | - DATABASE_URL=postgres://dbuser:secret@postgresd:5432/accesscontroldb?sslmode=disable 116 | - ISSUER_URL=http://$BROWSER_OATHKEEPER_PROXY_HOST:4455/ 117 | - AUTHORIZER_KETO_URL=http://keto:4466 118 | - CREDENTIALS_ISSUER_ID_TOKEN_ALGORITHM=ory-hydra 119 | - CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_JWK_SET_ID=resources:hydra:jwk:oathkeeper 120 | - CREDENTIALS_ISSUER_ID_TOKEN_HYDRA_ADMIN_URL=http://hydra:4445 121 | - CREDENTIALS_ISSUER_ID_TOKEN_LIFESPAN=1h 122 | - CREDENTIALS_ISSUER_ID_TOKEN_ISSUER=http://oathkeeper-api:4456 123 | - CREDENTIALS_ISSUER_ID_TOKEN_JWK_REFRESH_INTERVAL=30m 124 | - AUTHENTICATOR_OAUTH2_INTROSPECTION_URL=http://hydra:4445/oauth2/introspect 125 | - AUTHENTICATOR_OAUTH2_CLIENT_CREDENTIALS_TOKEN_URL=http://hydra:4444/oauth2/token 126 | restart: on-failure 127 | 128 | keto: 129 | image: oryd/keto:$KETO_VERSION 130 | links: 131 | - postgresd:postgresd 132 | ports: 133 | - "4466:4466" 134 | depends_on: 135 | - hydra 136 | - keto-migrate 137 | networks: 138 | - intranet 139 | environment: 140 | - CORS_ENABLED=true 141 | - CORS_ALLOWED_METHODS=POST,GET,PUT,DELETE 142 | - LOG_LEVEL=debug 143 | - DATABASE_URL=postgres://dbuser:secret@postgresd:5432/accesscontroldb?sslmode=disable 144 | restart: on-failure 145 | 146 | configurator: 147 | build: 148 | context: ../ 149 | dockerfile: full-stack/Dockerfile 150 | args: 151 | - HYDRA_VERSION=$HYDRA_VERSION 152 | - KETO_VERSION=$KETO_VERSION 153 | - OATHKEEPER_VERSION=$OATHKEEPER_VERSION 154 | depends_on: 155 | - hydra 156 | - keto 157 | - oathkeeper-api 158 | networks: 159 | - intranet 160 | environment: 161 | # All of these URLs MUST NOT end with a trailing slash. This is very important! 162 | - HYDRA_URL=http://hydra:4444 163 | - HYDRA_ADMIN_URL=http://hydra:4445 164 | - KETO_URL=http://keto:4466 165 | - RESOURCE_SERVER_URL=http://resource-server:4478 166 | - OATHKEEPER_API_URL=http://oathkeeper-api:4456 167 | - OATHKEEPER_PROXY_URL=http://oathkeeper-proxy:4455 168 | 169 | # This sets the prefix for all resource, action, and subject names. Be aware that this prefix is automatically 170 | # applied to all OAuth2 Clients as well. 171 | - "HYDRA_SUBJECT_PREFIX=subjects:hydra:" 172 | - "HYDRA_RESOURCE_PREFIX=resources:hydra:" 173 | - "HYDRA_ACTION_PREFIX=actions:hydra:" 174 | - "OATHKEEPER_RESOURCE_PREFIX=resources:oathkeeper:" 175 | - "OATHKEEPER_ACTION_PREFIX=actions:oathkeeper:" 176 | - "KETO_RESOURCE_PREFIX=resources:keto:" 177 | - "KETO_ACTION_PREFIX=actions:keto:" 178 | 179 | - OATHKEEPER_HYDRA_JWK_SET_ID=jwk:oathkeeper 180 | - OATHKEEPER_HYDRA_CLIENT_ID=clients:oathkeeper-client 181 | - OATHKEEPER_HYDRA_CLIENT_SECRET=dummy-oathkeeper-secret 182 | restart: on-failure 183 | 184 | identity-provider: 185 | environment: 186 | - HYDRA_ADMIN_URL=http://hydra:4445 187 | - NODE_TLS_REJECT_UNAUTHORIZED=0 188 | - PORT=4488 189 | image: oryd/hydra-login-consent-node:$LOGIN_CONSENT_VERSION 190 | networks: 191 | - intranet 192 | ports: 193 | - "4488:4488" 194 | restart: on-failure 195 | 196 | # This ".localhost" hack allows us to use http:// in the tests, because hydra only allows http:// on callback urls 197 | # for localhost and *.localhost 198 | "consumer.localhost": 199 | build: 200 | context: ../apps/consumer 201 | dockerfile: Dockerfile 202 | networks: 203 | - intranet 204 | depends_on: 205 | - hydra 206 | - keto 207 | - oathkeeper-proxy 208 | ports: 209 | - "4477:4477" 210 | environment: 211 | - PORT=4477 212 | - OAUTH2_AUTH_URL=http://$BROWSER_HYDRA_HOST:4444/oauth2/auth 213 | - OAUTH2_TOKEN_URL=http://hydra:4444/oauth2/token 214 | - OAUTH2_CLIENT_ID=consumer-app 215 | - OAUTH2_CLIENT_SECRET=consumer-secret 216 | - OAUTH2_REDIRECT_URL=http://$BROWSER_CONSUMER_HOST:4477/auth/callback 217 | - BACKEND_OATHKEEPER_URL=http://oathkeeper-proxy:4455/oathkeeper 218 | - BACKEND_WARDEN_SUBJECT_URL=http://resource-server:4478/keto/subject 219 | - BACKEND_INTROSPECT_URL=http://resource-server:4478/introspect 220 | 221 | resource-server: 222 | build: 223 | context: ../apps/resource-server 224 | dockerfile: Dockerfile 225 | depends_on: 226 | - hydra 227 | - keto 228 | - oathkeeper-proxy 229 | networks: 230 | - intranet 231 | ports: 232 | - "4478:4478" 233 | environment: 234 | - PORT=4478 235 | - OATHKEEPER_KEY_URL=http://oathkeeper-api:4456/.well-known/jwks.json 236 | - OAUTH2_INTROSPECT_URL=http://hydra:4445/oauth2/introspect 237 | - KETO_URL=http://keto:4466 238 | 239 | postgresd: 240 | image: postgres:9.6 241 | networks: 242 | - intranet 243 | environment: 244 | - POSTGRES_USER=dbuser 245 | - POSTGRES_PASSWORD=secret 246 | - POSTGRES_DB=accesscontroldb 247 | 248 | networks: 249 | intranet: 250 | driver: bridge 251 | -------------------------------------------------------------------------------- /.github/adopters/segment.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /.github/adopters/raspi.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /apps/consumer/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "consumer", 3 | "version": "0.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.5", 9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", 10 | "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", 11 | "requires": { 12 | "mime-types": "~2.1.18", 13 | "negotiator": "0.6.1" 14 | } 15 | }, 16 | "acorn": { 17 | "version": "2.7.0", 18 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", 19 | "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=" 20 | }, 21 | "acorn-globals": { 22 | "version": "1.0.9", 23 | "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz", 24 | "integrity": "sha1-VbtemGkVB7dFedBRNBMhfDgMVM8=", 25 | "requires": { 26 | "acorn": "^2.1.0" 27 | } 28 | }, 29 | "align-text": { 30 | "version": "0.1.4", 31 | "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", 32 | "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", 33 | "requires": { 34 | "kind-of": "^3.0.2", 35 | "longest": "^1.0.1", 36 | "repeat-string": "^1.5.2" 37 | } 38 | }, 39 | "amdefine": { 40 | "version": "1.0.1", 41 | "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", 42 | "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" 43 | }, 44 | "array-flatten": { 45 | "version": "1.1.1", 46 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 47 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 48 | }, 49 | "asap": { 50 | "version": "1.0.0", 51 | "resolved": "https://registry.npmjs.org/asap/-/asap-1.0.0.tgz", 52 | "integrity": "sha1-sqRdpf36ILBJb8N2jMJ8EvqRan0=" 53 | }, 54 | "basic-auth": { 55 | "version": "2.0.0", 56 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz", 57 | "integrity": "sha1-AV2z81PgLlY3d1X5YnQuiYHnu7o=", 58 | "requires": { 59 | "safe-buffer": "5.1.1" 60 | } 61 | }, 62 | "body-parser": { 63 | "version": "1.18.3", 64 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", 65 | "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", 66 | "requires": { 67 | "bytes": "3.0.0", 68 | "content-type": "~1.0.4", 69 | "debug": "2.6.9", 70 | "depd": "~1.1.2", 71 | "http-errors": "~1.6.3", 72 | "iconv-lite": "0.4.23", 73 | "on-finished": "~2.3.0", 74 | "qs": "6.5.2", 75 | "raw-body": "2.3.3", 76 | "type-is": "~1.6.16" 77 | }, 78 | "dependencies": { 79 | "debug": { 80 | "version": "2.6.9", 81 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 82 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 83 | "requires": { 84 | "ms": "2.0.0" 85 | } 86 | }, 87 | "ms": { 88 | "version": "2.0.0", 89 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 90 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 91 | } 92 | } 93 | }, 94 | "bytes": { 95 | "version": "3.0.0", 96 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 97 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 98 | }, 99 | "camelcase": { 100 | "version": "1.2.1", 101 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", 102 | "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" 103 | }, 104 | "center-align": { 105 | "version": "0.1.3", 106 | "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", 107 | "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", 108 | "requires": { 109 | "align-text": "^0.1.3", 110 | "lazy-cache": "^1.0.3" 111 | } 112 | }, 113 | "character-parser": { 114 | "version": "1.2.1", 115 | "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-1.2.1.tgz", 116 | "integrity": "sha1-wN3kqxgnE7kZuXCVmhI+zBow/NY=" 117 | }, 118 | "clean-css": { 119 | "version": "3.4.28", 120 | "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.28.tgz", 121 | "integrity": "sha1-vxlF6C/ICPVWlebd6uwBQA79A/8=", 122 | "requires": { 123 | "commander": "2.8.x", 124 | "source-map": "0.4.x" 125 | }, 126 | "dependencies": { 127 | "commander": { 128 | "version": "2.8.1", 129 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", 130 | "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", 131 | "requires": { 132 | "graceful-readlink": ">= 1.0.0" 133 | } 134 | } 135 | } 136 | }, 137 | "cliui": { 138 | "version": "2.1.0", 139 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", 140 | "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", 141 | "requires": { 142 | "center-align": "^0.1.1", 143 | "right-align": "^0.1.1", 144 | "wordwrap": "0.0.2" 145 | }, 146 | "dependencies": { 147 | "wordwrap": { 148 | "version": "0.0.2", 149 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", 150 | "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" 151 | } 152 | } 153 | }, 154 | "commander": { 155 | "version": "2.6.0", 156 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.6.0.tgz", 157 | "integrity": "sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0=" 158 | }, 159 | "constantinople": { 160 | "version": "3.0.2", 161 | "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.0.2.tgz", 162 | "integrity": "sha1-S5RdmTeQe82Y7ldRIsOBdRZUQUE=", 163 | "requires": { 164 | "acorn": "^2.1.0" 165 | } 166 | }, 167 | "content-disposition": { 168 | "version": "0.5.2", 169 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 170 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 171 | }, 172 | "content-type": { 173 | "version": "1.0.4", 174 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 175 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 176 | }, 177 | "cookie": { 178 | "version": "0.3.1", 179 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 180 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 181 | }, 182 | "cookie-parser": { 183 | "version": "1.4.3", 184 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz", 185 | "integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=", 186 | "requires": { 187 | "cookie": "0.3.1", 188 | "cookie-signature": "1.0.6" 189 | } 190 | }, 191 | "cookie-signature": { 192 | "version": "1.0.6", 193 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 194 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 195 | }, 196 | "crc": { 197 | "version": "3.4.4", 198 | "resolved": "https://registry.npmjs.org/crc/-/crc-3.4.4.tgz", 199 | "integrity": "sha1-naHpgOO9RPxck79as9ozeNheRms=" 200 | }, 201 | "css": { 202 | "version": "1.0.8", 203 | "resolved": "https://registry.npmjs.org/css/-/css-1.0.8.tgz", 204 | "integrity": "sha1-k4aBHKgrzMnuf7WnMrHioxfIo+c=", 205 | "requires": { 206 | "css-parse": "1.0.4", 207 | "css-stringify": "1.0.5" 208 | } 209 | }, 210 | "css-parse": { 211 | "version": "1.0.4", 212 | "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.0.4.tgz", 213 | "integrity": "sha1-OLBQP7+dqfVOnB29pg4UXHcRe90=" 214 | }, 215 | "css-stringify": { 216 | "version": "1.0.5", 217 | "resolved": "https://registry.npmjs.org/css-stringify/-/css-stringify-1.0.5.tgz", 218 | "integrity": "sha1-sNBClG2ylTu50pKQCmy19tASIDE=" 219 | }, 220 | "debug": { 221 | "version": "2.2.0", 222 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", 223 | "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", 224 | "requires": { 225 | "ms": "0.7.1" 226 | } 227 | }, 228 | "decamelize": { 229 | "version": "1.2.0", 230 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 231 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" 232 | }, 233 | "depd": { 234 | "version": "1.1.2", 235 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 236 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 237 | }, 238 | "destroy": { 239 | "version": "1.0.4", 240 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 241 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 242 | }, 243 | "ee-first": { 244 | "version": "1.1.1", 245 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 246 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 247 | }, 248 | "encodeurl": { 249 | "version": "1.0.2", 250 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 251 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 252 | }, 253 | "escape-html": { 254 | "version": "1.0.3", 255 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 256 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 257 | }, 258 | "etag": { 259 | "version": "1.8.1", 260 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 261 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 262 | }, 263 | "express": { 264 | "version": "4.16.3", 265 | "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", 266 | "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", 267 | "requires": { 268 | "accepts": "~1.3.5", 269 | "array-flatten": "1.1.1", 270 | "body-parser": "1.18.2", 271 | "content-disposition": "0.5.2", 272 | "content-type": "~1.0.4", 273 | "cookie": "0.3.1", 274 | "cookie-signature": "1.0.6", 275 | "debug": "2.6.9", 276 | "depd": "~1.1.2", 277 | "encodeurl": "~1.0.2", 278 | "escape-html": "~1.0.3", 279 | "etag": "~1.8.1", 280 | "finalhandler": "1.1.1", 281 | "fresh": "0.5.2", 282 | "merge-descriptors": "1.0.1", 283 | "methods": "~1.1.2", 284 | "on-finished": "~2.3.0", 285 | "parseurl": "~1.3.2", 286 | "path-to-regexp": "0.1.7", 287 | "proxy-addr": "~2.0.3", 288 | "qs": "6.5.1", 289 | "range-parser": "~1.2.0", 290 | "safe-buffer": "5.1.1", 291 | "send": "0.16.2", 292 | "serve-static": "1.13.2", 293 | "setprototypeof": "1.1.0", 294 | "statuses": "~1.4.0", 295 | "type-is": "~1.6.16", 296 | "utils-merge": "1.0.1", 297 | "vary": "~1.1.2" 298 | }, 299 | "dependencies": { 300 | "body-parser": { 301 | "version": "1.18.2", 302 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", 303 | "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", 304 | "requires": { 305 | "bytes": "3.0.0", 306 | "content-type": "~1.0.4", 307 | "debug": "2.6.9", 308 | "depd": "~1.1.1", 309 | "http-errors": "~1.6.2", 310 | "iconv-lite": "0.4.19", 311 | "on-finished": "~2.3.0", 312 | "qs": "6.5.1", 313 | "raw-body": "2.3.2", 314 | "type-is": "~1.6.15" 315 | } 316 | }, 317 | "debug": { 318 | "version": "2.6.9", 319 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 320 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 321 | "requires": { 322 | "ms": "2.0.0" 323 | } 324 | }, 325 | "iconv-lite": { 326 | "version": "0.4.19", 327 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", 328 | "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" 329 | }, 330 | "ms": { 331 | "version": "2.0.0", 332 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 333 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 334 | }, 335 | "qs": { 336 | "version": "6.5.1", 337 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 338 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" 339 | }, 340 | "raw-body": { 341 | "version": "2.3.2", 342 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", 343 | "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", 344 | "requires": { 345 | "bytes": "3.0.0", 346 | "http-errors": "1.6.2", 347 | "iconv-lite": "0.4.19", 348 | "unpipe": "1.0.0" 349 | }, 350 | "dependencies": { 351 | "depd": { 352 | "version": "1.1.1", 353 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 354 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" 355 | }, 356 | "http-errors": { 357 | "version": "1.6.2", 358 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 359 | "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", 360 | "requires": { 361 | "depd": "1.1.1", 362 | "inherits": "2.0.3", 363 | "setprototypeof": "1.0.3", 364 | "statuses": ">= 1.3.1 < 2" 365 | } 366 | }, 367 | "setprototypeof": { 368 | "version": "1.0.3", 369 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 370 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 371 | } 372 | } 373 | }, 374 | "statuses": { 375 | "version": "1.4.0", 376 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 377 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 378 | }, 379 | "utils-merge": { 380 | "version": "1.0.1", 381 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 382 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 383 | } 384 | } 385 | }, 386 | "express-session": { 387 | "version": "1.15.6", 388 | "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.15.6.tgz", 389 | "integrity": "sha512-r0nrHTCYtAMrFwZ0kBzZEXa1vtPVrw0dKvGSrKP4dahwBQ1BJpF2/y1Pp4sCD/0kvxV4zZeclyvfmw0B4RMJQA==", 390 | "requires": { 391 | "cookie": "0.3.1", 392 | "cookie-signature": "1.0.6", 393 | "crc": "3.4.4", 394 | "debug": "2.6.9", 395 | "depd": "~1.1.1", 396 | "on-headers": "~1.0.1", 397 | "parseurl": "~1.3.2", 398 | "uid-safe": "~2.1.5", 399 | "utils-merge": "1.0.1" 400 | }, 401 | "dependencies": { 402 | "debug": { 403 | "version": "2.6.9", 404 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 405 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 406 | "requires": { 407 | "ms": "2.0.0" 408 | } 409 | }, 410 | "ms": { 411 | "version": "2.0.0", 412 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 413 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 414 | }, 415 | "utils-merge": { 416 | "version": "1.0.1", 417 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 418 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 419 | } 420 | } 421 | }, 422 | "finalhandler": { 423 | "version": "1.1.1", 424 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", 425 | "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", 426 | "requires": { 427 | "debug": "2.6.9", 428 | "encodeurl": "~1.0.2", 429 | "escape-html": "~1.0.3", 430 | "on-finished": "~2.3.0", 431 | "parseurl": "~1.3.2", 432 | "statuses": "~1.4.0", 433 | "unpipe": "~1.0.0" 434 | }, 435 | "dependencies": { 436 | "debug": { 437 | "version": "2.6.9", 438 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 439 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 440 | "requires": { 441 | "ms": "2.0.0" 442 | } 443 | }, 444 | "ms": { 445 | "version": "2.0.0", 446 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 447 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 448 | }, 449 | "statuses": { 450 | "version": "1.4.0", 451 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 452 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 453 | } 454 | } 455 | }, 456 | "forwarded": { 457 | "version": "0.1.2", 458 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 459 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 460 | }, 461 | "fresh": { 462 | "version": "0.5.2", 463 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 464 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 465 | }, 466 | "graceful-readlink": { 467 | "version": "1.0.1", 468 | "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", 469 | "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" 470 | }, 471 | "http-errors": { 472 | "version": "1.6.3", 473 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", 474 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 475 | "requires": { 476 | "depd": "~1.1.2", 477 | "inherits": "2.0.3", 478 | "setprototypeof": "1.1.0", 479 | "statuses": ">= 1.4.0 < 2" 480 | } 481 | }, 482 | "iconv-lite": { 483 | "version": "0.4.23", 484 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", 485 | "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", 486 | "requires": { 487 | "safer-buffer": ">= 2.1.2 < 3" 488 | } 489 | }, 490 | "inherits": { 491 | "version": "2.0.3", 492 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 493 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 494 | }, 495 | "ipaddr.js": { 496 | "version": "1.8.0", 497 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", 498 | "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" 499 | }, 500 | "is-buffer": { 501 | "version": "1.1.6", 502 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 503 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 504 | }, 505 | "is-promise": { 506 | "version": "2.1.0", 507 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", 508 | "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" 509 | }, 510 | "jade": { 511 | "version": "1.11.0", 512 | "resolved": "https://registry.npmjs.org/jade/-/jade-1.11.0.tgz", 513 | "integrity": "sha1-nIDlOMEtP7lcjZu5VZ+gzAQEBf0=", 514 | "requires": { 515 | "character-parser": "1.2.1", 516 | "clean-css": "^3.1.9", 517 | "commander": "~2.6.0", 518 | "constantinople": "~3.0.1", 519 | "jstransformer": "0.0.2", 520 | "mkdirp": "~0.5.0", 521 | "transformers": "2.1.0", 522 | "uglify-js": "^2.4.19", 523 | "void-elements": "~2.0.1", 524 | "with": "~4.0.0" 525 | } 526 | }, 527 | "jstransformer": { 528 | "version": "0.0.2", 529 | "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-0.0.2.tgz", 530 | "integrity": "sha1-eq4pqQPRls+glz2IXT5HlH7Ndqs=", 531 | "requires": { 532 | "is-promise": "^2.0.0", 533 | "promise": "^6.0.1" 534 | } 535 | }, 536 | "kind-of": { 537 | "version": "3.2.2", 538 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 539 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 540 | "requires": { 541 | "is-buffer": "^1.1.5" 542 | } 543 | }, 544 | "lazy-cache": { 545 | "version": "1.0.4", 546 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", 547 | "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" 548 | }, 549 | "longest": { 550 | "version": "1.0.1", 551 | "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", 552 | "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" 553 | }, 554 | "media-typer": { 555 | "version": "0.3.0", 556 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 557 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 558 | }, 559 | "merge-descriptors": { 560 | "version": "1.0.1", 561 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 562 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 563 | }, 564 | "methods": { 565 | "version": "1.1.2", 566 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 567 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 568 | }, 569 | "mime": { 570 | "version": "1.4.1", 571 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 572 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" 573 | }, 574 | "mime-db": { 575 | "version": "1.35.0", 576 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", 577 | "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" 578 | }, 579 | "mime-types": { 580 | "version": "2.1.19", 581 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", 582 | "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", 583 | "requires": { 584 | "mime-db": "~1.35.0" 585 | } 586 | }, 587 | "minimist": { 588 | "version": "0.0.8", 589 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 590 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 591 | }, 592 | "mkdirp": { 593 | "version": "0.5.1", 594 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 595 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 596 | "requires": { 597 | "minimist": "0.0.8" 598 | } 599 | }, 600 | "morgan": { 601 | "version": "1.9.0", 602 | "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", 603 | "integrity": "sha1-0B+mxlhZt2/PMbPLU6OCGjEdgFE=", 604 | "requires": { 605 | "basic-auth": "~2.0.0", 606 | "debug": "2.6.9", 607 | "depd": "~1.1.1", 608 | "on-finished": "~2.3.0", 609 | "on-headers": "~1.0.1" 610 | }, 611 | "dependencies": { 612 | "debug": { 613 | "version": "2.6.9", 614 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 615 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 616 | "requires": { 617 | "ms": "2.0.0" 618 | } 619 | }, 620 | "ms": { 621 | "version": "2.0.0", 622 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 623 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 624 | } 625 | } 626 | }, 627 | "ms": { 628 | "version": "0.7.1", 629 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", 630 | "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" 631 | }, 632 | "negotiator": { 633 | "version": "0.6.1", 634 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 635 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 636 | }, 637 | "node-fetch": { 638 | "version": "2.2.0", 639 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.2.0.tgz", 640 | "integrity": "sha512-OayFWziIxiHY8bCUyLX6sTpDH8Jsbp4FfYd1j1f7vZyfgkcOnAyM4oQR16f8a0s7Gl/viMGRey8eScYk4V4EZA==" 641 | }, 642 | "oauth": { 643 | "version": "0.9.15", 644 | "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", 645 | "integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE=" 646 | }, 647 | "on-finished": { 648 | "version": "2.3.0", 649 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 650 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 651 | "requires": { 652 | "ee-first": "1.1.1" 653 | } 654 | }, 655 | "on-headers": { 656 | "version": "1.0.1", 657 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", 658 | "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" 659 | }, 660 | "optimist": { 661 | "version": "0.3.7", 662 | "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", 663 | "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=", 664 | "requires": { 665 | "wordwrap": "~0.0.2" 666 | } 667 | }, 668 | "parseurl": { 669 | "version": "1.3.2", 670 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 671 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 672 | }, 673 | "passport": { 674 | "version": "0.4.0", 675 | "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.0.tgz", 676 | "integrity": "sha1-xQlWkTR71a07XhgCOMORTRbwWBE=", 677 | "requires": { 678 | "passport-strategy": "1.x.x", 679 | "pause": "0.0.1" 680 | } 681 | }, 682 | "passport-oauth2": { 683 | "version": "1.4.0", 684 | "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.4.0.tgz", 685 | "integrity": "sha1-9i+BWDy+EmCb585vFguTlaJ7hq0=", 686 | "requires": { 687 | "oauth": "0.9.x", 688 | "passport-strategy": "1.x.x", 689 | "uid2": "0.0.x", 690 | "utils-merge": "1.x.x" 691 | } 692 | }, 693 | "passport-oauth2-refresh": { 694 | "version": "1.1.0", 695 | "resolved": "https://registry.npmjs.org/passport-oauth2-refresh/-/passport-oauth2-refresh-1.1.0.tgz", 696 | "integrity": "sha512-UsmDntTlEKj8GAME/u6hvgTgq/WC/1lh9wGKs7QEf5/e+pBo9dnphJ1qLfmwFck21lz3wHHHiPFJrihLkFSaXg==" 697 | }, 698 | "passport-strategy": { 699 | "version": "1.0.0", 700 | "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", 701 | "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" 702 | }, 703 | "path-to-regexp": { 704 | "version": "0.1.7", 705 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 706 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 707 | }, 708 | "pause": { 709 | "version": "0.0.1", 710 | "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", 711 | "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" 712 | }, 713 | "promise": { 714 | "version": "6.1.0", 715 | "resolved": "https://registry.npmjs.org/promise/-/promise-6.1.0.tgz", 716 | "integrity": "sha1-LOcp9rlLRcJoka0GAsXJDgTG7vY=", 717 | "requires": { 718 | "asap": "~1.0.0" 719 | } 720 | }, 721 | "proxy-addr": { 722 | "version": "2.0.4", 723 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", 724 | "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", 725 | "requires": { 726 | "forwarded": "~0.1.2", 727 | "ipaddr.js": "1.8.0" 728 | } 729 | }, 730 | "qs": { 731 | "version": "6.5.2", 732 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 733 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 734 | }, 735 | "random-bytes": { 736 | "version": "1.0.0", 737 | "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", 738 | "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" 739 | }, 740 | "range-parser": { 741 | "version": "1.2.0", 742 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 743 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 744 | }, 745 | "raw-body": { 746 | "version": "2.3.3", 747 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", 748 | "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", 749 | "requires": { 750 | "bytes": "3.0.0", 751 | "http-errors": "1.6.3", 752 | "iconv-lite": "0.4.23", 753 | "unpipe": "1.0.0" 754 | } 755 | }, 756 | "repeat-string": { 757 | "version": "1.6.1", 758 | "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", 759 | "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" 760 | }, 761 | "right-align": { 762 | "version": "0.1.3", 763 | "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", 764 | "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", 765 | "requires": { 766 | "align-text": "^0.1.1" 767 | } 768 | }, 769 | "safe-buffer": { 770 | "version": "5.1.1", 771 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 772 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 773 | }, 774 | "safer-buffer": { 775 | "version": "2.1.2", 776 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 777 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 778 | }, 779 | "send": { 780 | "version": "0.16.2", 781 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", 782 | "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", 783 | "requires": { 784 | "debug": "2.6.9", 785 | "depd": "~1.1.2", 786 | "destroy": "~1.0.4", 787 | "encodeurl": "~1.0.2", 788 | "escape-html": "~1.0.3", 789 | "etag": "~1.8.1", 790 | "fresh": "0.5.2", 791 | "http-errors": "~1.6.2", 792 | "mime": "1.4.1", 793 | "ms": "2.0.0", 794 | "on-finished": "~2.3.0", 795 | "range-parser": "~1.2.0", 796 | "statuses": "~1.4.0" 797 | }, 798 | "dependencies": { 799 | "debug": { 800 | "version": "2.6.9", 801 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 802 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 803 | "requires": { 804 | "ms": "2.0.0" 805 | } 806 | }, 807 | "ms": { 808 | "version": "2.0.0", 809 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 810 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 811 | }, 812 | "statuses": { 813 | "version": "1.4.0", 814 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 815 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 816 | } 817 | } 818 | }, 819 | "serve-favicon": { 820 | "version": "2.5.0", 821 | "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.5.0.tgz", 822 | "integrity": "sha1-k10kDN/g9YBTB/3+ln2IlCosvPA=", 823 | "requires": { 824 | "etag": "~1.8.1", 825 | "fresh": "0.5.2", 826 | "ms": "2.1.1", 827 | "parseurl": "~1.3.2", 828 | "safe-buffer": "5.1.1" 829 | }, 830 | "dependencies": { 831 | "ms": { 832 | "version": "2.1.1", 833 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 834 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 835 | } 836 | } 837 | }, 838 | "serve-static": { 839 | "version": "1.13.2", 840 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", 841 | "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", 842 | "requires": { 843 | "encodeurl": "~1.0.2", 844 | "escape-html": "~1.0.3", 845 | "parseurl": "~1.3.2", 846 | "send": "0.16.2" 847 | } 848 | }, 849 | "setprototypeof": { 850 | "version": "1.1.0", 851 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 852 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" 853 | }, 854 | "source-map": { 855 | "version": "0.4.4", 856 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", 857 | "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", 858 | "requires": { 859 | "amdefine": ">=0.0.4" 860 | } 861 | }, 862 | "statuses": { 863 | "version": "1.5.0", 864 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 865 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 866 | }, 867 | "transformers": { 868 | "version": "2.1.0", 869 | "resolved": "https://registry.npmjs.org/transformers/-/transformers-2.1.0.tgz", 870 | "integrity": "sha1-XSPLNVYd2F3Gf7hIIwm0fVPM6ac=", 871 | "requires": { 872 | "css": "~1.0.8", 873 | "promise": "~2.0", 874 | "uglify-js": "~2.2.5" 875 | }, 876 | "dependencies": { 877 | "is-promise": { 878 | "version": "1.0.1", 879 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-1.0.1.tgz", 880 | "integrity": "sha1-MVc3YcBX4zwukaq56W2gjO++duU=" 881 | }, 882 | "promise": { 883 | "version": "2.0.0", 884 | "resolved": "https://registry.npmjs.org/promise/-/promise-2.0.0.tgz", 885 | "integrity": "sha1-RmSKqdYFr10ucMMCS/WUNtoCuA4=", 886 | "requires": { 887 | "is-promise": "~1" 888 | } 889 | }, 890 | "source-map": { 891 | "version": "0.1.43", 892 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", 893 | "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", 894 | "requires": { 895 | "amdefine": ">=0.0.4" 896 | } 897 | }, 898 | "uglify-js": { 899 | "version": "2.2.5", 900 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.2.5.tgz", 901 | "integrity": "sha1-puAqcNg5eSuXgEiLe4sYTAlcmcc=", 902 | "requires": { 903 | "optimist": "~0.3.5", 904 | "source-map": "~0.1.7" 905 | } 906 | } 907 | } 908 | }, 909 | "type-is": { 910 | "version": "1.6.16", 911 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", 912 | "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", 913 | "requires": { 914 | "media-typer": "0.3.0", 915 | "mime-types": "~2.1.18" 916 | } 917 | }, 918 | "uglify-js": { 919 | "version": "2.8.29", 920 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", 921 | "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", 922 | "requires": { 923 | "source-map": "~0.5.1", 924 | "uglify-to-browserify": "~1.0.0", 925 | "yargs": "~3.10.0" 926 | }, 927 | "dependencies": { 928 | "source-map": { 929 | "version": "0.5.7", 930 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 931 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" 932 | } 933 | } 934 | }, 935 | "uglify-to-browserify": { 936 | "version": "1.0.2", 937 | "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", 938 | "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", 939 | "optional": true 940 | }, 941 | "uid-safe": { 942 | "version": "2.1.5", 943 | "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", 944 | "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", 945 | "requires": { 946 | "random-bytes": "~1.0.0" 947 | } 948 | }, 949 | "uid2": { 950 | "version": "0.0.3", 951 | "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz", 952 | "integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=" 953 | }, 954 | "unpipe": { 955 | "version": "1.0.0", 956 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 957 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 958 | }, 959 | "utils-merge": { 960 | "version": "1.0.0", 961 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", 962 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" 963 | }, 964 | "vary": { 965 | "version": "1.1.2", 966 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 967 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 968 | }, 969 | "void-elements": { 970 | "version": "2.0.1", 971 | "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", 972 | "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" 973 | }, 974 | "window-size": { 975 | "version": "0.1.0", 976 | "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", 977 | "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" 978 | }, 979 | "with": { 980 | "version": "4.0.3", 981 | "resolved": "https://registry.npmjs.org/with/-/with-4.0.3.tgz", 982 | "integrity": "sha1-7v0VTp550sjTQXtkeo8U2f7M4U4=", 983 | "requires": { 984 | "acorn": "^1.0.1", 985 | "acorn-globals": "^1.0.3" 986 | }, 987 | "dependencies": { 988 | "acorn": { 989 | "version": "1.2.2", 990 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz", 991 | "integrity": "sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ=" 992 | } 993 | } 994 | }, 995 | "wordwrap": { 996 | "version": "0.0.3", 997 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", 998 | "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" 999 | }, 1000 | "yargs": { 1001 | "version": "3.10.0", 1002 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", 1003 | "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", 1004 | "requires": { 1005 | "camelcase": "^1.0.2", 1006 | "cliui": "^2.1.0", 1007 | "decamelize": "^1.0.0", 1008 | "window-size": "0.1.0" 1009 | } 1010 | } 1011 | } 1012 | } 1013 | --------------------------------------------------------------------------------