Ganymede user provisioning¶
Status: Current | Last reviewed: 2026-09-16
Use the standalone Salesforce project in sfdc/ganymede to maintain Add Users to Ganymede, an LWC on the Ganymede application's Home tab. It searches Salesforce and Authentik by email, creates accounts in missing systems, and adds missing Ganymede group membership to existing Authentik users.
Deployment and access¶
Deploy from sfdc/ganymede, not the repository root. The root project builds the namespaced Agent Studio managed package; this component is unmanaged and specific to the Ganymede org authenticated as erigolden@trueserve.com.
cd sfdc/ganymede
npm install
npm run lint
npm run test:unit
sf project deploy start --target-org erigolden@trueserve.com \
--source-dir force-app --test-level RunSpecifiedTests \
--tests GanymedeUserProvisioningTest --wait 10
sf org assign permset --target-org erigolden@trueserve.com \
--name Ganymede_Administrators
python scripts/store-authentik-token.py \
--target-org erigolden@trueserve.com \
--token-file ../../secret/AuthentikToken.txt
The Ganymede Administrators permission set grants:
- Custom permission
Ganymede_Manage_Users(Manage Ganymede Users). - Access to
GanymedeUserProvisioning. - Access to the
Ganymede_Authentik-ServiceAccountexternal credential principal. - Availability of the Add Users to Ganymede Lightning component tab.
Assign this permission set only to administrators who should create Ganymede identities. It does not grant general Salesforce user administration. Both Apex entry points check the custom permission before any query or callout. The controller deliberately uses system context to detect identities regardless of user-record sharing and to perform its fixed User and PermissionSetAssignment operations.
Ganymede_Home copies the existing Home_Page_Default content and adds the component at the top. The Ganymede application's desktop Home override points to this new page. Other applications retain their current Home assignments. A component visibility rule also checks the custom permission, and the LWC renders no controls without it.
You can add the component to Home pages and App pages in Lightning App Builder, or add its Lightning component tab to another application's navigation.
Authentik credential¶
Ganymede_Authentik is a secured Named Credential pointing to https://sso.trueserve.deloitte.com. Its External Credential uses the Custom protocol and named principal ServiceAccount. The Authorization header formula prefixes the encrypted ApiToken parameter with Bearer.
The setup script reads the local token file and writes it through Salesforce's credential API. It prints only the encrypted-storage confirmation. Token values are never stored in metadata, Apex, custom settings, browser code, command arguments, or logs. To rotate an already populated parameter, run the same command with --rotate.
You can also rotate it in Setup → Named Credentials → External Credentials → Ganymede Authentik → Principals → ServiceAccount, using authentication parameter name ApiToken. The Authentik service account must be able to search users/groups, create users with the two required groups, and add existing users to Salesforce Users. Connection failures fail closed; they never mean “user missing.”
Provisioning rules¶
| Field | New Salesforce user | New Authentik user |
|---|---|---|
| Username | Email local part + @trueserve.com |
Email local part |
| Trimmed, lower-case work email | Same email | |
| Name | Entered first and last name | Last, First |
| SSO identity | FederationIdentifier = email |
Existing Authentik Salesforce SSO mapping |
| Access | Ganymede User profile + Ganymede_Access permission set |
Ganymede Only Users + Salesforce Users groups |
| Defaults | Active; Asia/Kolkata; en_US; UTF-8 |
Active; type=internal; path=users; empty attributes |
These defaults match the inspected adbhalerao account. Group IDs resolve by exact group name at runtime, and provisioning rejects missing, ambiguous, or superuser groups. No password, other role, or privilege is copied from the template. The workflow does not send password or welcome emails.
When Salesforce already has the email, the component displays names from that User record and the server ignores submitted names. Existing Salesforce accounts, profiles, and permission sets are preserved. An inactive account or a Salesforce federation ID that differs from the email blocks changes to its Authentik access.
For existing Authentik users, lookup checks membership in Salesforce Users, the group required by the Ganymede application's Require App Group policy. Missing membership is displayed explicitly, even when both accounts already exist. Grant Ganymede access adds that membership; Create in Salesforce and grant Ganymede access also creates the missing Salesforce account.
The controller uses POST /api/v3/core/groups/{group_uuid}/add_user/ with the exact matched user ID. This atomic addition preserves all existing memberships. Never add Ganymede Only Users to an existing user automatically: the live Block Deloitte External policy uses it to deny every other application, including Gallery and AI Manager. Only newly created Authentik accounts receive that restrictive group.
Account existence alone is not sufficient for sign-in. The initial creation-only behavior skipped the group grant for an existing Authentik account, producing a valid Salesforce user who was denied by Authentik. The membership check and repair action cover that case. Other application policies still apply; group membership does not override restrictions such as Deloitte External or Richmond Only Users.
Salesforce lookup is exact by email and rejects duplicates. Authentik uses case-insensitive search, walks pagination, and compares returned emails exactly ignoring case. A malformed response or excessive pagination blocks creation. Username collisions across different email domains also block creation. Salesforce's global username uniqueness is enforced again on insert.
Transactions and retries¶
Every creation request repeats the lookup. The server checks Salesforce configuration and license availability before creating Authentik users. All callouts precede Salesforce DML, avoiding “uncommitted work pending.”
Authentik creation and group additions cannot be part of a Salesforce database transaction. If Authentik succeeds and Salesforce fails, the controller rolls back the new Salesforce User and permission-set assignment together, retains the Authentik user and membership, and reports the partial result. Search again after resolving the Salesforce error; the next request creates only the missing Salesforce account.
A callout timeout may occur after Authentik commits. The UI therefore removes the stale creation action after a failure and requires a fresh search before retrying. Raw external response bodies are not exposed to users.
Validation¶
GanymedeUserProvisioningTest uses HTTP mocks and rolled-back Salesforce test users to cover permission denial, exact matching, pagination, duplicate and inactive identities, username collisions, SSO mismatch, input validation, group safety, creation in either or both systems, and partial failure after a concurrent Salesforce username claim. Regression tests cover existing-user group grants during Salesforce creation, repair when both accounts exist, repeated repair as a no-op, incomplete membership responses, and failure before Salesforce creation when a group grant is denied.
LWC Jest tests cover permission visibility, name fields, both-system status, create and repair labels, invalidation after editing email, validation, duplicate submission, success, and error recovery. Org smoke checks use read-only lookups through the real Named Credential. Authentik's GET /api/v3/core/applications/ganymede/check_access/?for_user={pk} verifies the effective policy result; it does not impersonate the user or complete a Salesforce login.