Skip to content

Commit 391c61b

Browse files
fix: correct security schema logic for OR verification
1 parent f022d21 commit 391c61b

File tree

3 files changed

+47
-40
lines changed

3 files changed

+47
-40
lines changed

src/middlewares/openapi.security.ts

Lines changed: 12 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ const defaultSecurityHandler = (
1717
type SecuritySchemesMap = {
1818
[key: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.SecuritySchemeObject;
1919
};
20+
2021
interface SecurityHandlerResult {
2122
success: boolean;
2223
status?: number;
2324
error?: string;
2425
}
26+
2527
export function security(
2628
apiDoc: OpenAPIV3.Document,
2729
securityHandlers: SecurityHandlers,
@@ -62,50 +64,19 @@ export function security(
6264

6365
// TODO handle AND'd and OR'd security
6466
// This assumes OR only! i.e. at least one security passed authentication
65-
let firstError: SecurityHandlerResult = null;
66-
let success = false;
67-
68-
function checkErrorWithOr(res) {
69-
return res.forEach((r) => {
70-
if (r.success) {
71-
success = true;
72-
} else if (!firstError) {
73-
firstError = r;
74-
}
75-
});
76-
}
77-
78-
function checkErrorsWithAnd(res) {
79-
let allSuccess = false;
80-
81-
res.forEach((r) => {
82-
if (!r.success) {
83-
allSuccess = false;
84-
if (!firstError) {
85-
firstError = r;
86-
}
87-
} else if (!firstError) {
88-
allSuccess = true;
89-
}
90-
});
91-
92-
if (allSuccess) {
93-
success = true;
94-
}
95-
}
96-
97-
results.forEach((result) => {
98-
if (Array.isArray(result) && result.length > 1) {
99-
checkErrorsWithAnd(result);
100-
} else {
101-
checkErrorWithOr(result);
67+
const success = results.some(result => Array.isArray(result) ? !result.some(it => !it.success) : result.success);
68+
const errors = results.map(result => {
69+
if(Array.isArray(result) ) {
70+
return result.map(it => it).filter(it => !it.success)
10271
}
103-
});
72+
return [result].filter(it => !it.success)
73+
})
10474

10575
if (success) {
10676
next();
10777
} else {
108-
throw firstError;
78+
const allErrors = errors.flatMap(it => [...it]);
79+
throw allErrors.find(Boolean)
10980
}
11081
} catch (e) {
11182
const message = e?.error?.message || 'unauthorized';
@@ -129,6 +100,7 @@ class SecuritySchemes {
129100
private securitySchemes: SecuritySchemesMap;
130101
private securityHandlers: SecurityHandlers;
131102
private securities: OpenAPIV3.SecurityRequirementObject[];
103+
132104
constructor(
133105
securitySchemes: SecuritySchemesMap,
134106
securityHandlers: SecurityHandlers,
@@ -213,6 +185,7 @@ class AuthValidator {
213185
private scheme;
214186
private path: string;
215187
private scopes: string[];
188+
216189
constructor(req: OpenApiRequest, scheme, scopes: string[] = []) {
217190
const openapi = <OpenApiRequestMetadata>req.openapi;
218191
this.req = req;

test/resources/security.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,19 @@ paths:
9090
"401":
9191
description: unauthorized
9292

93+
/multi_auth:
94+
get:
95+
security:
96+
- ApiKeyAuth: []
97+
BearerAuth: []
98+
- BasicAuth: []
99+
CookieAuth: []
100+
responses:
101+
"200":
102+
description: OK
103+
"401":
104+
description: unauthorized
105+
93106
/oauth2:
94107
get:
95108
security:

test/security.defaults.spec.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ describe('security.defaults', () => {
2121
.get(`/cookie_auth`, (req, res) => res.json({ logged_in: true }))
2222
.get(`/bearer`, (req, res) => res.json({ logged_in: true }))
2323
.get(`/basic`, (req, res) => res.json({ logged_in: true }))
24-
.get('/no_security', (req, res) => res.json({ logged_in: true })),
24+
.get('/no_security', (req, res) => res.json({ logged_in: true }))
25+
.get('/multi_auth', (req, res) => res.json({ logged_in: true }))
2526
);
2627
});
2728

@@ -64,4 +65,24 @@ describe('security.defaults', () => {
6465
.that.equals('cookie \'JSESSIONID\' required');
6566
});
6667
});
68+
69+
it("Should return 200 WHEN one of multiple security rules is met GIVEN a request with apiKey and bearer token", () => {
70+
return request(app)
71+
.get(`${basePath}/multi_auth`)
72+
.set({
73+
"Authorization": "Bearer rawww",
74+
"X-API-Key": "hello world"
75+
})
76+
.expect(200)
77+
})
78+
79+
it("Should return 200 WHEN one of multiple security rules is met GIVEN a request with cookie and basic auth", () => {
80+
return request(app)
81+
.get(`${basePath}/multi_auth`)
82+
.set({
83+
"Authorization": "Basic ZGVtbzpwQDU1dzByZA==",
84+
"Cookie": "JSESSIONID=dwdwdwdwd"
85+
})
86+
.expect(200)
87+
})
6788
});

0 commit comments

Comments
 (0)