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
8 changes: 5 additions & 3 deletions libs/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -1149,13 +1149,15 @@
"Git repository": "Git repository",
"Target revision is required.": "Target revision is required.",
"Must be an absolute path.": "Must be an absolute path.",
"Repository type is required": "Repository type is required",
"Value must not exceed {{ maxSize }} MiB when encoded.": "Value must not exceed {{ maxSize }} MiB when encoded.",
"Invalid format. Only ASCII characters are allowed.": "Invalid format. Only ASCII characters are allowed.",
"Enter a valid registry hostname (e.g., quay.io, registry.redhat.io, myregistry.com:5000)": "Enter a valid registry hostname (e.g., quay.io, registry.redhat.io, myregistry.com:5000)",
"Registry hostname is required": "Registry hostname is required",
"Password is required": "Password is required",
"Repository type is required": "Repository type is required",
"Client TLS certificate is required": "Client TLS certificate is required",
"Client TLS key is required": "Client TLS key is required",
"Must be a valid JWT token": "Must be a valid JWT token",
"Enter a valid registry hostname (e.g., quay.io, registry.redhat.io, myregistry.com:5000)": "Enter a valid registry hostname (e.g., quay.io, registry.redhat.io, myregistry.com:5000)",
"Registry hostname is required": "Registry hostname is required",
"Enter a valid repository URL. Example: {{ demoRepositoryUrl }}": "Enter a valid repository URL. Example: {{ demoRepositoryUrl }}",
"Repository URL is required": "Repository URL is required",
"Enter a valid HTTP service URL. Example: https://my-service-url": "Enter a valid HTTP service URL. Example: https://my-service-url",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ const httpRepoUrlRegex = /^(http|https)/;
const pathRegex = /\/.+/;
const jwtTokenRegexp = /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$/;

const MAX_REPO_ENCODED_CERT_LENGTH = 20 * 1024 * 1024; // 20 MiB as per the backend
const MAX_REPO_ORIGINAL_CERT_LENGTH = Math.floor((MAX_REPO_ENCODED_CERT_LENGTH / 4) * 3); // 15 MiB

export const isHttpRepoSpec = (repoSpec: RepositorySpec): repoSpec is HttpRepoSpec =>
repoSpec.type === RepoSpecType.RepoSpecTypeHttp;
export const isGitRepoSpec = (repoSpec: RepositorySpec): repoSpec is GitRepoSpec =>
Expand Down Expand Up @@ -680,38 +683,36 @@ export const singleResourceSyncSchema = (t: TFunction, existingRSs: ResourceSync
});
};

const validRepositoryCertificate = (t: TFunction) => (value: string | undefined, testContext: Yup.TestContext) => {
if (!value || value.trim() === '') {
return true;
}
if (value.length > MAX_REPO_ORIGINAL_CERT_LENGTH) {
return testContext.createError({
message: t('Value must not exceed {{ maxSize }} MiB when encoded.', {
maxSize: MAX_REPO_ENCODED_CERT_LENGTH / 1024 / 1024,
}),
});
}
try {
btoa(value);
return true;
} catch {
return testContext.createError({
message: t('Invalid format. Only ASCII characters are allowed.'),
});
}
};

// Regex for registry hostname: FQDN, IP address (IPv4 or IPv6), with optional port, matching as much as possible of the backend pattern
const registryHostnameRegex =
/^(([a-z0-9]([-a-z0-9]*[a-z0-9])?\.)*[a-z]([-a-z0-9]*[a-z0-9])?|[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|\[[a-fA-F0-9:]+\])(:[0-9]{1,5})?$/;

export const repositorySchema =
(t: TFunction, repository: Repository | undefined) => (values: RepositoryFormValues) => {
const baseSchema = {
name: validKubernetesDnsSubdomain(t, { isRequired: !repository }),
configType: values.useAdvancedConfig ? Yup.string().required(t('Repository type is required')) : Yup.string(),
httpConfig: Yup.object({
basicAuth: Yup.object({
username: values.httpConfig?.basicAuth?.use ? Yup.string().required(t('Username is required')) : Yup.string(),
password: values.httpConfig?.basicAuth?.use ? Yup.string().required(t('Password is required')) : Yup.string(),
}),
mTlsAuth: Yup.object({
tlsCrt: values.httpConfig?.mTlsAuth?.use
? Yup.string().required(t('Client TLS certificate is required'))
: Yup.string(),
tlsKey: values.httpConfig?.mTlsAuth?.use
? Yup.string().required(t('Client TLS key is required'))
: Yup.string(),
}),
token: Yup.string().matches(jwtTokenRegexp, t('Must be a valid JWT token')),
}),
useResourceSyncs: Yup.boolean(),
resourceSyncs: values.useResourceSyncs ? repoSyncSchema(t, values.resourceSyncs) : Yup.array(),
};

if (values.repoType === RepoSpecType.RepoSpecTypeOci) {
return Yup.object({
...baseSchema,
url: Yup.string(),
name: validKubernetesDnsSubdomain(t, { isRequired: !repository }),
ociConfig: Yup.object({
registry: Yup.string()
.matches(
Expand All @@ -726,14 +727,43 @@ export const repositorySchema =
username: values.ociConfig?.ociAuth?.use ? Yup.string().required(t('Username is required')) : Yup.string(),
password: values.ociConfig?.ociAuth?.use ? Yup.string().required(t('Password is required')) : Yup.string(),
}),
caCrt: Yup.string(),
caCrt: Yup.string().test('valid-certificate', validRepositoryCertificate(t)),
skipServerVerification: Yup.boolean(),
}),
});
}

// Git or Http repositories
return Yup.object({
...baseSchema,
name: validKubernetesDnsSubdomain(t, { isRequired: !repository }),
configType: values.useAdvancedConfig ? Yup.string().required(t('Repository type is required')) : Yup.string(),
httpConfig: Yup.object({
basicAuth: Yup.object({
username: values.httpConfig?.basicAuth?.use ? Yup.string().required(t('Username is required')) : Yup.string(),
password: values.httpConfig?.basicAuth?.use ? Yup.string().required(t('Password is required')) : Yup.string(),
}),
caCrt: Yup.string().test('valid-certificate', validRepositoryCertificate(t)),
mTlsAuth: Yup.object({
tlsCrt: values.httpConfig?.mTlsAuth?.use
? Yup.string()
.required(t('Client TLS certificate is required'))
.test('valid-certificate', validRepositoryCertificate(t))
: Yup.string(),
tlsKey: values.httpConfig?.mTlsAuth?.use
? Yup.string()
.required(t('Client TLS key is required'))
.test('valid-certificate', validRepositoryCertificate(t))
: Yup.string(),
}),
token: Yup.string().matches(jwtTokenRegexp, t('Must be a valid JWT token')),
}),
sshConfig: Yup.object({
sshPrivateKey: Yup.string().test('valid-certificate', validRepositoryCertificate(t)),
privateKeyPassphrase: Yup.string(),
skipServerVerification: Yup.boolean(),
}),
useResourceSyncs: Yup.boolean(),
resourceSyncs: values.useResourceSyncs ? repoSyncSchema(t, values.resourceSyncs) : Yup.array(),
url: Yup.string().when('repoType', {
is: (repoType: RepoSpecType) => repoType === RepoSpecType.RepoSpecTypeGit,
then: () =>
Expand All @@ -750,7 +780,6 @@ export const repositorySchema =
.matches(httpRepoUrlRegex, t('Enter a valid HTTP service URL. Example: https://my-service-url'))
.defined(t('HTTP service URL is required')),
}),
ociConfig: Yup.object(),
});
};

Expand Down