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
| Key | Value |
|---|---|
| Git Remote | https://git.tlab.co.id/tarantula/tarantula-v2/service/user-service.git |
| Active Branch | main |
git clone https://git.tlab.co.id/tarantula/tarantula-v2/service/user-service.git
cd user-serviceTech Stack
| Layer | Technology |
|---|---|
| Framework | NestJS 10 (TypeScript) |
| Database | PostgreSQL 16 via TypeORM 0.3 |
| Cache / Session | Redis / Dragonfly (ioredis + Bull) |
| Object Storage | MinIO (user profile photos) |
| Search / Index | Elasticsearch 8.19 (user activity) |
| Auth | JWT (@nestjs/jwt) + bcrypt |
| Secret Management | Infisical SDK v4 |
| Logging | nest-winston + Telegram Transport |
| Testing | Jest 29 + Supertest |
| Runtime | Node.js (Docker) |
Environment Variables
.env File (Infisical Bootstrap)
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/userpath in the Infisical project, unlike API Tarantula which uses/.
| Variable | Description |
|---|---|
INFISICAL_ENV | Target environment (dev / staging / prod) |
INFISICAL_PATH | Secrets path in Infisical, specific to this service: /user |
INFISICAL_SITE_URL | Self-hosted Infisical server URL |
INFISICAL_CLIENT_ID | Universal Auth Client ID |
INFISICAL_CLIENT_SECRET | Universal Auth Client Secret |
INFISICAL_PROJECT_ID | Infisical project ID |
Secrets via Infisical
Application
| Variable | Default | Description |
|---|---|---|
APP_TIMEZONE | UTC | Timezone used to format timestamps (created_at/updated_at) in response DTOs |
ORIGIN_URL | — | Frontend base URL; used to build the reset-password link sent by email ({ORIGIN_URL}/reset-password?token=...) |
Database (PostgreSQL)
| Variable | Description |
|---|---|
DB_HOST | PostgreSQL host |
DB_PORT | PostgreSQL port (default 5432) |
DB_USER | PostgreSQL username |
DB_PASSWORD | PostgreSQL password |
DB_NAME | Database name (dev: user_service) |
Redis / Dragonfly
| Variable | Default | Description |
|---|---|---|
REDIS_HOST | localhost | Redis/Dragonfly host |
REDIS_PORT | 6379 | Redis port |
REDIS_PASSWORD | — | Password (optional) |
Redis is used to store the access token & refresh token with a TTL, as the session invalidation mechanism.
Mail (SMTP)
| Variable | Description |
|---|---|
SMTP_HOST | SMTP server host |
SMTP_PORT | SMTP server port |
SMTP_USERNAME | SMTP username |
SMTP_PASSWORD | SMTP password |
SMTP_MAIL | Sender (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
| Variable | Description |
|---|---|
JWT_SECRET | Secret key for signing JWTs |
ACCESS_TOKEN_PREFIX_REDIS | Redis key prefix for the access token |
ACCESS_TOKEN_LIFETIME | Access token TTL in seconds |
REFRESH_TOKEN_PREFIX_REDIS | Redis key prefix for the refresh token |
REFRESH_TOKEN_LIFETIME | Refresh token TTL in seconds |
MinIO (Object Storage)
| Variable | Default | Description |
|---|---|---|
MINIO_ENDPOINT | localhost | MinIO endpoint |
MINIO_PORT | 9000 | MinIO port |
MINIO_ACCESS_KEY | — | Access key |
MINIO_SECRET_KEY | — | Secret key |
MINIO_BUCKET | — | Bucket name; files are stored under the user-service/ subfolder |
MINIO_DOMAIN | https://s3.ziwardingai.xyz | Public MinIO domain; used by the health controller to build the public profile-photo URL |
Elasticsearch
| Variable | Default | Description |
|---|---|---|
ELASTICSEARCH_HOST | http://localhost:9200 | Elasticsearch URL |
ELASTICSEARCH_USERNAME | — | Username |
ELASTICSEARCH_PASSWORD | — | Password |
MAXIMUM_CHAT_PER_ROOM | 2 | Chat history limit per room |
INDEX_USER_ACTIVITY_LOG | user-activity-log | Elasticsearch index/alias name activity-log writes each user action to |
OPA (Open Policy Agent)
| Variable | Default | Description |
|---|---|---|
OPA_DATA | — | Base URL of the opa-data service; called on policy/role changes to sync |
License API
| Variable | Default | Description |
|---|---|---|
LICENSE_API_URL | http://10.1.102.15:8003 | License management service URL |
LICENSE_API_EMAIL | admin@mail.com | Basic Auth authentication email |
LICENSE_API_PASSWORD | 123456 | Basic Auth authentication password |
API Tarantula Integration
| Variable | Default | Description |
|---|---|---|
TARANTULA_API_URL | — | api-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
| Variable | Default | Description |
|---|---|---|
GHOST_PASSWORD | Super@dm1n | Password for the ghost/healthcheck account (healthcheck@veloint.id) created by npm run seed:ghost |
BASE_URL | — | user-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_TARANTULA | — | api-tarantula base URL; used by the same seeds to determine the base path for api-tarantula's RBAC features |
Telegram Logging
| Variable | Description |
|---|---|
TELEGRAM_TOKEN | Telegram bot token for error alerting |
TELEGRAM_CHAT_ID | Destination chat ID for notifications |
TELEGRAM_TOPIC_ID | Topic 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.jsonModule Architecture
- Authentication —
auth(login/register/token),redis(token store TTL),mail(forgot password email) - User Management —
user(CRUD, photo, theme), backed by MinIO (profile photo) - RBAC —
role(role management + level),features(feature flags),subfeatures(sub-feature),policies(role ↔ feature ↔ subfeature),shortcut-acls(fast access per role),opa(sync to opa-data) - Support —
open-api(RBAC data endpoints for opa-data sync),activity-log(audit trail),infisical(secret loader),license(license validation)
Key Modules
| Module | Responsibility |
|---|---|
auth | Login (email + password + bcrypt), JWT issuance, token refresh, forgot/reset password |
user | User CRUD, profile photo upload to MinIO, password update, theme mode update |
redis | Stores access & refresh tokens with a TTL; used for session validation & invalidation |
role | Role management with a hierarchical level; assigns shortcut ACLs to roles |
features / subfeatures | Feature flag system; defines the features and sub-features present in the platform |
policies | Many-to-many relation between role ↔ feature ↔ subfeature; the basis for authorization decisions |
shortcut-acls | ACL shortcut: direct access per role to a specific set of subfeatures without going through full policies |
opa | Calls the opa-data service on every policy/role change so OPA rules stay up to date |
open-api | Exposes 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 |
infisical | Secret loader; preloads all env vars from Infisical before other modules run |
activity-log | Interceptor that logs every user action (read from token, stored in DB + Elasticsearch) |
Infrastructure (Development)
Run via docker-compose.dev.yml:
docker compose -f docker-compose.dev.yml up -d| Container | Image | Port | Description |
|---|---|---|---|
user-jabarin | Dockerfile.dev (NestJS) | 3000 | Application with hot-reload |
db-user-jabarin | postgres:16-alpine | 5432 | PostgreSQL; DB: user_service |
cached-jabarin | dragonflydb/dragonfly | 6379 | Dragonfly (Redis-compatible); emulated cluster mode + lock_on_hashtags |
minio-tarantula-user | minio/minio | 9000 (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
| Service | Env Variable | Description |
|---|---|---|
| opa-data | OPA_DATA | Triggered (GET {OPA_DATA}/role-users or /role-grants) on every role/user/feature/subfeature/policy change to sync OPA rules |
| api-tarantula | TARANTULA_API_URL | Called (PUT {TARANTULA_API_URL}/utils/update-user-name/:id) on every user data update, to sync the user name in api-tarantula |
| License API | LICENSE_API_URL | Platform license validation via Basic Auth |
| Infisical | INFISICAL_SITE_URL | Source of all runtime secrets |
| Telegram Bot | TELEGRAM_TOKEN | Error-level log alerting to a Telegram channel |
Development Commands
# 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