Skip to content

Commit 1dbf583

Browse files
committed
allow multiple security requirements
1 parent 671c555 commit 1dbf583

File tree

8 files changed

+66
-20
lines changed

8 files changed

+66
-20
lines changed

README.MD

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,9 @@ class PeopleService {
147147

148148
#### @Security
149149

150-
Add a security constraint to method generated docs, refering the security name from securityDefinitions
150+
Add a security constraint to method generated docs, referencing the security name from securityDefinitions.
151+
`@Security` can be used at the controller and method level; if defined on both, method security overwrites controller security.
152+
Multiple security schemes may be specified to require all of them.
151153

152154
```typescript
153155
@Path('people')

src/metadata/controllerGenerator.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,10 @@ export class ControllerGenerator {
8484

8585
const securityDecorators = getDecorators(this.node, decorator => decorator.text === 'Security');
8686
if (!securityDecorators || !securityDecorators.length) { return undefined; }
87-
if (securityDecorators.length > 1) {
88-
throw new Error(`Only one Security decorator allowed in '${this.node.name.text}' controller.`);
89-
}
90-
91-
const d = securityDecorators[0];
9287

93-
return {
88+
return securityDecorators.map(d => ({
9489
name: d.arguments[0],
9590
scopes: d.arguments[1] ? (d.arguments[1] as any).elements.map((e: any) => e.text) : undefined
96-
};
91+
}));
9792
}
9893
}

src/metadata/metadataGenerator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export interface Controller {
9494
consumes: string[];
9595
produces: string[];
9696
tags: string[];
97-
security?: Security;
97+
security?: Security[];
9898
}
9999

100100
export interface Method {
@@ -107,7 +107,7 @@ export interface Method {
107107
type: Type;
108108
tags: string[];
109109
responses: ResponseType[];
110-
security?: Security;
110+
security?: Security[];
111111
summary?: string;
112112
consumes: string[];
113113
produces: string[];

src/metadata/methodGenerator.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -219,16 +219,11 @@ export class MethodGenerator {
219219
private getMethodSecurity() {
220220
const securityDecorators = getDecorators(this.node, decorator => decorator.text === 'Security');
221221
if (!securityDecorators || !securityDecorators.length) { return undefined; }
222-
if (securityDecorators.length > 1) {
223-
throw new Error(`Only one Security decorator allowed in '${this.getCurrentLocation}' method.`);
224-
}
225-
226-
const d = securityDecorators[0];
227222

228-
return {
223+
return securityDecorators.map(d => ({
229224
name: d.arguments[0],
230225
scopes: d.arguments[1] ? (d.arguments[1] as any).elements.map((e: any) => e.text) : undefined
231-
};
226+
}));
232227
}
233228

234229
private getInitializerValue(initializer: any) {

src/swagger/generator.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ export class SpecGenerator {
120120
if (method.deprecated) { pathMethod.deprecated = method.deprecated; }
121121
if (method.tags.length) { pathMethod.tags = method.tags; }
122122
if (method.security) {
123-
const security: any = {};
124-
security[method.security.name] = method.security.scopes ? method.security.scopes : [];
125-
pathMethod.security = [security];
123+
pathMethod.security = method.security.map(s => ({
124+
[s.name]: s.scopes || []
125+
}));
126126
}
127127
this.handleMethodConsumes(method, pathMethod);
128128

test/data/apis.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,3 +348,28 @@ export class AbstractEntityEndpoint {
348348
return new NamedEntity();
349349
}
350350
}
351+
352+
@Path('secure')
353+
@swagger.Security('access_token')
354+
export class SecureEndpoint {
355+
@GET
356+
get(): string {
357+
return 'Access Granted';
358+
}
359+
360+
@POST
361+
@swagger.Security('user_email')
362+
post(): string {
363+
return 'Posted';
364+
}
365+
}
366+
367+
@Path('supersecure')
368+
@swagger.Security('access_token')
369+
@swagger.Security('user_email')
370+
export class SuperSecureEndpoint {
371+
@GET
372+
get(): string {
373+
return 'Access Granted';
374+
}
375+
}

test/data/swagger.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@
1414
"type": "apiKey",
1515
"name": "access_token",
1616
"in": "query"
17+
},
18+
"access_token": {
19+
"type": "apiKey",
20+
"name": "authorization",
21+
"in": "header"
22+
},
23+
"user_email": {
24+
"type": "apiKey",
25+
"name": "x-user-email",
26+
"in": "header"
1727
}
1828
},
1929
"spec": {

test/unit/definitions.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,4 +296,23 @@ describe('Definition generation', () => {
296296
expect(expression.evaluate(spec)).to.eq('A numeric identifier');
297297
});
298298
});
299+
300+
describe('SecureEndpoint', () => {
301+
it('should apply controller security to request', () => {
302+
const expression = jsonata('paths."/secure".get.security');
303+
expect(expression.evaluate(spec)).to.deep.equal([ { 'access_token': [] } ]);
304+
});
305+
306+
it('method security should override controller security', () => {
307+
const expression = jsonata('paths."/secure".post.security');
308+
expect(expression.evaluate(spec)).to.deep.equal([ { 'user_email': [] } ]);
309+
});
310+
});
311+
312+
describe('SuperSecureEndpoint', () => {
313+
it('should apply two controller securities to request', () => {
314+
const expression = jsonata('paths."/supersecure".get.security');
315+
expect(expression.evaluate(spec)).to.deep.equal([ { 'access_token': [] }, { 'user_email': [] } ]);
316+
});
317+
});
299318
});

0 commit comments

Comments
 (0)