Skip to content

Technical — User Service

The RAGA backend service that manages authentication, authorization, and user management. Responsible for login/logout, JWT tokens, role & policy management (RBAC), feature flags, profile photos, and syncing policies to OPA (Open Policy Agent).

Repository

KeyValue
Git Remotehttps://git.tlab.co.id/tarantula/tarantula-v2/service/user-service.git
Active Branchmain
bash
git clone https://git.tlab.co.id/tarantula/tarantula-v2/service/user-service.git
cd user-service

Tech Stack

LayerTechnology
FrameworkNestJS 10 (TypeScript)
DatabasePostgreSQL 16 via TypeORM 0.3
Cache / SessionRedis / Dragonfly (ioredis + Bull)
Object StorageMinIO (user profile photos)
Search / IndexElasticsearch 8.19 (user activity)
AuthJWT (@nestjs/jwt) + bcrypt
Secret ManagementInfisical SDK v4
Loggingnest-winston + Telegram Transport
TestingJest 29 + Supertest
RuntimeNode.js (Docker)

Environment Variables

.env File (Infisical Bootstrap)

bash
INFISICAL_ENV=dev
INFISICAL_PATH=/user
INFISICAL_SITE_URL=http://10.1.102.15:8002
INFISICAL_CLIENT_ID=<client-id>
INFISICAL_CLIENT_SECRET=<client-secret>
INFISICAL_PROJECT_ID=<project-id>

Note: INFISICAL_PATH=/user — this service's secrets are stored under the /user path in the Infisical project, unlike API Tarantula which uses /.

VariableDescription
INFISICAL_ENVTarget environment (dev / staging / prod)
INFISICAL_PATHSecrets path in Infisical, specific to this service: /user
INFISICAL_SITE_URLSelf-hosted Infisical server URL
INFISICAL_CLIENT_IDUniversal Auth Client ID
INFISICAL_CLIENT_SECRETUniversal Auth Client Secret
INFISICAL_PROJECT_IDInfisical project ID

Secrets via Infisical

Application

VariableDefaultDescription
APP_TIMEZONEUTCTimezone used to format timestamps (created_at/updated_at) in response DTOs
ORIGIN_URLFrontend base URL; used to build the reset-password link sent by email ({ORIGIN_URL}/reset-password?token=...)

Database (PostgreSQL)

VariableDescription
DB_HOSTPostgreSQL host
DB_PORTPostgreSQL port (default 5432)
DB_USERPostgreSQL username
DB_PASSWORDPostgreSQL password
DB_NAMEDatabase name (dev: user_service)

Redis / Dragonfly

VariableDefaultDescription
REDIS_HOSTlocalhostRedis/Dragonfly host
REDIS_PORT6379Redis port
REDIS_PASSWORDPassword (optional)

Redis is used to store the access token & refresh token with a TTL, as the session invalidation mechanism.

Mail (SMTP)

VariableDescription
SMTP_HOSTSMTP server host
SMTP_PORTSMTP server port
SMTP_USERNAMESMTP username
SMTP_PASSWORDSMTP password
SMTP_MAILSender (from) email address for forgot-password emails

Used by the mail module (via a Bull queue) to send forgot-password emails with a Handlebars template.

JWT & Token

VariableDescription
JWT_SECRETSecret key for signing JWTs
ACCESS_TOKEN_PREFIX_REDISRedis key prefix for the access token
ACCESS_TOKEN_LIFETIMEAccess token TTL in seconds
REFRESH_TOKEN_PREFIX_REDISRedis key prefix for the refresh token
REFRESH_TOKEN_LIFETIMERefresh token TTL in seconds

MinIO (Object Storage)

VariableDefaultDescription
MINIO_ENDPOINTlocalhostMinIO endpoint
MINIO_PORT9000MinIO port
MINIO_ACCESS_KEYAccess key
MINIO_SECRET_KEYSecret key
MINIO_BUCKETBucket name; files are stored under the user-service/ subfolder
MINIO_DOMAINhttps://s3.ziwardingai.xyzPublic MinIO domain; used by the health controller to build the public profile-photo URL

Elasticsearch

VariableDefaultDescription
ELASTICSEARCH_HOSThttp://localhost:9200Elasticsearch URL
ELASTICSEARCH_USERNAMEUsername
ELASTICSEARCH_PASSWORDPassword
MAXIMUM_CHAT_PER_ROOM2Chat history limit per room
INDEX_USER_ACTIVITY_LOGuser-activity-logElasticsearch index/alias name activity-log writes each user action to

OPA (Open Policy Agent)

VariableDefaultDescription
OPA_DATABase URL of the opa-data service; called on policy/role changes to sync

License API

VariableDefaultDescription
LICENSE_API_URLhttp://10.1.102.15:8003License management service URL
LICENSE_API_EMAILadmin@mail.comBasic Auth authentication email
LICENSE_API_PASSWORD123456Basic Auth authentication password

API Tarantula Integration

VariableDefaultDescription
TARANTULA_API_URLapi-tarantula base URL; after a user update, PUT {TARANTULA_API_URL}/utils/update-user-name/:id is called to sync the name. If empty, the sync is skipped

Seeder

VariableDefaultDescription
GHOST_PASSWORDSuper@dm1nPassword for the ghost/healthcheck account (healthcheck@veloint.id) created by npm run seed:ghost
BASE_URLuser-service base URL; used by feature.seed.ts/policy.seed.ts to determine the base path for this service's RBAC features
BASE_URL_API_TARANTULAapi-tarantula base URL; used by the same seeds to determine the base path for api-tarantula's RBAC features

Telegram Logging

VariableDescription
TELEGRAM_TOKENTelegram bot token for error alerting
TELEGRAM_CHAT_IDDestination chat ID for notifications
TELEGRAM_TOPIC_IDTopic ID (thread) within the Telegram group

If TELEGRAM_TOKEN and TELEGRAM_CHAT_ID are not set, the Telegram transport is not activated — logging falls back to console only.

Folder Structure

user-service/
├── src/
│   ├── app.module.ts               # Root module
│   ├── main.ts                     # NestJS entry point
│   │
│   ├── common/                     # Shared utilities & config
│   │   ├── config/
│   │   │   ├── elastic.config.ts   # Elasticsearch client + query helpers
│   │   │   ├── license.config.ts   # License API client (Basic Auth)
│   │   │   ├── minio.config.ts     # MinIO client + upload/download helpers
│   │   │   ├── typeorm.config.ts   # TypeORM config
│   │   │   ├── infisical-cli.ts    # CLI helper for migrations via Infisical
│   │   │   └── infisical-cli-seeder.ts
│   │   ├── decorator/
│   │   ├── dto/                    # PaginationDto, ParamDto
│   │   ├── exception/
│   │   ├── filter/                 # Global exception filter
│   │   ├── interceptor/            # Response format interceptor
│   │   ├── logger/                 # Telegram Winston transport
│   │   ├── seeder.helper.ts
│   │   └── sync-to-infisical.ts
│   │
│   ├── db/
│   │   ├── migrations/             # TypeORM migrations (13 files)
│   │   └── seeds/                  # data, feature, ghost, policy seeders
│   │
│   ├── infisical/                  # Infisical secret loader
│   │
│   ├── auth/                       # Login, register, forgot/reset password
│   ├── user/                       # User CRUD, profile photo, password/theme update
│   ├── role/                       # Role management; role level; assign shortcut ACL
│   ├── features/                   # Feature flags (features available in the system)
│   ├── subfeatures/                # Sub-features of each feature
│   ├── policies/                   # RBAC policy (feature + subfeature + role)
│   ├── shortcut-acls/              # Shortcut ACL: fast access per role to a subfeature
│   ├── opa/                        # Triggers policy sync to the opa-data service
│   ├── open-api/                   # RBAC data endpoints for opa-data sync (role-users, role-grants)
│   ├── redis/                      # Redis provider, service (token store)
│   ├── mail/                       # Email service (Bull queue + Handlebars templates)
│   ├── activity-log/               # User activity logging
│   ├── shared/                     # JWT helper, shared service
│   ├── log/                        # HTTP request logging middleware
│   ├── health/                     # Health check endpoint
│   └── utils/                      # Utility endpoints

├── test/                           # E2E tests
├── docker-compose.dev.yml          # Docker for local development
├── docker-compose.yml
├── Dockerfile.dev
├── Dockerfile
└── package.json

Module Architecture

  • Authenticationauth (login/register/token), redis (token store TTL), mail (forgot password email)
  • User Managementuser (CRUD, photo, theme), backed by MinIO (profile photo)
  • RBACrole (role management + level), features (feature flags), subfeatures (sub-feature), policies (role ↔ feature ↔ subfeature), shortcut-acls (fast access per role), opa (sync to opa-data)
  • Supportopen-api (RBAC data endpoints for opa-data sync), activity-log (audit trail), infisical (secret loader), license (license validation)

Key Modules

ModuleResponsibility
authLogin (email + password + bcrypt), JWT issuance, token refresh, forgot/reset password
userUser CRUD, profile photo upload to MinIO, password update, theme mode update
redisStores access & refresh tokens with a TTL; used for session validation & invalidation
roleRole management with a hierarchical level; assigns shortcut ACLs to roles
features / subfeaturesFeature flag system; defines the features and sub-features present in the platform
policiesMany-to-many relation between role ↔ feature ↔ subfeature; the basis for authorization decisions
shortcut-aclsACL shortcut: direct access per role to a specific set of subfeatures without going through full policies
opaCalls the opa-data service on every policy/role change so OPA rules stay up to date
open-apiExposes GET /open-api/role-users (user + role data) and GET /open-api/role-grants (role → feature/subfeature grant data) — these are the endpoints opa-data pulls from during sync
infisicalSecret loader; preloads all env vars from Infisical before other modules run
activity-logInterceptor that logs every user action (read from token, stored in DB + Elasticsearch)

Infrastructure (Development)

Run via docker-compose.dev.yml:

bash
docker compose -f docker-compose.dev.yml up -d
ContainerImagePortDescription
user-jabarinDockerfile.dev (NestJS)3000Application with hot-reload
db-user-jabarinpostgres:16-alpine5432PostgreSQL; DB: user_service
cached-jabarindragonflydb/dragonfly6379Dragonfly (Redis-compatible); emulated cluster mode + lock_on_hashtags
minio-tarantula-userminio/minio9000 (API), 9001 (Console)Object storage for profile photos

All containers share the network-jabarin network (subnet 123.16.238.0/24).

Dragonfly is used in place of Redis for full protocol compatibility with higher throughput under concurrent workloads. The --cluster_mode=emulated --lock_on_hashtags flags are required for BullMQ compatibility.

Authentication Flow

opa-data then pulls the fresh data via GET /open-api/role-users and GET /open-api/role-grants on user-service to rebuild its OPA rules.

External Integrations

ServiceEnv VariableDescription
opa-dataOPA_DATATriggered (GET {OPA_DATA}/role-users or /role-grants) on every role/user/feature/subfeature/policy change to sync OPA rules
api-tarantulaTARANTULA_API_URLCalled (PUT {TARANTULA_API_URL}/utils/update-user-name/:id) on every user data update, to sync the user name in api-tarantula
License APILICENSE_API_URLPlatform license validation via Basic Auth
InfisicalINFISICAL_SITE_URLSource of all runtime secrets
Telegram BotTELEGRAM_TOKENError-level log alerting to a Telegram channel

Development Commands

bash
# Install dependencies
npm install

# Run development (hot-reload)
npm run start:dev

# Build production
npm run build

# Run migrations
npm run migration:run

# Create a new migration
npm run migration:create --name=MigrationName

# Rollback migration
npm run migration:revert

# Run data seeder
npm run seed:run

# Run ghost user seeder
npm run seed:ghost

# Sync new secrets to Infisical
npm run sync:infisical