UNPKG

@linode/validation

Version:

Yup validation schemas for use with the Linode APIv4

1,291 lines (1,278 loc) 101 kB
// src/account.schema.ts import { array as array2, boolean as boolean2, mixed, number as number2, object as object2, string as string2 } from "yup"; // src/profile.schema.ts import { isPossiblePhoneNumber } from "libphonenumber-js"; import { array, boolean, number, object, string } from "yup"; var EMAIL_VALIDATION_REGEX = new RegExp( // eslint-disable-next-line sonarjs/regex-complexity /^(?!-.*|.*(\.{2}|@-))[a-zA-Z0-9_.+"-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]*[a-zA-Z0-9]$/ ); var createPersonalAccessTokenSchema = object({ scopes: string(), expiry: string(), label: string().min(1, "Label must be between 1 and 100 characters.").max(100, "Label must be between 1 and 100 characters.") }); var createSSHKeySchema = object({ label: string().required("Label is required.").min(1, "Label must be between 1 and 64 characters.").max(64, "Label must be between 1 and 64 characters.").trim(), ssh_key: string() }); var updateSSHKeySchema = object({ label: string().required("Label is required.").min(1, "Label must be between 1 and 64 characters.").max(64, "Label must be between 1 and 64 characters.").trim() }); var emailSchema = string().required("Email address is required.").max(128, "Email address must be 128 characters or less.").email("Must be a valid email address.").matches(EMAIL_VALIDATION_REGEX, `Invalid email address.`); var updateProfileSchema = object({ email: string().email().matches(EMAIL_VALIDATION_REGEX, `Invalid email address. `), timezone: string(), email_notifications: boolean(), authorized_keys: array().of(string()), restricted: boolean(), two_factor_auth: boolean(), lish_auth_method: string().oneOf(["password_keys", "keys_only", "disabled"]), authentication_type: string().oneOf(["password", "github"]) }); var SendCodeToPhoneNumberSchema = object({ iso_code: string().required(), phone_number: string().test( "is-phone-number", "Not a valid phone number", (phone_number, context) => { const { iso_code } = context.parent; if (!phone_number) { return false; } return isPossiblePhoneNumber(phone_number, iso_code); } ) }); var VerifyPhoneNumberCodeSchema = object({ otp_code: string().required("Verification Code is required.").test( "digits only", "The verification code must only contain digits.", (value) => { if (!value) { return true; } return /^\d+$/.test(value); } ) }); var SecurityQuestionsSchema = object({ security_questions: array().of( object({ question_id: number().required("You must pick a question."), response: string().min(3, "Answers must be at least 3 characters.").max(17, "Answers must be at most 17 characters.").required("You must provide an answer to each security question.") }).required() ).length(3, "You must answer all 3 security questions.").required() }); // src/account.schema.ts var updateAccountSchema = object2({ email: string2().max(128, "Email must be 128 characters or less."), address_1: string2().max(64, "Address must be 64 characters or less."), city: string2().max(24, "City must be 24 characters or less."), company: string2().max(128, "Company must be 128 characters or less."), country: string2().min(2, "Country code must be two letters.").max(2, "Country code must be two letters."), first_name: string2().max(50, "First name must be 50 characters or less."), last_name: string2().max(50, "Last name must be 50 characters or less."), address_2: string2().max(64, "Address must be 64 characters or less."), phone: string2().max(32, "Phone number must be 32 characters or less."), state: string2().max(24, "State must be 24 characters or less."), tax_id: string2().max(100, "Tax ID must be 100 characters or less."), zip: string2().max(16, "Zip code must be 16 characters or less.") }); var createOAuthClientSchema = object2({ label: string2().required("Label is required.").min(1, "Label must be between 1 and 512 characters.").max(512, "Label must be between 1 and 512 characters."), redirect_uri: string2().required("Redirect URI is required.") }); var updateOAuthClientSchema = object2({ label: string2().min(1, "Label must be between 1 and 512 characters.").max(512, "Label must be between 1 and 512 characters."), redirect_uri: string2() }); var PaymentSchema = object2({ usd: string2().required("USD payment amount is required.") }); var CreditCardSchema = object2({ card_number: string2().required("Credit card number is required.").min(13, "Credit card number must be between 13 and 23 characters.").max(23, "Credit card number must be between 13 and 23 characters."), expiry_year: number2().test( "length", "Expiration year must be 2 or 4 digits.", (value) => [2, 4].includes(String(value).length) ).required("Expiration year is required.").typeError("Expiration year must be a number.").min((/* @__PURE__ */ new Date()).getFullYear(), "Expiration year must not be in the past.").max((/* @__PURE__ */ new Date()).getFullYear() + 20, "Expiry too far in the future."), expiry_month: number2().required("Expiration month is required.").typeError("Expiration month must be a number.").min(1, "Expiration month must be a number from 1 to 12.").max(12, "Expiration month must be a number from 1 to 12."), cvv: string2().required("Security code is required.").min(3, "Security code must be between 3 and 4 characters.").max(4, "Security code must be between 3 and 4 characters.") }); var PaymentMethodSchema = object2({ type: mixed().oneOf( ["credit_card", "payment_method_nonce"], "Type must be credit_card or payment_method_nonce." ), data: object2().when("type", { is: "credit_card", then: () => CreditCardSchema, otherwise: () => object2({ nonce: string2().required("Payment nonce is required.") }) }), is_default: boolean2().required( "You must indicate if this should be your default method of payment." ) }); var userNameErrors = { lengthError: "Username must be between 3 and 32 characters.", consecutiveError: "Username must not include two dashes or underscores in a row.", charsError: "Username may only contain letters, numbers, dashes, and underscores and must begin and end with letters or numbers.", spacesError: "Username may not contain spaces or tabs.", nonAsciiError: "Username must only use ASCII characters." }; var usernameSchema = string2().required("Username is required.").min(3, userNameErrors.lengthError).max(32, userNameErrors.lengthError).test("ascii-only", userNameErrors.nonAsciiError, (value) => { if (!value) return false; return [...value].every((char) => char.charCodeAt(0) <= 127); }).test("no-whitespace", userNameErrors.spacesError, (value) => { if (!value) return true; return !/[ \t]/.test(value); }).test( "no-consecutive-separators", userNameErrors.consecutiveError, (value) => { if (!value) return true; return !value.includes("__") && !value.includes("--"); } ).test("valid-characters", userNameErrors.charsError, (value) => { if (!value) return false; const firstChar = value[0]; const lastChar = value[value.length - 1]; const isAlphaNum = /[a-zA-Z0-9]/; if (!isAlphaNum.test(firstChar) || !isAlphaNum.test(lastChar)) { return false; } return /^[a-zA-Z0-9_-]+$/.test(value); }); var CreateUserSchema = object2({ username: usernameSchema, email: emailSchema, restricted: boolean2().required( "You must indicate if this user should have restricted access." ) }); var UpdateUserNameSchema = object2({ username: usernameSchema }); var UpdateUserEmailSchema = object2({ email: emailSchema }); var UpdateUserSchema = object2({ username: string2().min(3, userNameErrors.lengthError).max(32, userNameErrors.lengthError), email: string2().email("Must be a valid email address."), restricted: boolean2() }); var GrantSchema = object2({ id: number2().required("ID is required."), permissions: string2().oneOf( ["read_only", "read_write"], "Permissions must be null, read_only, or read_write." ).nullable("Permissions must be null, read_only, or read_write.") }); var UpdateGrantSchema = object2({ global: object2(), linode: array2().of(GrantSchema), domain: array2().of(GrantSchema), nodebalancer: array2().of(GrantSchema), image: array2().of(GrantSchema), longview: array2().of(GrantSchema), stackscript: array2().of(GrantSchema), volume: array2().of(GrantSchema) }); var UpdateAccountSettingsSchema = object2({ network_helper: boolean2(), backups_enabled: boolean2(), managed: boolean2(), longview_subscription: string2().nullable(), object_storage: string2(), interfaces_for_new_linodes: string2() }); var PromoCodeSchema = object2({ promo_code: string2().required("Promo code is required.").min(1, "Promo code must be between 1 and 32 characters.").max(32, "Promo code must be between 1 and 32 characters.") }); // src/buckets.schema.ts import { boolean as boolean3, object as object3, string as string3 } from "yup"; var ENDPOINT_TYPES = ["E0", "E1", "E2", "E3"]; var CreateBucketSchema = object3().shape( { label: string3().required("Bucket name is required.").min(3, "Bucket name must be between 3 and 63 characters.").matches(/^\S*$/, "Bucket name must not contain spaces.").matches( /^[a-z0-9].*[a-z0-9]$/, "Bucket name must start and end with a lowercase letter or number." ).matches( /^(?!.*[.-]{2})[a-z0-9.-]+$/, "Bucket name must contain only lowercase letters, numbers, periods (.), and hyphens (-). Adjacent periods and hyphens are not allowed." ).max(63, "Bucket name must be between 3 and 63 characters.").test( "unique-label", "A bucket with this name already exists in your selected region", (value, context) => { var _a; const { cluster, region } = context.parent; const buckets = (_a = context.options.context) == null ? void 0 : _a.buckets; if (!Array.isArray(buckets)) { return true; } return !buckets.some( (bucket) => bucket.label === value && (bucket.cluster === cluster || bucket.region === region) ); } ), cluster: string3().when("region", { is: (region) => !region || region.length === 0, then: (schema) => schema.required("Cluster is required.") }), region: string3().when("cluster", { is: (cluster) => !cluster || cluster.length === 0, then: (schema) => schema.required("Region is required.") }), endpoint_type: string3().oneOf([...ENDPOINT_TYPES]).optional(), cors_enabled: boolean3().optional(), acl: string3().oneOf([ "private", "public-read", "authenticated-read", "public-read-write" ]).optional(), s3_endpoint: string3().optional() }, [["cluster", "region"]] ).test("cors-enabled-check", "Invalid CORS configuration.", function(value) { const { endpoint_type, cors_enabled } = value; if ((endpoint_type === "E0" || endpoint_type === "E1") && !cors_enabled) { return this.createError({ path: "cors_enabled", message: "CORS must be enabled for endpoint type E0 or E1." }); } if ((endpoint_type === "E2" || endpoint_type === "E3") && cors_enabled) { return this.createError({ path: "cors_enabled", message: "CORS must be disabled for endpoint type E2 or E3." }); } return true; }); var UploadCertificateSchema = object3({ certificate: string3().required("Certificate is required."), private_key: string3().required("Private key is required.") }); var UpdateBucketAccessSchema = object3({ acl: string3().oneOf([ "private", "public-read", "authenticated-read", "public-read-write" ]).notRequired(), cors_enabled: boolean3().notRequired() }); // src/cloudnats.schema.ts import { array as array3, number as number3, object as object4, string as string4 } from "yup"; var VALID_PORT_SIZES = [ 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384 ]; var createCloudNATSchema = object4({ addresses: array3().of( object4({ address: string4().required("Address must be a string.") }) ).notRequired(), label: string4().required("Label is required.").max(150, "Label must be 150 characters or fewer."), port_prefix_default_len: number3().oneOf(VALID_PORT_SIZES, "Invalid port size.").notRequired(), region: string4().required("Region is required.") }); var updateCloudNATSchema = object4({ label: string4().max(150, "Label must be 150 characters or fewer.") }); // src/cloudpulse.schema.ts import { array as array4, number as number4, object as object5, string as string5 } from "yup"; var fieldErrorMessage = "This field is required."; var dimensionFilters = object5({ dimension_label: string5().required(fieldErrorMessage), operator: string5().oneOf(["eq", "neq", "startswith", "endswith", "in"]).required(fieldErrorMessage), value: string5().required(fieldErrorMessage) }); var metricCriteria = object5({ metric: string5().required(fieldErrorMessage), aggregate_function: string5().oneOf(["avg", "count", "max", "min", "sum"]).required(fieldErrorMessage), operator: string5().oneOf(["eq", "gt", "lt", "gte", "lte"]).required(fieldErrorMessage), threshold: number4().required(fieldErrorMessage).min(0, "Enter a positive value.").typeError("The value should be a number."), dimension_filters: array4().of(dimensionFilters.defined()).optional() }); var triggerConditionValidation = object5({ criteria_condition: string5().oneOf(["ALL"]).required("Criteria condition is required"), polling_interval_seconds: number4().required(fieldErrorMessage), evaluation_period_seconds: number4().required(fieldErrorMessage), trigger_occurrences: number4().required(fieldErrorMessage).positive("Enter a positive value.").typeError("The value should be a number.") }); var specialStartEndRegex = /^[^a-zA-Z0-9]/; var createAlertDefinitionSchema = object5({ label: string5().required(fieldErrorMessage).matches( /^[^*#&+:<>"?@%{}\\\/]+$/, "Name cannot contain special characters: * # & + : < > ? @ % { } \\ /." ).max(100, "Name must be 100 characters or less.").test( "no-special-start-end", "Name cannot start or end with a special character.", (value) => { return !specialStartEndRegex.test(value != null ? value : ""); } ), description: string5().max(100, "Description must be 100 characters or less.").test( "no-special-start-end", "Description cannot start or end with a special character.", (value) => { return !specialStartEndRegex.test(value != null ? value : ""); } ).optional(), severity: number4().oneOf([0, 1, 2, 3]).required(fieldErrorMessage), rule_criteria: object5({ rules: array4().of(metricCriteria).min(1, "At least one metric criteria is required.").required() }).required(), trigger_conditions: triggerConditionValidation, channel_ids: array4().of(number4().required()).required().min(1, "At least one notification channel is required."), tags: array4().of(string5().defined()).optional(), entity_ids: array4().of(string5().defined()).optional(), regions: array4().of(string5().defined()).optional(), scope: string5().oneOf(["entity", "region", "account"]).nullable().optional() }); var editAlertDefinitionSchema = object5({ channel_ids: array4().of(number4().required()).optional(), label: string5().matches( /^[^*#&+:<>"?@%{}\\/]+$/, "Name cannot contain special characters: * # & + : < > ? @ % { } \\ /." ).max(100, "Name must be 100 characters or less.").test( "no-special-start-end", "Name cannot start or end with a special character.", (value) => { return !specialStartEndRegex.test(value != null ? value : ""); } ).optional(), description: string5().max(100, "Description must be 100 characters or less.").test( "no-special-start-end", "Description cannot start or end with a special character.", (value) => { return !specialStartEndRegex.test(value != null ? value : ""); } ).optional(), entity_ids: array4().of(string5().defined()).optional(), rule_criteria: object5({ rules: array4().of(metricCriteria).min(1, "At least one metric criteria is required.").required() }).optional().default(void 0), tags: array4().of(string5().defined()).optional(), trigger_conditions: triggerConditionValidation.optional().default(void 0), severity: number4().oneOf([0, 1, 2, 3]).optional(), status: string5().oneOf([ "enabled", "disabled", "in progress", "failed", "provisioning", "disabling", "enabling" ]).optional(), scope: string5().oneOf(["entity", "region", "account"]).nullable().optional(), regions: array4().of(string5().defined()).optional() }); // src/databases.schema.ts import { boolean as boolean4, mixed as mixed2, number as number5 } from "yup"; import { array as array5, object as object6, string as string6 } from "yup"; var LABEL_MESSAGE = "Label must be between 3 and 32 characters"; var createDatabaseSchema = object6({ label: string6().required("Label is required").min(3, LABEL_MESSAGE).max(32, LABEL_MESSAGE), engine: string6().required("Database Engine is required"), region: string6().required("Region is required"), type: string6().required("Type is required"), cluster_size: number5().oneOf([1, 2, 3], "Nodes are required").required("Nodes are required") }); var getDynamicDatabaseSchema = (isVPCSelected) => { if (!isVPCSelected) { return createDatabaseSchema; } return createDatabaseSchema.shape({ private_network: object6().shape({ subnet_id: number5().nullable().required("Subnet is required.") }) }); }; var updateMaintenanceSchema = object6({ frequency: string6().oneOf(["weekly", "monthly"]).optional(), hour_of_day: number5(), day_of_week: number5(), week_of_month: number5().nullable() }); var updateDatabaseSchema = object6({ label: string6().notRequired().min(3, LABEL_MESSAGE).max(32, LABEL_MESSAGE), allow_list: array5().of(string6()).notRequired(), updates: object6().notRequired().shape({ frequency: string6().oneOf(["weekly", "monthly"]), duration: number5(), hour_of_day: number5(), day_of_week: number5(), week_of_month: number5().nullable() }).nullable(), type: string6().notRequired() }); var updatePrivateNetworkSchema = object6({ private_network: object6().shape({ vpc_id: number5().required("VPC is required."), subnet_id: number5().required("Subnet is required."), public_access: boolean4().default(false) }) }); var createValidator = (key, field) => { const fieldTypes = Array.isArray(field.type) ? field.type : [field.type]; switch (true) { case fieldTypes.includes("integer"): return number5().transform((val, originalVal) => originalVal === "" ? void 0 : val).integer(`${key} must be a whole number`).required(`${key} is required`); case fieldTypes.includes("number"): return number5().transform((val, originalVal) => originalVal === "" ? void 0 : val).required(`${key} is required`); case fieldTypes.includes("string"): return string6(); case fieldTypes.includes("boolean"): return boolean4(); default: return null; } }; var applyConstraints = (validator, key, field) => { if (!validator) return null; if (field.minimum !== void 0) { validator = validator.min( field.minimum, `${key} must be at least ${field.minimum}` ); } if (field.maximum !== void 0) { if (field.maximum > Number.MAX_SAFE_INTEGER) { validator = validator.max( Number.MAX_SAFE_INTEGER, `${key} must be at most ${Number.MAX_SAFE_INTEGER}` ); } else { validator = validator.max( field.maximum, `${key} must be at most ${field.maximum}` ); } } if (field.minLength !== void 0) { validator = validator.min( field.minLength, `${key} must be at least ${field.minLength} characters` ); } if (field.maxLength !== void 0) { validator = validator.max( field.maxLength, `${key} must be at most ${field.maxLength} characters` ); } if (field.pattern) { const pattern = field.pattern; if (key === "default_time_zone") { validator = validator.matches( new RegExp(pattern), `${key} must be an IANA timezone, 'SYSTEM', or a valid UTC offset (e.g., '+03:00')` ); } else { validator = validator.matches( new RegExp(pattern), `Please ensure that ${key} follows the format ${field.example}` ); } } if (key === "wal_sender_timeout") { validator = validator.test( "is-zero-or-in-range", `${key} must be 0 or between 5000 and 10800000`, (value) => { if (typeof value !== "number") return false; return value === 0 || value >= 5e3 && value <= 108e5; } ); } if (key === "timezone") { if (!field.value) { validator = validator.required("timezone cannot be empty"); } } if (key === "net_buffer_length") { validator = validator.test( "is-multiple-of-1024", `${key} must be a multiple of 1024`, (value) => { if (typeof value !== "number") return false; return value % 1024 === 0; } ); } return validator; }; var processField = (schemaShape, field) => { if (!field.label || !field.type) { return; } const key = field.label; let validator = createValidator(key, field); validator = applyConstraints(validator, key, field); if (validator) { schemaShape[key] = object6().shape({ value: validator }); } }; var createDynamicAdvancedConfigSchema = (allConfigurations) => { if (!Array.isArray(allConfigurations) || allConfigurations.length === 0) { return object6().shape({}); } const schemaShape = {}; allConfigurations.forEach((field) => processField(schemaShape, field)); return object6().shape({ configs: array5().of( object6({ label: string6().required(), value: mixed2().when("label", (label, schema) => { var _a, _b; if (Array.isArray(label)) { label = label[0]; } if (typeof label !== "string" || !schemaShape[label]) { return schema; } const valueSchema = (_b = (_a = schemaShape[label]) == null ? void 0 : _a.fields) == null ? void 0 : _b.value; return valueSchema ? valueSchema : schema; }) }) ) }); }; var createDatabaseConnectionPoolSchema = object6({ database: string6().required("Database is required"), mode: string6().oneOf(["transaction", "session", "statement"], "Pool mode is required").required("Pool mode is required"), label: string6().required("Name is required").max(63, "Name must not exceed 63 characters"), size: number5().required("Size is required"), username: string6().nullable().required("Username is required") }); var updateDatabaseConnectionPoolSchema = object6({ database: string6().optional(), mode: string6().oneOf(["transaction", "session", "statement"]).optional(), size: number5().optional(), username: string6().nullable().optional() }); // src/delivery.schema.ts import { array as array6, boolean as boolean5, lazy, mixed as mixed3, number as number6, object as object7, string as string7 } from "yup"; var maxLength = 255; var maxLengthMessage = "Length must be 255 characters or less."; var authenticationDetailsSchema = object7({ basic_authentication_user: string7().max(maxLength, maxLengthMessage).required(), basic_authentication_password: string7().max(maxLength, maxLengthMessage).required() }); var authenticationSchema = object7({ type: string7().oneOf(["basic", "none"]).required(), details: mixed3().defined().when("type", { is: "basic", then: () => authenticationDetailsSchema.required(), otherwise: () => mixed3().nullable().test( "null-or-undefined", "For type `none` details should be `null` or `undefined`.", (value) => !value ) }) }); var clientCertificateDetailsSchema = object7({ tls_hostname: string7().max(maxLength, maxLengthMessage).required(), client_ca_certificate: string7().required(), client_certificate: string7().required(), client_private_key: string7().required() }); var customHeaderSchema = object7({ name: string7().max(maxLength, maxLengthMessage).required(), value: string7().max(maxLength, maxLengthMessage).required() }); var customHTTPsDetailsSchema = object7({ authentication: authenticationSchema.required(), client_certificate_details: clientCertificateDetailsSchema.optional(), content_type: string7().oneOf(["application/json", "application/json; charset=utf-8"]).required(), custom_headers: array6().of(customHeaderSchema).min(1).optional(), data_compression: string7().oneOf(["gzip", "None"]).required(), endpoint_url: string7().max(maxLength, maxLengthMessage).required() }); var akamaiObjectStorageDetailsBaseSchema = object7({ host: string7().max(maxLength, maxLengthMessage).required("Host is required."), bucket_name: string7().required("Bucket name is required.").min(3, "Bucket name must be between 3 and 63 characters.").matches(/^\S*$/, "Bucket name must not contain spaces.").matches( /^[a-z0-9].*[a-z0-9]$/, "Bucket name must start and end with a lowercase letter or number." ).matches( /^(?!.*[.-]{2})[a-z0-9.-]+$/, "Bucket name must contain only lowercase letters, numbers, periods (.), and hyphens (-). Adjacent periods and hyphens are not allowed." ).max(63, "Bucket name must be between 3 and 63 characters."), path: string7().max(maxLength, maxLengthMessage).defined(), access_key_id: string7().max(maxLength, maxLengthMessage).required("Access Key ID is required."), access_key_secret: string7().max(maxLength, maxLengthMessage).required("Secret Access Key is required.") }); var akamaiObjectStorageDetailsPayloadSchema = akamaiObjectStorageDetailsBaseSchema.shape({ path: string7().max(maxLength, maxLengthMessage).optional() }); var destinationSchemaBase = object7().shape({ label: string7().max(maxLength, maxLengthMessage).required("Destination name is required."), type: string7().oneOf(["akamai_object_storage", "custom_https"]).required(), details: mixed3().defined().required().when("type", { is: "akamai_object_storage", then: () => akamaiObjectStorageDetailsBaseSchema, otherwise: () => customHTTPsDetailsSchema }) }); var destinationFormSchema = destinationSchemaBase; var createDestinationSchema = destinationSchemaBase.shape({ details: mixed3().defined().required().when("type", { is: "akamai_object_storage", then: () => akamaiObjectStorageDetailsPayloadSchema, otherwise: () => customHTTPsDetailsSchema }) }); var updateDestinationSchema = createDestinationSchema.omit(["type"]).shape({ details: lazy((value) => { if ("bucket_name" in value) { return akamaiObjectStorageDetailsPayloadSchema.noUnknown( "Object contains unknown fields for Akamai Object Storage Details." ); } if ("client_certificate_details" in value) { return customHTTPsDetailsSchema.noUnknown( "Object contains unknown fields for Custom HTTPS Details." ); } return mixed3().test({ name: "details-schema", message: "Details object does not match any known schema.", test: () => false }); }) }); var clusterRequiredMessage = "At least one cluster must be selected."; var streamDetailsBase = object7({ cluster_ids: array6().of(number6().defined(clusterRequiredMessage)).when("is_auto_add_all_clusters_enabled", { is: false, then: (schema) => schema.min(1, clusterRequiredMessage).required(clusterRequiredMessage) }), is_auto_add_all_clusters_enabled: boolean5() }); var streamDetailsSchema = streamDetailsBase.test( "cluster_ids-or-all_clusters_enabled", "Either cluster_ids or is_auto_add_all_clusters_enabled should be set.", (value) => { const HasClusterIds = "cluster_ids" in value; const HasAllClustersEnabled = "is_auto_add_all_clusters_enabled" in value; return HasClusterIds !== HasAllClustersEnabled; } ); var detailsShouldNotExistOrBeNull = (schema) => schema.nullable().test( "details-should-not-exist", "Details should be null or no details passed for type `audit_logs`", (value, ctx) => !("details" in ctx) || value === null ); var streamSchemaBase = object7({ label: string7().min(3, "Stream name must have at least 3 characters").max(maxLength, maxLengthMessage).required("Stream name is required."), status: mixed3().oneOf(["active", "inactive"]), type: string7().oneOf(["audit_logs", "lke_audit_logs"]).required("Stream type is required."), destinations: array6().of(number6().defined()).ensure().min(1).required(), details: mixed3().when("type", { is: "lke_audit_logs", then: () => streamDetailsSchema.required(), otherwise: detailsShouldNotExistOrBeNull }) }); var createStreamSchema = streamSchemaBase; var updateStreamSchema = streamSchemaBase.omit(["type"]).shape({ status: mixed3().oneOf(["active", "inactive"]).required(), details: lazy((value) => { if (value && typeof value === "object" && ("cluster_ids" in value || "is_auto_add_all_clusters_enabled" in value)) { return streamDetailsSchema.required(); } return detailsShouldNotExistOrBeNull(mixed3()); }) }).noUnknown("Object contains unknown fields"); var streamAndDestinationFormSchema = object7({ stream: streamSchemaBase.shape({ destinations: array6().of(number6().required()).required(), details: mixed3().when("type", { is: "lke_audit_logs", then: () => streamDetailsBase.required(), otherwise: (schema) => schema.nullable().equals([null], "Details must be null for audit_logs type") }) }), destination: destinationFormSchema.defined().when("stream.destinations", { is: (value) => !(value == null ? void 0 : value.length), then: (schema) => schema, otherwise: (schema) => schema.shape({ details: mixed3().strip() }) }) }); // src/domains.schema.ts import { array as array7, mixed as mixed4, number as number7, object as object8, string as string8 } from "yup"; var importZoneSchema = object8({ domain: string8().required("Domain is required."), remote_nameserver: string8().required("Remote nameserver is required.") }); var domainSchemaBase = object8().shape({ domain: string8().matches( // eslint-disable-next-line sonarjs/slow-regex /([a-zA-Z0-9-_]+\.)+([a-zA-Z]{2,3}\.)?([a-zA-Z]{2,16}|XN--[a-zA-Z0-9]+)/, "Domain is not valid." ), status: mixed4().oneOf(["disabled", "active", "edit_mode", "has_errors"]), tags: array7(), description: string8().min(1, "Description must be between 1 and 253 characters.").max(253, "Description must be between 1 and 253 characters."), retry_sec: number7(), master_ips: array7().of(string8()), axfr_ips: array7().of(string8()).typeError("Must be a comma-separated list of IP addresses."), expire_sec: number7(), refresh_sec: number7(), ttl_sec: number7() }); var createDomainSchema = domainSchemaBase.shape({ domain: string8().required("Domain is required.").matches( // eslint-disable-next-line sonarjs/slow-regex /([a-zA-Z0-9-_]+\.)+([a-zA-Z]{2,3}\.)?([a-zA-Z]{2,16}|XN--[a-zA-Z0-9]+)/, "Domain is not valid." ), tags: array7().of(string8()), type: mixed4().required().oneOf(["master", "slave"]), soa_email: string8().when("type", { is: "master", then: (schema) => schema.required("SOA Email is required.") }).email("SOA Email is not valid.").trim(), master_ips: array7().of(string8()).when("type", { is: "slave", then: (schema) => schema.compact().ensure().required("At least one primary IP address is required.").min(1, "At least one primary IP address is required.") }) }); var updateDomainSchema = domainSchemaBase.shape({ domainId: number7(), soa_email: string8().email("SOA Email is not valid."), axfr_ips: array7().of(string8()), tags: array7().of(string8()) }); // src/firewalls.schema.ts import ipaddr from "ipaddr.js"; import { array as array8, number as number8, object as object9, string as string9 } from "yup"; var IP_ERROR_MESSAGE = "Must be a valid IPv4 or IPv6 address or range."; var validateIP = (ipAddress2) => { if (ipAddress2 !== "" && !ipAddress2) { return false; } const [, mask] = ipAddress2.split("/"); try { if (mask) { ipaddr.parseCIDR(ipAddress2); } else { ipaddr.parse(ipAddress2); } } catch (err) { if (ipAddress2 !== "") { return false; } } return true; }; var CreateFirewallDeviceSchema = object9({ linodes: array8().of(number8()), nodebalancers: array8().of(number8()) }); var ipAddress = string9().defined().test({ name: "validateIP", message: IP_ERROR_MESSAGE, test: validateIP }); var CUSTOM_PORTS_ERROR_MESSAGE = ""; var validatePort = (port) => { CUSTOM_PORTS_ERROR_MESSAGE = "Ports must be an integer, range of integers, or a comma-separated list of integers."; if (!port) { CUSTOM_PORTS_ERROR_MESSAGE = "Must be 1-65535"; return false; } const convertedPort = parseInt(port, 10); if (!(1 <= convertedPort && convertedPort <= 65535)) { CUSTOM_PORTS_ERROR_MESSAGE = "Must be 1-65535"; return false; } if (port.startsWith("0")) { CUSTOM_PORTS_ERROR_MESSAGE = "Port must not have leading zeroes"; return false; } if (String(convertedPort) !== port) { return false; } return true; }; var isCustomPortsValid = (ports) => { const portList = (ports == null ? void 0 : ports.split(",")) || []; let portLimitCount = 0; for (const port of portList) { const cleanedPort = port.trim(); if (cleanedPort.includes("-")) { const portRange = cleanedPort.split("-"); if (!validatePort(portRange[0]) || !validatePort(portRange[1])) { return false; } if (portRange.length !== 2) { CUSTOM_PORTS_ERROR_MESSAGE = "Ranges must have 2 values"; return false; } if (parseInt(portRange[0], 10) >= parseInt(portRange[1], 10)) { CUSTOM_PORTS_ERROR_MESSAGE = "Range must start with a smaller number and end with a larger number"; return false; } portLimitCount += 2; } else { if (!validatePort(cleanedPort)) { return false; } portLimitCount++; } } if (portLimitCount > 15) { CUSTOM_PORTS_ERROR_MESSAGE = "Number of ports or port range endpoints exceeded. Max allowed is 15"; return false; } return true; }; var validateFirewallPorts = string9().test({ name: "firewall-ports", message: CUSTOM_PORTS_ERROR_MESSAGE, test: (value) => { if (!value) { return false; } try { isCustomPortsValid(value); } catch (err) { return false; } return true; } }); var FirewallRuleTypeSchema = object9().shape({ action: string9().oneOf(["ACCEPT", "DROP"]).nullable(), description: string9().nullable(), label: string9().nullable(), protocol: string9().oneOf(["ALL", "TCP", "UDP", "ICMP", "IPENCAP"]).nullable(), ports: string9().when("protocol", { is: (val) => val !== "ICMP" && val !== "IPENCAP", then: () => validateFirewallPorts, // Workaround to get the test to fail if ports is defined when protocol === ICMP or IPENCAP otherwise: (schema) => schema.test({ name: "protocol", message: "Ports are not allowed for ICMP and IPENCAP protocols.", test: (value) => typeof value === "undefined" }) }).nullable(), addresses: object9().shape({ ipv4: array8().of(ipAddress).nullable(), ipv6: array8().of(ipAddress).nullable() }).strict(true).notRequired().nullable(), ruleset: number8().nullable() }); var FirewallRuleSchema = object9().shape({ inbound: array8(FirewallRuleTypeSchema).nullable(), outbound: array8(FirewallRuleTypeSchema).nullable(), inbound_policy: string9().oneOf(["ACCEPT", "DROP"]).required("Inbound policy is required."), outbound_policy: string9().oneOf(["ACCEPT", "DROP"]).required("Outbound policy is required.") }); var CreateFirewallDevicesSchema = object9().shape({ linodes: array8().of(number8().defined()), nodebalancers: array8().of(number8().defined()), linode_interfaces: array8().of(number8().defined()) }).notRequired(); var CreateFirewallSchema = object9().shape({ label: string9().required("Label is required.").min(3, "Label must be between 3 and 32 characters.").max(32, "Label must be between 3 and 32 characters."), // Label validation on the back end is more complicated, we only do basic checks here. tags: array8().of(string9().defined()), rules: FirewallRuleSchema, devices: CreateFirewallDevicesSchema }); var UpdateFirewallSchema = object9().shape({ label: string9(), tags: array8().of(string9()), status: string9().oneOf(["enabled", "disabled"]) // 'deleted' is also a status but it's not settable }); var FirewallDeviceSchema = object9({ type: string9().oneOf(["linode", "nodebalancer", "linode_interface"]).required("Device type is required."), id: number8().required("ID is required.") }); var UpdateFirewallSettingsSchema = object9({ default_firewall_ids: object9({ interface_public: number8().nullable(), interface_vpc: number8().nullable(), linode: number8().nullable(), nodebalancer: number8().nullable() }) }); // src/images.schema.ts import { array as array9, boolean as boolean6, number as number9, object as object10, string as string10 } from "yup"; var labelSchema = string10().min(1, "Label must be between 1 and 50 characters.").max(50, "Label must be between 1 and 50 characters.").matches( /^[a-zA-Z0-9,.?\-_\s']+$/, "Image labels cannot contain special characters." ); var baseImageSchema = object10({ label: labelSchema.optional(), description: string10().optional().min(1).max(65e3), cloud_init: boolean6().optional(), tags: array9(string10().min(3).max(50).required()).max(500).optional() }); var createImageSchema = baseImageSchema.shape({ disk_id: number9().typeError("Disk is required.").required("Disk is required.") }); var uploadImageSchema = baseImageSchema.shape({ label: labelSchema.required("Label is required."), region: string10().required("Region is required.") }); var updateImageSchema = object10({ label: labelSchema.optional(), description: string10().optional().max(65e3, "Length must be 65000 characters or less."), tags: array9(string10().required()).optional() }); var updateImageRegionsSchema = object10({ regions: array9(string10()).required("Regions are required.").min(1, "Must specify at least one region.") }); var sharegroupImageSchema = object10({ id: string10().required("Image ID is required"), label: labelSchema.optional(), description: string10().optional() }); var addSharegroupImagesSchema = object10({ images: array9(sharegroupImageSchema).required("Images are required.") }); var updateSharegroupImageSchema = object10({ label: labelSchema.optional(), description: string10().optional() }); var createSharegroupSchema = object10({ label: labelSchema.required("Label is required."), description: string10().optional(), images: array9(sharegroupImageSchema).notRequired() }); var updateSharegroupSchema = object10({ label: labelSchema.optional(), description: string10().optional() }); var addSharegroupMemberSchema = object10({ token: string10().required("Token is required."), label: labelSchema.required("Label is required.") }); var updateSharegroupMemberSchema = object10({ label: labelSchema.required("Label is required.") }); var generateSharegroupTokenSchema = object10({ label: labelSchema.optional(), valid_for_sharegroup_uuid: boolean6().required( "Valid sharegroup UUID required." ) }); var updateSharegroupTokenSchema = object10({ label: labelSchema.required("Label is required.") }); // src/kubernetes.schema.ts import { array as array10, boolean as boolean7, number as number10, object as object11, string as string11 } from "yup"; var alphaNumericValidCharactersRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-._]*[a-zA-Z0-9])?$/; var kubernetesTaintSchema = object11({ key: string11().required("Key is required.").test( "valid-key", "Key must start with a letter or number and may contain letters, numbers, hyphens, dots, and underscores, up to 253 characters.", (value) => { return alphaNumericValidCharactersRegex.test(value) || dnsKeyRegex.test(value); } ).max(253, "Key must be between 1 and 253 characters.").min(1, "Key must be between 1 and 253 characters."), value: string11().matches( alphaNumericValidCharactersRegex, "Value must start with a letter or number and may contain letters, numbers, hyphens, dots, and underscores, up to 63 characters." ).max(63, "Value must be between 0 and 63 characters.").notOneOf( ["kubernetes.io", "linode.com"], 'Value cannot be "kubernetes.io" or "linode.com".' ).notRequired() }); var NodePoolDiskSchema = object11({ size: number10().required(), type: string11().oneOf(["raw", "ext4"]).required() }); var AutoscaleSettingsSchema = object11({ enabled: boolean7().required(), max: number10().required(), min: number10().required() }); var CreateNodePoolSchema = object11({ autoscaler: AutoscaleSettingsSchema.notRequired().default(void 0), type: string11().required("Type is required."), count: number10().required(), tags: array10(string11().defined()).notRequired(), disks: array10(NodePoolDiskSchema).notRequired(), update_strategy: string11().oneOf(["rolling_update", "on_recycle"]).notRequired(), k8_version: string11().notRequired(), firewall_id: number10().notRequired(), labels: object11().notRequired(), taints: array10(kubernetesTaintSchema).notRequired() }); var EditNodePoolSchema = object11({ type: string11(), count: number10(), update_strategy: string11(), k8_version: string11(), firewall_id: number10() }); var clusterLabelSchema = string11().required("Label is required.").matches( /^[a-zA-Z0-9-]+$/, "Cluster labels cannot contain special characters, spaces, or underscores." ).min(3, "Length must be between 3 and 32 characters.").max(32, "Length must be between 3 and 32 characters."); var ipv4Address = string11().defined().test({ name: "validateIP", message: "Must be a valid IPv4 address.", test: validateIP }); var ipv6Address = string11().defined().test({ name: "validateIP", message: "Must be a valid IPv6 address.", test: validateIP }); var controlPlaneACLOptionsSchema = object11({ enabled: boolean7(), "revision-id": string11(), addresses: object11({ ipv4: array10().of(ipv4Address).nullable(), ipv6: array10().of(ipv6Address).nullable() }).notRequired() }); var controlPlaneEnterpriseACLOptionsSchema = object11({ enabled: boolean7(), "revision-id": string11(), addresses: object11({ ipv4: array10().of(ipv4Address), ipv6: array10().of(ipv6Address) }).required() }); var createKubeClusterSchema = object11({ label: clusterLabelSchema, region: string11().required("Region is required."), k8s_version: string11().required("Kubernetes version is required."), node_pools: array10().of(CreateNodePoolSchema).min(1, "Please add at least one node pool.") }); var createKubeClusterWithRequiredACLSchema = object11({ control_plane: object11({ high_availability: boolean7(), acl: object11({ enabled: boolean7(), "revision-id": string11(), addresses: object11({ ipv4: array10().of(ipv4Address), ipv6: array10().of(ipv6Address) }) }) }).test( "validateIPForEnterprise", "At least one IP address or CIDR range is required for LKE Enterprise.", function(controlPlane) { const { ipv4, ipv6 } = controlPlane.acl.addresses; return ipv4 && ipv4.length > 0 || ipv6 && ipv6.length > 0; } ).required() }); var kubernetesControlPlaneACLPayloadSchema = object11({ acl: controlPlaneACLOptionsSchema }); var kubernetesEnterpriseControlPlaneACLPayloadSchema = object11({ acl: controlPlaneEnterpriseACLOptionsSchema.test( "validateIPForEnterprise", "At least one IP address or CIDR range is required for LKE Enterprise.", function(acl) { const { ipv4, ipv6 } = acl.addresses || {}; return ipv4 && ipv4.length > 0 && ipv4[0] !== "" || ipv6 && ipv6.length > 0 && ipv6[0] !== ""; } ) }); var dnsKeyRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-._/]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$/; var MAX_DNS_KEY_TOTAL_LENGTH = 128; var MAX_DNS_KEY_SUFFIX_LENGTH = 62; var MAX_SIMPLE_KEY_OR_VALUE_LENGTH = 63; var validateKubernetesLabel = (labels) => { if (!labels) { return false; } for (const [labelKey, labelValue] of Object.entries(labels)) { if (!labelKey || !labelValue) { return false; } if (labelKey.includes("kubernetes.io") || labelKey.includes("linode.com")) { return false; } if (labelKey.includes("/")) { const suffix = labelKey.split("/")[0]; if (!dnsKeyRegex.test(labelKey)) { return false; } if (labelKey.length > MAX_DNS_KEY_TOTAL_LENGTH || suffix.length > MAX_DNS_KEY_SUFFIX_LENGTH) { return false; } } else { if (labelKey.length > MAX_SIMPLE_KEY_OR_VALUE_LENGTH || !alphaNumericValidCharactersRegex.test(labelKey)) { return false; } } if (labelValue.length > MAX_SIMPLE_KEY_OR_VALUE_LENGTH || !alphaNumericValidCharactersRegex.test(labelValue)) { return false; } } return true; }; var kubernetesLabelSchema = object11().test({ name: "validateLabels", message: "Labels must be valid key-value pairs.", test: validateKubernetesLabel }); // src/linodes.schema.ts import ipaddr3 from "ipaddr.js"; import { array as array12, boolean as boolean8, lazy as lazy3, mixed as mixed5, number as number11, object as object13, string as string13 } from "yup"; // src/vpcs.schema.ts import ipaddr2 from "ipaddr.js"; import { array as array11, lazy as lazy2, object as object12, string as string12 } from "yup"; var LABEL_MESSAGE2 = "Label must be between 1 and 64 characters."; var LABEL_REQUIRED = "Label is required."; var LABEL_REQUIREMENTS = "Label must include only ASCII letters, numbers, and dashes."; var labelTestDetails = { testName: "no two dashes in a row", testMessage: "Label must not contain two dashes in a row." }; var IP_EITHER_BOTH_NOT_NEITHER = "A subnet must have either IPv4 or IPv6, or both, but not neither."; var TEMPORARY_IPV4_REQUIRED_MESSAGE = "A subnet must have an IPv4 range."; var determineIPType = (ip) => { try { let addr; const [, mask] = ip.split("/"); if (mask) { const parsed = ipaddr2.parseCIDR(ip); addr = parsed[0]; } else { addr = ipaddr2.parse(ip); } return addr.kind(); } catch (e) { return void 0; } }; var vpcsValidateIP = ({ value, shouldHaveIPMask, mustBeIPMask, checkIPv6PrefixLengthIs64 }) => { if (!value) { return false; } const [, mask] = value.trim().split("/"); if (mustBeIPMask) { const valueIsMaskOnly = value === `/${mask}`; return !mask ? false : ipaddr2.IPv6.subnetMaskFromPrefixLength(Number(mask)) !== null && valueIsMaskOnly && Number(mask) >= 64 && Number(mask) <= 125; } try { const type = determineIPType(value); const isIPv4 = type === "ipv4"; const isIPv6 = type === "ipv6"; if (!isIPv4 && !isIPv6) { return false; } if (isIPv4) { if (shouldHaveIPMask) { ipaddr2.IPv4.parseCIDR(value); } else { ipaddr2.IPv4.isValid(value); ipaddr2.IPv4.parse(value); } } if (isIPv6) { if (checkIPv6PrefixLengthIs64) { return mask === "64"; } if (shouldHaveIPMask) { ipaddr2.IPv6.parseCIDR(value); } else { ipaddr2.IPv6.isValid(value); ipaddr2.IPv6.parse(value); } } return true; } catch (err) { return false; } }; var labelValidation = string12().test( labelTestDetails.testName, labelTestDetails.testMessage, (value) => !(value == null ? void 0 : value.includes("--")) ).min(1, LABEL_MESSAGE2).max(64, LABEL_MESSAGE2).matches(/^[a-zA-Z0-9-]*$/, LABEL_REQUIREMENTS); var updateVPCSchema = object12({ label: labelValidation, description: string12() }); var VPCIPv6Schema = object12({ range: string12().optional().test( "IPv6 prefix length", "Must be the prefix length 52, 48, or 44 of the IP, e.g. /52", (value) => { if (value && value.length > 0) { return ["/44", "/48", "/52"].includes(value); } } ) }); var VPCIPv6SubnetSchema = object12({ range: string12().required().test( "IPv6 prefix length", "Must be the prefix length (52-62) of the IP, e.g. /52", (value) => { if (value && value !== "auto" && value.length > 0) { const [, mask] = value.split("/"); return +mask >= 52 && +mask <= 62; } } ) }); var createSubnetSchemaIPv4 = object12({ label: labelValidation.required(LABEL_REQUIRED), ipv4: string12().when("ipv6", { is: (value) => value === "" || value === null || value === void 0, then: (schema) => schema.required(TEMPORARY_IPV4_REQUIRED_MESSAGE).test({ name: "IPv4 CIDR format", message: "The IPv4 range must be in CIDR format.", test: (value) => vpcsValidateIP({ value, shouldHaveIPMask: true, mustBeIPMask: false }) }), otherwise: (schema) => lazy2((value) => { switch (typeof value) { case "string": return schema.notRequired().test({ name: "IPv4 CIDR format", message: "The IPv4 range must be in CIDR format.", test: (value2) => vpcsValidateIP({ value: value2, shouldHaveIPMask: true, mustBeIPMask: false }) }); case "undefined": return schema.notRequired().nullable(); default: return schema.notRequired().nullable(); } }) }) }); var createSubnetSchemaWithIPv