diff --git a/.changeset/olive-stingrays-share.md b/.changeset/olive-stingrays-share.md new file mode 100644 index 0000000000000000000000000000000000000000..af0d69721729c2e5333eb71cda4095748013c268 --- /dev/null +++ b/.changeset/olive-stingrays-share.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/cas-validate": patch +--- + +Fixes a problem with CAS when URL connection ended with `/` diff --git a/packages/cas-validate/jest.config.ts b/packages/cas-validate/jest.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..90df717c7694523ba744995b986c4de66b37ffc2 --- /dev/null +++ b/packages/cas-validate/jest.config.ts @@ -0,0 +1,12 @@ +import server from '@rocket.chat/jest-presets/server'; +import type { Config } from 'jest'; + +export default { + projects: [ + { + displayName: 'server', + preset: server.preset, + testMatch: ['<rootDir>/src/**/*.spec.[jt]s?(x)'], + }, + ], +} satisfies Config; diff --git a/packages/cas-validate/package.json b/packages/cas-validate/package.json index 5c21ecfa41d4640571b6f6e2cb1e0eff5b3109b5..0a005a4d8cb9058dcfb9729281b15d52f9372953 100644 --- a/packages/cas-validate/package.json +++ b/packages/cas-validate/package.json @@ -11,7 +11,8 @@ "lint": "eslint --ext .js,.jsx,.ts,.tsx .", "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx . --fix", "build": "rm -rf dist && tsc -p tsconfig.json", - "dev": "tsc -p tsconfig.json --watch --preserveWatchOutput" + "dev": "tsc -p tsconfig.json --watch --preserveWatchOutput", + "testunit": "jest" }, "main": "./dist/index.js", "typings": "./dist/index.d.ts", @@ -19,6 +20,7 @@ "/dist" ], "dependencies": { - "cheerio": "1.0.0" + "cheerio": "1.0.0", + "jest": "^29.7.0" } } diff --git a/packages/cas-validate/src/validate.spec.ts b/packages/cas-validate/src/validate.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..5fab05761532c0ba6c01b30c3094a36fb341393d --- /dev/null +++ b/packages/cas-validate/src/validate.spec.ts @@ -0,0 +1,53 @@ +import { getQueryPath } from './validate'; + +describe('getQueryPath', () => { + it('should generate the correct query path without trailing slash', () => { + const partialPathname = '/cas'; + const validatePath = 'validate'; + const query = { ticket: 'ST-12345', service: 'https://myapp.com' }; + + const result = getQueryPath(partialPathname, validatePath, query); + + expect(result).toBe('/cas/validate?ticket=ST-12345&service=https%3A%2F%2Fmyapp.com'); + }); + + it('should generate the correct query path with trailing slash', () => { + const partialPathname = '/cas/'; + const validatePath = 'validate'; + const query = { ticket: 'ST-12345', service: 'https://myapp.com' }; + + const result = getQueryPath(partialPathname, validatePath, query); + + expect(result).toBe('/cas/validate?ticket=ST-12345&service=https%3A%2F%2Fmyapp.com'); + }); + + it('should generate the correct query path with `/` partialPathname', () => { + const partialPathname = '/'; + const validatePath = 'validate'; + const query = { ticket: 'ST-12345', service: 'https://myapp.com' }; + + const result = getQueryPath(partialPathname, validatePath, query); + + expect(result).toBe('/validate?ticket=ST-12345&service=https%3A%2F%2Fmyapp.com'); + }); + + it('should generate the correct query path with empty partialPathname', () => { + const partialPathname = ''; + const validatePath = 'validate'; + const query = { ticket: 'ST-12345', service: 'https://myapp.com' }; + + const result = getQueryPath(partialPathname, validatePath, query); + + expect(result).toBe('/validate?ticket=ST-12345&service=https%3A%2F%2Fmyapp.com'); + }); + + it('should generate the correct query path with empty query', () => { + const partialPathname = '/cas'; + const validatePath = 'validate'; + const query = {}; + + const result = getQueryPath(partialPathname, validatePath, query); + + expect(result).toBe('/cas/validate'); + }); +}); diff --git a/packages/cas-validate/src/validate.ts b/packages/cas-validate/src/validate.ts index cef47a50a230016c03087dc60d1524eb6b53824d..44cb86676671b5101cc2c673302be63faa3a19a7 100644 --- a/packages/cas-validate/src/validate.ts +++ b/packages/cas-validate/src/validate.ts @@ -1,5 +1,6 @@ import type { IncomingMessage } from 'http'; import https from 'https'; +import type { ParsedUrlQueryInput } from 'querystring'; import url from 'url'; import type { Cheerio, CheerioAPI } from 'cheerio'; @@ -149,6 +150,19 @@ function parseAttributes(elemSuccess: Cheerio<any>, cheerio: CheerioAPI): Record return attributes; } +export function getQueryPath( + partialPathname: string, + validatePath: string, + query: string | ParsedUrlQueryInput | null | undefined, +): string { + const pathname = partialPathname?.endsWith('/') ? partialPathname + validatePath : `${partialPathname}/${validatePath}`; + + return url.format({ + pathname, + query, + }); +} + export function validate(options: CasOptions, ticket: string, callback: CasCallback, renew = false): void { if (!options.base_url) { throw new Error('Required CAS option `base_url` missing.'); @@ -176,10 +190,7 @@ export function validate(options: CasOptions, ticket: string, callback: CasCallb ...(renew ? { renew: 1 } : {}), }; - const queryPath = url.format({ - pathname: `${pathname}/${validatePath}`, - query, - }); + const queryPath = getQueryPath(pathname ?? '/', validatePath, query); const req = https.get( { diff --git a/yarn.lock b/yarn.lock index 40b92d6b52beefb2aba16352d7f57b9c208180f5..3596ddbc6cb5b2946ab81472606cb3ddbf57aaae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7566,6 +7566,7 @@ __metadata: dependencies: cheerio: "npm:1.0.0" eslint: "npm:~8.45.0" + jest: "npm:^29.7.0" typescript: "npm:~5.7.2" languageName: unknown linkType: soft