Central backend infrastructure for the MEBAR platform — authentication, user management, plugin runtime, and observability in a single deployable service.
MEBAR Core is the kernel of the MEBAR platform. It owns identity, access control, and the plugin bus that every downstream service connects to. Plugins are standalone processes that register themselves at startup — Core proxies traffic to them, enforces their permissions, and routes domain events through a shared NATS JetStream bus. Core never restarts for a plugin update.
Hexagonal (Ports & Adapters) with a plugin bus. The kernel is a Cargo workspace of focused crates:
mebar-core/
├── crates/
│ ├── base — HTTP server, router, app state, observability
│ ├── core-types — shared error types, Role enum, AppResult
│ ├── auth — OTP, Microsoft OAuth, passkeys, sessions, RBAC, API keys
│ ├── user — user lifecycle, audit log, device tracking
│ ├── plugin-runtime — plugin registry, gRPC proxy, NATS event bus
│ ├── gateway — request routing and middleware composition
│ └── core-plugin-sdk — Rust SDK for building plugins
├── proto/
│ └── plugin.proto — CoreService + PluginService gRPC contracts
└── sdks/
├── typescript/core-plugin-sdk
└── python/core-plugin-sdk
Ports:
6073— HTTP API6074— CoreService gRPC (plugin callbacks)
| Concern | Choice |
|---|---|
| Language | Rust (stable, edition 2021) |
| HTTP | axum |
| Database | PostgreSQL 16 + sqlx |
| Cache / Session store | Redis 7 |
| Event bus | NATS JetStream |
| Auth tokens | RS256 JWT (jsonwebtoken) |
| Passkeys | webauthn-rs |
| Microsoft OAuth | OIDC + PKCE via oauth2 |
| Plugin transport | gRPC (tonic) |
| Observability | OpenTelemetry → Jaeger, Prometheus, Grafana |
| Secrets | HashiCorp Vault (opt-in) |
| API docs | utoipa + Swagger UI |
Prerequisites: Rust stable, PostgreSQL 16, Redis 7, NATS with JetStream, sqlx-cli
# 1. Clone (with plugin SDK submodules)
git clone --recurse-submodules git@github.com:mebar-dev/mebar-core.git
cd mebar-core
# 2. Bootstrap database, run migrations, generate JWT keypair
./scripts/setup.sh
# 3. Seed test users and sample plugin
./scripts/seed.sh
# 4. Start NATS with JetStream
nats-server -js
# nats-server -c /opt/homebrew/etc/nats-server.conf # macOS (Homebrew)
# 5. Run
cargo runServer is now listening on http://127.0.0.1:6073.
Swagger UI: http://127.0.0.1:6073/docs
All accounts authenticate via OTP — send the code request, check Mailpit at http://127.0.0.1:8025, then verify.
| Role | |
|---|---|
patient@medipol.edu.tr |
patient |
doctor@medipol.edu.tr |
doctor |
admin@medipol.edu.tr |
admin |
# Send OTP
curl -X POST http://127.0.0.1:6073/api/v1/auth/otp/send \
-H "Content-Type: application/json" \
-d '{"email": "doctor@medipol.edu.tr"}'
# Verify and receive tokens
curl -X POST http://127.0.0.1:6073/api/v1/auth/otp/verify \
-H "Content-Type: application/json" \
-d '{"email": "doctor@medipol.edu.tr", "code": "123456"}'Only emails from the following domains are accepted across all authentication flows:
@medipol.edu.tr@std.medipol.edu.tr@medipol.com.tr
| Method | Endpoint prefix |
|---|---|
| Email OTP | POST /api/v1/auth/otp/ |
| Microsoft OAuth 2.0 | GET /api/v1/auth/oauth/microsoft/ |
| Passkeys (WebAuthn / FIDO2) | POST /api/v1/auth/passkey/ |
| API keys | POST /api/v1/api-keys |
| OAuth 2.0 Client Credentials | POST /api/v1/auth/token |
All authentication paths enforce the domain allowlist before any token or session is issued.
Roles are hierarchical — higher roles include all permissions of lower ones.
| Role | Level |
|---|---|
none |
0 — provisioned on first login, no permissions |
patient |
1 |
doctor |
2 |
admin |
3 — user management, plugin lifecycle |
itadmin |
4 — role assignment, hard deletes, full access |
Full interactive documentation is at /docs. Key endpoint groups:
| Group | Base path |
|---|---|
| Health | /health/live, /health/ready |
| Auth | /api/v1/auth/ |
| Users | /api/v1/users/ |
| API keys | /api/v1/api-keys/ |
| Plugins | /api/v1/plugins/ |
| Plugin proxy | /p/{prefix}/{*path} |
| Metrics | /metrics |
Plugins are standalone processes (any language) that register with Core on startup and receive proxied HTTP traffic over gRPC.
# Scaffold a new plugin
core-cli plugin new my-service --lang rust # or typescript / python
# The scaffold includes:
# my-service/plugin.toml — manifest
# my-service/src/main.rs — entry point wired to core-plugin-sdk
# my-service/Cargo.toml — with SDK dependency placeholderplugin.toml fields:
[plugin]
name = "my-service"
version = "0.1.0"
route_prefix = "/my-service" # all /p/my-service/* traffic routes here
grpc_address = "http://127.0.0.1:50051"
health_endpoint = "/health"
[plugin.scopes]
required = [] # scopes requested from CORE on registrationLifecycle:
Register → Health Check → Active → Draining → Inactive
Core pings each plugin's HealthCheck RPC every 30 seconds. Three consecutive failures mark the plugin inactive and stop routing traffic to it.
Event bus:
Plugins publish and subscribe to domain events via NATS JetStream. Core guarantees at-least-once delivery with configurable retry and a dead letter queue.
Stream: CORE_EVENTS
DLQ: CORE_DLQ
Runtime configuration lives in config/default.toml and is overridden by environment variables with the MEBAR__ prefix (double underscore as separator).
[server]
port = 6073
[auth]
allowed_domains = ["medipol.edu.tr", "std.medipol.edu.tr", "medipol.com.tr"]
access_token_ttl = 900 # 15 min
refresh_token_ttl = 2592000 # 30 days
max_sessions = 5
[plugin]
nats_url = "nats://127.0.0.1:4222"
health_check_interval_secs = 30
health_check_failure_threshold = 3
grpc_port = 6074
[vault]
addr = "" # leave empty to use config file and PEM files on diskEnvironment override example:
MEBAR__SERVER__PORT=8080 cargo runBy default, secrets are read from config/default.toml and PEM files in secrets/. For production, point Core at a HashiCorp Vault instance:
[vault]
addr = "https://vault.internal"
token = "" # supply via MEBAR__VAULT__TOKEN env var
mount = "secret"
prefix = "mebar-core"When vault.addr is non-empty, all passwords and private key PEM contents are pulled from Vault at startup. No restart is needed for secret rotation.
Metrics — Prometheus scrape endpoint at /metrics. Import the bundled dashboard:
Grafana → Dashboards → Import → grafana/dashboards/mebar_core.json
Alert rules are in grafana/alerts/rules.yaml. Add to your prometheus.yml:
rule_files:
- "grafana/alerts/rules.yaml"Tracing — OTLP over HTTP to Jaeger. Enable in config:
[otel]
enabled = true
endpoint = "http://localhost:4318/v1/traces"
service_name = "mebar-core"Start Jaeger (dev):
Download the jaeger-all-in-one binary for your platform from the
latest GitHub release, then run:
# Linux — detects architecture automatically
ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
ASSET=$(curl -s https://api.github.com/repos/jaegertracing/jaeger/releases/latest \
| grep "browser_download_url.*jaeger-.*-linux-${ARCH}\.tar\.gz\"" | cut -d'"' -f4 | head -n1)
curl -sL "$ASSET" | tar -xz && chmod +x jaeger
./jaeger --config config/jaeger.yaml
# macOS — use darwin-amd64 or darwin-arm64 asset instead of linux-${ARCH}Jaeger UI: http://localhost:16686
core-cli is the management tool for MEBAR Core:
core-cli login # save credentials
core-cli user list
core-cli user create nurse@medipol.edu.tr --role patient
core-cli user role <uuid> doctor
core-cli api-key create ci-key --scopes service:ping
core-cli api-key list
core-cli api-key revoke <uuid>
core-cli plugin list
core-cli plugin deactivate <uuid>
core-cli plugin new my-service --lang typescript
core-cli audit-log tail <user-uuid>Global flags: --url, --api-key, --json. Credentials persist in ~/.config/mebar/credentials.toml.
# All integration tests (requires live Postgres + Redis)
cargo test --package base
# Linting
cargo clippy --workspace --all-targets -- -D warnings
# Format check
cargo fmt --check
# Dependency audit
cargo audit# Run migrations
sqlx migrate run --source crates/base/migrations
# Regenerate sqlx compile-time query cache
cargo sqlx prepare --workspaceUNLICENSED — proprietary software of MEBAR, Medipol research center.