Skip to content

TulevaEE/onboarding-service

Repository files navigation

Onboarding-service

CircleCI Known Vulnerabilities codecov

Architecture

Tuleva Architecture

Prerequisites

Tech stack

Database

PostgreSQL

Running locally with Docker: docker compose up database -d

Spring Profile

IMPORTANT: Set your Spring active profile to dev - this will also run DB schema/dev data migration

Backend

Java 25, Spring Boot, Gradle, Spock for testing

Running locally: ./gradlew bootRun

Testing

Tests can run against either H2 in-memory database or PostgreSQL via Testcontainers:

  • ./gradlew test - runs against H2 in-memory database (default, fast)
  • SPRING_PROFILES_ACTIVE=ci,test ./gradlew test - runs against PostgreSQL via Testcontainers (requires Docker to be running)

Some tests require PostgreSQL-specific features (jsonb, advanced queries) and will be skipped when running against H2. These tests are annotated with @Requires to only run when the ci profile is active or CI=true environment variable is set.

Note: Docker must be running for Testcontainers to work when using the ci profile.

Frontend

React, TypeScript, scss, custom bootstrap, react-testing-library

Exception Monitoring

Sentry

Analytics

Google Analytics / Mixpanel

Hosting

AWS ECS Fargate (production and staging) AWS Elastic Beanstalk: EC2 and ELB (legacy - being decommissioned)

Migration Status: Production and staging migrated to ECS in November 2025. Beanstalk environments scaled to zero, pending final decommission.

Infrastructure as Code

Terraform configurations are stored in S3, not committed to Git. See Infrastructure Management below.

Continuous Integration

CircleCI

Production Logs

Papertrail

API

Authentication: oAuth2 with Mobile-ID, ID-card and Smart-ID

Swagger UI

Postman API collection (outdated)

Build pipeline

Production: Merge GitHub pull request to master -> build in CircleCI -> auto-redeploy (if build is green)

How to add new pension funds?

  1. Add the new fund to the funds database table.

Development notes

Code style: Java, Kotlin

If you don't want to run epis-service, then you can use mock spring profile to mock EpisService, and adjust MockEpisService to your needs.

Common Issues

error="unsupported_grant_type", error_description="Unsupported grant type: mobile_id"

Make sure you are running against the right backend environment (dev or prod).

  • If you do npm run develop your package.json must proxy to http://localhost:9000
  • If you do npm run develop-production your package.json must proxy to https://onboarding-service.tuleva.ee

Known Issues

  • Digital signing does not work in the dev environment. Use the production configuration to test it locally. See DigiDocConfiguration.digiDocConfigDev() and smartid.hostUrl, smartid.relyingPartyUUID, smartid.relyingPartyName config values in application.yml and change them to production values. Use VPN for testing.

Caveats

When updating Spring Boot, sometimes you need to remove all of the existing access tokens from the oauth_access_token database table. However, there's one special token granted for tuleva.ee which allows it to fetch Fund NAV values and register new users. In order to generate a new token, you need to: token by

curl --location --request POST 'https://pension.tuleva.ee/api/oauth/token' \
--header 'Authorization: Basic <base64 of client_id:client_secret>' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=tuleva.ee'

and then update the token values in the WordPress Tuleva template.

Swedbank Gateway

Certificates

Download swedbank-gateway.p12 from tuleva-secrets/staging from S3 and place it in test_keys.

Account fetcher

For swedbank-gateway.accounts in application.yml, use EE062200221055091966 for test account variables.

Testing ID-card Authentication

ID-card authentication is implemented using AWS ALB mutual TLS in production and staging environments. Use or production environment such as id-staging.tuleva.ee/idLogin for tests.

AWS Profile

WE use AWS SSO, to get it working properly you need to configure the profile first either by running aws configure sso or pasting the following into ~/.aws/config:

[profile tuleva]
region = eu-central-1
output = json
sso_start_url = https://tuleva.awsapps.com/start
sso_region = eu-central-1
sso_account_id = 641866833894
sso_role_name = AdministratorAccess

VPN

We use AWS Client VPN. To get started, log into AWS SSO Portal and follow VPN Client Self Service instructions.

Connecting to the database

  • Establish VPN connection
  • Configure AWS Profile and login aws sso login
  • Connect to the DB using AWS IAM authentication where user is iamuser and profile tuleva.

Development Environment

Configuration is available AWS S3 s3://tulevasecrets/development-configuration/

Certificate upgrade

RDS

  1. Update .pem file in etc/docker
  2. If file was renamed, rename it in gradle/packaging.gradle.kts

In case file has multiple certificate chains, import-certs.sh will add all of them.

Smart-ID, Mobile-ID, ID-card

  1. Install keystore explorer if missing brew install --cask keystore-explorer or use command line keytool
  2. Navigate to the tulevasecrets S3 bucket, open either staging or development directory, download truststore.jks.
  3. Add new certs and upload new version back to S3 bucket.
    • If there are errors with multiple certificates, either remove or split them by opening the .pem file with a text editor.
      • For example a root cert might be added (unnecessarily for our use case) to the cert you are trying to add to the truststore
    • When changing staging certs, also add them to src/test_keys/truststore.jks for your local dev environment.
  4. For ECS (primary): Truststore is downloaded from S3 during container startup via entrypoint script. Redeploy via CircleCI or force new deployment.
  5. For Beanstalk (legacy): Do a clean deploy to ensure that new EC2 instance is spun up, and S3 files defined in .ebextensions/keystore.config are copied over.

PostgreSQL <-> H2 compatibility for integration tests

PostgreSQL (used while running the application) and H2 (used while running integration tests) have slightly different support for features, requiring some to be stubbed.
When adding a new migration for H2 <-> Postgres compatibility, the name must be V1_{n-1}_1__.sql for Flyway to execute the compatibility migration before it tries to execute the migration numbered n, for which the compatibility migration is required.

Adding mandrill emails

  1. Create email type with unique template name.
  2. In Mailchimp, under Content -> Email templates, add a template with the same name and _et and _en prefix for languages. Click dropdown arrow in list view -> Send to mandrill.
  • Use available merge vars as well.
  1. In Mandrill, add a subject line for both templates.

References

hwcrypto.js

hwcrypto Sequence Diagram

Test Authentication Methods

Test Mobile ID

Test ID Card

Test Smart ID

Infrastructure Management

ECS Infrastructure

Production and staging environments run on AWS ECS Fargate managed via Terraform.

Current Status (Nov 2025):

  • ✅ Production: Running on ECS (pension.tuleva.ee, onboarding-service.tuleva.ee)
  • ✅ Staging: Running on ECS (staging.tuleva.ee)
  • ⏳ Beanstalk: Scaled to zero, pending decommission

Key Resources:

  • ECS Cluster: onboarding-service-cluster
  • Production Logs: /ecs/onboarding-service-production
  • Staging Logs: /ecs/onboarding-service-staging

Terraform Files in S3

Terraform configuration files are stored in S3

Location: s3://tuleva-infrastructure/onboarding-service/terraform/

Setting Up Terraform on Your Machine

First time setup:

# 1. Navigate to terraform directory and set AWS_PROFILE
cd infrastructure/terraform
aws configure sso #if needed
export AWS_PROFILE=<your profile>

# 2. Set permissions
chmod +x download-terraform-from-s3.sh

# 3. Download all terraform files
./download-terraform-from-s3.sh

# 4. Initialize terraform
terraform init

# 5. You're ready!
terraform plan -var-file=staging.tfvars

Working with Terraform

After making changes:

# Test your changes
terraform plan -var-file=staging.tfvars

# Apply if good
terraform apply -var-file=staging.tfvars

# Upload to S3 for team
./upload-terraform-to-s3.sh

Getting latest changes from team:

./download-terraform-from-s3.sh

Available Scripts

  • setup-infrastructure-bucket.sh - One-time S3 bucket setup (already done)
  • upload-terraform-to-s3.sh - Upload your terraform changes to S3
  • download-terraform-from-s3.sh - Download latest terraform files from S3

What's in S3

  • *.tf files - Infrastructure code (main.tf, variables.tf, outputs.tf, etc.)
  • *.tfvars files - Environment configurations (staging, production)
  • Helper scripts - Setup, upload, and download scripts

CloudFlare Workers

The application uses CloudFlare Workers to route traffic between S3 (static assets) and ECS (backend API):

Routing Rules:

  • /api/* → Backend (strips /api prefix before forwarding)
  • /oauth/* → Backend (OAuth endpoints for authentication, including token refresh)
  • Everything else → S3 (static frontend assets)

Worker Files (in infrastructure/):

  • cloudflare-worker-pension.js - Production worker for pension.tuleva.ee
  • cloudflare-worker-staging.js - Staging worker for staging.tuleva.ee

Deployment: Workers are deployed manually via CloudFlare Dashboard (not automated in CircleCI).

Architecture:

  • Visitors access: https://pension.tuleva.ee (HTTPS via CloudFlare)
  • CloudFlare Worker handles routing based on path
  • Backend: https://ecs-onboarding-service.tuleva.ee (HTTPS)
  • S3 Static Assets: http://pension.tuleva.ee.s3-website.eu-central-1.amazonaws.com (HTTP)

Testing:

# Test API routing (should reach backend)
curl -X POST https://pension.tuleva.ee/api/authenticate \
  -H "Content-Type: application/json" \
  -d '{"phoneNumber":"test","personalCode":"test","type":"MOBILE_ID"}'

# Test OAuth routing (should reach backend, not S3)
curl -X POST https://pension.tuleva.ee/oauth/refresh-token \
  -H "Content-Type: application/json" \
  -d '{"refresh_token":"test"}'

# Test static assets (should reach S3)
curl -I https://pension.tuleva.ee/

Using AWS

Athena for log search

Service logs are at tuleva-papertrail Europe (Paris) eu-west-3 which means, that you need to use eu-west-3 Athena output S3 bucket.

Load Balancer logs are located at logs.tuleva.ee bucket.

Service log table example

CREATE EXTERNAL TABLE IF NOT EXISTS `s3papertraillogsdatabase`.`S3PaperTrailLogsTableTSV` (
  `ingestion_time` bigint,
  `request_date` string,
  `request_time` string,
  `log_id` bigint,
  `env` string,
  `originating_ip` string,
  `user_type` string,
  `log_level` string,
  `log_file` string,
  `message` string
)
PARTITIONED BY (`dt` string)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES ('field.delim' = '\t')
STORED AS INPUTFORMAT 'org.apache.hadoop.mapred.TextInputFormat' OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://tuleva-papertrail/logs/';

Then run MSCK REPAIR TABLE S3PaperTrailLogsDatabase.s3papertraillogstabletsv; For partitioning.

And query select * from s3papertraillogsdatabase.s3papertraillogstabletsv limit 10;

Adding VPN routes

For example, Pensionikeskus new IP-s

  1. Allow new routes in private-rounting tables for VPCs
  2. Update static routes for VPN

Configuring VPN Split Tunneling

To configure traffic routing through a VPN split tunnel:

  1. Open Client VPN endpoints and select the relevant VPN.

  2. Under Route Table, create a new route specifying the desired IP address with the designated subnet.

  3. In Authorization Rules, add the desired IP and set the appropriate access level.

  4. If you need to register the gateway IP with an external party and need to double-check VPN internet gateway IP

    1. use:
      dig ipv4.icanhazip.com
    2. And take note of the IPs for icanhazip.com. Add a new route and authorization rule similarly to steps 2 and 3 (currently done at ip 104.16.184.241) and establish the gateway ip
    3. Connect to VPN
    4. To check VPN internet gateway IP
       curl -H "Host: ipv4.icanhazip.com" http://104.16.184.241

Updating dependencies

Spring Boot

Update io.hypersistence:hypersistence-utils-hibernate version as well

Releases

No releases published

Packages

No packages published

Contributors 25