Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions packages/parser/src/models/v3/operation-trait.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,34 @@
}

security(): SecurityRequirements[] {
return (this._json.security || []).map((security, index) => {
const scheme = this.createModel(SecurityScheme, security as v3.SecuritySchemeObject, { id: '', pointer: this.jsonPath(`security/${index}`) });
const requirement = this.createModel(SecurityRequirement, { scheme, scopes: (security as v3.SecuritySchemeObject).scopes }, { id: '', pointer: this.jsonPath(`security/${index}`) });
return new SecurityRequirements([requirement]);
const securitySchemes = (this._meta?.asyncapi?.parsed?.components?.securitySchemes || {}) as Record<string, v3.SecuritySchemeObject>;
return (this._json.security || []).map((requirement, index) => {
const requirements: SecurityRequirement[] = [];

// Check if this is a reference
const isReference = requirement && typeof requirement === 'object' && '$ref' in requirement;
// Check if this is a SecuritySchemeObject (has 'type' property)
const isSecurityScheme = requirement && typeof requirement === 'object' && 'type' in requirement;

if (!isReference && !isSecurityScheme && requirement && typeof requirement === 'object') {
// New format: SecurityRequirementObject (Record<string, Array<SecuritySchemeObject>>)
Object.entries(requirement as v3.SecurityRequirementObject).forEach(([security, schemes]) => {

Check warning on line 36 in packages/parser/src/models/v3/operation-trait.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=asyncapi_parser-js&issues=AZ7jMa-jupWwgW3Y71yb&open=AZ7jMa-jupWwgW3Y71yb&pullRequest=1190
schemes.forEach((schemeData, schemeIndex) => {
const scheme = this.createModel(SecurityScheme, securitySchemes[security] || schemeData, { id: security, pointer: `/components/securitySchemes/${security}` });
requirements.push(
this.createModel(SecurityRequirement, { scheme, scopes: (schemeData as v3.SecuritySchemeObject).scopes || [] }, { id: security, pointer: `${this.meta().pointer}/security/${index}/${security}/${schemeIndex}` })
);
});
});
} else {
// Old format: direct SecuritySchemeObject or ReferenceObject (for backward compatibility)
const scheme = this.createModel(SecurityScheme, requirement as v3.SecuritySchemeObject, { id: '', pointer: this.jsonPath(`security/${index}`) });
requirements.push(
this.createModel(SecurityRequirement, { scheme, scopes: (requirement as v3.SecuritySchemeObject).scopes || [] }, { id: '', pointer: this.jsonPath(`security/${index}`) })
);
}

return new SecurityRequirements(requirements);
});
}
}
32 changes: 28 additions & 4 deletions packages/parser/src/models/v3/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,34 @@
}

security(): SecurityRequirements[] {
return (this._json.security || []).map((security, index) => {
const scheme = this.createModel(SecurityScheme, security as v3.SecuritySchemeObject, { id: '', pointer: this.jsonPath(`security/${index}`) });
const requirement = this.createModel(SecurityRequirement, { scheme, scopes: (security as v3.SecuritySchemeObject).scopes }, { id: '', pointer: this.jsonPath(`security/${index}`) });
return new SecurityRequirements([requirement]);
const securitySchemes = (this._meta?.asyncapi?.parsed?.components?.securitySchemes || {}) as Record<string, v3.SecuritySchemeObject>;
return (this._json.security || []).map((requirement, index) => {
const requirements: SecurityRequirement[] = [];

// Check if this is a reference
const isReference = requirement && typeof requirement === 'object' && '$ref' in requirement;
// Check if this is a SecuritySchemeObject (has 'type' property)
const isSecurityScheme = requirement && typeof requirement === 'object' && 'type' in requirement;

if (!isReference && !isSecurityScheme && requirement && typeof requirement === 'object') {
// New format: SecurityRequirementObject (Record<string, Array<SecuritySchemeObject>>)
Object.entries(requirement as v3.SecurityRequirementObject).forEach(([security, schemes]) => {

Check warning on line 130 in packages/parser/src/models/v3/server.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=asyncapi_parser-js&issues=AZ7jMbAoupWwgW3Y71yc&open=AZ7jMbAoupWwgW3Y71yc&pullRequest=1190
schemes.forEach((schemeData, schemeIndex) => {
const scheme = this.createModel(SecurityScheme, securitySchemes[security] || schemeData, { id: security, pointer: `/components/securitySchemes/${security}` });
requirements.push(
this.createModel(SecurityRequirement, { scheme, scopes: (schemeData as v3.SecuritySchemeObject).scopes || [] }, { id: security, pointer: `${this.meta().pointer}/security/${index}/${security}/${schemeIndex}` })
);
});
});
} else {
// Old format: direct SecuritySchemeObject or ReferenceObject (for backward compatibility)
const scheme = this.createModel(SecurityScheme, requirement as v3.SecuritySchemeObject, { id: '', pointer: this.jsonPath(`security/${index}`) });
requirements.push(
this.createModel(SecurityRequirement, { scheme, scopes: (requirement as v3.SecuritySchemeObject).scopes || [] }, { id: '', pointer: this.jsonPath(`security/${index}`) })
);
}

return new SecurityRequirements(requirements);
});
}
}
8 changes: 5 additions & 3 deletions packages/parser/src/spec-types/v3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
protocolVersion?: string;
description?: string;
variables?: Record<string, ServerVariableObject | ReferenceObject>;
security?: Array<SecuritySchemeObject | ReferenceObject>;
security?: Array<SecurityRequirementObject | SecuritySchemeObject | ReferenceObject>;

Check warning on line 49 in packages/parser/src/spec-types/v3.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this union type with a type alias.

See more on https://sonarcloud.io/project/issues?id=asyncapi_parser-js&issues=AZ7jMbA9upWwgW3Y71yd&open=AZ7jMbA9upWwgW3Y71yd&pullRequest=1190
tags?: TagsObject;
externalDocs?: ExternalDocumentationObject | ReferenceObject;
bindings?: ServerBindingsObject | ReferenceObject;
Expand Down Expand Up @@ -124,7 +124,7 @@
title?: string;
summary?: string;
description?: string;
security?: Array<SecuritySchemeObject | ReferenceObject>;
security?: Array<SecurityRequirementObject | SecuritySchemeObject | ReferenceObject>;
tags?: TagsObject;
externalDocs?: ExternalDocumentationObject | ReferenceObject;
bindings?: OperationBindingsObject | ReferenceObject;
Expand All @@ -135,7 +135,7 @@
title?: string;
summary?: string;
description?: string;
security?: Array<SecuritySchemeObject | ReferenceObject>;
security?: Array<SecurityRequirementObject | SecuritySchemeObject | ReferenceObject>;
tags?: TagsObject;
externalDocs?: ExternalDocumentationObject | ReferenceObject;
bindings?: OperationBindingsObject | ReferenceObject;
Expand Down Expand Up @@ -277,6 +277,8 @@
scopes?: string[];
}

export type SecurityRequirementObject = Record<string, Array<SecuritySchemeObject | ReferenceObject>>;

export type SecuritySchemeType =
| 'userPassword'
| 'apiKey'
Expand Down
138 changes: 137 additions & 1 deletion packages/parser/test/models/v3/operation-trait.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('OperationTrait model', function() {
});

describe('.security()', function() {
it('should return collection of security requirements', function() {
it('should return collection of security requirements - old format (direct SecuritySchemeObject)', function() {
const d = new OperationTrait({ security: [{ type: 'apiKey' }] });

const security = d.security();
Expand All @@ -46,13 +46,149 @@ describe('OperationTrait model', function() {
expect(requirement.scheme()).toBeInstanceOf(SecurityScheme);
expect(requirement.scopes()).toEqual([]);
});

it('should return collection of security requirements - new format (SecurityRequirementObject)', function() {
const securitySchemes = {
apiKey: { type: 'apiKey', in: 'user' },
oauth2: { type: 'oauth2', flows: {}, scopes: ['read', 'write'] }
};
const d = new OperationTrait(
{
security: [
{
apiKey: [{ type: 'apiKey', in: 'user' }],
oauth2: [{ type: 'oauth2', flows: {}, scopes: ['read', 'write'] }]
}
]
},
{
asyncapi: {
parsed: {
components: {
securitySchemes
}
}
}
} as any
);

const security = d.security();
expect(Array.isArray(security)).toEqual(true);
expect(security).toHaveLength(1);
expect(security[0]).toBeInstanceOf(SecurityRequirements);

const requirements = security[0].all();
expect(requirements).toHaveLength(2);

// First requirement (apiKey)
const requirement1 = requirements[0] as SecurityRequirement;
expect(requirement1).toBeInstanceOf(SecurityRequirement);
expect(requirement1.scheme()).toBeInstanceOf(SecurityScheme);
expect(requirement1.scheme().type()).toEqual('apiKey');
expect(requirement1.scopes()).toEqual([]);

// Second requirement (oauth2)
const requirement2 = requirements[1] as SecurityRequirement;
expect(requirement2).toBeInstanceOf(SecurityRequirement);
expect(requirement2.scheme()).toBeInstanceOf(SecurityScheme);
expect(requirement2.scheme().type()).toEqual('oauth2');
expect(requirement2.scopes()).toEqual(['read', 'write']);
});

it('should return collection of security requirements - new format with multiple schemes in array', function() {
const securitySchemes = {
apiKey: { type: 'apiKey', in: 'user' }
};
const d = new OperationTrait(
{
security: [
{
apiKey: [
{ type: 'apiKey', in: 'user' },
{ type: 'apiKey', in: 'password' }
]
}
]
},
{
asyncapi: {
parsed: {
components: {
securitySchemes
}
}
}
} as any
);

const security = d.security();
expect(Array.isArray(security)).toEqual(true);
expect(security).toHaveLength(1);

const requirements = security[0].all();
expect(requirements).toHaveLength(2);

requirements.forEach((req) => {
expect(req).toBeInstanceOf(SecurityRequirement);
expect(req.scheme()).toBeInstanceOf(SecurityScheme);
expect(req.scheme().type()).toEqual('apiKey');
});
});

it('should return collection of security requirements - old format with reference', function() {
const d = new OperationTrait({
security: [{ $ref: '#/components/securitySchemes/apiKey' }]
});

const security = d.security();
expect(Array.isArray(security)).toEqual(true);
expect(security).toHaveLength(1);
expect(security[0]).toBeInstanceOf(SecurityRequirements);
});

it('should return collection of security requirements when value is undefined', function() {
const doc = {};
const d = new OperationTrait(doc);
expect(Array.isArray(d.security())).toEqual(true);
expect(d.security()).toHaveLength(0);
});

it('should handle mixed security requirements', function() {
const securitySchemes = {
apiKey: { type: 'apiKey', in: 'user' }
};
const d = new OperationTrait(
{
security: [
{ type: 'http', scheme: 'bearer' }, // Old format
{ apiKey: [{ type: 'apiKey', in: 'user' }] } // New format
]
},
{
asyncapi: {
parsed: {
components: {
securitySchemes
}
}
}
} as any
);

const security = d.security();
expect(Array.isArray(security)).toEqual(true);
expect(security).toHaveLength(2);

// First requirement (old format)
expect(security[0]).toBeInstanceOf(SecurityRequirements);
const req1 = security[0].all()[0] as SecurityRequirement;
expect(req1.scheme().type()).toEqual('http');

// Second requirement (new format)
expect(security[1]).toBeInstanceOf(SecurityRequirements);
const req2 = security[1].all()[0] as SecurityRequirement;
expect(req2.scheme().type()).toEqual('apiKey');
});
});

describe('mixins', function() {
Expand Down
Loading
Loading