Gerege Kiosk¶
Production · Layer 4 — Vertical product ·
Repo: gerege-kiosk-mn · geregekiosk.mn
A self-service terminal platform — eID-based, AI-enabled. It connects terminals placed in public spaces to the national electronic ID so a citizen can pull a certificate, pay a fee and print a receipt without queueing or meeting a clerk. Available around the clock, independent of office hours.
Ownership
The platform is owned and operated by Gerege Kiosk LLC. Its source lives in
a private repository and is built on Gerege Systems' shared open-gerege-core
module.
geregekiosk.mn now serves Gerege Nexus
Checked on 2026-08-07: https://geregekiosk.mn/ returns a
Gerege Nexus deployment — the page title is "Gerege Nexus" and the
description is the Nexus one. The domain has its own valid certificate and sits
on 38.180.243.183.
So the gerege-kiosk-mn behaviour documented on this page may no longer match
what the public domain serves. Check the version running on a terminal against
that deployment.
The core idea — thin app, thick foundation¶
The Kiosk Go backend is a single file. Authentication, RBAC, the API gateway,
the AI pipeline, eID/SSO — every foundational capability lives in
github.com/gerege-systems/open-gerege-core. This repository pins a version of it and
boots it under its own name:
func main() {
server.ServiceName = "gerege-kiosk"
app, err := server.NewApp()
if err != nil { /* … */ }
// Application-specific routes go here:
// app.Router().Route("/api/xxx", xxx.Routes(app.Pool()))
if err := app.Run(); err != nil { /* … */ }
}
This is the answer to the fork-sync technical debt described on the
Template Platform page: the foundation used to be copied into every
repository, and each improvement had to be carried over by hand. Kiosk is now a
versioned consumer of open-gerege-core — security patches propagate from one
place, and an upgrade is a single go get open-gerege-core@latest.
| Layer | Where it lives | Who owns it |
|---|---|---|
| Foundation backend (auth, RBAC, AI, gateway, migrations) | open-gerege-core module |
Gerege Systems |
| Entry point, branding, configuration | gerege-kiosk-mn/backend |
Gerege Kiosk LLC |
| Frontend BFF, UI | gerege-kiosk-mn/frontend |
Gerege Kiosk LLC |
| Deployment, edge vhost | gerege-kiosk-mn/deploy |
Gerege Kiosk LLC |
Layout¶
gerege-kiosk-mn/
├── backend/ # Go · thin consumer of open-gerege-core (cmd/api/main.go)
├── frontend/ # Next.js 15 BFF — Node 20, TanStack Query, mn/en/zh/ru
├── ios/ # SwiftUI reference client (talks only to the BFF)
└── deploy/ # compose, edge nginx vhost, internal DB TLS certificates
The foundation backend is Clean Architecture — handler → usecase → repository →
domain, no back-imports, and no ORM (hand-written SQL over pgx).
Authentication — Gerege SSO only¶
The sign-in screen has exactly one button: Sign in with Gerege SSO. There is no password, no email/OTP registration and no direct eID flow.
graph LR
C["Citizen / terminal"] --> W["geregekiosk.mn<br/>Next.js BFF"]
W --> A["Kiosk API"]
A --> S["sso.gerege.mn<br/>Gerege SSO"]
S --> E["eID Mongolia"]
S -.->|"eID proxy"| A
- OIDC RP flow —
/api/auth/sso/start→sso.gerege.mn→/sso/callback. A separate PKCE public-client flow serves mobile. - Session — JWT access + refresh, with rotating refresh tokens; logout revokes the refresh token and deny-lists the access token.
- Tokens never reach the browser — they stay in httpOnly cookies and are attached server-side by the BFF.
Why isn't it an eID relying party itself?
Under boundary rule 2, Layer 3–4 applications never call eID directly. Kiosk holds no eID RP credentials — all eID traffic is relayed through Gerege SSO. That keeps credentials in one place and the audit trail unbroken.
eID PKI profile — through the proxy¶
The signed-in citizen's PKI dashboard is fetched through SSO's eID proxy. Kiosk calls it with the user's SSO access token, and SSO retrieves the data using its own eID credentials.
| What | Where it appears |
|---|---|
| Consolidated summary | /me/eid/id |
| Certificates and their status | /me/eid/certificates |
| Bound devices | /me/eid/devices |
| Authentication / signing history | /me/eid/logs |
| Linked organisations and authorised signers | /me/organizations |
A disabled proxy (the eid-proxy service switched off at SSO) or an expired token
is handled explicitly by the UI rather than surfacing as a 5xx.
An OIDC provider in its own right¶
Besides being a relying party of SSO, Kiosk can act as an identity provider. When
OAUTH_ISSUER and a state key are configured, its own Go OAuth2/OIDC provider
activates — no Ory Hydra involved:
- login · consent · logout screens under
/oauth, - RP registration and secret rotation in the
oauth_clientstable, - consent skipped for first-party clients, and remembered afterwards,
- discovery,
userinfoandid_token— with the signing key stored encrypted.
This lets satellite applications around Kiosk offer "Sign in with Gerege Kiosk".
The citizen-facing surface¶
| Area | What it does |
|---|---|
/me/dashboard |
Personal dashboard |
/me/services · /me/applications |
Service catalogue, applications and their progress |
/me/references |
Certificates and references |
/me/notifications |
Notifications |
/me/payments |
Payments |
/me/appointments |
Appointment booking |
/me/organizations |
Organisations, membership, permissions |
/me/eid/sign |
Sign a document electronically |
/me/integrations |
Third-party connections and Gerege Space |
/me/ai · /me/translate |
AI assistant, live translation |
Creating or searching for an organisation queries the state registry through Gerege Verify. Organisation data is isolated per user by Postgres RLS.
Unified service registry¶
The R1 component of Ring System — service passports and evidence management:
- service catalogue, versions, publish/archive,
- life events — grouping services by the citizen's situation,
- evidences and the once-only dashboard, which measures how well the principle of never asking twice for a document already held is being applied.
Two permission levels apply: registry.view (read) and registry.manage (write).
API gateway¶
An admin-managed service catalogue: services · routes · consumers · API keys · policies, plus request telemetry (overview and logs).
Current scope
The gateway is today a management and telemetry layer. Turning the configured routes and policies into actual reverse-proxy enforcement (per-consumer rate limits and quotas) is on the roadmap.
Electronic signatures and the sign relay¶
- PAdES — server-side PDF signing through eID Mongolia's
/v3interface, using a long-lived Document-Signer certificate (fail-closed in production). - Sign relay — a gateway that lets third-party relying parties sign through the platform's eID credentials. This route is served straight from the edge to the API over a loopback port rather than through the web BFF; results are delivered by webhook.
For the distinction between a citizen's PIN2 signature and a system Document-Signer signature, see G-Sign.
AI assistant (Gemini)¶
A pipeline built on an SDK-free REST client:
| Capability | Notes |
|---|---|
| Chat | Text and voice messages, function calling |
| STT | Speech → text |
| TTS | Text → speech (PCM→WAV) |
| Translation | Live streaming translation |
A three-layer system prompt: hard-coded guardrails plus a scope and instructions configurable by admins in the database. The guardrail layer is never configurable.
The search_knowledge tool grounds answers in the knowledge base. Search is
semantic — Gemini embeddings with pgvector cosine similarity, falling back
to ILIKE.
A transient Gemini failure does not become a 5xx: chat returns a localised
fallback reply (degraded: true). The /ai/* routes are limited to roughly 20
requests per minute per IP.
Integrations and storage¶
- Third-party OAuth connections — Google Drive · Google Meet · Dropbox. Tokens are stored encrypted with AES-256-GCM; without credentials configured the card stays inert with a "coming soon" state.
- Gerege Space — the platform's own SFTP storage with a per-user quota. The SFTP host key is verified (mandatory in production, otherwise fail-closed).
Roles, administration and audit¶
- RBAC — dynamic roles and a permission catalogue, in a four-tier model
(
superadmin → admin → manager → user). - Super admin — a separate account with an MFA onboarding flow (invite allow-list → Google → eID → email OTP → TOTP with recovery codes). It lives in its own table, so one person can be both an eID admin and a super admin.
- Audit log — hash-chained and append-only, readable by admins with an integrity-verification endpoint.
- Security events — ingest plus a monitoring view.
- Site appearance — admin-configured accent / font / density / theme, with a per-user override.
Security¶
| Control | Implementation |
|---|---|
| Data isolation | Postgres RLS (ENABLE + FORCE); the api connects as a non-superuser role, and a boot guard verifies that RLS actually applies |
| Session | httpOnly cookies; tokens never reach client JS |
| CSRF | Double protection — a custom header plus an origin check on every mutating BFF route |
| Headers | CSP · HSTS · COOP/COEP/CORP, CORS allow-list |
| Rate limits | Auth ~5 req/min (with a 4 KiB body cap), app 50 r/s, /ai/* ~20/min |
| Database connection | sslmode=verify-full in production, against an internal CA |
| Observability endpoints | /metrics and /swagger are bearer-token gated in production |
| Proxy trust | With TRUSTED_PROXIES unset, X-Forwarded-For is not trusted — preventing rate-limit and audit spoofing |
For the ecosystem-wide baseline see Security.
Observability¶
OpenTelemetry traces, Prometheus metrics and structured Zap logs. The service
appears in telemetry as gerege-kiosk.
Deployment¶
A Docker Compose stack: db (Postgres 16 with pgvector) · redis · migrate
(one-off) · api · web. The browser only ever reaches web; api, db and redis
stay on the internal network with no public ports.
Three deployment decisions are worth noting:
- Migrations are a separate step, not part of
up -d. Previously every migrate re-run recreated api and web, producing a second of 502s even on commits that touched no code. - If nothing changed, nothing moves. Docker builds are not reproducible — the same source yields a new image ID — so when HEAD is unchanged the deploy is skipped entirely.
- Stateful services are not rebuilt on every deploy. Recreating
dbwould drop every live database connection.
The edge nginx vhost is owned by this repository: each deploy installs it into
conf.d, runs nginx -t and reloads; if the test fails the previous config is
restored. This is the same pattern used by
docs.gerege.mn, the Developer Portal and the
Template Platform.
Languages¶
The UI and documentation are available in Mongolian · English · 中文 · Русский. A test enforces dictionary completeness in the frontend — a key missing from any one language fails CI.
Current status¶
Production Running at geregekiosk.mn. The full platform foundation is inherited; terminal-specific flows continue to be added.
Next up: real gateway enforcement, streaming (SSE) chat responses, nonce-based CSP, automated database backups with restore tests, and a staging environment.
Related pages¶
- Gerege SSO — Kiosk's identity source and eID proxy
- eID Mongolia — the identity and PKI core
- G-Sign — the signing gateway
- Gerege Verify — organisation lookups
- Gerege Template Platform — the inherited foundation
- Layered architecture — where Kiosk sits