Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Ory Action example for a vpn / geo check #75

Merged
merged 9 commits into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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