From d6702fb9c767c704a4814e8550369c33e5665629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Go=CC=81mez=20Bachiller?= Date: Sat, 25 Nov 2023 06:17:48 +0100 Subject: [PATCH] fix: add stricts eslint rules --- package.json | 2 + src/auth/access-map.ts | 4 +- src/auth/authorized.ts | 4 +- src/auth/security.ts | 4 +- src/index.ts | 8 ++- src/request/is-internal-request.ts | 9 ++- .../request-matcher/path-request-matcher.ts | 2 +- src/request/roles-for-session.ts | 2 +- tests/auth/access-map.spec.ts | 2 +- tests/auth/auth-firewall.spec.ts | 6 +- tests/auth/security.spec.ts | 2 +- tests/features/request.examples.ts | 36 +++++----- tests/request/is-internal-requests.spec.ts | 12 ++-- tests/request/roles-for-session.spec.ts | 2 +- yarn.lock | 70 ++++++++++++++++++- 15 files changed, 119 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index c918a5c..eaa7cbf 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-sort": "^2.11.0", + "eslint-plugin-unicorn": "^49.0.0", "eslint-plugin-unused-imports": "^3.0.0", "husky": "^8.0.3", "jest": "^29.7.0", @@ -57,6 +58,7 @@ "jest": true }, "extends": [ + "plugin:unicorn/recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended", "plugin:jest/recommended" diff --git a/src/auth/access-map.ts b/src/auth/access-map.ts index 7441180..d7f4a31 100644 --- a/src/auth/access-map.ts +++ b/src/auth/access-map.ts @@ -9,7 +9,7 @@ interface AccessMapEntry { } interface AccessMapPattern { - roles: string[] | InternalRole[] | null + roles: string[] | InternalRole[] } export default class AccessMap { @@ -32,6 +32,6 @@ export default class AccessMap { } } - return { roles: null } + return { roles: [] } } } diff --git a/src/auth/authorized.ts b/src/auth/authorized.ts index bc210f7..c4be582 100644 --- a/src/auth/authorized.ts +++ b/src/auth/authorized.ts @@ -9,6 +9,6 @@ export default function Authorized(config: NextAuthFirewallConfig) { const security = new Security({ accessControlRules }) - return (params: { auth: Session | null; request: NextRequest }) => - security.authorized(params) + return (parameters: { auth: Session | null; request: NextRequest }) => + security.authorized(parameters) } diff --git a/src/auth/security.ts b/src/auth/security.ts index aeb1227..dbd7ae3 100644 --- a/src/auth/security.ts +++ b/src/auth/security.ts @@ -20,11 +20,11 @@ class Security { this.accessMap = this.createAccessMap(config) } - async authorized(params: { + async authorized(parameters: { auth: Session | null request: Request | NextRequest }): Promise { - const { auth, request } = params + const { auth, request } = parameters const { roles } = this.accessMap.pattern(request) if (!roles || roles.includes('PUBLIC_ACCESS')) { diff --git a/src/index.ts b/src/index.ts index dc1b74a..59e5d4d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,10 @@ import { NextAuthFirewallConfig } from './types' export type * from './types' -type AppRouteHandlers = Record<'POST', (req: NextRequest) => Promise> +type AppRouteHandlers = Record< + 'POST', + (request: NextRequest) => Promise +> export interface NextAuthFirewallResult extends NextAuthResult { firewallHandler: AppRouteHandlers @@ -18,7 +21,8 @@ export default function NextAuthFirewall( ): NextAuthFirewallResult { const { accessControl, callbacks = {}, ...rest } = config - const firewallHttpHandler = (req: NextRequest) => AuthFirewall(req, config) + const firewallHttpHandler = (request: NextRequest) => + AuthFirewall(request, config) const nextAuth = NextAuth({ callbacks: { diff --git a/src/request/is-internal-request.ts b/src/request/is-internal-request.ts index 88b651c..fc19783 100644 --- a/src/request/is-internal-request.ts +++ b/src/request/is-internal-request.ts @@ -1,22 +1,21 @@ import { NextRequest } from 'next/server' -import * as process from 'process' -const INTERNAL_ADDRESS = ['127.0.0.1', '::ffff:127.0.0.1', '::1'] +const INTERNAL_ADDRESS = new Set(['127.0.0.1', '::ffff:127.0.0.1', '::1']) function isInternalRequest(request: NextRequest): boolean { const authorization = request.headers.get('authorization') const forwardedFor = request.headers.get('x-forwarded-for') - if (!process.env.AUTH_SECRET) { + if (!process.env?.AUTH_SECRET) { console.warn('Set AUTH_SECRET environment variable') return false } if ( - authorization !== `Bearer ${process.env.AUTH_SECRET}` || + authorization !== `Bearer ${process.env?.AUTH_SECRET}` || !forwardedFor || - !INTERNAL_ADDRESS.includes(forwardedFor) || + !INTERNAL_ADDRESS.has(forwardedFor) || request.method.toUpperCase() !== 'POST' ) { console.warn( diff --git a/src/request/request-matcher/path-request-matcher.ts b/src/request/request-matcher/path-request-matcher.ts index 4f7a196..27c97a5 100644 --- a/src/request/request-matcher/path-request-matcher.ts +++ b/src/request/request-matcher/path-request-matcher.ts @@ -13,6 +13,6 @@ export default class PathRequestMatcher implements RequestMatcherInterface { matches(request: Request | NextRequest): boolean { const url = new URL(request.url) - return !!url.pathname.match(this.pathname) + return this.pathname.test(url.pathname) } } diff --git a/src/request/roles-for-session.ts b/src/request/roles-for-session.ts index 3f18417..9e80d74 100644 --- a/src/request/roles-for-session.ts +++ b/src/request/roles-for-session.ts @@ -3,7 +3,7 @@ import { Session } from '@auth/core/types' const USER_PROVIDER_URL = `${process.env.AUTH_URL as string}/firewall/` export default async function rolesForSession( - auth: Session | null, + auth?: Session | null, ): Promise { const email = auth?.user?.email diff --git a/tests/auth/access-map.spec.ts b/tests/auth/access-map.spec.ts index 4b88dfe..4c1a93f 100644 --- a/tests/auth/access-map.spec.ts +++ b/tests/auth/access-map.spec.ts @@ -50,6 +50,6 @@ describe('AccessMap', () => { const pattern = accessMap.pattern(request) // Assert - expect(pattern).toEqual({ roles: null }) + expect(pattern).toEqual({ roles: [] }) }) }) diff --git a/tests/auth/auth-firewall.spec.ts b/tests/auth/auth-firewall.spec.ts index 043b86e..8fc1f86 100644 --- a/tests/auth/auth-firewall.spec.ts +++ b/tests/auth/auth-firewall.spec.ts @@ -3,17 +3,17 @@ import AuthFirewall from '../../src/auth/auth-firewall' import RequestExamples from '../features/request.examples' describe('AuthFirewall', () => { - const env = process.env + const environment = process.env beforeEach(() => { process.env = { - ...env, + ...environment, AUTH_SECRET: 'AUTH_SECRET', } }) afterEach(() => { - process.env = env + process.env = environment }) it('should return user roles from email', async () => { diff --git a/tests/auth/security.spec.ts b/tests/auth/security.spec.ts index a53f2cf..7f75c20 100644 --- a/tests/auth/security.spec.ts +++ b/tests/auth/security.spec.ts @@ -5,7 +5,7 @@ import RequestExamples from '../features/request.examples' jest.mock('../../src/request/roles-for-session') -// eslint-disable-next-line @typescript-eslint/no-var-requires +// eslint-disable-next-line @typescript-eslint/no-var-requires,unicorn/prefer-module const rolesForSession = require('../../src/request/roles-for-session').default describe('Security', () => { diff --git a/tests/features/request.examples.ts b/tests/features/request.examples.ts index 16baeb8..ccccc2c 100644 --- a/tests/features/request.examples.ts +++ b/tests/features/request.examples.ts @@ -1,21 +1,11 @@ import { NextRequest } from 'next/server' -export default class RequestExamples { - static basic(path: string = '/', options: { method?: string } = {}) { +const RequestExamples = { + basic: (path: string = '/', options: { method?: string } = {}) => { return new NextRequest(new URL(`http://internal${path}`), options) - } + }, - static internalAuthorized() { - return new NextRequest(new URL('http://internal'), { - headers: { - authorization: 'Bearer AUTH_SECRET', - 'x-forwarded-for': '::1', - }, - method: 'POST', - }) - } - - static externalAuthorized() { + externalAuthorized: () => { return new NextRequest(new URL('http://internal'), { headers: { authorization: 'Bearer AUTH_SECRET', @@ -23,9 +13,9 @@ export default class RequestExamples { }, method: 'POST', }) - } + }, - static firewallRequest() { + firewallRequest: () => { return { headers: new Headers({ authorization: 'Bearer AUTH_SECRET', @@ -34,5 +24,17 @@ export default class RequestExamples { json: jest.fn().mockResolvedValue({ email: 'test@example.com' }), method: 'POST', } as unknown as NextRequest - } + }, + + internalAuthorized: () => { + return new NextRequest(new URL('http://internal'), { + headers: { + authorization: 'Bearer AUTH_SECRET', + 'x-forwarded-for': '::1', + }, + method: 'POST', + }) + }, } + +export default RequestExamples diff --git a/tests/request/is-internal-requests.spec.ts b/tests/request/is-internal-requests.spec.ts index 8fb4193..f37cf6c 100644 --- a/tests/request/is-internal-requests.spec.ts +++ b/tests/request/is-internal-requests.spec.ts @@ -2,16 +2,16 @@ import { isInternalRequest } from '../../src/request/is-internal-request' import RequestExamples from '../features/request.examples' describe('is-internal-request', () => { - const env = process.env + const environment = process.env afterEach(() => { - process.env = env + process.env = environment }) it('should returns true if request is internal', () => { // Arrange process.env = { - ...env, + ...environment, AUTH_SECRET: 'AUTH_SECRET', } const request = RequestExamples.internalAuthorized() @@ -26,7 +26,7 @@ describe('is-internal-request', () => { it('should returns false if AUTH_SECRET is not configured', () => { // Arrange process.env = { - ...env, + ...environment, } const request = RequestExamples.internalAuthorized() @@ -40,7 +40,7 @@ describe('is-internal-request', () => { it('should returns false if AUTH_SECRET is invalid', () => { // Arrange process.env = { - ...env, + ...environment, AUTH_SECRET: 'OTHER_AUTH_SECRET', } const request = RequestExamples.internalAuthorized() @@ -55,7 +55,7 @@ describe('is-internal-request', () => { it('should returns false if request is external', () => { // Arrange process.env = { - ...env, + ...environment, AUTH_SECRET: 'AUTH_SECRET', } const request = RequestExamples.externalAuthorized() diff --git a/tests/request/roles-for-session.spec.ts b/tests/request/roles-for-session.spec.ts index 4b27867..2c6dd86 100644 --- a/tests/request/roles-for-session.spec.ts +++ b/tests/request/roles-for-session.spec.ts @@ -26,7 +26,7 @@ describe('rolesForSession', () => { it('should return an empty array for an invalid session', async () => { // Arrange - const mockSession = null + const mockSession = undefined // Act const result = await rolesForSession(mockSession) diff --git a/yarn.lock b/yarn.lock index b08b43b..a467afb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2043,6 +2043,11 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + builtins@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.0.1.tgz#87f6db9ab0458be728564fa81d876d8d74552fa9" @@ -2160,7 +2165,7 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== -ci-info@^3.2.0: +ci-info@^3.2.0, ci-info@^3.8.0: version "3.9.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== @@ -2182,6 +2187,13 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== +clean-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7" + integrity sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw== + dependencies: + escape-string-regexp "^1.0.5" + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" @@ -2849,6 +2861,26 @@ eslint-plugin-sort@^2.11.0: isomorphic-resolve "^1.0.0" natural-compare "^1.4.0" +eslint-plugin-unicorn@^49.0.0: + version "49.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-49.0.0.tgz#4449ea954d7e1455eec8518f9417d7021b245fa8" + integrity sha512-0fHEa/8Pih5cmzFW5L7xMEfUTvI9WKeQtjmKpTUmY+BiFCDxkxrTdnURJOHKykhtwIeyYsxnecbGvDCml++z4Q== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + "@eslint-community/eslint-utils" "^4.4.0" + ci-info "^3.8.0" + clean-regexp "^1.0.0" + esquery "^1.5.0" + indent-string "^4.0.0" + is-builtin-module "^3.2.1" + jsesc "^3.0.2" + pluralize "^8.0.0" + read-pkg-up "^7.0.1" + regexp-tree "^0.1.27" + regjsparser "^0.10.0" + semver "^7.5.4" + strip-indent "^3.0.0" + eslint-plugin-unused-imports@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.0.0.tgz#d25175b0072ff16a91892c3aa72a09ca3a9e69e7" @@ -2940,7 +2972,7 @@ esprima@^4.0.0, esprima@~4.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.2: +esquery@^1.4.2, esquery@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== @@ -3755,6 +3787,13 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-builtin-module@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" @@ -4435,6 +4474,16 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +jsesc@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== + json-buffer@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" @@ -5755,6 +5804,11 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + postcss-selector-parser@^6.0.10: version "6.0.13" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b" @@ -6030,6 +6084,11 @@ redeyed@~2.1.0: dependencies: esprima "~4.0.0" +regexp-tree@^0.1.27: + version "0.1.27" + resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" + integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== + regexp.prototype.flags@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" @@ -6046,6 +6105,13 @@ registry-auth-token@^5.0.0: dependencies: "@pnpm/npm-conf" "^2.1.0" +regjsparser@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.10.0.tgz#b1ed26051736b436f22fdec1c8f72635f9f44892" + integrity sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA== + dependencies: + jsesc "~0.5.0" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"