Simulators
Software TOTP factors
A software TOTP factor is the authenticator-app second factor, and it is the only factor type the simulator models. Enroll one, compute the code the secret gives at the simulator’s clock, then activate and verify it:
curl -s -X POST "$RYSTIC_URL/_rystic/scenario" -d '{"name":"design_studio"}' -o /dev/null
u="$RYSTIC_URL/api/v1/users"
code() { python3 -c 'import base64,datetime,hashlib,hmac,struct,sys; s,c=sys.argv[1:3]; t=datetime.datetime.fromisoformat(c.replace("Z","+00:00")).timestamp(); h=hmac.new(base64.b32decode(s+"="*(-len(s)%8)),struct.pack(">Q",int(t//30)),hashlib.sha1).digest(); o=h[-1]&15; print("%06d"%((struct.unpack(">I",h[o:o+4])[0]&0x7FFFFFFF)%1000000))' "$secret" "$(curl -s "$RYSTIC_URL/_rystic/version" | jq -r .clock)"; }
curl -s "$u?activate=true" -H 'Authorization: SSWS placeholder' -H 'Content-Type: application/json' -d '{"profile":{"firstName":"Mfa","lastName":"User","email":"mfa@example.com","login":"mfa@example.com"},"credentials":{"password":{"value":"Abcdef1!"}}}' | jq -c '{id, status}'
# -> {"id":"00u00000000000000001","status":"ACTIVE"}
factor=$(curl -s "$u/00u00000000000000001/factors" -H 'Authorization: SSWS placeholder' -H 'Content-Type: application/json' -d '{"factorType":"token:software:totp","provider":"OKTA"}')
jq -c '{id, factorType, status}' <<< "$factor"
# -> {"id":"caf00000000000000001","factorType":"token:software:totp","status":"PENDING_ACTIVATION"}
secret=$(jq -r ._embedded.activation.sharedSecret <<< "$factor")
curl -s "$u/00u00000000000000001/factors/caf00000000000000001/lifecycle/activate" -H 'Authorization: SSWS placeholder' -H 'Content-Type: application/json' -d "{\"passCode\":\"$(code)\"}" | jq -c '{status}'
# -> {"status":"ACTIVE"}
curl -s "$u/00u00000000000000001/factors/caf00000000000000001/verify" -H 'Authorization: SSWS placeholder' -H 'Content-Type: application/json' -d "{\"passCode\":\"$(code)\"}" | jq -c .
# -> {"factorResult":"SUCCESS"}
curl -s "$u/00u00000000000000001/factors/caf00000000000000001/verify" -H 'Authorization: SSWS placeholder' -H 'Content-Type: application/json' -d '{"passCode":"000000"}' | jq -c '{errorCode, errorSummary}'
# -> {"errorCode":"E0000068","errorSummary":"Invalid Passcode/Answer"}
Codes follow the simulator’s clock
Enrolling returns a base32 sharedSecret with a 30-second timeStep and six digits. Compute each code at the simulator’s clock, as the example does, not at wall time. GET /_rystic/version reports that clock as clock: the time you pinned, or the scenario’s start plus one millisecond for each write. A wrong code returns 403 E0000068.
Other factor types
GET /api/v1/users/{userId}/factors/catalog lists Okta’s factor types, but the simulator models only token:software:totp with provider OKTA.
Differs from Okta: any other factor type enrolls as PENDING_ACTIVATION and stays there. Activating or verifying it returns 501 with an __rystic_unmodeled_guard__ body.