@femike/swagger-protect v9.0.3
Description
A small tool to protect access to the openapi user interface. Creates a mechanism for checking the request URL: / api / *
and checks for the existence of a Cookie swagger_token
, if a cookie is present, checks its validity through a callback, in case of failure, redirects to the authorization page /login-api/index.html?backUrl=/path/to/openapi/ui
. After successfuly authorization, returns to the backUrl
.
Installation
$ npm install @femike/swagger-protect
$ yarn add @femike/swagger-protect
Swagger protect Fastify hook
Easy way to protect swagger with fastify use a hook.
// ./src/main.ts
import { fastifyProtectSwagger } from '@femike/swagger-protect'
import { getConnection } from 'typeorm'
import { TokenEntity } from 'your-application/src/entities'
// With clean fastify application
fastify.addHook(
'onRequest',
fastifyProtectSwagger({
cookieGuard: (
token, // must return boolean result (token: string) => Promise<boolean> for example typeorm find token on fail return false
) =>
getConnection()
.getRepository(TokenEntity)
.findOneOrFail(token)
.then(t => t.token === token),
cookieKey: 'swagger_token', // key must be stored in cookies on login.
swaggerPath: /^\/api\/(json|static\/index.html)(?:\/)?$/, // entry point will be protect with guard above.
loginPath: '/login-api', // redirect on fail guard.
}),
)
// For NestJS With Fastify as Adapter hook for module see below.
fastifyAdapter.getInstance().addHook(
'onRequest',
fastifyProtectSwagger({
cookieGuard: token =>
getConnection()
.getRepository(TokenEntity)
.findOneOrFail(token)
.then(t => t.token === token),
cookieKey: 'swagger_token',
swaggerPath: /^\/api\/(json|static\/index.html)(?:\/)?$/,
loginPath: '/login-api',
}),
)
When guard return true
, hook go to the next way and show swagger open api page.
If guard return false
, user will be redirected to the page /login-api
info Hint Your must create frontend application with sign-in form and set cookie with
swagger_token
key setted above on succesfuly login.Or use
@femike/swager-protect-ui
see below.
Swagger protect Express middleware
Warning Warning Cookie-parser must be import before setup protect middleware.
// ./src/main.ts
import { expressProtectSwagger } from '@femike/swagger-protect'
import express from 'express'
import { createSwagger } from './swagger'
import cookieParser from 'cookie-parser'
const app = express()
app.get('/', (req, res) => res.send('Home Page <a href="/api">API</a>'))
async function bootstrap() {
app.use(cookieParser()) // @!important need set cookie-parser before setup protect middleware
expressProtectSwagger({
guard: (token: string) => !!token, // if token exists access granted!
})
createSwagger(app).listen(3000, () => {
console.log(`Application is running on: http://localhost:3000`)
})
}
bootstrap()
Swagger protect NestJS Module for Express
Warning Warning Express have no method override exists routes we must register protect middleware before setup Swagger.
// touch ./src/swagger/config.ts
import { registerExpressProtectSwagger } from '@femike/swagger-protect'
import type { INestApplication } from '@nestjs/common'
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
import { SwaggerGuard } from './guard'
export const SWAGGER_PATH = 'api'
const options = new DocumentBuilder()
.setTitle('Cats example')
.setDescription('The cats API description')
.setVersion('1.0')
.addTag('cats')
.addBearerAuth()
.build()
export function createSwagger(app: INestApplication): INestApplication {
registerExpressProtectSwagger(app, {
guard: new SwaggerGuard(),
swaggerPath: SWAGGER_PATH,
loginPath: '/login-api',
cookieKey: 'swagger_token',
})
const document = SwaggerModule.createDocument(app, options)
SwaggerModule.setup(SWAGGER_PATH, app, document)
return app
}
info Hint Parrameters
guard
,swaggerPath
loginPath
andcookieKey
have no effect in moduleSwaggerProtect
when we useexpress
.
// ./src/main.ts
import { SwaggerProtect } from '@femike/swagger-protect'
import { Module } from '@nestjs/common'
import { CatsModule } from './cats/cats.module'
import { SwaggerLogin } from './swagger'
@Module({
imports: [
CatsModule,
SwaggerProtect.forRoot({
guard: () => false, // no effect on express
logIn: new SwaggerLogin(),
swaggerPath: 'api', // no effect on express
loginPath: '/login-api', // no effect on express
cookieKey: 'swagger_token', // no effect on express
}),
],
})
export class AppModule {}
// $ touch ./src/swagger/swagger.login.ts
import {
SwaggerProtectLogInDto,
SwaggerLoginInterface,
} from '@femike/swagger-protect'
import { v4 as uuid } from 'uuid'
/**
* Swagger Login
*/
export class SwaggerLogin implements SwaggerLoginInterface {
async execute({
login,
password,
}: SwaggerProtectLogInDto): Promise<{ token: string }> {
return login === 'admin' && password === 'changeme'
? { token: uuid() }
: { token: '' }
}
}
Example login
service must be implemented from SwaggerLoginInterface
Swagger protect NestJS Module for Fastify
Create class guard
must be implemented from SwaggerGuardInterface
// $ touch ./src/swagger/swagger.guard.ts
import type { SwaggerGuardInterface } from '@femike/swagger-protect'
import { Inject } from '@nestjs/common'
import { AuthService } from '../auth'
/**
* Swagger Guard
*/
export class SwaggerGuard implements SwaggerGuardInterface {
constructor(@Inject(AuthService) private readonly service: AuthService) {}
/**
* Method guard
*/
async canActivate(token: string): Promise<boolean> {
return await this.service.method(token)
}
}
Now register module SwaggerProtect
info Hint Fastify middleware give little bit more than Express,
swaggerPath
meight beRegExp
it can protect not onlyswagger openapi UI
.Warning Warning But remember if you override this option you must protect two entry points
/api/json
and/api/static/index.html
in thisRegExp
// ./src/app.module.ts
import { LoggerModule } from '@femike/logger'
import { Module } from '@nestjs/common'
import { SwaggerProtect } from '@femike/swagger-protect'
@Module({
imports: [
LoggerModule,
SwaggerProtect.forRootAsync<'fastify'>({
// <- pass
imports: [AuthModule],
useFactory: () => ({
guard: SwaggerGuard,
logIn: SwaggerLogin,
swaggerPath: /^\/api\/(json|static\/index.html)(?:\/)?$/,
useUI: true, // switch swagger-protect-ui
}),
}),
],
controllers: [AppController],
providers: [HttpStrategy, AppService, AppLogger],
})
export class AppModule {}
Warning Warning The controller
login-api
usesClassSerializer
you have to addValidationPipe
and container for fallback errors.
// ./src/main.ts
...
app.useGlobalPipes(
new ValidationPipe({
transform: true,
disableErrorMessages: false,
enableDebugMessages: true,
}),
)
useContainer(app.select(AppModule), { fallbackOnErrors: true })
...
info Hint If
useUI
options is not disabled, module creates controller with answered path/login-api
onGET
request redirect to staticindex.html
UI onPOST
passed data to callback function or injected class implemented fromSwaggerLoginInterface
response pass data to UI where on success setted Cookie.
graph TD;
REQUEST-->GUARD_CALLBACK-->COOKIE_VALIDATE
COOKIE_VALIDATE-->LOGIN_UI
COOKIE_VALIDATE-->OPENAPI
LOGIN_UI-->POST_REQUEST_AUTH
POST_REQUEST_AUTH-->LOGIN_UI
LOGIN_UI-->SET_COOKIE
SET_COOKIE-->COOKIE_VALIDATE
SET_COOKIE-->BACK_URL
BACK_URL-->OPENAPI
API Spec
The forRoot()
method takes an options object with a few useful properties.
Property | Type | Description |
---|---|---|
guard | Function / Class | Function or Class guard must be return boolean result. Class meight be implemented SwaggerGuardInterface . Default: (token: string) => !!token |
logIn | Function / Class | Function or Class logIn must return object with key token. Class meight be implemented SwaggerLoginInterface . Default: () => ({ token: '' }) |
swaggerPath? | string / RegExp | Default: RegExp /^\/api(?:\/\|-json\|\/static\/index\.html)?$ for fastify |
loginPath? | string | Path where user will be redirect on fail guard. Default /login-api |
cookieKey? | string | Key name stored in Cookie. Default swagger_token |
useUI? | Boolean | Use or not user interface for login to swagger ui. When loginPath was changed from /login-api ui will be disabled. Default true |
Examples
See full examples https://github.com/femike/swagger-protect/tree/main/samples.
UI
Installation
$ npm i @femike/swagger-protect-ui
$ yarn add @femike/swagger-protect-ui
Default url /login-api
info Hint UI have no settings, it must be only disabled by options
useUI
:false
inforRoot()
orforRootAsync()
Form sendPOST
request to/login-api
with data{ login, password }
on response set Cookie with default keyswagger_token
Roadmap
- Fastify Hook
- Express Middleware
- NestJS Module
- UI - login
- Example Page UI
- Sample Express
- Sample Fastify
- Sample Nestjs Express
- Sample Nestjs Fastify
- Tests e2e express
- Tests e2e nest-express
- Tests e2e fastify
- Tests e2e nest-fastify
- Units test replaceApi
- Units tests
- Github CI
- Inject Swagger UI Layout