io7m | single-page | multi-page | epub | Idstore User Manual 2.0.1

Idstore User Manual 2.0.1

DATE 2022-09-20T08:37:35+00:00
DESCRIPTION User manual for the idstore server.
IDENTIFIER 24c8402e-36b8-4d2b-ab1e-b501050e9611
LANGUAGE en
SOURCE https://www.io7m.com/software/idstore/
TITLE Idstore User Manual 2.0.1
The idstore package provides a server and client for managing user accounts.

1.2. Features

  • Simple, centralized identity storage and password checking. Passwords are securely stored using PBKDF2.
  • Email-based password reset functionality with a minimalist web interface.
  • Full API access for all operations: Separate user-facing and administrator-facing APIs are exposed on different ports and are accessed using an efficient binary protocol over HTTP.
  • Full Java API for performing user and administrative operations.
  • Strong separation between administrators and users.
  • Fine-grained capability based security model for administrative operations; Safely write external services that can perform administrative operations while maintaining the principle of least privilege.
  • Command-line administrative shell.
  • Complete audit log; every operation that changes the state of the system is logged in an append-only log.
  • Fully instrumented with OpenTelemetry.
  • A small, easily auditable codebase with a heavy use of modularity for correctness.
  • An extensive automated test suite with high coverage.
  • Platform independence. No platform-dependent code is included in any form, and installations can largely be carried between platforms without changes.
  • OSGi-ready
  • JPMS-ready
  • Support for Canonmill keystores.
  • ISC license.
The idstore server package is available from the following sources:
Regardless of the distribution method, the idstore package will contain a command named idstore that acts as the main entrypoint to all of the server and client functionality.
The idstore server requires a PostgreSQL server. The idstore server will create the required tables and database objects on first startup, given the name of a running PostgreSQL database, and a PostgreSQL role and password.
A distribution package can be found at Maven Central.
The idstore command requires that a Java 21+ compatible JVM be accessible via /usr/bin/env java.
Verify the integrity of the distribution zip file:

2.3.4. Verify

$ gpg --verify com.io7m.idstore.main-2.0.1-distribution.zip.asc
gpg: assuming signed data in 'com.io7m.idstore.main-2.0.1-distribution.zip.asc'
gpg: Signature made Tue 28 Jun 2022 15:01:56 GMT
gpg:                using RSA key 3CCE59428B30462D10459909C5607DA146E128B8
gpg:                issuer "contact@io7m.com"
gpg: using pgp trust model
gpg: Good signature from "io7m.com (2022 maven-rsa-key) <contact@io7m.com>" [unknown]
Unzip the zip file, and set up the environment appropriately. The idstore command expects an environment variable named IDSTORE_HOME to be defined that points to the installation directory.

2.3.6. Extract

$ unzip com.io7m.idstore.main-2.0.1-distribution.zip
$ export IDSTORE_HOME=$(realpath idstore)
$ ./idstore/bin/idstore
idstore: usage: idstore [command] [arguments ...]
...
OCI images are available from Quay.io for use with podman or docker.

2.4.1.2. Podman/Docker

$ podman pull quay.io/io7mcom/idstore:2.0.1
$ podman run quay.io/io7mcom/idstore:2.0.1
idstore: usage: idstore [command] [arguments ...]
...
The OCI image includes an idstore-healthcheck command that makes a request to a health endpoint on the Admin API server. The command expects the server to be accessible on localhost inside the container, and requires that the IDSTORE_HEALTHCHECK_PORT environment variable be set to the port used by the Admin API. By default, the container sets IDSTORE_HEALTHCHECK_PORT to 51000 and therefore there is no need to set this variable manually if the server is configured with the default settings.
This feature may only be available when running the image under docker due to limitations in the OCI image specification. The functionality of the health check service can be used directly via the Admin API or the User API.

2.4.2.3. Health Example

$ curl http://localhost:51000/health
OK
Given an appropriate configuration file in server.conf, it's necessary to tell idstore to configure the database and create an initial administrator:

2.5.1.2. Initialize

$ idstore initial-admin \
  --admin-id '92f83bce-3973-4db8-8aaf-d401443a9772' \
  --admin-email 'someone@example.com' \
  --admin-realname 'Someone Else' \
  --admin-username 'someone' \
  --admin-password 12345678 \
  --configuration server.conf
The server can now be run with idstore server:

2.5.2.2. Run

$ idstore server --configuration server.conf
info: [localhost/<unresolved>:50000] User API server started
info: [localhost/<unresolved>:50001] User view server started
info: [localhost/<unresolved>:51000] Admin API server started
The server does not fork into the background and is designed to be run under process supervision.
Run idstore shell [1].

2.6.2. Shell

$ idstore shell
[idstore]# version
com.io7m.idstore 0.0.15-SNAPSHOT 20af71248a7784b0e5247eab4b1ebd28de284739
The idstore package uses PostgreSQL for all persistent data.
The idstore package sets up multiple roles during database initialization. The configured roles have different degrees of privileges in order to allow, for example, external systems such as database metrics collectors read-only access to the database. All the defined rules are declared with the built-in PostgreSQL restrictions such as nocreatedb, nocreaterole, etc.
During the startup of the idstore server, the server will connect to the database using the owner role and do any database table initialization and/or schema upgrades necessary. The server will then disconnect from the database, and then connect to the database again using the worker role. The worker role is then used for normal operation of the server; if this role is somehow compromised, the role only has a limited ability to do any damage to the database, and cannot affect the audit log at all.
The owner role is the role that owns the database and is permitted to create tables, create new roles, etc. This role is used by the idstore package when creating the database during the first run of the server, and for upgrading database schemas later. Administrators are free to pick the name of the role, although it is recommended that the role be named idstore_install to make it clear as to the purpose of the role.
If the PostgreSQL OCI image is used, it is common to have the image create this role automatically using the POSTGRES_USER and POSTGRES_PASSWORD variables:

2.7.2.2.3. Example

$ podman run \
  --name some-postgres \
  -e POSTGRES_USER=idstore_install \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -d postgres
The worker role is the role that is used for normal database operation. It is a role that has read/write access to all tables (except for the audit log which is restricted to being append-only), although it is not granted the ability to create new tables, drop tables, or do other schema manipulation. The role is always named idstore, and adminstrators are required to set a password for this role.
The reader role is a role that is permitted read-only access to some of the database. It is effectively an optional role that can be used by various database metrics systems if required. If a password is not specified for the role in the server's configuration file, then logging in is not permitted at all.
The idstore package requires a working SMTP MTA. The MTA is used to send email to users.

Footnotes

1
If running under podman or docker, remember to use the -i and -t options.
References to this footnote: 1
The idstore server is configured using a single XML-formatted configuration file. The format has a fully documented schema and so configuration files can be independently validated and benefit from autocompletion in most modern IDEs.
The configuration file must consist of a single top-level Configuration element in the urn:com.io7m.idstore:configuration:1 namespace. In modern IDEs, simply creating a file containing this element will immediately fill in all the other required child elements.
The smallest working configuration file, assuming a database at db.example.com and an SMTP server at mail.example.com:

3.2.2. Example

<?xml version="1.0" encoding="UTF-8" ?>

<i:Configuration xmlns:i="urn:com.io7m.idstore:configuration:1"
                 xmlns:it="urn:com.io7m.idstore.tls:1">

  <i:Branding ProductTitle="idstore"/>

  <i:Database Name="idstore"
              Kind="POSTGRESQL"
              OwnerRoleName="idstore_install"
              OwnerRolePassword="mydatabasewill"
              WorkerRolePassword="probablybecompromised"
              Address="db.example.com"
              Port="5432"
              Create="true"
              Upgrade="true"/>

  <i:HTTPServices>
    <i:HTTPServiceAdminAPI ListenAddress="[::]"
                           ListenPort="51000"
                           ExternalURI="https://[::]:51000/">
      <it:TLSEnabled>
        <it:KeyStore Type="CANONMILL"
                     Provider="CANONMILL"
                     Password="changeit"
                     File="keystore.xml"/>
        <it:TrustStore Type="CANONMILL"
                       Provider="CANONMILL"
                       Password="changeit"
                       File="truststore.xml"/>
      </it:TLSEnabled>
    </i:HTTPServiceAdminAPI>
    <i:HTTPServiceUserAPI ListenAddress="[::]"
                          ListenPort="50000"
                          ExternalURI="http://[::]:50000/">
      <it:TLSDisabled/>
    </i:HTTPServiceUserAPI>
    <i:HTTPServiceUserView ListenAddress="[::]"
                           ListenPort="50001"
                           ExternalURI="http://[::]:50001/">
      <it:TLSDisabled/>
    </i:HTTPServiceUserView>
  </i:HTTPServices>

  <i:History UserLoginHistoryLimit="10"
             AdminLoginHistoryLimit="100"/>

  <i:Mail SenderAddress="no-reply@example.com"
          VerificationExpiration="PT24H">
    <i:SMTP Host="mail.example.com"
            Port="25"/>
  </i:Mail>

  <i:Maintenance/>

  <i:RateLimiting EmailVerificationRateLimit="PT10M"
                  PasswordResetRateLimit="PT10M"/>

  <i:Sessions UserSessionExpiration="PT30M"
              AdminSessionExpiration="PT30M"/>

</i:Configuration>
The Mail section of the configuration file configures the mail service. The server sends mail to users to verify emails and to handle password resets.
The Transport section of the Mail configuration specifies the mail server transport method. This must be one of SMTP, SMTP_TLS, or SMTPS.
The Host and Port attributes specify the host and port of the upstream MTA.
The SenderAddress attribute specifies the address that will be used on outgoing mail.
The VerificationExpiration attribute specifies how long links sent in emails will take to expire. These links are currently used for password resets, and email verification. The property must be formatted as an ISO 8601 duration string.
An example SMTP configuration:

3.3.5.2. Example

<Mail SenderAddress="no-reply@example.com"
      VerificationExpiration="PT24H">
  <SMTP Host="mail.example.com"
        Port="25"/>
</Mail>
The Maintenance section of the configuration file configures the maintenance service.
The TLSReloadInterval attribute specifies how frequently the server will reload the TLS certificates for the HTTP services.
An example maintenance configuration:

3.4.3.2. Example

<Maintenance TLSReloadInterval="PT30M"/>
The HTTP section of the configuration file configures the various HTTP services.
The configuration for the Admin API service.
The ListenAddress and ListenPort attributes specify the address and port to which to the HTTP service will bind.
The ExternalAddress attribute specifies the external address that clients will use to connect to this server. This value is used in, for example, password reset emails to provide URLs that users can follow to reset their passwords.
By convention, the Admin API should listen on TCP port 51000.
The HTTPServiceAdminAPI element must contain either a TLSEnabled or TLSDisabled element specifying whether TLS should be enabled or disabled, respectively. The TLSEnabled element describes the key store and trust store. The idstore server automatically reloads certificates periodically in order to work well in environments using the ACME protocol to issue certificates.
The configuration for the User API service.
The ListenAddress and ListenPort attributes specify the address and port to which to the HTTP service will bind.
The ExternalAddress attribute specifies the external address that clients will use to connect to this server. This value is used in, for example, password reset emails to provide URLs that users can follow to reset their passwords.
By convention, the User API should listen on TCP port 50000.
The HTTPServiceUserAPI element must contain either a TLSEnabled or TLSDisabled element specifying whether TLS should be enabled or disabled, respectively. The TLSEnabled element describes the key store and trust store. The idstore server automatically reloads certificates periodically in order to work well in environments using the ACME protocol to issue certificates.
The configuration for the User View service.
The ListenAddress and ListenPort attributes specify the address and port to which to the HTTP service will bind.
The ExternalAddress attribute specifies the external address that clients will use to connect to this server. This value is used in, for example, password reset emails to provide URLs that users can follow to reset their passwords.
By convention, the User API should listen on TCP port 50001.
The HTTPServiceUserView element must contain either a TLSEnabled or TLSDisabled element specifying whether TLS should be enabled or disabled, respectively. The TLSEnabled element describes the key store and trust store. The idstore server automatically reloads certificates periodically in order to work well in environments using the ACME protocol to issue certificates.
An example HTTP configuration:

3.5.5.2. Example

<HTTPServices>
  <HTTPServiceAdminAPI ListenAddress="localhost"
                       ListenPort="51000"
                       ExternalURI="http://localhost:51000/">
    <it:TLSEnabled>
      <it:KeyStore Type="CANONMILL"
                   Provider="CANONMILL"
                   Password="changeit"
                   File="keystore.xml"/>
      <it:TrustStore Type="CANONMILL"
                   Provider="CANONMILL"
                   Password="changeit"
                   File="truststore.xml"/>
    </it:TLSEnabled>
  </HTTPServiceAdminAPI>
  <HTTPServiceUserAPI ListenAddress="localhost"
                      ListenPort="50000"
                      ExternalURI="http://localhost:50000/">
    <it:TLSDisabled/>
  </HTTPServiceUserAPI>
  <HTTPServiceUserView ListenAddress="localhost"
                       ListenPort="50001"
                       ExternalURI="http://localhost:50001/">
    <it:TLSDisabled/>
  </HTTPServiceUserView>
</HTTPServices>
The Sessions section of the configuration file specifies parameters for controlling session expiration.
The UserSessionExpiration attribute specifies the duration after which idle user sessions on the server will expire. The value must be expressed as an ISO 8601 duration string.
The AdminSessionExpiration attribute specifies the duration after which idle admin sessions on the server will expire. The value must be expressed as an ISO 8601 duration string.
An example session configuration:

3.6.3.2. Example

<Sessions UserSessionExpiration="PT30M"
          AdminSessionExpiration="PT30M"/>
The History section of the configuration file specifies retention parameters for login histories.
An example history configuration:

3.7.2.2. Example

<History UserLoginHistoryLimit="100"
         AdminLoginHistoryLimit="10000"/>
The Database section of the configuration file configures the database.
The OwnerRoleName attribute specifies the name of the role that owns the database. Conventionally, this should be idstore_install, but can be set independently by the database administrator.
The OwnerRolePassword attribute specifies the password of the owner role.
The WorkerRolePassword attribute specifies the password of the worker role used for normal database operation.
The ReaderRolePassword attribute specifies the password of the reader role used for read-only database access. If this attribute is not specified, logging in using this role will be prevented.
An example database configuration:

3.8.2.2. Example

<Database Kind="POSTGRESQL"
          OwnerRoleName="idstore_install"
          OwnerRolePassword="mydatabasewill"
          WorkerRolePassword="probablybecompromised"
          ReaderRolePassword="lookbutdonottouch"
          Address="localhost"
          Port="54322"
          Create="true"
          Upgrade="true"/>
The RateLimiting section of the configuration file configures rate limiting for various server operations. Primarily, rate limiting is currently useful to limit any outgoing email that the server might send; unauthenticated clients could, for example, attempt to spam users with password reset emails. Setting a rate limit such that only one password reset email can be sent every ten minutes for a given client can reduce the possible damage.
The EmailVerificationRateLimit attribute specifies the minimum duration that must elapse between any two email verification requests for a given user. The attribute must be specified as an ISO 8601 duration string. Setting this attribute to PT0S (a duration of 0 seconds) effectively disables this kind of rate limiting.
The PasswordResetRateLimit attribute specifies the minimum duration that must elapse between any two password reset requests from a single IP address. The attribute must be specified as an ISO 8601 duration string. Setting this attribute to PT0S (a duration of 0 seconds) effectively disables this kind of rate limiting.
The optional UserLoginRateLimit attribute specifies the minimum duration that must elapse between any two user login requests from a single IP address. The attribute must be specified as an ISO 8601 duration string. The default value, if unspecified, is five seconds. Setting this attribute to PT0S (a duration of 0 seconds) effectively disables this kind of rate limiting.
The optional UserLoginDelay attribute specifies a fixed delay that is applied to all user login requests to reduce the practicality of online password brute forcing. The attribute must be specified as an ISO 8601 duration string. The default value, if unspecified, is one second. Setting this attribute to PT0S (a duration of 0 seconds) effectively disables this kind of rate limiting.
The optional AdminLoginRateLimit attribute specifies the minimum duration that must elapse between any two admin login requests from a single IP address. The attribute must be specified as an ISO 8601 duration string. The default value, if unspecified, is five seconds. Setting this attribute to PT0S (a duration of 0 seconds) effectively disables this kind of rate limiting.
The optional AdminLoginDelay attribute specifies a fixed delay that is applied to all admin login requests to reduce the practicality of online password brute forcing. The attribute must be specified as an ISO 8601 duration string. The default value, if unspecified, is one second. Setting this attribute to PT0S (a duration of 0 seconds) effectively disables this kind of rate limiting.
An example rate limit configuration:

3.9.2.2. Example

<RateLimiting
  EmailVerificationRateLimit="PT10M"
  PasswordResetRateLimit="PT10M"
  UserLoginRateLimit="PT5S"
  UserLoginDelay="PT1S"
  AdminLoginRateLimit="PT5S"
  AdminLoginDelay="PT1S" />
Passwords for both users and administrators can be configured with expiration dates. The PasswordExpiration section of the configuration file, if present, can specify durations for which passwords are valid after their initial creation.
The optional UserPasswordValidityDuration attribute specifies the duration for which user passwords are valid. If the attribute is not present, user passwords do not expire.
The optional AdminPasswordValidityDuration attribute specifies the duration for which admin passwords are valid. If the attribute is not present, user passwords do not expire.
Note that these configuration settings only apply to users and administrators that have not yet been created; existing users and administrators will not be affected if these settings are changed and the server restarted. New configuration settings will take effect, however, if the passwords of the users and administrators are changed.
An example password expiration configuration that configures user passwords to expire in 30 days (720 hours), and administrator passwords to expire in 90 days (2160 hours):

3.10.2.2. Example

<PasswordExpiration
  UserPasswordValidityDuration="PT720H"
  AdminPasswordValidityDuration="PT2160H" />
Note: These settings would likely annoy users and administrators alike, and would likely incentivize setting weaker passwords.
The OpenTelemetry section of the configuration file configures Open Telemetry. This section is optional and telemetry is disabled if the section is not present.
The logical service name should be provided in the LogicalServiceName attribute.
If the OpenTelemetry element contains a Traces element, OTLP traces will be sent to a specified endpoint. The Endpoint attribute specifies the endpoint, and the Protocol attribute can either be GRPC or HTTP.
If the OpenTelemetry element contains a Metrics element, OTLP metrics will be sent to a specified endpoint. The Endpoint attribute specifies the endpoint, and the Protocol attribute can either be GRPC or HTTP.
If the OpenTelemetry element contains a Logs element, OTLP logs will be sent to a specified endpoint. The Endpoint attribute specifies the endpoint, and the Protocol attribute can either be GRPC or HTTP.
An example Open Telemetry configuration:

3.11.5.2. Example

<OpenTelemetry LogicalServiceName="idstore">
  <Logs Endpoint="http://logs.example.com:4317"
        Protocol="GRPC"/>
  <Metrics Endpoint="http://metrics.example.com:4317"
           Protocol="GRPC"/>
  <Traces Endpoint="http://traces.example.com:4317"
          Protocol="GRPC"/>
</OpenTelemetry>
The Branding section of the configuration file configures branding.
An example branding configuration:

3.12.2.2. Example

<Branding ProductTitle="idstore">
  <ColorScheme>
    <ButtonColors>
      <Disabled>
        <BodyColor Red="0.101"
                   Green="0.309"
                   Blue="0.45"/>
        <BorderColor Red="0"
                     Green="0"
                     Blue="0"/>
        <EmbossEColor Red="0.175"
                      Green="0.527"
                      Blue="0.765"/>
        <EmbossNColor Red="0.175"
                      Green="0.527"
                      Blue="0.765"/>
        <EmbossSColor Red="0.07"
                      Green="0.214"
                      Blue="0.316"/>
        <EmbossWColor Red="0.07"
                      Green="0.214"
                      Blue="0.316"/>
        <TextColor Red="0"
                   Green="0"
                   Blue="0"/>
      </Disabled>
      <Enabled>
        <BodyColor Red="0.101"
                   Green="0.309"
                   Blue="0.45"/>
        <BorderColor Red="0"
                     Green="0"
                     Blue="0"/>
        <EmbossEColor Red="0.175"
                      Green="0.527"
                      Blue="0.765"/>
        <EmbossNColor Red="0.175"
                      Green="0.527"
                      Blue="0.765"/>
        <EmbossSColor Red="0.07"
                      Green="0.214"
                      Blue="0.316"/>
        <EmbossWColor Red="0.07"
                      Green="0.214"
                      Blue="0.316"/>
        <TextColor Red="0"
                   Green="0"
                   Blue="0"/>
      </Enabled>
      <Hover>
        <BodyColor Red="0.125"
                   Green="0.371"
                   Blue="0.539"/>
        <BorderColor Red="0"
                     Green="0"
                     Blue="0"/>
        <EmbossEColor Red="0.175"
                      Green="0.527"
                      Blue="0.765"/>
        <EmbossNColor Red="0.175"
                      Green="0.527"
                      Blue="0.765"/>
        <EmbossSColor Red="0.07"
                      Green="0.214"
                      Blue="0.316"/>
        <EmbossWColor Red="0.07"
                      Green="0.214"
                      Blue="0.316"/>
        <TextColor Red="0"
                   Green="0"
                   Blue="0"/>
      </Hover>
      <Pressed>
        <BodyColor Red="0.093"
                   Green="0.277"
                   Blue="0.406"/>
        <BorderColor Red="0"
                     Green="0"
                     Blue="0"/>
        <EmbossEColor Red="0.07"
                      Green="0.214"
                      Blue="0.316"/>
        <EmbossNColor Red="0.07"
                      Green="0.214"
                      Blue="0.316"/>
        <EmbossSColor Red="0.125"
                      Green="0.371"
                      Blue="0.539"/>
        <EmbossWColor Red="0.125"
                      Green="0.371"
                      Blue="0.539"/>
        <TextColor Red="0"
                   Green="0"
                   Blue="0"/>
      </Pressed>
    </ButtonColors>

    <ErrorBorderColor Red="1"
                      Green="0"
                      Blue="0"/>

    <HeaderBackgroundColor Red="0.184"
                           Green="0.184"
                           Blue="0.184"/>

    <HeaderLinkColor Red="1"
                     Green="1"
                     Blue="1"/>

    <HeaderTextColor Red="1"
                     Green="1"
                     Blue="1"/>

    <MainBackgroundColor Red="0.101"
                         Green="0.309"
                         Blue="0.45"/>

    <MainLinkColor Red="1"
                   Green="1"
                   Blue="1"/>

    <MainMessageBorderColor Red="0.1875"
                            Green="0.558"
                            Blue="0.8125"/>

    <MainTableBorderColor Red="0.1875"
                          Green="0.558"
                          Blue="0.8125"/>

    <MainTextColor Red="1"
                   Green="1"
                   Blue="1"/>
  </ColorScheme>
</Branding>
The XSD schema for the configuration file is as follows:

3.13.2. Configuration Schema

<?xml version="1.0" encoding="UTF-8" ?>

<!--
  Copyright © 2023 Mark Raynsford <code@io7m.com> https://www.io7m.com

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above
  copyright notice and this permission notice appear in all copies.

  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
  SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
  IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-->

<schema xmlns="http://www.w3.org/2001/XMLSchema"
        xmlns:c="urn:com.io7m.idstore:configuration:1"
        xmlns:ct="urn:com.io7m.idstore.tls:1"
        targetNamespace="urn:com.io7m.idstore:configuration:1">

  <import namespace="urn:com.io7m.idstore.tls:1"/>

  <simpleType name="ColorComponent">
    <annotation>
      <documentation>
        The type of a component within a color.
      </documentation>
    </annotation>
    <restriction base="double">
      <minInclusive value="0.0"/>
      <maxInclusive value="1.0"/>
    </restriction>
  </simpleType>

  <complexType name="ColorType">
    <annotation>
      <documentation>
        An RGB color.
      </documentation>
    </annotation>

    <attribute name="Red"
               type="c:ColorComponent"
               use="required"/>
    <attribute name="Green"
               type="c:ColorComponent"
               use="required"/>
    <attribute name="Blue"
               type="c:ColorComponent"
               use="required"/>
  </complexType>

  <element name="ErrorBorderColor"
           type="c:ColorType">
    <annotation>
      <documentation>
        The color used for the border around error messages on web pages.
      </documentation>
    </annotation>
  </element>

  <element name="HeaderBackgroundColor"
           type="c:ColorType">
    <annotation>
      <documentation>
        The color used for the web page header background.
      </documentation>
    </annotation>
  </element>

  <element name="HeaderLinkColor"
           type="c:ColorType">
    <annotation>
      <documentation>
        The color used for the web page header links.
      </documentation>
    </annotation>
  </element>

  <element name="HeaderTextColor"
           type="c:ColorType">
    <annotation>
      <documentation>
        The color used for the web page header text.
      </documentation>
    </annotation>
  </element>

  <element name="MainBackgroundColor"
           type="c:ColorType">
    <annotation>
      <documentation>
        The color used for the web page main background.
      </documentation>
    </annotation>
  </element>

  <element name="MainLinkColor"
           type="c:ColorType">
    <annotation>
      <documentation>
        The color used for the web page main links on web pages.
      </documentation>
    </annotation>
  </element>

  <element name="MainMessageBorderColor"
           type="c:ColorType">
    <annotation>
      <documentation>
        The color used for the border around non-error messages on web pages.
      </documentation>
    </annotation>
  </element>

  <element name="MainTableBorderColor"
           type="c:ColorType">
    <annotation>
      <documentation>
        The color used for table borders on web pages.
      </documentation>
    </annotation>
  </element>

  <element name="MainTextColor"
           type="c:ColorType">
    <annotation>
      <documentation>
        The color used for the main text on web pages.
      </documentation>
    </annotation>
  </element>

  <element name="TextColor"
           type="c:ColorType">
    <annotation>
      <documentation>
        The color used for the button text.
      </documentation>
    </annotation>
  </element>

  <element name="BodyColor"
           type="c:ColorType">
    <annotation>
      <documentation>
        The color used for the button body.
      </documentation>
    </annotation>
  </element>

  <element name="BorderColor"
           type="c:ColorType">
    <annotation>
      <documentation>
        The color used for the button border.
      </documentation>
    </annotation>
  </element>

  <element name="EmbossEColor"
           type="c:ColorType">
    <annotation>
      <documentation>
        The color used for the east-facing emboss.
      </documentation>
    </annotation>
  </element>

  <element name="EmbossSColor"
           type="c:ColorType">
    <annotation>
      <documentation>
        The color used for the south-facing emboss.
      </documentation>
    </annotation>
  </element>

  <element name="EmbossWColor"
           type="c:ColorType">
    <annotation>
      <documentation>
        The color used for the west-facing emboss.
      </documentation>
    </annotation>
  </element>

  <element name="EmbossNColor"
           type="c:ColorType">
    <annotation>
      <documentation>
        The color used for the north-facing emboss.
      </documentation>
    </annotation>
  </element>

  <complexType name="ButtonStateColors">
    <annotation>
      <documentation>
        The colors used for a particular button state.
      </documentation>
    </annotation>
    <sequence>
      <element ref="c:BodyColor"/>
      <element ref="c:BorderColor"/>
      <element ref="c:EmbossEColor"/>
      <element ref="c:EmbossNColor"/>
      <element ref="c:EmbossSColor"/>
      <element ref="c:EmbossWColor"/>
      <element ref="c:TextColor"/>
    </sequence>
  </complexType>

  <element name="Enabled"
           type="c:ButtonStateColors">
    <annotation>
      <documentation>
        The colors used when the button is in the unpressed, enabled state.
      </documentation>
    </annotation>
  </element>

  <element name="Disabled"
           type="c:ButtonStateColors">
    <annotation>
      <documentation>
        The colors used when the button is in the disabled state.
      </documentation>
    </annotation>
  </element>

  <element name="Pressed"
           type="c:ButtonStateColors">
    <annotation>
      <documentation>
        The colors used when the button is in the pressed state.
      </documentation>
    </annotation>
  </element>

  <element name="Hover"
           type="c:ButtonStateColors">
    <annotation>
      <documentation>
        The colors used when the button is in the hover state.
      </documentation>
    </annotation>
  </element>

  <element name="ButtonColors">
    <annotation>
      <documentation>
        The colors used for all button states.
      </documentation>
    </annotation>
    <complexType>
      <sequence>
        <element ref="c:Disabled"/>
        <element ref="c:Enabled"/>
        <element ref="c:Hover"/>
        <element ref="c:Pressed"/>
      </sequence>
    </complexType>
  </element>

  <element name="ColorScheme">
    <complexType>
      <sequence>
        <element ref="c:ButtonColors"/>
        <element ref="c:ErrorBorderColor"/>
        <element ref="c:HeaderBackgroundColor"/>
        <element ref="c:HeaderLinkColor"/>
        <element ref="c:HeaderTextColor"/>
        <element ref="c:MainBackgroundColor"/>
        <element ref="c:MainLinkColor"/>
        <element ref="c:MainMessageBorderColor"/>
        <element ref="c:MainTableBorderColor"/>
        <element ref="c:MainTextColor"/>
      </sequence>
    </complexType>
  </element>

  <element name="Branding">
    <complexType>
      <sequence>
        <element ref="c:ColorScheme"
                 minOccurs="0"
                 maxOccurs="1"/>
      </sequence>

      <attribute name="ProductTitle"
                 use="required"
                 type="string">
        <annotation>
          <documentation>
            The product title that will be used in web pages and emails.
          </documentation>
        </annotation>
      </attribute>

      <attribute name="Logo"
                 use="optional"
                 type="anyURI">
        <annotation>
          <documentation>
            A file that contains a logo image used in web pages.
          </documentation>
        </annotation>
      </attribute>

      <attribute name="LoginExtra"
                 use="optional"
                 type="anyURI">
        <annotation>
          <documentation>
            A file that contains XHTML text that will be inserted into login web pages.
          </documentation>
        </annotation>
      </attribute>
    </complexType>
  </element>

  <complexType name="MailTransportType">
    <annotation>
      <documentation>
        The base type of mail transport configurations.
      </documentation>
    </annotation>
  </complexType>

  <complexType name="SMTPType">
    <annotation>
      <documentation>
        Settings for communicating over SMTP without encryption.
      </documentation>
    </annotation>
    <complexContent>
      <extension base="c:MailTransportType">
        <attribute name="Host"
                   type="anyURI"
                   use="required"/>
        <attribute name="Port"
                   type="unsignedInt"
                   use="required"/>
      </extension>
    </complexContent>
  </complexType>

  <complexType name="SMTPTLSType">
    <annotation>
      <documentation>
        Settings for communicating over SMTP_TLS.
      </documentation>
    </annotation>
    <complexContent>
      <extension base="c:MailTransportType">
        <attribute name="Host"
                   type="anyURI"
                   use="required"/>
        <attribute name="Port"
                   type="unsignedInt"
                   use="required"/>
      </extension>
    </complexContent>
  </complexType>

  <complexType name="SMTPSType">
    <annotation>
      <documentation>
        Settings for communicating over SMTPS.
      </documentation>
    </annotation>
    <complexContent>
      <extension base="c:MailTransportType">
        <attribute name="Host"
                   type="anyURI"
                   use="required"/>
        <attribute name="Port"
                   type="unsignedInt"
                   use="required"/>
      </extension>
    </complexContent>
  </complexType>

  <element name="SMTP"
           type="c:SMTPType"/>
  <element name="SMTPTLS"
           type="c:SMTPTLSType"/>
  <element name="SMTPS"
           type="c:SMTPSType"/>

  <element name="MailAuthentication">
    <annotation>
      <documentation>
        The mail server authentication configuration.
      </documentation>
    </annotation>

    <complexType>
      <attribute name="Username"
                 type="string"
                 use="required">
        <annotation>
          <documentation>
            The username that will be used to authenticate to the mail server.
          </documentation>
        </annotation>
      </attribute>
      <attribute name="Password"
                 type="string"
                 use="required">
        <annotation>
          <documentation>
            The password that will be used to authenticate to the mail server.
          </documentation>
        </annotation>
      </attribute>
    </complexType>
  </element>

  <element name="Mail">
    <annotation>
      <documentation>
        The mail server configuration.
      </documentation>
    </annotation>

    <complexType>
      <sequence>
        <choice minOccurs="1"
                maxOccurs="1">
          <element ref="c:SMTP"/>
          <element ref="c:SMTPS"/>
          <element ref="c:SMTPTLS"/>
        </choice>
        <element ref="c:MailAuthentication"
                 minOccurs="0"
                 maxOccurs="1"/>
      </sequence>

      <attribute name="SenderAddress"
                 type="string"
                 use="required"/>
      <attribute name="VerificationExpiration"
                 type="duration"
                 use="required"/>
    </complexType>
  </element>

  <element name="Maintenance">
    <annotation>
      <documentation>
        Configuration for the server's periodic maintenance tasks.
      </documentation>
    </annotation>

    <complexType>
      <attribute name="TLSReloadInterval"
                 type="duration"
                 use="optional">
        <annotation>
          <documentation>
            The interval at which TLS contexts will be reloaded. If not
            specified, TLS contexts will not be reloaded.
          </documentation>
        </annotation>
      </attribute>
    </complexType>
  </element>

  <element name="History">
    <annotation>
      <documentation>
        Retention settings for logins.
      </documentation>
    </annotation>

    <complexType>
      <attribute name="UserLoginHistoryLimit"
                 type="unsignedInt"
                 use="required">
        <annotation>
          <documentation>
            The number of login records to store for users.
          </documentation>
        </annotation>
      </attribute>
      <attribute name="AdminLoginHistoryLimit"
                 type="unsignedInt"
                 use="required">
        <annotation>
          <documentation>
            The number of login records to store for admins.
          </documentation>
        </annotation>
      </attribute>
    </complexType>
  </element>

  <complexType name="HTTPServiceType">
    <sequence minOccurs="1"
              maxOccurs="1">
      <group ref="ct:TLSGroup"/>
    </sequence>

    <attribute name="ListenAddress"
               use="required"
               type="string">
      <annotation>
        <documentation>
          The address upon which this HTTP service will listen.
        </documentation>
      </annotation>
    </attribute>
    <attribute name="ListenPort"
               use="required"
               type="unsignedInt">
      <annotation>
        <documentation>
          The port upon which this HTTP service will listen.
        </documentation>
      </annotation>
    </attribute>
    <attribute name="ExternalURI"
               use="required"
               type="anyURI">
      <annotation>
        <documentation>
          The external URI by which this HTTP service will be accessible. This is used in emails and web pages and is
          primarily useful when the HTTP service will be used behind a reverse proxy.
        </documentation>
      </annotation>
    </attribute>
  </complexType>

  <element name="HTTPServiceAdminAPI"
           type="c:HTTPServiceType">
    <annotation>
      <documentation>
        Configuration for the Admin API service.
      </documentation>
    </annotation>
  </element>

  <element name="HTTPServiceUserAPI"
           type="c:HTTPServiceType">
    <annotation>
      <documentation>
        Configuration for the User API service.
      </documentation>
    </annotation>
  </element>

  <element name="HTTPServiceUserView"
           type="c:HTTPServiceType">
    <annotation>
      <documentation>
        Configuration for the User view service.
      </documentation>
    </annotation>
  </element>

  <element name="HTTPServices">
    <annotation>
      <documentation>
        Configuration for HTTP services.
      </documentation>
    </annotation>

    <complexType>
      <sequence>
        <element ref="c:HTTPServiceAdminAPI"/>
        <element ref="c:HTTPServiceUserAPI"/>
        <element ref="c:HTTPServiceUserView"/>
      </sequence>
    </complexType>
  </element>

  <element name="Sessions">
    <complexType>
      <attribute name="UserSessionExpiration"
                 type="duration"
                 use="required">
        <annotation>
          <documentation>
            The maximum age of idle user sessions before they are considered expired and deleted.
          </documentation>
        </annotation>
      </attribute>
      <attribute name="AdminSessionExpiration"
                 type="duration"
                 use="required">
        <annotation>
          <documentation>
            The maximum age of idle admin sessions before they are considered expired and deleted.
          </documentation>
        </annotation>
      </attribute>
    </complexType>
  </element>

  <element name="Database">
    <annotation>
      <documentation>
        Configuration for the database service.
      </documentation>
    </annotation>

    <complexType>
      <attribute name="OwnerRoleName" use="required" type="string">
        <annotation>
          <documentation>
            The name of the role that owns the database. This is used for the initial database setup, and for
            upgrades.
          </documentation>
        </annotation>
      </attribute>

      <attribute name="OwnerRolePassword" use="required" type="string">
        <annotation>
          <documentation>
            The password of the role that owns the database.
          </documentation>
        </annotation>
      </attribute>

      <attribute name="WorkerRolePassword" use="required" type="string">
        <annotation>
          <documentation>
            The password of the role used for normal database operation. This is an unprivileged role that
            does not have the ability to perform DDL or other database-changing operations.
          </documentation>
        </annotation>
      </attribute>

      <attribute name="ReaderRolePassword" use="optional" type="string">
        <annotation>
          <documentation>
            The password of the role used for read-only database operation. If this attribute is not specified,
            the read-only role is not allowed to log in.
          </documentation>
        </annotation>
      </attribute>

      <attribute name="Kind"
                 use="required"
                 type="string">
        <annotation>
          <documentation>
            The kind of the remote database, such as POSTGRESQL.
          </documentation>
        </annotation>
      </attribute>

      <attribute name="Name"
                 use="required"
                 type="string">
        <annotation>
          <documentation>
            The name of the database.
          </documentation>
        </annotation>
      </attribute>

      <attribute name="Address"
                 use="required"
                 type="string">
        <annotation>
          <documentation>
            The address of the database.
          </documentation>
        </annotation>
      </attribute>

      <attribute name="Port"
                 use="required"
                 type="unsignedInt">
        <annotation>
          <documentation>
            The port used to connect to the database.
          </documentation>
        </annotation>
      </attribute>

      <attribute name="Create"
                 use="required"
                 type="boolean">
        <annotation>
          <documentation>
            Should the database be created if it does not already exist?
          </documentation>
        </annotation>
      </attribute>

      <attribute name="Upgrade"
                 use="required"
                 type="boolean">
        <annotation>
          <documentation>
            Should the database schema be upgraded if the schema version is older than the current application?
          </documentation>
        </annotation>
      </attribute>
    </complexType>
  </element>

  <element name="PasswordExpiration">
    <annotation>
      <documentation>
        Configuration information for password expiration.
      </documentation>
    </annotation>

    <complexType>
      <attribute name="UserPasswordValidityDuration"
                 type="duration"
                 use="optional">
        <annotation>
          <documentation>
            The duration for which user passwords are valid. If unspecified, user passwords do not expire.
          </documentation>
        </annotation>
      </attribute>

      <attribute name="AdminPasswordValidityDuration"
                 type="duration"
                 use="optional">
        <annotation>
          <documentation>
            The duration for which admin passwords are valid. If unspecified, user passwords do not expire.
          </documentation>
        </annotation>
      </attribute>
    </complexType>
  </element>

  <element name="RateLimiting">
    <annotation>
      <documentation>
        Configuration information for request rate limiting.
      </documentation>
    </annotation>

    <complexType>
      <attribute name="UserLoginDelay"
                 type="duration"
                 default="PT1S"
                 use="optional">
        <annotation>
          <documentation>
            A fixed delay added to user login operations in order to reduce the feasibility of online password
            brute-forcing.
          </documentation>
        </annotation>
      </attribute>

      <attribute name="UserLoginRateLimit"
                 type="duration"
                 default="PT5S"
                 use="optional">
        <annotation>
          <documentation>
            User login attempts cannot be made more frequently than this duration, for a given IP address.
          </documentation>
        </annotation>
      </attribute>

      <attribute name="AdminLoginDelay"
                 type="duration"
                 default="PT1S"
                 use="optional">
        <annotation>
          <documentation>
            A fixed delay added to admin login operations in order to reduce the feasibility of online password
            brute-forcing.
          </documentation>
        </annotation>
      </attribute>

      <attribute name="AdminLoginRateLimit"
                 type="duration"
                 default="PT5S"
                 use="optional">
        <annotation>
          <documentation>
            Admin login attempts cannot be made more frequently than this duration, for a given IP address.
          </documentation>
        </annotation>
      </attribute>

      <attribute name="EmailVerificationRateLimit"
                 type="duration"
                 use="required">
        <annotation>
          <documentation>
            Email verifications for various operations cannot be prompted more frequently than this duration,
            for a given user account.
          </documentation>
        </annotation>
      </attribute>

      <attribute name="PasswordResetRateLimit"
                 type="duration"
                 use="required">
        <annotation>
          <documentation>
            Passwords cannot be reset more frequently than this duration, for a given IP address.
          </documentation>
        </annotation>
      </attribute>
    </complexType>
  </element>

  <simpleType name="OpenTelemetryProtocol">
    <annotation>
      <documentation>
        The protocol used to deliver OpenTelemetry data.
      </documentation>
    </annotation>

    <restriction base="string">
      <enumeration value="GRPC">
        <annotation>
          <documentation>
            The data will be sent using gRPC.
          </documentation>
        </annotation>
      </enumeration>
      <enumeration value="HTTP">
        <annotation>
          <documentation>
            The data will be sent using HTTP(s).
          </documentation>
        </annotation>
      </enumeration>
    </restriction>
  </simpleType>

  <element name="Metrics">
    <annotation>
      <documentation>
        Configuration information for OpenTelemetry metrics.
      </documentation>
    </annotation>

    <complexType>
      <attribute name="Endpoint"
                 use="required"
                 type="anyURI">
        <annotation>
          <documentation>
            The endpoint to which OTLP metrics data will be sent.
          </documentation>
        </annotation>
      </attribute>

      <attribute name="Protocol"
                 use="required"
                 type="c:OpenTelemetryProtocol">
        <annotation>
          <documentation>
            The protocol used to send metrics data.
          </documentation>
        </annotation>
      </attribute>
    </complexType>
  </element>

  <element name="Traces">
    <annotation>
      <documentation>
        Configuration information for OpenTelemetry traces.
      </documentation>
    </annotation>

    <complexType>
      <attribute name="Endpoint"
                 use="required"
                 type="anyURI">
        <annotation>
          <documentation>
            The endpoint to which OTLP trace data will be sent.
          </documentation>
        </annotation>
      </attribute>

      <attribute name="Protocol"
                 use="required"
                 type="c:OpenTelemetryProtocol">
        <annotation>
          <documentation>
            The protocol used to send trace data.
          </documentation>
        </annotation>
      </attribute>
    </complexType>
  </element>

  <element name="Logs">
    <annotation>
      <documentation>
        Configuration information for OpenTelemetry logs/events.
      </documentation>
    </annotation>

    <complexType>
      <attribute name="Endpoint"
                 use="required"
                 type="anyURI">
        <annotation>
          <documentation>
            The endpoint to which OTLP log data will be sent.
          </documentation>
        </annotation>
      </attribute>

      <attribute name="Protocol"
                 use="required"
                 type="c:OpenTelemetryProtocol">
        <annotation>
          <documentation>
            The protocol used to send log data.
          </documentation>
        </annotation>
      </attribute>
    </complexType>
  </element>

  <element name="OpenTelemetry">
    <annotation>
      <documentation>
        Configuration information for OpenTelemetry.
      </documentation>
    </annotation>

    <complexType>
      <sequence>
        <element ref="c:Logs"
                 minOccurs="0"
                 maxOccurs="1"/>
        <element ref="c:Metrics"
                 minOccurs="0"
                 maxOccurs="1"/>
        <element ref="c:Traces"
                 minOccurs="0"
                 maxOccurs="1"/>
      </sequence>

      <attribute name="LogicalServiceName"
                 use="required"
                 type="string">
        <annotation>
          <documentation>
            The logical name of the service as it will appear in OpenTelemetry.
          </documentation>
        </annotation>
      </attribute>
    </complexType>
  </element>

  <element name="Configuration">
    <annotation>
      <documentation>
        The main server configuration element.
      </documentation>
    </annotation>

    <complexType>
      <sequence>
        <element ref="c:Branding"/>
        <element ref="c:Database"/>
        <element ref="c:HTTPServices"/>
        <element ref="c:History"/>
        <element ref="c:Mail"/>
        <element ref="c:Maintenance"/>
        <element ref="c:OpenTelemetry"
                 minOccurs="0"
                 maxOccurs="1"/>
        <element ref="c:PasswordExpiration"
                 minOccurs="0"
                 maxOccurs="1"/>
        <element ref="c:RateLimiting"/>
        <element ref="c:Sessions"/>
      </sequence>
    </complexType>
  </element>

</schema>

3.13.3. TLS Schema

<?xml version="1.0" encoding="UTF-8" ?>

<!--
  Copyright © 2023 Mark Raynsford <code@io7m.com> https://www.io7m.com

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above
  copyright notice and this permission notice appear in all copies.

  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
  SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
  IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-->

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="urn:com.io7m.idstore.tls:1"
            xmlns:nt="urn:com.io7m.idstore.tls:1">

  <xsd:complexType name="StoreType"
                   abstract="true">
    <xsd:attribute name="Type"
                   type="xsd:string"
                   use="required"/>
    <xsd:attribute name="Provider"
                   type="xsd:string"
                   use="required"/>
    <xsd:attribute name="Password"
                   type="xsd:string"
                   use="required"/>
    <xsd:attribute name="File"
                   type="xsd:string"
                   use="required"/>
  </xsd:complexType>

  <xsd:complexType name="KeyStoreType">
    <xsd:complexContent>
      <xsd:extension base="nt:StoreType"/>
    </xsd:complexContent>
  </xsd:complexType>

  <xsd:complexType name="TrustStoreType">
    <xsd:complexContent>
      <xsd:extension base="nt:StoreType"/>
    </xsd:complexContent>
  </xsd:complexType>

  <xsd:element name="KeyStore"
               type="nt:KeyStoreType"/>

  <xsd:element name="TrustStore"
               type="nt:TrustStoreType"/>

  <xsd:complexType name="TLSType"
                   abstract="true"/>

  <xsd:complexType name="TLSDisabledType">
    <xsd:complexContent>
      <xsd:extension base="nt:TLSType"/>
    </xsd:complexContent>
  </xsd:complexType>

  <xsd:element name="TLSDisabled"
               type="nt:TLSDisabledType"/>

  <xsd:complexType name="TLSEnabledType">
    <xsd:complexContent>
      <xsd:extension base="nt:TLSType">
        <xsd:sequence>
          <xsd:element ref="nt:KeyStore"/>
          <xsd:element ref="nt:TrustStore"/>
        </xsd:sequence>
      </xsd:extension>
    </xsd:complexContent>
  </xsd:complexType>

  <xsd:element name="TLSEnabled"
               type="nt:TLSEnabledType"/>

  <xsd:group name="TLSGroup">
    <xsd:choice>
      <xsd:element ref="nt:TLSDisabled"/>
      <xsd:element ref="nt:TLSEnabled"/>
    </xsd:choice>
  </xsd:group>

</xsd:schema>
The idstore server is primarily intended to act as a single-source-of-truth for the identities of users within arbitrary sets of decoupled services. It acts as a secure store for login credentials for users, and is intended to relieve server application authors of the burden of having to constantly reimplement login processes, credential stores, password reset workflows, and other miscellanea related to authentication.
This section of the documentation describes the internal idstore model.
The idstore package contains two types of user accounts: users and administrators. A user is an unprivileged account that is permitted to log in to the server only via the user view, and/or the user API. An administrator is an account that is permitted to log in to the server only via the admin API, and is permitted to perform administrative actions (subject to permissions checks) such as creating user accounts, reading the audit log, etc.
User and administrator accounts have immutable, unique identifiers represented by RFC 4122 UUID values. Identifiers are not secret and may be freely shared between systems.
User and administrator accounts have names that are used for login operations. Names must match the regular expression \p{LC}[\p{LC}\p{N}_-]{0,255}. Names are case-insensitive [1] and must be unique within a given idstore server, but can be changed at the user/administrator's discretion at any time.
User and administrator accounts must have at least one unique (within a given idstore server) email address associated with them. Email addresses can be added and removed at any time, but it is not permitted for the number of addresses on an account to be less than one.
Each administrator account has a set of permissions associated with it. When the administrator account attempts to perform an operation on the server, the account's permissions are checked to see if it has all the permissions required to perform the action.
An administrator account holding the ADMIN_WRITE_PERMISSIONS permission may grant any permissions it holds to another administrator account. Similarly, An administrator account holding the ADMIN_WRITE_PERMISSIONS permission may revoke any permissions it holds from another administrator account.
The initial administrator is a designated adminstrator account that always has all of the available permissions. For security reasons, this account should have an extremely strong password, and should only be used to perform the necessary initial configuration of the server; it is analogous to the UNIX root account.
The following administrator permissions are available:

4.3.2. Permissions

Permission Description
ADMIN_DELETE The bearer of this permission is permitted to delete administrator accounts.
ADMIN_CREATE The bearer of this permission is permitted to create new administrator accounts.
USER_BAN The bearer of this permission is permitted to ban users.
ADMIN_BAN The bearer of this permission is permitted to ban administrators.
ADMIN_WRITE_CREDENTIALS The bearer of this permission is permitted to modify the credentials of administrators.
ADMIN_WRITE_CREDENTIALS_SELF The bearer of this permission is permitted to modify its own credentials.
ADMIN_WRITE_EMAIL The bearer of this permission is permitted to modify the email addresses of administrators.
ADMIN_WRITE_EMAIL_SELF The bearer of this permission is permitted to modify its own email addresses.
ADMIN_WRITE_PERMISSIONS The bearer of this permission is permitted to modify the permissions of administrators.
ADMIN_WRITE_PERMISSIONS_SELF The bearer of this permission is permitted to modify its own permissions.
ADMIN_READ The bearer of this permission is permitted to read the accounts of administrators.
AUDIT_READ The bearer of this permission is permitted to read the audit log.
USER_DELETE The bearer of this permission is permitted to delete user accounts.
USER_CREATE The bearer of this permission is permitted to create new user accounts.
USER_WRITE_CREDENTIALS The bearer of this permission is permitted to modify the credentials of existing user accounts.
USER_WRITE_EMAIL The bearer of this permission is permitted to modify the email addresses existing user accounts.
USER_READ The bearer of this permission is permitted to read existing user accounts.
MAIL_TEST The bearer of this permission is permitted to send test emails.
The server maintains an append-only audit log consisting of a series of audit events. An audit event has an integer id, an owner (represented by an account UUID), a timestamp, a type, and a message.
The user view is the web interface exposed to users. It offers the ability for users to see their own account information, and also to handle the email-based password reset workflow.
The user API is the interface exposed to user clients. It exposes a Cedarbridge-based API over HTTP, using the included schema.
The user API is largely intended to be used by other servers that want to use an idstore server to handle authentication.
The admin API is the interface exposed to administrators. Like the user API, it exposes a Cedarbridge-based API over HTTP, using the included schema. It is the service used by the included admin shell, and by projects such as idstore_gui.
There are a number of situations where the idstore server uses an external MTA.
If a user forgets their password, they can request a password reset token. The server will generate a strong random token t with a configurable expiration date. For each email address e associated with the user's account, the server will email t to e. The user can retrieve t from one of their email accounts and recite t back to the server to have their password reset.
If a user wants to add a new email address f to their account, they can request email addition tokens. The server will generate a pair of strong random tokens tpermit and tdeny, each with a configurable expiration date. For each email account e associated with the user's account, the server will email tdeny to e. The server will email tpermit to f. Assuming the user really does have access to f, they can read the resulting email and recite tpermit back to the idstore server to have the new address added to their account.
In the event that the user's account is compromised, and a hostile party is attempting to add a new email address to the user's account, the user can recite tdeny back to the server from any of their other email accounts in order to deny the address addition. This potentially alerts users to the fact that their account has been compromised, and allows them to mitigate the damage if they act fast enough.
If a user wants to remove an existing email address f from their account, they can request email removal tokens. The server will generate a pair of strong random tokens tpermit and tdeny, each with a configurable expiration date. For each email account e associated with the user's account (where e != f), the server will email tdeny to e. The server will email tpermit to f. Assuming the user really does have access to f, they can read the resulting email and recite tpermit back to the idstore server to have the address removed from their account.
In the event that the user's account is compromised, and a hostile party is attempting to remove the user's existing email address from the compromised account, the user can recite tdeny back to the server from any of their other email accounts in order to deny the address removal. This potentially alerts users to the fact that their account has been compromised, and allows them to mitigate the damage if they act fast enough.
If the user no longer has access to f, they cannot remove it. In this case, an administrator will need to remove the address manually.
When performing essential maintenance such as a database upgrade, the server can be placed into maintenance mode. In this mode, all requests made to the user API or user view will return a 503 HTTP status code and a configurable message.
Maintenance mode is not a persistent setting; if the server is restarted, it will start up in the normal non-maintenance mode.

Footnotes

1
Names are case-insensitive in order to make it slightly more difficult for a hostile user to impersonate another by picking a name that only varies with case.
References to this footnote: 1
The idstore package is extensively instrumented with OpenTelemetry in order to allow for the server to be continually monitored. The package publishes metrics, logs, and traces, all of which can be independently enabled or disabled. Most installations will only want to enable metrics or logs in production; traces are more useful when trying to diagnose performance problems, or for doing actual development on the idstore package.
The package publishes the following metrics that can be used for monitoring:

5.2.1.2. Metrics

Name Description
idstore_up A gauge that displays a constant 1 value while the server is up.
idstore_closed_for_maintenance A gauge that displays a 1 value while the server is in maintenance mode and 0 otherwise.
idstore_http_time A gauge that logs the time each HTTP request has taken in nanoseconds.
idstore_http_requests A counter that is incremented every time an HTTP request is handled.
idstore_http_requests_size A counter that is incremented with the size of every HTTP request.
idstore_http_responses_size A counter that is incremented with the size of every produced HTTP response.
idstore_http_responses_2xx A counter that is incremented with every HTTP response that produces a 2xx status code.
idstore_http_responses_4xx A counter that is incremented with every HTTP response that produces a 4xx status code. A 4xx status code should be understood to mean "blame the client".
idstore_http_responses_5xx A counter that is incremented with every HTTP response that produces a 5xx status code. A 5xx status code should be understood to mean "blame the server".
idstore_mail_ok A counter that is incremented every time mail is sent successfully.
idstore_mail_failed A counter that is incremented every time mail fails to send correctly.
idstore_mail_time A gauge that logs the time each mail operation has taken in nanoseconds.
idstore_ratelimit_triggers A counter that is incremented every time a rate limit is violated.
idstore_ratelimit_login_delay A gauge that produces a constant value taken directly from the configured user and admin login delays. This metric is useful because login delays add significantly to HTTP response times, and this metric can be used to subtract those delays from the logged response times.
idstore_sessions A gauge that displays the number of currently active user or admin sessions.
Most metrics are labelled with various attributes that allow for distinguishing between requests that occurred as part of the user service, or part of the admin service. Metrics involving the mail system typically include the destination address as an attribute.
The package may produce other metrics, however these are undocumented and should not be relied upon.
The idstore package provides a command-line interface for performing tasks such as starting the server, checking configuration files, hashing passwords, and etc. The base idstore command is broken into a number of subcommands which are documented over the following sections.

6.1.2. Command-Line Overview

idstore: usage: idstore [command] [arguments ...]

  The idstore server and command-line application.

  Use the "help" command to examine specific commands:

    $ idstore help help.

  Command-line arguments can be placed one per line into a file, and
  the file can be referenced using the @ symbol:

    $ echo help > file.txt
    $ echo help >> file.txt
    $ idstore @file.txt

  Commands:
    help          Show usage information for a command.
    initialize    Initialize the server and database.
    server        Start the server.
    shell         Run the admin command shell.
    version       Show the application version.

  Documentation:
    https://www.io7m.com/software/idstore/
All subcommands accept a --verbose parameter that may be set to one of trace, debug, info, warn, or error. This parameter sets the lower bound for the severity of messages that will be logged. For example, at debug verbosity, only messages of severity debug and above will be logged. Setting the verbosity to trace level effectively causes everything to be logged, and will produce large volumes of debugging output.
The idstore command-line tool uses quarrel to parse command-line arguments, and therefore supports placing command-line arguments into a file, one argument per line, and then referencing that file with @. For example:

6.1.5. @ Syntax

$ idstore server --configuration server.conf

$ (cat <<EOF
--configuration
server.conf
EOF
) > args.txt

$ idstore @args.txt
All subcommands, unless otherwise specified, yield an exit code of 0 on success, and a non-zero exit code on failure.
initial-admin - Initialize the server
The initial-admin command creates or updates the initial administrator.
If the initial administrator does not exist, it is created and assigned the given names, email address, and password. If an administrator already exists with the given ID and is the initial administrator, it is updated with the given names, email address, and password. Otherwise, if the administrator exists but is not the initial administrator, the command fails with an error.

6.2.2.3. Parameters

Parameter Type Required Description
--configuration Path true The configuration file
--admin-id UUID true The ID of the initial administrator
--admin-username String true The initial administrator to create.
--admin-password String true The password of the initial administrator.
--admin-email String true The email address of the initial administrator.
--admin-realname String true The real name of the initial administrator.
--verbose CLPLogLevel false Set the minimum logging verbosity level.

6.2.3.1. Example

$ idstore initial-admin \
  --admin-id '92f83bce-3973-4db8-8aaf-d401443a9772' \
  --admin-email 'someone@example.com' \
  --admin-realname 'Someone Else' \
  --admin-username 'someone' \
  --admin-password 12345678 \
  --configuration server.conf
server - Start the server
The server command starts the server.

6.3.2.2. Parameters

Parameter Type Required Description
--verbose CLPLogLevel false Set the minimum logging verbosity level.
--configuration Path true The configuration file

6.3.3.1. Example

$ idstore server --configuration server.conf
info: [localhost/<unresolved>:50000] User API server started
info: [localhost/<unresolved>:50001] User view server started
info: [localhost/<unresolved>:51000] Admin API server started
shell - Start the admin shell
The server command starts the admin shell.

6.4.2.2. Parameters

Parameter Type Required Description
--verbose CLPLogLevel false Set the minimum logging verbosity level.

6.4.3.1. Example

$ idstore shell
[idstore]# version
com.io7m.idstore 0.0.15-SNAPSHOT 20af71248a7784b0e5247eab4b1ebd28de284739
version - Display the package version
The version command displays the current version of the package.

6.5.2.2. Parameters

Parameter Type Required Description
--verbose CLPLogLevel false Set the minimum logging verbosity level.

6.5.3.1. Example

$ idstore version
1.0.0
In addition to the various types of API access the server provides, the idstore package includes an interactive command-line shell for performing basic administrative tasks.
The shell is started using the shell command. The shell supports basic tab-completion and history accessed with the up and down arrows.
When running on an appropriate terminal, the shell supports tab completion for command and most command arguments. Begin typing the name of a command, or the name of a command argument, and then press tab. A set of completion options will be displayed.
When running on an appropriate terminal, the command shell stores the history of command in memory (the history is not saved to disk, for security reasons). Press the up and down arrows to navigate to previously executed commands.
Arguments to shell commands may be quoted using the " or ' characters. This is required when calling commands that take arguments that may need to contain strings.
Typically, the ENTER key ends the current line. This will cause the shell to interpret the contents of a line as a command and execute it. Pressing ALT+ENTER inserts an actual newline character without executing the command. This can be useful if an argument to a command needs to contain a newline character:
When in this mode, the cursor can be moved around freely with the arrow keys to allow for editing lines. Pressing the ENTER key ends the multiline editing mode and executes the command.
All search commands in the idstore package provide automatic pagination. Searches are performed by beginning a search with a begin command which will yield the first page of results. Searches are then continued with next and previous commands which will return the next and previous pages of results, respectively. It is an error to try to execute a next or previous command without first having executed a begin command.
Each page of search results includes the number of the current page, and the number of pages of search results available. Attempting to seek beyond the end of the set of pages with a next command is not an error; the server will simply repeatedly return the last page in the set. Similarly, attempting to seek before the first page of results with a previous command will simply return the first page of results, repeatedly.
The shell provides different formatting options for tabular data.

7.1.7.2. Formatters

Name Description
PRETTY Provides pretty Unicode tables.
RAW Provides raw tables.
The formatter can be set using the set command.

7.1.7.4. Example

[idstore]# set --formatter PRETTY
[idstore]# audit-search-begin
 Page 1 of 2, offset 0
┌────┬──────────────────────┬──────────────────────────────────────┬─────────────────┬──────────────────────────────────────────────────────────────────────────────────────┐
│ ID │ Time                 │ Owner                                │ Type            │ Message                                                                              │
├────┼──────────────────────┼──────────────────────────────────────┼─────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ 9  │ 2023-12-20T10:56:39Z │ 5b238548-cf55-44c1-89d2-de3c7c6950a8 │ ADMIN_LOGGED_IN │ {Host=10.0.2.100:33514}                                                              │
├────┼──────────────────────┼──────────────────────────────────────┼─────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ 10 │ 2023-12-20T11:07:12Z │ 3cd252a7-5d9f-44a9-87d1-cb60bdd608b3 │ USER_LOGGED_IN  │ {Host=10.0.2.100:51752}                                                              │
├────┼──────────────────────┼──────────────────────────────────────┼─────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ 11 │ 2023-12-20T11:09:04Z │ 5b238548-cf55-44c1-89d2-de3c7c6950a8 │ ADMIN_LOGGED_IN │ {Host=10.0.2.100:36410}                                                              │
├────┼──────────────────────┼──────────────────────────────────────┼─────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ 12 │ 2023-12-20T16:55:33Z │ 5b238548-cf55-44c1-89d2-de3c7c6950a8 │ ADMIN_LOGGED_IN │ {Host=10.0.2.100:32848}                                                              │
├────┼──────────────────────┼──────────────────────────────────────┼─────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ 13 │ 2023-12-21T11:20:09Z │ 3cd252a7-5d9f-44a9-87d1-cb60bdd608b3 │ USER_LOGGED_IN  │ {Host=10.0.2.100:42982 (10.2.4.1:42990)}                                             │
├────┼──────────────────────┼──────────────────────────────────────┼─────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ 14 │ 2023-12-21T11:21:12Z │ 3cd252a7-5d9f-44a9-87d1-cb60bdd608b3 │ USER_LOGGED_IN  │ {Host=10.0.2.100:37490 (10.2.4.1:58114)}                                             │
├────┼──────────────────────┼──────────────────────────────────────┼─────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ 15 │ 2023-12-21T11:30:10Z │ 3cd252a7-5d9f-44a9-87d1-cb60bdd608b3 │ USER_LOGGED_IN  │ {Host=10.0.2.100:60566 (10.2.4.1:53236)}                                             │
├────┼──────────────────────┼──────────────────────────────────────┼─────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ 16 │ 2023-12-21T11:56:03Z │ 3cd252a7-5d9f-44a9-87d1-cb60bdd608b3 │ USER_LOGGED_IN  │ {Host=10.0.2.100:56994 (10.2.4.1:48326)}                                             │
├────┼──────────────────────┼──────────────────────────────────────┼─────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ 17 │ 2023-12-21T11:56:53Z │ 3cd252a7-5d9f-44a9-87d1-cb60bdd608b3 │ USER_LOGGED_IN  │ {Host=10.0.2.100:56268 (10.2.4.1:39182)}                                             │
├────┼──────────────────────┼──────────────────────────────────────┼─────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ 18 │ 2023-12-21T14:09:40Z │ 5b238548-cf55-44c1-89d2-de3c7c6950a8 │ ADMIN_LOGGED_IN │ {Host=10.0.2.100:36966}                                                              │
└────┴──────────────────────┴──────────────────────────────────────┴─────────────────┴──────────────────────────────────────────────────────────────────────────────────────┘
[idstore]# set --formatter RAW
[idstore]# audit-search-begin
# Page 1 of 2, offset 0
# ID | Time | Owner | Type | Message
9            | 2023-12-20T10:56:39Z     | 5b238548-cf55-44c1-89d2-de3c7c6950a8 | ADMIN_LOGGED_IN          | {Host=10.0.2.100:33514}
10           | 2023-12-20T11:07:12Z     | 3cd252a7-5d9f-44a9-87d1-cb60bdd608b3 | USER_LOGGED_IN           | {Host=10.0.2.100:51752}
11           | 2023-12-20T11:09:04Z     | 5b238548-cf55-44c1-89d2-de3c7c6950a8 | ADMIN_LOGGED_IN          | {Host=10.0.2.100:36410}
12           | 2023-12-20T16:55:33Z     | 5b238548-cf55-44c1-89d2-de3c7c6950a8 | ADMIN_LOGGED_IN          | {Host=10.0.2.100:32848}
13           | 2023-12-21T11:20:09Z     | 3cd252a7-5d9f-44a9-87d1-cb60bdd608b3 | USER_LOGGED_IN           | {Host=10.0.2.100:42982 (10.1.6.1:42990)}
14           | 2023-12-21T11:21:12Z     | 3cd252a7-5d9f-44a9-87d1-cb60bdd608b3 | USER_LOGGED_IN           | {Host=10.0.2.100:37490 (10.1.6.1:58114)}
15           | 2023-12-21T11:30:10Z     | 3cd252a7-5d9f-44a9-87d1-cb60bdd608b3 | USER_LOGGED_IN           | {Host=10.0.2.100:60566 (10.1.6.1:53236)}
16           | 2023-12-21T11:56:03Z     | 3cd252a7-5d9f-44a9-87d1-cb60bdd608b3 | USER_LOGGED_IN           | {Host=10.0.2.100:56994 (10.1.6.1:48326)}
17           | 2023-12-21T11:56:53Z     | 3cd252a7-5d9f-44a9-87d1-cb60bdd608b3 | USER_LOGGED_IN           | {Host=10.0.2.100:56268 (10.1.6.1:39182)}
18           | 2023-12-21T14:09:40Z     | 5b238548-cf55-44c1-89d2-de3c7c6950a8 | ADMIN_LOGGED_IN          | {Host=10.0.2.100:36966}
admin-ban-create - Ban an admin.
The admin-ban-create command creates a new ban for an admin.

7.2.1.2.2. Parameters

Parameter Type Cardinality Default Description
--admin java.util.UUID [1, 1] The admin ID.
--expires-on java.time.OffsetDateTime [0, 1] The time/date the ban expires.
--reason java.lang.String [1, 1] The ban reason.

7.2.1.3.1. Example

[idstore]# admin-ban-create
> --admin 3a193a61-9427-4c24-8bd4-667d19914970
> --expires-on 2100-01-01T00:00:00+00:00
> --reason "You did something objectionable"
admin-ban-delete - Unban an admin.
The admin-ban-delete command deletes a ban for an admin. Has no effect if the admin is not currently banned.

7.2.2.2.2. Parameters

Parameter Type Cardinality Default Description
--admin java.util.UUID [1, 1] The admin ID.
admin-ban-get - Retrieve the ban on an admin, if one exists.
The admin-ban-get command retrieves a ban for an admin, if any.

7.2.3.2.2. Parameters

Parameter Type Cardinality Default Description
--admin java.util.UUID [1, 1] The admin ID.
admin-create - Create an admin.
The admin-create command creates a new admin.

7.2.4.2.2. Parameters

Parameter Type Cardinality Default Description
--email com.io7m.idstore.model.IdEmail [1, 1] The email address.
--id java.util.UUID [0, 1] The user ID.
--name java.lang.String [1, 1] The user name.
--password java.lang.String [1, 1] The password.
--permission com.io7m.idstore.model.IdAdminPermission [0, N] [] A permission to grant the admin.
--real-name java.lang.String [1, 1] The user's real name.
admin-email-add - Add an email address to an admin.
The admin-email-add command adds an email address to an admin.

7.2.5.2.2. Parameters

Parameter Type Cardinality Default Description
--admin java.util.UUID [1, 1] The admin ID.
--email com.io7m.idstore.model.IdEmail [1, 1] The email address.
admin-email-remove - Remove an email address from an admin.
The admin-email-remove command removes an email address from an admin.

7.2.6.2.2. Parameters

Parameter Type Cardinality Default Description
--admin java.util.UUID [1, 1] The admin ID.
--email com.io7m.idstore.model.IdEmail [1, 1] The email address.
admin-get-by-email - Retrieve an admin by email address.
The admin-get-by-email command retrieves an admin by email address.

7.2.7.2.2. Parameters

Parameter Type Cardinality Default Description
--email com.io7m.idstore.model.IdEmail [1, 1] The email address.
admin-get - Retrieve an admin.
The admin-get command retrieves an admin.

7.2.8.2.2. Parameters

Parameter Type Cardinality Default Description
--admin java.util.UUID [1, 1] The admin ID.
admin-search-begin - Begin searching for admins.
The admin-search-begin command starts searching for admins.

7.2.9.2.2. Parameters

Parameter Type Cardinality Default Description
--created-from java.time.OffsetDateTime [1, 1] 1970-01-01T00:00Z Return admins created later than this date.
--created-to java.time.OffsetDateTime [1, 1] +101970-01-01T00:00Z Return admins created earlier than this date.
--query java.lang.String [0, 1] Match admins against this query text.
--updated-from java.time.OffsetDateTime [1, 1] 1970-01-01T00:00Z Return admins updated later than this date.
--updated-to java.time.OffsetDateTime [1, 1] +101970-01-01T00:00Z Return admins updated earlier than this date.
--limit java.lang.Integer [1, 1] 10 The maximum number of results per page.
admin-search-by-email-begin - Begin searching for admins by email.
The admin-search-by-email-begin command starts searching for admins by email address.

7.2.10.2.2. Parameters

Parameter Type Cardinality Default Description
--created-from java.time.OffsetDateTime [1, 1] 1970-01-01T00:00Z Return admins created later than this date.
--created-to java.time.OffsetDateTime [1, 1] +101970-01-01T00:00Z Return admins created earlier than this date.
--email java.lang.String [0, 1] Match admin emails against this query text.
--updated-from java.time.OffsetDateTime [1, 1] 1970-01-01T00:00Z Return admins updated later than this date.
--updated-to java.time.OffsetDateTime [1, 1] +101970-01-01T00:00Z Return admins updated earlier than this date.
--limit java.lang.Integer [1, 1] 10 The maximum number of results per page.
admin-search-by-email-next - Go to the next page of admins.
The admin-search-by-email-next command moves to the next page of search results.
admin-search-by-email-previous - Go to the previous page of admins.
The admin-search-by-email-previous command moves to the previous page of search results.
admin-search-next - Go to the next page of admins.
The admin-search-next command moves to the next page of search results.
admin-search-previous - Go to the previous page of admins.
The admin-search-previous command moves to the previous page of search results.
admin-update-password-expiration - Update an admin's password expiration.
The admin-update-password-expiration command updates the password expiration of a given administrator.

7.2.15.2.2. Parameters

Parameter Type Cardinality Default Description
--admin java.util.UUID [1, 1] The admin ID.
--expires com.io7m.idstore.protocol.admin.IdAPasswordExpirationSetType [1, 1] The password expiration.

7.2.15.3.1. Example

[idstore#] admin-update-password-expiration
  --admin 3a193a61-9427-4c24-8bd4-667d19914970
  --expires default

The password will not expire.

[idstore#] admin-update-password-expiration
  --admin 3a193a61-9427-4c24-8bd4-667d19914970
  --expires 2100-01-01T00:00:00+00:00

The password will expire at 2100-01-01T00:00:00Z.

[idstore#] admin-update-password-expiration
  --admin 3a193a61-9427-4c24-8bd4-667d19914970
  --expires never

The password will not expire.
audit-search-begin - Begin searching for audits.
The audit-search-begin command begins searching for audit events.

7.2.16.2.2. Parameters

Parameter Type Cardinality Default Description
--message java.lang.String [0, 1] Filter events by message.
--owner java.lang.String [0, 1] Filter events by owner.
--time-from java.time.OffsetDateTime [1, 1] 1970-01-01T00:00Z Return audit events later than this date.
--time-to java.time.OffsetDateTime [1, 1] +101970-01-01T00:00Z Return audit events earlier than this date.
--type java.lang.String [0, 1] Filter events by type.
--limit java.lang.Integer [1, 1] 10 The maximum number of results per page.
audit-search-next - Go to the next page of audit events.
The audit-search-next command moves to the next page of search results.
audit-search-previous - Go to the previous page of audit events.
The audit-search-previous command moves to the previous page of search results.
help - Display help for a given command.
The help command displays help for a given command.
login - Log in.
The login command logs in to a server. This command is required to be executed before most other commands can be executed.
logout - Log out.
The logout command logs out of a server. Has no effect if the login command has not been executed.
mail-test - Send a test email.
The mail-test command checks to see if the mail system is working by sending a test email.
For obvious reasons, the command can only check that the idstore server is capable of sending mail through its configured mail service - it cannot automatically verify that the target will actually receive the sent mail.

7.2.22.2.3. Parameters

Parameter Type Cardinality Default Description
--email com.io7m.idstore.model.IdEmail [1] The target email address.
--token com.io7m.idstore.model.IdShortHumanToken [0, 1] The short token to include in the email. If a value is not provided, one is randomly generated.

7.2.22.3.1. Example

[idstore]# mail-test --email user@example.com
Mail sent successfully.
Token: 220384
maintenance-mode - Enable/disable maintenance mode.
The maintenance-mode command enables or disables maintenance mode.

7.2.23.2.2. Parameters

Parameter Type Cardinality Default Description
--set String [0, 1] The message to display to users announcing maintenance mode.
--unset String [0, 1] Unset maintenance mode. The string argument is ignored.

7.2.23.3.1. Example

[idstore]# maintenance-mode --set 'We are performing maintenance. Normal service will resume shortly.'
Server is in maintenance mode with message "We are performing maintenance. Normal service will resume shortly."

[idstore]# maintenance-mode --unset Ignored
Server is now actively serving requests.
self - Return the admin's own profile.
The self command returns the ID of the admin running the shell.
set - Set shell options.
The set command sets local shell options.

7.2.25.2.2. Parameters

Parameter Type Cardinality Default Description
--terminate-on-errors java.lang.Boolean [0, 1] Terminate execution on the first command that returns an error.
user-ban-create - Ban a user.
The user-ban-create command creates a new ban for a user.

7.2.26.2.2. Parameters

Parameter Type Cardinality Default Description
--expires-on java.time.OffsetDateTime [0, 1] The time/date the ban expires.
--reason java.lang.String [1, 1] The ban reason.
--user java.util.UUID [1, 1] The user ID.

7.2.26.3.1. Example

[idstore]# user-ban-create
> --user 3a193a61-9427-4c24-8bd4-667d19914970
> --expires-on 2100-01-01T00:00:00+00:00
> --reason "You did something objectionable"
user-ban-delete - Unban a user.
The user-ban-delete command deletes a ban for a user.

7.2.27.2.2. Parameters

Parameter Type Cardinality Default Description
--user java.util.UUID [1, 1] The user ID.
user-ban-get - Retrieve the ban on a user, if one exists.
The user-ban-get command retrieves a ban for a user, if any.

7.2.28.2.2. Parameters

Parameter Type Cardinality Default Description
--user java.util.UUID [1, 1] The user ID.
user-create - Create a user.
The user-create command creates a new user.

7.2.29.2.2. Parameters

Parameter Type Cardinality Default Description
--email com.io7m.idstore.model.IdEmail [1, 1] The email address.
--id java.util.UUID [0, 1] The user ID.
--name java.lang.String [1, 1] The user name.
--password java.lang.String [1, 1] The password.
--real-name java.lang.String [1, 1] The user's real name.
user-email-add - Add an email address to a user.
The user-email-add command adds an email address to a user.

7.2.30.2.2. Parameters

Parameter Type Cardinality Default Description
--email com.io7m.idstore.model.IdEmail [1, 1] The email address.
--user java.util.UUID [1, 1] The user ID.
user-email-remove - Remove an email address from a user.
The user-email-remove command removes an email address from a user.

7.2.31.2.2. Parameters

Parameter Type Cardinality Default Description
--email com.io7m.idstore.model.IdEmail [1, 1] The email address.
--user java.util.UUID [1, 1] The user ID.
user-get-by-email - Retrieve a user by email address.
The user-get-by-email command retrieves a user by email address.

7.2.32.2.2. Parameters

Parameter Type Cardinality Default Description
--email com.io7m.idstore.model.IdEmail [1, 1] The email address.
user-get - Retrieve a user.
The user-get command retrieves a user.

7.2.33.2.2. Parameters

Parameter Type Cardinality Default Description
--user java.util.UUID [1, 1] The user ID.
user-login-history - Retrieve a user's login history.
The user-login-history command retrieves the login history for a user.

7.2.34.2.2. Parameters

Parameter Type Cardinality Default Description
--user java.util.UUID [1, 1] The user ID.
user-search-begin - Begin searching for users.
The user-search-begin command starts searching for users.

7.2.35.2.2. Parameters

Parameter Type Cardinality Default Description
--created-from java.time.OffsetDateTime [1, 1] 1970-01-01T00:00Z Return users created later than this date.
--created-to java.time.OffsetDateTime [1, 1] +101970-01-01T00:00Z Return users created earlier than this date.
--query java.lang.String [0, 1] Match users against this query text.
--updated-from java.time.OffsetDateTime [1, 1] 1970-01-01T00:00Z Return users updated later than this date.
--updated-to java.time.OffsetDateTime [1, 1] +101970-01-01T00:00Z Return users updated earlier than this date.
--limit java.lang.Integer [1, 1] 10 The maximum number of results per page.
user-search-by-email-begin - Begin searching for users by email.
The user-search-by-email-begin command starts searching for users by email address.

7.2.36.2.2. Parameters

Parameter Type Cardinality Default Description
--created-from java.time.OffsetDateTime [1, 1] 1970-01-01T00:00Z Return users created later than this date.
--created-to java.time.OffsetDateTime [1, 1] +101970-01-01T00:00Z Return users created earlier than this date.
--email java.lang.String [0, 1] Match user emails against this query text.
--updated-from java.time.OffsetDateTime [1, 1] 1970-01-01T00:00Z Return users updated later than this date.
--updated-to java.time.OffsetDateTime [1, 1] +101970-01-01T00:00Z Return users updated earlier than this date.
--limit java.lang.Integer [1, 1] 10 The maximum number of results per page.
user-search-by-email-next - Go to the next page of users.
The user-search-by-email-next command moves to the next page of search results.
user-search-by-email-previous - Go to the previous page of users.
The user-search-by-email-previous command moves to the previous page of search results.
user-search-next - Go to the next page of users.
The user-search-next command moves to the next page of search results.
user-search-previous - Go to the previous page of users.
The user-search-previous command moves to the previous page of search results.
user-update-password-expiration - Update an user's password expiration.
The user-update-password-expiration command updates the password expiration of a given user.

7.2.41.2.2. Parameters

Parameter Type Cardinality Default Description
--user java.util.UUID [1, 1] The user ID.
--expires com.io7m.idstore.protocol.admin.IdAPasswordExpirationSetType [1, 1] The password expiration.

7.2.41.3.1. Example

[idstore#] user-update-password-expiration
  --user 3a193a61-9427-4c24-8bd4-667d19914970
  --expires default

The password will not expire.

[idstore#] user-update-password-expiration
  --user 3a193a61-9427-4c24-8bd4-667d19914970
  --expires 2100-01-01T00:00:00+00:00

The password will expire at 2100-01-01T00:00:00Z.

[idstore#] user-update-password-expiration
  --user 3a193a61-9427-4c24-8bd4-667d19914970
  --expires never

The password will not expire.
version - Display the shell version.
The version command displays the shell version.
The Admin API service exposes one or more versions of the Admin protocol. The service uses the verdant protocol to advertise which versions of the Admin protocol are available. Executing a GET request to the root endpoint will yield a verdant message showing which versions of the Admin protocol are available. The Admin protocol is assigned the protocol identifier de1ef9f2-5ea7-388a-9b79-788c132abfd1.
As an example, the following shows that the Admin protocol version 1 is available at /admin/1/0/:

8.1.1.3. Verdant Example

$ curl https://idstore.example.com:51000/ | hexdump
0000:0000 | 00 00 00 01  00 00 00 01  DE 1E F9 F2  5E A7 38 8A | ........Þ.ùò^§8.
0000:0010 | 9B 79 78 8C  13 2A BF D1  00 00 00 01  00 00 00 00 | .yx..*¿Ñ........
0000:0020 | 00 00 00 0B  2F 61 64 6D  69 6E 2F 31  2F 30 2F    | ..../admin/1/0/
The Admin API service exposes a health check endpoint at /health. The endpoint returns a 200 status code and the string OK if the server's most recent internal health checks succeeded. The server returns a 500 status code and string not equal to OK if the server's most recent internal health checks failed. In both cases, the string is returned directly as a text/plain UTF-8 value.
The version 1 Admin protocol uses cedarbridge encoded messages over HTTP(s).
Send an IdA1CommandLogin command to /admin/1/0/login. If the login succeeds, a cookie named IDSTORE_ADMIN_API_SESSION will be set. This cookie must be included with all subsequent requests.
After logging in successfully, send commands of type IdA1Command* to /admin/1/0/command. Failed commands will yield a value of type IdA1ResponseError, whilst successful results will yield values of type IdA1Response*.
Generated documentation for the Cedarbridge schemas is available:

8.1.4.1. Schemas

;
; Copyright © 2023 Mark Raynsford <code@io7m.com> https://www.io7m.com
;
; Permission to use, copy, modify, and/or distribute this software for any
; purpose with or without fee is hereby granted, provided that the above
; copyright notice and this permission notice appear in all copies.
;
; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
; WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
; MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
; SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
; WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
; ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
; IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
;

[language cedarbridge 1 0]

[package com.io7m.idstore.protocol.admin.cb]

[import com.io7m.cedarbridge cb]
[import com.io7m.cedarbridge.time ct]

[documentation IdA1Password "Information for a hashed password."]
[record IdA1Password
  [documentation algorithm "The password algorithm."]
  [field algorithm cb:String]
  [documentation hash "The password hash."]
  [field hash cb:String]
  [documentation salt "The password salt."]
  [field salt cb:String]
  [documentation expires "The password expiration date."]
  [field expires [cb:Option ct:OffsetDateTime]]
]

[documentation IdA1TimestampUTC "A UTC timestamp value."]
[record IdA1TimestampUTC
  [documentation year "The year."]
  [field year cb:IntegerUnsigned32]
  [documentation month "The month in the range [1, 12]."]
  [field month cb:IntegerUnsigned8]
  [documentation day "The day of the month in the range [1, 31]."]
  [field day cb:IntegerUnsigned8]
  [documentation hour "The hour in the range [0, 23]."]
  [field hour cb:IntegerUnsigned8]
  [documentation minute "The minute in the range [0, 59]."]
  [field minute cb:IntegerUnsigned8]
  [documentation second "The second in the range [0, 59]."]
  [field second cb:IntegerUnsigned8]
  [documentation millisecond "The millisecond in the range [0, 999]."]
  [field millisecond cb:IntegerUnsigned32]
]

[documentation IdA1Ban "An account ban."]
[record IdA1Ban
  [documentation user "The user (or admin) ID."]
  [field user cb:UUID]
  [documentation reason "The ban reason."]
  [field reason cb:String]
  [documentation expires "The expiration date, if any."]
  [field expires [cb:Option IdA1TimestampUTC]]
]

[documentation IdA1TimeRange "A UTC time range."]
[record IdA1TimeRange
  [documentation lower "The lower bound."]
  [field lower IdA1TimestampUTC]
  [documentation upper "The upper bound."]
  [field upper IdA1TimestampUTC]
]

[documentation IdA1Login "A login record."]
[record IdA1Login
  [documentation user "The user ID."]
  [field user cb:UUID]
  [documentation time "The login time."]
  [field time IdA1TimestampUTC]
  [documentation host "The user's host."]
  [field host cb:String]
  [documentation agent "The user's agent."]
  [field agent cb:String]
]

[documentation IdA1AdminPermission "An admin permission."]
[variant IdA1AdminPermission
  [documentation AdminBan "A permission that allows for banning admins."]
  [case AdminBan]
  [documentation AdminCreate "A permission that allows for creating admins."]
  [case AdminCreate]
  [documentation AdminDelete "A permission that allows for deleting admins."]
  [case AdminDelete]
  [documentation AdminWriteCredentials "A permission that allows updating admin credentials."]
  [case AdminWriteCredentials]
  [documentation AdminWriteCredentialsSelf "A permission that allows an admin to update its own credentials."]
  [case AdminWriteCredentialsSelf]
  [documentation AdminWriteEmail "A permission that allows updating admin emails."]
  [case AdminWriteEmail]
  [documentation AdminWriteEmailSelf "A permission that allows an admin to update its own emails."]
  [case AdminWriteEmailSelf]
  [documentation AdminWritePermissions "A permission that allows updating admin permissions."]
  [case AdminWritePermissions]
  [documentation AdminWritePermissionsSelf "A permission that allows an admin to update its own permissions."]
  [case AdminWritePermissionsSelf]
  [documentation AdminRead "A permission that allows reading admins."]
  [case AdminRead]
  [documentation AuditRead "A permission that allows reading the audit log."]
  [case AuditRead]
  [documentation UserDelete "A permission that allows deleting users."]
  [case UserDelete]
  [documentation UserCreate "A permission that allows creating users."]
  [case UserCreate]
  [documentation UserWriteCredentials "A permission that allows updating user credentials."]
  [case UserWriteCredentials]
  [documentation UserWriteEmail "A permission that allows updating user emails."]
  [case UserWriteEmail]
  [documentation UserRead "A permission that allows reading users."]
  [case UserRead]
  [documentation UserBan "A permission that allows for banning users."]
  [case UserBan]
  [documentation MailTest "A permission that allows for sending test mail."]
  [case MailTest]
  [documentation MaintenanceMode "A permission that allows for changing the server's maintenance mode."]
  [case MaintenanceMode]
]

[documentation IdA1AdminColumn "A column to use for sorting admins."]
[variant IdA1AdminColumn
  [documentation ByID "Order by admin ID."]
  [case ByID]
  [documentation ByIDName "Order by admin id name."]
  [case ByIDName]
  [documentation ByRealName "Order by admin realname."]
  [case ByRealName]
  [documentation ByTimeCreated "Order by admin creation time."]
  [case ByTimeCreated]
  [documentation ByTimeUpdated "Order by admin update time."]
  [case ByTimeUpdated]
]

[documentation IdA1AdminColumnOrdering "A column ordering spec."]
[record IdA1AdminColumnOrdering
  [documentation column "The admin column."]
  [field column IdA1AdminColumn]
  [documentation ascending "True if the results should be ascending."]
  [field ascending cb:Boolean]
]

[documentation IdA1AdminSearchParameters "Search parameters for admins."]
[record IdA1AdminSearchParameters
  [documentation timeCreatedRange "The range of creation times for returned admins."]
  [field timeCreatedRange IdA1TimeRange]
  [documentation timeUpdatedRange "The range of update times for returned admins."]
  [field timeUpdatedRange IdA1TimeRange]
  [documentation search "The search query text."]
  [field search [cb:Option cb:String]]
  [documentation ordering "The result ordering."]
  [field ordering IdA1AdminColumnOrdering]
  [documentation limit "The limit on the number of results."]
  [field limit cb:IntegerUnsigned16]
]

[documentation IdA1AdminSearchByEmailParameters "Search parameters for admins (by email)."]
[record IdA1AdminSearchByEmailParameters
  [documentation timeCreatedRange "The range of creation times for returned admins."]
  [field timeCreatedRange IdA1TimeRange]
  [documentation timeUpdatedRange "The range of update times for returned admins."]
  [field timeUpdatedRange IdA1TimeRange]
  [documentation search "The search query text."]
  [field search cb:String]
  [documentation ordering "The result ordering."]
  [field ordering IdA1AdminColumnOrdering]
  [documentation limit "The limit on the number of results."]
  [field limit cb:IntegerUnsigned16]
]

[documentation IdA1AuditSearchParameters "Search parameters for audit records."]
[record IdA1AuditSearchParameters
  [documentation timeRange "The range of time to include."]
  [field timeRange IdA1TimeRange]
  [documentation owner "All records contain this owner, if specified."]
  [field owner [cb:Option cb:String]]
  [documentation type "All records contain this type, if specified."]
  [field type [cb:Option cb:String]]
  [documentation limit "The limit on the number of results."]
  [field limit cb:IntegerUnsigned16]
]

[documentation IdA1UserColumn "A column to use for sorting users."]
[variant IdA1UserColumn
  [documentation ByID "Order by user ID."]
  [case ByID]
  [documentation ByIDName "Order by user id name."]
  [case ByIDName]
  [documentation ByRealName "Order by user realname."]
  [case ByRealName]
  [documentation ByTimeCreated "Order by user creation time."]
  [case ByTimeCreated]
  [documentation ByTimeUpdated "Order by user update time."]
  [case ByTimeUpdated]
]

[documentation IdA1UserColumnOrdering "A column ordering spec."]
[record IdA1UserColumnOrdering
  [documentation column "The user column."]
  [field column IdA1UserColumn]
  [documentation ascending "True if the results should be ascending."]
  [field ascending cb:Boolean]
]

[documentation IdA1UserSearchParameters "Search parameters for users."]
[record IdA1UserSearchParameters
  [documentation timeCreatedRange "The range of creation times for returned users."]
  [field timeCreatedRange IdA1TimeRange]
  [documentation timeUpdatedRange "The range of update times for returned users."]
  [field timeUpdatedRange IdA1TimeRange]
  [documentation search "The search query text."]
  [field search [cb:Option cb:String]]
  [documentation ordering "The result ordering."]
  [field ordering IdA1UserColumnOrdering]
  [documentation limit "The limit on the number of results."]
  [field limit cb:IntegerUnsigned16]
]

[documentation IdA1UserSearchByEmailParameters "Search parameters for users (by email)."]
[record IdA1UserSearchByEmailParameters
  [documentation timeCreatedRange "The range of creation times for returned users."]
  [field timeCreatedRange IdA1TimeRange]
  [documentation timeUpdatedRange "The range of update times for returned users."]
  [field timeUpdatedRange IdA1TimeRange]
  [documentation search "The search query text."]
  [field search cb:String]
  [documentation ordering "The result ordering."]
  [field ordering IdA1UserColumnOrdering]
  [documentation limit "The limit on the number of results."]
  [field limit cb:IntegerUnsigned16]
]

[documentation IdA1Admin "An administrator."]
[record IdA1Admin
  [documentation id "The admin ID."]
  [field id cb:UUID]
  [documentation idName "The admin ID name."]
  [field idName cb:String]
  [documentation realName "The admin real name."]
  [field realName cb:String]
  [documentation emails "The admin emails."]
  [field emails [cb:List cb:String]]
  [documentation timeCreated "The time the admin was created."]
  [field timeCreated IdA1TimestampUTC]
  [documentation timeUpdated "The time the admin was last updated."]
  [field timeUpdated IdA1TimestampUTC]
  [documentation password "The hashed admin password."]
  [field password IdA1Password]
  [documentation permissions "The admin permissions."]
  [field permissions [cb:List IdA1AdminPermission]]
]

[documentation IdA1Page "A page of results."]
[record IdA1Page
  [documentation T "The type of result values."]
  [parameter T]
  [documentation items "The list of results."]
  [field items [cb:List T]]
  [documentation pageIndex "The page index."]
  [field pageIndex cb:IntegerUnsigned32]
  [documentation pageCount "The page count."]
  [field pageCount cb:IntegerUnsigned32]
  [documentation pageFirstOffset "The offset of the first item in the page."]
  [field pageFirstOffset cb:IntegerUnsigned64]]

[documentation IdA1AdminSummary "An admin summary."]
[record IdA1AdminSummary
  [documentation id "The admin ID."]
  [field id cb:UUID]
  [documentation idName "The admin ID name."]
  [field idName cb:String]
  [documentation realName "The admin real name."]
  [field realName cb:String]
  [documentation timeCreated "The time the admin was created."]
  [field timeCreated IdA1TimestampUTC]
  [documentation timeUpdated "The time the admin was last updated."]
  [field timeUpdated IdA1TimestampUTC]
]

[documentation IdA1AuditEvent "An audit event."]
[record IdA1AuditEvent
  [documentation id "The audit event ID."]
  [field id cb:IntegerUnsigned64]
  [documentation owner "The audit event owner."]
  [field owner cb:UUID]
  [documentation time "The audit event time."]
  [field time IdA1TimestampUTC]
  [documentation type "The audit event type."]
  [field type cb:String]
  [documentation data "The audit event data."]
  [field data [cb:Map cb:String cb:String]]
]

[documentation IdA1User "A user."]
[record IdA1User
  [documentation id "The user ID."]
  [field id cb:UUID]
  [documentation idName "The user ID name."]
  [field idName cb:String]
  [documentation realName "The user real name."]
  [field realName cb:String]
  [documentation emails "The user emails."]
  [field emails [cb:List cb:String]]
  [documentation timeCreated "The time the user was created."]
  [field timeCreated IdA1TimestampUTC]
  [documentation timeUpdated "The time the user was last updated."]
  [field timeUpdated IdA1TimestampUTC]
  [documentation password "The hashed user password."]
  [field password IdA1Password]
]

[documentation IdA1UserSummary "A user summary."]
[record IdA1UserSummary
  [documentation id "The user ID."]
  [field id cb:UUID]
  [documentation idName "The user ID name."]
  [field idName cb:String]
  [documentation realName "The user real name."]
  [field realName cb:String]
  [documentation timeCreated "The time the user was created."]
  [field timeCreated IdA1TimestampUTC]
  [documentation timeUpdated "The time the user was last updated."]
  [field timeUpdated IdA1TimestampUTC]
]

[documentation IdA1PasswordExpirationSet "The possible behaviours for setting password expirations."]
[variant IdA1PasswordExpirationSet
  [documentation Never "Never expire, regardless of the server setting."]
  [case Never]
  [documentation Refresh "Refresh the password to the server setting."]
  [case Refresh]
  [documentation Specific "Expire at the given time, regardless of the server setting."]
  [case Specific [field time ct:OffsetDateTime]]
]

;
; Admin commands.
;

[documentation IdA1CommandAdminBanCreate "Ban an admin."]
[record IdA1CommandAdminBanCreate
  [documentation ban "The ban."]
  [field ban IdA1Ban]
]

[documentation IdA1CommandAdminBanDelete "Remove a ban on an admin."]
[record IdA1CommandAdminBanDelete
  [documentation adminId "The admin."]
  [field adminId cb:UUID]
]

[documentation IdA1CommandAdminBanGet "Get a ban on an admin."]
[record IdA1CommandAdminBanGet
  [documentation adminId "The admin."]
  [field adminId cb:UUID]
]

[documentation IdA1CommandAdminCreate "Create an admin."]
[record IdA1CommandAdminCreate
  [documentation adminId "The admin."]
  [field adminId [cb:Option cb:UUID]]
  [documentation idName "The admin ID name."]
  [field idName cb:String]
  [documentation realName "The admin real name."]
  [field realName cb:String]
  [documentation email "The admin email."]
  [field email cb:String]
  [documentation password "The admin password."]
  [field password IdA1Password]
  [documentation permissions "The set of permissions."]
  [field permissions [cb:List IdA1AdminPermission]]
]

[documentation IdA1CommandAdminDelete "Delete an admin."]
[record IdA1CommandAdminDelete
  [documentation adminId "The admin."]
  [field adminId cb:UUID]
]

[documentation IdA1CommandAdminEmailAdd "Add an email address to the given admin."]
[record IdA1CommandAdminEmailAdd
  [documentation adminId "The admin."]
  [field adminId cb:UUID]
  [documentation email "The email address."]
  [field email cb:String]
]

[documentation IdA1CommandAdminEmailRemove "Remove an email address from the given admin."]
[record IdA1CommandAdminEmailRemove
  [documentation adminId "The admin."]
  [field adminId cb:UUID]
  [documentation email "The email address."]
  [field email cb:String]
]

[documentation IdA1CommandAdminGet "Get the given admin."]
[record IdA1CommandAdminGet
  [documentation adminId "The admin."]
  [field adminId cb:UUID]
]

[documentation IdA1CommandAdminGetByEmail "Get the admin with the given email address."]
[record IdA1CommandAdminGetByEmail
  [documentation email "The email address."]
  [field email cb:String]
]

[documentation IdA1CommandAdminPermissionGrant "Grant a permission to the given admin."]
[record IdA1CommandAdminPermissionGrant
  [documentation adminId "The admin."]
  [field adminId cb:UUID]
  [documentation permission "The permission."]
  [field permission IdA1AdminPermission]
]

[documentation IdA1CommandAdminPermissionRevoke "Revoke a permission from the given admin."]
[record IdA1CommandAdminPermissionRevoke
  [documentation adminId "The admin."]
  [field adminId cb:UUID]
  [documentation permission "The permission."]
  [field permission IdA1AdminPermission]
]

[documentation IdA1CommandAdminSearchBegin "Start searching for admins."]
[record IdA1CommandAdminSearchBegin
  [documentation parameters "The search parameters."]
  [field parameters IdA1AdminSearchParameters]
]

[documentation IdA1CommandAdminSearchByEmailBegin "Start searching for admins (by email)."]
[record IdA1CommandAdminSearchByEmailBegin
  [documentation parameters "The search parameters."]
  [field parameters IdA1AdminSearchByEmailParameters]
]

[documentation IdA1CommandAdminSearchByEmailNext "Get the next page of admin search results."]
[record IdA1CommandAdminSearchByEmailNext]

[documentation IdA1CommandAdminSearchByEmailPrevious "Get the previous page of admin search results."]
[record IdA1CommandAdminSearchByEmailPrevious]

[documentation IdA1CommandAdminSearchNext "Get the next page of admin search results."]
[record IdA1CommandAdminSearchNext]

[documentation IdA1CommandAdminSearchPrevious "Get the previous page of admin search results."]
[record IdA1CommandAdminSearchPrevious]

[documentation IdA1CommandAdminSelf "A request to fetch the admin's own profile."]
[record IdA1CommandAdminSelf]

[documentation IdA1CommandAdminUpdateCredentials "A request to edit an admin's credentials."]
[record IdA1CommandAdminUpdateCredentials
  [documentation adminId "The admin."]
  [field adminId cb:UUID]
  [documentation idName "The admin's new ID name."]
  [field idName [cb:Option cb:String]]
  [documentation realName "The admin's new real name."]
  [field realName [cb:Option cb:String]]
  [documentation password "The admin's new password"]
  [field password [cb:Option IdA1Password]]
]

[documentation IdA1CommandAdminUpdatePasswordExpiration "A request to update the admin's password expiration."]
[record IdA1CommandAdminUpdatePasswordExpiration
  [documentation userId "The admin."]
  [field userId cb:UUID]
  [documentation set "The set behaviour."]
  [field set IdA1PasswordExpirationSet]
]

[documentation IdA1CommandAuditSearchBegin "Start searching/listing audit records."]
[record IdA1CommandAuditSearchBegin
  [documentation parameters "The search parameters."]
  [field parameters IdA1AuditSearchParameters]
]

[documentation IdA1CommandAuditSearchNext "Get the next page of audit search results."]
[record IdA1CommandAuditSearchNext]

[documentation IdA1CommandAuditSearchPrevious "Get the previous page of audit search results."]
[record IdA1CommandAuditSearchPrevious]

[documentation IdA1CommandMailTest "Send a test email to a given address."]
[record IdA1CommandMailTest
  [documentation address "The target email address."]
  [field address cb:String]
  [documentation token "The email token."]
  [field token cb:String]
]

[documentation IdA1CommandMaintenanceModeSet "Move the server to/from maintenance mode."]
[record IdA1CommandMaintenanceModeSet
  [documentation message "The message (if maintenance mode is to be turned on)."]
  [field message [cb:Option cb:String]]
]

[documentation IdA1CommandLogin "A request to log in."]
[record IdA1CommandLogin
  [documentation userName "The username."]
  [field userName cb:String]
  [documentation password "The password."]
  [field password cb:String]
  [documentation metadata "The extra metadata"]
  [field metadata [cb:Map cb:String cb:String]]
]

;
; User commands.
;

[documentation IdA1CommandUserBanCreate "Ban a user."]
[record IdA1CommandUserBanCreate
  [documentation ban "The ban."]
  [field ban IdA1Ban]
]

[documentation IdA1CommandUserBanDelete "Remove a ban on a user."]
[record IdA1CommandUserBanDelete
  [documentation userId "The user."]
  [field userId cb:UUID]
]

[documentation IdA1CommandUserBanGet "Get a ban on a user."]
[record IdA1CommandUserBanGet
  [documentation userId "The user."]
  [field userId cb:UUID]
]

[documentation IdA1CommandUserCreate "Create a user."]
[record IdA1CommandUserCreate
  [documentation userId "The user."]
  [field userId [cb:Option cb:UUID]]
  [documentation idName "The user ID name."]
  [field idName cb:String]
  [documentation realName "The user real name."]
  [field realName cb:String]
  [documentation email "The user email."]
  [field email cb:String]
  [documentation password "The user password."]
  [field password IdA1Password]
]

[documentation IdA1CommandUserDelete "Delete a user."]
[record IdA1CommandUserDelete
  [documentation userId "The user."]
  [field userId cb:UUID]
]

[documentation IdA1CommandUserEmailAdd "Add an email address to the given user."]
[record IdA1CommandUserEmailAdd
  [documentation userId "The user."]
  [field userId cb:UUID]
  [documentation email "The email address."]
  [field email cb:String]
]

[documentation IdA1CommandUserEmailRemove "Remove an email address from the given user."]
[record IdA1CommandUserEmailRemove
  [documentation userId "The user."]
  [field userId cb:UUID]
  [documentation email "The email address."]
  [field email cb:String]
]

[documentation IdA1CommandUserGet "Get the given user."]
[record IdA1CommandUserGet
  [documentation userId "The user."]
  [field userId cb:UUID]
]

[documentation IdA1CommandUserGetByEmail "Get the user with the given email address."]
[record IdA1CommandUserGetByEmail
  [documentation email "The email address."]
  [field email cb:String]
]

[documentation IdA1CommandUserLoginHistory "Get the given user's login history."]
[record IdA1CommandUserLoginHistory
  [documentation userId "The user."]
  [field userId cb:UUID]
]

[documentation IdA1CommandUserSearchBegin "Start searching for users."]
[record IdA1CommandUserSearchBegin
  [documentation parameters "The search parameters."]
  [field parameters IdA1UserSearchParameters]
]

[documentation IdA1CommandUserSearchByEmailBegin "Start searching for users (by email)."]
[record IdA1CommandUserSearchByEmailBegin
  [documentation parameters "The search parameters."]
  [field parameters IdA1UserSearchByEmailParameters]
]

[documentation IdA1CommandUserSearchByEmailNext "Get the next page of user search results."]
[record IdA1CommandUserSearchByEmailNext]

[documentation IdA1CommandUserSearchByEmailPrevious "Get the previous page of user search results."]
[record IdA1CommandUserSearchByEmailPrevious]

[documentation IdA1CommandUserSearchNext "Get the next page of user search results."]
[record IdA1CommandUserSearchNext]

[documentation IdA1CommandUserSearchPrevious "Get the previous page of user search results."]
[record IdA1CommandUserSearchPrevious]

[documentation IdA1CommandUserUpdateCredentials "A request to fetch the user's own profile."]
[record IdA1CommandUserUpdateCredentials
  [documentation userId "The user."]
  [field userId cb:UUID]
  [documentation idName "The user's new ID name."]
  [field idName [cb:Option cb:String]]
  [documentation realName "The user's new real name."]
  [field realName [cb:Option cb:String]]
  [documentation password "The user's new password"]
  [field password [cb:Option IdA1Password]]
]

[documentation IdA1CommandUserUpdatePasswordExpiration "A request to update the user's password expiration."]
[record IdA1CommandUserUpdatePasswordExpiration
  [documentation userId "The user."]
  [field userId cb:UUID]
  [documentation set "The set behaviour."]
  [field set IdA1PasswordExpirationSet]
]

;
; Admin responses.
;

[documentation IdA1ResponseBlame "A blame assignment."]
[variant IdA1ResponseBlame
  [documentation BlameClient "The client sent a bad response."]
  [case BlameClient]
  [documentation BlameServer "Something went wrong on the server."]
  [case BlameServer]
]

[documentation IdA1ResponseAdminBanCreate "A response to IdA1CommandAdminBanCreate."]
[record IdA1ResponseAdminBanCreate
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation ban "The ban."]
  [field ban IdA1Ban]
]

[documentation IdA1ResponseAdminBanDelete "A response to IdA1CommandAdminBanDelete."]
[record IdA1ResponseAdminBanDelete
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
]

[documentation IdA1ResponseAdminBanGet "A response to IdA1CommandAdminBanGet."]
[record IdA1ResponseAdminBanGet
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation ban "The ban."]
  [field ban [cb:Option IdA1Ban]]
]

[documentation IdA1ResponseAdminCreate "A response to IdA1CommandAdminCreate."]
[record IdA1ResponseAdminCreate
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation admin "The admin."]
  [field admin IdA1Admin]
]

[documentation IdA1ResponseAdminDelete "A response to IdA1CommandAdminDelete."]
[record IdA1ResponseAdminDelete
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
]

[documentation IdA1ResponseAdminGet "A response to IdA1CommandAdminGet."]
[record IdA1ResponseAdminGet
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation admin "The admin."]
  [field admin [cb:Option IdA1Admin]]
]

[documentation IdA1ResponseAdminSearchBegin "A response to IdA1CommandAdminSearchBegin."]
[record IdA1ResponseAdminSearchBegin
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation page "The first page of results."]
  [field page [IdA1Page IdA1AdminSummary]]
]

[documentation IdA1ResponseAdminSearchNext "A response to IdA1CommandAdminSearchNext."]
[record IdA1ResponseAdminSearchNext
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation page "The next page of results."]
  [field page [IdA1Page IdA1AdminSummary]]
]

[documentation IdA1ResponseAdminSearchPrevious "A response to IdA1CommandAdminSearchPrevious."]
[record IdA1ResponseAdminSearchPrevious
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation page "The previous page of results."]
  [field page [IdA1Page IdA1AdminSummary]]
]

[documentation IdA1ResponseAdminSearchByEmailBegin "A response to IdA1CommandAdminSearchByEmailBegin."]
[record IdA1ResponseAdminSearchByEmailBegin
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation page "The first page of results."]
  [field page [IdA1Page IdA1AdminSummary]]
]

[documentation IdA1ResponseAdminSearchByEmailNext "A response to IdA1CommandAdminSearchByEmailNext."]
[record IdA1ResponseAdminSearchByEmailNext
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation page "The next page of results."]
  [field page [IdA1Page IdA1AdminSummary]]
]

[documentation IdA1ResponseAdminSearchByEmailPrevious "A response to IdA1CommandAdminSearchByEmailPrevious."]
[record IdA1ResponseAdminSearchByEmailPrevious
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation page "The previous page of results."]
  [field page [IdA1Page IdA1AdminSummary]]
]

[documentation IdA1ResponseAdminSelf "A response to IdA1CommandAdminSelf."]
[record IdA1ResponseAdminSelf
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation admin "The admin."]
  [field admin IdA1Admin]
]

[documentation IdA1ResponseAdminUpdate "A response to IdA1CommandAdminUpdateCredentials."]
[record IdA1ResponseAdminUpdate
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation admin "The admin."]
  [field admin IdA1Admin]
]

[documentation IdA1ResponseAuditSearchBegin "A response to IdA1CommandAuditSearchBegin."]
[record IdA1ResponseAuditSearchBegin
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation page "The first page of results."]
  [field page [IdA1Page IdA1AuditEvent]]
]

[documentation IdA1ResponseAuditSearchNext "A response to IdA1CommandAuditSearchNext."]
[record IdA1ResponseAuditSearchNext
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation page "The next page of results."]
  [field page [IdA1Page IdA1AuditEvent]]
]

[documentation IdA1ResponseAuditSearchPrevious "A response to IdA1CommandAuditSearchPrevious."]
[record IdA1ResponseAuditSearchPrevious
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation page "The previous page of results."]
  [field page [IdA1Page IdA1AuditEvent]]
]

[documentation IdA1ResponseMaintenanceModeSet "A response to IdA1CommandMaintenanceModeSet."]
[record IdA1ResponseMaintenanceModeSet
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation message "The response message."]
  [field message cb:String]
]

[documentation IdA1ResponseMailTest "A response to IdA1CommandMailTest."]
[record IdA1ResponseMailTest
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation token "The unique token that was placed into the test email."]
  [field token cb:String]
]

;
; User responses.
;

[documentation IdA1ResponseUserBanCreate "A response to IdA1CommandUserBanCreate."]
[record IdA1ResponseUserBanCreate
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation ban "The ban."]
  [field ban IdA1Ban]
]

[documentation IdA1ResponseUserBanDelete "A response to IdA1CommandUserBanDelete."]
[record IdA1ResponseUserBanDelete
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
]

[documentation IdA1ResponseUserBanGet "A response to IdA1CommandUserBanGet."]
[record IdA1ResponseUserBanGet
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation ban "The ban."]
  [field ban [cb:Option IdA1Ban]]
]

[documentation IdA1ResponseUserCreate "A response to IdA1CommandUserCreate."]
[record IdA1ResponseUserCreate
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation user "The user."]
  [field user IdA1User]
]

[documentation IdA1ResponseUserDelete "A response to IdA1CommandUserDelete."]
[record IdA1ResponseUserDelete
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
]

[documentation IdA1ResponseUserGet "A response to IdA1CommandUserGet."]
[record IdA1ResponseUserGet
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation user "The user."]
  [field user [cb:Option IdA1User]]
]

[documentation IdA1ResponseUserSearchBegin "A response to IdA1CommandUserSearchBegin."]
[record IdA1ResponseUserSearchBegin
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation page "The first page of results."]
  [field page [IdA1Page IdA1UserSummary]]
]

[documentation IdA1ResponseUserSearchNext "A response to IdA1CommandUserSearchNext."]
[record IdA1ResponseUserSearchNext
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation page "The next page of results."]
  [field page [IdA1Page IdA1UserSummary]]
]

[documentation IdA1ResponseUserSearchPrevious "A response to IdA1CommandUserSearchPrevious."]
[record IdA1ResponseUserSearchPrevious
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation page "The previous page of results."]
  [field page [IdA1Page IdA1UserSummary]]
]

[documentation IdA1ResponseUserSearchByEmailBegin "A response to IdA1CommandUserSearchByEmailBegin."]
[record IdA1ResponseUserSearchByEmailBegin
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation page "The first page of results."]
  [field page [IdA1Page IdA1UserSummary]]
]

[documentation IdA1ResponseUserSearchByEmailNext "A response to IdA1CommandUserSearchByEmailNext."]
[record IdA1ResponseUserSearchByEmailNext
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation page "The next page of results."]
  [field page [IdA1Page IdA1UserSummary]]
]

[documentation IdA1ResponseUserSearchByEmailPrevious "A response to IdA1CommandUserSearchByEmailPrevious."]
[record IdA1ResponseUserSearchByEmailPrevious
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation page "The previous page of results."]
  [field page [IdA1Page IdA1UserSummary]]
]

[documentation IdA1ResponseUserUpdate "A response to IdA1CommandUserUpdateCredentials."]
[record IdA1ResponseUserUpdate
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation user "The user."]
  [field user IdA1User]
]

[documentation IdA1ResponseUserLoginHistory "A response to IdA1CommandUserLoginHistory."]
[record IdA1ResponseUserLoginHistory
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation requestId "The user's login history."]
  [field history [cb:List IdA1Login]]
]

;
; General responses.
;

[documentation IdA1ResponseError "An error response."]
[record IdA1ResponseError
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation errorCode "The error code."]
  [field errorCode cb:String]
  [documentation message "The error message."]
  [field message cb:String]
  [documentation attributes "The error attributes."]
  [field attributes [cb:Map cb:String cb:String]]
  [documentation remediatingAction "The remediating action, if any."]
  [field remediatingAction [cb:Option cb:String]]
  [documentation blame "The blame assignment."]
  [field blame IdA1ResponseBlame]
]

[documentation IdA1ResponseLogin "A response to IdA1CommandLogin."]
[record IdA1ResponseLogin
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation admin "The admin."]
  [field admin IdA1Admin]
]

[documentation IdA "The Admin protocol."]
[protocol IdA
  [version 1
    [types-added
      IdA1CommandAdminBanCreate
      IdA1CommandAdminBanDelete
      IdA1CommandAdminBanGet
      IdA1CommandAdminCreate
      IdA1CommandAdminDelete
      IdA1CommandAdminEmailAdd
      IdA1CommandAdminEmailRemove
      IdA1CommandAdminGet
      IdA1CommandAdminGetByEmail
      IdA1CommandAdminPermissionGrant
      IdA1CommandAdminPermissionRevoke
      IdA1CommandAdminSearchBegin
      IdA1CommandAdminSearchByEmailBegin
      IdA1CommandAdminSearchByEmailNext
      IdA1CommandAdminSearchByEmailPrevious
      IdA1CommandAdminSearchNext
      IdA1CommandAdminSearchPrevious
      IdA1CommandAdminSelf
      IdA1CommandAdminUpdateCredentials
      IdA1CommandAdminUpdatePasswordExpiration
      IdA1CommandAuditSearchBegin
      IdA1CommandAuditSearchNext
      IdA1CommandAuditSearchPrevious
      IdA1CommandLogin
      IdA1CommandMailTest
      IdA1CommandMaintenanceModeSet
      IdA1CommandUserBanCreate
      IdA1CommandUserBanDelete
      IdA1CommandUserBanGet
      IdA1CommandUserCreate
      IdA1CommandUserDelete
      IdA1CommandUserEmailAdd
      IdA1CommandUserEmailRemove
      IdA1CommandUserGet
      IdA1CommandUserGetByEmail
      IdA1CommandUserLoginHistory
      IdA1CommandUserSearchBegin
      IdA1CommandUserSearchByEmailBegin
      IdA1CommandUserSearchByEmailNext
      IdA1CommandUserSearchByEmailPrevious
      IdA1CommandUserSearchNext
      IdA1CommandUserSearchPrevious
      IdA1CommandUserUpdateCredentials
      IdA1CommandUserUpdatePasswordExpiration
      IdA1ResponseAdminBanCreate
      IdA1ResponseAdminBanDelete
      IdA1ResponseAdminBanGet
      IdA1ResponseAdminCreate
      IdA1ResponseAdminDelete
      IdA1ResponseAdminGet
      IdA1ResponseAdminSearchBegin
      IdA1ResponseAdminSearchByEmailBegin
      IdA1ResponseAdminSearchByEmailNext
      IdA1ResponseAdminSearchByEmailPrevious
      IdA1ResponseAdminSearchNext
      IdA1ResponseAdminSearchPrevious
      IdA1ResponseAdminSelf
      IdA1ResponseAdminUpdate
      IdA1ResponseAuditSearchBegin
      IdA1ResponseAuditSearchNext
      IdA1ResponseAuditSearchPrevious
      IdA1ResponseError
      IdA1ResponseLogin
      IdA1ResponseMailTest
      IdA1ResponseMaintenanceModeSet
      IdA1ResponseUserBanCreate
      IdA1ResponseUserBanDelete
      IdA1ResponseUserBanGet
      IdA1ResponseUserCreate
      IdA1ResponseUserDelete
      IdA1ResponseUserGet
      IdA1ResponseUserLoginHistory
      IdA1ResponseUserSearchBegin
      IdA1ResponseUserSearchByEmailBegin
      IdA1ResponseUserSearchByEmailNext
      IdA1ResponseUserSearchByEmailPrevious
      IdA1ResponseUserSearchNext
      IdA1ResponseUserSearchPrevious
      IdA1ResponseUserUpdate
    ]
  ]
]
The User API service exposes one or more versions of the User protocol. The service uses the verdant protocol to advertise which versions of the User protocol are available. Executing a GET request to the root endpoint will yield a verdant message showing which versions of the User protocol are available. The User protocol is assigned the protocol identifier ed628c5a-0182-36ab-bee3-5fe6f6a21894.
As an example, the following shows that the User protocol version 1 is available at /user/1/0/:

8.2.1.3. Verdant Example

$ curl https://idstore.example.com:50000/ | hexdump
0000:0000 | 00 00 00 01  00 00 00 01  ED 62 8C 5A  01 82 36 AB | ........íb.Z..6«
0000:0010 | BE E3 5F E6  F6 A2 18 94  00 00 00 01  00 00 00 00 | ¾ã_æö¢..........
0000:0020 | 00 00 00 0A  2F 75 73 65  72 2F 31 2F  30 2F       | ..../user/1/0/
The User API service exposes a health check endpoint at /health. The endpoint returns a 200 status code and the string OK if the server's most recent internal health checks succeeded. The server returns a 500 status code and string not equal to OK if the server's most recent internal health checks failed. In both cases, the string is returned directly as a text/plain UTF-8 value.
The version 1 User protocol uses cedarbridge encoded messages over HTTP(s).
Send an IdU1CommandLogin command to /user/1/0/login. If the login succeeds, a cookie named IDSTORE_USER_API_SESSION will be set. This cookie must be included with all subsequent requests.
After logging in successfully, send commands of type IdU1Command* to /user/1/0/command. Failed commands will yield a value of type IdU1ResponseError, whilst successful results will yield values of type IdU1Response*.
Generated documentation for the Cedarbridge schemas is available:

8.2.4.1. Schemas

;
; Copyright © 2023 Mark Raynsford <code@io7m.com> https://www.io7m.com
;
; Permission to use, copy, modify, and/or distribute this software for any
; purpose with or without fee is hereby granted, provided that the above
; copyright notice and this permission notice appear in all copies.
;
; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
; WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
; MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
; SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
; WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
; ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
; IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
;

[language cedarbridge 1 0]

[package com.io7m.idstore.protocol.user.cb]

[import com.io7m.cedarbridge cb]
[import com.io7m.cedarbridge.time ct]

[documentation IdU1Password "Information for a hashed password."]
[record IdU1Password
  [documentation algorithm "The password algorithm."]
  [field algorithm cb:String]
  [documentation hash "The password hash."]
  [field hash cb:String]
  [documentation salt "The password salt."]
  [field salt cb:String]
  [documentation expires "The password expiration date."]
  [field expires [cb:Option ct:OffsetDateTime]]
]

[documentation IdU1TimestampUTC "A UTC timestamp value."]
[record IdU1TimestampUTC
  [documentation year "The year."]
  [field year cb:IntegerUnsigned32]
  [documentation month "The month in the range [1, 12]."]
  [field month cb:IntegerUnsigned8]
  [documentation day "The day of the month in the range [1, 31]."]
  [field day cb:IntegerUnsigned8]
  [documentation hour "The hour in the range [0, 23]."]
  [field hour cb:IntegerUnsigned8]
  [documentation minute "The minute in the range [0, 59]."]
  [field minute cb:IntegerUnsigned8]
  [documentation second "The second in the range [0, 59]."]
  [field second cb:IntegerUnsigned8]
  [documentation millisecond "The millisecond in the range [0, 999]."]
  [field millisecond cb:IntegerUnsigned32]
]

[documentation IdU1User "A user."]
[record IdU1User
  [documentation id "The user ID."]
  [field id cb:UUID]
  [documentation idName "The user ID name."]
  [field idName cb:String]
  [documentation realName "The user real name."]
  [field realName cb:String]
  [documentation emails "The user emails."]
  [field emails [cb:List cb:String]]
  [documentation timeCreated "The time the account was created."]
  [field timeCreated IdU1TimestampUTC]
  [documentation timeUpdated "The time the account was updated."]
  [field timeUpdated IdU1TimestampUTC]
  [documentation password "The user password."]
  [field password IdU1Password]
]

;
; Commands.
;

[documentation IdU1CommandLogin "A request to log in."]
[record IdU1CommandLogin
  [documentation userName "The username."]
  [field userName cb:String]
  [documentation password "The password."]
  [field password cb:String]
  [documentation metadata "Extra metadata included with the login request."]
  [field metadata [cb:Map cb:String cb:String]]
]

[documentation IdU1CommandUserSelf "A request to fetch the user's own profile."]
[record IdU1CommandUserSelf]

[documentation IdU1CommandEmailAddBegin "A request to add an email address."]
[record IdU1CommandEmailAddBegin
  [documentation email "The email address."]
  [field email cb:String]
]

[documentation IdU1CommandEmailRemoveBegin "A request to remove an email address."]
[record IdU1CommandEmailRemoveBegin
  [documentation email "The email address."]
  [field email cb:String]
]

[documentation IdU1CommandEmailAddPermit "A request to complete an email address challenge."]
[record IdU1CommandEmailAddPermit
  [documentation token "The challenge token."]
  [field token cb:String]
]

[documentation IdU1CommandEmailRemovePermit "A request to complete an email address challenge."]
[record IdU1CommandEmailRemovePermit
  [documentation token "The challenge token."]
  [field token cb:String]
]

[documentation IdU1CommandEmailAddDeny "A request to complete an email address challenge."]
[record IdU1CommandEmailAddDeny
  [documentation token "The challenge token."]
  [field token cb:String]
]

[documentation IdU1CommandEmailRemoveDeny "A request to complete an email address challenge."]
[record IdU1CommandEmailRemoveDeny
  [documentation token "The challenge token."]
  [field token cb:String]
]

[documentation IdU1CommandRealnameUpdate "A request to update the user's real name."]
[record IdU1CommandRealnameUpdate
  [documentation name "The new name."]
  [field name cb:String]
]

[documentation IdU1CommandPasswordUpdate "A request to update the user's password."]
[record IdU1CommandPasswordUpdate
  [documentation password "The new password."]
  [field password cb:String]
  [documentation passwordConfirm "The password confirmation."]
  [field passwordConfirm cb:String]
]

;
; Responses.
;

[documentation IdU1ResponseBlame "A blame assignment."]
[variant IdU1ResponseBlame
  [documentation BlameClient "The client sent a bad response."]
  [case BlameClient]
  [documentation BlameServer "Something went wrong on the server."]
  [case BlameServer]
]

[documentation IdU1ResponseError "An error response."]
[record IdU1ResponseError
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation errorCode "The error code."]
  [field errorCode cb:String]
  [documentation message "The error message."]
  [field message cb:String]
  [documentation attributes "The error attributes."]
  [field attributes [cb:Map cb:String cb:String]]
  [documentation remediatingAction "The remediating action, if any."]
  [field remediatingAction [cb:Option cb:String]]
  [documentation blame "The blame assignment."]
  [field blame IdU1ResponseBlame]
]

[documentation IdU1ResponseLogin "A response to IdU1CommandLogin."]
[record IdU1ResponseLogin
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation user "The user."]
  [field user IdU1User]
]

[documentation IdU1ResponseUserSelf "A response to IdU1CommandUserSelf."]
[record IdU1ResponseUserSelf
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation user "The user."]
  [field user IdU1User]
]

[documentation IdU1ResponseUserUpdate "A response to various update commands."]
[record IdU1ResponseUserUpdate
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
  [documentation user "The user."]
  [field user IdU1User]
]

[documentation IdU1ResponseEmailAddBegin "A response to IdU1CommandEmailAddBegin."]
[record IdU1ResponseEmailAddBegin
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
]

[documentation IdU1ResponseEmailRemoveBegin "A response to IdU1CommandEmailRemoveBegin."]
[record IdU1ResponseEmailRemoveBegin
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
]

[documentation IdU1ResponseEmailAddPermit "A response to IdU1CommandEmailAddPermit."]
[record IdU1ResponseEmailAddPermit
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
]

[documentation IdU1ResponseEmailRemovePermit "A response to IdU1CommandEmailRemovePermit."]
[record IdU1ResponseEmailRemovePermit
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
]

[documentation IdU1ResponseEmailAddDeny "A response to IdU1CommandEmailAddDeny."]
[record IdU1ResponseEmailAddDeny
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
]

[documentation IdU1ResponseEmailRemoveDeny "A response to IdU1CommandEmailRemoveDeny."]
[record IdU1ResponseEmailRemoveDeny
  [documentation requestId "The ID of the request that yielded this response."]
  [field requestId cb:UUID]
]

;
; Protocol.
;

[documentation IdU "The User protocol."]
[protocol IdU
  [version 1
    [types-added
      IdU1CommandEmailAddBegin
      IdU1CommandEmailAddDeny
      IdU1CommandEmailAddPermit
      IdU1CommandEmailRemoveBegin
      IdU1CommandEmailRemoveDeny
      IdU1CommandEmailRemovePermit
      IdU1CommandLogin
      IdU1CommandPasswordUpdate
      IdU1CommandRealnameUpdate
      IdU1CommandUserSelf
      IdU1ResponseEmailAddBegin
      IdU1ResponseEmailAddDeny
      IdU1ResponseEmailAddPermit
      IdU1ResponseEmailRemoveBegin
      IdU1ResponseEmailRemoveDeny
      IdU1ResponseEmailRemovePermit
      IdU1ResponseError
      IdU1ResponseLogin
      IdU1ResponseUserSelf
      IdU1ResponseUserUpdate
    ]
  ]
]
The server and clients expose Java APIs that are documented in the included JavaDoc.
This section of the manual attempts to describe the security properties of the idstore server.
All command execution in the idstore server passes through a single code path that captures OpenTelemetry traces and publishes events. Administrators should write alerting rules in their metrics system of choice to watch for instances of the traces, metrics, and events described here; they almost certainly indicate malicious behaviour.
At the time of writing, events are published as part of a containing trace, and therefore the server must be configured to publish traces in order for an external monitoring system to be able to observe the events. The reason for this is that the OpenTelemetry Events API specification is not yet stable and so traces are currently the only way to publish events. It is expected that a stable version of the Events API will be released soon, and then the idstore server will switch to using that API directly (and this will require only log telemetry to be configured as opposed to full trace collection).
If an operation attempts to violate the server's built-in security policy, an exception will be raised with an idstore.errorCode attribute set to error-security-policy-denied.
If an administrator logs in successfully, the server emits an event of type IdEventAdminLoggedIn. Events of this type have the following attributes:

9.2.3.2. IdEventAdminLoggedIn

Attribute Description Defined Value
event.domain The OpenTelemetry event domain. server
event.name The OpenTelemetry event name. security.admin.login.succeeded
idstore.admin The ID of the administrator.
If someone attempting to log into an administrator account provides invalid credentials, the server emits an event of type IdEventAdminLoginAuthenticationFailed. Events of this type have the following attributes:

9.2.4.2. IdEventAdminLoginAuthenticationFailed

Attribute Description Defined Value
event.domain The OpenTelemetry event domain. server
event.name The OpenTelemetry event name. security.admin.login.authentication_failed
idstore.admin The ID of the administrator.
idstore.remote_host The remote host making the attempt.
Login attempts to administrator accounts are rate limited. If a rate limit is exceeded, the server emits an event of type IdEventAdminLoginRateLimitExceeded. Events of this type have the following attributes:

9.2.5.2. IdEventAdminLoginRateLimitExceeded

Attribute Description Value
event.domain The OpenTelemetry event domain. server
event.name The OpenTelemetry event name. security.admin.login.rate_limit_exceeded
idstore.remote_host The remote host making the login attempt.
idstore.username The username associated with the login attempt.
In general, if an operation attempts to violate the server's various configured rate limits, an exception will be raised with an idstore.errorCode attribute set to error-rate-limit-exceeded.
Attempts to verify email addresses (either to add new addresses, or remove existing addresses) are rate limited. If a rate limit is exceeded, the server emits an event of type IdEventUserEmailVerificationRateLimitExceeded. Events of this type have the following attributes:

9.2.6.2. IdEventUserEmailVerificationRateLimitExceeded

Attribute Description Defined Value
event.domain The OpenTelemetry event domain. server
event.name The OpenTelemetry event name. security.user.email.rate_limit_exceeded
idstore.email The email address.
idstore.user The user ID associated with the operation.
In general, if an operation attempts to violate the server's various configured rate limits, an exception will be raised with an idstore.errorCode attribute set to error-rate-limit-exceeded.
If someone attempting to log into an administrator account provides invalid credentials, the server emits an event of type IdEventAdminLoginAuthenticationFailed. Events of this type have the following attributes:

9.2.7.2. IdEventAdminLoginAuthenticationFailed

Attribute Description Defined Value
event.domain The OpenTelemetry event domain. server
event.name The OpenTelemetry event name. security.user.login.authentication_failed
idstore.remote_host The remote host making the attempt.
idstore.user The username associated with the attempt.
If an administrator logs in successfully, the server emits an event of type IdEventUserLoggedIn. Events of this type have the following attributes:

9.2.8.2. IdEventUserLoggedIn

Attribute Description Defined Value
event.domain The OpenTelemetry event domain. server
event.name The OpenTelemetry event name. security.user.login.succeeded
idstore.user The ID of the user.
Login attempts to user accounts are rate limited. If a rate limit is exceeded, the server emits an event of type IdEventUserLoginRateLimitExceeded. Events of this type have the following attributes:

9.2.9.2. IdEventUserLoginRateLimitExceeded

Attribute Description Value
event.domain The OpenTelemetry event domain. server
event.name The OpenTelemetry event name. security.user.login.rate_limit_exceeded
idstore.remote_host The remote host making the login attempt.
idstore.username The username associated with the login attempt.
In general, if an operation attempts to violate the server's various configured rate limits, an exception will be raised with an idstore.errorCode attribute set to error-rate-limit-exceeded.
Password reset attempts to user accounts are rate limited. If a rate limit is exceeded, the server emits an event of type IdEventUserPasswordResetRateLimitExceeded. Events of this type have the following attributes:

9.2.10.2. IdEventUserPasswordResetRateLimitExceeded

Attribute Description Value
event.domain The OpenTelemetry event domain. server
event.name The OpenTelemetry event name. security.user.login.rate_limit_exceeded
idstore.remote_host The remote host making the attempt.
idstore.target The username or the email address associated with the attempt (depending on which was specified).
In general, if an operation attempts to violate the server's various configured rate limits, an exception will be raised with an idstore.errorCode attribute set to error-rate-limit-exceeded.
An attacker with access to the APIs could send specially crafted messages designed to exhaust server resources during parsing/validation of the messages.
All APIs exposed by the idstore server are defined using the Cedarbridge protocol. Cedarbridge-based protocols have the following properties:

9.3.1.2.2. Cedarbridge Properties

  • Parsing and validation complexity is linear in the size of the parsed message. It is not possible to specify a message that will result in an exponentially complex parse. Additionally, the server currently places hard limits on message sizes from clients; 1048576 octets for commands, and 1024 octets for login requests.
  • Cedarbridge protocols are very strongly-typed, operate under a closed-world assumption, and are immune to any kind of reflection-based deserialization vulnerabilities. Deserialization of messages cannot under any circumstances result in the deserialization of arbitrary objects.
  • Cedarbridge protocols have a structure that is known ahead of time by both peers. Neither side gets to decide the structure of messages during communication. This is a common source of vulnerabilities in other protocols, where message types are self-describing on the wire and therefore a hostile client is able to transmit the description of a message that will cause the server to do an unbounded amount of work to parse and/or validate the message.
  • Parsing code generated by the Cedarbridge compiler does not preallocate any structures. It is not possible, therefore, for a specially-crafted message to result in the server performing a huge allocation based on an attacker-provided size value. If a message claims to provide a string that is 4294967295 octets long, the parser will attempt to consume 4294967295 octets one at a time. This requires an attacker to actually provide 4294967295 octets over the network, and the attacker will run into a hard request size limit before this completes.
An attacker managing to compromise the database of the idstore server will have access to the hashed passwords of all users.
The idstore server currently hashes all user passwords using PBKDF2 with a SHA-256 HMAC, with a default configuration of 100000 rounds and a NIST-recommended size of 16 bytes for salt values. This combination of algorithm and parameters is, at the time of writing, expected to be expensive enough to break to deter most attackers. The system never stores plain text passwords in any form, and all plain-text-carrying data types are carefully defined in a manner that prevents the possibility of plain-text passwords accidentally leaking into log files as they move through the system.
The algorithm and parameters used are stored per-password and can therefore be upgraded on a per-password basis as stronger algorithms are standardized and become available in Java.
Administrators enjoy a somewhat higher level of trust in the idstore server; they are typically expected to be actual members of whatever organization happens to be running the server. This documentation, therefore, does not attempt to cover all the ways a privileged administrator could damage a running system. Administrators are individually subject to a fine-grained capability-based security policy, so administrators should be granted the minimum privileges needed to do their jobs.
A malicious administrator could attempt to grant themselves more permissions than they currently have.
The idstore server has a hard-coded security policy that states that an arbitrary administrator Z must first have the ADMIN_WRITE_PERMISSIONS_SELF permission to modify itself. Assuming Z passes this check, the security policy then states that, for any administrators X and Y, the only permissions X can grant to Y is the set of permissions held by X. As, X = Y = Z, Z cannot grant any new permissions it does not have to itself.
A malicious administrator could effectively grant themselves more permissions than they currently have by accessing the hashed password of another administrator. By getting access to an account with sufficient privileges, the administrator could use the compromised account to grant their existing account more permissions.
The only way to retrieve the complete account details of an administrator is using the IdACommandAdminGet command internally. The code that processes the IdACommandAdminGet command redacts the password of any administrator that passes through it; it is not even possible for an administrator to obtain their own hashed password. The system never stores plain-text passwords in any form.
An administrator could attempt to guess the password of the initial administrator. There is no mitigation against this; set a very strong password and do not use that account for regular operations.
An attacker could automate login requests to the user API in an attempt to repeatedly try to guess the password of a given account, or simply to try to probe all accounts for weak passwords.
The idstore server currently applies configurable rate limiting to login operations. The default setting is to require that no more than one login request be made from a given IP address in a given five-second duration. Additionally, a configurable delay is automatically applied to every login operation so that, by default, every login attempt takes at least one second to complete.
The idstore server uses constant-time algorithms for checking password hashes in the hope that this will avoid leaking timing information to clients. The idstore server should NOT be considered timing-invariant in the general case.
An attacker with access to a compromised user account could request the addition of lots of random email addresses to the account. The intent of this attack would be to get the idstore server to send lots of mail to random addresses, either to annoy the owners of those addresses, or to try to get the MTA associated with the server blacklisted as a sender of bulk unsolicited mail.
The idstore server currently applies configurable rate limiting for email verifications, with the suggested default being one verification allowed every ten minutes. In this case, the limit is applied on a per-user basis, so a compromised account trying to produce a flood of verification requests does not affect any other user's ability to verify email addresses.
Additionally, a user may have at most 10 in-progress email verifications at any given time. Any attempt to create another email verification beyond this limit will be denied by the security policy. The user will have to either manually clear the verifications (by denying the requests from an email account under the user's control), or wait for the requests to expire before submitting more.
An attacker with access to a compromised user account could first add an email address under the attacker's control to the account, and then remove the user's other email accounts in order to lock them out of the account and prevent them using their email addresses for password resets.
When an attempt is made to remove an email address from an account, the idstore server sends a token to all the other addresses on the account that allows the owner of the email address to deny the removal. This serves two purposes: The user whose account was compromised is effectively notified of the compromise, and has an (albeit likely short-lived) opportunity to mitigate the damage by refusing to allow the attacker to remove the address (assuming that the user can deny the request before the attacker has the opportunity to permit it).
If the attacker does manage to succeed in removing the email address, the audit log allows the administrator to see the exact series of events that occurred, and manually reset the user's account password and email addresses.
An attacker with knowledge that a given email address is used by a user account in the system could request lots of password resets for that address. The intent of this attack would be to get the idstore server to send lots of mail to the email address, either to annoy the owner of the address, or to try to get the MTA associated with the server blacklisted as a sender of bulk unsolicited mail.
The idstore server currently applies configurable rate limiting for password resets, with the suggested default being one verification allowed every ten minutes. In this case, the limit is applied on a per-IP basis.
This will, unfortunately, have the side effect of counting towards the user account in question's email verification limit. A user may have at most 10 in-progress email verifications at any given time. Any attempt to create another email verification beyond this limit will be denied by the security policy. The user will have to either manually clear the verifications (by denying the requests from an email account under the user's control), or wait for the requests to expire before submitting more.
io7m | single-page | multi-page | epub | Idstore User Manual 2.0.1