Skip to content

Commit 337e847

Browse files
authored
Merge pull request lukeautry#684 from picturelink/feature/665
feature/665 - Add NoSecurity decorator
2 parents 80559dd + 63935eb commit 337e847

File tree

5 files changed

+104
-0
lines changed

5 files changed

+104
-0
lines changed

src/decorators/security.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
/**
2+
* Can be used to indicate that a method requires no security.
3+
*/
4+
export function NoSecurity(): Function {
5+
return () => {
6+
return;
7+
};
8+
}
9+
110
/**
211
* @param {name} security name from securityDefinitions
312
*/

src/metadataGeneration/controllerGenerator.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,13 @@ export class ControllerGenerator {
7979
}
8080

8181
private getSecurity(): Tsoa.Security[] {
82+
const noSecurityDecorators = getDecorators(this.node, identifier => identifier.text === 'NoSecurity');
8283
const securityDecorators = getDecorators(this.node, identifier => identifier.text === 'Security');
84+
85+
if (noSecurityDecorators?.length) {
86+
throw new GenerateMetadataError(`NoSecurity decorator is unnecessary in '${this.node.name!.text}' class.`);
87+
}
88+
8389
if (!securityDecorators || !securityDecorators.length) {
8490
return [];
8591
}

src/metadataGeneration/methodGenerator.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,21 @@ export class MethodGenerator {
217217
}
218218

219219
private getSecurity(): Tsoa.Security[] {
220+
const noSecurityDecorators = this.getDecoratorsByIdentifier(this.node, 'NoSecurity');
220221
const securityDecorators = this.getDecoratorsByIdentifier(this.node, 'Security');
222+
223+
if (noSecurityDecorators?.length > 1) {
224+
throw new GenerateMetadataError(`Only one NoSecurity decorator allowed in '${this.getCurrentLocation}' method.`);
225+
}
226+
227+
if (noSecurityDecorators?.length && securityDecorators?.length) {
228+
throw new GenerateMetadataError(`NoSecurity decorator cannot be used in conjunction with Security decorator in '${this.getCurrentLocation}' method.`);
229+
}
230+
231+
if (noSecurityDecorators?.length) {
232+
return [];
233+
}
234+
221235
if (!securityDecorators || !securityDecorators.length) {
222236
return this.parentSecurity || [];
223237
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Get, Request, Response, Route, Security, NoSecurity } from '../../../src';
2+
import { ErrorResponseModel, UserResponseModel } from '../../fixtures/testModel';
3+
4+
interface RequestWithUser {
5+
user?: any;
6+
}
7+
8+
@Security('tsoa_auth', ['write:pets', 'read:pets'])
9+
@Route('NoSecurityTest')
10+
export class NoSecurityTestController {
11+
@Response<ErrorResponseModel>('default', 'Unexpected error')
12+
@Security('api_key')
13+
@Get()
14+
public async GetWithApi(@Request() request: RequestWithUser): Promise<UserResponseModel> {
15+
return Promise.resolve(request.user);
16+
}
17+
18+
@Response<ErrorResponseModel>('404', 'Not Found')
19+
@Get('Oauth')
20+
public async GetWithImplicitSecurity(@Request() request: RequestWithUser): Promise<UserResponseModel> {
21+
return Promise.resolve(request.user);
22+
}
23+
24+
@Response<ErrorResponseModel>('404', 'Not Found')
25+
@Get('Anonymous')
26+
@NoSecurity()
27+
public async GetWithNoSecurity(@Request() request: RequestWithUser): Promise<UserResponseModel> {
28+
return Promise.resolve(request.user);
29+
}
30+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { expect } from 'chai';
2+
import 'mocha';
3+
import { MetadataGenerator } from '../../../../src/metadataGeneration/metadataGenerator';
4+
import { SpecGenerator2 } from '../../../../src/swagger/specGenerator2';
5+
import { getDefaultExtendedOptions } from '../../../fixtures/defaultOptions';
6+
import { VerifyPath } from '../../utilities/verifyPath';
7+
8+
describe('NoSecurity route generation', () => {
9+
const metadata = new MetadataGenerator('./tests/fixtures/controllers/noSecurityController.ts').Generate();
10+
const spec = new SpecGenerator2(metadata, getDefaultExtendedOptions()).GetSpec();
11+
12+
it('should generate a route with a named security', () => {
13+
const path = verifyPath('/NoSecurityTest');
14+
15+
if (!path.get) {
16+
throw new Error('No get operation.');
17+
}
18+
19+
expect(path.get.security).to.deep.equal([{ api_key: [] }]);
20+
});
21+
22+
it('should generate a route with scoped security', () => {
23+
const path = verifyPath('/NoSecurityTest/Oauth');
24+
25+
if (!path.get) {
26+
throw new Error('No get operation.');
27+
}
28+
29+
expect(path.get.security).to.deep.equal([{ tsoa_auth: ['write:pets', 'read:pets'] }]);
30+
});
31+
32+
it('should generate a route with no security', () => {
33+
const path = verifyPath('/NoSecurityTest/Anonymous');
34+
35+
if (!path.get) {
36+
throw new Error('No get operation.');
37+
}
38+
39+
expect(path.get.security).to.deep.equal([]);
40+
});
41+
42+
function verifyPath(route: string, isCollection?: boolean) {
43+
return VerifyPath(spec, route, path => path.get, isCollection, false, '#/definitions/UserResponseModel');
44+
}
45+
});

0 commit comments

Comments
 (0)