Skip to content

Commit

Permalink
No longer redirect to ./static/index.html for serving UI
Browse files Browse the repository at this point in the history
  • Loading branch information
jdhollander committed Jun 11, 2024
1 parent 7d1fffc commit 0c494c8
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 96 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down
20 changes: 10 additions & 10 deletions lib/index-html.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@ function indexHtml (opts) {
<head>
<meta charset="UTF-8">
<title>${opts.theme?.title || 'Swagger UI'}</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
<link rel="stylesheet" type="text/css" href="./index.css" />
${opts.theme && opts.theme.css ? opts.theme.css.map(css => `<link rel="stylesheet" type="text/css" href="./theme/${css.filename}" />\n`).join('') : ''}
<link rel="stylesheet" type="text/css" href=".${opts.staticPrefix}/swagger-ui.css" />
<link rel="stylesheet" type="text/css" href=".${opts.staticPrefix}/index.css" />
${opts.theme && opts.theme.css ? opts.theme.css.map(css => `<link rel="stylesheet" type="text/css" href=".${opts.staticPrefix}/theme/${css.filename}" />\n`).join('') : ''}
${opts.theme && opts.theme.favicon
? opts.theme.favicon.map(favicon => `<link rel="${favicon.rel}" type="${favicon.type}" href="./theme/${favicon.filename}" sizes="${favicon.sizes}" />\n`).join('')
? opts.theme.favicon.map(favicon => `<link rel="${favicon.rel}" type="${favicon.type}" href=".${opts.staticPrefix}/theme/${favicon.filename}" sizes="${favicon.sizes}" />\n`).join('')
: `
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<link rel="icon" type="image/png" href=".${opts.staticPrefix}/favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href=".${opts.staticPrefix}/favicon-16x16.png" sizes="16x16" />
`}
</head>
<body>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
<script src="./swagger-initializer.js" charset="UTF-8"> </script>
${opts.theme && opts.theme.js ? opts.theme.js.map(js => `<script src="./theme/${js.filename}" charset="UTF-8"> </script>\n`).join('') : ''}
<script src=".${opts.staticPrefix}/swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src=".${opts.staticPrefix}/swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
<script src=".${opts.staticPrefix}/swagger-initializer.js" charset="UTF-8"> </script>
${opts.theme && opts.theme.js ? opts.theme.js.map(js => `<script src=".${opts.staticPrefix}/theme/${js.filename}" charset="UTF-8"> </script>\n`).join('') : ''}
</body>
</html>
`
Expand Down
37 changes: 12 additions & 25 deletions lib/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand All @@ -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({
Expand Down
12 changes: 6 additions & 6 deletions test/csp.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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;`)
Expand Down Expand Up @@ -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';")
Expand Down Expand Up @@ -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';")
Expand Down Expand Up @@ -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';")
Expand Down Expand Up @@ -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';")
Expand Down
108 changes: 59 additions & 49 deletions test/route.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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' })
Expand All @@ -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' })
Expand All @@ -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('/', () => {})
Expand All @@ -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) {
Expand All @@ -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)
Expand All @@ -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) => {
Expand All @@ -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, '/')
}

{
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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) => {
Expand All @@ -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)
})
Loading

0 comments on commit 0c494c8

Please sign in to comment.