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 @@ export class OperationTrait<J extends v3.OperationTraitObject = v3.OperationTrai
}

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]) => {
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 @@ export class Server extends CoreModel<v3.ServerObject, { id: string }> implement
}

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]) => {
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 @@ export interface ServerObject extends SpecificationExtensions {
protocolVersion?: string;
description?: string;
variables?: Record<string, ServerVariableObject | ReferenceObject>;
security?: Array<SecuritySchemeObject | ReferenceObject>;
security?: Array<SecurityRequirementObject | SecuritySchemeObject | ReferenceObject>;
tags?: TagsObject;
externalDocs?: ExternalDocumentationObject | ReferenceObject;
bindings?: ServerBindingsObject | ReferenceObject;
Expand Down Expand Up @@ -124,7 +124,7 @@ export interface OperationObject extends SpecificationExtensions {
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 @@ export interface OperationTraitObject extends SpecificationExtensions {
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 @@ export interface SecuritySchemeObject extends SpecificationExtensions {
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