Skip to content

Instantly share code, notes, and snippets.

@iamyellow
Created April 17, 2020 10:41
Show Gist options
  • Save iamyellow/7051e1bcd5792f22169883223141dc28 to your computer and use it in GitHub Desktop.
Save iamyellow/7051e1bcd5792f22169883223141dc28 to your computer and use it in GitHub Desktop.
protecting with a password NestJs Swagger docs
<!DOCTYPE html>
<html lang="en">
<html>
<head>
<title></title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta charset="utf-8" />
<!-- thanks to https://github.com/Mehedi61/Login-Signup-form/ -->
<style>
@import url(https://fonts.googleapis.com/css?family=Roboto:300);
.body {
background-image: url('https://ordinaryfaith.net/wp-content/uploads/2016/03/Gray-plain-website-background.jpg');
background-repeat: no-repeat;
background-size: cover;
}
.login-page {
width: 360px;
padding: 8% 0 0;
margin: auto;
}
.form {
position: relative;
z-index: 1;
background: #48c9b0;
max-width: 360px;
margin: 0 auto 100px;
padding: 45px;
text-align: center;
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2),
0 5px 5px 0 rgba(0, 0, 0, 0.24);
}
.form input {
font-family: FontAwesome, 'Roboto', sans-serif;
outline: 0;
background: #f2f2f2;
width: 100%;
border: 0;
margin: 0 0 15px;
padding: 15px;
box-sizing: border-box;
font-size: 14px;
}
.form button {
font-family: 'Titillium Web', sans-serif;
font-size: 14px;
font-weight: bold;
letter-spacing: 0.1em;
outline: 0;
background: #17a589;
width: 100%;
border: 0;
margin: 0px 0px 8px;
padding: 15px;
color: #ffffff;
-webkit-transition: all 0.3 ease;
transition: all 0.3 ease;
cursor: pointer;
}
.form button:hover,
.form button:active,
.form button:focus {
background: #148f77;
}
.form .message {
margin: 6px 6px;
color: #808080;
font-size: 11px;
text-align: center;
font-weight: bold;
font-style: normal;
}
.form .message a {
color: #ffffff;
text-decoration: none;
font-size: 13px;
}
.form .register-form {
display: none;
}
.container {
position: relative;
z-index: 1;
max-width: 300px;
margin: 0 auto;
}
.container:before,
.container:after {
content: '';
display: block;
clear: both;
}
.container .info {
margin: 50px auto;
text-align: center;
}
.container .info h1 {
margin: 0 0 15px;
padding: 0;
font-size: 36px;
font-weight: 300;
color: #1a1a1a;
}
.container .info span {
color: #4d4d4d;
font-size: 12px;
}
.container .info span a {
color: #000000;
text-decoration: none;
}
.container .info span .fa {
color: #ef3b3a;
}
body {
background: #76b852; /* fallback for old browsers */
background: -webkit-linear-gradient(right, #76b852, #8dc26f);
background: -moz-linear-gradient(right, #76b852, #8dc26f);
background: -o-linear-gradient(right, #76b852, #8dc26f);
background: linear-gradient(to left, #76b852, #8dc26f);
font-family: 'Roboto', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</style>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Titillium+Web:400,300,600"
rel="stylesheet"
type="text/css"
/>
</head>
<body class="body">
<div class="login-page">
<div class="form">
<form method="post" enctype="application/json">
<input
name="you_know"
type="password"
placeholder="&#xf023; you know"
/>
<button type="submit">go</button>
<p class="message"></p>
</form>
</div>
</div>
</body>
</html>
</html>
import { ValidationPipe } from '@nestjs/common'
import { NestFactory } from '@nestjs/core'
import {
FastifyAdapter,
NestFastifyApplication
} from '@nestjs/platform-fastify'
import { FastifyInstance } from 'fastify'
import setUpSwagger from './setUpSwagger'
import { AppModule } from './app.module'
async function bootstrap() {
const adapter = new FastifyAdapter()
const server: FastifyInstance = adapter.getInstance()
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
adapter
)
app.useGlobalPipes(new ValidationPipe())
setUpSwagger(app, server)
await app.listen(3000, '0.0.0.0')
}
bootstrap()
import { NestFastifyApplication } from '@nestjs/platform-fastify'
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
import Cookies from 'cookies'
import { FastifyInstance } from 'fastify'
import { createReadStream } from 'fs'
import jwt from 'jsonwebtoken'
const DOC_ENDPOINT = 'doc'
const DOC_LOGIN_ENDPOINT = 'doc-login'
const DOC_LOGIN_HTML_PATH = `${process.cwd()}/doc-login.html`
const COOKIE_NAME = 'ms::api-front-doc'
export default function setUpSwagger(
app: NestFastifyApplication,
server: FastifyInstance
) {
server.after(() => {
server.addHook('onRoute', (opts) => {
const url = opts.url
if (
url !== `/${DOC_LOGIN_ENDPOINT}` &&
url.indexOf(`/${DOC_ENDPOINT}`) === 0
) {
opts.onRequest = (request, reply, next) => {
const keys = [process.env.DOC_LOGIN_KEY as string]
const cookies = new Cookies(request.req, reply.res, {
keys: keys
})
const cookie = cookies.get(COOKIE_NAME, { signed: true })
if (cookie) {
try {
jwt.verify(cookie, keys[0])
next()
return
} catch {}
}
reply.redirect(`/${DOC_LOGIN_ENDPOINT}`)
}
}
})
// login routes
server.route({
method: 'GET',
url: '/doc-login',
handler: (_request, reply) => {
reply
.header('Content-Type', 'text/html; charset=UTF-8')
.send(createReadStream(DOC_LOGIN_HTML_PATH, 'utf8'))
}
})
server.route({
method: 'POST',
url: '/doc-login',
handler: (request, reply) => {
const keys = [process.env.DOC_LOGIN_KEY as string]
const pw = process.env.DOC_LOGIN_PW as string
const you_know: string = request.body.you_know
if (you_know !== pw) {
reply.redirect(`/${DOC_LOGIN_ENDPOINT}`)
return
}
const token = jwt.sign({ id: new Date().getTime() }, keys[0])
const cookies = new Cookies(request.req, reply.res, {
keys: keys
})
cookies.set(COOKIE_NAME, token, { signed: true })
reply.redirect(`/${DOC_ENDPOINT}`)
}
})
})
const options = new DocumentBuilder()
.setTitle('api-front docs')
.setVersion('1.0')
.build()
const document = SwaggerModule.createDocument(app, options)
SwaggerModule.setup(DOC_ENDPOINT, app, document)
}
@toannguyen5597
Copy link

toannguyen5597 commented Sep 9, 2020

Can you help me with nestjs/platform-exress please!

@alcorntech
Copy link

alcorntech commented Oct 31, 2023

I realize this thread is 3 years old, but I struggled to get this code working (as some things have breaking changes, such as reply.res no longer exists and is apparently reply.raw), and the redirect code only fires on assets (JS and CSS) but not on the top-level route. Ultimately, I decided to eliminate the cookie and the HTML webpage and just throw Basic Auth HTTP headers. Hopefully this code helps others:

import { INestApplication } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { FastifyInstance } from 'fastify';

export function setupSwagger(app: INestApplication, fastify: FastifyInstance) {
const docPath = 'api-docs';
const docLoginUsername = 'admin';
const docLoginPassword = 'abc123';

fastify.after(() => {
fastify.addHook('onRequest', (request, reply, next) => {
if (request.url.indexOf('/' + docPath) === 0) {
try {
const base64AuthString = request.headers['authorization'].split(' ')[1];
const authString = Buffer.from(base64AuthString, 'base64').toString('utf8');
const username = authString.split(':')[0];
const password = authString.split(':')[1];

      if (username === docLoginUsername && password === docLoginPassword) {
        next();
        return;
      }
    } catch { }

    reply
      .header('WWW-Authenticate', 'Basic realm="Swagger"; charset=UTF-8')
      .status(401)
      .send();
    return;
  }

  next();
});

});

const config = new DocumentBuilder()
.setTitle('The API')
.setDescription('The API that controls all backend functionality')
.setVersion('1.0')
.addBearerAuth()
.build();

const document = SwaggerModule.createDocument(app, config);

SwaggerModule.setup(docPath, app, document);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment