From 30a94c42298638d4070bd24cfc9e14318faee1c9 Mon Sep 17 00:00:00 2001 From: Soeren Leibach Date: Fri, 15 May 2026 18:46:20 +0200 Subject: [PATCH] add authenticationIdentifier auto as default --- CHANGELOG.md | 2 + README.md | 8 +++- lib/utils.js | 21 ++++++---- tests/unit/lib/utils.test.js | 81 +++++++++++++++++++++++++++++++++++- 4 files changed, 102 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5d32b1..be7e2f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - Return the full HTTP response from the REST notification handler. Note: With outbox enabled (default), the application's `await notify()` resolves when the message is queued; the return value is only available when `outbox: false`. +- New default `auto` for `cds.env.requires.notifications.authenticationIdentifier`. Each recipient is inspected: UUID values are published with `GlobalUserId`, everything else with `RecipientId`, with a warning when a value is neither a UUID nor an email. The previous values `UserUUID` and `RecipientId` are still supported for an explicit choice. + In practice this means the plugin "just works" without configuration: applications can pass emails, UUIDs, or a mix of both, and the correct recipient key is chosen per value — no upfront configuration about Work Zone's authentication identifier required. ## Version 0.3.0 diff --git a/README.md b/README.md index 1fb8532..dfd44de 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,13 @@ To make notification types unique to the application, prefix is added to the typ ### Authentication Identifier -Depending on your Work Zone Notifications configuration, set `cds.env.requires.notifications.authenticationIdentifier` to `UserUUID` if the authenciation identifier in Work Zone is set to `User ID`. Notifications are then published with Recipient Key `GlobalUserId` instead of `RecipientId`. If not set, `RecipientId` is used. Note, that in order for E-Mail Notifications to be sent for notifications published with a User ID, a destination to the IDS needs to be configured for the lookup of the corresponding email address. +`cds.env.requires.notifications.authenticationIdentifier` controls which recipient key the plugin uses when publishing notifications. Supported values: + +- `auto` (default): the recipient key is chosen per recipient. Values matching the UUID format are published with `GlobalUserId`, everything else with `RecipientId`. If a value is neither a UUID nor an email a warning is logged. This allows mixing UUIDs and emails in the same `recipients` array without additional configuration. +- `UserUUID`: always publish with `GlobalUserId`. Use this when the authentication identifier in Work Zone is set to `User ID`. +- `RecipientId`: always publish with `RecipientId`. Use this when you want to enforce email recipients. + +Note, that in order for E-Mail Notifications to be sent for notifications published with a User ID, a destination to the IDS needs to be configured for the lookup of the corresponding email address. For the Work Zone Authentication Identifier configuration details refer to: [Work Zone Subaccount Settings](https://help.sap.com/docs/build-work-zone-standard-edition/sap-build-work-zone-standard-edition/subaccount-settings) diff --git a/lib/utils.js b/lib/utils.js index a377927..510defb 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -4,6 +4,8 @@ const cds = require("@sap/cds"); const LOG = cds.log('notifications'); const { getDestination } = require("@sap-cloud-sdk/connectivity"); const PRIORITIES = ["LOW", "NEUTRAL", "MEDIUM", "HIGH"]; +const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; +const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const messages = { TYPES_FILE_NOT_EXISTS: "Notification Types file path is incorrect.", @@ -116,13 +118,16 @@ async function getNotificationDestination() { return notificationDestination; } -function getRecipientKey() { - const authenticationIdentifier = cds.env.requires.notifications?.authenticationIdentifier; - var recipientKey = 'RecipientId'; // Email - if (authenticationIdentifier === "UserUUID") { - recipientKey = 'GlobalUserId'; +function getRecipientKey(recipient) { + const authenticationIdentifier = cds.env.requires.notifications?.authenticationIdentifier ?? 'auto'; + if (authenticationIdentifier === 'UserUUID') return 'GlobalUserId'; + if (authenticationIdentifier === 'RecipientId') return 'RecipientId'; + // 'auto' (and any unrecognized value): detect format per recipient + if (typeof recipient === 'string' && UUID_REGEX.test(recipient)) return 'GlobalUserId'; + if (typeof recipient === 'string' && !EMAIL_REGEX.test(recipient)) { + LOG._warn && LOG.warn(`Recipient '${recipient}' is neither a UUID nor an email format. Falling back to RecipientId.`); } - return recipientKey; + return 'RecipientId'; } let prefix // be filled in below... @@ -170,7 +175,7 @@ function buildDefaultNotification( NotificationTypeVersion: "1", Priority: priority, Properties: properties, - Recipients: recipients.map((recipient) => ({ [getRecipientKey()]: recipient })) + Recipients: recipients.map((recipient) => ({ [getRecipientKey(recipient)]: recipient })) }; } @@ -179,7 +184,7 @@ function buildCustomNotification(_) { // Properties with simple API variants NotificationTypeKey: getNotificationTypesKeyWithPrefix(_.NotificationTypeKey || _.type), - Recipients: _.Recipients || _.recipients?.map(id => ({ [getRecipientKey()]: id })), + Recipients: _.Recipients || _.recipients?.map(id => ({ [getRecipientKey(id)]: id })), Priority: _.Priority || _.priority || "NEUTRAL", Properties: _.Properties || Object.entries(_.data).map(([k,v]) => ({ Key:k, Value:v, Language: "en", Type: typeof v, // IsSensitive: false diff --git a/tests/unit/lib/utils.test.js b/tests/unit/lib/utils.test.js index 109e1ce..a1665d7 100644 --- a/tests/unit/lib/utils.test.js +++ b/tests/unit/lib/utils.test.js @@ -441,6 +441,9 @@ describe("Test utils", () => { }); describe("Configuration", () => { + const log = cds.test.log(); + afterEach(() => { delete cds.env.requires.notifications?.authenticationIdentifier; }); + it("Use GlobalUserId as the recipient key when authenticationIdentifier is set to UserUUID", () => { cds.env.requires.notifications ??= {}; cds.env.requires.notifications.authenticationIdentifier = "UserUUID"; @@ -450,10 +453,86 @@ describe("Test utils", () => { title: "Test Title" }); - delete cds.env.requires.notifications.authenticationIdentifier; expect(result.Recipients[0]).toMatchObject({ GlobalUserId: "user-uuid-123" }); }); + it("Auto mode picks GlobalUserId for UUID recipients", () => { + cds.env.requires.notifications ??= {}; + cds.env.requires.notifications.authenticationIdentifier = "auto"; + + const result = buildNotification({ + recipients: ["550e8400-e29b-41d4-a716-446655440000"], + title: "Test Title" + }); + + expect(result.Recipients[0]).toMatchObject({ GlobalUserId: "550e8400-e29b-41d4-a716-446655440000" }); + }); + + it("Auto mode picks RecipientId for email recipients", () => { + cds.env.requires.notifications ??= {}; + cds.env.requires.notifications.authenticationIdentifier = "auto"; + + const result = buildNotification({ + recipients: ["test.mail@mail.com"], + title: "Test Title" + }); + + expect(result.Recipients[0]).toMatchObject({ RecipientId: "test.mail@mail.com" }); + }); + + it("Auto mode supports mixed UUID and email recipients in one notification", () => { + cds.env.requires.notifications ??= {}; + cds.env.requires.notifications.authenticationIdentifier = "auto"; + + const result = buildNotification({ + recipients: ["550e8400-e29b-41d4-a716-446655440000", "test.mail@mail.com"], + title: "Test Title" + }); + + expect(result.Recipients).toEqual([ + { GlobalUserId: "550e8400-e29b-41d4-a716-446655440000" }, + { RecipientId: "test.mail@mail.com" } + ]); + }); + + it("Auto mode warns and falls back to RecipientId when value is neither UUID nor email", () => { + cds.env.requires.notifications ??= {}; + cds.env.requires.notifications.authenticationIdentifier = "auto"; + log.clear(); + + const result = buildNotification({ + recipients: ["not-a-uuid-or-email"], + title: "Test Title" + }); + + expect(result.Recipients[0]).toMatchObject({ RecipientId: "not-a-uuid-or-email" }); + expect(log.output).toContain("neither a UUID nor an email"); + }); + + it("Auto is the default when authenticationIdentifier is not configured", () => { + cds.env.requires.notifications ??= {}; + delete cds.env.requires.notifications.authenticationIdentifier; + + const result = buildNotification({ + recipients: ["550e8400-e29b-41d4-a716-446655440000"], + title: "Test Title" + }); + + expect(result.Recipients[0]).toMatchObject({ GlobalUserId: "550e8400-e29b-41d4-a716-446655440000" }); + }); + + it("Explicit RecipientId mode never resolves to GlobalUserId even for UUID values", () => { + cds.env.requires.notifications ??= {}; + cds.env.requires.notifications.authenticationIdentifier = "RecipientId"; + + const result = buildNotification({ + recipients: ["550e8400-e29b-41d4-a716-446655440000"], + title: "Test Title" + }); + + expect(result.Recipients[0]).toMatchObject({ RecipientId: "550e8400-e29b-41d4-a716-446655440000" }); + }); + it("Fall back to basename of cds.root as prefix when package.json cannot be read", () => { let result; jest.isolateModules(() => {