diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..f84f87b --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,64 @@ +# OneGround Developer Portal - AI Assistant Instructions + +## Project Overview + +This repository hosts the [dev.oneground.nl](https://dev.oneground.nl) developer portal, built with **Docusaurus 3** (React static site generator). It documents the implementation of Dutch case management APIs (ZGW - Zaakgericht Werken). + +## Technology Stack + +- **Framework**: Docusaurus 3.x +- **Language**: TypeScript (`.ts`, `.tsx`) +- **UI/Components**: React 19, Infima (CSS framework), custom CSS modules +- **Content**: Markdown/MDX (`.md`, `.mdx`) +- **Formatting**: Prettier + +## Critical Workflows + +- **Development Server**: `npm start` (Hot reloading enabled) +- **Production Build**: `npm build` (Outputs to `build/`) +- **Formatting**: `npm run format` + - **CRITICAL**: Always run this command before finishing a task. The CI/CD pipeline enforces strict formatting and will fail if code is not formatted with Prettier. + +## Project Structure & Architecture + +- **Configuration**: + - `docusaurus.config.ts`: Main site config, plugins, presets, and theme config. + - `sidebars.ts`: Documentation sidebar structure. +- **Content**: + - `docs/`: Main documentation files (Markdown/MDX). + - `blog/`: Blog posts (Markdown/MDX). + - `static/`: Static assets (images, robots.txt) - copied directly to build root. +- **Source Code**: + - `src/pages/`: React components that become standalone pages (e.g., `index.tsx` is the homepage). + - `src/components/`: Reusable React components. + - `src/css/custom.css`: Global custom styles (overrides Infima variables). + +## Coding Conventions + +- **Links**: When linking between documentation pages, use relative file paths (e.g., `[Link](./doc-file.md)`), not URL paths. Docusaurus resolves them automatically. +- **Sidebars**: New documentation pages must be added to `sidebars.ts` unless using autogenerated sidebars (this project uses manual definition). +- **Images**: Place specific images in `static/img` and reference them via `/img/...`. +- **React Components**: Use functional components with TypeScript. +- **Styling**: Prefer updating `src/css/custom.css` for global changes or using CSS modules/Infima utility classes for components. + +## Docusaurus Specifics + +- **Swizzle**: Use `npm run swizzle` only if you need to deeply customize internal Docusaurus components (avoid if possible). +- **Frontmatter**: Ensure valid frontmatter (id, title, description) in Markdown files. +- **Admonitions**: Use `:::note`, `:::tip`, `:::warning` syntax for callouts. + +## Common Tasks + +- **Adding a Doc**: + 1. Create file in `docs/`. + 2. Add entry to `sidebars.ts` in the appropriate category. + 3. Run `npm run format`. +- **Adding a Blog Post**: + 1. Create file in `blog/` with date prefix `YYYY-MM-DD-title.md`. + 2. Run `npm run format`. + +## Examples + +- **Sidebar Config**: See `sidebars.ts` for nested category structure. +- **Homepage**: See `src/pages/index.tsx`. +- **Config**: See `docusaurus.config.ts` for plugin and preset options. diff --git a/blog/2025-03-27-integrating-signing-software-with-zgw.md b/blog/2025-03-27-integrating-signing-software-with-zgw.md index 9dd6514..3bda489 100644 --- a/blog/2025-03-27-integrating-signing-software-with-zgw.md +++ b/blog/2025-03-27-integrating-signing-software-with-zgw.md @@ -59,7 +59,7 @@ The pattern described here is already implemented and used in production setting #### Authentication on the ZGW requests -The requests to the ZGW components use the standard authentication for the ZGW API: a JWT generated based on a ClientId and secret. See the [OneGround documentation](/docs/usage-of-clientids) or the general [ZGW documentation](https://vng-realisatie.github.io/gemma-zaken/themas/achtergronddocumentatie/authenticatie-autorisatie) for details. +The requests to the ZGW components use the standard authentication for the ZGW API: a JWT generated based on a ClientId and secret. See the [OneGround documentation](/docs/general/authentication) or the general [ZGW documentation](https://vng-realisatie.github.io/gemma-zaken/themas/achtergronddocumentatie/authenticatie-autorisatie) for details. #### Authentication on the trigger message @@ -73,7 +73,7 @@ Before the documents are presented to the signer, this person should authenticat To initiate the signing process, the TSA or ZAC should post a trigger message to the signing software in this format: -``` +```javascript { "naam": "...", // Name of signing transaction, can be displayed to the signer(s) by the signing software "eigenaar": "lisa@breda.dev", // e-mail of user that initiates the signing transaction @@ -121,7 +121,7 @@ Some details about properties that might not be clear from the description: If the trigger message is received correctly, the signing software should respond with status code 200 and this message: -``` +```jsonc { "transaction_id": "2opbDxqSB8BX3137ulqdw2q9_Mw=" // unique id of this signing transaction; format to be determined by the signing software } @@ -135,7 +135,7 @@ Note that signing documents is a very asynchronous sequence: it might be several When signing is completed successfully, the request to the NRC should be: -``` +```javascript { "kanaal": "documentacties", "hoofdObject": ""$url"", // ZRC url of the case @@ -151,7 +151,7 @@ When signing is completed successfully, the request to the NRC should be: When signing has failed, is cancelled or has otherwise not completed successfully, the request to the NRC should be: -``` +```javascript { "kanaal": "documentacties", "hoofdObject": ""$url"", // ZRC url of the case diff --git a/blog/2025-11-14-oauth2-token-endpoint.md b/blog/2025-11-14-oauth2-token-endpoint.md index ced1583..1346c73 100644 --- a/blog/2025-11-14-oauth2-token-endpoint.md +++ b/blog/2025-11-14-oauth2-token-endpoint.md @@ -131,7 +131,7 @@ By centralizing token creation, we ensure consistent security practices for all Of course, we understand that this may require some adjustments on each customer application, so we are still supporting legacy integration during this transition period. Additionally, we are expecting that in the future (like ZGW 2.x) self-signed tokens will be deprecated in favor of centralized OAuth2 flows. Our approach not only enhances security today but also ensures your integration remains compliant and robust for years to come. -You can read more about our implementation and how to use the OAuth2 Token Endpoint in our [ClientID Management and JWT Authentication in OneGround](../docs/usage-of-clientids) documentation. +You can read more about our implementation and how to use the OAuth2 Token Endpoint in our [ClientID Management and JWT Authentication in OneGround](../docs/general/authentication) documentation. ## Conclusion diff --git a/docs/authorization/ac-configuration.md b/docs/authorization/ac-configuration.md new file mode 100644 index 0000000..409f515 --- /dev/null +++ b/docs/authorization/ac-configuration.md @@ -0,0 +1,53 @@ +--- +title: "Configuration & Tooling" +description: "Guidance on using the OneGround configuration tool for Authorizations, including profiles and best practices." +keywords: ["configuration", "tooling", "authorizations", "profiles", "OneGround", "limitations"] +slug: /authorization/configuration +--- + +# Configuration & Tooling + +:::info OneGround Unlimited Only +The configuration tool described in this section is exclusive to **OneGround Unlimited** (the managed SaaS solution). It is not available in the open-source version of the components. +::: + +Authorizations in OneGround Unlimited are managed via the detailed configuration tool. This tool simplifies the assignment of permissions using predefined profiles and enforces specific best practices for stability. + +## Authorization Profiles + +Instead of manually configuring every granular permission scope, the tool utilizes **Authorization profiles**. These profiles perform two main functions: + +1. **Simplification**: They group logical sets of permissions together. +2. **Suggestion**: They guide you toward meaningful authorization sets for common use cases. + +## Best Practices + +### One Client ID per Application + +We strongly advise configuring only **one Client ID per Application**. + +- **Reason**: This establishes a clear 1-to-1 relationship between your configuration and the external consumer. +- **Benefit**: It allows you to adjust Authorizations for a specific application without unintended side effects on other consumers sharing the same resource. + +### Restrict Permissions (Least Privilege) + +Always restrict Authorizations as much as possible. + +- **Risk**: Granting broad access (e.g., 'all Authorizations') can have severe consequences, such as an application having the permission to **delete all cases**. +- **Advice**: Only assign the profiles strictly necessary for the application's function. + +## Tool Limitations & Design Decisions + +The OneGround configuration tool implements specific constraints to ensure system stability. + +### Single Client ID Enforcement + +While the ZGW API standard allows multiple Client IDs for a single Application entity, our tool restricts this to one. This decision was made to keep the configuration view simple and unambiguous for administrators. + +### Case Type Granularity + +The tool does **not** support restricting Authorizations to specific Case Types (Zaaktypes). + +- **Background**: The API standard supports this feature. +- **Issue**: In previous versions of our tooling, this granularity caused significant maintenance problems. Applications frequently lost access to cases when new versions of a Case Type were published, or conversely, could not access older versions. +- **Solution**: By omitting this granularity in the tool, we ensure consistent access across all versions of a case type, preventing "breaking changes" in permissions during functional updates. diff --git a/docs/authorization/ac-introduction.md b/docs/authorization/ac-introduction.md new file mode 100644 index 0000000..306530a --- /dev/null +++ b/docs/authorization/ac-introduction.md @@ -0,0 +1,52 @@ +--- +title: "Introduction to Authorizations" +description: "Overview of the ZGW Authorizations (AC) system, managing application permissions and access control." +keywords: [authorizations, introduction, AC, access control, ZGW, API, OneGround, permissions, ClientID] +slug: /authorization/introduction +--- + +# Introduction to Authorizations + +The Authorizations system (often referred to as **AC** or Authorizations Component) is a fundamental part of the ZGW API landscape provided by OneGround. It manages the permissions for client applications, ensuring that they can only access or modify the data they are authorized for within the ZGW APIs (such as Zaken or Documenten). + +## How it works + +The AC acts as the central authority for checking permissions when an API request is made. + +1. **API Call**: A client application makes a request to a ZGW API (e.g., creating a Case), including a **Bearer Token**. +2. **Identification**: The API validates the token and extracts the **Client ID**. +3. **Verification**: The API consults the **AC** to verify if the application associated with that Client ID has the necessary authorizations for the requested operation. +4. **Access**: If a valid authorization exists, the request is processed; otherwise, it is denied. + +## Getting Started + +To manage access for your applications, you typically use the OneGround configuration tool. The process involves: + +1. **Create an Application**: Define a logical application entity in the system. +2. **Assign Client ID**: Link a unique Client ID (from your identity provider) to the application. +3. **Configure Authorizations**: detailed permissions are assigned to the application. OneGround provides "Authorization profiles" to help select meaningful sets of permissions. + +> **Best Practice**: We advise using only **one Client ID per Application**. This creates a clear 1-to-1 relationship, making it easier to adjust authorizations for specific consumers without affecting others. + +## Key Concepts + +- **Applicatie (Application)**: A registry of a consumer system. In the ZGW standard, an application can have multiple Client IDs, but OneGround tooling enforces a simplified model. +- **Client ID**: The unique identifier found in the authentication token (JWT) that identifies the calling software. +- **Autorisatie (Authorization)**: A specific rule granting permission to perform certain actions (scopes) on certain resources. + +## Limitations and Advice + +When using the OneGround configuration tool, keep the following in mind: + +- **One Client ID per App**: While the standard allows multiple, the tool restricts this to simplify configuration and clarity. +- **Granularity**: The tool is designed to prevent "Authorization Hell". While the API technically supports very granular authorizations (e.g., specific case types), the tool focuses on robust, workable profiles to avoid issues where applications lose access to relevant data (e.g., versioning conflicts with case types). +- **Least Privilege**: Always restrict authorizations as much as possible. Giving an application "all Authorizations" might inadvertently grant permission to delete all cases. + +## Official Standards (VNG) + +The OneGround Authorizations system is implemented according to the standards defined by VNG Realisatie. The official standards provide the complete specification: + +- **[VNG Autorisaties Standard](https://vng-realisatie.github.io/gemma-zaken/standaard/autorisaties/)** + The core specification for the Authorizations API (Autorisaties services). It defines how applications and authorizations should be structured and queried. + +For further technical details on how to configure authorizations in OneGround, please refer to the specific configuration guides. diff --git a/docs/authorizations.md b/docs/authorizations.md deleted file mode 100644 index 288ef84..0000000 --- a/docs/authorizations.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: "Authorizations" -description: "Explanation of how authorizations, applications, and Client IDs function within OneGround, including configuration guidelines and tool limitations." -keywords: ["Authorizations", "OneGround", "ClientID", "Application", "ZGW", "Configuration", "Security"] ---- - -# Authorizations - -## How it works - -The ZGW standard has a function to create an application with multiple Client ID's. -The application has associated Authorizations. -When a client application calls a ZGW Api, it's bearer token is checked and the ClientID is extracted. If the token and ClientID are valid, the ZGW component looks up the associated Authorizations in the AC. - -## How to create applications/Authorizations - -Authorizations vcan be added using the configuration tool of OneGround. -We advice to use only one ClientID per Application so that you can adjust Authorizations per application if needed. -We also advice to restrict Authorizations as much as possible to prevent unwanted effects. For example, if an aplication has 'all Authorizations', it also has permission for deletion of all cases. - -The tool helps you in creating a meaningful Authorization by providing suggestions in the form of "Authorization profiles". - -## Limitations of the OneGround configuration tool - -Our tool only supports only one ClientID per application (whereas the api's allow multiple ClientID's) so that configuraton is as easy and clear as possible. -Also it does not provide the possibility (supported by the Api's) to add an Authorization per casetype. The reason is that this feature has shown many problems in previous versions of our tooling: applications could not access cases with newer (versions of) casetypes than configured, but also not of older versions. diff --git a/docs/catalogs/ztc-introduction.md b/docs/catalogs/ztc-introduction.md new file mode 100644 index 0000000..78eb007 --- /dev/null +++ b/docs/catalogs/ztc-introduction.md @@ -0,0 +1,46 @@ +--- +title: "Introduction to Catalogs" +description: "Overview of the ZGW Catalogs (ZTC) system, managing the definitions and blueprints for cases." +keywords: [catalogs, introduction, ZTC, ZGW, API, OneGround, zaaktypen, blueprints] +slug: /catalogs/introduction +--- + +# Introduction to Catalogs + +The Catalogs system (often referred to as **ZTC** or ZaakTypeCatalogus) is a foundational part of the ZGW API landscape provided by OneGround. It acts as the repository for all definitions (metadata) that describe how cases ("Zaken") and related objects should behave. + +## How it works + +Before an organization can work with cases, they must define _what_ those cases are. The ZTC stores these definitions. + +1. **Definition**: An administrator creates a **ZaakType** (Case Type) in the catalog. This defines properties like: + - What phases (Statustypen) the case goes through. + - What documents (InformatieObjectTypen) are relevant. + - What information is recorded (EigenschapTypen). +2. **Execution**: When a client application creates a new Case (via the Zaken API), it must refer to a specific **ZaakType** URL from the ZTC. +3. **Validation**: The Zaken API uses the definition in the ZTC to validate the case data and ensure the process follows the designed workflow. + +## Getting Started + +To effectively use the ZTC, you typically interact with a management interface or configuration tool to set up your catalogs. + +1. **Create a Catalog**: A logical grouping for your definitions (Catalogus). +2. **Design Case Types**: Define the blueprints for your business processes (e.g., "Building Permit Application", "Complaint", "Internal Review"). +3. **Publish**: Once a Case Type is finalized, it is published so it can be used by consumer applications. + +## Key Concepts + +- **Catalogus (Catalog)**: A collection of types. An organization can have one or more catalogs. +- **ZaakType (Case Type)**: The definition of a generic business process (e.g., "Vergunningaanvraag"). It determines the lifecycle and data structure of concrete Cases. +- **InformatieObjectType (Information Object Type)**: The definition of a document type (e.g., "ID Card Copy", "Decision Document"). +- **BesluitType (Decision Type)**: The definition of a specific decision that can be taken within a case. +- **StatusType**: Defines a possible state in the lifecycle of a case. + +## Official Standards (VNG) + +The OneGround Catalogs system is implemented according to the standards defined by VNG Realisatie. The official standards provide the complete specification: + +- **[VNG Catalogussen Standard](https://vng-realisatie.github.io/gemma-zaken/standaard/catalogi/)** + The core specification for the Catalogs API (Catalogi services). It defines the resources and relations for case types, document types, and more. + +For further technical details, please refer to the specific documentation pages in this section. diff --git a/docs/ztc1_3problemsandsolutions.md b/docs/catalogs/ztc1_3problemsandsolutions.md similarity index 100% rename from docs/ztc1_3problemsandsolutions.md rename to docs/catalogs/ztc1_3problemsandsolutions.md diff --git a/docs/documents/drc-introduction.md b/docs/documents/drc-introduction.md new file mode 100644 index 0000000..a35b741 --- /dev/null +++ b/docs/documents/drc-introduction.md @@ -0,0 +1,50 @@ +--- +title: "Introduction to Documents" +description: "Overview of the ZGW Documents (DRC) system, managing storage and retrieval of documents and metadata." +keywords: [documents, introduction, DRC, ZGW, API, OneGround, storage, files] +slug: /documents/introduction +--- + +# Introduction to Documents + +The Documents system (often referred to as **DRC** or Document Registration Component) is a crucial part of the ZGW API landscape provided by OneGround. It allows client applications to store, retrieve, and manage documents (EnkelvoudigInformatieObjecten) and their metadata in a standardized way. + +## How it works + +The DRC serves as the central register for documents within the application landscape. It handles both the binary content (the file itself) and the metadata describing it. + +1. **Creation**: A client application creates a document object, providing metadata (like title, confidentiality, author). +2. **Content Upload**: The actual file content is uploaded to the DRC. +3. **Storage**: The DRC securely stores the file and indexes the metadata. +4. **Linking**: The document is often linked to a process or object in another system (e.g., a "Zaak" in the ZRC). +5. **Retrieval**: Authorized applications and users can search for and download the document. + +## Getting Started + +To work with the Documents API, your application usually performs these high-level steps: + +1. **Locking (Optional)**: In some workflows, you might lock a document to prevent concurrent edits. +2. **Upload**: Use the API to upload the physical file and its metadata. +3. **Versioning**: Manage different versions of a document if supported/required. + +## Documentation Overview + +We have guides to help you integrate with the Documents system: + +- **[Example Document Upload](./example-document-upload)**: A step-by-step guide on how to upload a document to the API. + +## Key Concepts + +- **EnkelvoudigInformatieObject (EIO)**: The core resource representing a document. It contains metadata such as the title, creation date, and language. +- **Inhoud**: The physical content of the document (the byte stream). +- **Gebruiksrechten**: Metadata defining who is allowed to read, write, or destroy the document. +- **Vertrouwelijkheidaanduiding**: Indicates the confidentiality level of the document (e.g., Openbaar, Beperkt Openbaar, Vertrouwelijk). + +## Official Standards (VNG) + +The OneGround Documents system is implemented according to the standards defined by VNG Realisatie. While this documentation covers the specific implementation and usage within OneGround, the official standards provide the complete specification: + +- **[VNG Documenten API Standard](https://vng-realisatie.github.io/gemma-zaken/standaard/documenten/)** + The core specification for the Documents API. It defines the resources, behavior, and architecture for storing and managing information objects. + +For further technical details, please refer to the specific pages in this section. diff --git a/docs/example-document-upload/example-document-upload.md b/docs/documents/example-document-upload.md similarity index 100% rename from docs/example-document-upload/example-document-upload.md rename to docs/documents/example-document-upload.md diff --git a/docs/example-document-upload/examplepm.json b/docs/documents/examplepm.json similarity index 100% rename from docs/example-document-upload/examplepm.json rename to docs/documents/examplepm.json diff --git a/docs/api-versions.md b/docs/general/api-versions.md similarity index 100% rename from docs/api-versions.md rename to docs/general/api-versions.md diff --git a/docs/general/authentication.md b/docs/general/authentication.md new file mode 100644 index 0000000..f01e24f --- /dev/null +++ b/docs/general/authentication.md @@ -0,0 +1,107 @@ +--- +title: "Authentication" +sidebar_label: "Authentication" +description: "Learn how to obtain or generate JWT bearer tokens using ClientIDs for OneGround APIs in both Community and Unlimited editions." +keywords: ["ClientID", "JWT", "OneGround", "API", "Authentication", "ZGW"] +--- + +# Authentication + +## Overview + +Client applications require a JWT bearer token to authenticate themselves when calling an API on OneGround. The ZGW standard itself does not define a specific function or endpoint for retrieving such a token, so the method depends on the OneGround edition you are using. + +## Token Retrieval in OneGround Community Edition + +In the OneGround Community edition (Open Source), tokens are issued by Keycloak using OAuth2. Please refer to the **Getting Started** documentation for detailed instructions on setting up and retrieving tokens from Keycloak. + +## Token Retrieval in OneGround Unlimited + +For consumers of OneGround Unlimited, there is a dedicated endpoint available to return a token: + +| Method | Endpoint | +| :----- | :---------------------------------- | +| `POST` | `https:///token` | + +The request body must be sent as `multipart/form-data` and contain the following fields: + +- `client_id`: `` +- `client_secret`: `` +- `grant_type`: `client_credentials` + +Additionally, you can discover configuration details via the well-known endpoint: + +```text +https:///.well-known/openid-configuration +``` + +## Self-Generated Tokens + +To maintain compatibility with various ZGW-API consumers, OneGround also accepts self-generated tokens. Below is an example code snippet demonstrating how to generate such a token (based on a Postman script): + +```javascript +function base64url(source) { + // Encode in classical base64 + var encodedSource = CryptoJS.enc.Base64.stringify(source); + + // Remove padding equal characters + encodedSource = encodedSource.replace(/=+$/, ""); + + // Replace characters according to base64url specifications + encodedSource = encodedSource.replace(/\+/g, "-"); + encodedSource = encodedSource.replace(/\//g, "_"); + + return encodedSource; +} + +function addIAT(data) { + var iat = Math.floor(Date.now() / 1000); // Current time in seconds + // You might add a small offset (e.g. + 257) if clock skew is an issue, + // but standard claims usually start at current time. + + data.iat = iat; + data.nbp = iat; + data.exp = iat + 3600; // Expires in 1 hour + return data; +} + +// 1. Define Header +var header = { + typ: "JWT", + alg: "HS256" +}; + +// 2. Define Payload (Data) +var data = { + client_id: "client-12345", + rsin: "000000000", + jti: "49f1ddb1-3b4b-4a44-9739-013728b9a826", // Unique ID for this token + sub: "client-12345", + aud: "ZGW_APIs", + iss: "" +}; + +// Add Issued At, Not Before, and Expiration times +data = addIAT(data); + +var secret = ""; + +// 3. Encode Header +var stringifiedHeader = CryptoJS.enc.Utf8.parse(JSON.stringify(header)); +var encodedHeader = base64url(stringifiedHeader); + +// 4. Encode Payload +var stringifiedData = CryptoJS.enc.Utf8.parse(JSON.stringify(data)); +var encodedData = base64url(stringifiedData); + +// 5. Build Token Signature +var tokenContent = encodedHeader + "." + encodedData; + +var signature = CryptoJS.HmacSHA256(tokenContent, secret); +signature = base64url(signature); + +// 6. Concatenate to form the final JWT +var signedToken = tokenContent + "." + signature; + +console.log("Generated Token:", signedToken); +``` diff --git a/docs/version-header.md b/docs/general/version-header.md similarity index 100% rename from docs/version-header.md rename to docs/general/version-header.md diff --git a/docs/notifications/nrc-db-subscriptions.md b/docs/notifications/nrc-db-subscriptions.md new file mode 100644 index 0000000..558a577 --- /dev/null +++ b/docs/notifications/nrc-db-subscriptions.md @@ -0,0 +1,114 @@ +--- +title: "Explanation of the Notification Database Tables for Subscriptions" +description: "This document describes the subscription data model in the NRC database. It clarifies the usage of tables like kanalen, abonnementen, and filtervalues." +keywords: [NRC, database, subscriptions, notifications, data model, kanalen, abonnementen, filtervalues, ZGW, webhook] +slug: /notifications/notification-subscription-database-model +--- + +# Notification Database: Subscription Data Model + +This document outlines the subscription data model within the NRC database. + +**Goal:** To clarify the relationship between tables, specifically focusing on the `abonnementkanalen` table. While this table may appear to contain duplicate records, this design is intentional and necessary for the flexible functioning of notification filters. + +![ERD: Subscription Data Model](./nrc_db_subscriptions-1.png) + +--- + +## 1. Kanalen + +**OAS Resource:** [`kanaal`](https://vng-realisatie.github.io/gemma-zaken/standaard/notificaties/redoc-1.0.0#tag/kanaal/operation/kanaal_create) + +The `kanalen` table represents the source of notifications (e.g., 'zaken' or 'besluiten' in the ZRC). + +| Column | Description | +| :--------------------- | :---------------------------------------------------------------------------------------- | +| **`id`** | **Primary Key.** Unique identifier for the kanaal. | +| **`naam`** | The name of the channel (e.g., `zaken`, `besluiten`). | +| **`documentatielink`** | URL pointing to the documentation for this specific channel. | +| **`filters`** | A list of available attributes that can be used to filter subscriptions for this channel. | + +--- + +## 2. Abonnementen + +**OAS Resource:** [`abonnement`](https://vng-realisatie.github.io/gemma-zaken/standaard/notificaties/redoc-1.0.0#tag/abonnement/operation/abonnement_create) + +The `abonnementen` table stores the actual subscriptions for ZGW notifications received via POST requests. + +| Column | Description | +| :---------------- | :--------------------------------------------------------------------------- | +| **`id`** | **Primary Key.** Unique identifier for the subscription. | +| **`callbackurl`** | The full webhook URL of the application where the notification must be sent. | +| **`auth`** | The bearer token required by the webhook receiver for authorization. | +| **`owner`** | The RSIN of the organization granting access to the application. | + +--- + +## 3. Abonnementkanalen + +**OAS Element:** `kanalen` array within `abonnement`. + +This table establishes an **N:M (Many-to-Many)** relationship between `abonnementen` and `kanalen`. + +> **Key Concept:** The Primary Key (`id`) of this table is referenced by the `filtervalues` table. This architecture allows a single subscription to link to the _same_ `kanaal` multiple times. While this looks like duplicate data, it allows different sets of `filtervalues` to be applied to the same channel within one subscription. + +| Column | Description | +| :------------------ | :-------------------------------------------------------------------- | +| **`id`** | **Primary Key.** Referenced by `filtervalues`. | +| **`abonnement_id`** | **Foreign Key.** Points to the parent subscription in `abonnementen`. | +| **`kanaal_id`** | **Foreign Key.** Points to the source channel in `kanalen`. | + +--- + +## 4. Filtervalues + +**OAS Element:** `filters` element within `abonnement`. + +To prevent message flooding, subscriptions often restrict which notifications they receive. The `filtervalues` table stores these restrictions. + +| Column | Description | +| :------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **`id`** | **Primary Key.** | +| **`abonnement_kanaal_id`** | **Foreign Key.** Links to the `abonnementkanalen` table. | +| **`key`** | The filter name. Valid values are listed in `kanalen.filters`.
Special values include:
• `#resource`: The resource triggering the event.
• `#actie`: The event type (e.g., create, destroy). | +| **`value`** | The specific value to match against. | + +### Example Usage + +If you want to filter for a specific domain, the definition for the **zaken** channel might look like this: + +- **Key:** `domein` +- **Value:** `VTH` + +_Result: The notification is delivered only if the 'zaak' belongs to a 'zaaktype' within the 'VTH' domain._ + +--- + +## Logic: Combining Filters + +The data model supports complex filtering logic by combining multiple `filtervalues` and multiple `abonnementkanalen`. + +### The Logic Rule + +> 1. **Inside** a single `abonnementkanaal`: Filters are combined with **AND**. +> 2. **Between** different `abonnementkanalen` (for the same subscription): Logic is combined with **OR**. + +### Scenario: "Create OR Delete" + +A client application wants to receive notifications from the `zaken` channel _only_ when a `zaakinformatieobject` is **created** OR **deleted**. + +**Abonnementkanalen ID: 1** (The "Create" Condition) + +- Filter A: `key='#resource'`, `value='zaakinformatieobject'` +- Filter B: `key='#actie'`, `value='create'` +- _Logic: Resource is object **AND** action is create._ + +**Abonnementkanalen ID: 2** (The "Delete" Condition) + +- Filter A: `key='#resource'`, `value='zaakinformatieobject'` +- Filter B: `key='#actie'`, `value='destroy'` +- _Logic: Resource is object **AND** action is destroy._ + +**Final Result:** +If (Object created) **OR** (Object destroyed) then **Send Notification**. diff --git a/docs/notifications/nrc-introduction.md b/docs/notifications/nrc-introduction.md new file mode 100644 index 0000000..35f9409 --- /dev/null +++ b/docs/notifications/nrc-introduction.md @@ -0,0 +1,52 @@ +--- +title: "Introduction to Notifications" +description: "Overview of the ZGW Notifications (NRC) system, enabling real-time updates via webhooks." +keywords: [notifications, introduction, NRC, webhooks, ZGW, API, OneGround, subscription] +slug: /notifications/introduction +--- + +# Introduction to Notifications + +The Notifications system (often referred to as **NRC** or Notification Routing Component) is a crucial part of the ZGW API landscape provided by OneGround. It allows client applications to stay synchronized with the state of data within the ZGW APIs without the need for constant polling. + +## How it works + +When a relevant event occurs within a ZGW API (such as the creation of a Case/Zaak, or an update to a Document/EnkelvoudigInformatieObject), a notification is generated. The NRC is responsible for routing these notifications to the appropriate subscribers. + +1. **Events**: Changes occur in the source systems (OpenZaak, OpenNotificaties, etc.). +2. **Notifications**: A notification message is published. +3. **NRC**: The component enables the routing and delivery of these messages. +4. **Subscribers**: External applications receive these updates via **webhooks**. + +## Getting Started + +To receive notifications, your application needs to: + +1. **Expose a Webhook Endpoint**: A public or internally accessible URL that can accept HTTP POST requests containing the notification payload. +2. **Create a Subscription**: Register your interest in specific events (channels) and define filters to receive only relevant updates. + +## Documentation Overview + +We have detailed guides to help you integrate with the Notifications system: + +- **[Usage Guide](./nrc-subscriptions-use.md)**: Learn how to create subscriptions and filter messages for specific channels like Zaken or Documenten. +- **[Data Models](./nrc-db-subscriptions.md)**: Understand the underlying database structure for subscriptions if you need deep insight into how channels and filters are stored. +- **[Retries & Reliability](./nrc-retry-architecture.md)**: Information on how the system ensures delivery, including retry policies (Polly, Hangfire) and circuit breakers. + +## Key Concepts + +- **Kanaal (Channel)**: Represents a stream of messages, usually tied to a specific resource type (e.g., 'zaken', 'documenten'). +- **Abonnement (Subscription)**: The registration that links a receiving application (via a callback URL) to a channel and set of filters. +- **Notificatie**: The actual message payload describing the event. + +## Official Standards (VNG) + +The OneGround Notifications system is implemented according to the standards defined by VNG Realisatie. While this documentation covers the specific implementation and usage within OneGround, the official standards provide the complete specification: + +- **[VNG Notificaties Standard](https://vng-realisatie.github.io/gemma-zaken/standaard/notificaties/)** + The core specification for the Notifications API (Notificaties services). It defines the resources, behavior, and architecture of the notification system. + +- **[VNG Notificaties Consumer Guide](https://vng-realisatie.github.io/gemma-zaken/standaard/notificaties-consumer/)** + A guide specifically for "consumers" — applications that subscribe to and receive notifications. This resource provides sequence diagrams and rules for handling notifications correctly. + +For further technical details, please refer to the specific pages in this section. diff --git a/docs/notifications-architecture.md b/docs/notifications/nrc-retry-architecture.md similarity index 98% rename from docs/notifications-architecture.md rename to docs/notifications/nrc-retry-architecture.md index ca78964..af51867 100644 --- a/docs/notifications-architecture.md +++ b/docs/notifications/nrc-retry-architecture.md @@ -1,7 +1,8 @@ --- -title: "How does the retry system for notitifations work?" +title: "How does the retry system for notifications work?" description: "Overview of the Notifications system architecture, detailing retry strategies with Polly and Hangfire, circuit breaker patterns, and HTTP status code handling." keywords: [notifications, architecture, polly, hangfire, circuit breaker, retry, webhook, OneGround, ZGW] +slug: /notifications/notification-retry-architecture --- # How does the retry system for notifications work? diff --git a/docs/gebruik-van-subscriptions-in-autorisaties.md b/docs/notifications/nrc-subscriptions-use.md similarity index 99% rename from docs/gebruik-van-subscriptions-in-autorisaties.md rename to docs/notifications/nrc-subscriptions-use.md index c30dc14..4d35aee 100644 --- a/docs/gebruik-van-subscriptions-in-autorisaties.md +++ b/docs/notifications/nrc-subscriptions-use.md @@ -2,6 +2,7 @@ title: "Use of Subscriptions for Notifications" description: "A comprehensive guide on creating and managing subscriptions for ZGW API notifications, detailing filter options for various channels like Zaken, Besluiten, and Documenten." keywords: [subscriptions, notifications, filters, ZGW, API, webhooks, callbacks, Zaken, Besluiten, Documenten, OneGround, Roxit] +slug: /notifications/notification-subscription-usage-guide --- # Use of Subscriptions for Notifications diff --git a/docs/nrc_db_subscriptions-1.png b/docs/notifications/nrc_db_subscriptions-1.png similarity index 100% rename from docs/nrc_db_subscriptions-1.png rename to docs/notifications/nrc_db_subscriptions-1.png diff --git a/docs/nrc-db-subscriptions.md b/docs/nrc-db-subscriptions.md deleted file mode 100644 index 8a5ac43..0000000 --- a/docs/nrc-db-subscriptions.md +++ /dev/null @@ -1,84 +0,0 @@ -# Explanation of the Notification database tables for subscriptions - -This document describes the subscription data model in the NRC database. The aim is to clarify the usage of the tables and especially the purpose of table abonnementkanalen that seemingly contains double records. We will explain that these double records are necessary for proper functioning of notification. There are four relevant tables: kanalen, abonnementen, abonnementkanalen, and filtervalues. The following ERD shows their relationships: - -![alt text](./nrc_db_subscriptions-1.png) - -Below follows an explanation of each of the tables. - -## kanalen - -The 'kanalen' table corresponds to the ['kanaal' resource in the OAS](https://vng-realisatie.github.io/gemma-zaken/standaard/notificaties/redoc-1.0.0#tag/kanaal/operation/kanaal_create). It represents the source from which notifications originate, such as 'zaken' in the ZRC. -The key columns are: - -1. id: PrimaryKey of table 'kanalen' -2. naam: name of the 'kanaal', for example 'zaken' or 'besluiten' -3. documentatielink: the URL to documentation about the 'kanaal' -4. filters: List of possible filter attributes for a kanaal. These filter attributes can be used when creating a subscription. - -## abonnementen - -The 'abonnementen' table corresponds to the ['abonnement' resource in the OAS](https://vng-realisatie.github.io/gemma-zaken/standaard/notificaties/redoc-1.0.0#tag/abonnement/operation/abonnement_create). -Subscriptions on ZGW notifications received by POST request are stored in this table. The most important columns are: - -1. id: PrimaryKey of table abonnementen -2. callbackurl: the full URL of the application's webhook to which the notification should be sent -3. auth: bearer token of the webhook receiver for authorization -4. owner: rsin of the organization that gives the application access - -## abonnementkanalen - -The 'abonnementkanalen' table corresponds to the individual elements in the 'kanalen' array within 'abonnement' in the OAS. -The table thus establishes the N:M relationship between 'abonnementen' and 'kanalen', which is enabled by the OAS. The PrimaryKey (id) of the table is referenced by the filtervalues table. This allows for a single subscription to be related to the same 'kanaal' multiple times (seemingly double information), but with different filter values in the filtervalues table ​​each time. - -The key columns are: - -1. id: PrimaryKey of table 'abonnementkanalen' -2. abonnement_id: ForeignKey pointing to a abonnement in the 'abonnementen' table -3. kanaal_id: ForeignKey pointing to a kanaal in the 'kanalen' table - -## filtervalues - -The 'filtervalues' table corresponds to the 'filters' element within 'abonnement' in the OAS. - -Often, it is undesirable to receive all notifications from a kanaal, as this would generate a lot of message traffic. Therefore, it is possible to specify filters on fields from the underlying kanaal in the subscription. Only notifications that match these filter values are being sent thus reducing traffic. The filters are stored in the filtervalues table. - -The most important columns are: - -1. id: PrimaryKey of table 'filtervalues' -2. abonnement_kanaal_id: foreign key to 'abonnementkanalen' table -3. key: the name of the filter. Possible values are shown in the table 'kanalen' in column 'filters'. Additionally, the values '#resource' and '#actie' are possible in the key field. These refer to the events that trigger the notification to be sent by the resource of the kanaal. -4. value: the value of the filter to be matched. - -For example, a filter-value definition for the 'zaken' kanaal could look like this: - -- key='domein' -- value='VTH' - -In this example, the notification is only delivered if the zaak in question has a zaaktype from the catalog of domein 'VTH'. - -The data model allows for multiple filtervalues for each 'abonnementkanalen'. The example below illustrates this: - -Abonnementkanalen 1 of abonnement X -Filter A: - -- key='#resource' -- value='zaakinformatieobject' - -Filter B: - -- key='#actie' -- value='create' - -Abonnementkanalen 2 of abonnement X -Filter A: - -- key='#resource' -- value='zaakinformatieobject' - -Filter B: - -- key='#actie' -- value='destroy' - -The combination of filters within an abonnementkanaal is evaluated as 'AND'. The combination of filters among abonnementkanalen of the same abonnement is evaluated as 'OR'. In this example therefore, the client application will only receive a notification from the zaken kanaal when a zaakinformatieobject is created or deleted. diff --git a/docs/usage-of-clientids.md b/docs/usage-of-clientids.md deleted file mode 100644 index 7891975..0000000 --- a/docs/usage-of-clientids.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -title: "Usage of ClientIDs" -description: "Learn how to obtain or generate JWT bearer tokens using ClientIDs for OneGround APIs in both Community and Unlimited editions." -keywords: ["ClientID", "JWT", "OneGround", "API", "Authentication", "ZGW"] ---- - -# ClientID's - -## What you need to know - -Client applications need a JWT bearer token to identify themselves when they are calling an API on OneGround. The ZGW standard does not define a function for retrieving such a token. - -## How to get a token in Oneground Community edition - -In OneGround Community edition (Open Source) the token is delivered by Keycloak using OAuth2. See the documentation of Getting-Started for more details. - -## How to get a token in Oneground Unlimited - -For consumers of OneGround unlimited there is an endpoint that returns a token: - -``` - [POST] https:///token -``` - -The body (multipart/formdata) must contain: - -``` - client_id: - client_secret: - grant_type: client_credentials -``` - -In addition there is a well-known endpoint: - -``` - https:///.well-known/openid-configuration -``` - -## How to generate a token - -In order to be compatible with many ZGW-API consumers, OneGround accepts self generated tokens. Here is an example code snippet for generating such a token (taken from Postman): - -```js -function base64url(source) { - // Encode in classical base64 - encodedSource = CryptoJS.enc.Base64.stringify(source); - - // Remove padding equal characters - encodedSource = encodedSource.replace(/=+$/, ""); - - // Replace characters according to base64url specifications - encodedSource = encodedSource.replace(/\+/g, "-"); - encodedSource = encodedSource.replace(/\//g, "_"); - - return encodedSource; -} - -function addIAT(request) { - var iat = Math.floor(Date.now() / 1000) + 257; - data.iat = iat; - data.nbp = iat; - data.exp = iat + 3600; - return data; -} - -var header = { - typ: "JWT", - alg: "HS256" -}; - -var data = { - client_id: "client-12345", - rsin: "000000000", - jti: "49f1ddb1-3b4b-4a44-9739-013728b9a826", - sub: "client-12345", - aud: "ZGW_APIs", - iss: "" -}; -data = addIAT(data); - -var secret = ""; - -// encode header -var stringifiedHeader = CryptoJS.enc.Utf8.parse(JSON.stringify(header)); -var encodedHeader = base64url(stringifiedHeader); - -// encode data -var stringifiedData = CryptoJS.enc.Utf8.parse(JSON.stringify(data)); -var encodedData = base64url(stringifiedData); - -// build token -var token = encodedHeader + "." + encodedData; - -var signature = CryptoJS.HmacSHA256(token, secret); -signature = base64url(signature); - -var signedToken = token + "." + signature; -``` diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 1403eed..4853e9b 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -248,32 +248,37 @@ const config: Config = { to: "/changelog" }, { - from: ["/About/ApiVersions.html", "/About/ApiVersions"], - to: "/docs/api-versions" + from: ["/About/ApiVersions.html", "/About/ApiVersions", "/docs/api-versions"], + to: "/docs/general/api-versions" }, { - from: ["/About/ClientID.html", "/About/ClientID"], - to: "/docs/usage-of-clientids" + from: ["/About/ClientID.html", "/About/ClientID", "/docs/usage-of-clientids"], + to: "/docs/general/authentication" }, { - from: ["/About/Authorisation.html", "/About/Authorisation"], - to: "/docs/authorizations" + from: ["/About/Authorisation.html", "/About/Authorisation", "/docs/authorizations"], + to: "/docs/authorization/introduction" }, { - from: ["/About/VersionHeader.html", "/About/VersionHeader"], - to: "/docs/version-header" + from: ["/About/VersionHeader.html", "/About/VersionHeader", "/docs/version-header"], + to: "/docs/general/version-header" }, { from: [ "/About/exampledocumentupload.html", "/About/exampledocumentupload", - "/About/example-document-upload/examplepm.json" + "/About/example-document-upload/examplepm.json", + "/docs/example-document-upload" ], - to: "/docs/example-document-upload" + to: "/docs/documents/example-document-upload" }, { - from: ["/About/ztc1_3problemsandsolutions.html", "/About/ztc1_3problemsandsolutions"], - to: "/docs/ztc1_3problemsandsolutions" + from: [ + "/About/ztc1_3problemsandsolutions.html", + "/About/ztc1_3problemsandsolutions", + "/docs/ztc1_3problemsandsolutions" + ], + to: "/docs/catalogs/ztc1_3problemsandsolutions" }, { from: [ @@ -281,6 +286,10 @@ const config: Config = { "/About/Articles/20241203_Best-Practices-for-JWT-Usage-in-APIs.html" ], to: "/blog/best-practices-for-jwt-usage-in-apis" + }, + { + from: ["/docs/gebruik-van-subscriptions-in-autorisaties"], + to: "/docs/notifications/notification-subscription-usage-guide" } ] } diff --git a/package-lock.json b/package-lock.json index fda6a37..0cb74cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,8 @@ "@docusaurus/module-type-aliases": "3.9.2", "@docusaurus/tsconfig": "3.9.2", "@docusaurus/types": "3.9.2", - "prettier": "^3.8.0", + "baseline-browser-mapping": "^2.9.17", + "prettier": "^3.8.1", "typescript": "~5.9.3" }, "engines": { @@ -5769,9 +5770,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.24", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.24.tgz", - "integrity": "sha512-uUhTRDPXamakPyghwrUcjaGvvBqGrWvBHReoiULMIpOJVM9IYzQh83Xk2Onx5HlGI2o10NNCzcs9TG/S3TkwrQ==", + "version": "2.9.17", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.17.tgz", + "integrity": "sha512-agD0MgJFUP/4nvjqzIB29zRPUuCF7Ge6mEv9s8dHrtYD7QWXRcx75rOADE/d5ah1NI+0vkDl0yorDd5U852IQQ==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -14694,9 +14695,9 @@ } }, "node_modules/prettier": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.0.tgz", - "integrity": "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", "bin": { @@ -17742,9 +17743,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/package.json b/package.json index 3757e6a..3f218f3 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "@docusaurus/module-type-aliases": "3.9.2", "@docusaurus/tsconfig": "3.9.2", "@docusaurus/types": "3.9.2", - "prettier": "^3.8.0", + "baseline-browser-mapping": "^2.9.17", + "prettier": "^3.8.1", "typescript": "~5.9.3" }, "browserslist": { diff --git a/sidebars.ts b/sidebars.ts index 7985d01..dc60dcb 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -2,33 +2,103 @@ import type { SidebarsConfig } from "@docusaurus/plugin-content-docs"; const sidebars: SidebarsConfig = { docs: [ - "about-oneground", - "api-versions", - "usage-of-clientids", - "authorizations", { type: "doc", - id: "nrc-db-subscriptions", - label: "Notification database tables for subscriptions" + id: "about-oneground", + label: "About OneGround" }, { - type: "doc", - id: "gebruik-van-subscriptions-in-autorisaties", - label: "Use of Subscriptions for Notifications" + type: "category", + label: "General", + collapsed: false, + items: [ + { + type: "doc", + id: "general/authentication", + label: "Authentication" + }, + { + type: "doc", + id: "general/api-versions", + label: "Supported API Versions" + }, + { + type: "doc", + id: "general/version-header", + label: "Version Headers" + } + ] }, { - type: "doc", - id: "notifications-architecture", - label: "How does the retry system for notitifations work?" + type: "category", + label: "Authorization (AC)", + link: { + type: "doc", + id: "authorization/ac-introduction" + }, + items: [ + { + type: "doc", + id: "authorization/ac-configuration", + label: "Configuration & Tooling" + } + ] + }, + { + type: "category", + label: "Catalogs (ZTC)", + link: { + type: "doc", + id: "catalogs/ztc-introduction" + }, + items: [ + { + type: "doc", + id: "catalogs/ztc1_3problemsandsolutions", + label: "ZTC 1.3 Compliance" + } + ] }, - "version-header", - "example-document-upload/example-document-upload", - "ztc1_3problemsandsolutions" - // { - // type: 'category', - // label: 'Category', - // items: ['usage-of-clientids'], - // }, + { + type: "category", + label: "Notifications (NRC)", + link: { + type: "doc", + id: "notifications/nrc-introduction" + }, + items: [ + { + type: "doc", + id: "notifications/nrc-subscriptions-use", + label: "Usage Guide" + }, + { + type: "doc", + id: "notifications/nrc-db-subscriptions", + label: "Data Models" + }, + { + type: "doc", + id: "notifications/nrc-retry-architecture", + label: "Retries & Reliability" + } + ] + }, + { + type: "category", + label: "Documents (DRC)", + link: { + type: "doc", + id: "documents/drc-introduction" + }, + items: [ + { + type: "doc", + id: "documents/example-document-upload", + label: "Document Upload Guide" + } + ] + } ] };