9.0.3 • Published 2 years ago

@femike/swagger-protect v9.0.3

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

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 and cookieKey have no effect in module SwaggerProtect when we use express.

// ./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 be RegExp it can protect not only swagger 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 this RegExp

// ./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 uses ClassSerializer you have to add ValidationPipe 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 on GET request redirect to static index.html UI on POST passed data to callback function or injected class implemented from SwaggerLoginInterface 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.

PropertyTypeDescription
guardFunction / ClassFunction or Class guard must be return boolean result. Class meight be implemented SwaggerGuardInterface. Default: (token: string) => !!token
logInFunction / ClassFunction or Class logIn must return object with key token. Class meight be implemented SwaggerLoginInterface. Default: () => ({ token: '' })
swaggerPath?string / RegExpDefault: RegExp /^\/api(?:\/\|-json\|\/static\/index\.html)?$ for fastify
loginPath?stringPath where user will be redirect on fail guard. Default /login-api
cookieKey?stringKey name stored in Cookie. Default swagger_token
useUI?BooleanUse 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 in forRoot() or forRootAsync() Form send POST request to /login-api with data { login, password } on response set Cookie with default key swagger_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
9.0.3

2 years ago

9.0.2

2 years ago

9.0.1

2 years ago

9.0.0

2 years ago

2.0.1

3 years ago

2.0.0

3 years ago

1.2.0

3 years ago

1.1.0

3 years ago

1.0.0

3 years ago