@fastify/auth
Advanced tools
Comparing version 4.2.0 to 4.3.0
@@ -6,3 +6,3 @@ import { ContextConfigDefault, RouteGenericInterface, FastifyInstance, FastifyPluginCallback, FastifyReply, FastifyRequest, FastifySchema, preHandlerHookHandler } from 'fastify'; | ||
auth( | ||
functions: fastifyAuth.FastifyAuthFunction[], | ||
functions: fastifyAuth.FastifyAuthFunction[] | (fastifyAuth.FastifyAuthFunction | fastifyAuth.FastifyAuthFunction[])[], | ||
options?: { | ||
@@ -9,0 +9,0 @@ relation?: 'and' | 'or', |
70
auth.js
@@ -42,3 +42,10 @@ 'use strict' | ||
for (var i = 0; i < functions.length; i++) { | ||
functions[i] = functions[i].bind(this) | ||
if (Array.isArray(functions[i]) === false) { | ||
functions[i] = [functions[i].bind(this)] | ||
} else { | ||
/* eslint-disable-next-line no-var */ | ||
for (var j = 0; j < functions[i].length; j++) { | ||
functions[i][j] = functions[i][j].bind(this) | ||
} | ||
} | ||
} | ||
@@ -57,4 +64,5 @@ | ||
obj.i = 0 | ||
obj.start = true | ||
obj.j = 0 | ||
obj.firstResult = null | ||
obj.sufficient = false | ||
@@ -69,3 +77,3 @@ obj.nextAuth() | ||
this.i = 0 | ||
this.start = true | ||
this.j = 0 | ||
this.functions = [] | ||
@@ -77,2 +85,3 @@ this.options = {} | ||
this.firstResult = null | ||
this.sufficient = false | ||
@@ -82,6 +91,6 @@ const that = this | ||
this.nextAuth = function nextAuth (err) { | ||
const func = that.functions[that.i++] | ||
const func = that.functions[that.i][that.j++] | ||
if (!func) { | ||
that.completeAuth(err) | ||
that.completeAuthArray(err) | ||
return | ||
@@ -102,27 +111,44 @@ } | ||
this.onAuth = function onAuth (err, results) { | ||
if (that.options.relation === 'or') { | ||
if (err) { | ||
return that.nextAuth(err) | ||
if (err) { | ||
return that.completeAuthArray(err) | ||
} | ||
return that.nextAuth(err) | ||
} | ||
this.completeAuthArray = function (err) { | ||
if (err) { | ||
if (that.options.relation === 'and') { | ||
if (that.options.run === 'all') { | ||
that.firstResult = that.firstResult ?? err | ||
} else { | ||
that.firstResult = err | ||
this.completeAuth() | ||
return | ||
} | ||
} else { | ||
that.firstResult = that.sufficient ? null : err | ||
} | ||
} else { | ||
if (that.options.relation === 'or') { | ||
that.sufficient = true | ||
that.firstResult = null | ||
return that.completeAuth() | ||
} else { | ||
if (err) { | ||
return that.completeAuth(err) | ||
if (that.options.run !== 'all') { | ||
this.completeAuth() | ||
return | ||
} | ||
} | ||
} | ||
if (that.i < that.functions.length - 1) { | ||
that.i += 1 | ||
that.j = 0 | ||
return that.nextAuth(err) | ||
} | ||
this.completeAuth() | ||
} | ||
this.completeAuth = function (err) { | ||
if (that.start) { | ||
that.start = false | ||
that.firstResult = err | ||
} | ||
if (that.options.run === 'all' && that.i < that.functions.length) { | ||
return that.nextAuth(err) | ||
} | ||
this.completeAuth = function () { | ||
if (that.firstResult && (!that.reply.raw.statusCode || that.reply.raw.statusCode < 400)) { | ||
@@ -129,0 +155,0 @@ that.reply.code(401) |
{ | ||
"name": "@fastify/auth", | ||
"version": "4.2.0", | ||
"version": "4.3.0", | ||
"description": "Run multiple auth functions in Fastify", | ||
@@ -16,5 +16,4 @@ "repository": { | ||
"test": "npm run test:unit && npm run test:typescript", | ||
"test:ci": "standard && tap -J ./test/*.test.js --coverage-report=lcovonly && npm run test:typescript", | ||
"test:typescript": "tsd", | ||
"test:unit": "tap -J ./test/*.test.js" | ||
"test:unit": "tap" | ||
}, | ||
@@ -39,9 +38,9 @@ "keywords": [ | ||
"@fastify/type-provider-json-schema-to-ts": "^2.1.1", | ||
"@fastify/type-provider-typebox": "^2.3.0", | ||
"@types/node": "^18.7.14", | ||
"@fastify/type-provider-typebox": "^3.0.0", | ||
"@types/node": "^20.1.0", | ||
"fastify": "^4.5.3", | ||
"rimraf": "^3.0.2", | ||
"rimraf": "^5.0.0", | ||
"standard": "^17.0.0", | ||
"tap": "^16.3.0", | ||
"tsd": "^0.24.1" | ||
"tsd": "^0.28.0" | ||
}, | ||
@@ -48,0 +47,0 @@ "dependencies": { |
@@ -76,2 +76,36 @@ # @fastify/auth | ||
If you need composited authentication, such as verifying user account passwords and levels or meeting VIP eligibility criteria. e.g. [(verifyUserPassword `and` verifyLevel) `or` (verifyVIP)] | ||
```js | ||
fastify | ||
.decorate('verifyUserPassword', function (request, reply, done) { | ||
// your validation logic | ||
done() // pass an error if the authentication fails | ||
}) | ||
.decorate('verifyLevel', function (request, reply, done) { | ||
// your validation logic | ||
done() // pass an error if the authentication fails | ||
}) | ||
.decorate('verifyVIP', function (request, reply, done) { | ||
// your validation logic | ||
done() // pass an error if the authentication fails | ||
}) | ||
.register(require('@fastify/auth')) | ||
.after(() => { | ||
fastify.route({ | ||
method: 'POST', | ||
url: '/auth-multiple', | ||
preHandler: fastify.auth([ | ||
[fastify.verifyUserPassword, fastify.verifyLevel], // The arrays within an array always have an AND relationship. | ||
fastify.verifyVIP | ||
], { | ||
relation: 'or' // default relation | ||
}), | ||
handler: (req, reply) => { | ||
req.log.info('Auth route') | ||
reply.send({ hello: 'world' }) | ||
} | ||
}) | ||
}) | ||
``` | ||
You can use the `defaultRelation` option while registering the plugin, to change the default `relation`: | ||
@@ -156,2 +190,14 @@ ```js | ||
## Security Considerations | ||
### `onRequest` vs. `preHandler` hook | ||
The main difference between the `onRequest` and `preHandler` stages of the [Fastify Lifecycle](https://www.fastify.io/docs/latest/Reference/Lifecycle/) is that the body payload is not parsed in the `onRequest` stage. Parsing the body can be a potential security risk, as it can be used for denial of service (DoS) attacks. Therefore, it is recommended to avoid parsing the body for unauthorized access. | ||
Using the `@fastify/auth` plugin in the `preHandler` hook can result in unnecessary memory allocation if a malicious user sends a large payload in the request body and the request is unauthorized. In this case, Fastify will parse the body, even though the request is not authorized, leading to unnecessary memory allocation. To avoid this, it is recommended to use the `onRequest` hook for authentication, if the authentication method does not require the request body, such as `@fastify/jwt`, which expects the authentication in the request header. | ||
For authentication methods that do require the request body, such as sending a token in the body, you must use the `preHandler` hook. | ||
In mixed cases you must use the `preHandler` hook. | ||
## API | ||
@@ -158,0 +204,0 @@ |
@@ -5,3 +5,3 @@ 'use strict' | ||
const test = t.test | ||
const rimraf = require('rimraf') | ||
const { rimrafSync } = require('rimraf') | ||
const build = require('./example-async') | ||
@@ -12,15 +12,10 @@ | ||
t.teardown(() => { | ||
fastify.close() | ||
rimraf('./authdb', err => { | ||
if (err) throw err | ||
}) | ||
t.before(() => { | ||
rimrafSync('./authdb') | ||
fastify = build() | ||
}) | ||
test('boot server', t => { | ||
t.plan(1) | ||
rimraf('./authdb', err => { | ||
fastify = build() | ||
t.error(err) | ||
}) | ||
t.teardown(async () => { | ||
await fastify.close() | ||
rimrafSync('./authdb') | ||
}) | ||
@@ -27,0 +22,0 @@ |
@@ -73,2 +73,12 @@ 'use strict' | ||
method: 'POST', | ||
url: '/checkarrayand', | ||
preHandler: fastify.auth([[fastify.verifyNumber], [fastify.verifyOdd]], { relation: 'and' }), | ||
handler: (req, reply) => { | ||
req.log.info('Auth route') | ||
reply.send({ hello: 'world' }) | ||
} | ||
}) | ||
fastify.route({ | ||
method: 'POST', | ||
url: '/checkor', | ||
@@ -84,2 +94,12 @@ preHandler: fastify.auth([fastify.verifyOdd, fastify.verifyBig]), | ||
method: 'POST', | ||
url: '/checkarrayor', | ||
preHandler: fastify.auth([[fastify.verifyOdd], [fastify.verifyBig]]), | ||
handler: (req, reply) => { | ||
req.log.info('Auth route') | ||
reply.send({ hello: 'world' }) | ||
} | ||
}) | ||
fastify.route({ | ||
method: 'POST', | ||
url: '/singleor', | ||
@@ -95,2 +115,12 @@ preHandler: fastify.auth([fastify.verifyOdd]), | ||
method: 'POST', | ||
url: '/singlearrayor', | ||
preHandler: fastify.auth([[fastify.verifyOdd]]), | ||
handler: (req, reply) => { | ||
req.log.info('Auth route') | ||
reply.send({ hello: 'world' }) | ||
} | ||
}) | ||
fastify.route({ | ||
method: 'POST', | ||
url: '/singleand', | ||
@@ -106,2 +136,32 @@ preHandler: fastify.auth([fastify.verifyOdd], { relation: 'and' }), | ||
method: 'POST', | ||
url: '/singlearrayand', | ||
preHandler: fastify.auth([[fastify.verifyOdd]], { relation: 'and' }), | ||
handler: (req, reply) => { | ||
req.log.info('Auth route') | ||
reply.send({ hello: 'world' }) | ||
} | ||
}) | ||
fastify.route({ | ||
method: 'POST', | ||
url: '/singlearraycheckand', | ||
preHandler: fastify.auth([[fastify.verifyNumber, fastify.verifyOdd]]), | ||
handler: (req, reply) => { | ||
req.log.info('Auth route') | ||
reply.send({ hello: 'world' }) | ||
} | ||
}) | ||
fastify.route({ | ||
method: 'POST', | ||
url: '/checkarrayorsingle', | ||
preHandler: fastify.auth([[fastify.verifyNumber, fastify.verifyOdd], fastify.verifyBig]), | ||
handler: (req, reply) => { | ||
req.log.info('Auth route') | ||
reply.send({ hello: 'world' }) | ||
} | ||
}) | ||
fastify.route({ | ||
method: 'POST', | ||
url: '/run-all-or', | ||
@@ -108,0 +168,0 @@ preHandler: fastify.auth([fastify.verifyOdd, fastify.verifyBig, fastify.verifyNumber], { run: 'all' }), |
@@ -9,10 +9,8 @@ 'use strict' | ||
t.teardown(() => { | ||
fastify.close() | ||
t.teardown(async () => { | ||
await fastify.close() | ||
}) | ||
test('boot server', t => { | ||
t.plan(1) | ||
t.before(() => { | ||
fastify = build() | ||
t.error(false) | ||
}) | ||
@@ -56,2 +54,38 @@ | ||
test('And Relation sucess for single [Array] case', t => { | ||
t.plan(2) | ||
fastify.inject({ | ||
method: 'POST', | ||
url: '/singlearrayand', | ||
payload: { | ||
n: 11 | ||
} | ||
}, (err, res) => { | ||
t.error(err) | ||
const payload = JSON.parse(res.payload) | ||
t.same(payload, { hello: 'world' }) | ||
}) | ||
}) | ||
test('And Relation failed for single [Array] case', t => { | ||
t.plan(2) | ||
fastify.inject({ | ||
method: 'POST', | ||
url: '/singlearrayand', | ||
payload: { | ||
n: 10 | ||
} | ||
}, (err, res) => { | ||
t.error(err) | ||
const payload = JSON.parse(res.payload) | ||
t.same(payload, { | ||
error: 'Unauthorized', | ||
message: '`n` is not odd', | ||
statusCode: 401 | ||
}) | ||
}) | ||
}) | ||
test('Or Relation sucess for single case', t => { | ||
@@ -93,2 +127,38 @@ t.plan(2) | ||
test('Or Relation sucess for single [Array] case', t => { | ||
t.plan(2) | ||
fastify.inject({ | ||
method: 'POST', | ||
url: '/singlearrayor', | ||
payload: { | ||
n: 11 | ||
} | ||
}, (err, res) => { | ||
t.error(err) | ||
const payload = JSON.parse(res.payload) | ||
t.same(payload, { hello: 'world' }) | ||
}) | ||
}) | ||
test('Or Relation failed for single [Array] case', t => { | ||
t.plan(2) | ||
fastify.inject({ | ||
method: 'POST', | ||
url: '/singlearrayor', | ||
payload: { | ||
n: 10 | ||
} | ||
}, (err, res) => { | ||
t.error(err) | ||
const payload = JSON.parse(res.payload) | ||
t.same(payload, { | ||
error: 'Unauthorized', | ||
message: '`n` is not odd', | ||
statusCode: 401 | ||
}) | ||
}) | ||
}) | ||
test('And Relation failed for first check', t => { | ||
@@ -171,2 +241,19 @@ t.plan(2) | ||
test('[Array] notation And Relation success', t => { | ||
t.plan(3) | ||
fastify.inject({ | ||
method: 'POST', | ||
url: '/checkarrayand', | ||
payload: { | ||
n: 11 | ||
} | ||
}, (err, res) => { | ||
t.error(err) | ||
const payload = JSON.parse(res.payload) | ||
t.same(payload, { hello: 'world' }) | ||
t.equal(res.statusCode, 200) | ||
}) | ||
}) | ||
test('Or Relation success under first case', t => { | ||
@@ -189,2 +276,19 @@ t.plan(3) | ||
test('[Array] notation Or Relation success under first case', t => { | ||
t.plan(3) | ||
fastify.inject({ | ||
method: 'POST', | ||
url: '/checkarrayor', | ||
payload: { | ||
n: 1 | ||
} | ||
}, (err, res) => { | ||
t.error(err) | ||
const payload = JSON.parse(res.payload) | ||
t.same(payload, { hello: 'world' }) | ||
t.equal(res.statusCode, 200) | ||
}) | ||
}) | ||
test('Or Relation success under second case', t => { | ||
@@ -207,2 +311,19 @@ t.plan(3) | ||
test('[Array] notation Or Relation success under second case', t => { | ||
t.plan(3) | ||
fastify.inject({ | ||
method: 'POST', | ||
url: '/checkarrayor', | ||
payload: { | ||
n: 200 | ||
} | ||
}, (err, res) => { | ||
t.error(err) | ||
const payload = JSON.parse(res.payload) | ||
t.same(payload, { hello: 'world' }) | ||
t.equal(res.statusCode, 200) | ||
}) | ||
}) | ||
test('Or Relation failed for both case', t => { | ||
@@ -228,2 +349,110 @@ t.plan(2) | ||
test('[Array] notation Or Relation failed for both case', t => { | ||
t.plan(2) | ||
fastify.inject({ | ||
method: 'POST', | ||
url: '/checkarrayor', | ||
payload: { | ||
n: 90 | ||
} | ||
}, (err, res) => { | ||
t.error(err) | ||
const payload = JSON.parse(res.payload) | ||
t.same(payload, { | ||
error: 'Unauthorized', | ||
message: '`n` is not big', | ||
statusCode: 401 | ||
}) | ||
}) | ||
}) | ||
test('single [Array] And Relation sucess', t => { | ||
t.plan(2) | ||
fastify.inject({ | ||
method: 'POST', | ||
url: '/singlearraycheckand', | ||
payload: { | ||
n: 11 | ||
} | ||
}, (err, res) => { | ||
t.error(err) | ||
const payload = JSON.parse(res.payload) | ||
t.same(payload, { hello: 'world' }) | ||
}) | ||
}) | ||
test('single [Array] And Relation failed', t => { | ||
t.plan(2) | ||
fastify.inject({ | ||
method: 'POST', | ||
url: '/singlearraycheckand', | ||
payload: { | ||
n: 10 | ||
} | ||
}, (err, res) => { | ||
t.error(err) | ||
const payload = JSON.parse(res.payload) | ||
t.same(payload, { | ||
error: 'Unauthorized', | ||
message: '`n` is not odd', | ||
statusCode: 401 | ||
}) | ||
}) | ||
}) | ||
test('[Array] notation & single case Or Relation sucess under first case', t => { | ||
t.plan(2) | ||
fastify.inject({ | ||
method: 'POST', | ||
url: '/checkarrayorsingle', | ||
payload: { | ||
n: 11 | ||
} | ||
}, (err, res) => { | ||
t.error(err) | ||
const payload = JSON.parse(res.payload) | ||
t.same(payload, { hello: 'world' }) | ||
}) | ||
}) | ||
test('[Array] notation & single case Or Relation sucess under second case', t => { | ||
t.plan(2) | ||
fastify.inject({ | ||
method: 'POST', | ||
url: '/checkarrayorsingle', | ||
payload: { | ||
n: 1002 | ||
} | ||
}, (err, res) => { | ||
t.error(err) | ||
const payload = JSON.parse(res.payload) | ||
t.same(payload, { hello: 'world' }) | ||
}) | ||
}) | ||
test('[Array] notation & single case Or Relation failed', t => { | ||
t.plan(2) | ||
fastify.inject({ | ||
method: 'POST', | ||
url: '/checkarrayorsingle', | ||
payload: { | ||
n: 2 | ||
} | ||
}, (err, res) => { | ||
t.error(err) | ||
const payload = JSON.parse(res.payload) | ||
t.same(payload, { | ||
error: 'Unauthorized', | ||
message: '`n` is not big', | ||
statusCode: 401 | ||
}) | ||
}) | ||
}) | ||
test('Check run all line fail with AND', t => { | ||
@@ -230,0 +459,0 @@ t.plan(8) |
@@ -5,3 +5,3 @@ 'use strict' | ||
const test = t.test | ||
const rimraf = require('rimraf') | ||
const { rimrafSync } = require('rimraf') | ||
const build = require('./example') | ||
@@ -12,15 +12,10 @@ | ||
t.teardown(() => { | ||
fastify.close() | ||
rimraf('./authdb', err => { | ||
if (err) throw err | ||
}) | ||
t.before(() => { | ||
rimrafSync('./authdb') | ||
fastify = build() | ||
}) | ||
test('boot server', t => { | ||
t.plan(1) | ||
rimraf('./authdb', err => { | ||
fastify = build() | ||
t.error(err) | ||
}) | ||
t.teardown(async () => { | ||
await fastify.close() | ||
rimrafSync('./authdb') | ||
}) | ||
@@ -27,0 +22,0 @@ |
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
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
59683
17
1795
223