Compare commits

...

57 Commits
v7.0.5 ... main

Author SHA1 Message Date
dependabot[bot]
88c618023a chore(deps): bump actions/checkout from 4.1.7 to 4.2.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.7 to 4.2.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.7...v4.2.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-26 07:39:48 +02:00
dependabot[bot]
490836962a chore(deps): bump path-to-regexp from 6.2.1 to 6.3.0
Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) from 6.2.1 to 6.3.0.
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v6.2.1...v6.3.0)

---
updated-dependencies:
- dependency-name: path-to-regexp
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-13 06:28:57 +02:00
Joachim Van Herwegen
868275789d fix: Correctly delete data in validate-configs.sh 2024-09-10 07:38:32 +02:00
Joachim Van Herwegen
221dcc41cd chore(release): Release version 7.1.2 of the npm package 2024-08-20 07:39:31 +02:00
elf Pavlik
3e8365bb26
fix: Use full encoded topic iri in streaming http receiveFrom url template
* fix: use full encoded topic iri in streaming http receiveFrom url template

* clean up urls and routing
2024-08-19 08:58:53 +02:00
Joachim Van Herwegen
4599bf413e chore(release): Release version 7.1.1 of the npm package 2024-08-07 09:08:58 +02:00
Joachim Van Herwegen
e15c59c157 chore: Increase jest timeout 2024-08-07 08:53:38 +02:00
Joachim Van Herwegen
b93aa31c93 chore: Use correct markdownlint-cli2 fix command 2024-08-07 08:42:32 +02:00
elf Pavlik
3dd8602acc fix: Ensure streaming HTTP streams the whole notification in a single chunk 2024-08-05 07:51:33 +02:00
Joachim Van Herwegen
576eefede6 docs: Add missing newline 2024-07-19 15:04:51 +02:00
Joachim Van Herwegen
9c44f375f2 docs: Update server architecture documentation 2024-07-19 15:04:50 +02:00
Joachim Van Herwegen
73619fda05 docs: Explain oidc.json 2024-07-19 15:04:50 +02:00
Joachim Van Herwegen
ab419674df docs: Explain WAC vs ACP 2024-07-19 15:04:50 +02:00
Joachim Van Herwegen
ed6f2ec8e9 docs: Explain the provided configs 2024-07-19 15:04:49 +02:00
Joachim Van Herwegen
3aa28fa03b docs: Add test instructions to documentation 2024-07-19 15:04:49 +02:00
Joachim Van Herwegen
e45bce89aa docs: Add more explicit installation instructions 2024-07-19 15:04:48 +02:00
Joachim Van Herwegen
6b8223ba9c chore: Update ts-node 2024-07-19 15:04:09 +02:00
Joachim Van Herwegen
46f5fc239e chore: Depend on external eslint package 2024-07-19 15:04:09 +02:00
Joachim Van Herwegen
ecd031e69f chore: Update lint dependencies 2024-07-19 15:04:09 +02:00
Joachim Van Herwegen
d1282f6b1a chore: Update eslint-plugin-jest dependency 2024-07-19 15:04:09 +02:00
Joachim Van Herwegen
86e8c09e2d chore: Update markdownlint-cli2 dependency 2024-07-19 15:04:09 +02:00
dependabot[bot]
5936aceccd chore(deps): bump ws from 8.14.2 to 8.17.1
Bumps [ws](https://github.com/websockets/ws) from 8.14.2 to 8.17.1.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.14.2...8.17.1)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-24 09:29:53 +02:00
dependabot[bot]
1f474a3c8e chore(deps): bump docker/build-push-action from 5 to 6
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5 to 6.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-24 09:22:52 +02:00
dependabot[bot]
a0ea743449 chore(deps-dev): bump braces from 3.0.2 to 3.0.3
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-13 07:51:02 +02:00
dependabot[bot]
a402aa6382 chore(deps): bump actions/checkout from 4.1.5 to 4.1.7
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.5 to 4.1.7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.5...v4.1.7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-13 07:36:00 +02:00
Joachim Van Herwegen
d350c140fd docs: Add missing index for starting the server 2024-06-11 08:58:00 +02:00
Joachim Van Herwegen
556899dbdb docs: Add HTTP streaming notification option to docs 2024-05-24 13:10:20 +02:00
Joachim Van Herwegen
2f10d22c18 chore(release): Release version 7.1.0 of the npm package 2024-05-24 08:41:54 +02:00
elf Pavlik
cb38613b4c
feat: Add support for StreamingHTTPChannel2023 notifications
* feat: initial StremingHTTPChannel2023 notifications

Co-authored-by: Maciej Samoraj <maciej.samoraj@gmail.com>

* test: unit for StremingHTTPChannel2023 notifications

Co-authored-by: Maciej Samoraj <maciej.samoraj@gmail.com>

* test: integration for StremingHTTPChannel2023 notifications

Co-authored-by: Maciej Samoraj <maciej.samoraj@gmail.com>

* emit initial notification on streaming http channel

* fix linting erros

* ensure canceling fetch body in integration tests

* extract defaultChannel for topic into util

* add documentation

* Apply suggestions from code review

Co-authored-by: Ted Thibodeau Jr <tthibodeau@openlinksw.com>

* only generate notifications when needed

Co-authored-by: Maciej Samoraj <maciej.samoraj@gmail.com>

* test: set body timeout to pass on node >21

Co-authored-by: Maciej Samoraj <maciej.samoraj@gmail.com>

* address review feedback

* remove node 21 workaround

* add architecture documentation

* Apply suggestions from code review

Co-authored-by: Joachim Van Herwegen <joachimvh@gmail.com>

---------

Co-authored-by: Maciej Samoraj <maciej.samoraj@gmail.com>
Co-authored-by: Ted Thibodeau Jr <tthibodeau@openlinksw.com>
Co-authored-by: Joachim Van Herwegen <joachimvh@gmail.com>
2024-05-22 08:58:26 +02:00
elf Pavlik
203f80020c
chore: update CI node versions
* test: update CI node versions

* Update npm-test.yml

* revert changes to quotes

* 22.0 broken on windows

https://github.com/nodejs/node/issues/52682
2024-05-15 14:20:58 +02:00
dependabot[bot]
f244b288bd chore(deps): bump actions/checkout from 4.1.1 to 4.1.5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.1.5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.1...v4.1.5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-14 14:06:20 +02:00
dependabot[bot]
386babff42 chore(deps): bump ejs from 3.1.9 to 3.1.10
Bumps [ejs](https://github.com/mde/ejs) from 3.1.9 to 3.1.10.
- [Release notes](https://github.com/mde/ejs/releases)
- [Commits](https://github.com/mde/ejs/compare/v3.1.9...v3.1.10)

---
updated-dependencies:
- dependency-name: ejs
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-14 14:05:58 +02:00
dependabot[bot]
f59b2c2970 chore(deps): bump peaceiris/actions-gh-pages from 3 to 4
Bumps [peaceiris/actions-gh-pages](https://github.com/peaceiris/actions-gh-pages) from 3 to 4.
- [Release notes](https://github.com/peaceiris/actions-gh-pages/releases)
- [Changelog](https://github.com/peaceiris/actions-gh-pages/blob/main/CHANGELOG.md)
- [Commits](https://github.com/peaceiris/actions-gh-pages/compare/v3...v4)

---
updated-dependencies:
- dependency-name: peaceiris/actions-gh-pages
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-24 08:24:55 +02:00
Noel De Martin
07499631b4
fix: Fix .nvmrc version 2024-04-23 08:03:14 +02:00
Joachim Van Herwegen
e20efac3ea fix: Combine metadata with data when generating resources 2024-04-22 09:48:24 +02:00
Joachim Van Herwegen
099897013c fix: Make getParentContainer work with query parameters 2024-04-22 09:13:22 +02:00
Joachim Van Herwegen
f73dfb31c0 fix: Do not reuse the same error in StaticThrowHandler 2024-04-09 08:29:56 +02:00
Joachim Van Herwegen
2846c711ab docs: Fix language
Co-authored-by: Ted Thibodeau Jr <tthibodeau@openlinksw.com>
2024-04-09 08:29:56 +02:00
Joachim Van Herwegen
5e60000681 fix: Make allow headers more accurate 2024-04-09 08:29:56 +02:00
Joachim Van Herwegen
d7078ad692 fix: Expose auxiliary links on errors 2024-04-09 08:29:56 +02:00
Joachim Van Herwegen
419312ee5f feat: Store original target in error metadata 2024-04-09 08:29:56 +02:00
Joachim Van Herwegen
486241f3d4 docs: Fix language
Co-authored-by: Ted Thibodeau Jr <tthibodeau@openlinksw.com>
2024-04-02 09:16:51 +02:00
Joachim Van Herwegen
cac70b1f88 refactor: Simplify eslint configs 2024-04-02 09:16:51 +02:00
Joachim Van Herwegen
7abca33b67 chore: Update @antfu/eslint-config dependency to 2.11.4 2024-04-02 09:16:51 +02:00
Joachim Van Herwegen
fa060b86f3 refactor: Remove eslint-disable when possible 2024-04-02 09:16:51 +02:00
Joachim Van Herwegen
65bf2bd34e refactor: Enable jsdoc/tag-lines and jsdoc/sort-tags rules 2024-04-02 09:16:51 +02:00
Joachim Van Herwegen
3e59aa4b55 refactor: Enable jsdoc/valid-types rule 2024-04-02 09:16:51 +02:00
Joachim Van Herwegen
5fc4ce8f73 refactor: Enable jsdoc/no-types rule 2024-04-02 09:16:51 +02:00
Joachim Van Herwegen
45640a36d6 refactor: Enable import/no-extraneous-dependencies rule 2024-04-02 09:16:51 +02:00
Joachim Van Herwegen
b381a9c926 refactor: Enable ts/no-explicit-any rule 2024-04-02 09:16:51 +02:00
Joachim Van Herwegen
331f83d659 refactor: Enable style/indent-binary-ops rule 2024-04-02 09:16:51 +02:00
Joachim Van Herwegen
73fbe80cff refactor: Enable global-require rule 2024-04-02 09:16:51 +02:00
Joachim Van Herwegen
c96b60d4d3 refactor: Enable callback-return rule 2024-04-02 09:16:51 +02:00
Joachim Van Herwegen
c65096020e refactor: Enable prefer-global rules 2024-04-02 09:16:51 +02:00
Joachim Van Herwegen
c24e6d5a18 refactor: Enable consistent-this rule 2024-04-02 09:16:51 +02:00
Joachim Van Herwegen
5c1553bdda refactor: Enable no-unnecessary-type-arguments rule 2024-04-02 09:16:51 +02:00
Joachim Van Herwegen
28af181eee refactor: Make no-extra-parens rule stricter 2024-04-02 09:16:51 +02:00
241 changed files with 6833 additions and 3455 deletions

View File

@ -42,7 +42,7 @@ jobs:
with:
node-version: 16.x
- name: Check out the project
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4.2.0
with:
ref: ${{ inputs.branch || github.ref }}
- name: Install dependencies and run build scripts

View File

@ -21,7 +21,7 @@ jobs:
tags: ${{ steps.meta-main.outputs.tags || steps.meta-version.outputs.tags }}
steps:
- name: Checkout
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4.2.0
- if: startsWith(github.ref, 'refs/tags/v') || (github.ref == 'refs/heads/main')
name: Docker meta edge and version tag
id: meta-main
@ -55,7 +55,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4.2.0
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
@ -66,7 +66,7 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and export to docker
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
context: .
load: true
@ -85,7 +85,7 @@ jobs:
done <<< "${{ needs.docker-meta.outputs.tags }}";
- name: Build and push
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
context: .
push: true

View File

@ -21,7 +21,7 @@ jobs:
outputs:
major: ${{ steps.tagged_version.outputs.major || steps.current_version.outputs.major }}
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.0
- uses: actions/setup-node@v4
with:
node-version: 16.x
@ -43,7 +43,7 @@ jobs:
runs-on: ubuntu-latest
needs: mkdocs-prep
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.0
- uses: actions/setup-python@v5
with:
python-version: 3.x
@ -62,7 +62,7 @@ jobs:
needs: [mkdocs-prep, mkdocs]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.0
- uses: actions/setup-node@v4
with:
node-version: 16.x
@ -70,7 +70,7 @@ jobs:
- name: Generate typedocs
run: npm run typedocs
- name: Deploy typedocs
uses: peaceiris/actions-gh-pages@v3
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs

View File

@ -7,10 +7,10 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.0
- uses: actions/setup-node@v4
with:
node-version: 16.x
node-version: 20.x
- run: npm ci --ignore-scripts
- run: npm run lint
@ -27,7 +27,8 @@ jobs:
- 18.x
- '20.0'
- 20.x
- 21.x
- '22.1'
- 22.x
timeout-minutes: 15
steps:
- name: Use Node.js ${{ matrix.node-version }}
@ -37,7 +38,7 @@ jobs:
- name: Ensure line endings are consistent
run: git config --global core.autocrlf input
- name: Check out repository
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4.2.0
- name: Install dependencies and run build scripts
run: npm ci
- name: Type-check tests
@ -59,7 +60,7 @@ jobs:
node-version:
- 18.x
- 20.x
- 21.x
- 22.x
env:
TEST_DOCKER: true
services:
@ -80,7 +81,7 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- name: Check out repository
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4.2.0
- name: Install dependencies and run build scripts
run: npm ci
- name: Run integration tests
@ -94,7 +95,7 @@ jobs:
node-version:
- 18.x
- 20.x
- 21.x
- 22.x
timeout-minutes: 20
steps:
- name: Use Node.js ${{ matrix.node-version }}
@ -104,7 +105,7 @@ jobs:
- name: Ensure line endings are consistent
run: git config --global core.autocrlf input
- name: Check out repository
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4.2.0
- name: Install dependencies and run build scripts
run: npm ci
- name: Run integration tests
@ -124,9 +125,9 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 16.x
node-version: 20.x
- name: Check out repository
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4.2.0
- name: Install dependencies and run build scripts
run: npm ci
- name: Run deploy tests

View File

@ -22,7 +22,7 @@ module.exports = {
// Allow multiple subheadings with the same content
// across different section (#1 ##A ##B #2 ##A ##B)
MD024: {
allow_different_nesting: true,
siblings_only: true,
},
// Set Ordered list item prefix to "ordered" (use 1. 2. 3. not 1. 1. 1.)

2
.nvmrc
View File

@ -1 +1 @@
16
18

View File

@ -3,6 +3,55 @@
All notable changes to this project will be documented in this file.
## [7.1.2](https://github.com/CommunitySolidServer/CommunitySolidServer/compare/v7.1.1...v7.1.2) (2024-08-20)
### Fixes
* Use full encoded topic iri in streaming http receiveFrom url template ([3e8365b](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/3e8365bb2613737fb28c376b5967a351a1300432))
## [7.1.1](https://github.com/CommunitySolidServer/CommunitySolidServer/compare/v7.1.0...v7.1.1) (2024-08-07)
### Fixes
* Ensure streaming HTTP streams the whole notification in a single chunk ([3dd8602](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/3dd8602acce892b36d1ecaf584c938032e754213))
### Chores
* Increase jest timeout ([e15c59c](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/e15c59c157882181340fa87a7116b5b34252a79b))
* Use correct markdownlint-cli2 fix command ([b93aa31](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/b93aa31c932935c21f1e3666fdab3d0947a645eb))
* Depend on external eslint package ([46f5fc2](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/46f5fc239efa794f5309834fa818d17c96f83bd1))
### Documentation
* Update server architecture documentation ([9c44f37](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/9c44f375f2537fa0277a6c6831c63c1c1cfc5373))
* Explain oidc.json ([73619fd](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/73619fda056d5a9b0b0fac271f29fbced0424169))
* Explain WAC vs ACP ([ab41967](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/ab419674df5a92054128588747c3abc06086c3ab))
* Explain the provided configs ([ed6f2ec](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/ed6f2ec8e953e84efa6701482d00f616cf6ecbc2))
* Add test instructions to documentation ([3aa28fa](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/3aa28fa03b4d1324998e7f6a5ebe5788d0e6b2c9))
* Add more explicit installation instructions ([e45bce8](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/e45bce89aabc95b34ecbefcf46f899a88e60cfef))
* Add missing index for starting the server ([d350c14](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/d350c140fd184d33cbaf6880b9d4b1476d1ffb7c))
* Add HTTP streaming notification option to docs ([556899d](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/556899dbdbf3bb285de71225d156c4891dce23a9))
## [7.1.0](https://github.com/CommunitySolidServer/CommunitySolidServer/compare/v7.0.5...v7.1.0) (2024-05-24)
### Features
* Add support for StreamingHTTPChannel2023 notifications ([cb38613](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/cb38613b4cea7f4e808b30a69f1d9aecbb9506e2))
* Store original target in error metadata ([419312e](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/419312ee5f4790881a5d101afea7ab6ca88f5e61))
### Fixes
* Fix .nvmrc version ([0749963](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/07499631b44154fd24d3fd8fd704df34dfca0d0a))
* Combine metadata with data when generating resources ([e20efac](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/e20efac3eaa79b2ed8b09cd72a7f8f0d85655894))
* Make `getParentContainer` work with query parameters ([0998970](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/099897013c4ea014212495965d4972e5078ed406))
* Do not reuse the same error in StaticThrowHandler ([f73dfb3](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/f73dfb31c0fe132524323acf6c4f4636bcd8bc80))
* Make allow headers more accurate ([5e60000](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/5e600006819ae1cf1f8edf804218aee700c59bae))
* Expose auxiliary links on errors ([d7078ad](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/d7078ad69261566c44e38d1bb19142fb8bd4dd0f))
### Refactors
* Simplify eslint configs ([cac70b1](https://github.com/CommunitySolidServer/CommunitySolidServer/commit/cac70b1f88dcbbb3ebbe0b8e0b082ead4ab27b33))
## [7.0.5](https://github.com/CommunitySolidServer/CommunitySolidServer/compare/v7.0.4...v7.0.5) (2024-03-25)
### Fixes

View File

@ -31,7 +31,9 @@ And, of course, for many others who like to experience Solid.
## ⚡ Running the Community Solid Server
Use [Node.js](https://nodejs.org/en/) 18.0 or up and execute:
Make sure you have [Node.js](https://nodejs.org/en/) 18.0 or higher.
If this is your first time using Node.js,
you can find instructions on how to do this [here](https://nodejs.org/en/download/package-manager).
```shell
npx @solid/community-server
@ -45,7 +47,9 @@ To persist your pod's contents between restarts, use:
npx @solid/community-server -c @css:config/file.json -f data/
```
Find more ways to run the server in the [documentation](https://communitysolidserver.github.io/CommunitySolidServer/latest/usage/starting-server/).
In case you prefer to use Docker instead,
you can find instructions for this and other methods in the
[documentation](https://communitysolidserver.github.io/CommunitySolidServer/latest/usage/starting-server/).
## 🔧 Configure your server

View File

@ -1,67 +1,129 @@
# Configuration
This folder contains several configurations that can be used to start up the server.
These can be used directly, or used as inspiration on how you would want to configure your server.
All those configurations are created in the same way:
features are enabled or disabled by choosing a specific option for every component.
All components are represented by the subfolders found in the folders here:
`ldp` contains all LDP related components,
`identity` all IDP components, etc.
Options are then chosen by importing 1 entry from every component subfolder.
In case none of the available options have the exact feature configuration you want,
it is always possible to not choose any of them and create your own custom version instead.
More information on how this can be done manually,
can be found in this [tutorial](https://github.com/CommunitySolidServer/tutorials/blob/main/custom-configurations.md).
## How to use
As manually changing server options can be cumbersome,
there is also an online [configuration generator](https://communitysolidserver.github.io/configuration-generator/).
The easiest way to create a new config is by creating a JSON-LD file
that imports one option from every component subfolder
(such as either `allow-all.json` or `webacl.json` from `ldp/authorization`).
In case none of the available options suffice, there are 2 other ways to handle this:
Below we give an overview of the main identifying features of the configurations.
We start with all features of the default configuration,
after which we will explain in which features the other ones differ from it.
### Append to an existing config
## default.json
In case the options mostly suffice, but they just need to do a bit more,
it might be possible to append to one of the solutions.
This is the configuration that is used if no configuration is provided when starting the server.
It stores all data in memory, so this server is perfect for quickly trying some things out,
but not if you want a persistent server.
For example, in case all the existing metadata parsers can remain,
but an additional one needs to be added,
you could import `ldp/metadata-parser/default.json`
and then add the following in your root config:
For authorization, it uses [Web Access Control (WAC)](https://solid.github.io/web-access-control-spec/),
it supports all [notification methods](https://solidproject.org/TR/notifications-protocol) implemented in CSS,
allows users to create accounts, pods, WebIDs, and use them for [Solid-OIDC](https://solid.github.io/solid-oidc/).
```json
{
"@id": "urn:solid-server:default:MetadataParser",
"@type": "ParallelHandler",
"handlers": [
{ "@type": "MyNewParser" }
]
}
```
It is also initialized with an `index.html` page at root level,
with permissions set in such a way that everyone has full access to the server.
This will add the new parser to the list of metadata parsers.
The `@id` value is needed so Components.js knows which object to add the values to,
and the `@type` is needed so it can interpret the other fields (`handlers` in this case).
Although strictly not allowed by the Solid specification,
this configuration allows users to both write data at root level of the server,
and also create pods in subcontainers.
In all other configurations only or the other (or neither) will be allowed,
but here both are enabled for maximum flexibility when testing things out.
Note that generally it is only advised to append to ParallelHandlers or key/value maps.
In case the order is important this can not be guaranteed over separate files.
## file.json
### Create a new option
The most important difference with the `default.json` configuration is that this one stores its data as files on disk,
thereby making the data persistent.
Besides that, it also prevents data from being written to the root,
the only way to add data is to create a pod and add data there.
To still show something at root level when the server is started,
a static page is shown which can not be modified using standard Solid requests.
If a more drastic change is required,
the solution is to not import anything from that folder but instead write your own.
## file-acp.json
For example, in case you only want the slug parser but not any of the others,
you would have to not import anything from `ldp/metadata-parser` folder,
but instead have the following in your root config:
The only difference with `file.json`is that this uses
[Access Control Policy (ACP)](https://solid.github.io/authorization-panel/acp-specification/)
for authorization instead of WAC.
```json
{
"@id": "urn:solid-server:default:MetadataParser",
"@type": "ParallelHandler",
"handlers": [
{ "@type": "SlugParser" }
]
}
```
## file-root.json
Don't forget that in some cases you would also have to copy some imports!
The existing options can be used as inspiration.
This configuration starts from `file.json`, but does not allow the creation of accounts.
Instead, it allows data to be written directly to the root of the server.
To make sure users can write data there after starting the server,
permissions have been set to grant everyone full access,
so this needs to be changed after starting the server.
## file-root-pod.json
The same idea as `file-root.json`,
but here it is done by creating an account with a pod
in the root of the server the first time it is started.
The credentials to this account are stored in the configuration so should be changed afterwards.
This has the advantage of both having your data at root level,
but also allowing you to authenticate using Solid-OIDC.
## https-file-cli.json
A variant of `file.json` that uses HTTPS of HTTP.
The required key and cert file paths need to be defined using two new CLI options: `--httpsKey` and `-httpCert`.
## example-https-file.json
Another way to define HTTPS, but this time through the configuration file itself instead of the CLI.
As can be seen in the configuration itself, two paths are defined, pointing to the key and cert files.
To actually use this solution, you need to update the paths in that file before running the server.
## sparql-endpoint.json
Sets up a server that uses a SPARQL endpoint to store the data.
Only RDF data can be stored on a server using this configuration.
For internal data, such as accounts, temporary OIDC resources, etc,
the servers uses non-RDF data formats.
While other configurations store this kind of data in the same backend as the Solid data,
this is not feasible when using a SPARQL endpoint.
For this reason, this configuration stores all that data in memory,
meaning this solution should not be used if you want persistent accounts.
## sparql-endpoint-root.json
This differs from `sparql-endpoint.json` in the same way as `file-root.json` differs from `file.json`.
## sparql-file-storage.json
Similar to `sparql-endpoint.json` with the main difference being
that here internal data is stored on disk instead of in memory.
## memory-subdomains.json
A memory-based server whose main differentiating feature is how pod URLs are constructed.
In most other configurations, pods are created by appending the chosen name to the base URL of the server,
so for a server running at `http://example.com/`,
choosing the name `test` for your pod would result in `http://example.com/test/`.
With this configuration, the name is used as a subdomain of the url instead,
so the above values would result in a pod at `http://test.example.com/` instead.
## quota-file.json
A file-based server that limits the amount of data a user can put in a pod.
The values in the configuration determine the limit.
## path-routing.json
This configuration serves as an example of how a server can be configured
to serve data from different backends depending on the URL that is used.
In this example, all data in the `/sparql/` container will be stored in a SPARQL backend,
and similarly for `/memory/` and `/file/`.
## oidc.json
A configuration that sets up the server to only function as an Identity Provider.
It does not support creating pods or storing data on the server,
the only available options are creating accounts and linking them to WebIDs.
This way the server can be used to identify those WebIDs during an OIDC interaction.

View File

@ -22,12 +22,14 @@ Determines how notifications should be sent out from the server when resources c
* *all*: Supports all available notification types of the Solid Notifications protocol
[specification](https://solidproject.org/TR/notifications-protocol).
Currently, this includes WebhookChannel2023 and WebSocketChannel2023.
Currently, this includes WebhookChannel2023, WebSocketChannel2023 and StreamingHTTPChannel2023.
* *disabled*: No notifications are sent out.
* *legacy-websocket*: Follows the legacy Solid WebSocket
[specification](https://github.com/solid/solid-spec/blob/master/api-websockets.md).
Will be removed in future versions.
* *new-old-websockets.json*: Support for both the legacy Solid Websockets and the new WebSocketChannel2023.
* *streaming-http*: Follows the StreamingHTTPChannel2023
[specification](https://solid.github.io/notifications/streaming-http-channel-2023) draft.
* *webhooks*: Follows the WebhookChannel2023
[specification](https://solid.github.io/notifications/webhook-channel-2023) draft.
* *websockets*: Follows the WebSocketChannel2023

View File

@ -6,6 +6,7 @@
"css:config/http/notifications/base/http.json",
"css:config/http/notifications/base/listener.json",
"css:config/http/notifications/base/storage.json",
"css:config/http/notifications/streaming-http/http.json",
"css:config/http/notifications/websockets/handler.json",
"css:config/http/notifications/websockets/http.json",
"css:config/http/notifications/websockets/subscription.json",

View File

@ -0,0 +1,15 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld",
"import": [
"css:config/http/notifications/base/description.json",
"css:config/http/notifications/base/handler.json",
"css:config/http/notifications/base/http.json",
"css:config/http/notifications/base/storage.json",
"css:config/http/notifications/streaming-http/http.json"
],
"@graph": [
{
"comment": "All the relevant components are made in the specific imports seen above."
}
]
}

View File

@ -0,0 +1,87 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld",
"@graph": [
{
"@id": "urn:solid-server:default:StreamingHTTP2023Route",
"@type": "RelativePathInteractionRoute",
"base": { "@id": "urn:solid-server:default:NotificationRoute" },
"relativePath": "/StreamingHTTPChannel2023/"
},
{
"comment": "Creates updatesViaStreamingHttp2023 Link relations",
"@id": "urn:solid-server:default:StreamingHttpMetadataWriter",
"@type": "StreamingHttpMetadataWriter",
"route": { "@id": "urn:solid-server:default:StreamingHTTP2023Route" }
},
{
"comment": "Allows discovery of the corresponding streaming HTTP channel",
"@id": "urn:solid-server:default:MetadataWriter",
"@type": "ParallelHandler",
"handlers": [
{ "@id": "urn:solid-server:default:StreamingHttpMetadataWriter" }
]
},
{
"comment": "Handles the request targeting a StreamingHTTPChannel2023 receiveFrom endpoint.",
"@id": "urn:solid-server:default:StreamingHttp2023Router",
"@type": "OperationRouterHandler",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
"allowedMethods": [ "GET" ],
"allowedPathNames": [ "/StreamingHTTPChannel2023/" ],
"handler": {
"@id": "urn:solid-server:default:StreamingHttp2023RequestHandler",
"@type": "StreamingHttpRequestHandler",
"streamMap": { "@id": "urn:solid-server:default:StreamingHttpMap" },
"route": { "@id": "urn:solid-server:default:StreamingHTTP2023Route" },
"generator": { "@id": "urn:solid-server:default:BaseNotificationGenerator" },
"serializer": { "@id": "urn:solid-server:default:BaseNotificationSerializer" },
"credentialsExtractor": { "@id": "urn:solid-server:default:CredentialsExtractor" },
"permissionReader": { "@id": "urn:solid-server:default:PermissionReader" },
"authorizer": { "@id": "urn:solid-server:default:Authorizer" }
}
},
{
"comment": "Add the router to notification type handler",
"@id": "urn:solid-server:default:NotificationTypeHandler",
"@type": "WaterfallHandler",
"handlers": [
{ "@id": "urn:solid-server:default:StreamingHttp2023Router" }
]
},
{
"comment": "Opened response streams will be stored in this Map.",
"@id": "urn:solid-server:default:StreamingHttpMap",
"@type": "StreamingHttpMap"
},
{
"comment": "Emits serialized notifications through Streaming HTTP.",
"@id": "urn:solid-server:default:StreamingHttp2023Emitter",
"@type": "StreamingHttp2023Emitter",
"streamMap": { "@id": "urn:solid-server:default:StreamingHttpMap" }
},
{
"comment": "Listens to the activities emitted by the MonitoringStore.",
"@id": "urn:solid-server:default:StreamingHttpListeningActivityHandler",
"@type": "StreamingHttpListeningActivityHandler",
"emitter": { "@id": "urn:solid-server:default:ResourceStore" },
"streamMap": { "@id": "urn:solid-server:default:StreamingHttpMap" },
"source": {
"comment": "Handles the generation and serialization of notifications for StreamingHTTPChannel2023",
"@id": "urn:solid-server:default:StreamingHttpNotificationHandler",
"@type": "ComposedNotificationHandler",
"generator": { "@id": "urn:solid-server:default:BaseNotificationGenerator" },
"serializer": { "@id": "urn:solid-server:default:BaseNotificationSerializer" },
"emitter": { "@id": "urn:solid-server:default:StreamingHttp2023Emitter" },
"eTagHandler": { "@id": "urn:solid-server:default:ETagHandler" }
}
},
{
"comment": "Add the activity handler to the primary initializer",
"@id": "urn:solid-server:default:PrimaryParallelInitializer",
"@type": "ParallelHandler",
"handlers": [
{ "@id": "urn:solid-server:default:StreamingHttpListeningActivityHandler" }
]
}
]
}

View File

@ -39,9 +39,16 @@
},
"enabledJWA": {
"dPoPSigningAlgValues": [
"RS256", "RS384", "RS512",
"PS256", "PS384", "PS512",
"ES256", "ES256K", "ES384", "ES512",
"RS256",
"RS384",
"RS512",
"PS256",
"PS384",
"PS512",
"ES256",
"ES256K",
"ES384",
"ES512",
"EdDSA"
]
},

View File

@ -7,20 +7,26 @@
"@type": "SafeErrorHandler",
"showStackTrace": { "@id": "urn:solid-server:default:variable:showStackTrace" },
"errorHandler": {
"@type": "WaterfallHandler",
"handlers": [
{
"comment": "Internally redirects are created by throwing a specific error, this handler converts them to the correct response.",
"@type": "RedirectingErrorHandler"
},
{
"comment": "Converts an Error object into a representation for an HTTP response.",
"@type": "ConvertingErrorHandler",
"converter": { "@id": "urn:solid-server:default:UiEnabledConverter" },
"preferenceParser": { "@id": "urn:solid-server:default:PreferenceParser" },
"showStackTrace": { "@id": "urn:solid-server:default:variable:showStackTrace" }
}
]
"@id": "urn:solid-server:default:TargetExtractorErrorHandler",
"@type": "TargetExtractorErrorHandler",
"targetExtractor": { "@id": "urn:solid-server:default:TargetExtractor" },
"errorHandler": {
"@id": "urn:solid-server:default:WaterfallErrorHandler",
"@type": "WaterfallHandler",
"handlers": [
{
"comment": "Redirects are created internally by throwing a specific error; this handler converts them to the correct response.",
"@type": "RedirectingErrorHandler"
},
{
"comment": "Converts an Error object into a representation for an HTTP response.",
"@type": "ConvertingErrorHandler",
"converter": { "@id": "urn:solid-server:default:UiEnabledConverter" },
"preferenceParser": { "@id": "urn:solid-server:default:PreferenceParser" },
"showStackTrace": { "@id": "urn:solid-server:default:variable:showStackTrace" }
}
]
}
}
}
]

View File

@ -38,6 +38,7 @@ the [changelog](https://github.com/CommunitySolidServer/CommunitySolidServer/blo
* [How to automatically seed pods on startup](usage/seeding-pods.md)
* [Receiving notifications when resources change](usage/notifications.md)
* [Using the CSS as a development server in another project](usage/dev-configuration.md)
* [Which authorization method to pick](usage/authorization-methods.md)
## What the internals look like

View File

@ -17,7 +17,7 @@ flowchart LR
ControlHandler --password--> PasswordControlHandler("<strong>PasswordControlHandler</strong><br>ControlHandler")
ControlHandler --"oidc"--> OidcControlHandler("<strong>OidcControlHandler</strong><br>OidcControlHandler")
ControlHandler --html--> HtmlControlHandler("<strong>HtmlControlHandler</strong><br>ControlHandler")
HtmlControlHandler --main--> MainHtmlControlHandler("<strong>MainHtmlControlHandler</strong><br>ControlHandler")
HtmlControlHandler --account--> AccountHtmlControlHandler("<strong>AccountHtmlControlHandler</strong><br>ControlHandler")
HtmlControlHandler --password--> PasswordHtmlControlHandler("<strong>PasswordHtmlControlHandler</strong><br>ControlHandler")

View File

@ -10,7 +10,7 @@ flowchart LR
Handler("<strong>IdentityProviderHandler</strong><br>RouterHandler")
ParsingHandler("<strong>IdentityProviderParsingHandler</strong><br>AuthorizingHttpHandler")
AuthorizingHandler("<strong>IdentityProviderAuthorizingHandler</strong><br>AuthorizingHttpHandler")
Handler --> ParsingHandler
ParsingHandler --> AuthorizingHandler
AuthorizingHandler --> HttpHandler("<strong>IdentityProviderHttpHandler</strong><br>IdentityProviderHttpHandler")
@ -26,12 +26,12 @@ flowchart TD
HttpHandler("<strong>IdentityProviderHttpHandler</strong><br>IdentityProviderHttpHandler")
HttpHandler --> InteractionHandler("<strong>InteractionHandler</strong><br>WaterfallHandler")
InteractionHandler --> InteractionHandlerArgs
subgraph InteractionHandlerArgs[" "]
HtmlViewHandler("<strong>HtmlViewHandler</strong><br>HtmlViewHandler")
LockingInteractionHandler("<strong>LockingInteractionHandler</strong><br>LockingInteractionHandler")
end
LockingInteractionHandler --> JsonConversionHandler("<strong>JsonConversionHandler</strong><br>JsonConversionHandler")
JsonConversionHandler --> VersionHandler("<strong>VersionHandler</strong><br>VersionHandler")
VersionHandler --> CookieInteractionHandler("<strong>CookieInteractionHandler</strong><br>CookieInteractionHandler")

View File

@ -108,32 +108,33 @@ to add any custom initializers that need to run.
The `ServerInitializer` is the initializer that finally starts up the server by listening to the relevant port,
once all the initialization described above is finished.
It takes as input 2 components: a `HttpServerFactory` and a `ServerListener`.
To do this it makes use of an `HttpServerFactory`.
```mermaid
flowchart TD
ServerInitializer("<strong>ServerInitializer</strong><br>ServerInitializer")
ServerInitializer --> ServerInitializerArgs
subgraph ServerInitializerArgs[" "]
ServerInitializer --> ServerFactory("<strong>ServerFactory</strong><br>BaseServerFactory")
ServerFactory --> ServerConfigurator("<strong>ServerConfigurator</strong><br>ParallelHandler")
ServerConfigurator --> ServerConfiguratorArgs
subgraph ServerConfiguratorArgs[" "]
direction LR
ServerFactory("<strong>ServerFactory</strong><br>BaseServerFactory")
ServerListener("<strong>ServerListener</strong><br>ParallelHandler")
HandlerServerConfigurator("<strong>HandlerServerConfigurator</strong><br>HandlerServerConfigurator")
WebSocketServerConfigurator("<strong>WebSocketServerConfigurator</strong><br>WebSocketServerConfigurator")
end
ServerListener --> HandlerServerListener("<strong>HandlerServerListener</strong><br>HandlerServerListener")
HandlerServerListener --> HttpHandler("<strong>HttpHandler</strong><br><i>HttpHandler</i>")
HandlerServerConfigurator --> HttpHandler("<strong>HttpHandler</strong><br><i>HttpHandler</i>")
WebSocketServerConfigurator --> WebSocketHandler("<strong>WebSocketHandler</strong><br><i>WebSocketHandler</i>")
```
The `HttpServerFactory` is responsible for starting a server on a given port.
Depending on the configuration this can be an HTTP or an HTTPS server.
The created server emits events when it receives requests.
A `ServerListener` is a class that takes the created server as input and attaches a listener to interpret events.
One listener that is always used is the `urn:solid-server:default:HandlerServerListener`,
which calls an `HttpHandler` [to resolve HTTP requests](http-handler.md).
Sometimes it is necessary to add additional listeners,
these can then be added to the `urn:solid-server:default:ServerListener` as it is a `ParallellHandler`.
An example of this is when WebSockets are used [to handle notifications](notifications.md).
Any requests it receives, it sends to its `ServerConfigurator`,
which handles the request as needed.
This is a `ParallelHandler`, supporting two kinds of requests:
HTTP requests go through a configurator that sends those
to an `HttpHandler` [to resolve HTTP requests](http-handler.md).
In case WebSockets are enabled [to handle notifications](notifications.md),
these are handled by the `WebSocketHandler`.

View File

@ -78,7 +78,7 @@ flowchart TB
ResourceStore("<strong>ResourceStore</strong><br><i>ActivityEmitter</i>")
NotificationHandler("<strong>NotificationHandler</strong><br>WaterfallHandler")
end
NotificationHandler --> NotificationHandlerArgs
subgraph NotificationHandlerArgs[" "]
direction TB
@ -154,9 +154,9 @@ flowchart TB
NotificationChannelStorage("<strong>NotificationChannelStorage</strong><br>NotificationChannelStorage")
SequenceHandler("<br>SequenceHandler")
end
SequenceHandler --> SequenceHandlerArgs
subgraph SequenceHandlerArgs[" "]
direction TB
WebSocket2023Storer("<strong>WebSocket2023Storer</strong><br>WebSocket2023Storer")
@ -184,3 +184,45 @@ are quite similar to those needed for WebSocketChannel2023:
* The `WebhookChannel2023Type` class contains all the necessary typing information.
* `WebhookEmitter` is the `NotificationEmitter` that sends the request.
* `WebhookUnsubscriber` and `WebhookWebId` are additional utility classes to support the spec requirements.
## StreamingHTTPChannel2023
Currently, support for [StreamingHTTPChannel2023](https://solid.github.io/notifications/streaming-http-channel-2023)
only covers default, pre-established channels made available for every resource. Those channels output `text/turtle`.
Support for custom, subscription-based channels can be added in the future.
* For discovery, there is a `StreamingHttpMetadataWriter`, which adds `Link` to every `HTTP` response header
using `rel="http://www.w3.org/ns/solid/terms#updatesViaStreamingHttp2023"`. It links directly to the `receiveFrom`
endpoint of the default, pre-established channel for that topic resource.
* Requests to `receiveFrom` endpoints are handled by a `StreamingHttpRequestHandler`.
* It performs an authorization check.
* It creates a new response stream and adds it to the `StreamingHttpMap`, indexed by the topic resource.
* It sends an initial notification, similar to notification channels using a `state` feature.
* `StreamingHttp2023Emitter` is the `NotificationEmitter` that writes notifications to matching response streams.
* `StreamingHttpListeningActivityHandler` is responsible for observing the `MonitoringStore`
and emitting notifications when needed.
It doesn't use a `NotificationChannelStorage` since the default, pre-established channels are not
subscription-based. Instead, it uses a `StreamingHttpMap` to check for active receivers.
```mermaid
flowchart TB
StreamingHttpListeningActivityHandler("<strong>StreamingHttpListeningActivityHandler</strong><br>StreamingHttpListeningActivityHandler")
StreamingHttpListeningActivityHandler --> StreamingHttpListeningActivityHandlerArgs
subgraph StreamingHttpListeningActivityHandlerArgs[" "]
StreamingHttpMap("<strong>StreamingHttpMap</strong><br><i>StreamingHttpMap</i>")
ResourceStore("<strong>ResourceStore</strong><br><i>ActivityEmitter</i>")
StreamingHttpNotificationHandler("<strong>StreamingHttpNotificationHandler</strong><br><i>ComposedNotificationHandler</i>")
end
StreamingHttpNotificationHandler --> StreamingHttpNotificationHandlerArgs
subgraph StreamingHttpNotificationHandlerArgs[" "]
direction TB
Generator("<strong>BaseNotificationGenerator</strong>")
Serializer("<strong>BaseNotificationSerializer</strong>")
Emitter("<strong>StreamingHttp2023Emitter</strong><br><i>StreamingHttp2023Emitter</i>")
ETagHandler("<strong>ETagHandler</strong>")
end
```

View File

@ -27,15 +27,15 @@ flowchart LR
RdfPatcher("<strong>RdfPatcher</strong><br>RdfPatcher")
RdfPatcher --> RDFStore("<strong>PatchHandler_RDFStore</strong><br>WaterfallHandler")
RDFStore --> RDFStoreArgs
subgraph RDFStoreArgs[" "]
Immutable("<strong>PatchHandler_ImmutableMetadata</strong><br>ImmutableMetadataPatcher")
RDF("<strong>PatchHandler_RDF</strong><br>WaterfallHandler")
Immutable --> RDF
end
RDF --> RDFArgs
subgraph RDFArgs[" "]
direction LR
N3("<br>N3Patcher")

View File

@ -0,0 +1,76 @@
# Testing the server
There are several test sets in place to ensure the server conforms to the necessary requirements,
and to prevent changes from breaking this.
## Unit tests
For every TypeScript file,
most of which correspond to a single class implementation,
there is a corresponding unit test file in the `test/unit` folder.
These tests require 100% code coverage over the corresponding implementation,
making sure every line is checked.
These tests can be run using the `npm run test:unit` script.
## Integration tests
The `test/integration` folder contains several test suites that set up a complete server instance
and validate its functionality.
`test/intergration/config` contains the configurations used by these test suites.
These make sure that no features get lost after changes are made to the server.
These tests can be run using the `npm run test:integration` script.
## Specification conformance
To make sure the server conforms to the Solid specification,
we run the [Conformance Test Harness (CTH)](https://github.com/solid-contrib/conformance-test-harness)
combined with the [specification test suite](https://github.com/solid-contrib/specification-tests/).
This test suite was made specifically so any Solid server can be tested
on how well it conforms to the Solid specifications.
The configuration that runs these tests in the repository can be found [here](https://github.com/CommunitySolidServer/CommunitySolidServer/blob/main/.github/workflows/cth-test.yml).
You can also run this test suite locally.
Besides the standard requirements for running the server,
this also requires Docker.
First make sure you have a running CSS instance,
in this example we will assume it is running at `http://localhost:3000`.
After that you can run the following commands.
The paths are relative to the root folder of your CSS source folder,
and should be adjusted accordingly if you are not running this from the source folder.
```bash
# Generate the folder where the reports will be located
mkdir -p ../conformance/reports/css
# Pull the CTH Docker image
docker pull solidproject/conformance-test-harness
# Set up the env file necessary for the CTH
echo 'SOLID_IDENTITY_PROVIDER=http://localhost:3000/idp/
USERS_ALICE_WEBID=http://localhost:3000/alice/profile/card#me
USERS_BOB_WEBID=http://localhost:3000/bob/profile/card#me
RESOURCE_SERVER_ROOT=http://localhost:3000
TEST_CONTAINER=/alice/
quarkus.log.category."ResultLogger".level=INFO
quarkus.log.category."com.intuit.karate".level=DEBUG
quarkus.log.category."org.solid.testharness.http.Client".level=DEBUG
quarkus.log.category."org.solid.testharness.http.AuthManager".level=DEBUG
MAXTHREADS=1' > ../conformance/conformance.env
# Generate the test users required by the CTH on the server to be tested
npx ts-node test/deploy/createAccountCredentials.ts http://localhost:3000/ >> ../conformance/conformance.env
# Run the CTH
docker run -i --rm \
-v $(pwd)/../conformance/reports/css:/reports \
--env-file=../conformance/conformance.env \
--network="host" \
solidproject/conformance-test-harness \
--skip-teardown \
--output=/reports \
--target=https://github.com/solid/conformance-test-harness/css
```
When this process is finished you can find the conformance report in the `../reports/css` folder.

View File

@ -0,0 +1,36 @@
# Choosing the authorization method for your server
The CSS comes with support for two different authorization solutions:
[Web Access Control (WAC)](https://solidproject.org/TR/wac)
and [Access Control Policy (ACP)](https://solid.github.io/authorization-panel/acp-specification/).
When configuring a server, one of these needs to be picked if you do not want everyone to have full access to your data.
Both of these are similar in that they both make use of RDF resources to describe who can access which documents,
WAC is the older specification of the two,
it was designed together with the beginning of the Solid specification.
Because of that, there is more tooling available that can interpret the corresponding authorization resources,
potentially making it easier to get started with Solid development.
ACP is a more recent specification,
that was made to address certain concerns within WAC.
ACP provides more options in how to define who gets to access your data,
allowing you to have better security.
When using WAC, you define which WebIDs have access to certain data.
When you then authenticate with a Solid client,
that client will identify with your WebID,
indicating to the server that it is allowed to access that data.
The problem is that there is no (safe) way to differentiate between clients.
This means that if you use a client to store your favorite movies in your pod,
and another one to store your bank details,
the movie client would be able to access your bank details if it was malicious.
ACP on the other hand allows you to set more specific restrictions,
where clients also have to identify themselves.
This way you can make sure the movie client can only access movie data.
Currently, the CSS still enables WAC in most of the configurations bundled with the server,
as we want the server to be easily accessible for newer users,
for whom the chances are higher they are using apps only compatible with WAC.
However, we are planning to eventually phase this out in favor of ACP,
starting with logged warnings when WAC is enabled,
and in the end changing the bundled configurations to use ACP instead.

View File

@ -56,7 +56,7 @@ The next step generates the token and assumes you have an authorization value as
```ts
// Now that we are logged in, we need to request the updated controls from the server.
// These will now have more values than in the previous example.
const indexResponse = await fetch('http://localhost:3000/.account/', {
const indexResponse = await fetch('http://localhost:3000/.account/', {
headers: { authorization: `CSS-Account-Token ${authorization}` }
});
const { controls } = await indexResponse.json();

View File

@ -127,6 +127,31 @@ The response would then be something like this:
}
```
### Streaming HTTP
Currently, Streaming HTTP channels are only available as pre-established channels on each resource.
This means that subscribing and unsubscribing are not supported, and no subscription services are advertised.
Instead, each resource advertises the `receiveFrom` of its pre-established notification channel using HTTP Link header,
using `rel="http://www.w3.org/ns/solid/terms#updatesViaStreamingHttp2023"`.
For example, this —
```shell
curl --head 'http://localhost:3000/foo/'
```
```http
HTTP/1.1 200 OK
Link: <http://localhost:3000/.notifications/StreamingHTTPChannel2023/foo/>; rel="http://www.w3.org/ns/solid/terms#updatesViaStreamingHttp2023"
```
It is essential to remember that any HTTP request to that `receiveFrom` endpoint requires the same authorization
as a `GET` request on the resource which advertises it.
Currently, all pre-established Streaming HTTP channels have `Content-Type: text/turtle`.
Information on how to consume Streaming HTTP responses [is available on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams#consuming_a_fetch_as_a_stream)
## Unsubscribing from a notification channel
!!! note

View File

@ -78,8 +78,10 @@ nav:
- Welcome:
- README.md
- Features:
- features.md
- Overview: features.md
- Tests: features/test.md
- Usage:
- Starting the server: usage/starting-server.md
- Example request: usage/example-requests.md
- Metadata: usage/metadata.md
- Identity provider:
@ -91,6 +93,7 @@ nav:
- Seeding pods: usage/seeding-pods.md
- Notifications: usage/notifications.md
- Development server: usage/dev-configuration.md
- Authorization methods: usage/authorization-methods.md
- Architecture:
- Overview: architecture/overview.md
- Dependency injection: architecture/dependency-injection.md

View File

@ -1,45 +1,11 @@
const antfu = require('@antfu/eslint-config');
const generalConfig = require('./eslint/general');
const testConfig = require('./eslint/test');
const typedConfig = require('./eslint/typed');
const unicornConfig = require('./eslint/unicorn');
const opinionated = require('opinionated-eslint-config');
// The default ignore list contains all `output` folders, which conflicts with our src/http/output folder
// See https://github.com/antfu/eslint-config/blob/7071af7024335aad319a91db41ce594ebc6a0899/src/globs.ts#L55
const index = antfu.GLOB_EXCLUDE.indexOf('**/output');
if (index < 0) {
throw new Error('Could not update GLOB_EXCLUDE. Check if antfu changed how it handles ignores.');
}
antfu.GLOB_EXCLUDE.splice(index, 1);
module.exports = antfu.default(
module.exports = opinionated(
{
// Don't want to lint test assets, or TS snippets in markdown files
ignores: [ 'test/assets/*', '**/*.md/**/*.ts' ],
},
generalConfig,
unicornConfig,
typedConfig({
project: [ './tsconfig.json', './scripts/tsconfig.json', './test/tsconfig.json' ],
tsconfigRootDir: __dirname,
}),
testConfig,
{
// JSON rules
files: [ '**/*.json' ],
rules: {
'jsonc/array-bracket-spacing': [ 'error', 'always', {
singleValue: true,
objectsInArrays: false,
arraysInArrays: false,
}],
},
},
{
// This is necessary to prevent filename checks caused by JSON being present in a README.
files: [ '**/README.md/**' ],
rules: {
'unicorn/filename-case': 'off',
ignores: [ 'test/assets/*', '**/*.md' ],
typescript: {
tsconfigPath: [ './tsconfig.json', './scripts/tsconfig.json', './test/tsconfig.json' ],
},
},
);

View File

@ -1,114 +0,0 @@
module.exports = {
rules: {
'antfu/consistent-list-newline': 'error',
'arrow-body-style': [ 'error', 'as-needed', { requireReturnForObjectLiteral: false }],
'capitalized-comments': [ 'error', 'always', { ignoreConsecutiveComments: true }],
curly: [ 'error', 'all' ],
'default-case': 'error',
eqeqeq: [ 'error', 'always' ],
'for-direction': 'error',
'func-style': [ 'error', 'declaration' ],
'function-call-argument-newline': [ 'error', 'consistent' ],
'function-paren-newline': [ 'error', 'consistent' ],
'getter-return': [ 'error', { allowImplicit: true }],
'grouped-accessor-pairs': [ 'error', 'getBeforeSet' ],
'guard-for-in': 'error',
'line-comment-position': [ 'error', { position: 'above' }],
'linebreak-style': [ 'error', 'unix' ],
'multiline-comment-style': [ 'error', 'separate-lines' ],
// Need to override `allow` value
'no-console': [ 'error', { allow: [ '' ]}],
'no-constructor-return': 'error',
'no-dupe-else-if': 'error',
'no-else-return': [ 'error', { allowElseIf: false }],
'no-implicit-coercion': 'error',
'no-implicit-globals': 'error',
'no-lonely-if': 'error',
'no-plusplus': [ 'error', { allowForLoopAfterthoughts: true }],
'no-sync': [ 'error', { allowAtRootLevel: false }],
'no-useless-concat': 'error',
'no-useless-escape': 'error',
'operator-assignment': [ 'error', 'always' ],
'prefer-object-spread': 'error',
radix: 'error',
'require-unicode-regexp': 'error',
'require-yield': 'error',
'sort-imports': [
'error',
{
allowSeparatedGroups: false,
ignoreCase: true,
ignoreDeclarationSort: true,
ignoreMemberSort: false,
memberSyntaxSortOrder: [ 'none', 'all', 'multiple', 'single' ],
},
],
'import/extensions': 'error',
'jsdoc/no-multi-asterisks': [ 'error', { allowWhitespace: true }],
'node/prefer-global/buffer': 'off',
'node/prefer-global/process': 'off',
'style/array-bracket-spacing': [ 'error', 'always', {
singleValue: true,
objectsInArrays: false,
arraysInArrays: false,
}],
// Conflicts with style/object-curly-spacing
'style/block-spacing': 'off',
'style/brace-style': [ 'error', '1tbs', { allowSingleLine: false }],
'style/generator-star-spacing': [ 'error', { before: false, after: true }],
// Seems to be inconsistent in when it adds indentation and when it does not
'style/indent-binary-ops': 'off',
'style/member-delimiter-style': [ 'error', {
multiline: { delimiter: 'semi', requireLast: true },
singleline: { delimiter: 'semi', requireLast: false },
}],
'style/no-extra-parens': [ 'error', 'functions' ],
'style/object-curly-spacing': [ 'error', 'always', {
objectsInObjects: false,
arraysInObjects: false,
}],
'style/operator-linebreak': [ 'error', 'after' ],
'style/semi': [ 'error', 'always' ],
'style/semi-style': [ 'error', 'last' ],
'style/space-before-function-paren': [ 'error', 'never' ],
'style/switch-colon-spacing': 'error',
'style/quote-props': [ 'error', 'as-needed', {
keywords: false,
unnecessary: true,
numbers: false,
}],
'style/yield-star-spacing': [ 'error', 'after' ],
'ts/adjacent-overload-signatures': 'error',
'ts/array-type': 'error',
'ts/ban-ts-comment': [ 'error', {
'ts-expect-error': true,
}],
'ts/consistent-indexed-object-style': [ 'error', 'record' ],
'ts/consistent-type-definitions': 'off',
'ts/explicit-member-accessibility': 'error',
'ts/method-signature-style': 'error',
'ts/no-confusing-non-null-assertion': 'error',
'ts/no-extraneous-class': [ 'error', {
allowConstructorOnly: false,
allowEmpty: false,
allowStaticOnly: false,
}],
'ts/no-inferrable-types': [ 'error', {
ignoreParameters: false,
ignoreProperties: false,
}],
'ts/prefer-for-of': 'error',
'ts/prefer-function-type': 'error',
'unused-imports/no-unused-vars': [
'error',
{ args: 'after-used', vars: 'all', ignoreRestSiblings: true },
],
},
};

View File

@ -1,45 +0,0 @@
const jest = require('eslint-plugin-jest');
// Specifically for tests
module.exports = {
// See https://github.com/jest-community/eslint-plugin-jest/issues/1408
plugins: {
jest,
},
files: [ 'test/**/*.ts' ],
rules: {
...jest.configs.all.rules,
// Rule is not smart enough to check called function in the test
'jest/expect-expect': 'off',
'jest/valid-title': [ 'error', {
mustNotMatch: {
describe: /\.$/u.source,
},
mustMatch: {
it: /\.$/u.source,
},
}],
// Default rules that are overkill
'jest/no-hooks': 'off',
'jest/max-expects': 'off',
'jest/no-conditional-in-test': 'off',
'jest/prefer-expect-assertions': 'off',
'jest/prefer-lowercase-title': 'off',
'jest/prefer-strict-equal': 'off',
'jest/require-hook': 'off',
'test/prefer-lowercase-title': 'off',
'ts/naming-convention': 'off',
'ts/no-unsafe-argument': 'off',
'ts/no-unsafe-assignment': 'off',
'ts/no-unsafe-call': 'off',
'ts/no-unsafe-member-access': 'off',
'ts/no-unsafe-return': 'off',
'ts/unbound-method': 'off',
// Incorrectly detects usage of undefined in "toHaveBeenLastCalledWith" checks
'unicorn/no-useless-undefined': 'off',
},
};

View File

@ -1,105 +0,0 @@
// Copied from https://github.com/antfu/eslint-config/blob/main/src/configs/typescript.ts
// Doing it like this, so we can make sure these only try to trigger on *.ts files,
// Preventing issues with the *.js files.
const typeAwareRules = {
'dot-notation': 'off',
'no-implied-eval': 'off',
'no-throw-literal': 'off',
'ts/await-thenable': 'error',
'ts/dot-notation': [ 'error', { allowKeywords: true }],
'ts/no-floating-promises': 'error',
'ts/no-for-in-array': 'error',
'ts/no-implied-eval': 'error',
'ts/no-misused-promises': 'error',
'ts/no-throw-literal': 'error',
'ts/no-unnecessary-type-assertion': 'error',
'ts/no-unsafe-argument': 'error',
'ts/no-unsafe-assignment': 'error',
'ts/no-unsafe-call': 'error',
'ts/no-unsafe-member-access': 'error',
'ts/no-unsafe-return': 'error',
'ts/restrict-plus-operands': 'error',
'ts/restrict-template-expressions': 'error',
'ts/unbound-method': 'error',
};
const defaults = {
project: [ './tsconfig.json' ],
files: [ '**/*.ts' ],
tsconfigRootDir: process.cwd(),
};
module.exports = function(options) {
options = { ...defaults, ...options };
return {
// By default, antfu also triggers type rules on *.js files which causes all kinds of issues for us
files: options.files,
languageOptions: {
parserOptions: {
tsconfigRootDir: options.tsconfigRootDir,
project: options.project,
},
},
rules: {
...typeAwareRules,
'ts/consistent-type-assertions': [ 'error', {
assertionStyle: 'as',
}],
'ts/naming-convention': [
'error',
{
selector: 'default',
format: [ 'camelCase' ],
leadingUnderscore: 'forbid',
trailingUnderscore: 'forbid',
},
{
selector: 'import',
format: null,
},
{
selector: 'variable',
format: [ 'camelCase', 'UPPER_CASE' ],
leadingUnderscore: 'forbid',
trailingUnderscore: 'forbid',
},
{
selector: 'typeLike',
format: [ 'PascalCase' ],
},
{
selector: [ 'typeParameter' ],
format: [ 'PascalCase' ],
prefix: [ 'T' ],
},
],
'ts/explicit-function-return-type': [ 'error', {
allowExpressions: false,
allowTypedFunctionExpressions: false,
allowHigherOrderFunctions: false,
}],
'ts/no-base-to-string': 'error',
'ts/no-floating-promises': [ 'error', { ignoreVoid: false }],
'ts/promise-function-async': 'error',
'ts/no-unnecessary-boolean-literal-compare': 'error',
'ts/no-unnecessary-qualifier': 'error',
'ts/prefer-nullish-coalescing': 'error',
'ts/prefer-readonly': 'error',
'ts/prefer-reduce-type-parameter': 'error',
'ts/prefer-regexp-exec': 'error',
'ts/prefer-string-starts-ends-with': 'error',
'ts/require-array-sort-compare': 'error',
// These are not type specific, but we only care about these in TS files
'max-len': [ 'error', { code: 120, ignoreUrls: true }],
'unicorn/filename-case': [ 'error', {
cases: {
camelCase: true,
pascalCase: true,
kebabCase: false,
snakeCase: false,
},
}],
},
};
};

View File

@ -1,60 +0,0 @@
module.exports = {
rules: {
'unicorn/better-regex': 'error',
'unicorn/empty-brace-spaces': 'error',
'unicorn/consistent-function-scoping': 'error',
'unicorn/expiring-todo-comments': [ 'error', {
ignoreDatesOnPullRequests: false,
terms: [ 'todo' ],
allowWarningComments: false,
}],
'unicorn/explicit-length-check': 'error',
'unicorn/filename-case': [ 'error', {
cases: {
camelCase: false,
pascalCase: false,
kebabCase: true,
snakeCase: false,
},
}],
'unicorn/new-for-builtins': 'error',
'unicorn/no-array-for-each': 'error',
'unicorn/no-array-reduce': 'error',
'unicorn/no-for-loop': 'error',
'unicorn/no-invalid-remove-event-listener': 'error',
'unicorn/no-lonely-if': 'error',
'unicorn/no-negated-condition': 'error',
'unicorn/no-nested-ternary': 'error',
'unicorn/no-object-as-default-parameter': 'error',
'unicorn/no-process-exit': 'error',
'unicorn/no-thenable': 'error',
'unicorn/no-useless-fallback-in-spread': 'error',
'unicorn/no-useless-length-check': 'error',
'unicorn/no-useless-promise-resolve-reject': 'error',
'unicorn/no-useless-spread': 'error',
'unicorn/no-useless-undefined': 'error',
'unicorn/no-zero-fractions': 'error',
'unicorn/prefer-array-find': 'error',
'unicorn/prefer-array-flat-map': 'error',
'unicorn/prefer-array-index-of': 'error',
'unicorn/prefer-array-some': 'error',
'unicorn/prefer-at': 'error',
'unicorn/prefer-code-point': 'error',
'unicorn/prefer-date-now': 'error',
'unicorn/prefer-default-parameters': 'error',
'unicorn/prefer-math-trunc': 'error',
'unicorn/prefer-native-coercion-functions': 'error',
'unicorn/prefer-negative-index': 'error',
'unicorn/prefer-object-from-entries': 'error',
'unicorn/prefer-optional-catch-binding': 'error',
'unicorn/prefer-reflect-apply': 'error',
'unicorn/prefer-regexp-test': 'error',
'unicorn/prefer-set-has': 'error',
'unicorn/prefer-set-size': 'error',
'unicorn/prefer-spread': 'error',
'unicorn/prefer-string-replace-all': 'error',
'unicorn/prefer-string-slice': 'error',
'unicorn/require-array-join-separator': 'error',
'unicorn/require-number-to-fixed-digits-argument': 'error',
},
};

View File

@ -70,7 +70,7 @@ module.exports = {
'^jose/(.*)$': '<rootDir>/node_modules/jose/dist/node/cjs/$1',
},
// Slower machines had problems calling the WebSocket integration callbacks on time
testTimeout: 60000,
testTimeout: 90000,
reporters: ci ? [ 'default', 'github-actions' ] : [ 'default' ],
...ci && jestGithubRunnerSpecs(),

6885
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "@solid/community-server",
"version": "7.0.5",
"version": "7.1.2",
"description": "Community Solid Server: an open and modular implementation of the Solid specifications",
"license": "MIT",
"homepage": "https://github.com/CommunitySolidServer/CommunitySolidServer#readme",
@ -56,7 +56,6 @@
"lint": "npm run lint:eslint && npm run lint:markdown",
"lint:eslint": "eslint . --cache --max-warnings 0",
"lint:markdown": "markdownlint-cli2",
"lint:markdown:fix": "markdownlint-cli2-fix",
"prepare": "npm run build",
"release": "commit-and-tag-version",
"postrelease": "ts-node ./scripts/finalizeRelease.ts",
@ -143,32 +142,31 @@
"yup": "^1.3.2"
},
"devDependencies": {
"@antfu/eslint-config": "2.3.4",
"@commitlint/cli": "^17.7.2",
"@commitlint/config-conventional": "^17.7.0",
"@commitlint/cli": "^19.3.0",
"@commitlint/config-conventional": "^19.2.2",
"@inrupt/solid-client-authn-core": "^2.0.0",
"@inrupt/solid-client-authn-node": "^2.0.0",
"@tsconfig/node18": "^18.2.2",
"@types/jest": "^29.5.5",
"@types/jest": "^29.5.12",
"@types/set-cookie-parser": "^2.4.4",
"@types/supertest": "^2.0.14",
"commit-and-tag-version": "^11.3.0",
"componentsjs-generator": "^3.1.2",
"eslint-plugin-jest": "^27.4.3",
"husky": "^4.3.8",
"jest": "^29.7.0",
"jest-esm-transformer-2": "^1.0.0",
"jest-rdf": "^1.8.0",
"markdownlint-cli2": "^0.10.0",
"markdownlint-cli2": "^0.13.0",
"node-mocks-http": "^1.13.0",
"nodemon": "^3.0.1",
"opinionated-eslint-config": "0.1.0",
"set-cookie-parser": "^2.6.0",
"simple-git": "^3.20.0",
"supertest": "^6.3.3",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"typedoc": "^0.25.2",
"typescript": "^5.2.2"
"ts-node": "^10.9.2",
"typedoc": "^0.26.4",
"typescript": "^5.5.3"
},
"husky": {
"hooks": {
@ -179,7 +177,7 @@
"commit-and-tag-version": {
"scripts": {
"postbump": "ts-node ./scripts/upgradeConfig.ts",
"postchangelog": "ts-node ./scripts/formatChangelog.ts && markdownlint-cli2-fix"
"postchangelog": "ts-node ./scripts/formatChangelog.ts && markdownlint-cli2 --fix"
},
"writerOpts": {
"commitsSort": false

View File

@ -27,7 +27,9 @@ async function commitAndTag(): Promise<void> {
/**
* Prompts the user for input
*
* @param query - A string to prompt the user
*
* @returns Promise with the input of the user
*/
async function waitForUserInput(query: string): Promise<string> {

View File

@ -14,11 +14,13 @@ import { readFile, writeFile } from 'fs-extra';
/**
* Capitalize all list entries
*
* @param input - String to search/replace
*
* @returns Promise with output string
*/
async function capitalizeListEntries(input: string): Promise<string> {
return input.replaceAll(/^(\W*\* [a-z])/gmu, (match): string => match.toUpperCase());
return input.replaceAll(/^\W*\* [a-z]/gmu, (match): string => match.toUpperCase());
}
/**
@ -31,7 +33,9 @@ function endProcess(error: Error): never {
/**
* Main function for changelog formatting
*
* @param filePath - Path to the changelog file
*
* @returns Promise
*/
async function formatChangelog(filePath: string): Promise<void> {

View File

@ -18,6 +18,7 @@ import { joinFilePath, readPackageJson } from '../src/util/PathUtil';
/**
* Search and replace the version of a component with given name
*
* @param filePath - File to search/replace
* @param regex - RegExp matching the component reference
* @param version - Semantic version to change to
@ -31,8 +32,10 @@ async function replaceComponentVersion(filePath: string, regex: RegExp, version:
/**
* Recursive search for files that match a given Regex
*
* @param path - Path of folder to start search in
* @param regex - A regular expression to which file names will be matched
*
* @returns Promise with all file pathss
*/
async function getFilePaths(path: string, regex: RegExp): Promise<string[]> {

View File

@ -19,7 +19,7 @@ export class UnsecureWebIdExtractor extends CredentialsExtractor {
}
public async handle({ headers }: HttpRequest): Promise<Credentials> {
const webId = /^WebID\s+(.*)/ui.exec(headers.authorization!)![1];
const webId = /^WebID\s+(.*)/iu.exec(headers.authorization!)![1];
this.logger.info(`Agent unsecurely claims to be ${webId}`);
return { agent: { webId }};
}

View File

@ -24,7 +24,7 @@ import { AclMode } from './permissions/AclPermissionSet';
import { AccessMode } from './permissions/Permissions';
import type { PermissionMap, PermissionSet } from './permissions/Permissions';
const modesMap: Record<string, Readonly<(keyof AclPermissionSet)[]>> = {
const modesMap: Record<string, readonly (keyof AclPermissionSet)[]> = {
[ACL.Read]: [ AccessMode.read ],
[ACL.Write]: [ AccessMode.append, AccessMode.write ],
[ACL.Append]: [ AccessMode.append ],
@ -65,6 +65,7 @@ export class AcpReader extends PermissionReader {
/**
* Generates the allowed permissions.
*
* @param target - Target to generate permissions for.
* @param credentials - Credentials that are trying to access the resource.
* @param resourceCache - Cache used to store ACR data.

View File

@ -27,6 +27,7 @@ function getObjectValues(data: Store, subject: Term, predicate: NamedNode): stri
/**
* Finds the {@link IMatcher} with the given identifier in the given dataset.
*
* @param data - Dataset to look in.
* @param matcher - Identifier of the matcher.
*/
@ -42,6 +43,7 @@ export function getMatcher(data: Store, matcher: Term): IMatcher {
/**
* Finds the {@link IPolicy} with the given identifier in the given dataset.
*
* @param data - Dataset to look in.
* @param policy - Identifier of the policy.
*/
@ -58,6 +60,7 @@ export function getPolicy(data: Store, policy: Term): IPolicy {
/**
* Finds the {@link IAccessControl} with the given identifier in the given dataset.
*
* @param data - Dataset to look in.
* @param accessControl - Identifier of the access control.
*/
@ -71,6 +74,7 @@ export function getAccessControl(data: Store, accessControl: Term): IAccessContr
/**
* Finds the {@link IAccessControlResource} with the given identifier in the given dataset.
*
* @param data - Dataset to look in.
* @param acr - Identifier of the access control resource.
*/
@ -88,6 +92,7 @@ export function getAccessControlResource(data: Store, acr: Term): IAccessControl
/**
* Finds all {@link IAccessControlledResource} in the given dataset.
*
* @param data - Dataset to look in.
*/
export function* getAccessControlledResources(data: Store): Iterable<IAccessControlledResource> {

View File

@ -102,7 +102,9 @@ export class ParentContainerReader extends PermissionReader {
// When an operation requests to delete a resource,
// the server MUST match Authorizations allowing the acl:Write access privilege
// on the resource and the containing container.
mergedPermission.delete = resourcePermission.write && containerPermission.write &&
mergedPermission.delete =
resourcePermission.write &&
containerPermission.write &&
resourcePermission.delete !== false;
return mergedPermission;

View File

@ -24,6 +24,7 @@ export class PermissionBasedAuthorizer extends Authorizer {
/**
* The existence of the target resource determines the output status code for certain situations.
* The provided {@link ResourceSet} will be used for that.
*
* @param resourceSet - {@link ResourceSet} that can verify the target resource existence.
*/
public constructor(resourceSet: ResourceSet) {
@ -77,6 +78,7 @@ export class PermissionBasedAuthorizer extends Authorizer {
* Ensures that at least one of the credentials provides permissions for the given mode.
* Throws a {@link ForbiddenHttpError} or {@link UnauthorizedHttpError} depending on the credentials
* if access is not allowed.
*
* @param credentials - Credentials that require access.
* @param permissionSet - PermissionSet describing the available permissions of the credentials.
* @param mode - Which mode is requested.
@ -98,6 +100,7 @@ export class PermissionBasedAuthorizer extends Authorizer {
/**
* Checks whether the agent is authenticated (logged in) or not (public/anonymous).
*
* @param credentials - Credentials to check.
*/
private isAuthenticated(credentials: Credentials): boolean {

View File

@ -22,7 +22,7 @@ import type { PermissionMap } from './permissions/Permissions';
import { AccessMode } from './permissions/Permissions';
// Maps WebACL-specific modes to generic access modes.
const modesMap: Record<string, Readonly<(keyof AclPermissionSet)[]>> = {
const modesMap: Record<string, readonly (keyof AclPermissionSet)[]> = {
[ACL.Read]: [ AccessMode.read ],
[ACL.Write]: [ AccessMode.append, AccessMode.write ],
[ACL.Append]: [ AccessMode.append ],
@ -69,7 +69,7 @@ export class WebAclReader extends PermissionReader {
this.logger.debug(`Retrieving permissions of ${credentials.agent?.webId ?? 'an unknown agent'}`);
const aclMap = await this.getAclMatches(requestedModes.distinctKeys());
const storeMap = await this.findAuthorizationStatements(aclMap);
return await this.findPermissions(storeMap, credentials);
return this.findPermissions(storeMap, credentials);
}
/**
@ -96,6 +96,7 @@ export class WebAclReader extends PermissionReader {
/**
* Determines the available permissions for the given credentials.
*
* @param acl - Store containing all relevant authorization triples.
* @param credentials - Credentials to find the permissions for.
*/
@ -223,6 +224,7 @@ export class WebAclReader extends PermissionReader {
/**
* Extracts all rules from the store that are relevant for the given target,
* based on either the `acl:accessTo` or `acl:default` predicates.
*
* @param store - Store to filter.
* @param target - The identifier of which the acl rules need to be known.
* @param directAcl - If the store contains triples from the direct acl resource of the target or not.

View File

@ -21,7 +21,7 @@ export class AgentGroupAccessChecker extends AccessChecker {
const { webId } = credentials.agent;
const groups = acl.getObjects(rule, ACL.terms.agentGroup, null);
return await promiseSome(groups.map(async(group: Term): Promise<boolean> =>
return promiseSome(groups.map(async(group: Term): Promise<boolean> =>
this.isMemberOfGroup(webId, group)));
}
return false;
@ -29,6 +29,7 @@ export class AgentGroupAccessChecker extends AccessChecker {
/**
* Checks if the given agent is member of a given vCard group.
*
* @param webId - WebID of the agent that needs access.
* @param group - URL of the vCard group that needs to be checked.
*
@ -50,6 +51,6 @@ export class AgentGroupAccessChecker extends AccessChecker {
const representation = await fetchDataset(url);
return readableToQuads(representation.data);
})();
return await prom;
return prom;
}
}

View File

@ -19,6 +19,7 @@ export class IntermediateCreateExtractor extends ModesExtractor {
/**
* Certain permissions depend on the existence of the target resource.
* The provided {@link ResourceSet} will be used for that.
*
* @param resourceSet - {@link ResourceSet} that can verify the target resource existence.
* @param strategy - {@link IdentifierStrategy} that will be used to determine parent containers.
* @param source - The source {@link ModesExtractor}.

View File

@ -20,6 +20,7 @@ export class MethodModesExtractor extends ModesExtractor {
/**
* Certain permissions depend on the existence of the target resource.
* The provided {@link ResourceSet} will be used for that.
*
* @param resourceSet - {@link ResourceSet} that can verify the target resource existence.
*/
public constructor(resourceSet: ResourceSet) {

View File

@ -22,6 +22,7 @@ export class N3PatchModesExtractor extends ModesExtractor {
/**
* Certain permissions depend on the existence of the target resource.
* The provided {@link ResourceSet} will be used for that.
*
* @param resourceSet - {@link ResourceSet} that can verify the target resource existence.
*/
public constructor(resourceSet: ResourceSet) {

View File

@ -20,6 +20,7 @@ export class SparqlUpdateModesExtractor extends ModesExtractor {
/**
* Certain permissions depend on the existence of the target resource.
* The provided {@link ResourceSet} will be used for that.
*
* @param resourceSet - {@link ResourceSet} that can verify the target resource existence.
*/
public constructor(resourceSet: ResourceSet) {

View File

@ -79,7 +79,7 @@ class WebSocketListener extends WebSocketListenerEmitter {
private onMessage(message: string): void {
// Parse the message
const match = /^(\w+)\s+(.+)$/u.exec(message);
const match = /^(\w+)\s+(\S.+)$/u.exec(message);
if (!match) {
this.sendMessage('warning', `Unrecognized message format: ${message}`);
return;

View File

@ -31,6 +31,7 @@ export interface AuxiliaryIdentifierStrategy {
* Checks if the input identifier corresponds to an auxiliary resource.
* This does not check if that auxiliary resource exists,
* only if the identifier indicates that there could be an auxiliary resource there.
*
* @param identifier - Identifier to check.
*
* @returns true if the input identifier points to an auxiliary resource.
@ -40,6 +41,7 @@ export interface AuxiliaryIdentifierStrategy {
/**
* Returns the identifier of the resource which this auxiliary resource is referring to.
* This does not guarantee that this resource exists.
*
* @param identifier - Identifier of the auxiliary resource.
*
* @returns The ResourceIdentifier of the subject resource.

View File

@ -11,6 +11,7 @@ import type { AuxiliaryIdentifierStrategy } from './AuxiliaryIdentifierStrategy'
export interface AuxiliaryStrategy extends AuxiliaryIdentifierStrategy {
/**
* Whether this auxiliary resources uses its own authorization instead of the subject resource authorization.
*
* @param identifier - Identifier of the auxiliary resource.
*/
usesOwnAuthorization: (identifier: ResourceIdentifier) => boolean;
@ -18,6 +19,7 @@ export interface AuxiliaryStrategy extends AuxiliaryIdentifierStrategy {
/**
* Whether the root storage container requires this auxiliary resource to be present.
* If yes, this means they can't be deleted individually from such a container.
*
* @param identifier - Identifier of the auxiliary resource.
*/
isRequiredInRoot: (identifier: ResourceIdentifier) => boolean;
@ -42,6 +44,7 @@ export interface AuxiliaryStrategy extends AuxiliaryIdentifierStrategy {
/**
* Validates if the representation contains valid data for an auxiliary resource.
* Should throw an error in case the data is invalid.
*
* @param identifier - Identifier of the auxiliary resource.
* @param representation - Representation of the auxiliary resource.
*/

View File

@ -50,7 +50,9 @@ export class LinkRelObject {
/**
* Checks whether the object can be added to the metadata
*
* @param object - The link target.
*
* @returns a boolean to indicate whether it can be added to the metadata or not
*/
private objectAllowed(object: string): boolean {
@ -59,6 +61,7 @@ export class LinkRelObject {
/**
* Adds the object to the metadata when it is allowed
*
* @param object - The link target.
* @param metadata - Metadata of the resource.
* @param logger - Logger
@ -67,8 +70,10 @@ export class LinkRelObject {
if (this.objectAllowed(object)) {
if (this.ephemeral) {
metadata.add(this.value, namedNode(object), SOLID_META.terms.ResponseMetadata);
logger.debug(`"<${metadata.identifier.value}> <${this.value.value}> <${object}>." ` +
`will not be stored permanently in the metadata.`);
logger.debug(
`"<${metadata.identifier.value}> <${this.value.value}> <${object}>." ` +
`will not be stored permanently in the metadata.`,
);
} else {
metadata.add(this.value, namedNode(object));
}

View File

@ -62,8 +62,8 @@ export class ConvertingErrorHandler extends ErrorHandler {
private async extractErrorDetails({ error, request }: ErrorHandlerArgs): Promise<PreparedArguments> {
if (!this.showStackTrace) {
delete error.stack;
// eslint-disable-next-line ts/no-unsafe-member-access
delete (error as any).cause;
// Cheating here to delete a readonly field
delete (error as { cause: unknown }).cause;
}
const representation = new BasicRepresentation([ error ], error.metadata, INTERNAL_ERROR, false);
const identifier = { path: representation.metadata.identifier.value };

View File

@ -0,0 +1,30 @@
import { DataFactory } from 'n3';
import { SOLID_ERROR } from '../../../util/Vocabularies';
import type { TargetExtractor } from '../../input/identifier/TargetExtractor';
import type { ResponseDescription } from '../response/ResponseDescription';
import type { ErrorHandlerArgs } from './ErrorHandler';
import { ErrorHandler } from './ErrorHandler';
/**
* Adds metadata to an error to indicate the identifier of the originally targeted resource.
*/
export class TargetExtractorErrorHandler extends ErrorHandler {
protected readonly errorHandler: ErrorHandler;
protected readonly targetExtractor: TargetExtractor;
public constructor(errorHandler: ErrorHandler, targetExtractor: TargetExtractor) {
super();
this.errorHandler = errorHandler;
this.targetExtractor = targetExtractor;
}
public async canHandle(input: ErrorHandlerArgs): Promise<void> {
return this.errorHandler.canHandle(input);
}
public async handle(input: ErrorHandlerArgs): Promise<ResponseDescription> {
const target = await this.targetExtractor.handleSafe(input);
input.error.metadata.add(SOLID_ERROR.terms.target, DataFactory.namedNode(target.path));
return this.errorHandler.handle(input);
}
}

View File

@ -8,8 +8,11 @@ import { LDP, PIM, RDF, SOLID_ERROR } from '../../../util/Vocabularies';
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
import { MetadataWriter } from './MetadataWriter';
// Only PUT and PATCH can be used to create a new resource
const NEW_RESOURCE_ALLOWED_METHODS = new Set([ 'PUT', 'PATCH' ]);
enum ResourceType {
document,
container,
unknown,
}
/**
* Generates Allow, Accept-Patch, Accept-Post, and Accept-Put headers.
@ -30,8 +33,20 @@ export class AllowAcceptHeaderWriter extends MetadataWriter {
public async handle(input: { response: HttpResponse; metadata: RepresentationMetadata }): Promise<void> {
const { response, metadata } = input;
let resourceType: ResourceType;
if (metadata.has(RDF.terms.type, LDP.terms.Resource)) {
resourceType = isContainerPath(metadata.identifier.value) ? ResourceType.container : ResourceType.document;
} else {
const target = metadata.get(SOLID_ERROR.terms.target)?.value;
if (target) {
resourceType = isContainerPath(target) ? ResourceType.container : ResourceType.document;
} else {
resourceType = ResourceType.unknown;
}
}
// Filter out methods which are not allowed
const allowedMethods = this.filterAllowedMethods(metadata);
const allowedMethods = this.filterAllowedMethods(metadata, resourceType);
// Generate the Allow headers (if required)
const generateAllow = this.generateAllow(allowedMethods, response, metadata);
@ -43,29 +58,34 @@ export class AllowAcceptHeaderWriter extends MetadataWriter {
/**
* Starts from the stored set of methods and removes all those that are not allowed based on the metadata.
*/
private filterAllowedMethods(metadata: RepresentationMetadata): Set<string> {
private filterAllowedMethods(metadata: RepresentationMetadata, resourceType: ResourceType): Set<string> {
const disallowedMethods = new Set(metadata.getAll(SOLID_ERROR.terms.disallowedMethod)
.map((term): string => term.value));
const allowedMethods = new Set(this.supportedMethods.filter((method): boolean => !disallowedMethods.has(method)));
// POST is only allowed on containers.
// Metadata only has the resource URI in case it has resource metadata.
if (!this.isPostAllowed(metadata)) {
if (!this.isPostAllowed(resourceType)) {
allowedMethods.delete('POST');
}
if (!this.isPutAllowed(metadata)) {
if (!this.isPutAllowed(metadata, resourceType)) {
allowedMethods.delete('PUT');
}
if (!this.isDeleteAllowed(metadata)) {
if (!this.isPatchAllowed(resourceType)) {
allowedMethods.delete('PATCH');
}
if (!this.isDeleteAllowed(metadata, resourceType)) {
allowedMethods.delete('DELETE');
}
// If we are sure the resource does not exist: only keep methods that can create a new resource.
if (metadata.has(SOLID_ERROR.terms.errorResponse, NotFoundHttpError.uri)) {
for (const method of allowedMethods) {
if (!NEW_RESOURCE_ALLOWED_METHODS.has(method)) {
// Containers can only be created by PUT; documents by PUT or PATCH
if (method !== 'PUT' && (method !== 'PATCH' || resourceType === ResourceType.container)) {
allowedMethods.delete(method);
}
}
@ -76,18 +96,23 @@ export class AllowAcceptHeaderWriter extends MetadataWriter {
/**
* POST is only allowed on containers.
* The metadata URI is only valid in case there is resource metadata,
* otherwise it is just a blank node.
*/
private isPostAllowed(metadata: RepresentationMetadata): boolean {
return !metadata.has(RDF.terms.type, LDP.terms.Resource) || isContainerPath(metadata.identifier.value);
private isPostAllowed(resourceType: ResourceType): boolean {
return resourceType !== ResourceType.document;
}
/**
* PUT is not allowed on existing containers.
*/
private isPutAllowed(metadata: RepresentationMetadata): boolean {
return !metadata.has(RDF.terms.type, LDP.terms.Resource) || !isContainerPath(metadata.identifier.value);
private isPutAllowed(metadata: RepresentationMetadata, resourceType: ResourceType): boolean {
return resourceType !== ResourceType.container || !metadata.has(RDF.terms.type, LDP.terms.Resource);
}
/**
* PATCH is not allowed on containers.
*/
private isPatchAllowed(resourceType: ResourceType): boolean {
return resourceType !== ResourceType.container;
}
/**
@ -97,14 +122,14 @@ export class AllowAcceptHeaderWriter extends MetadataWriter {
*
* Note that the identifier value check only works if the metadata is not about an error.
*/
private isDeleteAllowed(metadata: RepresentationMetadata): boolean {
if (!isContainerPath(metadata.identifier.value)) {
private isDeleteAllowed(metadata: RepresentationMetadata, resourceType: ResourceType): boolean {
if (resourceType !== ResourceType.container) {
return true;
}
const isStorage = metadata.has(RDF.terms.type, PIM.terms.Storage);
const isEmpty = metadata.has(LDP.terms.contains);
return !isStorage && !isEmpty;
const isEmpty = !metadata.has(LDP.terms.contains);
return !isStorage && isEmpty;
}
/**

View File

@ -1,9 +1,10 @@
import { Util } from 'n3';
import { getLoggerFor } from '../../../logging/LogUtil';
import type { HttpResponse } from '../../../server/HttpResponse';
import { addHeader } from '../../../util/HeaderUtil';
import { LDP, RDF, SOLID_ERROR } from '../../../util/Vocabularies';
import type { AuxiliaryStrategy } from '../../auxiliary/AuxiliaryStrategy';
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
import type { ResourceIdentifier } from '../../representation/ResourceIdentifier';
import { MetadataWriter } from './MetadataWriter';
/**
@ -30,9 +31,17 @@ export class AuxiliaryLinkMetadataWriter extends MetadataWriter {
}
public async handle(input: { response: HttpResponse; metadata: RepresentationMetadata }): Promise<void> {
const identifier = { path: input.metadata.identifier.value };
let identifier: ResourceIdentifier | undefined;
if (input.metadata.has(RDF.terms.type, LDP.terms.Resource)) {
identifier = { path: input.metadata.identifier.value };
} else {
const target = input.metadata.get(SOLID_ERROR.terms.target);
if (target) {
identifier = { path: target.value };
}
}
// The metadata identifier will be a blank node in case an error was thrown.
if (!this.auxiliaryStrategy.isAuxiliaryIdentifier(identifier) && !Util.isBlankNode(input.metadata.identifier)) {
if (identifier && !this.auxiliaryStrategy.isAuxiliaryIdentifier(identifier)) {
const auxiliaryIdentifier = this.specificStrategy.getAuxiliaryIdentifier(identifier);
addHeader(input.response, 'Link', `<${auxiliaryIdentifier.path}>; rel="${this.relationType}"`);
}

View File

@ -35,7 +35,7 @@ export class BasicRepresentation implements Representation {
* @param binary - Whether the representation is a binary or object stream
*/
public constructor(
data: Guarded<Readable> | Readable | any[] | string,
data: Guarded<Readable> | Readable | unknown[] | string,
metadata: RepresentationMetadata | MetadataRecord,
binary?: boolean,
);
@ -47,7 +47,7 @@ export class BasicRepresentation implements Representation {
* @param binary - Whether the representation is a binary or object stream
*/
public constructor(
data: Guarded<Readable> | Readable | any[] | string,
data: Guarded<Readable> | Readable | unknown[] | string,
metadata: RepresentationMetadata | MetadataRecord,
contentType?: string,
binary?: boolean,
@ -59,7 +59,7 @@ export class BasicRepresentation implements Representation {
* @param binary - Whether the representation is a binary or object stream
*/
public constructor(
data: Guarded<Readable> | Readable | any[] | string,
data: Guarded<Readable> | Readable | unknown[] | string,
contentType: string,
binary?: boolean,
);
@ -71,7 +71,7 @@ export class BasicRepresentation implements Representation {
* @param binary - Whether the representation is a binary or object stream
*/
public constructor(
data: Guarded<Readable> | Readable | any[] | string,
data: Guarded<Readable> | Readable | unknown[] | string,
identifier: MetadataIdentifier,
metadata?: MetadataRecord,
binary?: boolean,
@ -84,14 +84,14 @@ export class BasicRepresentation implements Representation {
* @param binary - Whether the representation is a binary or object stream
*/
public constructor(
data: Guarded<Readable> | Readable | any[] | string,
data: Guarded<Readable> | Readable | unknown[] | string,
identifier: MetadataIdentifier,
contentType?: string,
binary?: boolean,
);
public constructor(
data?: Readable | any[] | string,
data?: Readable | unknown[] | string,
metadata?: RepresentationMetadata | MetadataRecord | MetadataIdentifier | string,
metadataRest?: MetadataRecord | string | boolean,
binary?: boolean,
@ -109,8 +109,7 @@ export class BasicRepresentation implements Representation {
}
if (!isRepresentationMetadata(metadata) || typeof metadataRest === 'string') {
// This combination will always match with a valid overload
// eslint-disable-next-line ts/no-unsafe-argument
metadata = new RepresentationMetadata(metadata as any, metadataRest as any);
metadata = new RepresentationMetadata(metadata as RepresentationMetadata, metadataRest as string);
}
this.metadata = metadata;

View File

@ -15,7 +15,7 @@ export type MetadataGraph = NamedNode | BlankNode | DefaultGraph | string;
/**
* Determines whether the object is a `RepresentationMetadata`.
*/
export function isRepresentationMetadata(object: any): object is RepresentationMetadata {
export function isRepresentationMetadata(object: unknown): object is RepresentationMetadata {
return typeof (object as RepresentationMetadata)?.setMetadata === 'function';
}
@ -26,6 +26,7 @@ const cachedNamedNodes: Record<string, NamedNode> = {};
* Converts the incoming name (URI or shorthand) to a named node.
* The generated terms get cached to reduce the number of created nodes,
* so only use this for internal constants!
*
* @param name - Predicate to potentially transform.
*/
function toCachedNamedNode(name: string): NamedNode {
@ -167,6 +168,7 @@ export class RepresentationMetadata {
/**
* Helper function to import all entries from the given metadata.
* If the new metadata has a different identifier the internal one will be updated.
*
* @param metadata - Metadata to import.
*/
public setMetadata(metadata: RepresentationMetadata): this {
@ -235,22 +237,24 @@ export class RepresentationMetadata {
/**
* Adds a value linked to the identifier. Strings get converted to literals.
*
* @param predicate - Predicate linking identifier to value.
* @param object - Value(s) to add.
* @param graph - Optional graph of where to add the values to.
*/
public add(predicate: NamedNode, object: MetadataValue, graph?: MetadataGraph): this {
return this.forQuads(predicate, object, (pred, obj): any => this.addQuad(this.id, pred, obj, graph));
return this.forQuads(predicate, object, (pred, obj): unknown => this.addQuad(this.id, pred, obj, graph));
}
/**
* Removes the given value from the metadata. Strings get converted to literals.
*
* @param predicate - Predicate linking identifier to value.
* @param object - Value(s) to remove.
* @param graph - Optional graph of where to remove the values from.
*/
public remove(predicate: NamedNode, object: MetadataValue, graph?: MetadataGraph): this {
return this.forQuads(predicate, object, (pred, obj): any => this.removeQuad(this.id, pred, obj, graph));
return this.forQuads(predicate, object, (pred, obj): unknown => this.removeQuad(this.id, pred, obj, graph));
}
/**
@ -271,6 +275,7 @@ export class RepresentationMetadata {
/**
* Removes all values linked through the given predicate.
*
* @param predicate - Predicate to remove.
* @param graph - Optional graph where to remove from.
*/
@ -290,12 +295,17 @@ export class RepresentationMetadata {
): boolean {
// This works with N3.js but at the time of writing the typings have not been updated yet.
// If you see this line of code check if the typings are already correct and update this if so.
// eslint-disable-next-line ts/no-unsafe-call
return (this.store.has as any)(this.id, predicate, object, graph) as boolean;
return (this.store as unknown as {
has: (subject: Term,
predicate: Term | string | null,
object: Term | string | null,
graph: Term | string | null) => boolean;
}).has(this.id, predicate, object, graph);
}
/**
* Finds all object values matching the given predicate and/or graph.
*
* @param predicate - Optional predicate to get the values for.
* @param graph - Optional graph where to get from.
*
@ -310,10 +320,10 @@ export class RepresentationMetadata {
* @param predicate - Predicate to get the value for.
* @param graph - Optional graph where the triple should be found.
*
* @returns The corresponding value. Undefined if there is no match
*
* @throws Error
* If there are multiple matching values.
*
* @returns The corresponding value. Undefined if there is no match
*/
public get(predicate: NamedNode, graph?: MetadataGraph): Term | undefined {
const terms = this.getAll(predicate, graph);
@ -333,6 +343,7 @@ export class RepresentationMetadata {
/**
* Sets the value for the given predicate, removing all other instances.
* In case the object is undefined this is identical to `removeAll(predicate)`.
*
* @param predicate - Predicate linking to the value.
* @param object - Value(s) to set.
* @param graph - Optional graph where the triple should be stored.
@ -379,6 +390,7 @@ export class RepresentationMetadata {
/**
* Parse the internal RDF structure to retrieve the Record with ContentType Parameters.
*
* @returns A {@link ContentType} object containing the value and optional parameters if there is one.
*/
private getContentType(): ContentType | undefined {

View File

@ -12,5 +12,5 @@ export interface ResourceIdentifier {
* Determines whether the object is a {@link ResourceIdentifier}.
*/
export function isResourceIdentifier(object: unknown): object is ResourceIdentifier {
return Boolean(object) && (typeof (object as ResourceIdentifier).path === 'string');
return Boolean(object) && typeof (object as ResourceIdentifier).path === 'string';
}

View File

@ -187,14 +187,20 @@ export class IdentityProviderFactory implements ProviderFactory {
provider.use(async(ctx, next): Promise<void> => {
const accepts = ctx.accepts.bind(ctx);
// Using `any` typings to make sure we support all different versions of `ctx.accepts`
ctx.accepts = (...types): any => {
// This is how you get the correct typing for an overloaded function
type AcceptFn = {
(): string[];
(...types: string[]): string | false;
(types: string[]): string | false;
};
ctx.accepts = ((...types): string[] | string | false => {
// Make sure we only override our specific case
if (types.length === 2 && types[0] === 'json' && types[1] === 'html') {
return 'html';
}
return accepts(...types as string[]);
};
}) as AcceptFn;
return next();
});
@ -254,10 +260,10 @@ export class IdentityProviderFactory implements ProviderFactory {
}
/**
* Checks if the given token is an access token.
* Checks whether the given token is an access token.
* The AccessToken interface is not exported, so we have to access it like this.
*/
private isAccessToken(token: any): token is KoaContextWithOIDC['oidc']['accessToken'] {
private isAccessToken(token: unknown): token is KoaContextWithOIDC['oidc']['accessToken'] {
return (token as KoaContextWithOIDC['oidc']['accessToken'])?.kind === 'AccessToken';
}
@ -270,7 +276,7 @@ export class IdentityProviderFactory implements ProviderFactory {
// Some fields are still missing, see https://github.com/CommunitySolidServer/CommunitySolidServer/issues/1154#issuecomment-1040233385
config.findAccount = async(ctx: KoaContextWithOIDC, sub: string): Promise<Account> => ({
accountId: sub,
async claims(): Promise<{ sub: string; [key: string]: any }> {
async claims(): Promise<{ sub: string; [key: string]: unknown }> {
return { sub, webid: sub, azp: ctx.oidc.client?.clientId };
},
});

View File

@ -1,5 +1,6 @@
import type { Json } from '../../util/Json';
import { ACCOUNT_ID_KEY } from './account/AccountIdRoute';
import type { Json, JsonRepresentation } from './InteractionUtil';
import type { JsonRepresentation } from './InteractionUtil';
import type { JsonInteractionHandlerInput } from './JsonInteractionHandler';
import { JsonInteractionHandler } from './JsonInteractionHandler';
import type { InteractionRoute } from './routing/InteractionRoute';

View File

@ -3,16 +3,12 @@ import type Provider from '../../../templates/types/oidc-provider';
import type { RepresentationMetadata } from '../../http/representation/RepresentationMetadata';
import { getLoggerFor } from '../../logging/LogUtil';
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
import type { Json } from '../../util/Json';
import type { Interaction } from './InteractionHandler';
import Dict = NodeJS.Dict;
const logger = getLoggerFor('AccountUtil');
/**
* A JSON object.
*/
export type Json = string | number | boolean | Dict<Json> | Json[];
/**
* Contains a JSON object and any associated metadata.
* Similar to a {@link Representation} but with all the data in memory instead of as a stream
@ -53,6 +49,7 @@ export type AccountInteractionResults = { [ACCOUNT_PROMPT]?: string } & Interact
/**
* Updates the `oidcInteraction` object with the necessary data in case a prompt gets updated.
*
* @param oidcInteraction - Interaction to update.
* @param result - New data to add to the interaction.
* @param mergeWithLastSubmission - If this new data needs to be merged with already existing data in the interaction.
@ -76,6 +73,7 @@ export async function finishInteraction(
* Removes the WebID, the `accountId`, from the OIDC session object,
* allowing us to replace it with a new value.
* If there is no session in the Interaction, nothing will happen.
*
* @param provider - The OIDC provider.
* @param oidcInteraction - The current interaction.
*/

View File

@ -3,10 +3,10 @@ import type { Representation } from '../../http/representation/Representation';
import { RepresentationMetadata } from '../../http/representation/RepresentationMetadata';
import type { RepresentationConverter } from '../../storage/conversion/RepresentationConverter';
import { APPLICATION_JSON } from '../../util/ContentTypes';
import type { Json } from '../../util/Json';
import { readJsonStream } from '../../util/StreamUtil';
import type { InteractionHandlerInput } from './InteractionHandler';
import { InteractionHandler } from './InteractionHandler';
import type { Json } from './InteractionUtil';
import type { JsonInteractionHandler, JsonInteractionHandlerInput } from './JsonInteractionHandler';
/**

View File

@ -1,8 +1,9 @@
import type { RepresentationMetadata } from '../../http/representation/RepresentationMetadata';
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
import { AsyncHandler } from '../../util/handlers/AsyncHandler';
import type { Json } from '../../util/Json';
import type { Interaction } from './InteractionHandler';
import type { Json, JsonRepresentation } from './InteractionUtil';
import type { JsonRepresentation } from './InteractionUtil';
import Dict = NodeJS.Dict;
export interface JsonInteractionHandlerInput {

View File

@ -1,5 +1,5 @@
import type { Json } from '../../util/Json';
import { ControlHandler } from './ControlHandler';
import type { Json } from './InteractionUtil';
import type { JsonInteractionHandlerInput } from './JsonInteractionHandler';
/**

View File

@ -1,4 +1,5 @@
import type { Json, JsonRepresentation } from './InteractionUtil';
import type { Json } from '../../util/Json';
import type { JsonRepresentation } from './InteractionUtil';
import { JsonInteractionHandler } from './JsonInteractionHandler';
/**

View File

@ -1,14 +1,16 @@
import { string } from 'yup';
import type { ObjectSchema, Schema, ValidateOptions } from 'yup';
import type { AnyObject, Maybe, ObjectSchema, Schema, ValidateOptions } from 'yup';
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
import { createErrorMessage } from '../../util/errors/ErrorUtil';
import type { Json } from '../../util/Json';
import { isUrl } from '../../util/StringUtil';
import type { Json } from './InteractionUtil';
import Dict = NodeJS.Dict;
type BaseObjectSchema = ObjectSchema<Maybe<AnyObject>>;
// The builtin `url` validator of `yup` does not support localhost URLs, so we create a custom one here.
// The reason for having a URL validator on the WebID is to prevent us from generating invalid ACL,
// which would break the pod creation causing us to have an incomplete pod.
// We validate the WebID URL to prevent generation of invalid ACL,
// which would break the pod creation, causing us to have an incomplete pod.
export const URL_SCHEMA = string().trim().optional().test({
name: 'url',
message: (value: { value: string }): string => `"${value.value}" is not a valid URL`,
@ -20,16 +22,16 @@ export const URL_SCHEMA = string().trim().optional().test({
},
});
function isObjectSchema(schema: Schema): schema is ObjectSchema<any> {
function isObjectSchema(schema: Schema): schema is BaseObjectSchema {
return schema.type === 'object';
}
// `T` can't extend Schema since it could also be a Reference, which is a type `yup` doesn't export
type SchemaType<T> = T extends ObjectSchema<any> ? ObjectType<T> : { required: boolean; type: string };
type SchemaType<T> = T extends BaseObjectSchema ? ObjectType<T> : { required: boolean; type: string };
// The type of the fields in an object schema
type FieldType<T extends ObjectSchema<any>> = T extends { fields: Record<infer R, any> } ? R : never;
type FieldType<T extends BaseObjectSchema> = T extends { fields: Record<infer R, unknown> } ? R : never;
// Simplified type we use to represent yup objects
type ObjectType<T extends ObjectSchema<any>> =
type ObjectType<T extends BaseObjectSchema> =
{ required: boolean; type: 'object'; fields: {[ K in FieldType<T> ]: SchemaType<T['fields'][K]> }};
/**
@ -50,7 +52,7 @@ function parseSchemaDescription<T extends Schema>(schema: T): SchemaType<T> {
/**
* Generates a simplified representation of a yup schema.
*/
export function parseSchema<T extends ObjectSchema<any>>(schema: T): Pick<SchemaType<T>, 'fields'> {
export function parseSchema<T extends BaseObjectSchema>(schema: T): Pick<SchemaType<T>, 'fields'> {
const result = parseSchemaDescription(schema);
return { fields: result.fields };
}
@ -58,13 +60,12 @@ export function parseSchema<T extends ObjectSchema<any>>(schema: T): Pick<Schema
/**
* Same functionality as the yup validate function, but throws a {@link BadRequestHttpError} if there is an error.
*/
export async function validateWithError<T extends ObjectSchema<any>>(
export async function validateWithError<T extends BaseObjectSchema>(
schema: T,
data: unknown,
options?: ValidateOptions<any>,
options?: ValidateOptions<AnyObject>,
): Promise<T['__outputType']> {
try {
// eslint-disable-next-line ts/no-unsafe-return
return await schema.validate(data, options);
} catch (error: unknown) {
throw new BadRequestHttpError(createErrorMessage(error));

View File

@ -5,7 +5,6 @@ export const ACCOUNT_SETTINGS_REMEMBER_LOGIN = 'rememberLogin';
export type AccountSettings = { [ACCOUNT_SETTINGS_REMEMBER_LOGIN]?: boolean };
/* eslint-disable ts/method-signature-style */
/**
* Used to store account data.
*/
@ -20,16 +19,18 @@ export interface AccountStore {
/**
* Finds the setting of the account with the given identifier.
*
* @param id - The account identifier.
* @param setting - The setting to find the value of.
*/
getSetting<T extends keyof AccountSettings>(id: string, setting: T): Promise<AccountSettings[T]>;
getSetting: <T extends keyof AccountSettings>(id: string, setting: T) => Promise<AccountSettings[T]>;
/**
* Updates the settings for the account with the given identifier to the new values.
*
* @param id - The account identifier.
* @param setting - The setting to update.
* @param value - The new value for the setting.
*/
updateSetting<T extends keyof AccountSettings>(id: string, setting: T, value: AccountSettings[T]): Promise<void>;
updateSetting: <T extends keyof AccountSettings>(id: string, setting: T, value: AccountSettings[T]) => Promise<void>;
}

View File

@ -22,9 +22,10 @@ export class BaseAccountStore extends Initializer implements AccountStore {
private readonly storage: AccountLoginStorage<{ [ACCOUNT_TYPE]: typeof ACCOUNT_STORAGE_DESCRIPTION }>;
private initialized = false;
public constructor(storage: AccountLoginStorage<any>) {
// Wrong typings to prevent Components.js typing issues
public constructor(storage: AccountLoginStorage<Record<string, never>>) {
super();
this.storage = storage as typeof this.storage;
this.storage = storage as unknown as typeof this.storage;
}
// Initialize the type definitions

View File

@ -23,7 +23,7 @@ export class BaseCookieStore implements CookieStore {
}
public async get(cookie: string): Promise<string | undefined> {
return await this.storage.get(cookie);
return this.storage.get(cookie);
}
public async refresh(cookie: string): Promise<Date | undefined> {

View File

@ -61,7 +61,7 @@ export class BaseLoginAccountStorage<T extends IndexTypeCollection<T>> implement
return this.storage.defineType(type, description);
}
public async createIndex<TType extends StringKey<T>>(type: TType, key: StringKey<TType>): Promise<void> {
public async createIndex<TType extends StringKey<T>>(type: TType, key: StringKey<T[TType]>): Promise<void> {
return this.storage.createIndex(type, key);
}
@ -103,7 +103,7 @@ export class BaseLoginAccountStorage<T extends IndexTypeCollection<T>> implement
}
public async findIds<TType extends StringKey<T>>(type: TType, query: IndexedQuery<T, TType>): Promise<string[]> {
return await this.storage.findIds(type, query);
return this.storage.findIds(type, query);
}
public async set<TType extends StringKey<T>>(type: TType, value: TypeObject<T[TType]>): Promise<void> {

View File

@ -5,6 +5,7 @@ export interface CookieStore {
/**
* Generates and stores a new cookie for the given accountId.
* This does not replace previously generated cookies.
*
* @param accountId - Account to create a cookie for.
*
* @returns The generated cookie.
@ -13,18 +14,21 @@ export interface CookieStore {
/**
* Return the accountID associated with the given cookie.
*
* @param cookie - Cookie to find the account for.
*/
get: (cookie: string) => Promise<string | undefined>;
/**
* Refreshes the cookie expiration and returns when it will expire if the cookie exists.
*
* @param cookie - Cookie to refresh.
*/
refresh: (cookie: string) => Promise<Date | undefined>;
/**
* Deletes the given cookie.
*
* @param cookie - Cookie to delete.
*/
delete: (cookie: string) => Promise<boolean>;

View File

@ -27,9 +27,10 @@ export class BaseClientCredentialsStore extends Initializer implements ClientCre
private initialized = false;
public constructor(storage: AccountLoginStorage<any>) {
// Wrong typings to prevent Components.js typing issues
public constructor(storage: AccountLoginStorage<Record<string, never>>) {
super();
this.storage = storage as typeof this.storage;
this.storage = storage as unknown as typeof this.storage;
}
// Initialize the type definitions

View File

@ -1,10 +1,11 @@
import { RepresentationMetadata } from '../../../http/representation/RepresentationMetadata';
import { getLoggerFor } from '../../../logging/LogUtil';
import type { Json } from '../../../util/Json';
import { SOLID_HTTP } from '../../../util/Vocabularies';
import { ACCOUNT_SETTINGS_REMEMBER_LOGIN } from '../account/util/AccountStore';
import type { AccountStore } from '../account/util/AccountStore';
import type { CookieStore } from '../account/util/CookieStore';
import type { Json, JsonRepresentation } from '../InteractionUtil';
import type { JsonRepresentation } from '../InteractionUtil';
import { finishInteraction } from '../InteractionUtil';
import type { JsonInteractionHandlerInput } from '../JsonInteractionHandler';
import { JsonInteractionHandler } from '../JsonInteractionHandler';
@ -80,7 +81,8 @@ export abstract class ResolveLoginHandler extends JsonInteractionHandler {
}
/**
* Updates the account setting that determines if the login status needs to be remembered.
* Updates the account setting that determines whether the login status needs to be remembered.
*
* @param accountId - ID of the account.
* @param remember - If the account should be remembered or not. The setting will not be updated if this is undefined.
*/
@ -94,6 +96,7 @@ export abstract class ResolveLoginHandler extends JsonInteractionHandler {
/**
* Takes the necessary steps to log a user in.
*
* @param input - Same input that was passed to the handle function.
*/
public abstract login(input: JsonInteractionHandlerInput): Promise<JsonRepresentation<LoginOutputType>>;

View File

@ -29,9 +29,10 @@ export class BasePasswordStore extends Initializer implements PasswordStore {
private readonly saltRounds: number;
private initialized = false;
public constructor(storage: AccountLoginStorage<any>, saltRounds = 10) {
// Wrong typings to prevent Components.js typing issues
public constructor(storage: AccountLoginStorage<Record<string, never>>, saltRounds = 10) {
super();
this.storage = storage as typeof this.storage;
this.storage = storage as unknown as typeof this.storage;
this.saltRounds = saltRounds;
}

View File

@ -6,7 +6,9 @@ export interface ForgotPasswordStore {
* Creates a Forgot Password Confirmation Record. This will be to remember that
* a user has made a request to reset a password. Throws an error if the email doesn't
* exist.
*
* @param id - ID of the email/password login object.
*
* @returns The record id. This should be included in the reset password link.
*/
generate: (id: string) => Promise<string>;
@ -14,13 +16,16 @@ export interface ForgotPasswordStore {
/**
* Gets the email associated with the forgot password confirmation record
* or undefined if it's not present.
*
* @param recordId - The record id retrieved from the link.
*
* @returns The user's email.
*/
get: (recordId: string) => Promise<string | undefined>;
/**
* Deletes the Forgot Password Confirmation Record.
*
* @param recordId - The record id of the forgot password confirmation record.
*/
delete: (recordId: string) => Promise<boolean>;

View File

@ -44,9 +44,10 @@ export class BasePodStore extends Initializer implements PodStore {
private initialized = false;
public constructor(storage: AccountLoginStorage<any>, manager: PodManager, visible = false) {
// Wrong typings to prevent Components.js typing issues
public constructor(storage: AccountLoginStorage<Record<string, never>>, manager: PodManager, visible = false) {
super();
this.storage = storage as typeof this.storage;
this.storage = storage as unknown as typeof this.storage;
this.visible = visible;
this.manager = manager;
}

View File

@ -10,7 +10,7 @@ import type { InteractionRoute } from './InteractionRoute';
* Rejects operations that target a different route,
* otherwise the input parameters are passed to the source handler.
*/
export class InteractionRouteHandler<T extends InteractionRoute<any>> extends JsonInteractionHandler {
export class InteractionRouteHandler<T extends InteractionRoute<string>> extends JsonInteractionHandler {
protected readonly route: T;
protected readonly source: JsonInteractionHandler;

View File

@ -23,9 +23,10 @@ export class BaseWebIdStore extends Initializer implements WebIdStore {
private readonly storage: AccountLoginStorage<{ [WEBID_STORAGE_TYPE]: typeof WEBID_STORAGE_DESCRIPTION }>;
private initialized = false;
public constructor(storage: AccountLoginStorage<any>) {
// Wrong typings to prevent Components.js typing issues
public constructor(storage: AccountLoginStorage<Record<string, never>>) {
super();
this.storage = storage as typeof this.storage;
this.storage = storage as unknown as typeof this.storage;
}
// Initialize the type definitions

View File

@ -81,6 +81,7 @@ export class ClientIdAdapter extends PassthroughAdapter {
/**
* Parses RDF data found at a Client ID.
*
* @param data - Raw data from the Client ID.
* @param id - The actual Client ID.
* @param response - Response object from the request.

View File

@ -100,6 +100,7 @@ export * from './http/output/error/ConvertingErrorHandler';
export * from './http/output/error/ErrorHandler';
export * from './http/output/error/RedirectingErrorHandler';
export * from './http/output/error/SafeErrorHandler';
export * from './http/output/error/TargetExtractorErrorHandler';
// HTTP/Output/Metadata
export * from './http/output/metadata/AllowAcceptHeaderWriter';
@ -403,6 +404,14 @@ export * from './server/notifications/WebSocketChannel2023/WebSocket2023Util';
export * from './server/notifications/WebSocketChannel2023/WebSocketMap';
export * from './server/notifications/WebSocketChannel2023/WebSocketChannel2023Type';
// Server/Notifications/StreamingHTTPChannel2023
export * from './server/notifications/StreamingHttpChannel2023/StreamingHttp2023Emitter';
export * from './server/notifications/StreamingHttpChannel2023/StreamingHttp2023Util';
export * from './server/notifications/StreamingHttpChannel2023/StreamingHttpListeningActivityHandler';
export * from './server/notifications/StreamingHttpChannel2023/StreamingHttpMap';
export * from './server/notifications/StreamingHttpChannel2023/StreamingHttpMetadataWriter';
export * from './server/notifications/StreamingHttpChannel2023/StreamingHttpRequestHandler';
// Server/Notifications
export * from './server/notifications/ActivityEmitter';
export * from './server/notifications/BaseChannelType';
@ -615,6 +624,7 @@ export * from './util/GenericEventEmitter';
export * from './util/GuardedStream';
export * from './util/HeaderUtil';
export * from './util/IterableUtil';
export * from './util/Json';
export * from './util/PathUtil';
export * from './util/PromiseUtil';
export * from './util/QuadUtil';

View File

@ -1,4 +1,3 @@
/* eslint-disable unicorn/no-process-exit */
import { existsSync } from 'node:fs';
import type { WriteStream } from 'node:tty';
import type { IComponentsManagerBuilderOptions } from 'componentsjs';
@ -105,9 +104,9 @@ export class AppRunner {
let configs = input.config ?? [ '@css:config/default.json' ];
configs = (Array.isArray(configs) ? configs : [ configs ]).map(resolveAssetPath);
let componentsManager: ComponentsManager<any>;
let componentsManager: ComponentsManager<App | CliResolver>;
try {
componentsManager = await this.createComponentsManager<any>(loaderProperties, configs);
componentsManager = await this.createComponentsManager<App>(loaderProperties, configs);
} catch (error: unknown) {
this.resolveError(`Could not build the config files from ${configs.join(',')}`, error);
}
@ -145,6 +144,7 @@ export class AppRunner {
public runCliSync({ argv, stderr = process.stderr }: { argv?: CliArgv; stderr?: WriteStream }): void {
this.runCli(argv).catch((error): never => {
stderr.write(createErrorMessage(error));
// eslint-disable-next-line unicorn/no-process-exit
process.exit(1);
});
}
@ -203,6 +203,7 @@ export class AppRunner {
/**
* Retrieves settings from package.json or configuration file when
* part of an npm project.
*
* @returns The settings defined in the configuration file
*/
public async getPackageSettings(): Promise<undefined | Record<string, unknown>> {
@ -225,9 +226,9 @@ export class AppRunner {
return import(cssConfigPathJs) as Promise<Record<string, unknown>>;
}
// Finally try and read from the config.community-solid-server
// Finally try to read from the config.community-solid-server
// field in the root package.json
const pkg = await readJSON(packageJsonPath) as { config?: Record<string, any> };
const pkg = await readJSON(packageJsonPath) as { config?: Record<string, unknown> };
if (typeof pkg.config?.['community-solid-server'] === 'object') {
return pkg.config['community-solid-server'] as Record<string, unknown>;
}

View File

@ -14,6 +14,7 @@ import { Initializer } from './Initializer';
* Part of the dynamic pod creation.
* Reads the contents from the configuration storage, uses those values to instantiate ResourceStores,
* and then adds them to the routing storage.
*
* @see {@link ConfigPodManager}, {@link TemplatedPodGenerator}, {@link BaseUrlRouterRule}
*/
export class ConfigPodInitializer extends Initializer {

View File

@ -1,5 +1,4 @@
import type { Server } from 'node:http';
import { URL } from 'node:url';
import { promisify } from 'node:util';
import { getLoggerFor } from '../logging/LogUtil';
import { isHttpsServer } from '../server/HttpServerFactory';

View File

@ -19,7 +19,7 @@ export class YargsParameter {
* @param name - Name of the parameter. Corresponds to the first parameter passed to the `yargs.options` function.
* @param options - Options for a single parameter that should be parsed. @range {json}
*/
public constructor(name: string, options: Record<string, any>) {
public constructor(name: string, options: Record<string, unknown>) {
this.name = name;
this.options = options;
}
@ -76,8 +76,9 @@ export class YargsCliExtractor extends CliExtractor {
yArgv.check((args): boolean => {
for (const [ name, options ] of Object.entries(this.yargsArgOptions)) {
if (options.type !== 'array' && Array.isArray(args[name])) {
// eslint-disable-next-line ts/restrict-template-expressions
throw new Error(`Multiple values for --${name} (-${options.alias}) were provided where only one is allowed`);
throw new Error(
`Multiple values for --${name} (-${options.alias as string}) were provided where only one is allowed`,
);
}
}
return true;

View File

@ -18,7 +18,9 @@ enum ClusterMode {
/**
* Convert workers amount to {@link ClusterMode}
*
* @param workers - Amount of workers
*
* @returns ClusterMode enum value
*/
function toClusterMode(workers: number): ClusterMode {
@ -92,6 +94,7 @@ export class ClusterManager {
/**
* Check whether the CSS server was booted in single threaded mode.
*
* @returns True is single threaded.
*/
public isSingleThreaded(): boolean {
@ -100,6 +103,7 @@ export class ClusterManager {
/**
* Whether the calling process is the primary process.
*
* @returns True if primary
*/
public isPrimary(): boolean {
@ -108,6 +112,7 @@ export class ClusterManager {
/**
* Whether the calling process is a worker process.
*
* @returns True if worker
*/
public isWorker(): boolean {

View File

@ -11,8 +11,10 @@ export interface SingleThreaded {}
/**
* Convert an exported interface name to the properly expected Components.js type URI.
*
* @param componentsManager - The currently used ComponentsManager
* @param interfaceName - An interface name
*
* @returns A Components.js type URI
*/
export async function toComponentsJsType<T>(componentsManager: ComponentsManager<T>, interfaceName: string):
@ -38,6 +40,7 @@ Promise<string> {
/**
* Will list class names of components instantiated implementing the {@link SingleThreaded}
* interface while the application is being run in multithreaded mode.
*
* @param componentsManager - The componentsManager being used to set up the application
*/
export async function listSingleThreadedComponents<T>(componentsManager: ComponentsManager<T>): Promise<string[]> {

View File

@ -68,11 +68,13 @@ export interface V6MigrationInitializerArgs {
/**
* Storages for which all entries need to be removed.
*/
// eslint-disable-next-line ts/no-explicit-any
cleanupStorages: KeyValueStorage<string, any>[];
/**
* The storage that will contain the account data in the new format.
* Wrong typings to prevent Components.js typing issues.
*/
newAccountStorage: AccountLoginStorage<any>;
newAccountStorage: AccountLoginStorage<Record<string, never>>;
/**
* The storage that will contain the setup entries in the new format.
*/
@ -100,7 +102,7 @@ export class V6MigrationInitializer extends Initializer {
private readonly accountStorage: KeyValueStorage<string, Account | Settings>;
private readonly clientCredentialsStorage: KeyValueStorage<string, ClientCredentials>;
private readonly cleanupStorages: KeyValueStorage<string, any>[];
private readonly cleanupStorages: KeyValueStorage<string, unknown>[];
private readonly newAccountStorage: AccountLoginStorage<typeof STORAGE_DESCRIPTION>;
private readonly newSetupStorage: KeyValueStorage<string, string>;
@ -113,7 +115,7 @@ export class V6MigrationInitializer extends Initializer {
this.accountStorage = args.accountStorage;
this.clientCredentialsStorage = args.clientCredentialsStorage;
this.cleanupStorages = args.cleanupStorages;
this.newAccountStorage = args.newAccountStorage as AccountLoginStorage<typeof STORAGE_DESCRIPTION>;
this.newAccountStorage = args.newAccountStorage as unknown as AccountLoginStorage<typeof STORAGE_DESCRIPTION>;
this.newSetupStorage = args.newSetupStorage;
}
@ -147,7 +149,7 @@ export class V6MigrationInitializer extends Initializer {
].join(' '), resolve);
});
readline.close();
if (!/^y(?:es)?$/ui.test(answer)) {
if (!/^y(?:es)?$/iu.test(answer)) {
throw new Error('Stopping server as migration was cancelled.');
}
}

View File

@ -14,7 +14,7 @@ export class CombinedShorthandResolver extends ShorthandResolver {
}
public async handle(input: Record<string, unknown>): Promise<Record<string, unknown>> {
const vars: Record<string, any> = {};
const vars: Record<string, unknown> = {};
for (const [ name, computer ] of Object.entries(this.resolvers)) {
try {
vars[name] = await computer.handleSafe(input);

View File

@ -42,6 +42,7 @@ export function getLoggerFor(loggable: string | Instance): Logger {
/**
* Sets the global logger factory.
* This causes loggers created by {@link getLoggerFor} to delegate to a logger from the given factory.
*
* @param loggerFactory - A logger factory.
*/
export function setGlobalLoggerFactory(loggerFactory: LoggerFactory): void {

View File

@ -1,5 +1,4 @@
import cluster from 'node:cluster';
import process from 'node:process';
import type { LogLevel } from './LogLevel';
export interface LogMetadata {
@ -18,6 +17,7 @@ export interface SimpleLogger {
/**
* Log the given message at the given level.
* If the internal level is higher than the given level, the message may be voided.
*
* @param level - The level to log at.
* @param message - The message to log.
* @param meta - Optional metadata to include in the log message.
@ -34,6 +34,7 @@ export interface Logger extends SimpleLogger {
/**
* Log the given message at the given level.
* If the internal level is higher than the given level, the message may be voided.
*
* @param level - The level to log at.
* @param message - The message to log.
* @param meta - Optional metadata to include in the log message.
@ -42,6 +43,7 @@ export interface Logger extends SimpleLogger {
/**
* Log a message at the 'error' level.
*
* @param message - The message to log.
* @param meta - Optional metadata to include in the log message.
*/
@ -49,6 +51,7 @@ export interface Logger extends SimpleLogger {
/**
* Log a message at the 'warn' level.
*
* @param message - The message to log.
* @param meta - Optional metadata to include in the log message.
*/
@ -56,6 +59,7 @@ export interface Logger extends SimpleLogger {
/**
* Log a message at the 'info' level.
*
* @param message - The message to log.
* @param meta - Optional metadata to include in the log message.
*/
@ -63,6 +67,7 @@ export interface Logger extends SimpleLogger {
/**
* Log a message at the 'verbose' level.
*
* @param message - The message to log.
* @param meta - Optional metadata to include in the log message.
*/
@ -70,6 +75,7 @@ export interface Logger extends SimpleLogger {
/**
* Log a message at the 'debug' level.
*
* @param message - The message to log.
* @param meta - Optional metadata to include in the log message.
*/
@ -77,6 +83,7 @@ export interface Logger extends SimpleLogger {
/**
* Log a message at the 'silly' level.
*
* @param message - The message to log.
* @param meta - Optional metadata to include in the log message.
*/

View File

@ -6,6 +6,7 @@ import type { Logger } from './Logger';
export interface LoggerFactory {
/**
* Create a logger instance for the given label.
*
* @param label - A label that is used to identify the given logger.
*/
createLogger: (label: string) => Logger;

View File

@ -13,7 +13,7 @@ export class WinstonLogger extends BaseLogger {
this.logger = logger;
}
public log(level: LogLevel, message: string, meta?: any): this {
public log(level: LogLevel, message: string, meta?: unknown): this {
this.logger.log(level, message, meta);
return this;
}

View File

@ -1,3 +1,4 @@
import type { TransformableInfo } from 'logform';
import { createLogger, format, transports } from 'winston';
import type * as Transport from 'winston-transport';
import type { Logger, LogMetadata } from './Logger';
@ -33,7 +34,7 @@ export class WinstonLoggerFactory implements LoggerFactory {
format.timestamp(),
format.metadata({ fillExcept: [ 'level', 'timestamp', 'label', 'message' ]}),
format.printf(
({ level: levelInner, message, label: labelInner, timestamp, metadata: meta }: Record<string, any>): string =>
({ level: levelInner, message, label: labelInner, timestamp, metadata: meta }: TransformableInfo): string =>
`${timestamp} [${labelInner}] {${this.clusterInfo(meta as LogMetadata)}} ${levelInner}: ${message}`,
),
),

View File

@ -7,6 +7,7 @@ import type { PodSettings } from './settings/PodSettings';
export interface PodManager {
/**
* Creates a pod for the given settings.
*
* @param settings - Settings describing the pod.
* @param overwrite - If the creation should proceed if there already is a resource there.
*/

View File

@ -10,7 +10,7 @@ import type { ComponentsJsFactory } from './ComponentsJsFactory';
* but moduleState will be stored in between calls.
*/
export class BaseComponentsJsFactory implements ComponentsJsFactory {
private readonly options: IComponentsManagerBuilderOptions<any>;
private readonly options: IComponentsManagerBuilderOptions<unknown>;
public constructor(relativeModulePath = '../../../', logLevel = 'error') {
this.options = {
@ -21,7 +21,7 @@ export class BaseComponentsJsFactory implements ComponentsJsFactory {
};
}
private async buildManager(): Promise<ComponentsManager<any>> {
private async buildManager(): Promise<ComponentsManager<unknown>> {
const manager = await ComponentsManager.build(this.options);
this.options.moduleState = manager.moduleState;
return manager;
@ -29,16 +29,17 @@ export class BaseComponentsJsFactory implements ComponentsJsFactory {
/**
* Calls Components.js to instantiate a new object.
*
* @param configPath - Location of the config to instantiate.
* @param componentIri - Iri of the object in the config that will be the result.
* @param variables - Variables to send to Components.js
*
* @returns The resulting object, corresponding to the given component IRI.
*/
public async generate<T>(configPath: string, componentIri: string, variables: Record<string, any>):
public async generate<T>(configPath: string, componentIri: string, variables: Record<string, unknown>):
Promise<T> {
const manager = await this.buildManager();
await manager.configRegistry.register(configPath);
return await manager.instantiate(componentIri, { variables });
return manager.instantiate(componentIri, { variables });
}
}

View File

@ -185,8 +185,20 @@ export class BaseResourcesGenerator implements TemplatedResourcesGenerator {
data = await this.processFile(link, options);
metadata.contentType = link.contentType;
}
// Do not yield a container resource if it already exists
if (!isContainerIdentifier(link.identifier) || !await this.store.hasResource(link.identifier)) {
// Add metadata from .meta file if there is one
if (metaLink) {
const rawMetadata = await this.generateMetadata(metaLink, options);
if (rawMetadata.contentType) {
// Prevent having 2 content types
metadata.contentType = undefined;
}
metadata.setMetadata(rawMetadata);
this.logger.debug(`Adding metadata for ${metaLink.identifier.path}`);
}
const shouldYield = !isContainerIdentifier(link.identifier) || !await this.store.hasResource(link.identifier);
if (shouldYield) {
this.logger.debug(`Generating resource ${link.identifier.path}`);
yield {
identifier: link.identifier,
@ -194,20 +206,15 @@ export class BaseResourcesGenerator implements TemplatedResourcesGenerator {
};
}
// Add metadata from .meta file if there is one
if (metaLink) {
const rawMetadata = await this.generateMetadata(metaLink, options);
if (!rawMetadata.contentType) {
// Make sure this does not remove the content-type if none is explicitly defined
rawMetadata.contentType = metadata.contentType;
}
// Still need to yield metadata in case the actual resource is not being yielded.
// We also do this for containers as existing containers can't be edited in the same way.
if (metaLink && (!shouldYield || isContainerIdentifier(link.identifier))) {
const metaIdentifier = this.metadataStrategy.getAuxiliaryIdentifier(link.identifier);
const descriptionMeta = new RepresentationMetadata(metaIdentifier);
addResourceMetadata(rawMetadata, isContainerIdentifier(link.identifier));
addResourceMetadata(metadata, isContainerIdentifier(link.identifier));
this.logger.debug(`Generating resource ${metaIdentifier.path}`);
yield {
identifier: metaIdentifier,
representation: new BasicRepresentation(rawMetadata.quads(), descriptionMeta, INTERNAL_QUADS),
representation: new BasicRepresentation(metadata.quads(), metaIdentifier, INTERNAL_QUADS),
};
}
}

View File

@ -4,11 +4,12 @@
export interface ComponentsJsFactory {
/**
* Instantiates a new object using Components.js.
*
* @param configPath - Location of the config to instantiate.
* @param componentIri - Iri of the object in the config that will be the result.
* @param componentIri - IRI of the object in the config that will be the result.
* @param variables - Variables to send to Components.js
*
* @returns The resulting object, corresponding to the given component IRI.
*/
generate: <T>(configPath: string, componentIri: string, variables: Record<string, any>) => Promise<T>;
generate: <T>(configPath: string, componentIri: string, variables: Record<string, unknown>) => Promise<T>;
}

View File

@ -4,6 +4,7 @@ import type { ResourcesGenerator } from './ResourcesGenerator';
/**
* Generates resources with the given generator and adds them to the given store.
*
* @param settings - Settings from which the pod is being created.
* @param generator - Generator to be used.
* @param store - Store to be updated.

View File

@ -15,6 +15,7 @@ export interface ResourcesGenerator {
/**
* Generates resources with the given options.
* The output Iterable should be sorted so that containers always appear before their contents.
*
* @param location - Base identifier.
* @param options - Options that can be used when generating resources.
*

Some files were not shown because too many files have changed in this diff Show More