mercurius-auth
Advanced tools
Comparing version 1.3.1 to 1.4.0
@@ -17,2 +17,3 @@ # mercurius-auth | ||
* **filterSchema** `boolean` - when `true`, [introspection queries](https://graphql.org/learn/introspection/) will only return the parts of the schema which are accessible based on the applied policies. | ||
### `external` mode | ||
@@ -19,0 +20,0 @@ |
@@ -40,2 +40,3 @@ # Auth context | ||
const app = Fastify() | ||
app.register(mercurius, { | ||
@@ -42,0 +43,0 @@ schema, |
@@ -65,2 +65,6 @@ import { FastifyPluginAsync } from 'fastify' | ||
authDirective: string; | ||
/** | ||
* When set to true, the plugin will automatically filter the output Schema during Introspection queries if the applyPolicy function is not satisfated. | ||
*/ | ||
filterSchema?: boolean; | ||
} | ||
@@ -67,0 +71,0 @@ |
@@ -6,2 +6,3 @@ 'use strict' | ||
const { validateOpts } = require('./lib/validation') | ||
const filterSchema = require('./lib/filter-schema') | ||
@@ -25,2 +26,5 @@ const plugin = fp( | ||
auth.registerAuthHandlers(schema, authSchema) | ||
if (opts.filterSchema === true) { | ||
filterSchema.updatePolicy(app, authSchema, opts) | ||
} | ||
}) | ||
@@ -31,2 +35,6 @@ | ||
} | ||
if (opts.filterSchema === true) { | ||
filterSchema(app, authSchema, opts) | ||
} | ||
}, | ||
@@ -33,0 +41,0 @@ { |
@@ -29,2 +29,6 @@ 'use strict' | ||
} | ||
if (opts.filterSchema === true) { | ||
throw new MER_AUTH_ERR_INVALID_OPTS('opts.filterSchema cannot be used when mode is external.') | ||
} | ||
// Default mode | ||
@@ -31,0 +35,0 @@ } else { |
{ | ||
"name": "mercurius-auth", | ||
"version": "1.3.1", | ||
"version": "1.4.0", | ||
"description": "Mercurius Auth Plugin adds configurable Authentication and Authorization support to Mercurius.", | ||
@@ -41,3 +41,3 @@ "main": "index.js", | ||
"fastify": "^3.0.2", | ||
"mercurius": "^8.0.0", | ||
"mercurius": "^8.10.0", | ||
"pre-commit": "^1.2.2", | ||
@@ -47,3 +47,3 @@ "snazzy": "^9.0.0", | ||
"tap": "^15.0.2", | ||
"tsd": "^0.18.0", | ||
"tsd": "^0.19.0", | ||
"typescript": "^4.0.3", | ||
@@ -53,2 +53,3 @@ "wait-on": "^6.0.0" | ||
"dependencies": { | ||
"@graphql-tools/wrap": "^8.3.2", | ||
"fastify-error": "^0.3.0", | ||
@@ -55,0 +56,0 @@ "fastify-plugin": "^3.0.0", |
@@ -28,2 +28,3 @@ # mercurius-auth | ||
- [Auth Directive](docs/auth-directive.md) | ||
- [Schema filtering](docs/schema-filtering.md) | ||
- [External Policy](docs/external-policy.md) | ||
@@ -30,0 +31,0 @@ - [Errors](docs/errors.md) |
@@ -442,1 +442,49 @@ 'use strict' | ||
}) | ||
test('gateway - should filter the schema output', async (t) => { | ||
t.plan(4) | ||
const order = [ | ||
'topPosts', | ||
'name', | ||
'author' | ||
] | ||
const app = await createTestGatewayServer(t, { | ||
filterSchema: true, | ||
async applyPolicy (authDirectiveAST, parent, args, context, info) { | ||
t.equal(info.fieldName, order.shift()) | ||
return false | ||
}, | ||
authDirective: 'auth' | ||
}) | ||
const query = `{ | ||
__type(name: "Query") { | ||
name | ||
fields { | ||
name | ||
} | ||
} | ||
}` | ||
const res = await app.inject({ | ||
method: 'POST', | ||
headers: { 'content-type': 'application/json', 'x-user': 'admin' }, | ||
url: '/graphql', | ||
body: JSON.stringify({ query }) | ||
}) | ||
t.same(res.json(), { | ||
data: { | ||
__type: { | ||
name: 'Query', | ||
fields: [ | ||
{ | ||
name: 'me' | ||
} | ||
] | ||
} | ||
} | ||
}) | ||
}) |
@@ -195,1 +195,245 @@ 'use strict' | ||
}) | ||
test('polling a filtered schema should complete the refresh succesfully', async (t) => { | ||
t.plan(8) | ||
const clock = FakeTimers.install({ | ||
shouldAdvanceTime: true, | ||
advanceTimeDelta: 40 | ||
}) | ||
t.teardown(() => clock.uninstall()) | ||
const user = { | ||
id: 'u1', | ||
name: 'John', | ||
lastName: 'Doe' | ||
} | ||
const resolvers = { | ||
Query: { | ||
me: (root, args, context, info) => user | ||
}, | ||
User: { | ||
__resolveReference: (user, args, context, info) => user | ||
} | ||
} | ||
const userService = Fastify() | ||
const gateway = Fastify() | ||
t.teardown(async () => { | ||
await gateway.close() | ||
await userService.close() | ||
}) | ||
userService.register(mercurius, { | ||
schema: ` | ||
directive @auth on OBJECT | FIELD_DEFINITION | ||
extend type Query { | ||
me: User | ||
} | ||
type User @key(fields: "id") { | ||
id: ID! | ||
name: String @auth | ||
} | ||
`, | ||
resolvers: resolvers, | ||
federationMetadata: true | ||
}) | ||
await userService.listen(0) | ||
const userServicePort = userService.server.address().port | ||
await gateway.register(mercurius, { | ||
gateway: { | ||
services: [ | ||
{ | ||
name: 'user', | ||
url: `http://localhost:${userServicePort}/graphql` | ||
} | ||
], | ||
pollingInterval: 2000 | ||
} | ||
}) | ||
await gateway.register(mercuriusAuth, { | ||
filterSchema: true, | ||
authContext (context) { | ||
return { | ||
identity: context.reply.request.headers['x-user'] | ||
} | ||
}, | ||
async applyPolicy (authDirectiveAST, parent, args, context, info) { | ||
t.ok('should be called') | ||
return context.auth.identity === 'admin' | ||
}, | ||
authDirective: 'auth' | ||
}) | ||
{ | ||
const query = `query { | ||
me { | ||
id | ||
name | ||
} | ||
}` | ||
const res = await gateway.inject({ | ||
method: 'POST', | ||
headers: { 'content-type': 'application/json', 'x-user': 'user' }, | ||
url: '/graphql', | ||
body: JSON.stringify({ query }) | ||
}) | ||
t.same(JSON.parse(res.body), { | ||
data: { | ||
me: { | ||
id: 'u1', | ||
name: null | ||
} | ||
}, | ||
errors: [ | ||
{ | ||
message: 'Failed auth policy check on name', | ||
locations: [ | ||
{ | ||
line: 4, | ||
column: 7 | ||
} | ||
], | ||
path: [ | ||
'me', | ||
'name' | ||
] | ||
} | ||
] | ||
}) | ||
} | ||
{ | ||
const query = `{ | ||
__type(name:"User"){ | ||
name | ||
fields{ | ||
name | ||
} | ||
} | ||
}` | ||
const res = await gateway.inject({ | ||
method: 'POST', | ||
headers: { 'content-type': 'application/json', 'x-user': 'user' }, | ||
url: '/graphql', | ||
body: JSON.stringify({ query }) | ||
}) | ||
t.same(res.json(), { | ||
data: { | ||
__type: { | ||
name: 'User', | ||
fields: [ | ||
{ name: 'id' } | ||
] | ||
} | ||
} | ||
}) | ||
} | ||
userService.graphql.replaceSchema( | ||
mercurius.buildFederationSchema(` | ||
directive @auth on OBJECT | FIELD_DEFINITION | ||
extend type Query { | ||
me: User | ||
} | ||
type User @key(fields: "id") { | ||
id: ID! | ||
name: String | ||
lastName: String @auth | ||
} | ||
`) | ||
) | ||
userService.graphql.defineResolvers(resolvers) | ||
await clock.tickAsync(2000) | ||
// We need the event loop to actually spin twice to | ||
// be able to propagate the change | ||
await immediate() | ||
await immediate() | ||
{ | ||
const query = `query { | ||
me { | ||
id | ||
name | ||
lastName | ||
} | ||
}` | ||
const res = await gateway.inject({ | ||
method: 'POST', | ||
headers: { 'content-type': 'application/json', 'x-user': 'user' }, | ||
url: '/graphql', | ||
body: JSON.stringify({ query }) | ||
}) | ||
t.same(JSON.parse(res.body), { | ||
data: { | ||
me: { | ||
id: 'u1', | ||
name: 'John', | ||
lastName: null | ||
} | ||
}, | ||
errors: [ | ||
{ | ||
message: 'Failed auth policy check on lastName', | ||
locations: [ | ||
{ | ||
line: 5, | ||
column: 7 | ||
} | ||
], | ||
path: [ | ||
'me', | ||
'lastName' | ||
] | ||
} | ||
] | ||
}) | ||
} | ||
{ | ||
const query = `{ | ||
__type(name:"User"){ | ||
name | ||
fields{ | ||
name | ||
} | ||
} | ||
}` | ||
const res = await gateway.inject({ | ||
method: 'POST', | ||
headers: { 'content-type': 'application/json', 'x-user': 'user' }, | ||
url: '/graphql', | ||
body: JSON.stringify({ query }) | ||
}) | ||
t.same(res.json(), { | ||
data: { | ||
__type: { | ||
name: 'User', | ||
fields: [ | ||
{ name: 'id' }, | ||
{ name: 'name' } | ||
] | ||
} | ||
} | ||
}) | ||
} | ||
}) |
@@ -141,3 +141,3 @@ 'use strict' | ||
test('registration - external policy', t => { | ||
t.plan(3) | ||
t.plan(4) | ||
@@ -204,2 +204,19 @@ t.test('should error if policy is not an object', async (t) => { | ||
}) | ||
t.test('cannot filter introspection schema on external mode', async (t) => { | ||
t.plan(1) | ||
const app = Fastify() | ||
t.teardown(app.close.bind(app)) | ||
app.register(mercurius, { | ||
schema, | ||
resolvers | ||
}) | ||
await t.rejects(app.register(mercuriusAuth, { | ||
applyPolicy: () => {}, | ||
mode: 'external', | ||
filterSchema: true | ||
}), new MER_AUTH_ERR_INVALID_OPTS('opts.filterSchema cannot be used when mode is external.')) | ||
}) | ||
}) |
@@ -44,2 +44,3 @@ import { expectAssignable, expectType } from 'tsd' | ||
const authOptions: MercuriusAuthOptions = { | ||
filterSchema: true, | ||
authDirective: 'auth', | ||
@@ -46,0 +47,0 @@ async applyPolicy ( |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
241855
54
7590
284
4
1
1
+ Added@graphql-tools/wrap@^8.3.2
+ Added@graphql-tools/batch-execute@8.5.1(transitive)
+ Added@graphql-tools/delegate@8.8.1(transitive)
+ Added@graphql-tools/merge@8.3.1(transitive)
+ Added@graphql-tools/schema@8.5.1(transitive)
+ Added@graphql-tools/utils@8.9.0(transitive)
+ Added@graphql-tools/wrap@8.5.1(transitive)
+ Addeddataloader@2.1.0(transitive)
+ Addedtslib@2.4.12.8.1(transitive)
+ Addedvalue-or-promise@1.0.11(transitive)