Skip to content

Allow a user-registered service to match multiple hosts (repeatable --base-api-url, or a pattern) #127

Description

@thad-imbue

Problem

A user-registered service can only ever match one URL prefix, so an API
that spans more than one host needs one registration per host — and one
latchkey auth set per registration, even when a single token covers all of
them.

Fastmail is a clean example. The JMAP API lives on api.fastmail.com, blobs
({downloadUrl}) on www.fastmailusercontent.com, and accounts homed on a
regional shard use different hosts again — phl.api.fastmail.com and
phl-www.fastmailusercontent.com. One API token covers all four, but setup is:

latchkey services register fastmail             --base-api-url="https://api.fastmail.com/"
latchkey services register fastmail-content     --base-api-url="https://www.fastmailusercontent.com/"
latchkey services register fastmail-phl         --base-api-url="https://phl.api.fastmail.com/"
latchkey services register fastmail-content-phl --base-api-url="https://phl-www.fastmailusercontent.com/"

latchkey auth set fastmail             -H "Authorization: Bearer $(pbpaste)"
latchkey auth set fastmail-content     -H "Authorization: Bearer $(pbpaste)"
latchkey auth set fastmail-phl         -H "Authorization: Bearer $(pbpaste)"
latchkey auth set fastmail-content-phl -H "Authorization: Bearer $(pbpaste)"

Four services and four auth set calls for one credential — and all four have
to be re-run every time the token rotates.

The engine already supports what's needed

ServiceRegistry.matchesUrl handles both strings and patterns
(src/serviceRegistry.ts):

matchesUrl(service, url) {
  for (const baseApiUrl of service.baseApiUrls) {
    if (typeof baseApiUrl === 'string') { if (url.startsWith(baseApiUrl)) return true; }
    else { if (baseApiUrl.test(url)) return true; }
  }
  return false;
}

Service.baseApiUrls is typed readonly (string | RegExp)[], and built-ins
already use both the array and the regex form — aws is
/^https:\/\/[^/]*\.amazonaws\.com\//, mailchimp is
/^https:\/\/[^/]+\.api\.mailchimp\.com\//.

Three places close that off for user-registered services:

  1. services register takes --base-api-url <url>, not repeatable.
  2. RegisteredService's constructor hardcodes this.baseApiUrls = [baseApiUrl]
    — always exactly one, always a string.
  3. RegisteredServiceEntrySchema in src/configDataStore.ts is
    baseApiUrl: z.string(), so hand-editing ~/.latchkey/config.json can't
    express it either.

Why there's no workaround

Prefix matching is against the whole URL, so the part that varies (the
subdomain) sits at the front of the string. The only common prefix of
https://api.fastmail.com/ and https://phl.api.fastmail.com/ is https://
— a catch-all that would match every URL and collide with every other
registered service.

Requests

Either would solve it; the first is the smaller change:

  1. Make --base-api-url repeatable, and widen the stored entry to
    baseApiUrls: z.array(z.string()) (accepting the old singular baseApiUrl
    key on read, for compatibility).
  2. Accept a pattern — e.g. --base-api-url-pattern <regex>, compiled to a
    RegExp and pushed into the same baseApiUrls array the matcher already
    walks.

Observed on latchkey 3.5.0.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions