Repository: n8n-io/n8n
File: packages/cli/src/credentials/credentials.service.ts (TypeScript)

Code before:
					throw new BadRequestError(
						`The field "${property.name}" is mandatory for credentials of type "${type}"`,
					);
				}
			}
		}

		// TODO: add further validation if needed
	}

	/**
	 * Create a new managed credential in user's account and return it along the scopes.
	 * Managed credentials are managed by n8n and cannot be edited by the user.
	 */
	async createManagedCredential(dto: CreateCredentialDto, user: User) {
		return await this.createCredential({ ...dto, isManaged: true }, user);
	}

	private async createCredential(opts: CreateCredentialOptions, user: User) {
		this.checkCredentialData(opts.type, opts.data as ICredentialDataDecryptedObject);
		const encryptedCredential = this.createEncryptedData({
			id: null,
			name: opts.name,
			type: opts.type,
			data: opts.data as ICredentialDataDecryptedObject,
		});

		// Set isGlobal if provided in the payload and user has permission
		const isGlobal = opts.isGlobal;
		if (isGlobal === true) {
			const canShareGlobally = hasGlobalScope(user, 'credential:shareGlobally');
			if (!canShareGlobally) {
				throw new ForbiddenError(
					'You do not have permission to create globally shared credentials',
				);
			}
			encryptedCredential.isGlobal = isGlobal;
		}

		const credentialEntity = this.credentialsRepository.create({
			...encryptedCredential,
			isManaged: opts.isManaged,
			isResolvable: opts.isResolvable ?? false,
		});

		const { shared, ...credential } = await this.save(
			credentialEntity,
			encryptedCredential,
			user,
			opts.projectId,
		);

Review comment (security):
do we need to evaluate oauth1 and oauth2 separately?

afaik the function would also pass if we just iterated over const 
`oauthUrlFields = ['authUrl', 'accessTokenUrl',  'requestTokenUrl', 'serverUrl'] as const;`

regardless of oauth type since either requestTokenUrl or serverUrl would be undefined

Code after:
		for (const property of credentialProperties) {
			if (property.required && displayParameter(data, property, null, null)) {
				// Check if value is present in data, if not, check if default value exists
				const value = data[property.name];
				const hasDefault =
					property.default !== undefined && property.default !== null && property.default !== '';
				if ((value === undefined || value === null || value === '') && !hasDefault) {
					throw new BadRequestError(
						`The field "${property.name}" is mandatory for credentials of type "${type}"`,
					);
				}
			}
		}

		this.validateOAuthCredentialUrls(type, data);
	}

	/**
	 * Validates that OAuth credential URL fields (authUrl, accessTokenUrl, etc.) use http/https only.
	 * No-op if the credential type is not OAuth1 or OAuth2 (including extended types).
	 */
	private validateOAuthCredentialUrls(type: string, data: ICredentialDataDecryptedObject) {
		const parentTypes = this.credentialTypes.getParentTypes(type) ?? [];
		const isOAuth2 = type === 'oAuth2Api' || parentTypes.includes('oAuth2Api');
		const isOAuth1 = type === 'oAuth1Api' || parentTypes.includes('oAuth1Api');
		if (isOAuth2) {
			const oauthUrlFields = ['authUrl', 'accessTokenUrl', 'serverUrl'] as const;
			for (const field of oauthUrlFields) {
				const value = data[field];
				if (typeof value === 'string' && value.trim() !== '') {
					validateOAuthUrl(value);
				}
			}
		}
		if (isOAuth1) {
			const oauthUrlFields = ['authUrl', 'requestTokenUrl', 'accessTokenUrl'] as const;
			for (const field of oauthUrlFields) {
				const value = data[field];
				if (typeof value === 'string' && value.trim() !== '') {
					validateOAuthUrl(value);
				}
			}
		}
	}

	/**
	 * Create a new managed credential in user's account and return it along the scopes.
	 * Managed credentials are managed by n8n and cannot be edited by the user.
	 */
	async createManagedCredential(dto: CreateCredentialDto, user: User) {
		return await this.createCredential({ ...dto, isManaged: true }, user);