# Cygnus cloud service ## Identity login API `POST /api/v1/identity/login` requires a valid machine JWT with the `identity.login` scope. The JWT must carry `client_id` and `installation_id`; both must equal the values inside the encrypted payload. The request uses a hybrid encrypted envelope: ```json { "keyId": "cygnus-login-2026-01", "encryptedKey": "base64 RSA-OAEP-SHA256 encrypted AES key", "initializationVector": "base64 12-byte AES-GCM IV", "encryptedPayload": "base64 AES-GCM ciphertext and tag" } ``` The AES-GCM additional authenticated data is the UTF-8 `keyId`. The decrypted JSON is: ```json { "loginId": "user", "password": "password", "clientId": "client-id-from-jwt", "installationId": "installation-id-from-jwt", "nonce": "unique-random-value", "issuedAt": "2026-07-23T06:30:00Z" } ``` Configure the PKCS#8 RSA private key with `CYGNUS_LOGIN_PRIVATE_KEY=file:/secure/path/login-private.pem`. Keep this key outside the source tree and container image. The corresponding public key is distributed to the on-prem gateway. The database bootstrap is `src/main/resources/db/identity/001_identity_login_schema.sql`. It is transactional and idempotent; it copies login/menu data from `matrix.public` to `matrix.identity`. It is intended for initial migration and controlled development refreshes. Do not run it after `identity` becomes the production system of record because its upserts intentionally refresh rows from `public`. ## Machine token endpoint `POST /oauth2/token` implements the client-credentials flow used by the on-premises gateway. The client assertion must be: - an inner RS256 JWT signed with the installation private key; - encrypted as RSA-OAEP-256 plus AES-256-GCM using the cloud assertion key; - bound to the configured client ID, installation ID, and token audience; - unexpired and no longer-lived than `CYGNUS_ASSERTION_TTL`. The endpoint returns a short-lived RS256 access token carrying the client, installation, tenant, license, security-version, and approved-scope claims. The identity endpoint requires the `identity.login` scope and verifies the same machine and tenant binding in the encrypted login payload. Generate separate cloud key pairs: ```bash mkdir -p config/keys openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 \ -out config/keys/assertion-decryption-private.pem openssl pkey -in config/keys/assertion-decryption-private.pem -pubout \ -out config/keys/assertion-decryption-public.pem openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 \ -out config/keys/access-token-private.pem openssl pkey -in config/keys/access-token-private.pem -pubout \ -out config/keys/access-token-public.pem chmod 600 config/keys/*private.pem ``` ## Dynamic tenant, installation, and license registration Machine clients are no longer configured in a runtime `clients.yml`. The authoritative records are: - `identity.client_account`: tenant identity and status; - `identity.client_installation`: machine identity, assertion public key, allowed scopes, enabled state, and security version; - `identity.client_license`: subscription period, package, type, status, and licensed limits. Token issuance resolves the installation and active license through a Redis cache-aside service with PostgreSQL fallback. Cache entries have a bounded TTL and can be invalidated after administrative changes. Therefore, new customers, installations, key rotations, scope changes, and license changes do not require restarting the cloud service. The login/menu queries are tenant-scoped. Tenant-owned identity tables carry `tenant_id`; `identity.pages` remains the shared feature catalog while permissions are assigned per tenant. Use `scripts/setup-local-communication.sh` to create keys, register or update the database records, create the initial license, and generate the on-premises machine assertion. Never place cloud private keys, customer assertions, or installation private keys in the repository or container image.