skill / auth-intent
!

Not installable via adompkg

This skill has no published release. adompkg install kyle/auth-intent will not work until a maintainer publishes a tarball with install.sh and uninstall.sh.

See the publishing docs for the package.json schema and tarball layout required to ship this skill.

AuthenticationIntent Integration

AuthenticationIntent lets 3rd-party apps authenticate users via Adom without handling credentials. The flow:

  1. Your app calls POST /auth/intents → receives a token and auth_url
  2. Direct the user to auth_url (browser, QR code, deep link, etc.)
  3. The user logs in on Adom; your app receives a session_token via SSE or polling
  4. Use session_token for authenticated API requests

Intents expire after 15 minutes. Sessions default to 90 days but accept a custom max_age.

If $ARGUMENTS is set, focus examples and explanations on: $ARGUMENTS


API reference

Base prefix: /auth

POST /auth/intents — Create an intent

Request body (JSON, all fields optional):

{ "max_age": 2592000 }

max_age is the desired session lifetime in seconds. Defaults to 7,776,000 (90 days).

Response 201 Created:

{
    "token": "abc123xyz",
    "created_at": "2024-01-01T00:00:00Z",
    "expires_at": "2024-01-01T00:15:00Z",
    "requested_max_age": 2592000,
    "auth_url": "https://hydrogen.adom.inc/auth/intent?token=abc123xyz",
    "updates_url": "https://carbon.adom.inc/auth/intents/abc123xyz/status",
    "completed": false
}

GET /auth/intents/{token} — Fetch intent state

Returns the same shape as the POST response. completed becomes true once the user has logged in.

PATCH /auth/intents/{token} — Complete intent (internal)

Called by the Adom frontend when a logged-in user visits auth_url. Your application does not call this endpoint — the user's browser does.

GET /auth/intents/{token}/status — Poll or stream for completion

Short polling — no special headers required:

{ "state": "pending" }
// or, once the user has logged in (token is consumed on first retrieval):
{ "state": "authenticated", "session_token": "tok_..." }

SSE streaming — send Accept: text/event-stream:

Holds the connection open until the user logs in or the intent expires.

Event Data
authenticated {"session_token": "tok_..."}
timeout (no data — intent expired)

The server sends SSE keep-alive pings, so normal connection timeouts won't fire prematurely.


Integration examples

TypeScript — SSE (recommended for long-lived CLI / desktop apps)

async function authenticateWithAdom(
    apiBase: string,
    maxAge?: number,
): Promise<string> {
    const res = await fetch(`${apiBase}/auth/intents`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(maxAge ? { max_age: maxAge } : {}),
    });
    if (!res.ok) throw new Error(`Failed to create intent: ${res.status}`);
    const intent = await res.json();

    console.log("Authenticate here:", intent.auth_url);

    return new Promise((resolve, reject) => {
        const es = new EventSource(intent.updates_url);

        es.addEventListener("authenticated", (e) => {
            es.close();
            resolve(JSON.parse(e.data).session_token);
        });

        es.addEventListener("timeout", () => {
            es.close();
            reject(new Error("Authentication timed out — intent expired"));
        });

        es.onerror = () => {
            es.close();
            reject(new Error("SSE connection failed"));
        };
    });
}

TypeScript — Short polling (simpler, works anywhere)

async function createIntent(apiBase: string, maxAge?: number) {
    const res = await fetch(`${apiBase}/auth/intents`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(maxAge ? { max_age: maxAge } : {}),
    });
    if (!res.ok) throw new Error(`${res.status}`);
    return res.json() as Promise<{
        token: string;
        auth_url: string;
        updates_url: string;
        expires_at: string;
    }>;
}

async function pollForToken(
    statusUrl: string,
    intervalMs = 2000,
): Promise<string> {
    while (true) {
        const res = await fetch(statusUrl);
        if (!res.ok) throw new Error(`Poll failed: ${res.status}`);
        const { state, session_token } = await res.json();
        if (state === "authenticated") return session_token;
        if (state !== "pending") throw new Error(`Unexpected state: ${state}`);
        await new Promise((r) => setTimeout(r, intervalMs));
    }
}

// Usage:
const intent = await createIntent("https://carbon.adom.inc");
console.log("Open:", intent.auth_url);
const token = await pollForToken(intent.updates_url);

Error reference

HTTP Scenario
404 Token not found
410 Intent expired (>15 min old)
409 Intent already linked to a session (PATCH only)

Key behaviours to be aware of

  • session_token is consumed on first retrieval. Once returned by the status endpoint the consumed flag is set. Subsequent polls still return the token as long as the underlying session exists, but the intent can no longer be re-consumed by a different caller.
  • Intent TTL ≠ session TTL. The intent always expires in 15 minutes. max_age only controls how long the resulting session lives.
  • Sessions are Application kind, separate from browser sessions. They carry different trust characteristics server-side.
  • SSE clients outside the browser must set Accept: text/event-stream explicitly. Without it the server falls back to a one-shot JSON response.
  • The auth_url embeds the token as a query param. Treat it as a short-lived, single-use URL — don't cache or log it.