Overview
NotifyBC is a general purpose API Server to manage subscriptions and dispatch notifications. It aims to implement some common backend processes of a notification service without imposing any constraints on the UI frontend, nor impeding other server components' functionality. This is achieved by interacting with user browser and other server components through RESTful API and other standard protocols in a loosely coupled way.
Features
NotifyBC facilitates both anonymous and authentication-enabled secure webapps implementing notification feature. A NotifyBC server instance supports multiple notification services. A service is a topic of interest that user wants to receive updates. It is used as the partition of notification messages and user subscriptions. A user may subscribe to a service in multiple push delivery channels allowed. A user may subscribe to multiple services. In-app pull notification doesn't require subscription as it's not intrusive to user.
notification
- both in-app pull notifications (a.k.a. messages or alerts) and push notifications
- multiple push notifications delivery channels
- sms
- unicast and broadcast message types
- future-dated notifications
- for in-app pull notifications
- support read and deleted message states
- message expiration
- deleted messages are not deleted immediately for auditing and recovery purposes
- for broadcast push notifications
- allow both sync and async POST API calls. For async API call, an optional callback url is supported
- can be auto-generated from RSS feeds
- allow user to specify filter rules evaluated against data contained in the notification
- allow sender to specify filter rules evaluated against data contained in the subscription
- allow application developer to create custom filter functions used by the filter rules mentioned above
subscription and un-subscription
- verify the ownership of push notification subscription channel:
- generates confirmation code based on a regex input
- send confirmation request to unconfirmed subscription channel
- verify confirmation code
- generate random un-subscription code
- send acknowledgement message after un-subscription for anonymous subscribers
- bulk unsubscription
- list-unsubscribe by email
- track bounces and unsubscribe the recipient from all subscriptions when hard bounce count exceeds threshold
- sms user can unsubscribe by replying a shortcode keyword with Swift sms provider
mail merge
Strings in notification or subscription message that are enclosed between curly braces { } are called tokens, also known as placeholders. Tokens are replaced based on the context of notification or subscription when dispatching the message. To avoid treating a string between curly braces as a token, escape the curly braces with backslash \. For example \{i_am_not_a_token\} is not a token. It will be rendered as {i_am_not_a_token}.
Tokens whose names are predetermined by NotifyBC are called static tokens; otherwise they are called dynamic tokens.
static tokens
NotifyBC recognizes following case-insensitive static tokens. Most of the names are self-explanatory.
- {subscription_confirmation_url}
- {subscription_confirmation_code}
- {service_name}
- {http_host} - http host in the form http(s): //<host_name>:<port>. The value is obtained from the http request that triggers the message
- {rest_api_root} - REST API URL path prefix
- {subscription_id}
- anonymous unsubscription related tokens
- {unsubscription_url}
- {unsubscription_all_url} - url to unsubscribe all services the user has subscribed on this NotifyBC instance
- {unsubscription_code}
- {unsubscription_reversion_url}
- {unsubscription_service_names} - includes {service_name} and additional services user has unsubscribed, prefixed with conditionally pluralized word service.
dynamic tokens
Dynamic tokens are replaced with correspondingly named sub-field of data field in the notification or subscription if exist. Qualify token name with notification:: or subscription:: to indicate the source of substitution. If token name is not qualified, then both notification and subscription are checked, with notification taking precedence. Nested and indexed sub-fields are supported.
Examples
- {notification::description} is replaced with field data.description of the notification if exist
- {subscription::gender} is replaced with field data.gender of the subscription if exist
- {addresses[0].city} is replaced with field data.addresses[0].city of the notification if exist; otherwise is replaced with field data.addresses[0].city of the subscription if exist
- {nonexistingDataField} is unreplaced if neither notification nor subscription contains data.nonexistingDataField
As exception, in order to prevent spamming by unconfirmed subscribers, dynamic tokens in subscription confirmation request message and duplicated subscription message are not replaced with subscription data, for example {subscription::...} tokens are left unchanged.
Notification by RSS feeds relies on dynamic token
A notification created by RSS feeds relies on dynamic token to supply the context to message template. In this case the data field contains the RSS item.
Architecture
Request Types
NotifyBC, designed to be a microservice, doesn't use full-blown ACL to secure API calls. Instead, it classifies incoming requests into admin and user types. The key difference is while both admin and user can subscribe to notifications, only admin can post notifications.
Each type has two subtypes based on following criteria
super-admin, if the request meets both of the following two requirements
The request carries one of the following two attributes
- the source ip is in the admin ip list
- has a client certificate that is signed using NotifyBC server certificate. See Client certificate authentication on how to sign.
The request doesn't contain any of following case insensitive HTTP headers, with the first three being SiteMinder headers
- sm_universalid
- sm_user
- smgov_userdisplayname
- is_anonymous
admin, if the request is not super-admin and meets one of the following criteria
- has a valid access token associated with an builtin admin user created and logged in using the administrator api, and the request doesn't contain any HTTP headers listed above
- has a valid OIDC access token containing customizable admin profile attributes
access token disambiguation
Here the term access token has been used to refer two different things
- the token associated with a builtin admin user
- the token generated by OIDC provider.
To reduce confusion, throughout the documentation the former is called access token and the latter is called OIDC access token.
authenticated user, if the request is neither super-admin nor admin, and meets one fo the following criteria
- contains any of the 3 SiteMinder headers listed above, and comes from either trusted SiteMinder proxy or admin ip list
- contains a valid OIDC access token
anonymous user, if the request doesn't meet any of the above criteria
The only extra privileges that a super-admin has over admin are that super-admin can perform CRUD operations on configuration, bounce and administrator entities through REST API. In the remaining docs, when no further distinction is necessary, an admin request refers to both super-admin and admin request; a user request refers to both authenticated and anonymous request.
An admin request carries full authorization whereas user request has limited access. For example, a user request is not allowed to
- send notification
- bypass the delivery channel confirmation process when subscribing to a service
- retrieve push notifications through API (can only receive notification from push notification channel such as email)
- retrieve in-app notifications that is not targeted to the current user
The result of an API call to the same end point may differ depending on the request type. For example, the call GET /notifications without a filter will return all notifications to all users for an admin request, but only non-deleted, non-expired in-app notifications for authenticated user request, and forbidden for anonymous user request. Sometimes it is desirable for a request from admin ip list, which would normally be admin request, to be voluntarily downgraded to user request in order to take advantage of predefined filters such as the ones described above. This can be achieved by adding one of the HTTP headers listed above to the request. This is also why admin request is not determined by ip or token alone.
The way NotifyBC interacts with other components is diagrammed below.
Authentication Strategies
API requests to NotifyBC can be either anonymous or authenticated. As alluded in Request Types above, NotifyBC supports following authentication strategies
- ip whitelisting
- client certificate
- access token associated with an builtin admin user
- OpenID Connect (OIDC)
- CA SiteMinder
Authentication is performed in above order. Once a request passed an authentication strategy, the rest strategies are skipped. A request that failed all authentication strategies is anonymous.
The mapping between authentication strategy and request type is
Admin | User | |||
Super-admin | admin | authenticated | anonymous | |
ip whitelisting | ✔ | |||
client certifcate | ✔ | |||
access token | ✔ | |||
OIDC | ✔ | ✔ | ||
SiteMinder | ✔ |
Which authentication strategy to use?
Because ip whitelist doesn't expire and client certificate usually has a relatively long expiration period (say one year), they are suitable for long-running unattended server processes such as server-side code of web apps, cron jobs, IOT sensors etc. The server processes have to be trusted because once authenticated, they have full privilege to NotifyBC. Usually the server processes and NotifyBC instance are in the same administrative domain, i.e. managed by the same admin group of an organization.
By contrast, OIDC and SiteMinder use short-lived tokens or session cookies. Therefore they are only suitable for interactive user sessions.
Access token associated with an builtin admin user should be avoided whenever possible.
Here are some common scenarios and recommendations
For server-side code of web apps
- use OIDC if the web app is OIDC enabled and user requests can be proxied to NotifyBC by web app; otherwise
- use ip whitelisting if obtaining ip is feasible; otherwise
- use client certificate (requires a little more config than ip whitelisting)
For front-end browser-based web apps such as SPAs
- use OIDC
For server apps that send requests spontaneously such as IOT sensors, cron jobs
- use ip whitelisting if obtaining ip is feasible; otherwise
- client certificate
If NotifyBC is ued by a SiteMinder protected web apps and NotifyBC is also protected by SiteMinder
- use SiteMinder
Application Framework
NotifyBC is created on NestJS. Contributors to source code of NotifyBC should be familiar with NestJS. NestJS Docs serves a good complement to this documentation.