From ccaff6d1e7946300bf46c650761a7dc7e060a406 Mon Sep 17 00:00:00 2001 From: David Moore Date: Tue, 24 Oct 2023 11:13:51 +1100 Subject: [PATCH] feat: cors api resource support --- package.json | 2 +- src/gen/proto/deploy/v1/deploy_pb.d.ts | 6 + src/gen/proto/deploy/v1/deploy_pb.js | 53 ++- src/gen/proto/resource/v1/resource_pb.d.ts | 54 +++ src/gen/proto/resource/v1/resource_pb.js | 438 ++++++++++++++++++++- src/resources/api.ts | 139 ++++++- src/types.ts | 10 + yarn.lock | 62 ++- 8 files changed, 741 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index fc9ced5f..aa22f5d4 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "@typescript-eslint/parser": "^4.22.0", "codecov": "^3.8.3", "eslint": "^7.24.0", - "eslint-plugin-jsdoc": "^40.1.0", + "eslint-plugin-jsdoc": "^46.8.2", "glob-run": "^0.1.7", "grpc-tools": "^1.11.3", "husky": "^6.0.0", diff --git a/src/gen/proto/deploy/v1/deploy_pb.d.ts b/src/gen/proto/deploy/v1/deploy_pb.d.ts index 61b2cb6b..d1b16448 100644 --- a/src/gen/proto/deploy/v1/deploy_pb.d.ts +++ b/src/gen/proto/deploy/v1/deploy_pb.d.ts @@ -515,6 +515,11 @@ export class Api extends jspb.Message { getOpenapi(): string; setOpenapi(value: string): void; + hasCors(): boolean; + clearCors(): void; + getCors(): proto_resource_v1_resource_pb.ApiCorsDefinition | undefined; + setCors(value?: proto_resource_v1_resource_pb.ApiCorsDefinition): void; + getDocumentCase(): Api.DocumentCase; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Api.AsObject; @@ -529,6 +534,7 @@ export class Api extends jspb.Message { export namespace Api { export type AsObject = { openapi: string, + cors?: proto_resource_v1_resource_pb.ApiCorsDefinition.AsObject, } export enum DocumentCase { diff --git a/src/gen/proto/deploy/v1/deploy_pb.js b/src/gen/proto/deploy/v1/deploy_pb.js index acda8668..a4a3efde 100644 --- a/src/gen/proto/deploy/v1/deploy_pb.js +++ b/src/gen/proto/deploy/v1/deploy_pb.js @@ -4059,7 +4059,8 @@ proto.nitric.deploy.v1.Api.prototype.toObject = function(opt_includeInstance) { */ proto.nitric.deploy.v1.Api.toObject = function(includeInstance, msg) { var f, obj = { - openapi: jspb.Message.getFieldWithDefault(msg, 1, "") + openapi: jspb.Message.getFieldWithDefault(msg, 1, ""), + cors: (f = msg.getCors()) && proto_resource_v1_resource_pb.ApiCorsDefinition.toObject(includeInstance, f) }; if (includeInstance) { @@ -4100,6 +4101,11 @@ proto.nitric.deploy.v1.Api.deserializeBinaryFromReader = function(msg, reader) { var value = /** @type {string} */ (reader.readString()); msg.setOpenapi(value); break; + case 2: + var value = new proto_resource_v1_resource_pb.ApiCorsDefinition; + reader.readMessage(value,proto_resource_v1_resource_pb.ApiCorsDefinition.deserializeBinaryFromReader); + msg.setCors(value); + break; default: reader.skipField(); break; @@ -4136,6 +4142,14 @@ proto.nitric.deploy.v1.Api.serializeBinaryToWriter = function(message, writer) { f ); } + f = message.getCors(); + if (f != null) { + writer.writeMessage( + 2, + f, + proto_resource_v1_resource_pb.ApiCorsDefinition.serializeBinaryToWriter + ); + } }; @@ -4175,6 +4189,43 @@ proto.nitric.deploy.v1.Api.prototype.hasOpenapi = function() { }; +/** + * optional nitric.resource.v1.ApiCorsDefinition cors = 2; + * @return {?proto.nitric.resource.v1.ApiCorsDefinition} + */ +proto.nitric.deploy.v1.Api.prototype.getCors = function() { + return /** @type{?proto.nitric.resource.v1.ApiCorsDefinition} */ ( + jspb.Message.getWrapperField(this, proto_resource_v1_resource_pb.ApiCorsDefinition, 2)); +}; + + +/** + * @param {?proto.nitric.resource.v1.ApiCorsDefinition|undefined} value + * @return {!proto.nitric.deploy.v1.Api} returns this +*/ +proto.nitric.deploy.v1.Api.prototype.setCors = function(value) { + return jspb.Message.setWrapperField(this, 2, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.nitric.deploy.v1.Api} returns this + */ +proto.nitric.deploy.v1.Api.prototype.clearCors = function() { + return this.setCors(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.nitric.deploy.v1.Api.prototype.hasCors = function() { + return jspb.Message.getField(this, 2) != null; +}; + + diff --git a/src/gen/proto/resource/v1/resource_pb.d.ts b/src/gen/proto/resource/v1/resource_pb.d.ts index 8632bdb6..a7de6e1c 100644 --- a/src/gen/proto/resource/v1/resource_pb.d.ts +++ b/src/gen/proto/resource/v1/resource_pb.d.ts @@ -293,11 +293,64 @@ export namespace ApiScopes { } } +export class ApiCorsDefinition extends jspb.Message { + getAllowCredentials(): boolean; + setAllowCredentials(value: boolean): void; + + clearAllowHeadersList(): void; + getAllowHeadersList(): Array; + setAllowHeadersList(value: Array): void; + addAllowHeaders(value: string, index?: number): string; + + clearAllowMethodsList(): void; + getAllowMethodsList(): Array; + setAllowMethodsList(value: Array): void; + addAllowMethods(value: string, index?: number): string; + + clearAllowOriginsList(): void; + getAllowOriginsList(): Array; + setAllowOriginsList(value: Array): void; + addAllowOrigins(value: string, index?: number): string; + + clearExposeHeadersList(): void; + getExposeHeadersList(): Array; + setExposeHeadersList(value: Array): void; + addExposeHeaders(value: string, index?: number): string; + + getMaxAge(): number; + setMaxAge(value: number): void; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): ApiCorsDefinition.AsObject; + static toObject(includeInstance: boolean, msg: ApiCorsDefinition): ApiCorsDefinition.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: ApiCorsDefinition, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): ApiCorsDefinition; + static deserializeBinaryFromReader(message: ApiCorsDefinition, reader: jspb.BinaryReader): ApiCorsDefinition; +} + +export namespace ApiCorsDefinition { + export type AsObject = { + allowCredentials: boolean, + allowHeadersList: Array, + allowMethodsList: Array, + allowOriginsList: Array, + exposeHeadersList: Array, + maxAge: number, + } +} + export class ApiResource extends jspb.Message { getSecurityDefinitionsMap(): jspb.Map; clearSecurityDefinitionsMap(): void; getSecurityMap(): jspb.Map; clearSecurityMap(): void; + hasCors(): boolean; + clearCors(): void; + getCors(): ApiCorsDefinition | undefined; + setCors(value?: ApiCorsDefinition): void; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ApiResource.AsObject; static toObject(includeInstance: boolean, msg: ApiResource): ApiResource.AsObject; @@ -312,6 +365,7 @@ export namespace ApiResource { export type AsObject = { securityDefinitionsMap: Array<[string, ApiSecurityDefinition.AsObject]>, securityMap: Array<[string, ApiScopes.AsObject]>, + cors?: ApiCorsDefinition.AsObject, } } diff --git a/src/gen/proto/resource/v1/resource_pb.js b/src/gen/proto/resource/v1/resource_pb.js index d22d7ca5..5c271375 100644 --- a/src/gen/proto/resource/v1/resource_pb.js +++ b/src/gen/proto/resource/v1/resource_pb.js @@ -22,6 +22,7 @@ var global = (function() { }.call(null)); goog.exportSymbol('proto.nitric.resource.v1.Action', null, global); +goog.exportSymbol('proto.nitric.resource.v1.ApiCorsDefinition', null, global); goog.exportSymbol('proto.nitric.resource.v1.ApiResource', null, global); goog.exportSymbol('proto.nitric.resource.v1.ApiResourceDetails', null, global); goog.exportSymbol('proto.nitric.resource.v1.ApiScopes', null, global); @@ -274,6 +275,27 @@ if (goog.DEBUG && !COMPILED) { */ proto.nitric.resource.v1.ApiScopes.displayName = 'proto.nitric.resource.v1.ApiScopes'; } +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.nitric.resource.v1.ApiCorsDefinition = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, proto.nitric.resource.v1.ApiCorsDefinition.repeatedFields_, null); +}; +goog.inherits(proto.nitric.resource.v1.ApiCorsDefinition, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.nitric.resource.v1.ApiCorsDefinition.displayName = 'proto.nitric.resource.v1.ApiCorsDefinition'; +} /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a @@ -2387,6 +2409,369 @@ proto.nitric.resource.v1.ApiScopes.prototype.clearScopesList = function() { +/** + * List of repeated fields within this message type. + * @private {!Array} + * @const + */ +proto.nitric.resource.v1.ApiCorsDefinition.repeatedFields_ = [2,3,4,5]; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.toObject = function(opt_includeInstance) { + return proto.nitric.resource.v1.ApiCorsDefinition.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.nitric.resource.v1.ApiCorsDefinition} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.nitric.resource.v1.ApiCorsDefinition.toObject = function(includeInstance, msg) { + var f, obj = { + allowCredentials: jspb.Message.getBooleanFieldWithDefault(msg, 1, false), + allowHeadersList: (f = jspb.Message.getRepeatedField(msg, 2)) == null ? undefined : f, + allowMethodsList: (f = jspb.Message.getRepeatedField(msg, 3)) == null ? undefined : f, + allowOriginsList: (f = jspb.Message.getRepeatedField(msg, 4)) == null ? undefined : f, + exposeHeadersList: (f = jspb.Message.getRepeatedField(msg, 5)) == null ? undefined : f, + maxAge: jspb.Message.getFieldWithDefault(msg, 6, 0) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.nitric.resource.v1.ApiCorsDefinition} + */ +proto.nitric.resource.v1.ApiCorsDefinition.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.nitric.resource.v1.ApiCorsDefinition; + return proto.nitric.resource.v1.ApiCorsDefinition.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.nitric.resource.v1.ApiCorsDefinition} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.nitric.resource.v1.ApiCorsDefinition} + */ +proto.nitric.resource.v1.ApiCorsDefinition.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setAllowCredentials(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.addAllowHeaders(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.addAllowMethods(value); + break; + case 4: + var value = /** @type {string} */ (reader.readString()); + msg.addAllowOrigins(value); + break; + case 5: + var value = /** @type {string} */ (reader.readString()); + msg.addExposeHeaders(value); + break; + case 6: + var value = /** @type {number} */ (reader.readInt32()); + msg.setMaxAge(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.nitric.resource.v1.ApiCorsDefinition.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.nitric.resource.v1.ApiCorsDefinition} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.nitric.resource.v1.ApiCorsDefinition.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getAllowCredentials(); + if (f) { + writer.writeBool( + 1, + f + ); + } + f = message.getAllowHeadersList(); + if (f.length > 0) { + writer.writeRepeatedString( + 2, + f + ); + } + f = message.getAllowMethodsList(); + if (f.length > 0) { + writer.writeRepeatedString( + 3, + f + ); + } + f = message.getAllowOriginsList(); + if (f.length > 0) { + writer.writeRepeatedString( + 4, + f + ); + } + f = message.getExposeHeadersList(); + if (f.length > 0) { + writer.writeRepeatedString( + 5, + f + ); + } + f = message.getMaxAge(); + if (f !== 0) { + writer.writeInt32( + 6, + f + ); + } +}; + + +/** + * optional bool allow_credentials = 1; + * @return {boolean} + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.getAllowCredentials = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 1, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.nitric.resource.v1.ApiCorsDefinition} returns this + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.setAllowCredentials = function(value) { + return jspb.Message.setProto3BooleanField(this, 1, value); +}; + + +/** + * repeated string allow_headers = 2; + * @return {!Array} + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.getAllowHeadersList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 2)); +}; + + +/** + * @param {!Array} value + * @return {!proto.nitric.resource.v1.ApiCorsDefinition} returns this + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.setAllowHeadersList = function(value) { + return jspb.Message.setField(this, 2, value || []); +}; + + +/** + * @param {string} value + * @param {number=} opt_index + * @return {!proto.nitric.resource.v1.ApiCorsDefinition} returns this + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.addAllowHeaders = function(value, opt_index) { + return jspb.Message.addToRepeatedField(this, 2, value, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.nitric.resource.v1.ApiCorsDefinition} returns this + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.clearAllowHeadersList = function() { + return this.setAllowHeadersList([]); +}; + + +/** + * repeated string allow_methods = 3; + * @return {!Array} + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.getAllowMethodsList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 3)); +}; + + +/** + * @param {!Array} value + * @return {!proto.nitric.resource.v1.ApiCorsDefinition} returns this + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.setAllowMethodsList = function(value) { + return jspb.Message.setField(this, 3, value || []); +}; + + +/** + * @param {string} value + * @param {number=} opt_index + * @return {!proto.nitric.resource.v1.ApiCorsDefinition} returns this + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.addAllowMethods = function(value, opt_index) { + return jspb.Message.addToRepeatedField(this, 3, value, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.nitric.resource.v1.ApiCorsDefinition} returns this + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.clearAllowMethodsList = function() { + return this.setAllowMethodsList([]); +}; + + +/** + * repeated string allow_origins = 4; + * @return {!Array} + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.getAllowOriginsList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 4)); +}; + + +/** + * @param {!Array} value + * @return {!proto.nitric.resource.v1.ApiCorsDefinition} returns this + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.setAllowOriginsList = function(value) { + return jspb.Message.setField(this, 4, value || []); +}; + + +/** + * @param {string} value + * @param {number=} opt_index + * @return {!proto.nitric.resource.v1.ApiCorsDefinition} returns this + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.addAllowOrigins = function(value, opt_index) { + return jspb.Message.addToRepeatedField(this, 4, value, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.nitric.resource.v1.ApiCorsDefinition} returns this + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.clearAllowOriginsList = function() { + return this.setAllowOriginsList([]); +}; + + +/** + * repeated string expose_headers = 5; + * @return {!Array} + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.getExposeHeadersList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 5)); +}; + + +/** + * @param {!Array} value + * @return {!proto.nitric.resource.v1.ApiCorsDefinition} returns this + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.setExposeHeadersList = function(value) { + return jspb.Message.setField(this, 5, value || []); +}; + + +/** + * @param {string} value + * @param {number=} opt_index + * @return {!proto.nitric.resource.v1.ApiCorsDefinition} returns this + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.addExposeHeaders = function(value, opt_index) { + return jspb.Message.addToRepeatedField(this, 5, value, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.nitric.resource.v1.ApiCorsDefinition} returns this + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.clearExposeHeadersList = function() { + return this.setExposeHeadersList([]); +}; + + +/** + * optional int32 max_age = 6; + * @return {number} + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.getMaxAge = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 6, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.nitric.resource.v1.ApiCorsDefinition} returns this + */ +proto.nitric.resource.v1.ApiCorsDefinition.prototype.setMaxAge = function(value) { + return jspb.Message.setProto3IntField(this, 6, value); +}; + + + if (jspb.Message.GENERATE_TO_OBJECT) { @@ -2419,7 +2804,8 @@ proto.nitric.resource.v1.ApiResource.prototype.toObject = function(opt_includeIn proto.nitric.resource.v1.ApiResource.toObject = function(includeInstance, msg) { var f, obj = { securityDefinitionsMap: (f = msg.getSecurityDefinitionsMap()) ? f.toObject(includeInstance, proto.nitric.resource.v1.ApiSecurityDefinition.toObject) : [], - securityMap: (f = msg.getSecurityMap()) ? f.toObject(includeInstance, proto.nitric.resource.v1.ApiScopes.toObject) : [] + securityMap: (f = msg.getSecurityMap()) ? f.toObject(includeInstance, proto.nitric.resource.v1.ApiScopes.toObject) : [], + cors: (f = msg.getCors()) && proto.nitric.resource.v1.ApiCorsDefinition.toObject(includeInstance, f) }; if (includeInstance) { @@ -2468,6 +2854,11 @@ proto.nitric.resource.v1.ApiResource.deserializeBinaryFromReader = function(msg, jspb.Map.deserializeBinary(message, reader, jspb.BinaryReader.prototype.readString, jspb.BinaryReader.prototype.readMessage, proto.nitric.resource.v1.ApiScopes.deserializeBinaryFromReader, "", new proto.nitric.resource.v1.ApiScopes()); }); break; + case 3: + var value = new proto.nitric.resource.v1.ApiCorsDefinition; + reader.readMessage(value,proto.nitric.resource.v1.ApiCorsDefinition.deserializeBinaryFromReader); + msg.setCors(value); + break; default: reader.skipField(); break; @@ -2505,6 +2896,14 @@ proto.nitric.resource.v1.ApiResource.serializeBinaryToWriter = function(message, if (f && f.getLength() > 0) { f.serializeBinary(2, writer, jspb.BinaryWriter.prototype.writeString, jspb.BinaryWriter.prototype.writeMessage, proto.nitric.resource.v1.ApiScopes.serializeBinaryToWriter); } + f = message.getCors(); + if (f != null) { + writer.writeMessage( + 3, + f, + proto.nitric.resource.v1.ApiCorsDefinition.serializeBinaryToWriter + ); + } }; @@ -2552,6 +2951,43 @@ proto.nitric.resource.v1.ApiResource.prototype.clearSecurityMap = function() { return this;}; +/** + * optional ApiCorsDefinition cors = 3; + * @return {?proto.nitric.resource.v1.ApiCorsDefinition} + */ +proto.nitric.resource.v1.ApiResource.prototype.getCors = function() { + return /** @type{?proto.nitric.resource.v1.ApiCorsDefinition} */ ( + jspb.Message.getWrapperField(this, proto.nitric.resource.v1.ApiCorsDefinition, 3)); +}; + + +/** + * @param {?proto.nitric.resource.v1.ApiCorsDefinition|undefined} value + * @return {!proto.nitric.resource.v1.ApiResource} returns this +*/ +proto.nitric.resource.v1.ApiResource.prototype.setCors = function(value) { + return jspb.Message.setWrapperField(this, 3, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.nitric.resource.v1.ApiResource} returns this + */ +proto.nitric.resource.v1.ApiResource.prototype.clearCors = function() { + return this.setCors(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.nitric.resource.v1.ApiResource.prototype.hasCors = function() { + return jspb.Message.getField(this, 3) != null; +}; + + diff --git a/src/resources/api.ts b/src/resources/api.ts index 77ddce85..109813d5 100644 --- a/src/resources/api.ts +++ b/src/resources/api.ts @@ -13,6 +13,7 @@ // limitations under the License. import { HttpMiddleware, Faas } from '../faas'; import { + ApiCorsDefinition, ApiResource, ApiScopes, ApiSecurityDefinition, @@ -26,7 +27,7 @@ import { } from '@nitric/api/proto/resource/v1/resource_pb'; import { fromGrpcError } from '../api/errors'; import resourceClient from './client'; -import { HttpMethod } from '../types'; +import { Duration, HttpMethod } from '../types'; import { make, Resource as Base } from './common'; import path from 'path'; @@ -233,6 +234,74 @@ export interface JwtSecurityDefinition extends BaseSecurityDefinition<'jwt'> { // TODO: Union type for multiple security definition mappings export type SecurityDefinition = JwtSecurityDefinition; +export interface CorsOptions { + /** + * Specifies whether credentials are included in the CORS request. + * + * @default false + */ + allowCredentials?: boolean; + /** + * The collection of allowed headers. + * + * @default Allow all headers. + * + * @example + * ```js + * // Allow all headers + * allowHeaders: ["*"] + * + * // Allow specific headers + * allowHeaders: ["Accept", "Content-Type", "Authorization"] + * ``` + */ + allowHeaders?: string[]; + /** + * The collection of allowed HTTP methods. + * + * @default Allow all methods. + * + * @example + * ```js + * // Allow specific methods + * allowMethods: ["GET", "POST"] + * ``` + */ + allowMethods?: HttpMethod[]; + /** + * The collection of allowed origins. + * + * @default Allow all origins. + * + * @example + * ```js + * // Allow all origins + * allowOrigins: ["*"] + * + * // Allow specific origins. Note that the url protocol, ie. "https://", is required. + * allowOrigins: ["https://domain.com"] + * ``` + */ + allowOrigins?: string[]; + /** + * The collection of exposed headers. + * + * @default No expose headers are allowed. + */ + exposeHeaders?: string[]; + /** + * Specify how long the results of a preflight response can be cached + * + * @default No caching + * + * @example + * ```js + * maxAge: "1 day" + * ``` + */ + maxAge?: Duration; +} + export interface ApiOptions { /** * The base path for all routes in the API. @@ -255,6 +324,24 @@ export interface ApiOptions { * Optional root level security for the API */ security?: Record; + + /** + * CORS support applied to all endpoints in this API + * + * @default false + * + * @example + * ```js + * const mainApi = api('main', { + * cors: { + * allowOrigins: ['*'], + * allowMethods: ['GET', 'POST'], + * allowCredentials: true, + * }, + * }); + * ``` + */ + cors?: boolean | CorsOptions; } interface ApiDetails { @@ -276,6 +363,7 @@ export class Api extends Base { SecurityDefinition >; private readonly security?: Record; + private readonly cors?: boolean | CorsOptions; constructor(name: string, options: ApiOptions = {}) { super(name); @@ -284,6 +372,7 @@ export class Api extends Base { path = '/', securityDefinitions = null, security = {} as Record, + cors, } = options; // prepend / to path if its not there this.path = path.replace(/^\/?/, '/'); @@ -291,6 +380,7 @@ export class Api extends Base { this.securityDefinitions = securityDefinitions; this.security = security; this.routes = []; + this.cors = cors; } /** @@ -323,7 +413,9 @@ export class Api extends Base { // join the api level middleware and route level (route options) middleware middleware: [...this.middleware, ...routeMiddleware], }); + this.routes.push(r); + return r; } @@ -467,7 +559,7 @@ export class Api extends Base { const req = new ResourceDeclareRequest(); const resource = new Resource(); const apiResource = new ApiResource(); - const { security, securityDefinitions } = this; + const { security, securityDefinitions, cors } = this; if (security) { Object.keys(security).forEach((k) => { @@ -497,6 +589,22 @@ export class Api extends Base { }); } + if (cors) { + const corsConfig = typeof cors === 'object' ? cors : {}; + const corsDef = new ApiCorsDefinition(); + + corsDef.setAllowCredentials(corsConfig.allowCredentials); + corsDef.setAllowOriginsList(corsConfig.allowOrigins); + corsDef.setAllowHeadersList(corsConfig.allowHeaders); + corsDef.setAllowMethodsList(corsConfig.allowMethods); + corsDef.setExposeHeadersList(corsConfig.exposeHeaders); + corsDef.setMaxAge( + corsConfig.maxAge ? durationToSeconds(corsConfig.maxAge) : undefined + ); + + apiResource.setCors(corsDef); + } + req.setApi(apiResource); req.setResource(resource); @@ -541,3 +649,30 @@ export const jwt = ( const composeMiddleware = (middleware: HttpMiddleware | HttpMiddleware[]) => Array.isArray(middleware) ? middleware : middleware ? [middleware] : []; + +// Function to convert Duration to seconds +/** + * + * @param duration the duration as a string + * @returns number + */ +function durationToSeconds(duration: Duration): number { + const [amount, unit] = duration.split(' '); + + switch (unit) { + case 'second': + case 'seconds': + return parseInt(amount, 10); + case 'minute': + case 'minutes': + return parseInt(amount, 10) * 60; + case 'hour': + case 'hours': + return parseInt(amount, 10) * 3600; + case 'day': + case 'days': + return parseInt(amount, 10) * 86400; + default: + throw new Error('Invalid duration unit'); + } +} diff --git a/src/types.ts b/src/types.ts index 492e8a12..c49e2523 100644 --- a/src/types.ts +++ b/src/types.ts @@ -62,3 +62,13 @@ export type HttpMethod = | 'PUT' | 'DELETE' | 'OPTIONS'; + +export type Duration = `${number} ${ + | 'second' + | 'seconds' + | 'minute' + | 'minutes' + | 'hour' + | 'hours' + | 'day' + | 'days'}`; diff --git a/yarn.lock b/yarn.lock index d47c6b64..e954d028 100644 --- a/yarn.lock +++ b/yarn.lock @@ -312,13 +312,13 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@es-joy/jsdoccomment@~0.37.0": - version "0.37.0" - resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.37.0.tgz#aaafb4bb6c88288aa7899aef0f3b1b851c36f908" - integrity sha512-hjK0wnsPCYLlF+HHB4R/RbUjOWeLW2SlarB67+Do5WsKILOkmIZvvPJFbtWSmbypxcjpoECLAMzoao0D4Bg5ZQ== +"@es-joy/jsdoccomment@~0.40.1": + version "0.40.1" + resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.40.1.tgz#13acd77fb372ed1c83b7355edd865a3b370c9ec4" + integrity sha512-YORCdZSusAlBrFpZ77pJjc5r1bQs5caPWtAu+WWmiSo+8XaUzseapVrfAtiRFbQWnrBxxLLEwF6f6ZG/UgCQCg== dependencies: - comment-parser "1.3.1" - esquery "^1.4.0" + comment-parser "1.4.0" + esquery "^1.5.0" jsdoc-type-pratt-parser "~4.0.0" "@esbuild/android-arm64@0.17.14": @@ -1336,6 +1336,11 @@ anymatch@^3.0.3, anymatch@~3.1.2: resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== +are-docs-informative@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/are-docs-informative/-/are-docs-informative-0.0.2.tgz#387f0e93f5d45280373d387a59d34c96db321963" + integrity sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig== + are-we-there-yet@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" @@ -1611,6 +1616,11 @@ buffer-from@1.x, buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + bundle-require@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/bundle-require/-/bundle-require-4.0.1.tgz#2cc1ad76428043d15e0e7f30990ee3d5404aa2e3" @@ -1871,10 +1881,10 @@ commander@^6.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== -comment-parser@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.3.1.tgz#3d7ea3adaf9345594aedee6563f422348f165c1b" - integrity sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA== +comment-parser@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.4.0.tgz#0f8c560f59698193854f12884c20c0e39a26d32c" + integrity sha512-QLyTNiZ2KDOibvFPlZ6ZngVsZ/0gYnE6uTXi5aoDg8ed3AkJAz4sEje3Y8a29hQ1s6A99MZXe47fLAXQ1rTqaw== compare-func@^2.0.0: version "2.0.0" @@ -2508,17 +2518,19 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" -eslint-plugin-jsdoc@^40.1.0: - version "40.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-40.1.0.tgz#fec2f649a60167fa5a94f05ce2c6c041caaab129" - integrity sha512-ANvrhiu62VlSorARM0hup60VQsS3hNyp0Ca7cnJDj8tpJzM7tNhBVqMVYXSuLzEmqrpwx6aAh+NAN2DdAGG5fQ== +eslint-plugin-jsdoc@^46.8.2: + version "46.8.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.8.2.tgz#3e6b1c93e91e38fe01874d45da121b56393c54a5" + integrity sha512-5TSnD018f3tUJNne4s4gDWQflbsgOycIKEUBoCLn6XtBMgNHxQFmV8vVxUtiPxAQq8lrX85OaSG/2gnctxw9uQ== dependencies: - "@es-joy/jsdoccomment" "~0.37.0" - comment-parser "1.3.1" + "@es-joy/jsdoccomment" "~0.40.1" + are-docs-informative "^0.0.2" + comment-parser "1.4.0" debug "^4.3.4" escape-string-regexp "^4.0.0" esquery "^1.5.0" - semver "^7.3.8" + is-builtin-module "^3.2.1" + semver "^7.5.4" spdx-expression-parse "^3.0.1" eslint-scope@^5.1.1: @@ -3486,6 +3498,13 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +is-builtin-module@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" @@ -5627,7 +5646,7 @@ semver-compare@^1.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@7.x, semver@^7.1.1, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.8: +semver@7.x, semver@^7.1.1, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: version "7.3.8" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== @@ -5639,6 +5658,13 @@ semver@^6.0.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.5.4: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"