# Technical documentation — lambda (IM Capital backend)

Evidence date: 2026-09-28. Revision: `master` `dfb4dca` (2024-02-02).

Credential values are omitted. Parameter names, hosts, and resource ids below are taken from source.

## What this repository is

A single Bitbucket repository with three serverless backends for **IM Capital**, plus a SonarQube pipeline. There is one branch, `master`. About 5,028 tracked files. 4,846 of those are under `SAM/IM-CAPITAL-BACKEND-SERVICE/.aws-sam/` (SAM build output committed to git).

`sonarqube_scan.bat` was listed by the onboarding scan and is not in git.

## Repository layout

```text
lambda/
  serverless.yml                 Node service IM-CAPITAL-BACKEND-SERVICE
  handler.js                     GET /getToken
  token_check.js                 GET /getTokenCheck
  sanlam_statement_data.js       GET /getStatements
  sanlam_subgroup_members.js     GET /getMembers
  package.json                   axios, express, https, qs, serverless-http
  test/token_check.json          Local invoke fixture (contains a bearer token)
  bitbucket-pipelines.yml
  sonar-project.properties
  Java/IM-CAPITAL-BACKEND-SERVICE-JAVA/    Kotlin, Gradle, Serverless Java 11
  SAM/IM-CAPITAL-BACKEND-SERVICE/
    template.yaml                SAM transform, Java 17
    samconfig.toml               dev and prod deploy profiles
    README.md                    AWS Hello World sample text
    IM-CAPITAL-BACKEND-SERVICE/  Maven module, handlers and functions
    .aws-sam/                    Committed build tree
```

Java packages under the Maven module:

- One package per route (`GetSettings`, `SanlamPurchaseFlow`, `InitiatePayment`, and the rest of the route table).
- `functions/` — shared JDBC, SSM, Secrets Manager, I&M HTTP, encryption, SQL.
- `SanlamFunctions/` — Sanlam HTTP calls whose URLs come from SSM.
- `helloworld/` — `GatewayResponse` used by the handlers, plus an unrouted `App`.

The update-transaction package directory is `UpadteTransaction`.

## Languages and frameworks

| Tree | Language | Runtime | Tooling |
|---|---|---|---|
| Root | Node.js, CommonJS | `nodejs18.x` | Serverless Framework 3, `serverless-offline` 12 |
| `Java/` | Kotlin 1.3.71 / 1.3.72 | `java11` | Gradle 6 wrapper, Shadow plugin 6, Serverless Framework 3 |
| `SAM/` | Java | Lambda `java17` | Maven compiler source/target 1.8, Shade plugin 3.2.4, SAM CLI |

SAM README still says install Java 8. The template runtime is Java 17. Provenance: **Documented** versus **Implemented**. Which JDK is used in deployment is **Unknown / Requires Confirmation**.

Node dependencies that the handlers do not import: `express`, `serverless-http`, npm package `https`.

Kotlin dependencies include `software.amazon.awscdk:core:1.139.0`. No CDK app was found. Provenance: **Implemented** as a Gradle dependency only.

## AWS account and network

Region `eu-west-1` in all three stacks.

Account `597707462019`.

Role ARN `arn:aws:iam::597707462019:role/IMCapital_serverless_deployment` is the function role. The inline SAM statements only add EC2 network-interface actions. Permissions for Secrets Manager, SSM, and S3 are not in the template. They would have to be on that role. Provenance: **Unknown / Requires Confirmation** for the role policy.

VPC, same in Node, Kotlin, and SAM:

- Security group `sg-0089502251d79aa23`
- Subnets `subnet-08882af1374cdd5d2`, `subnet-0b9b9e77b66be7a4d`

SAM function globals: timeout 30 seconds, memory 1536 MB, architecture `x86_64`.

Deployment buckets:

- `imcapital-dev-lambda-deployment` (Node, Kotlin, SAM dev, SSE AES256 on the Serverless buckets)
- `imcapital-prod-lambda-deployment` (`samconfig.toml` prod profile only)

S3 log bucket `imcapital-dev-app-logs` is a string in `WriteLogsToS3.java`.

## Environments

`template.yaml` parameter `Environment` allows `dev` and `prod`, default `dev`.

`samconfig.toml`:

| Profile | Stack | Parameter |
|---|---|---|
| `default` and `dev` | `IM-CAPITAL-BACKEND-DEV` | `Environment=dev` |
| `prod` | `IM-CAPITAL-BACKEND-PROD` | `Environment=prod` |

Deploy flags in the default profile: `confirm_changeset = true`, `resolve_s3 = true`, `disable_rollback = true`, `capabilities = CAPABILITY_IAM`.

Function names are `${Environment}-<Name>`, for example `dev-GetSettings`.

**Discrepancy:** `Globals.Function.Environment.Variables.SECRET_NAME` is `imcapital-dev-mysql-aurora` with no `prod` condition. The log bucket is also the dev name. A prod stack built from this template still points at dev data stores unless those values are changed outside git.

Node `provider.stage` is the string `DEV`. Kotlin `provider.stage` is `dev`. Neither file has a prod stage.

## API Gateway

SAM resource `AWSApiGateway`, name `IM-CAPITAL-BACKEND-SERVICE`, stage name = `Environment`.

CORS: methods `GET, POST, OPTIONS`, headers `*`, origin `*`.

Auth sets `DefaultAuthorizer` type `AWS_IAM` and `AuthorizerId` to `MyApiGatewayRole`. That role trusts `amplify.amazonaws.com` and allows `apigateway:*` on `*`. This is not the usual SAM authorizer shape (`Auth.Authorizers` plus a name). Whether deployed methods require SigV4 is **Unknown / Requires Confirmation**.

Node HTTP events have no authorizer.

The route table is in `features.md`. Handlers return `GatewayResponse` status 200 with JSON content type, `X-Custom-Header: application/json`, `Access-Control-Allow-Origin: *`, and `Access-Control-Allow-Credentials: true`.

## Data

No DDL is in the repository. Tables and columns below are the ones named in SQL. Provenance: **Inferred** from statements. Types and extra columns are **Unknown**.

| Table | Columns referenced |
|---|---|
| `user` | `id`, `user_name`, `email`, `name`, `given_name`, `family_name`, `im_customer_identifier`, `accepted_terms_timestamp`, `phone_number`, `postal_address` |
| `sanlam_link` | `user_id`, `sanlam_customer_identifier`, `member_no`, `parent_no` |
| `sanlam_member` | `last_kes_number`, `last_usd_number` |
| `transaction` | `id`, `account`, `member_number`, `value`, `type`, `status`, `process`, `im_customer_identifier`, `created_by`, `updated_by` |
| `settings` | `setting_name`, `value` |
| `white_listed` | `CIF`, `Member No` |
| `logs` | `log_title`, `classification`, `message`, `reference`, `created_at` |
| `logs_exported_time` | `time_exported` |
| `log` | `GetLogs.java` runs `SELECT * FROM log` (singular). No caller of `GetLogs` was confirmed |

Connection: `GetSecret` reads `SECRET_NAME` in `REGION`, parses JSON `host`, `username`, `password`, `port`, then JDBC URL database `IMCapital`. Driver `com.mysql.cj.jdbc.Driver`. Connector `mysql:mysql-connector-java:8.0.33`.

Many statements concatenate request strings. `PostCreateUser`, `PostCreateTransaction`, `PostUpdateUser`, `UpdateTransactionProcess`, and `LogToDatabase` use placeholders. `UpdateTransactionProcess` spells the keyword `UPDAtE`.

`GetParameter` calls `System.exit(1)` on `SsmException`.

## Parameter Store names

`GetParameter` uses region `EU_WEST_1`, decryption enabled, and returns a value only for type `SecureString`.

I&M:

- `IM_Consumer_Key`, `IM_Consumer_Secret`
- `IM_Request_OTP`, `IM_Validate_OTP`, `IM_Preferred_2FA`
- `IM_Service_Agreement`, `IM_Service_Agreement_Context`
- `IM_Fetch_customer_accounts`
- `IM_Get_Profile_Image`, `IM_Get_Customer_Details`
- `IM_Initiate_payment`, `IM_public_key`, `IM_request_ref_number`

Sanlam:

- `Sanlam_Get_Token`
- `Sanlam_Taxable_Consumer_Key`, `Sanlam_Taxable_Consumer_Secret`
- `Sanlam_NonTaxable_Consumer_Key`, `Sanlam_NonTaxable_Consumer_Secret` (unused while `taxable` is true)
- `Sanlam_Create_Customer`, `Sanlam_Create_Purchase_Order`, `Sanlam_Create_Withdrawal_Order`
- `Sanlam_Withdrawal_charges`, `Sanlam_Transaction_list`, `Sanlam_Get_Member_Details`
- `Sanlam_Get_Account_Balance`, `Sanlam_Get_Customer_Statements`, `Sanlam_Yield`

## External hosts written in source

I&M:

- `https://test-upg-dxp.imbank.com` realm `inm-retail` (password grant, service agreements, product summary)
- `https://dev-upg-dxp.imbank.com` same realm (OTP token helper)
- Node product-summary header `authority: uat-upg-dxp.imbank.com` while the URL is the test host
- `https://192.168.205.135:2284/KEOAuthTokenService/1.0/GetToken` (client credentials; key and secret from SSM)
- Commented customer URL on the same host: `KECBSRESTService/1.0/CustomerDetailsInq`
- `http://192.168.205.26:1880/restgateway/services/KEWeathManagementStatement/statement`

Sanlam:

- `http://105.27.230.181:3002` paths `/login`, `/statementData`, `/subGroupMembers`
- `https://client.sanlameastafrica.com:8080/login` in `GetStatementData.java`
- `http://api-uat.sanlameastafrica.com/im/api/customer` in `SanlamCreateUser.java`
- `http://api-uat.sanlameastafrica.com/im/api/account/balance/` plus a hardcoded account in `AccountBalance.java`

Other Sanlam URLs are only the SSM names above.

## Auth

There is no user-password store in MySQL for end users. The local `user` insert has no password column.

I&M access for the original token function is a hardcoded password grant. Later SAM routes expect the caller to pass `access_token` or `auth_token`.

Sanlam Node login posts a hardcoded username and password to `/login` and sends the returned token as header `auth-token`.

Payment calls encrypt the body and pass `MFACode`. OTP routes delegate to SSM URLs.

`test/token_check.json` contains a bearer token. It is a sample invoke event, not a runtime config file. The token value is omitted.

## Background work

`WriteS3Logs` / `functions.WriteLogsToS3`:

- EventBridge schedule `rate(1 hour)`
- No HTTP event
- Reads `logs`, writes one object `logs/<yyyyMMddHHmm>.log`
- Inserts `logs_exported_time` after a successful put
- S3 client is `S3Client.create()` (default region chain)

`StatementTest` has a function resource and no event.

## Security notes from the tree (no secret values)

- Passwords, client secrets, and cookies are hardcoded in Node handlers and in several Java helpers.
- CORS origin is `*`.
- SQL concatenation is used for lookups that take request input (`setting`, `user_name`, `email`, `cif`, `im_customer_identifier`).
- `GetParameter` can terminate the process with `System.exit(1)`.
- IAM policy in the template allows `apigateway:*` on `*` for the Amplify role.
- Function role is a shared account role. Resource policies in the template do not scope Secrets Manager or SSM.
- `.aws-sam` build output is committed (4,846 files), including a built `template.yaml`.

## Build, run, and deploy

Node, from `README.md`:

- `serverless deploy --aws-profile <profile> --verbose`
- `serverless invoke local --function getToken` or `getTokenCheck`
- `getTokenCheck` example passes `--path test/token_check.json`
- `serverless offline` after the plugin install steps the README also shows

The documented function name `getTransactions` is not in `serverless.yml`.

Kotlin, from its README and `build.gradle`:

- `./gradlew clean build` (build finalizes with `shadowJar`)
- `./gradlew deploy` runs `sls deploy` via `cmd /c` (Windows command in the Gradle task)
- Artifact `build/libs/getToken-dev-all.jar`

SAM, from `samconfig.toml` and the sample README:

- `sam build`, then `sam deploy`
- Sample README's `sam local invoke HelloWorldFunction` does not match the current template

No Dockerfile. No automated unit run in the pipeline.

## Pipeline and quality

`bitbucket-pipelines.yml`:

- Image `maven:3.3.9`
- Clone depth `full`
- Branch pattern `{master}` only
- Step "SonarQube analysis" using pipe `sonarsource/sonarqube-scan:2.0.1`
- Variables `SONAR_HOST_URL` and `SONAR_TOKEN` from the repository or workspace
- Cache `~/.sonar`

`sonar.projectKey=overdrive_lambda_AYqpvp-fuNwRc2P2S9SZ`. No other Sonar properties (sources, exclusions, tests) are set. The committed `.aws-sam` tree is therefore in the default scan set unless the Sonar server overrides it. Provenance: **Inferred**.

## Tests

| Location | What it contains |
|---|---|
| `test/token_check.json` | API Gateway-style event with `queryStringParameters.access_token` |
| `src/test/java/getToken/GetTokenTest.java` | Empty or commented test methods |
| `TokenCheckTest`, `SanlamStatementDataTest`, `helloworld/AppTest` | Files exist. `GetTokenTest` was read and does not assert |
| Kotlin `src/test/kotlin/.gitkeep` | No tests |

No Node test script in `package.json`.

## Monitoring

Application Insights resources are commented out in `template.yaml`. Handlers use `System.out` and `System.err`. No metrics or alarms are defined. CloudWatch log groups are the Lambda default and are not named in the template.

## Local development gaps

- Database and SSM are not stubbed.
- Node Sanlam calls use a public IP and hardcoded members.
- SAM handlers that need `body` cannot be exercised with an empty event.
- `serverless-offline` is installed for the Node service only.
