Skip to content

Commit

Permalink
chore: swagger
Browse files Browse the repository at this point in the history
  • Loading branch information
radoslavirha committed Nov 30, 2023
1 parent 7bd3b1f commit e7248d5
Show file tree
Hide file tree
Showing 18 changed files with 88 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { SwaggerDocsVersion } from '@hikers-book/tsed-common/types';
import { Controller } from '@tsed/di';
import { BadRequest, Forbidden, NotFound } from '@tsed/exceptions';
import { BadRequest, Forbidden, NotFound, UnprocessableEntity } from '@tsed/exceptions';
import { Authenticate } from '@tsed/passport';
import { BodyParams } from '@tsed/platform-params';
import { Description, Post, Returns } from '@tsed/schema';
import { Docs } from '@tsed/swagger';
import { CredentialsAlreadyExist } from '../../exceptions';
import { EmailSendVerificationHandler, EmailVerifyTokenHandler } from '../../handlers';
import { EmailSendVerificationRequest, EmailVerifyTokenRequest } from '../../models';
import { JWTResponse } from '../../models/auth/email/JWTResponse';

@Description('Email provider controllers.')
@Controller('/auth/provider/email')
Expand Down Expand Up @@ -39,17 +40,20 @@ export class AuthProviderEmailController {
@Post('/sign-up')
@Description('Sign up user with email and password.')
@Authenticate('email-sign-up', { session: false })
@Returns(200)
@Returns(200, JWTResponse)
@Returns(NotFound.STATUS, NotFound)
@Returns(Forbidden.STATUS, Forbidden)
@Returns(BadRequest.STATUS, BadRequest)
@Returns(UnprocessableEntity.STATUS, UnprocessableEntity)
@Returns(CredentialsAlreadyExist.STATUS, CredentialsAlreadyExist)
// istanbul ignore next
async signUp() {}

@Post('/sign-in')
@Description('Sign in user with email and password.')
@Authenticate('email-sign-in', { session: false })
@Returns(200)
@Returns(200, JWTResponse)
@Returns(Forbidden.STATUS, Forbidden)
// istanbul ignore next
async signIn() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Controller } from '@tsed/di';
import { Authenticate } from '@tsed/passport';
import { Description, Get } from '@tsed/schema';
import { Docs } from '@tsed/swagger';
import { OAuth2ReturnStatuses } from '../../decorators';

@Description('Facebook provider controllers.')
@Controller('/auth/provider/facebook')
Expand All @@ -12,12 +13,14 @@ export class AuthProviderFacebookController {

@Get('/')
@Description('Login with Facebook.')
@OAuth2ReturnStatuses()
@Authenticate('facebook', { session: false, scope: ['email'] })
// istanbul ignore next
async authenticated() {}

@Get('/callback')
@Description('Login with Facebook.')
@OAuth2ReturnStatuses()
@Authenticate('facebook', { session: false, scope: ['email'] })
// istanbul ignore next
async callback() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Controller } from '@tsed/di';
import { Authenticate } from '@tsed/passport';
import { Description, Get } from '@tsed/schema';
import { Docs } from '@tsed/swagger';
import { OAuth2ReturnStatuses } from '../../decorators';

@Description('Github provider controllers.')
@Controller('/auth/provider/github')
Expand All @@ -12,12 +13,14 @@ export class AuthProviderGithubController {

@Get('/')
@Description('Login with Github.')
@OAuth2ReturnStatuses()
@Authenticate('github', { session: false, scope: ['user', 'email'] })
// istanbul ignore next
async authenticated() {}

@Get('/callback')
@Description('Login with Github.')
@OAuth2ReturnStatuses()
@Authenticate('github', { session: false, scope: ['user', 'email'] })
// istanbul ignore next
async callback() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Controller } from '@tsed/di';
import { Authenticate } from '@tsed/passport';
import { Description, Get } from '@tsed/schema';
import { Docs } from '@tsed/swagger';
import { OAuth2ReturnStatuses } from '../../decorators';

@Description('Google provider controllers.')
@Controller('/auth/provider/google')
Expand All @@ -12,12 +13,14 @@ export class AuthProviderGoogleController {

@Get('/')
@Description('Login with Google.')
@OAuth2ReturnStatuses()
@Authenticate('google', { session: false, scope: ['email', 'profile'] })
// istanbul ignore next
async authenticated() {}

@Get('/callback')
@Description('Login with Google.')
@OAuth2ReturnStatuses()
@Authenticate('google', { session: false, scope: ['email', 'profile'] })
// istanbul ignore next
async callback() {}
Expand Down
11 changes: 7 additions & 4 deletions api/authentication/src/global/decorators/JWTAuth.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { SwaggerSecurityScheme } from '@hikers-book/tsed-common/types';
import { useDecorators } from '@tsed/core';
import { Unauthorized } from '@tsed/exceptions';
import { Authenticate, AuthorizeOptions } from '@tsed/passport';
import { In, Returns, Security } from '@tsed/schema';
import { Returns, Security } from '@tsed/schema';

/**
* Set JWTAuth access on decorated route
Expand All @@ -10,8 +11,10 @@ import { In, Returns, Security } from '@tsed/schema';
export function JWTAuth(options: AuthorizeOptions = {}) {
return useDecorators(
Authenticate('jwt-authentication-api', { ...options, session: false }),
Security('jwt'),
Returns(401, Unauthorized).Description('Unauthorized'),
In('header').Name('Authorization').Description('JWT Bearer token').Type(String).Required(false)
Security(SwaggerSecurityScheme.BEARER_JWT),
Returns(401, Unauthorized).Description('Unauthorized')
// Do not set .Required(true), as it will cause Swagger to require Authorization header.
// Properly set securitySchemes will unlock Authorize header which automatically uses Bearer JWT token.
// In('header').Name('Authorization').Description('Bearer JWT token').Type(String)
);
}
17 changes: 17 additions & 0 deletions api/authentication/src/global/decorators/OAuth2ReturnStatuses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useDecorators } from '@tsed/core';
import { Forbidden, UnprocessableEntity } from '@tsed/exceptions';
import { Returns } from '@tsed/schema';
import { CredentialsAlreadyExist } from '../exceptions';

/**
* Set Swagger return statuses for UAuth2
* @param options
*/
export function OAuth2ReturnStatuses() {
return useDecorators(
Returns(301),
Returns(UnprocessableEntity.STATUS, UnprocessableEntity),
Returns(Forbidden.STATUS, Forbidden),
Returns(CredentialsAlreadyExist.STATUS, CredentialsAlreadyExist)
);
}
1 change: 1 addition & 0 deletions api/authentication/src/global/decorators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
*/

export * from './JWTAuth';
export * from './OAuth2ReturnStatuses';
14 changes: 14 additions & 0 deletions api/authentication/src/global/models/auth/email/JWTResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Description, Required, Schema, Title } from '@tsed/schema';

@Schema({ additionalProperties: false })
export class JWTResponse {
@Title('jwt')
@Description('JWT token.')
@Required()
jwt!: string;

@Title('refresh')
@Description('JWT refresh token.')
@Required()
refresh!: string;
}
1 change: 1 addition & 0 deletions api/authentication/src/global/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export * from './auth/email/EmailSendVerificationRequest';
export * from './auth/email/EmailSignInRequest';
export * from './auth/email/EmailSignUpRequest';
export * from './auth/email/EmailVerifyTokenRequest';
export * from './auth/email/JWTResponse';
export * from './config/AuthModel';
export * from './config/FrontendConfigModel';
export * from './config/JWTConfigModel';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
import { AuthProviderEnum } from '../enums';
import { CredentialsAlreadyExist } from '../exceptions';
import { CredentialsMapper } from '../mappers/CredentialsMapper';
import { Credentials, EmailSignInRequest, EmailSignUpRequest, User } from '../models';
import { Credentials, EmailSignInRequest, EmailSignUpRequest, JWTResponse, User } from '../models';
import { ProviderGithubPair } from '../types';
import { CryptographyUtils } from '../utils';
import { JWTService } from './JWTService';
Expand Down Expand Up @@ -523,12 +523,13 @@ describe('ProtocolAuthService', () => {
});

it('Should return jwt', async () => {
expect.assertions(1);
expect.assertions(2);

// @ts-expect-error private
const jwt = await service.createJWT(CredentialsStubPopulated);

expect(jwt).toStrictEqual({ jwt: 'jwt', refresh: 'refresh' });
expect(jwt).toBeInstanceOf(JWTResponse);
expect(jwt).toEqual({ jwt: 'jwt', refresh: 'refresh' });
});

it('Should throw 422', async () => {
Expand Down
7 changes: 4 additions & 3 deletions api/authentication/src/global/services/ProtocolAuthService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { AuthProviderEnum } from '../enums';
import { CredentialsAlreadyExist } from '../exceptions';
import { CredentialsMapper } from '../mappers/CredentialsMapper';
import { Credentials, EmailSignInRequest, EmailSignUpRequest, User } from '../models';
import { AuthProviderPair, JWTResponse, OAuth2ProviderPair } from '../types';
import { JWTResponse } from '../models/auth/email/JWTResponse';
import { AuthProviderPair, OAuth2ProviderPair } from '../types';
import { CryptographyUtils } from '../utils/CryptographyUtils';
import { JWTService } from './JWTService';
import { CredentialsMongoService } from './mongo/CredentialsMongoService';
Expand Down Expand Up @@ -122,10 +123,10 @@ export class ProtocolAuthService {
true
);

return {
return CommonUtils.buildModel(JWTResponse, {
jwt,
refresh
};
});
}

private getEmailFromAuthProfile(data: AuthProviderPair): string {
Expand Down
5 changes: 0 additions & 5 deletions api/authentication/src/global/types/ProtocolAuthService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,3 @@ export type ProviderGooglePair = { provider: AuthProviderEnum.GOOGLE; profile: G

export type OAuth2ProviderPair = ProviderFacebookPair | ProviderGithubPair | ProviderGooglePair;
export type AuthProviderPair = ProviderEmailPair | OAuth2ProviderPair;

export type JWTResponse = {
jwt: string;
refresh: string;
};
8 changes: 4 additions & 4 deletions api/authentication/src/test/stubs/Auth.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { CommonUtils } from '@hikers-book/tsed-common/utils';
import { Profile as FacebookProfile } from 'passport-facebook';
import { Profile as GithubProfile } from 'passport-github2';
import { Profile as GoogleProfile } from 'passport-google-oauth20';
import { EmailSignUpRequest } from '../../global/models';
import { JWTResponse } from '../../global/types';
import { EmailSignUpRequest, JWTResponse } from '../../global/models';

export const ProfileFacebookStub: FacebookProfile = {
id: 'id',
Expand Down Expand Up @@ -40,7 +40,7 @@ export const ProfileEmailStub: EmailSignUpRequest = {
full_name: 'Tester'
};

export const JWTStub: JWTResponse = {
export const JWTStub = CommonUtils.buildModel(JWTResponse, {
jwt: 'jwt',
refresh: 'refresh'
};
});
2 changes: 1 addition & 1 deletion api/trips/src/v1/controllers/TripsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class TripsController {

@Get('/')
@Description('Returns list of trips.')
@Returns(200, [Trip])
@Returns(200, Array).Of(Trip)
async get(): Promise<Trip[]> {
return this.getTripsHandler.execute();
}
Expand Down
11 changes: 7 additions & 4 deletions packages/tsed-common/src/decorators/JWTAuth.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useDecorators } from '@tsed/core';
import { Unauthorized } from '@tsed/exceptions';
import { Authenticate, AuthorizeOptions } from '@tsed/passport';
import { In, Returns, Security } from '@tsed/schema';
import { Returns, Security } from '@tsed/schema';
import { SwaggerSecurityScheme } from '../types';

/**
* Set JWTAuth access on decorated route
Expand All @@ -10,8 +11,10 @@ import { In, Returns, Security } from '@tsed/schema';
export function JWTAuth(options: AuthorizeOptions = {}) {
return useDecorators(
Authenticate('jwt', { ...options, session: false }),
Security('bearerAuth'),
Returns(401, Unauthorized).Description('Unauthorized'),
In('header').Name('Authorization').Description('JWT Bearer token').Type(String).Required(true)
Security(SwaggerSecurityScheme.BEARER_JWT),
Returns(401, Unauthorized).Description('Unauthorized')
// Do not set .Required(true), as it will cause Swagger to require Authorization header.
// Properly set securitySchemes will unlock Authorize header which automatically uses Bearer JWT token.
// In('header').Name('Authorization').Description('Bearer JWT token').Type(String)
);
}
11 changes: 8 additions & 3 deletions packages/tsed-common/src/server/ConfigSwagger.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { OpenSpec3, OpenSpecInfo } from '@tsed/openspec';
import { SwaggerSettings } from '@tsed/swagger';
import { SwaggerSecurityScheme } from '../types';
import { SwaggerDocsVersion } from '../types/SwaggerDocsVersion.enum';

export class ConfigSwagger {
Expand All @@ -26,7 +27,10 @@ export class ConfigSwagger {
return {
path: `/${docsVersion}/docs`,
doc: docsVersion,
specVersion: '3.0.1',
specVersion: '3.0.3',
options: {
requestSnippets: true
},
spec: <Partial<OpenSpec3>>{
info: <OpenSpecInfo>{
title,
Expand All @@ -35,10 +39,11 @@ export class ConfigSwagger {
},
components: {
securitySchemes: {
bearerAuth: {
[SwaggerSecurityScheme.BEARER_JWT]: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
bearerFormat: 'JWT',
description: 'Bearer JWT token'
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions packages/tsed-common/src/types/SwaggerSecurityScheme.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum SwaggerSecurityScheme {
BEARER_JWT = 'BEARER_JWT'
}
1 change: 1 addition & 0 deletions packages/tsed-common/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@

export * from './JWTPayload';
export * from './SwaggerDocsVersion.enum';
export * from './SwaggerSecurityScheme.enum';
export * from './mongo';

0 comments on commit e7248d5

Please sign in to comment.