Skip to content

Commit

Permalink
Various improvements in Best Practices guide (#449)
Browse files Browse the repository at this point in the history
* Various improvements in Best Practices guide

* Better wording for tip

Co-authored-by: René Jeglinsky <[email protected]>

---------

Co-authored-by: René Jeglinsky <[email protected]>
  • Loading branch information
swaldmann and renejeglinsky authored Dec 6, 2023
1 parent 0d62dff commit 9bf722d
Showing 1 changed file with 39 additions and 37 deletions.
76 changes: 39 additions & 37 deletions node.js/best-practices.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 &rarr; for Example, `^5.5.0` {#use-caret }
### Always Use the _Latest Minor_ Releases &rarr; 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:

Expand All @@ -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 }

<!-- TODO: revisit duplicated attribute { #reuse} -->

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",
Expand All @@ -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).
Expand Down Expand Up @@ -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.<br>
::: 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.

<div id="renovate-sap" />
Expand Down Expand Up @@ -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
Expand All @@ -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 <service endpoint>s of served services.
// Optional: Adapt for non-Fiori Elements UIs.
app.head('/<service endpoint>', csrfProtection, function (req, res) {
res.set('X-CSRF-Token', req.csrfToken())
res.send()
.head('/<service endpoint>', csrfProtection, (req, res) => {
res.set({
'X-CSRF-Token': req.csrfToken(),
'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate'
}).send()
})

// Must: Provide actual <service endpoint>s of served services.
// Optional: Adapt for non-Fiori Elements UIs.
app.post('/<service endpoint>/$batch', parseForm, csrfProtection, function (req, res, next) {
next()
})
.post('/<service endpoint>/$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.
Expand Down

0 comments on commit 9bf722d

Please sign in to comment.