From 9bf722d7fd1c3cac1337e2d20c4e3ab572a1d722 Mon Sep 17 00:00:00 2001 From: Steffen Waldmann Date: Wed, 6 Dec 2023 07:45:47 +0100 Subject: [PATCH] Various improvements in Best Practices guide (#449) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Various improvements in Best Practices guide * Better wording for tip Co-authored-by: René Jeglinsky --------- Co-authored-by: René Jeglinsky --- node.js/best-practices.md | 76 ++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/node.js/best-practices.md b/node.js/best-practices.md index 56aa681e1..3a4f7cb90 100644 --- a/node.js/best-practices.md +++ b/node.js/best-practices.md @@ -26,9 +26,9 @@ From generic Node.js best practices like dependency management and error handlin Projects using CAP need to manage dependencies to the respective tools and libraries in their _package.json_ and/or _pom.xml_ respectively. Follow the guidelines to make sure that you consume the latest fixes and avoid vulnerabilities and version incompatibilities. These guidelines apply to you as a _consumer_ of reuse packages as well as a _provider_ of such reuse packages. -### Always Use the _Latest Minor_ Releases → for Example, `^5.5.0` {#use-caret } +### Always Use the _Latest Minor_ Releases → for Example, `^7.2.0` {#use-caret } -This applies to both, *@sap* packages as well as open source ones. It makes sure that your projects receive the latest features and important fixes during development. It also makes sure that bundles have a minimal footprint by leveraging [npm's dedupe](https://docs.npmjs.com/cli/dedupe.html) feature. +This applies to both, *@sap* packages as well as open source ones. It ensures your projects receive the latest features and important fixes during development. It also leverages [NPM's dedupe](https://docs.npmjs.com/cli/dedupe.html) to make sure bundles have a minimal footprint. Example: @@ -39,17 +39,19 @@ Example: "express": "^4.17.0" } ``` -::: tip -We **recommend** using the caret form, that is, `^1.0.2` to add your dependencies, which are also the default for `npm install`, as that clearly captures the minimum patch version. +::: tip We **recommend** using the caret form such as `^1.0.2` +Caret form is the default for `npm install`, as that format clearly captures the minimum patch version. ::: ### Keep Open Ranges When *Publishing* for Reuse {#publish } -Let's explain this by looking at counter examples. +Let's explain this by looking at two examples. + +#### Bad {.bad} -Let's assume that you've developed a reuse package that others can use in their projects, and you also use a reuse package. For whatever reason, you decided to violate the previous rules and use exact dependencies in your _package.json_ as follows: +Assume that you've developed a reuseable package, and consume a reuse package yourself. You decided to violate the previous rules and use exact dependencies in your _package.json_: ```json "name": "@sap/your-reuse-package", @@ -63,10 +65,12 @@ Let's assume that you've developed a reuse package that others can use in their The effect would be as follows: -1. Consuming projects would get duplicate versions of each package that they also use directly, for example, `@sap/cds`, `@sap/foundation`, and `express`. -2. Consuming projects wouldn't receive important fixes for the packages used in your implementations unless you also provide an update. +1. Consuming projects get duplicate versions of each package they also use directly, for example, `@sap/cds`, `@sap/foundation`, and `express`. +2. Consuming projects don't receive important fixes for the packages used in your _package.json_ unless you also provide an update. 3. It wouldn't be possible to reuse CDS models from common reuse packages (for example, would already fail for `@sap/cds/common`). +#### Good {.good} + Therefore, the rules when publishing packages for reuse are: * **Keep** the open ranges in your _package.json_ (just don't touch them). @@ -98,9 +102,7 @@ The _package-lock.json_ file in your project root freezes all dependencies and i This ensures that the deployed tool/service/app doesn't receive new vulnerabilities, for example, through updated open source packages, without you being able to apply the necessary tests as prescribed by our security standards. -:::tip - -We recommend running `npm update` regularly and frequently during development to ensure that you receive the latest fixes.
+::: tip Run `npm update` frequently to receive latest fixes regularly Tools like [renovate](https://github.com/renovatebot/renovate) or [GitHub's dependabot](https://docs.github.com/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically) can help you automate this process.
@@ -188,21 +190,21 @@ cds.on('bootstrap', app => { ) }) ``` -::: tip -Consult [OpenUI5 Content Security Policy documentation](https://openui5.hana.ondemand.com/topic/fe1a6dba940e479fb7c3bc753f92b28c) for the list of directives that SAPUI5 requires. -::: +Find required directives in the [OpenUI5 Content Security Policy documentation](https://openui5.hana.ondemand.com/topic/fe1a6dba940e479fb7c3bc753f92b28c) {.learn-more} ### Cross-Site Request Forgery (CSRF) Token -Protect against cross-side request forgery (CSRF) attacks by enabling CSRF token handling either through the _App Router_ or by adding it manually. -::: tip -For a SAPUI5 (SAP Fiori/SAP Fiori Elements) application developer, CSRF token handling is transparent. -There's no need to program or to configure anything in additional. In case the server rejects the request with _403_ and _“X-CSRF-Token: required”_, the UI sends a _HEAD_ request to the service document to fetch a new token. +Protect against cross-side request forgery (CSRF) attacks by enabling CSRF token handling through the _App Router_. +::: tip For a SAPUI5 (SAP Fiori/SAP Fiori Elements) developer, CSRF token handling is transparent +There's no need to program or to configure anything in addition. In case the server rejects the request with _403_ and _“X-CSRF-Token: required”_, the UI sends a _HEAD_ request to the service document to fetch a new token. ::: [Learn more about CSRF tokens and SAPUI5 in the **Cross-Site Scripting** documentation.](https://sapui5.hana.ondemand.com/#/topic/91f0bd316f4d1014b6dd926db0e91070){.learn-more} -::: warning _❗ The request must never be cacheable._ +Alternatively, you can add a CSRF token handler manually. + +::: warning This request must never be cacheable +If a CSRF token is cached, it can potentially be reused in multiple requests, defeating its purpose of securing each individual request. Always set appropriate cache-control headers to `no-store, no-cache, must-revalidate, proxy-revalidate` to prevent caching of the CSRF token. ::: #### Using App Router @@ -222,47 +224,47 @@ If you use SAP Fiori Elements, requests to the backend are sent as batch request As already mentioned, in case the server rejects because of a bad CSRF token, the response with a status _403_ and a header _“X-CSRF-Token: required”_ should be returned to the UI. For this purpose, the error handling in the following example is extended: ```js -cds.on('bootstrap', async app => { - var csrfProtection = csrf({ cookie: true }) - var parseForm = express.urlencoded({ extended: false }) +const csrfProtection = csrf({ cookie: true }) +const parseForm = express.urlencoded({ extended: false }) +cds.on('bootstrap', app => { app.use(cookieParser()) // Must: Provide actual s of served services. // Optional: Adapt for non-Fiori Elements UIs. - app.head('/', csrfProtection, function (req, res) { - res.set('X-CSRF-Token', req.csrfToken()) - res.send() + .head('/', csrfProtection, (req, res) => { + res.set({ + 'X-CSRF-Token': req.csrfToken(), + 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate' + }).send() }) // Must: Provide actual s of served services. // Optional: Adapt for non-Fiori Elements UIs. - app.post('//$batch', parseForm, csrfProtection, function (req, res, next) { - next() - }) + .post('//$batch', parseForm, csrfProtection, (req, res, next) => next()) - app.use(function (err, req, res, next) { + .use((err, req, res, next) => { if (err.code !== 'EBADCSRFTOKEN') return next(err) - - res.status(403) - res.set('X-CSRF-Token', 'required') - res.send() + res.status(403).set('X-CSRF-Token', 'required').send() }) }) ``` [Learn more about backend coding in the **csurf** documentation.](https://www.npmjs.com/package/csurf){.learn-more} -::: tip -If you're using horizontal scaling of Node.js virtual machines, the CSRF handling should be done at the approuter level. +::: tip Use _App Router_ CSRF handling when scaling Node.js VMs horizontally +Handling CSRF at the _App Router_ level ensures consistency across instances. This avoids potential token mismatches that could occur if each VM handled CSRF independently. ::: + ### Cross-Origin Resource Sharing (CORS) With _Cross-Origin Resource Sharing_ (CORS) the server that hosts the UI can tell the browser about servers it trusts to provide resources. In addition, so-called "preflight" requests tell the browser if the cross-origin server will process a request with a specific method and a specific origin. -::: tip -CORS should either be configured in the _App Router_ or in the backend, but not in both places. + +::: tip Avoid configuring CORS in both _App Router_ and backend +Configuring CORS in multiple places can lead to confusing debugging scenarios. Centralizing CORS settings in one location decreases complexity, and thus, improves security. ::: + #### Using App Router The _App Router_ has full support for CORS. Thus, by adding the _App Router_ as described in the [Deployment Guide: Using App Router as Gateway](../guides/deployment/to-cf#add-app-router), CORS can be configured in the _App Router_ configuration.