diff --git a/README.md b/README.md index 49f20ed776..c956f8d358 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,11 @@ This project is dual licensed under the AGPL V3 (see NOTICE) and commercial lice ## Setup ### Installing JDK + #### With sdkman A good way to manage JDK versions and install the correct version for OBP is [sdkman](https://sdkman.io/). If you have this installed then you can install the correct JDK easily using: + ``` sdk env install ``` @@ -368,11 +370,13 @@ OBP-API now operates exclusively as a backend API server. There is no configurat If your props file contains `server_mode`, you can safely remove it. The property is ignored. **Before:** + ```properties server_mode=apis ``` **After:** + ```properties # server_mode property removed - no configuration needed # OBP-API automatically runs in API-only mode @@ -540,6 +544,7 @@ hostname=https://your-api-server.com If not configured, the application will fail to start with error "OBP-00001: Hostname not specified". The OpenAPI documentation will show a single server entry using the configured hostname: + ```json "servers": [ {"url": "https://your-api-server.com", "description": "Back-end server"} @@ -683,17 +688,17 @@ If the email cannot be sent, returns 500 with the most specific OBP error code for the underlying cause. The exception chain (class name + message) is always appended after `Detail:` so the operator can diagnose without server logs: -| Failure | Status | Code | -|---|---|---| -| Caller has no email address | 400 | `OBP-30339 UserEmailAddressMissing` | -| `portal_external_url` unset | 500 | `OBP-10056 IncompleteServerConfiguration` | -| `mail.users.userinfo.sender.address` is default | 500 | `OBP-10056 IncompleteServerConfiguration` | -| SMTP rejected credentials | 500 | `OBP-30341 SmtpAuthenticationFailed` | -| TCP connect / host unreachable / timeout / DNS fail | 500 | `OBP-30342 SmtpConnectionFailed` | -| TLS / SSL handshake fail | 500 | `OBP-30343 SmtpTlsHandshakeFailed` | -| Recipient / From / message rejected by server | 500 | `OBP-30344 SmtpRecipientRejected` | -| Other Jakarta Mail protocol error | 500 | `OBP-30345 SmtpProtocolError` | -| Truly unknown | 500 | `OBP-30340 EmailSendingFailed` (fallback) | +| Failure | Status | Code | +| --------------------------------------------------- | ------ | ----------------------------------------- | +| Caller has no email address | 400 | `OBP-30339 UserEmailAddressMissing` | +| `portal_external_url` unset | 500 | `OBP-10056 IncompleteServerConfiguration` | +| `mail.users.userinfo.sender.address` is default | 500 | `OBP-10056 IncompleteServerConfiguration` | +| SMTP rejected credentials | 500 | `OBP-30341 SmtpAuthenticationFailed` | +| TCP connect / host unreachable / timeout / DNS fail | 500 | `OBP-30342 SmtpConnectionFailed` | +| TLS / SSL handshake fail | 500 | `OBP-30343 SmtpTlsHandshakeFailed` | +| Recipient / From / message rejected by server | 500 | `OBP-30344 SmtpRecipientRejected` | +| Other Jakarta Mail protocol error | 500 | `OBP-30345 SmtpProtocolError` | +| Truly unknown | 500 | `OBP-30340 EmailSendingFailed` (fallback) | Use this from APIManager (or a `curl` with appropriate auth headers) to confirm that signup / password-reset emails will be deliverable on this instance, @@ -718,7 +723,9 @@ Body: { "username": "alice", "email": "alice@example.com" } of whether the user exists, is already validated, the rate limit was hit, or the SMTP send failed: ```json - { "message": "If an unvalidated account exists for this username and email, a validation email has been sent." } + { + "message": "If an unvalidated account exists for this username and email, a validation email has been sent." + } ``` - **Rate-limited**: 3 attempts per email per hour, Redis-backed. Over-limit requests still get the same 201. @@ -980,3 +987,7 @@ Steps to add Spanish language: - add file `lift-core_es_ES.properties` at the folder `/resources/i18n` Please note that default translation file is `lift-core.properties` + +## Funding Note + +This repository has recived funding via The OGCR Project which has received funding from the European Union's Horizon Europe programme under grant agreement 101218854. diff --git a/obp-api/src/main/scala/code/api/util/NotificationUtil.scala b/obp-api/src/main/scala/code/api/util/NotificationUtil.scala index cc2e521606..a9bf4420aa 100644 --- a/obp-api/src/main/scala/code/api/util/NotificationUtil.scala +++ b/obp-api/src/main/scala/code/api/util/NotificationUtil.scala @@ -9,11 +9,19 @@ import net.liftweb.common.Box import scala.collection.immutable.List +import scala.concurrent.Future +import com.openbankproject.commons.ExecutionContext.Implicits.global object NotificationUtil extends MdcLoggable { def sendEmailRegardingAssignedRole(userId : String, entitlement: Entitlement): Unit = { - val user = Users.users.vend.getUserByUserId(userId) - sendEmailRegardingAssignedRole(user, entitlement) + // Fire-and-forget: the user lookup and the SMTP send both block, and the + // grant-entitlement response must not wait on them. + Future { + val user = Users.users.vend.getUserByUserId(userId) + sendEmailRegardingAssignedRole(user, entitlement) + }.failed.foreach(e => + logger.error(s"sendEmailRegardingAssignedRole says: failed for userId=$userId role=${entitlement.roleName}", e) + ) } def sendEmailRegardingAssignedRole(user: Box[User], entitlement: Entitlement): Unit = { val mailSent = for { @@ -32,7 +40,8 @@ object NotificationUtil extends MdcLoggable { subject = s"You have been granted the role: ${entitlement.roleName}", textContent = Some(bodyOfMessage) ) - //this is an async call + // Blocking SMTP send (Transport.send) — only call this off the request + // thread; the userId overload above wraps it in a Future. CommonsEmailWrapper.sendTextEmail(emailContent) } if(mailSent.isEmpty) { diff --git a/obp-api/src/main/scala/code/api/v6_0_0/Http4s600.scala b/obp-api/src/main/scala/code/api/v6_0_0/Http4s600.scala index 2bda440e6a..fbe05e4793 100644 --- a/obp-api/src/main/scala/code/api/v6_0_0/Http4s600.scala +++ b/obp-api/src/main/scala/code/api/v6_0_0/Http4s600.scala @@ -13208,6 +13208,8 @@ object Http4s600 { None, http4sPartialFunction = Some(getConfigProps) ) + // Intentional drift from Lift's APIMethods600.scala source-of-truth: + // description reworded to mention "config" explicitly (searchability), post-migration. resourceDocs += ResourceDoc( implementedInApiVersion, nameOf(getAppDirectory), @@ -13216,11 +13218,11 @@ object Http4s600 { "Get App Directory", s"""Get connectivity information for apps in the OBP ecosystem. | - |Returns configuration properties that apps (Portal, API Explorer, API Manager, + |Returns config (configuration) properties that apps (Portal, API Explorer, API Manager, |Sandbox Populator, OIDC, Keycloak, Hola, MCP, Opey) and agents can use to discover |endpoints in the OBP ecosystem. | - |Any props starting with public_ and ending with _url are included automatically. + |Any config props starting with public_ and ending with _url are included automatically. | |Known public app URL props: |${APIUtil.publicAppUrlPropNames.mkString(", ")}