ores.iam.account_credential
Table of Contents
The secret an account proves when it authenticates, held apart from the
account's own profile. One row per account, keyed by account_id, and no
row for an account that holds no credential.
The split exists because the two are written by different access paths.
ores.iam.account is the profile: a screen writes it, and every column
travels on the wire. This entity is the credential: the IAM service alone
writes it, and no column travels anywhere. Held in one table, a profile
write replaced the whole row, and a column the domain type could not carry
came back empty.
Every secret column is :no_wire:, so the domain struct carries the value
the service needs while a serialisation of that struct omits it.
The table is bi-temporal and audited, like every other iam entity: it
carries version, the four audit columns and the valid_from=/=valid_to
pair with the GIST exclusion, so the model takes the ordinary audited
shape and needs no shape flag. A credential write therefore keeps its own
history, and the account's history never mentions it.
The entity is projected across the whole stack, exactly as the account is: SQL
schema and notify trigger, domain type, repository, generator, protocol, NATS
handler and sub-registrar, generated CRUD service, history provider,
presentation mapper, shell command, HTTP route, eventing, and the TypeScript
twins. The secrets stay off the wire because the columns are :no_wire:, not
because the entity has no wire surface. See NATS entity protocol specification.
The generated half is reads-only, and that is the specification's answer
rather than ours. A write carries "the fields the user owns, and no others",
and a server-owned field never appears in one; a credential's only meaningful
field is server-derived, so the resource has no write record to carry, and "a
read-only resource simply declares no write verb". The credential's writes are
domain operations, iam.v1.ops.*.
1. Flags
2. Columns
2.1. id
Unique identifier for the credential row. A surrogate, like the account's own: nothing outside this table names it.
2.2. account_id
The account this credential belongs to. One row per account, so the
generated table adds the unique index on (tenant_id, account_id).
uuid_gen()
2.3. password_hash
The hash a user account proves when it signs in. Unset for an account that cannot sign in with a password: a service, algorithm or llm account.
The stored hash embeds its own salt, so no salt column sits beside it.
ctx.alphanumeric(64)
2.4. service_password_hash
The SHA-256 hash, as hex, of a service account's machine password. Unset for an account that does not authenticate as a service.
ctx.alphanumeric(64)
2.5. totp_secret
Time-based One-Time Password secret for two-factor authentication. No verification path reads it yet, and no caller may ever see it.
ctx.alphanumeric(32)
3. SQL
3.1. Flags
4. Foreign keys
4.1. account_id
The account this credential belongs to, validated against the accounts
table. The key is not flagged :bump_parent_version:: a credential write
is not a change to the account, and the account's history must not record
one.
5. C++
5.1. Flags
5.2. Repository
5.3. Domain includes
#include <boost/uuid/uuid.hpp> #include <string>
5.4. Entity includes
#include <string>
5.5. Conventions
5.6. Table display
The columns an operator reads when a credential row is printed. No secret is among them: the table exists to say which account holds a credential and who last wrote it.
| column | header |
|---|---|
| account_id | Account |
| id | ID (UUID) |
| modified_by | Modified By |
| version | Version |
6. Physical space
Nothing is withdrawn. The entity projects like any other; what keeps the
secrets in is the :no_wire: flag on the columns, not the absence of a
surface.
7. See also
- ores.iam — component group overview.
- ores.iam.account — the profile this credential belongs to.