diff --git a/docs/modules/ROOT/content-nav.adoc b/docs/modules/ROOT/content-nav.adoc index 3e1ec513a9..5b160bfe33 100644 --- a/docs/modules/ROOT/content-nav.adoc +++ b/docs/modules/ROOT/content-nav.adoc @@ -63,3 +63,4 @@ **** xref:guides/v2-migration/miscellaneous.adoc[] ** xref:troubleshooting/index.adoc[] *** xref:troubleshooting/faqs.adoc[] +*** xref:troubleshooting/security.adoc[] diff --git a/docs/modules/ROOT/pages/troubleshooting/security.adoc b/docs/modules/ROOT/pages/troubleshooting/security.adoc new file mode 100644 index 0000000000..7c664d8440 --- /dev/null +++ b/docs/modules/ROOT/pages/troubleshooting/security.adoc @@ -0,0 +1,10 @@ +[[security]] += Security + +This chapter describes security considerations and known issues. + +== Authorization not triggered for empty match + +If a query yields no results, the xref::auth/auth-directive.adoc[Authorization] process will not be triggered. +This means that the result will be empty, instead of throwing an authentication error. Unauthorized users may +then discern whether or not a certain type exists in the database, even if data itself cannot be accessed. diff --git a/packages/graphql/tests/integration/auth/roles.int.test.ts b/packages/graphql/tests/integration/auth/roles.int.test.ts index ceb82de0a5..8dc9876f3f 100644 --- a/packages/graphql/tests/integration/auth/roles.int.test.ts +++ b/packages/graphql/tests/integration/auth/roles.int.test.ts @@ -37,6 +37,23 @@ describe("auth/roles", () => { await driver.close(); }); + beforeAll(async () => { + const session = driver.session(); + + try { + await session.run(` + CREATE (:Product { name: 'p1', id:123 }) + CREATE (:User { id: 1234, password:'dontpanic' }) + `); + await session.run(` + MATCH(N:NotANode) + DETACH DELETE(N) + `); + } finally { + await session.close(); + } + }); + describe("read", () => { test("should throw if missing role on type definition", async () => { const session = driver.session(); @@ -118,6 +135,48 @@ describe("auth/roles", () => { await session.close(); } }); + + // This tests reproduces the security issue related to authorization without match #195 + test.skip("should throw if missing role on type definition and no nodes are matched", async () => { + const session = driver.session(); + + const typeDefs = ` + type NotANode @auth(rules: [{ + operations: [READ], + roles: ["admin"] + }]) { + name: String + } + `; + + const query = ` + { + notANodes { + name + } + } + `; + + const secret = "secret"; + const token = jsonwebtoken.sign({ roles: [] }, secret); + const neoSchema = new Neo4jGraphQL({ typeDefs, config: { jwt: { secret } } }); + + try { + const socket = new Socket({ readable: true }); + const req = new IncomingMessage(socket); + req.headers.authorization = `Bearer ${token}`; + + const gqlResult = await graphql({ + schema: neoSchema.schema, + source: query, + contextValue: { driver, req, driverConfig: { bookmarks: session.lastBookmark() } }, + }); + + expect((gqlResult.errors as any[])[0].message).toEqual("Forbidden"); + } finally { + await session.close(); + } + }); }); describe("create", () => {