GoodMemGoodMem
How-To GuidesUsers and Access

Onboard a User

Create a human user, assign the standard USER role, and issue a one-time enrollment credential.

Onboard a User

This guide is for administrators adding a person to a GoodMem instance. The result of onboarding is an enrollment credential, which you deliver to the person. They exchange it for their own API key by following Redeem an Enrollment. Onboarding never issues the person's API key; only they can do that.

Creating a user has no side effects. A freshly created user has no credential, no role, and no grants — it is an identity and nothing more. Onboarding is the three administrative steps that turn that identity into a usable account:

  1. Create the user.
  2. Assign the instance USER role, which supplies the ordinary self-service capabilities.
  3. Create a one-time enrollment credential bound to that user.

The goodmem user onboard command runs all three. Each step is a separate RPC and a separate audit record; the command coordinates them but does not pretend they are one transaction. Over REST you make the three calls yourself, as shown in The Manual Path.

Before You Start

  • An administrative credential: the goodmem CLI authenticated, or an API key for REST.
  • Authority to create users and manage enrollments. The instance ADMIN role covers both. If you authenticate with a scoped API key, its ceiling must cover CREATE_USER, MANAGE_ACCESS, and MANAGE_USER_ENROLLMENT.
  • Export the values the REST examples use:
export GOODMEM_REST_URL="https://localhost:8080"   # REST base URL; on GoodMem Cloud, your instance's https:// hostname
export GOODMEM_API_KEY="gm_your_key"

The CLI speaks gRPC, which GoodMem Cloud instances do not expose. On Cloud, use the cURL or HTTPie tab — every step here is a plain REST call over HTTPS — or the console, whose Invite user wizard runs the same three steps. If your own server uses a self-signed certificate, add -k to curl and --verify=no to HTTPie.

Enrollment administration is restricted to authenticated humans. A service identity cannot create enrollment credentials, and the authority cannot be granted to all authenticated principals. This keeps workloads and broad grants away from account bootstrap.

The Short Path

goodmem user onboard \
  --email [email protected] \
  --username sarah \
  --display-name "Sarah"

Only --email is required. The command prints progress for each step to stderr, then the result:

User ID:             70e025f6-76ca-4cbe-b8fc-7dab8e84590a
Role Assignment ID:  0198b9f4-c42f-7ae3-8c56-44d827ea03bd
Enrollment ID:       3f1c9d22-8a41-4f0e-9c37-5be2a17d6c91
Enrollment Token:    <one-time enrollment credential>

The enrollment token is shown once. The server stores only a verifier, so nothing can retrieve it later. If it is lost before the person redeems it, rotate the enrollment and deliver the new token.

Deliver the token to the person over a channel you trust — a password-manager share, or a direct message on an internal system. The token is single-use, bound to that user, and expires 24 hours after creation.

For scripting, --quiet prints only the token, and --format json returns the full result including per-step status.

onboard is a client-side convenience; there is no single REST endpoint behind it. Over REST, make the three calls in The Manual Path in that order.

Skipping steps

  • --skip-role creates the user and enrollment without assigning USER. Use this when the person should get a different set of capabilities — for example, only a space role assigned by a space administrator. They will be able to authenticate after enrolling, but can do very little until someone grants them authority.
  • --skip-enrollment creates the identity and role only. Use this when you are provisioning accounts ahead of time and will create enrollments later. With this flag, --quiet prints the user UUID instead of a token.

When a step fails

The command generates every UUID client-side before the first RPC, and reports each step as SUCCEEDED, FAILED, UNKNOWN, or NOT_RUN. Completed steps are durable: if role assignment fails, the user still exists, and rerunning onboard would try to create a second user. Resume with the individual commands from the manual path below, using the IDs from the step report.

A step reported UNKNOWN means the CLI lost the response and cannot say whether the server committed. Inspect before retrying — goodmem user get <user-id> (GET /v1/users/{userId}) and goodmem user enrollment list <user-id> (GET /v1/users/{userId}/enrollments) show what exists on the server.

The Manual Path

The same three steps as separate calls, for when you want each one explicit — and the only path over REST. Capture the user's UUID from the first response; the other two calls need it.

Create the identity:

goodmem user create --email [email protected] --username sarah --display-name "Sarah"
export USER_ID="<the UUID from the output>"
export USER_ID="$(curl -sS --json @- "$GOODMEM_REST_URL/v1/users" \
  --header "x-api-key: $GOODMEM_API_KEY" <<'JSON' | jq -r '.userId'
{ "email": "[email protected]", "username": "sarah", "displayName": "Sarah" }
JSON
)"
export USER_ID="$(http POST "$GOODMEM_REST_URL/v1/users" x-api-key:"$GOODMEM_API_KEY" \
  email="[email protected]" username="sarah" displayName="Sarah" | jq -r '.userId')"

Assign the standard instance role. An instance role names the INSTANCE singleton as its resource, with no resourceId:

goodmem access-policy role-assignment assign \
  --principal $USER_ID --role USER --resource-kind INSTANCE
curl -sS --json @- "$GOODMEM_REST_URL/v1/access-policy/role-assignments" \
  --header "x-api-key: $GOODMEM_API_KEY" <<JSON
{ "principalId": "$USER_ID", "role": "USER", "assignedResource": { "kind": "INSTANCE" } }
JSON
http POST "$GOODMEM_REST_URL/v1/access-policy/role-assignments" x-api-key:"$GOODMEM_API_KEY" \
  principalId="$USER_ID" role="USER" assignedResource:='{"kind": "INSTANCE"}'

Create the enrollment credential. The REST response carries the token exactly once, in enrollmentToken, alongside the enrollment's metadata, and is served with Cache-Control: no-store:

goodmem user enrollment create $USER_ID
curl -sS --json '{}' "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments" \
  --header "x-api-key: $GOODMEM_API_KEY"
http POST "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments" x-api-key:"$GOODMEM_API_KEY"

user create accepts --id (userId over REST) for a stable client-provided UUID, useful when mirroring an external directory. The Built-in Roles reference lists what USER grants; substitute a different role, direct grants, or a space role via Share a Space if the defaults do not fit.

Managing Enrollments

A user has at most one open enrollment. The lifecycle operations — history, metadata for one enrollment (never the token), permanent revocation, and atomic rotation:

goodmem user enrollment list $USER_ID                          # history: pending, consumed, revoked, expired
goodmem user enrollment get $USER_ID $ENROLLMENT_ID            # metadata only, never the token
goodmem user enrollment revoke $USER_ID $ENROLLMENT_ID         # permanent
goodmem user enrollment create $USER_ID --rotate-existing      # revoke and replace atomically
curl -sS "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments" \
  --header "x-api-key: $GOODMEM_API_KEY"
curl -sS "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments/$ENROLLMENT_ID" \
  --header "x-api-key: $GOODMEM_API_KEY"
curl -sS -X DELETE "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments/$ENROLLMENT_ID" \
  --header "x-api-key: $GOODMEM_API_KEY"
curl -sS --json '{"rotateExisting": true}' "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments" \
  --header "x-api-key: $GOODMEM_API_KEY"
http GET "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments" x-api-key:"$GOODMEM_API_KEY"
http GET "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments/$ENROLLMENT_ID" x-api-key:"$GOODMEM_API_KEY"
http DELETE "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments/$ENROLLMENT_ID" x-api-key:"$GOODMEM_API_KEY"
http POST "$GOODMEM_REST_URL/v1/users/$USER_ID/enrollments" x-api-key:"$GOODMEM_API_KEY" \
  rotateExisting:=true

Enrollments expire on their own after 24 hours. Creating a new enrollment after expiration needs no flag; replacing one that is still open requires --rotate-existing ("rotateExisting": true), which revokes the old credential and issues the new one in a single transaction. Revoke without replacement when an invitation was sent to the wrong person or the account should stay dormant.

Once the person has enrolled, the enrollment is consumed and cannot be reused. Eligibility for enrollment ends permanently at that point — deleting or revoking the person's keys does not restore it. Credential recovery is ordinary API-key rotation, performed by the person with a key they still hold; human keys are self-issued, so an administrator can revoke a compromised key but cannot mint the replacement. A person whose only working key is lost has no recovery path, which is why Redeem an Enrollment advises minting a backup key immediately after enrolling.

Enrollment administration can be delegated without granting ADMIN. An EXACT grant scopes it to one user:

goodmem access-policy grant create \
  --principal $MANAGER_ID \
  --operation MANAGE_USER_ENROLLMENT \
  --selector EXACT --resource-kind USER --resource-id $USER_ID
curl -sS --json @- "$GOODMEM_REST_URL/v1/access-policy/grants" \
  --header "x-api-key: $GOODMEM_API_KEY" <<JSON
{
  "audience": { "principalId": "$MANAGER_ID" },
  "rule": { "operation": "MANAGE_USER_ENROLLMENT", "selector": "EXACT",
            "assignedResource": { "kind": "USER", "resourceId": "$USER_ID" } }
}
JSON
http POST "$GOODMEM_REST_URL/v1/access-policy/grants" x-api-key:"$GOODMEM_API_KEY" <<JSON
{
  "audience": { "principalId": "$MANAGER_ID" },
  "rule": { "operation": "MANAGE_USER_ENROLLMENT", "selector": "EXACT",
            "assignedResource": { "kind": "USER", "resourceId": "$USER_ID" } }
}
JSON

The manager must be an authenticated human, and can then create, rotate, and revoke that one user's enrollments. Nobody can manage their own.

Further Reading