BytesMap
Manage access

Identity and access

Understand accounts, roles, grants, API keys and deployment modes.

Open Markdown

BytesMap uses OpenShip's contracts → platform → host/SDK structure for accounts, workspaces, grants, and API keys. The dashboard, HTTP API, and native SDK call the same authorization and access services. The packaged desktop and its Mindwire assistant use this boundary too; local tooling does not create a second authority model.

Ownership

LayerOwns
packages/contractsRoles, permissions, operation inputs/outputs, HTTP paths, and MCP opt-in metadata
packages/platformVerified execution context, authorization, shared operation lifecycle and Better Auth factory
packages/platform/src/engine/modulesSources, servers, queries/views, flows, reports, teams, keys, delivery and audit services, following OpenShip's folders
packages/dbMetadata schema, additive upgrades, and transaction-aware repositories for Postgres/PGlite
packages/sdkTyped HTTP client and native adapter over the same product/access operations
apps/cli, packages/bytesmapCatalogue-driven commands and SDK/API/worker distribution entrypoints
apps/apiRequest authentication, Origin/peer checks, session transport, routing, and MCP transport
packages/uiShared access editor, controls, tokens, and themes
apps/dashboardSettings and sign-in forms; it never decides the caller's authority
apps/desktopElectron shell, private local services, protected host keys, navigation and process lifetime

Query/flow/chart protocols and pure execution remain in their shared packages. Product services run through the platform's operation boundary across HTTP, MCP, CLI and native SDK. The catalogue covers sources, servers, queries, charts, dashboards, flows, reports, reporting, notifications, permissions, tokens and audit. See architecture and SDK/CLI usage.

Workspace owners/admins manage notification channels and scheduler/SMTP settings with an account session. API keys and MCP cannot configure outbound destinations. Report authors choose safe channel summaries without receiving credentials. See scheduled reports for delivery and verification behavior.

A request's identity

Explicit API credentials are validated first. A real signed-in session then takes precedence over the anonymous desktop account, including in auth=none mode. Only a request without a session can use the guarded local fallback. Session lookup failures never select a different account. Workspace selection stays bound to that account's current membership.

  1. The host verifies a Better Auth session, an API key, or an explicitly enabled local device connection.
  2. It resolves an organization from a key binding, an explicit requested scope, the signed workspace cookie, or the user's membership list.
  3. The platform loads the live user, membership, role, and credential. It produces a frozen ExecutionContext; role and identity fields never come from an operation body.
  4. The operation validates its input, checks access, performs the work, and validates its public response. Mutating access operations recheck authority inside the transaction that changes it.

Contexts include the user, membership id, workspace, session kind, credential id, scope, and request source. Membership changes, expiry, revocation, and removal apply to subsequent operations, including already-resolved native contexts. Background work also carries the membership id so leaving and rejoining a team cannot revive an older job's authority.

HTTP is fixed to the selected workspace. A native host can deliberately request resource-based scope, which resolves a resource's owning workspace and checks membership there again. A role from one workspace is never reused in another. Keys always remain fixed to their bound workspace.

Roles and grants

RoleWorkspace access
OwnerProduct operations and team administration; can change owners
AdminProduct operations and team administration; cannot change owners
MemberSources, queries, charts, dashboards, and execution; no team/settings/billing administration
ViewerRead/list; no query execution
Custom access (restricted)Granted projects/items and their allowed actions

Grants use { resourceType, resourceId, permissions }. * deliberately grants the whole resource collection. read, write, admin, list, and run are semantic actions, independent of the HTTP method. write includes reading; admin includes management but does not imply run for a custom grant.

Schema, profile, model, and SQL permissions are rooted in their source. Queries, charts, dashboards, and servers have their own grants. Executing a saved query also checks the sources it touches. Lists filter inaccessible rows; dashboard responses filter nested charts, queries, and sources too. Reusing a saved server's SSH connection while adding a source requires access to manage that server.

The same grant validator is used for keys, custom members, and invitations. It checks every action, actual resource ownership, and wildcard authority. The UI loads only resources and actions the current caller can grant.

Projects have stored membership and inherit their grants to their queries, charts, actions, panels, dashboards and folders. Individual item shares remain additive and do not reveal neighboring items. Project Write permits authoring its items; Run and source/data permissions remain separate. Moves and project removal change inherited access immediately without copying grants to items. Keys and agent tools apply the same intersection with current member access. The sidebar project choice controls navigation, never authority or ownership. Canonical project links select that context; old unscoped resource links resolve stored ownership independently of the remembered collection filter.

Workspaces and invitations

New accounts receive a workspace with one owner. Settings → Workspace → Add people invites directly to that workspace. Its first valid invitation marks it as shared in the same transaction; its ID, sources, queries, charts, dashboards, memberships, and API keys stay in place. A rejected invitation request leaves the workspace unchanged. The SDK's createTeamOrg operation remains available when a caller explicitly wants a separate workspace; it is not a prerequisite for adding people. Workspace switching persists the verified account session's active organization. The shared resolver validates that membership on every operation, keeps a valid selection, and otherwise prefers the first team before the personal workspace. Sessionless local access uses a signed, account-bound navigation preference. Selecting a workspace never proves membership.

Workspace owners/admins can invite members by a copyable link or configured SMTP, change roles, edit custom grants, and remove members. The last owner cannot be removed or demoted. Removing or leaving a workspace revokes its keys and grants, clears stale session selections, and removes notification subscriptions in the same transaction. A self-leave cannot remove the last owner.

Invitations expire after seven days. Acceptance checks the recipient's signed-in email, current status/expiry, and the sender's current authority, then creates membership and grants atomically. Acceptance can be retried safely. Cancellation removes pending grants. If email delivery fails after creation, the response still returns the saved invitation link with deliveryStatus: 'failed'.

The Better Auth organization plugin remains installed for schema/session support. Its alternate organization mutation endpoints are blocked. Team lifecycle goes through the shared platform operations in every host.

API keys and MCP

Create a key in Settings → API keys. Keys are bound to the current workspace, displayed once, and stored only as a SHA-256 hash plus a short display prefix. The list includes expiry, revocation, last use, and request count.

Key authority is the intersection of the owner's current membership/grants and the key's selected grants. An empty selection cannot silently become full access; full access requires an explicit choice. Read-only keys permit read/list only, including for POST endpoints whose action is read. They cannot run queries.

Keys cannot create more credentials or manage accounts/team authority, even when full access was selected. Those operations require an account/device session.

MCP is a stateless Streamable HTTP endpoint at /api/mcp. Give a key the MCP connection permission plus its desired resource permissions. Tools are generated from the registered routes/contracts, filtered against current capabilities, and dispatched through the real app. A process-local WeakMap<Request, Context> passes verified identity to the internal request; headers cannot fabricate that context, and one tool request increments key usage only once.

MCP includes query/chart/dashboard authoring and updates. A key can author without execution permission. Separate run/preview tools retain the same source checks as the UI. Account, key, membership, and invitation mutations are not exposed as agent tools. See MCP setup.

On hosted endpoints, OAuth MCP clients use the same live grant-holder model as keys. Consent chooses one workspace and uses the shared resource editor; connected clients can be edited or disconnected from Settings. Tokens are audience-bound to /api/mcp, require S256 PKCE, and cannot create credentials or change account/team authority. Reconnect creates a new binding so a revoked token never becomes valid again.

The packaged desktop has a different external transport: its private host accepts MCP POST requests from direct loopback clients only with an expiring, explicitly scoped API key and the MCP connection permission. Browser cookies, OAuth tokens, Origin/proxy headers and broad keys do not enter that path. Ordinary REST and account routes still require the owning desktop's private capability. Settings reads the host's declared endpoint and authentication capabilities instead of promising OAuth on every host. The CLI's desktop stdio transport discovers the current per-launch identity, verifies a fresh proof, and sends the scoped key on the same non-reconnecting TCP connection. It never uses the private host capability.

Desktop assistant conversations have private, non-exportable grant holders too. The caller must have an account/device session on a locally verified desktop host. Each conversation belongs to one user and workspace. Permission changes stop its tools and require fresh model context; approving a tool never grants additional access. These services are unavailable to API keys, OAuth clients, cloud, and self-hosted hosts. See native assistant.

Deployment and authentication modes

Edition and authentication mode are independent:

EditionHost configurationCurrent account behavior
DesktopDEPLOY_MODE=desktopPackaged Electron host starts private loopback services and explicitly provisions a pinned local identity
Self-hosted/VPSDEPLOY_MODE=bare or dockerInstance-local accounts; first-admin setup followed by invitation-only signup
CloudDEPLOY_MODE=cloud or CLOUD_MODE=trueAccounts and workspaces hosted by the cloud API; verified email required

BYTESMAP_AUTH_MODE explicitly pins none, local, or cloud. When unset, the resolver reads instance_settings; a missing value or read failure defaults to local, never anonymous access. The cloud auth mode has a shared desktop login handoff implementation with stored one-use attempts, PKCE and local session claims. Cloud edition hosts its own Better Auth accounts. The packaged desktop starts in guarded none mode; connecting that local workspace to a cloud account is a separate, explicit operation.

none additionally requires an actual kernel-reported loopback peer, no public URL, no require-auth flag, and either desktop mode or BYTESMAP_ALLOW_ZERO_AUTH=true. Host/forwarded headers do not satisfy it. Every dashboard/proxy in a local deployment must also bind to loopback; a public proxy must use authenticated mode. An invalid supplied key never falls back to local identity. Deleting the pinned local user cannot select another account silently.

The packaged desktop launcher starts the existing Next dashboard and Bun API on separate IPv4 loopback ports. It isolates its PGlite store from development data, loads persistent keys through the operating system credential store, and supplies a fresh private capability for each launch. Its owning IPC channel is required; losing that owner stops the API. Only this explicit desktop startup, or the native CLI's local setup, provisions a pinned device identity. Ordinary network API startup does not provision an anonymous administrator. See the desktop host guide.

First account and upgrades

For a fresh self-hosted/VPS instance:

  1. Choose persistent PostgreSQL or embedded PGlite metadata. API startup applies the shared baseline/additive migrations automatically.
  2. Set a generated BETTER_AUTH_SECRET, BYTESMAP_AUTH_MODE=local, and BYTESMAP_REQUIRE_AUTH=true. Set BYTESMAP_PUBLIC_URL for public hosting.
  3. Generate a host-only setup token, for example openssl rand -base64 32, and set BYTESMAP_SETUP_TOKEN (at least 24 characters).
  4. Open /setup, enter that token, and create the first administrator.

Local desktop/CLI setup may use the same independently checked loopback proof. The setup token ceases to authorize signup once an account exists. Subsequent self-hosted accounts require a valid invitation for the submitted email.

First-account checks use a metadata row lock across concurrent requests. User, credential, session, and personal workspace creation commit together; a failed provision cannot leave a half-created administrator or return a session cookie. Signup and other account routes retain Origin checks and their existing rate limits.

Startup applies the shared metadata migrations before constructing the auth adapter. Upgrades include passkeys, two-factor fields, invitation grants, key usage, report settings and audit tables. They do not rewrite roles or reconnect customer databases. Legacy keys without a workspace binding are refused and must be recreated.

Account security

The shared auth factory pins Better Auth, its core, and passkeys to 1.6.26. OpenShip's passkey/TOTP guards were adapted to that version's challenge handling. Security endpoints join one transaction, serialize credential changes on the user row, reject stale cached sessions, and preserve failed-code attempt budgets.

Settings supports profile/password changes, session revocation, passkeys, authenticator setup, and recovery codes. A working second factor cannot be replaced by a concurrent enrollment. Enabled two-factor authentication also applies to non-password sign-in hooks; no primary sign-in can issue its final session before the challenge. Invitation destinations survive verification and two-factor navigation.

Email verification/recovery and invitation mail use the host's configured email transport. The platform owns reusable delivery logic; hosts inject configuration or alternative adapters. It never prints credentials.

Activity and request logs

Settings → Activity and client.audit expose workspace-scoped operation history: actor, action, resource, outcome, duration and server-issued trace ID. Owner/admin accounts can change recording and retention. Custom members and keys may receive audit read access; it is not enabled for ordinary members/viewers by default.

The shared observer records mutations and execution, including native calls. Account creation and session lifecycle have shared hooks. Routine successful reads are omitted. Audit payloads exclude SQL, request bodies, result data and credentials. Logging is best effort and does not roll back a committed operation. It is not an exhaustive record of every failed sign-in or network denial.

HTTP diagnostics use structured logs and x-request-id, without query strings, headers or raw error payloads. MCP's internal dispatch preserves that trace and does not duplicate the key-use count or operation event. Report delivery retains its separate schedule/run history. The shared scheduler prunes expired activity.

Desktop integrations and release limits

Mindwire is integrated through its TypeScript SDK and native daemon. The desktop assistant discovers installed Claude Code/Codex harnesses, manages private profiles, and gives each conversation a permission-scoped BytesMap tool bridge. Model arguments cannot choose an identity or filesystem path; native execution tools are restricted by the host's harness policy. The current native assistant requires macOS or Linux on ARM64/x64; the Windows desktop shell does not imply Windows assistant support. Cloud and self-hosted hosts do not launch it. See native assistant for supported capabilities and fixture coverage.

Cloud workspace linking has a packaged external-browser return path. The desktop accepts only a registered, unexpired state from its configured cloud authorization origin and consumes it once. The platform independently checks the stored PKCE verifier, authorization code, local owner and workspace. The packaged smoke verifies browser-to-desktop navigation using a local redirect fixture; it intentionally does not mint a real cloud session. Separate platform tests cover the exchange and identity rules. Real provider login is still a release gate.

macOS arm64 packaging and isolated native tests have local evidence. Signed releases, signed Keychain/DPAPI continuity across versions, and the remaining platform installation checks need their own results. Automatic updates are not shipped. Desktop discovery and the scoped CLI stdio bridge have local Node/Bun integration evidence; Windows account-directory ACL and native installation checks still need their platform results. The launch-readiness record tracks these gates; fixture login and an unsigned package are not evidence of a production provider or signed release.

Verification

packages/platform/test uses isolated PGlite databases for access, invitations, key revocation, first-account rollback, real WebAuthn/TOTP, and concurrent security changes. apps/api/src/lib/identity-http.test.ts starts a separate API fixture and checks HTTP/native SDK/CLI parity, scoped MCP authoring, shared audit, setup races, origin/peer guards, workspace isolation, and revoked authority. It never connects to customer data sources. The same fixture runs against disposable PostgreSQL metadata in the real database integration job; native hosts also have isolated PGlite tests.

packages/platform/test/mcp-oauth.test.ts verifies consent, S256 PKCE, audience binding, refresh rotation and live revocation. cloud-auth-proxy.test.ts and cloud-link.test.ts cover local/cloud identity and one-use exchange using isolated fixtures. Desktop host integration checks private transport and process cleanup; its packaged smoke checks navigation, rendering and the external-browser return. Native assistant tests use the real Mindwire daemon with fixture CLIs and local scripted model responses, without paid inference or real provider sign-in.

apps/dashboard/e2e/auth.e2e.ts and settings.e2e.ts cover user flows and Light/Dark/Dim layouts with keyboard and narrow-viewport checks. Their HTTP fixtures exercise the real browser SDK; backend behavior is checked separately by the API and platform integration tests.

On this page