diff --git a/README.md b/README.md index 6e1af48..a28d686 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,7 @@ await fastify.ready() | baseDir | undefined | Specify the directory where all spec files that are included in the main one using $ref will be located. By default, this is the directory where the main spec file is located. Provided value should be an absolute path without trailing slash. | | initOAuth | {} | Configuration options for [Swagger UI initOAuth](https://swagger.io/docs/open-source-tools/swagger-ui/usage/oauth2/). | | routePrefix | '/documentation' | Overwrite the default Swagger UI route prefix. | + | noRedirect | false | Switches from redirection to `static/index.html` to serving the UI directly at the route prefix for a cleaner url. | staticCSP | false | Enable CSP header for static resources. | | transformStaticCSP | undefined | Synchronous function to transform CSP header for static resources if the header has been previously set. | | transformSpecification | undefined | Synchronous function to transform the swagger document. | diff --git a/lib/index-html.js b/lib/index-html.js index 518c4a6..00471e9 100644 --- a/lib/index-html.js +++ b/lib/index-html.js @@ -7,23 +7,23 @@ function indexHtml (opts) { ${opts.theme?.title || 'Swagger UI'} - - - ${opts.theme && opts.theme.css ? opts.theme.css.map(css => `\n`).join('') : ''} + + + ${opts.theme && opts.theme.css ? opts.theme.css.map(css => `\n`).join('') : ''} ${opts.theme && opts.theme.favicon -? opts.theme.favicon.map(favicon => `\n`).join('') +? opts.theme.favicon.map(favicon => `\n`).join('') : ` - - + + `}
- - - - ${opts.theme && opts.theme.js ? opts.theme.js.map(js => `\n`).join('') : ''} + + + + ${opts.theme && opts.theme.js ? opts.theme.js.map(js => `\n`).join('') : ''} ` diff --git a/lib/routes.js b/lib/routes.js index a179ff8..1bae2de 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -10,19 +10,6 @@ const indexHtml = require('./index-html') // URI prefix to separate static assets for swagger UI const staticPrefix = '/static' -function getRedirectPathForTheRootRoute (url) { - let redirectPath - - if (url.length !== 0 && url[url.length - 1] === '/') { - redirectPath = `.${staticPrefix}/index.html` - } else { - const urlPathParts = url.split('/') - redirectPath = `./${urlPathParts[urlPathParts.length - 1]}${staticPrefix}/index.html` - } - - return redirectPath -} - function fastifySwagger (fastify, opts, done) { let staticCSP = false if (opts.staticCSP === true) { @@ -66,16 +53,6 @@ function fastifySwagger (fastify, opts, done) { } } - fastify.route({ - url: '/', - method: 'GET', - schema: { hide: true }, - ...hooks, - handler: (req, reply) => { - reply.redirect(getRedirectPathForTheRootRoute(req.raw.url)) - } - }) - if (opts.theme) { const themePrefix = `${staticPrefix}/theme` if (opts.theme.css) { @@ -127,10 +104,10 @@ function fastifySwagger (fastify, opts, done) { } } - const indexHtmlContent = indexHtml(opts) + const indexHtmlContent = indexHtml({ ...opts, staticPrefix }) fastify.route({ - url: `${staticPrefix}/index.html`, + url: '/', method: 'GET', schema: { hide: true }, ...hooks, @@ -141,6 +118,16 @@ function fastifySwagger (fastify, opts, done) { } }) + fastify.route({ + url: `${staticPrefix}/index.html`, + method: 'GET', + schema: { hide: true }, + ...hooks, + handler: (req, reply) => { + reply.redirect('/') + } + }) + const swaggerInitializerContent = swaggerInitializer(opts) fastify.route({ diff --git a/test/csp.test.js b/test/csp.test.js index 6d59260..af3d650 100644 --- a/test/csp.test.js +++ b/test/csp.test.js @@ -31,7 +31,7 @@ test('staticCSP = undefined', async (t) => { const res = await fastify.inject({ method: 'GET', - url: '/documentation/static/index.html' + url: '/documentation' }) t.equal(res.statusCode, 200) t.equal(typeof res.headers['content-security-policy'], 'undefined') @@ -57,7 +57,7 @@ test('staticCSP = true', async (t) => { { const res = await fastify.inject({ method: 'GET', - url: '/documentation/static/index.html' + url: '/documentation' }) t.equal(res.statusCode, 200) t.equal(res.headers['content-security-policy'], `default-src 'self'; base-uri 'self'; font-src 'self' https: data:; frame-ancestors 'self'; img-src 'self' data: validator.swagger.io; object-src 'none'; script-src 'self' ${csp.script.join(' ')}; script-src-attr 'none'; style-src 'self' https: ${csp.style.join(' ')}; upgrade-insecure-requests;`) @@ -93,7 +93,7 @@ test('staticCSP = "default-src \'self\';"', async (t) => { { const res = await fastify.inject({ method: 'GET', - url: '/documentation/static/index.html' + url: '/documentation' }) t.equal(res.statusCode, 200) t.equal(res.headers['content-security-policy'], "default-src 'self';") @@ -132,7 +132,7 @@ test('staticCSP = object', async (t) => { { const res = await fastify.inject({ method: 'GET', - url: '/documentation/static/index.html' + url: '/documentation' }) t.equal(res.statusCode, 200) t.equal(res.headers['content-security-policy'], "default-src 'self'; script-src 'self';") @@ -172,7 +172,7 @@ test('transformStaticCSP = function', async (t) => { { const res = await fastify.inject({ method: 'GET', - url: '/documentation/static/index.html' + url: '/documentation' }) t.equal(res.statusCode, 200) t.equal(res.headers['content-security-policy'], "default-src 'self'; script-src 'self';") @@ -212,7 +212,7 @@ test('transformStaticCSP = function, with @fastify/helmet', async (t) => { { const res = await fastify.inject({ method: 'GET', - url: '/documentation/static/index.html' + url: '/documentation' }) t.equal(res.statusCode, 200) t.equal(res.headers['content-security-policy'], "default-src 'self'; script-src 'self';") diff --git a/test/route.test.js b/test/route.test.js index 56e41aa..457a755 100644 --- a/test/route.test.js +++ b/test/route.test.js @@ -100,8 +100,8 @@ test('fastify.swagger should return a valid swagger yaml', async (t) => { t.pass('valid swagger yaml') }) -test('/documentation should redirect to ./documentation/static/index.html', async (t) => { - t.plan(3) +test('/documentation should display index html', async (t) => { + t.plan(4) const fastify = Fastify() await fastify.register(fastifySwagger, swaggerOption) await fastify.register(fastifySwaggerUi) @@ -117,13 +117,14 @@ test('/documentation should redirect to ./documentation/static/index.html', asyn method: 'GET', url: '/documentation' }) - t.equal(res.statusCode, 302) - t.equal(res.headers.location, './documentation/static/index.html') + t.equal(res.statusCode, 200) + t.equal(res.headers.location, undefined) t.equal(typeof res.payload, 'string') + t.equal('text/html; charset=utf-8', res.headers['content-type']) }) -test('/documentation/ should redirect to ./static/index.html', async (t) => { - t.plan(3) +test('/documentation/ should display index html ', async (t) => { + t.plan(4) const fastify = Fastify() await fastify.register(fastifySwagger, swaggerOption) await fastify.register(fastifySwaggerUi) @@ -139,13 +140,14 @@ test('/documentation/ should redirect to ./static/index.html', async (t) => { method: 'GET', url: '/documentation/' }) - t.equal(res.statusCode, 302) - t.equal(res.headers.location, './static/index.html') + t.equal(res.statusCode, 200) + t.equal(res.headers.location, undefined) t.equal(typeof res.payload, 'string') + t.equal('text/html; charset=utf-8', res.headers['content-type']) }) -test('/v1/documentation should redirect to ./documentation/static/index.html', async (t) => { - t.plan(3) +test('/v1/documentation should display index html', async (t) => { + t.plan(4) const fastify = Fastify() await fastify.register(fastifySwagger, swaggerOption) await fastify.register(fastifySwaggerUi, { routePrefix: '/v1/documentation' }) @@ -161,13 +163,14 @@ test('/v1/documentation should redirect to ./documentation/static/index.html', a method: 'GET', url: '/v1/documentation' }) - t.equal(res.statusCode, 302) - t.equal(res.headers.location, './documentation/static/index.html') + t.equal(res.statusCode, 200) + t.equal(res.headers.location, undefined) t.equal(typeof res.payload, 'string') + t.equal('text/html; charset=utf-8', res.headers['content-type']) }) -test('/v1/documentation/ should redirect to ./static/index.html', async (t) => { - t.plan(3) +test('/v1/documentation/ should display index html', async (t) => { + t.plan(4) const fastify = Fastify() await fastify.register(fastifySwagger, swaggerOption) await fastify.register(fastifySwaggerUi, { routePrefix: '/v1/documentation' }) @@ -183,18 +186,19 @@ test('/v1/documentation/ should redirect to ./static/index.html', async (t) => { method: 'GET', url: '/v1/documentation/' }) - t.equal(res.statusCode, 302) - t.equal(res.headers.location, './static/index.html') + t.equal(res.statusCode, 200) + t.equal(res.headers.location, undefined) t.equal(typeof res.payload, 'string') + t.equal('text/html; charset=utf-8', res.headers['content-type']) }) -test('/v1/foobar should redirect to ./foobar/static/index.html - in plugin', async (t) => { - t.plan(3) +test('/v1/foobar should display index html', async (t) => { + t.plan(4) const fastify = Fastify() fastify.register(async function (fastify, options) { await fastify.register(fastifySwagger, swaggerOption) - await fastify.register(fastifySwaggerUi, { routePrefix: '/foobar' }) + await fastify.register(fastifySwaggerUi, { routePrefix: '/foobar', noRedirect: true }) fastify.get('/', () => {}) fastify.post('/', () => {}) @@ -208,13 +212,14 @@ test('/v1/foobar should redirect to ./foobar/static/index.html - in plugin', asy method: 'GET', url: '/v1/foobar' }) - t.equal(res.statusCode, 302) - t.equal(res.headers.location, './foobar/static/index.html') + t.equal(res.statusCode, 200) + t.equal(res.headers.location, undefined) t.equal(typeof res.payload, 'string') + t.equal('text/html; charset=utf-8', res.headers['content-type']) }) -test('/v1/foobar/ should redirect to ./static/index.html - in plugin', async (t) => { - t.plan(3) +test('/v1/foobar/ should display index html', async (t) => { + t.plan(4) const fastify = Fastify() fastify.register(async function (fastify, options) { @@ -233,13 +238,14 @@ test('/v1/foobar/ should redirect to ./static/index.html - in plugin', async (t) method: 'GET', url: '/v1/foobar/' }) - t.equal(res.statusCode, 302) - t.equal(res.headers.location, './static/index.html') + t.equal(res.statusCode, 200) + t.equal(res.headers.location, undefined) t.equal(typeof res.payload, 'string') + t.equal('text/html; charset=utf-8', res.headers['content-type']) }) -test('with routePrefix: \'/\' should redirect to ./static/index.html', async (t) => { - t.plan(3) +test('with routePrefix: \'/\' should display index html', async (t) => { + t.plan(4) const fastify = Fastify() await fastify.register(fastifySwagger, swaggerOption) @@ -251,9 +257,10 @@ test('with routePrefix: \'/\' should redirect to ./static/index.html', async (t) method: 'GET', url: '/' }) - t.equal(res.statusCode, 302) - t.equal(res.headers.location, './static/index.html') + t.equal(res.statusCode, 200) + t.equal(res.headers.location, undefined) t.equal(typeof res.payload, 'string') + t.equal('text/html; charset=utf-8', res.headers['content-type']) }) test('/documentation/static/:file should send back the correct file', async (t) => { @@ -275,10 +282,10 @@ test('/documentation/static/:file should send back the correct file', async (t) { const res = await fastify.inject({ method: 'GET', - url: '/documentation/' + url: '/documentation/static/index.html' }) t.equal(res.statusCode, 302) - t.equal(res.headers.location, './static/index.html') + t.equal(res.headers.location, '/') } { @@ -481,20 +488,6 @@ test('/documentation/:myfile should run custom NotFoundHandler in dynamic mode', t.equal(res.statusCode, 410) }) -test('/documentation/ should redirect to ./static/index.html', async (t) => { - t.plan(2) - const fastify = Fastify() - await fastify.register(fastifySwagger, swaggerOption) - await fastify.register(fastifySwaggerUi) - - const res = await fastify.inject({ - method: 'GET', - url: '/documentation/' - }) - t.equal(res.statusCode, 302) - t.equal(res.headers.location, './static/index.html') -}) - test('/documentation/* should not return module files when baseDir not set', async (t) => { t.plan(1) const fastify = Fastify() @@ -522,8 +515,8 @@ test('should return silent log level of route /documentation', async (t) => { method: 'GET', url: '/documentation/' }) - t.equal(res.statusCode, 302) - t.equal(res.headers.location, './static/index.html') + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], 'text/html; charset=utf-8') }) test('should return empty log level of route /documentation', async (t) => { @@ -540,6 +533,23 @@ test('should return empty log level of route /documentation', async (t) => { method: 'GET', url: '/documentation/' }) - t.equal(res.statusCode, 302) - t.equal(res.headers.location, './static/index.html') + t.equal(res.statusCode, 200) + t.equal(res.headers['content-type'], 'text/html; charset=utf-8') +}) + +test('/documentation should display index html with correct asset urls', async (t) => { + t.plan(4) + const fastify = Fastify() + await fastify.register(fastifySwagger, swaggerOption) + await fastify.register(fastifySwaggerUi, { theme: { js: [{ filename: 'theme-js.js' }] } }) + + const res = await fastify.inject({ + method: 'GET', + url: '/documentation' + }) + + t.equal(res.payload.includes('href="./static/index.css"'), true) + t.equal(res.payload.includes('src="./static/theme/theme-js.js"'), true) + t.equal(res.payload.includes('href="./index.css"'), false) + t.equal(res.payload.includes('src="./theme/theme-js.js"'), false) }) diff --git a/test/theme.test.js b/test/theme.test.js index 894004b..755e2c7 100644 --- a/test/theme.test.js +++ b/test/theme.test.js @@ -20,7 +20,7 @@ test('swagger route does not return additional theme', async (t) => { const res = await fastify.inject({ method: 'GET', - url: '/documentation/static/index.html' + url: '/documentation' }) t.equal(typeof res.payload, 'string') @@ -63,7 +63,7 @@ test('swagger route returns additional theme', async (t) => { const res = await fastify.inject({ method: 'GET', - url: '/documentation/static/index.html' + url: '/documentation' }) t.equal(typeof res.payload, 'string') @@ -119,7 +119,7 @@ test('swagger route returns additional theme - only js', async (t) => { const res = await fastify.inject({ method: 'GET', - url: '/documentation/static/index.html' + url: '/documentation' }) t.equal(typeof res.payload, 'string') @@ -156,7 +156,7 @@ test('swagger route returns additional theme - only css', async (t) => { const res = await fastify.inject({ method: 'GET', - url: '/documentation/static/index.html' + url: '/documentation' }) t.equal(typeof res.payload, 'string') @@ -199,7 +199,7 @@ test('swagger route returns additional theme - only favicon', async (t) => { const res = await fastify.inject({ method: 'GET', - url: '/documentation/static/index.html' + url: '/documentation' }) t.equal(typeof res.payload, 'string') @@ -235,7 +235,7 @@ test('swagger route returns additional theme - only title', async (t) => { const res = await fastify.inject({ method: 'GET', - url: '/documentation/static/index.html' + url: '/documentation' }) t.equal(typeof res.payload, 'string')