Skip to content

Commit

Permalink
feat: add Ory Action example for a vpn / geo check (#75)
Browse files Browse the repository at this point in the history
* feat: add Ory Action example for a vpn / geo check

* chore: fix format

* fix: format

* fix:format

* chore: format all the things

* fix: conditional cloud logging

* fix: conditional logging attempt 2

* fix: conditional logging, take 3

* fix: format and move to folder

---------

Co-authored-by: Vincent Kraus <[email protected]>
  • Loading branch information
kmherrmann and vinckr committed Jul 19, 2023
1 parent 2face08 commit f386aa9
Show file tree
Hide file tree
Showing 33 changed files with 1,702 additions and 1,286 deletions.
24 changes: 14 additions & 10 deletions hydra-nextjs/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
# ORY Hydra Next.js Reference Implementation

This is a Next.js app which sets up a OAuth 2.0 and OpenID Connect Provider using [Ory Hydra](https://www.ory.sh/docs/hydra/) as a backend. It has an unstyled UI and doesn't implement user management but can be easily modified to fit into your existing system.
This is a Next.js app which sets up a OAuth 2.0 and OpenID Connect Provider
using [Ory Hydra](https://www.ory.sh/docs/hydra/) as a backend. It has an
unstyled UI and doesn't implement user management but can be easily modified to
fit into your existing system.

# Features

* User login, logout, registration, consent
* CSRF protection with [edge-csrf](https://github.com/amorey/edge-csrf)
* Super-strict HTTP security headers (configurable)
* Client-side JavaScript disabled by default
* Unit tests with Jest
* E2E tests with Cypress
* Start/stop Hydra in development using docker-compose
* Easily customizable
- User login, logout, registration, consent
- CSRF protection with [edge-csrf](https://github.com/amorey/edge-csrf)
- Super-strict HTTP security headers (configurable)
- Client-side JavaScript disabled by default
- Unit tests with Jest
- E2E tests with Cypress
- Start/stop Hydra in development using docker-compose
- Easily customizable

## Configuration

Expand All @@ -36,7 +39,8 @@ To run the Next.js app server in development mode:
yarn dev
```

To start/stop hydra in development you can use the docker-compose file found in the `ory/` directory:
To start/stop hydra in development you can use the docker-compose file found in
the `ory/` directory:

```sh
# start
Expand Down
13 changes: 8 additions & 5 deletions hydra-nextjs/cypress.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
const { defineConfig } = require('cypress');
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

const { defineConfig } = require("cypress")

module.exports = defineConfig({
screenshotOnRunFailure: false,
video: false,
e2e: {
baseUrl: 'http://localhost:3000',
supportFile: false
}
});
baseUrl: "http://localhost:3000",
supportFile: false,
},
})
52 changes: 25 additions & 27 deletions hydra-nextjs/cypress/e2e/csrf.cy.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,36 @@
describe('CSRF protection tests', () => {
const authPaths = [
'/sign-in',
'/sign-up',
'/sign-out',
'/consent'
];
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

context('should reject POST request without csrf token', () => {
describe("CSRF protection tests", () => {
const authPaths = ["/sign-in", "/sign-up", "/sign-out", "/consent"]

context("should reject POST request without csrf token", () => {
authPaths.forEach((path) => {
it(path, async () => {
const response = await cy.request({
method: 'POST',
url: '/auth' + path,
failOnStatusCode: false
});
expect(response.status).to.equal(403);
});
});
});
method: "POST",
url: "/auth" + path,
failOnStatusCode: false,
})
expect(response.status).to.equal(403)
})
})
})

context('should reject POST request with invalid csrf token', () => {
context("should reject POST request with invalid csrf token", () => {
authPaths.forEach((path) => {
it(path, async () => {
const response = await cy.request({
method: 'POST',
url: '/auth' + path,
method: "POST",
url: "/auth" + path,
form: true,
body: {
csrf_token: 'invalid-token'
csrf_token: "invalid-token",
},
failOnStatusCode: false
});
expect(response.status).to.equal(403);
});
});
});
});
failOnStatusCode: false,
})
expect(response.status).to.equal(403)
})
})
})
})
51 changes: 28 additions & 23 deletions hydra-nextjs/cypress/e2e/headers.cy.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
describe('HTTP response header tests', () => {
it('should have performance headers present', async () => {
const response = await cy.request('GET', '/');
expect(response.headers).to.have.property('x-dns-prefetch-control', 'on');
});

it('should have security headers present', async () => {
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

describe("HTTP response header tests", () => {
it("should have performance headers present", async () => {
const response = await cy.request("GET", "/")
expect(response.headers).to.have.property("x-dns-prefetch-control", "on")
})

it("should have security headers present", async () => {
const securityHeaders = {
'strict-transport-security': 'max-age=31536000 ; includeSubDomains',
'x-frame-options': 'deny',
'x-content-type-options': 'nosniff',
'content-security-policy': "default-src 'self'; object-src 'none'; style-src 'unsafe-inline'; frame-ancestors 'none'; upgrade-insecure-requests; block-all-mixed-content",
'x-permitted-cross-domain-policies': 'none',
'referrer-policy': 'no-referrer',
'cross-origin-embedder-policy': 'require-corp',
'cross-origin-opener-policy': 'same-origin',
'cross-origin-resource-policy': 'same-origin',
'permissions-policy': 'accelerometer=(),autoplay=(),camera=(),display-capture=(),document-domain=(),encrypted-media=(),fullscreen=(),geolocation=(),gyroscope=(),magnetometer=(),microphone=(),midi=(),payment=(),picture-in-picture=(),publickey-credentials-get=(),screen-wake-lock=(),sync-xhr=(self),usb=(),web-share=(),xr-spatial-tracking=()'
};
"strict-transport-security": "max-age=31536000 ; includeSubDomains",
"x-frame-options": "deny",
"x-content-type-options": "nosniff",
"content-security-policy":
"default-src 'self'; object-src 'none'; style-src 'unsafe-inline'; frame-ancestors 'none'; upgrade-insecure-requests; block-all-mixed-content",
"x-permitted-cross-domain-policies": "none",
"referrer-policy": "no-referrer",
"cross-origin-embedder-policy": "require-corp",
"cross-origin-opener-policy": "same-origin",
"cross-origin-resource-policy": "same-origin",
"permissions-policy":
"accelerometer=(),autoplay=(),camera=(),display-capture=(),document-domain=(),encrypted-media=(),fullscreen=(),geolocation=(),gyroscope=(),magnetometer=(),microphone=(),midi=(),payment=(),picture-in-picture=(),publickey-credentials-get=(),screen-wake-lock=(),sync-xhr=(self),usb=(),web-share=(),xr-spatial-tracking=()",
}

const response = await cy.request('GET', '/');
const response = await cy.request("GET", "/")
for (const [key, val] of Object.entries(securityHeaders)) {
cy.log(key);
expect(response.headers).to.have.property(key, val);
cy.log(key)
expect(response.headers).to.have.property(key, val)
}
});
});
})
})
11 changes: 7 additions & 4 deletions hydra-nextjs/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
const nextJest = require("next/jest");
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

const nextJest = require("next/jest")

const createJestConfig = nextJest({
dir: "./",
});
})

const customJestConfig = {
moduleDirectories: ["node_modules", "<rootDir>/"],
testEnvironment: "jest-environment-jsdom",
};
}

module.exports = createJestConfig(customJestConfig);
module.exports = createJestConfig(customJestConfig)
13 changes: 8 additions & 5 deletions hydra-nextjs/lib/hydra.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { AdminApi, Configuration } from '@ory/hydra-client';
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

import { AdminApi, Configuration } from "@ory/hydra-client"

const hydraAdmin = new AdminApi(
new Configuration({
basePath: process.env.HYDRA_ADMIN_URL
})
);
basePath: process.env.HYDRA_ADMIN_URL,
}),
)

export { hydraAdmin };
export { hydraAdmin }
15 changes: 9 additions & 6 deletions hydra-nextjs/lib/ssr-helpers.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import { parseBody as nextParseBody } from 'next/dist/server/api-utils/node';
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

import { parseBody as nextParseBody } from "next/dist/server/api-utils/node"

export function redirect(statusCode, url) {
return {
redirect: {
statusCode: statusCode,
destination: url,
},
};
}
}

export function parseBody(req, limit) {
// check cache
if (req.body) return req.body;
if (req.body) return req.body

// parse and cache
const body = nextParseBody(req, limit || '1mb');
req.body = body;
return body;
const body = nextParseBody(req, limit || "1mb")
req.body = body
return body
}
22 changes: 12 additions & 10 deletions hydra-nextjs/middleware.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import csrf from 'edge-csrf';
import { NextResponse } from 'next/server';
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

import csrf from "edge-csrf"
import { NextResponse } from "next/server"

// initialize csrf protection function
const csrfProtect = csrf();
const csrfProtect = csrf()

export async function middleware(request) {
const response = NextResponse.next();
const response = NextResponse.next()

// csrf protection
const csrfError = await csrfProtect(request, response);
const csrfError = await csrfProtect(request, response)

// check result
if (csrfError) {
const url = request.nextUrl.clone();
url.pathname = '/api/csrf-invalid';
return NextResponse.rewrite(url);
const url = request.nextUrl.clone()
url.pathname = "/api/csrf-invalid"
return NextResponse.rewrite(url)
}

return response;
return response
}

Loading

0 comments on commit f386aa9

Please sign in to comment.