// --------------------------------------------------------------------------- // The owner a provider item id embeds — the single parser for the id grammar // the SDK constructs (accessItemId in oauth-service.ts, the connection value // ids in executor.ts, the oauth-client secret ids): // // connection:::: // oauth:::[:refresh] // oauth-client:::secret // // Credential providers file plugin-storage rows by THIS owner, not the acting // caller's binding — an org connection whose OAuth consent completes in one // member's browser session must produce rows every member can resolve // (issues #950, #1453). // --------------------------------------------------------------------------- import { Owner } from "./ids"; import type { OwnerBinding } from "./plugin"; /** Item-id prefixes whose SECOND colon-segment is the owning partition. */ export const OWNER_SCOPED_ITEM_ID_PREFIXES: ReadonlySet = new Set([ "connection", "oauth", "oauth-client", ]); /** The owner a logical item id embeds, or null for ids that carry none * (legacy random `secret_*` ids). Reads the second colon-segment of the * owner-scoped prefixes. */ export const embeddedItemOwner = (id: string): Owner | null => { const [prefix, owner] = id.split(":"); if (OWNER_SCOPED_ITEM_ID_PREFIXES.has(prefix ?? "") && (owner === "org" || owner === "user")) { return Owner.make(owner); } return null; }; /** Map the executor's (tenant, subject?) binding onto the storage `Owner` * literal: a bound subject writes the user's own partition, otherwise the * org-shared one. Fallback only — prefer `ownerForItemId`. */ const ownerOf = (binding: OwnerBinding): Owner => binding.subject == null ? Owner.make("org") : Owner.make("user"); /** The partition a credential belongs to: the CREDENTIAL's owner (embedded in * the item id), not the acting caller's binding. Ids without an embedded * owner fall back to the caller binding. */ export const ownerForItemId = (id: string, binding: OwnerBinding): Owner => embeddedItemOwner(id) ?? ownerOf(binding);