diff --git a/packages/backend/src/helpers/authentication.js b/packages/backend/src/helpers/authentication.js index 90b99c35..a8929b36 100644 --- a/packages/backend/src/helpers/authentication.js +++ b/packages/backend/src/helpers/authentication.js @@ -3,7 +3,7 @@ import jwt from 'jsonwebtoken'; import appConfig from '../config/app.js'; import User from '../models/user.js'; -const isAuthenticated = rule()(async (_parent, _args, req) => { +export const isAuthenticated = async (_parent, _args, req) => { const token = req.headers['authorization']; if (token == null) return false; @@ -26,29 +26,32 @@ const isAuthenticated = rule()(async (_parent, _args, req) => { } catch (error) { return false; } -}); +}; -const authentication = shield( - { - Query: { - '*': isAuthenticated, - getAutomatischInfo: allow, - getConfig: allow, - getNotifications: allow, - healthcheck: allow, - listSamlAuthProviders: allow, - }, - Mutation: { - '*': isAuthenticated, - forgotPassword: allow, - login: allow, - registerUser: allow, - resetPassword: allow, - }, +const isAuthenticatedRule = rule()(isAuthenticated); + +export const authenticationRules = { + Query: { + '*': isAuthenticatedRule, + getAutomatischInfo: allow, + getConfig: allow, + getNotifications: allow, + healthcheck: allow, + listSamlAuthProviders: allow, }, - { - allowExternalErrors: true, - } -); + Mutation: { + '*': isAuthenticatedRule, + forgotPassword: allow, + login: allow, + registerUser: allow, + resetPassword: allow, + }, +}; + +export const authenticationOptions = { + allowExternalErrors: true, +}; + +const authentication = shield(authenticationRules, authenticationOptions); export default authentication; diff --git a/packages/backend/src/helpers/authentication.test.js b/packages/backend/src/helpers/authentication.test.js new file mode 100644 index 00000000..8c448061 --- /dev/null +++ b/packages/backend/src/helpers/authentication.test.js @@ -0,0 +1,82 @@ +import { describe, it, expect, vi } from 'vitest'; +import { allow } from 'graphql-shield'; +import jwt from 'jsonwebtoken'; +import User from '../models/user.js'; +import { + isAuthenticated, + isAuthenticatedRule, + authenticationRules, +} from './authentication.js'; + +vi.mock('jsonwebtoken'); +vi.mock('../models/user.js'); + +describe('isAuthenticated', () => { + it('should return false if no token is provided', async () => { + const req = { headers: {} }; + expect(await isAuthenticated(null, null, req)).toBe(false); + }); + + it('should return false if token is invalid', async () => { + jwt.verify.mockImplementation(() => { + throw new Error('invalid token'); + }); + + const req = { headers: { authorization: 'invalidToken' } }; + expect(await isAuthenticated(null, null, req)).toBe(false); + }); + + it('should return true if token is valid', async () => { + jwt.verify.mockReturnValue({ userId: '123' }); + + User.query.mockReturnValue({ + findById: vi.fn().mockReturnValue({ + leftJoinRelated: vi.fn().mockReturnThis(), + withGraphFetched: vi + .fn() + .mockResolvedValue({ id: '123', role: {}, permissions: {} }), + }), + }); + + const req = { headers: { authorization: 'validToken' } }; + expect(await isAuthenticated(null, null, req)).toBe(true); + }); +}); + +describe('authentication rules', () => { + const getQueryAndMutationNames = (rules) => { + const queries = Object.keys(rules.Query || {}); + const mutations = Object.keys(rules.Mutation || {}); + return { queries, mutations }; + }; + + const { queries, mutations } = getQueryAndMutationNames(authenticationRules); + + describe('for queries', () => { + queries.forEach((query) => { + it(`should apply correct rule for query: ${query}`, () => { + const ruleApplied = authenticationRules.Query[query]; + + if (query === '*') { + expect(ruleApplied.func).toBe(isAuthenticated); + } else { + expect(ruleApplied).toEqual(allow); + } + }); + }); + }); + + describe('for mutations', () => { + mutations.forEach((mutation) => { + it(`should apply correct rule for mutation: ${mutation}`, () => { + const ruleApplied = authenticationRules.Mutation[mutation]; + + if (mutation === '*') { + expect(ruleApplied.func).toBe(isAuthenticated); + } else { + expect(ruleApplied).toBe(allow); + } + }); + }); + }); +});