@civitai/app-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -0
- package/dist/client/index.d.ts +23 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +19 -0
- package/dist/client/index.js.map +1 -0
- package/dist/cookies/index.d.ts +27 -0
- package/dist/cookies/index.d.ts.map +1 -0
- package/dist/cookies/index.js +85 -0
- package/dist/cookies/index.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth/authorize.d.ts +15 -0
- package/dist/oauth/authorize.d.ts.map +1 -0
- package/dist/oauth/authorize.js +18 -0
- package/dist/oauth/authorize.js.map +1 -0
- package/dist/oauth/index.d.ts +4 -0
- package/dist/oauth/index.d.ts.map +1 -0
- package/dist/oauth/index.js +4 -0
- package/dist/oauth/index.js.map +1 -0
- package/dist/oauth/pkce.d.ts +10 -0
- package/dist/oauth/pkce.d.ts.map +1 -0
- package/dist/oauth/pkce.js +15 -0
- package/dist/oauth/pkce.js.map +1 -0
- package/dist/oauth/token.d.ts +37 -0
- package/dist/oauth/token.d.ts.map +1 -0
- package/dist/oauth/token.js +89 -0
- package/dist/oauth/token.js.map +1 -0
- package/dist/orchestrator/index.d.ts +119 -0
- package/dist/orchestrator/index.d.ts.map +1 -0
- package/dist/orchestrator/index.js +155 -0
- package/dist/orchestrator/index.js.map +1 -0
- package/dist/scopes/index.d.ts +59 -0
- package/dist/scopes/index.d.ts.map +1 -0
- package/dist/scopes/index.js +155 -0
- package/dist/scopes/index.js.map +1 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/workflows/index.d.ts +52 -0
- package/dist/workflows/index.d.ts.map +1 -0
- package/dist/workflows/index.js +77 -0
- package/dist/workflows/index.js.map +1 -0
- package/package.json +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# `@civitai/app-sdk`
|
|
2
|
+
|
|
3
|
+
Shared OAuth + orchestrator glue for building third-party [Civitai](https://civitai.com) apps. Used internally by every starter in [`civitai/civitai-app-starters`](https://github.com/civitai/civitai-app-starters), and publishable for direct use in your own app.
|
|
4
|
+
|
|
5
|
+
This package is **runtime-agnostic** — Node 20+ APIs only. There is no Next.js, SvelteKit, or Express coupling. Each starter writes a ~30-line framework adapter that calls into these primitives.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @civitai/app-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## What it does
|
|
14
|
+
|
|
15
|
+
| Surface | Why it exists |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `oauth/*` — `generatePkce`, `buildAuthorizeUrl`, `exchangeCode`, `refreshToken`, `revokeToken`, `fetchMe` | The Civitai OAuth flow (Authorization Code + PKCE S256), as a set of stateless functions you call from your server-side handlers. |
|
|
18
|
+
| `scopes/*` — `TokenScope`, `TokenScopePresets`, `bitmaskFromScopes`, `scopesFromBitmask`, `hasScope`, `getScopeLabel` | Civitai scopes are stored as bitmasks. These helpers let you compose scope sets from named flags rather than magic numbers. |
|
|
19
|
+
| `cookies/*` — `sealCookie`, `unsealCookie`, `buildSetCookieHeader`, `readCookie` | AES-256-CTR encrypted cookie crypto. Use to seal a session blob (refresh token, expiry, scope) into an `httpOnly` cookie with zero external session store. |
|
|
20
|
+
| `orchestrator/*` — `createOrchestratorClient`, `estimateWorkflow`, `submitWorkflow`, `getWorkflow`, `pollWorkflow`, `buildTextToImageBody`, `isTerminal`, `extractImageUrls`, `OrchestratorError`, `WorkflowSnapshot`, `GenerateInput`, `DEFAULT_MODEL_AIR` | Orchestrator workflow glue — types, body builder, raw HTTP, and long-poll helper. Client + server safe (fetch-only). `estimateWorkflow` calls `?whatif=true` to preview Buzz cost without spending. `pollWorkflow` long-polls to terminal status. |
|
|
21
|
+
|
|
22
|
+
## Minimal usage example
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import {
|
|
26
|
+
buildAuthorizeUrl, generatePkce, generateState,
|
|
27
|
+
exchangeCode, fetchMe,
|
|
28
|
+
TokenScope, bitmaskFromScopes,
|
|
29
|
+
sealCookie, unsealCookie, buildSetCookieHeader,
|
|
30
|
+
createOrchestratorClient, buildTextToImageBody,
|
|
31
|
+
estimateWorkflow, submitWorkflow, pollWorkflow,
|
|
32
|
+
} from '@civitai/app-sdk';
|
|
33
|
+
|
|
34
|
+
// 1. Kick off OAuth login from your server-side handler
|
|
35
|
+
const { verifier, challenge } = generatePkce();
|
|
36
|
+
const state = generateState();
|
|
37
|
+
const scope = bitmaskFromScopes(['AIServicesWrite', 'BuzzRead', 'UserRead']);
|
|
38
|
+
|
|
39
|
+
// Persist { verifier, state, scope } against the user's session — e.g. in an
|
|
40
|
+
// encrypted cookie sealed with `sealCookie`. Then redirect to:
|
|
41
|
+
const authorizeUrl = buildAuthorizeUrl({
|
|
42
|
+
clientId: process.env.CIVITAI_CLIENT_ID!,
|
|
43
|
+
redirectUri: 'https://your-app.com/api/auth/callback/civitai',
|
|
44
|
+
scope,
|
|
45
|
+
state,
|
|
46
|
+
codeChallenge: challenge,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// 2. In your callback handler, exchange the code for tokens
|
|
50
|
+
const tokens = await exchangeCode({
|
|
51
|
+
clientId: process.env.CIVITAI_CLIENT_ID!,
|
|
52
|
+
clientSecret: process.env.CIVITAI_CLIENT_SECRET, // omit for public clients
|
|
53
|
+
redirectUri: 'https://your-app.com/api/auth/callback/civitai',
|
|
54
|
+
code: codeFromQuery,
|
|
55
|
+
codeVerifier: verifierFromSealedCookie,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// 3. Store tokens in an encrypted httpOnly cookie
|
|
59
|
+
const sealed = sealCookie(JSON.stringify(tokens), process.env.SESSION_SECRET!);
|
|
60
|
+
const setCookie = buildSetCookieHeader('civ_session', sealed, { maxAge: 3600 });
|
|
61
|
+
|
|
62
|
+
// 4. Use the token to make orchestrator calls
|
|
63
|
+
const client = createOrchestratorClient({ accessToken: tokens.access_token });
|
|
64
|
+
const me = await fetchMe({ accessToken: tokens.access_token });
|
|
65
|
+
console.log(`Hi ${me.username}, you have ${me.balance} Buzz`);
|
|
66
|
+
|
|
67
|
+
// 5. Estimate cost, then submit
|
|
68
|
+
const body = buildTextToImageBody({ prompt: 'a fox' }, { tags: ['my-app'] });
|
|
69
|
+
const estimate = await estimateWorkflow(client, body);
|
|
70
|
+
console.log(`This will cost ${estimate.cost?.total ?? 0} Buzz`);
|
|
71
|
+
// ...show to user, get confirmation...
|
|
72
|
+
const submitted = await submitWorkflow(client, body);
|
|
73
|
+
const finished = await pollWorkflow(client, submitted.id, { timeoutMs: 30_000 });
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The starters in `civitai/civitai-app-starters` wire this into framework-specific route handlers (Next.js App Router, SvelteKit `+server.ts`, Hono inside a Vite-built PWA). Read those for end-to-end reference implementations.
|
|
77
|
+
|
|
78
|
+
## Public vs. confidential clients
|
|
79
|
+
|
|
80
|
+
Civitai's OAuth server supports both:
|
|
81
|
+
|
|
82
|
+
- **Confidential** — your server holds `CIVITAI_CLIENT_SECRET`. Use this for any starter that has a server side at all (Next.js, SvelteKit, or PWAs with a BFF). This is what every current starter uses.
|
|
83
|
+
- **Public** — no `client_secret`, PKCE alone is the security boundary. Civitai's token endpoint supports CORS for browser-direct exchange. Useful for fully static PWAs. We don't currently ship a static-PWA starter; planned for a later milestone.
|
|
84
|
+
|
|
85
|
+
Pass `clientSecret` to `exchangeCode` / `refreshToken` / `revokeToken` for confidential, omit for public.
|
|
86
|
+
|
|
87
|
+
## Buzz mechanics (important)
|
|
88
|
+
|
|
89
|
+
When your app calls the orchestrator with a user's OAuth access token, the orchestrator debits **the user's Buzz**, not yours. This is the right tenant model for a third-party app, but it means:
|
|
90
|
+
|
|
91
|
+
1. **Request `AIServicesWrite` scope at consent time.** Without it the user can't grant their Buzz for generation.
|
|
92
|
+
2. **Show cost before spending.** Call `estimateCost` first — it returns `cost.total` in Buzz without debiting. Display it. Let the user confirm.
|
|
93
|
+
3. **Show balance.** Request `BuzzRead` scope, then call Civitai's balance endpoint. Don't surprise users.
|
|
94
|
+
4. **Handle the cap-denial case.** Per-app spending caps (set by the user at consent and at Account → Connected Apps) can cause a successful `whatif` to be rejected at real submit time with a generic `BAD_REQUEST`. Treat that as "insufficient or denied" in your UI.
|
|
95
|
+
|
|
96
|
+
## TODOs / future work
|
|
97
|
+
|
|
98
|
+
- `scopes/index.ts` is hand-copied from `civitai/civitai`'s `src/shared/constants/token-scope.constants.ts`. Plan: replace with build-time codegen from `/.well-known/openid-configuration`.
|
|
99
|
+
- Add a `tokenStore/` abstraction so starters can plug in alternative storage (Redis, KV) without rewriting auth handlers.
|
|
100
|
+
- Static-PWA helpers for the public-client flow (browser-side token exchange via CORS, in-memory token storage, no refresh persistence).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createCivitaiClient } from '@civitai/client';
|
|
2
|
+
/** The orchestrator client returned by `createCivitaiClient`. */
|
|
3
|
+
export type CivitaiClient = ReturnType<typeof createCivitaiClient>;
|
|
4
|
+
export interface CreateAppClientOptions {
|
|
5
|
+
/** OAuth access token or personal API key. Used as Bearer credential. */
|
|
6
|
+
token: string;
|
|
7
|
+
/** Orchestrator base URL. Defaults to Civitai's prod orchestrator. */
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
/** SDK env mode. Defaults to 'prod'. */
|
|
10
|
+
env?: 'dev' | 'prod';
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Create a typed Civitai orchestrator client authenticated with the user's
|
|
14
|
+
* OAuth access token (or a personal API key — same Bearer scheme).
|
|
15
|
+
*
|
|
16
|
+
* Wraps `@civitai/client`'s factory with sensible defaults. The returned
|
|
17
|
+
* client is the same shape `@civitai/client` produces — pass it to the
|
|
18
|
+
* SDK's standalone functions (`submitWorkflow({ client, body })`, etc.)
|
|
19
|
+
* or to the wrappers in `@civitai/app-sdk/workflows`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createAppClient(opts: CreateAppClientOptions): CivitaiClient;
|
|
22
|
+
export type AppClient = CivitaiClient;
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEtD,iEAAiE;AACjE,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEnE,MAAM,WAAW,sBAAsB;IACrC,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CACtB;AAID;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,aAAa,CAM3E;AAED,MAAM,MAAM,SAAS,GAAG,aAAa,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createCivitaiClient } from '@civitai/client';
|
|
2
|
+
const DEFAULT_ORCHESTRATOR_BASE_URL = 'https://orchestration-new.civitai.com';
|
|
3
|
+
/**
|
|
4
|
+
* Create a typed Civitai orchestrator client authenticated with the user's
|
|
5
|
+
* OAuth access token (or a personal API key — same Bearer scheme).
|
|
6
|
+
*
|
|
7
|
+
* Wraps `@civitai/client`'s factory with sensible defaults. The returned
|
|
8
|
+
* client is the same shape `@civitai/client` produces — pass it to the
|
|
9
|
+
* SDK's standalone functions (`submitWorkflow({ client, body })`, etc.)
|
|
10
|
+
* or to the wrappers in `@civitai/app-sdk/workflows`.
|
|
11
|
+
*/
|
|
12
|
+
export function createAppClient(opts) {
|
|
13
|
+
return createCivitaiClient({
|
|
14
|
+
baseUrl: opts.baseUrl ?? DEFAULT_ORCHESTRATOR_BASE_URL,
|
|
15
|
+
env: opts.env ?? 'prod',
|
|
16
|
+
auth: opts.token,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AActD,MAAM,6BAA6B,GAAG,uCAAuC,CAAC;AAE9E;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,IAA4B;IAC1D,OAAO,mBAAmB,CAAC;QACzB,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,6BAA6B;QACtD,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,MAAM;QACvB,IAAI,EAAE,IAAI,CAAC,KAAK;KACjB,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encrypt an arbitrary string for storage in a cookie value.
|
|
3
|
+
* Returns `${iv}:${tag}:${ciphertext}`, hex-encoded.
|
|
4
|
+
*
|
|
5
|
+
* The `secret` should be a high-entropy value held server-side only,
|
|
6
|
+
* supplied via env (e.g. SESSION_SECRET). Treat it like a session key.
|
|
7
|
+
*/
|
|
8
|
+
export declare function sealCookie(plaintext: string, secret: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Reverse of `sealCookie`. Returns `null` on any error (tampered, wrong
|
|
11
|
+
* secret, malformed) — never throws. Callers should treat `null` as
|
|
12
|
+
* "session missing or invalid" and re-issue.
|
|
13
|
+
*/
|
|
14
|
+
export declare function unsealCookie(sealed: string, secret: string): string | null;
|
|
15
|
+
export interface CookieAttributes {
|
|
16
|
+
maxAge?: number;
|
|
17
|
+
path?: string;
|
|
18
|
+
domain?: string;
|
|
19
|
+
secure?: boolean;
|
|
20
|
+
httpOnly?: boolean;
|
|
21
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
22
|
+
}
|
|
23
|
+
/** Build a `Set-Cookie` header value with sensible defaults for session cookies. */
|
|
24
|
+
export declare function buildSetCookieHeader(name: string, value: string, attrs?: CookieAttributes): string;
|
|
25
|
+
/** Parse a `Cookie` request header and return a value by name, or null. */
|
|
26
|
+
export declare function readCookie(cookieHeader: string | null | undefined, name: string): string | null;
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cookies/index.ts"],"names":[],"mappings":"AAqBA;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAMpE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiB1E;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;CACtC;AAED,oFAAoF;AACpF,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,gBAAqB,GAC3B,MAAM,CASR;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQ/F"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Sealed cookie format: `${iv}:${tag}:${ciphertext}` (all hex).
|
|
4
|
+
*
|
|
5
|
+
* AES-256-GCM (authenticated encryption — AEAD). Wrong secret or tampered
|
|
6
|
+
* ciphertext causes decryption to throw, which `unsealCookie` catches and
|
|
7
|
+
* returns `null`. This is a security upgrade over plain AES-CTR (which is
|
|
8
|
+
* malleable and silently returns garbage on wrong keys).
|
|
9
|
+
*/
|
|
10
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
11
|
+
const IV_BYTES = 12;
|
|
12
|
+
const TAG_BYTES = 16;
|
|
13
|
+
const KEY_BYTES = 32;
|
|
14
|
+
const SALT = 'civitai-app-sdk-cookie-salt';
|
|
15
|
+
function getKey(secret) {
|
|
16
|
+
return scryptSync(secret, SALT, KEY_BYTES);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Encrypt an arbitrary string for storage in a cookie value.
|
|
20
|
+
* Returns `${iv}:${tag}:${ciphertext}`, hex-encoded.
|
|
21
|
+
*
|
|
22
|
+
* The `secret` should be a high-entropy value held server-side only,
|
|
23
|
+
* supplied via env (e.g. SESSION_SECRET). Treat it like a session key.
|
|
24
|
+
*/
|
|
25
|
+
export function sealCookie(plaintext, secret) {
|
|
26
|
+
const iv = randomBytes(IV_BYTES);
|
|
27
|
+
const cipher = createCipheriv(ALGORITHM, getKey(secret), iv);
|
|
28
|
+
const enc = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
29
|
+
const tag = cipher.getAuthTag();
|
|
30
|
+
return `${iv.toString('hex')}:${tag.toString('hex')}:${enc.toString('hex')}`;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Reverse of `sealCookie`. Returns `null` on any error (tampered, wrong
|
|
34
|
+
* secret, malformed) — never throws. Callers should treat `null` as
|
|
35
|
+
* "session missing or invalid" and re-issue.
|
|
36
|
+
*/
|
|
37
|
+
export function unsealCookie(sealed, secret) {
|
|
38
|
+
try {
|
|
39
|
+
const parts = sealed.split(':');
|
|
40
|
+
if (parts.length !== 3)
|
|
41
|
+
return null;
|
|
42
|
+
const [ivHex, tagHex, ctHex] = parts;
|
|
43
|
+
if (!ivHex || !tagHex || !ctHex)
|
|
44
|
+
return null;
|
|
45
|
+
const iv = Buffer.from(ivHex, 'hex');
|
|
46
|
+
const tag = Buffer.from(tagHex, 'hex');
|
|
47
|
+
const ciphertext = Buffer.from(ctHex, 'hex');
|
|
48
|
+
if (iv.length !== IV_BYTES || tag.length !== TAG_BYTES)
|
|
49
|
+
return null;
|
|
50
|
+
const decipher = createDecipheriv(ALGORITHM, getKey(secret), iv);
|
|
51
|
+
decipher.setAuthTag(tag);
|
|
52
|
+
const dec = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
53
|
+
return dec.toString('utf8');
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Build a `Set-Cookie` header value with sensible defaults for session cookies. */
|
|
60
|
+
export function buildSetCookieHeader(name, value, attrs = {}) {
|
|
61
|
+
const parts = [`${name}=${value}`];
|
|
62
|
+
parts.push(`Max-Age=${attrs.maxAge ?? 3600}`);
|
|
63
|
+
parts.push(`Path=${attrs.path ?? '/'}`);
|
|
64
|
+
if (attrs.domain)
|
|
65
|
+
parts.push(`Domain=${attrs.domain}`);
|
|
66
|
+
if (attrs.secure !== false)
|
|
67
|
+
parts.push('Secure');
|
|
68
|
+
if (attrs.httpOnly !== false)
|
|
69
|
+
parts.push('HttpOnly');
|
|
70
|
+
parts.push(`SameSite=${attrs.sameSite ?? 'lax'}`);
|
|
71
|
+
return parts.join('; ');
|
|
72
|
+
}
|
|
73
|
+
/** Parse a `Cookie` request header and return a value by name, or null. */
|
|
74
|
+
export function readCookie(cookieHeader, name) {
|
|
75
|
+
if (!cookieHeader)
|
|
76
|
+
return null;
|
|
77
|
+
const target = `${name}=`;
|
|
78
|
+
for (const raw of cookieHeader.split(';')) {
|
|
79
|
+
const trimmed = raw.trim();
|
|
80
|
+
if (trimmed.startsWith(target))
|
|
81
|
+
return trimmed.slice(target.length);
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cookies/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,mBAAmB,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface BuildAuthorizeUrlOptions {
|
|
2
|
+
baseUrl?: string;
|
|
3
|
+
clientId: string;
|
|
4
|
+
redirectUri: string;
|
|
5
|
+
scope: number;
|
|
6
|
+
state: string;
|
|
7
|
+
codeChallenge: string;
|
|
8
|
+
codeChallengeMethod?: 'S256';
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Build the Civitai OAuth authorize URL. The returned URL is what you redirect
|
|
12
|
+
* the browser to. Token exchange happens on the redirect_uri callback.
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildAuthorizeUrl(opts: BuildAuthorizeUrlOptions): string;
|
|
15
|
+
//# sourceMappingURL=authorize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorize.d.ts","sourceRoot":"","sources":["../../src/oauth/authorize.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,wBAAwB;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,wBAAwB,GAAG,MAAM,CAWxE"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const DEFAULT_CIVITAI_BASE_URL = 'https://civitai.com';
|
|
2
|
+
/**
|
|
3
|
+
* Build the Civitai OAuth authorize URL. The returned URL is what you redirect
|
|
4
|
+
* the browser to. Token exchange happens on the redirect_uri callback.
|
|
5
|
+
*/
|
|
6
|
+
export function buildAuthorizeUrl(opts) {
|
|
7
|
+
const base = opts.baseUrl ?? DEFAULT_CIVITAI_BASE_URL;
|
|
8
|
+
const url = new URL('/api/auth/oauth/authorize', base);
|
|
9
|
+
url.searchParams.set('client_id', opts.clientId);
|
|
10
|
+
url.searchParams.set('redirect_uri', opts.redirectUri);
|
|
11
|
+
url.searchParams.set('response_type', 'code');
|
|
12
|
+
url.searchParams.set('state', opts.state);
|
|
13
|
+
url.searchParams.set('scope', String(opts.scope));
|
|
14
|
+
url.searchParams.set('code_challenge', opts.codeChallenge);
|
|
15
|
+
url.searchParams.set('code_challenge_method', opts.codeChallengeMethod ?? 'S256');
|
|
16
|
+
return url.toString();
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=authorize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorize.js","sourceRoot":"","sources":["../../src/oauth/authorize.ts"],"names":[],"mappings":"AAUA,MAAM,wBAAwB,GAAG,qBAAqB,CAAC;AAEvD;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAA8B;IAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,IAAI,wBAAwB,CAAC;IACtD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,2BAA2B,EAAE,IAAI,CAAC,CAAC;IACvD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACvD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;IAC9C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAClD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;IAC3D,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,uBAAuB,EAAE,IAAI,CAAC,mBAAmB,IAAI,MAAM,CAAC,CAAC;IAClF,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/oauth/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/oauth/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface Pkce {
|
|
2
|
+
verifier: string;
|
|
3
|
+
challenge: string;
|
|
4
|
+
method: 'S256';
|
|
5
|
+
}
|
|
6
|
+
/** Generate a PKCE code verifier and S256 challenge. */
|
|
7
|
+
export declare function generatePkce(): Pkce;
|
|
8
|
+
/** Generate a random URL-safe state value for OAuth state-parameter use. */
|
|
9
|
+
export declare function generateState(byteLength?: number): string;
|
|
10
|
+
//# sourceMappingURL=pkce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../src/oauth/pkce.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,IAAI;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wDAAwD;AACxD,wBAAgB,YAAY,IAAI,IAAI,CAInC;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,UAAU,SAAK,GAAG,MAAM,CAErD"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
function base64UrlEncode(buf) {
|
|
3
|
+
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
4
|
+
}
|
|
5
|
+
/** Generate a PKCE code verifier and S256 challenge. */
|
|
6
|
+
export function generatePkce() {
|
|
7
|
+
const verifier = base64UrlEncode(randomBytes(32));
|
|
8
|
+
const challenge = base64UrlEncode(createHash('sha256').update(verifier).digest());
|
|
9
|
+
return { verifier, challenge, method: 'S256' };
|
|
10
|
+
}
|
|
11
|
+
/** Generate a random URL-safe state value for OAuth state-parameter use. */
|
|
12
|
+
export function generateState(byteLength = 16) {
|
|
13
|
+
return base64UrlEncode(randomBytes(byteLength));
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=pkce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../src/oauth/pkce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEtD,SAAS,eAAe,CAAC,GAAW;IAClC,OAAO,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC3F,CAAC;AAQD,wDAAwD;AACxD,MAAM,UAAU,YAAY;IAC1B,MAAM,QAAQ,GAAG,eAAe,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,eAAe,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IAClF,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AACjD,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,aAAa,CAAC,UAAU,GAAG,EAAE;IAC3C,OAAO,eAAe,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { OAuthTokens } from '../types.js';
|
|
2
|
+
interface CommonOpts {
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
clientId: string;
|
|
5
|
+
/** Only required for confidential clients. Public clients omit this. */
|
|
6
|
+
clientSecret?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ExchangeCodeOpts extends CommonOpts {
|
|
9
|
+
code: string;
|
|
10
|
+
redirectUri: string;
|
|
11
|
+
codeVerifier: string;
|
|
12
|
+
}
|
|
13
|
+
export interface RefreshTokenOpts extends CommonOpts {
|
|
14
|
+
refreshToken: string;
|
|
15
|
+
}
|
|
16
|
+
export interface RevokeTokenOpts extends CommonOpts {
|
|
17
|
+
token: string;
|
|
18
|
+
}
|
|
19
|
+
export declare class OAuthError extends Error {
|
|
20
|
+
readonly status: number;
|
|
21
|
+
readonly body: unknown;
|
|
22
|
+
readonly name = "OAuthError";
|
|
23
|
+
constructor(message: string, status: number, body: unknown);
|
|
24
|
+
}
|
|
25
|
+
/** Exchange an authorization code for tokens (PKCE flow). */
|
|
26
|
+
export declare function exchangeCode(opts: ExchangeCodeOpts): Promise<OAuthTokens>;
|
|
27
|
+
/** Refresh an access token using a refresh_token grant. */
|
|
28
|
+
export declare function refreshToken(opts: RefreshTokenOpts): Promise<OAuthTokens>;
|
|
29
|
+
/** Revoke an access or refresh token. */
|
|
30
|
+
export declare function revokeToken(opts: RevokeTokenOpts): Promise<void>;
|
|
31
|
+
/** Fetch the OAuth-authenticated user's profile via `/api/v1/me`. */
|
|
32
|
+
export declare function fetchMe(opts: {
|
|
33
|
+
baseUrl?: string;
|
|
34
|
+
accessToken: string;
|
|
35
|
+
}): Promise<unknown>;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/oauth/token.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,WAAW,EAAE,MAAM,aAAa,CAAC;AAInE,UAAU,UAAU;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,gBAAiB,SAAQ,UAAU;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAiB,SAAQ,UAAU;IAClD,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAgB,SAAQ,UAAU;IACjD,KAAK,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,UAAW,SAAQ,KAAK;IAIjC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO;IAJxB,SAAkB,IAAI,gBAAgB;gBAEpC,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO;CAIzB;AA0CD,6DAA6D;AAC7D,wBAAsB,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC,CAW/E;AAED,2DAA2D;AAC3D,wBAAsB,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC,CAS/E;AAED,yCAAyC;AACzC,wBAAsB,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAOtE;AAED,qEAAqE;AACrE,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAS/F"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const DEFAULT_CIVITAI_BASE_URL = 'https://civitai.com';
|
|
2
|
+
export class OAuthError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
body;
|
|
5
|
+
name = 'OAuthError';
|
|
6
|
+
constructor(message, status, body) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.body = body;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
async function postForm(url, body) {
|
|
13
|
+
const params = new URLSearchParams();
|
|
14
|
+
for (const [k, v] of Object.entries(body)) {
|
|
15
|
+
if (v !== undefined)
|
|
16
|
+
params.set(k, v);
|
|
17
|
+
}
|
|
18
|
+
const res = await fetch(url, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
21
|
+
body: params,
|
|
22
|
+
});
|
|
23
|
+
const text = await res.text();
|
|
24
|
+
let parsed;
|
|
25
|
+
try {
|
|
26
|
+
parsed = text.length > 0 ? JSON.parse(text) : null;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
parsed = text;
|
|
30
|
+
}
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
throw new OAuthError(`OAuth request failed: ${res.status} ${res.statusText}`, res.status, parsed);
|
|
33
|
+
}
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
function shapeTokens(json, fallbackScope) {
|
|
37
|
+
return {
|
|
38
|
+
access_token: json.access_token,
|
|
39
|
+
refresh_token: json.refresh_token,
|
|
40
|
+
expires_at: Date.now() + (json.expires_in ?? 3600) * 1000,
|
|
41
|
+
scope: typeof json.scope === 'number' ? json.scope : Number(json.scope ?? fallbackScope ?? 0),
|
|
42
|
+
token_type: json.token_type ?? 'Bearer',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Exchange an authorization code for tokens (PKCE flow). */
|
|
46
|
+
export async function exchangeCode(opts) {
|
|
47
|
+
const base = opts.baseUrl ?? DEFAULT_CIVITAI_BASE_URL;
|
|
48
|
+
const json = (await postForm(`${base}/api/auth/oauth/token`, {
|
|
49
|
+
grant_type: 'authorization_code',
|
|
50
|
+
code: opts.code,
|
|
51
|
+
redirect_uri: opts.redirectUri,
|
|
52
|
+
client_id: opts.clientId,
|
|
53
|
+
client_secret: opts.clientSecret,
|
|
54
|
+
code_verifier: opts.codeVerifier,
|
|
55
|
+
}));
|
|
56
|
+
return shapeTokens(json);
|
|
57
|
+
}
|
|
58
|
+
/** Refresh an access token using a refresh_token grant. */
|
|
59
|
+
export async function refreshToken(opts) {
|
|
60
|
+
const base = opts.baseUrl ?? DEFAULT_CIVITAI_BASE_URL;
|
|
61
|
+
const json = (await postForm(`${base}/api/auth/oauth/token`, {
|
|
62
|
+
grant_type: 'refresh_token',
|
|
63
|
+
refresh_token: opts.refreshToken,
|
|
64
|
+
client_id: opts.clientId,
|
|
65
|
+
client_secret: opts.clientSecret,
|
|
66
|
+
}));
|
|
67
|
+
return shapeTokens(json);
|
|
68
|
+
}
|
|
69
|
+
/** Revoke an access or refresh token. */
|
|
70
|
+
export async function revokeToken(opts) {
|
|
71
|
+
const base = opts.baseUrl ?? DEFAULT_CIVITAI_BASE_URL;
|
|
72
|
+
await postForm(`${base}/api/auth/oauth/revoke`, {
|
|
73
|
+
token: opts.token,
|
|
74
|
+
client_id: opts.clientId,
|
|
75
|
+
client_secret: opts.clientSecret,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/** Fetch the OAuth-authenticated user's profile via `/api/v1/me`. */
|
|
79
|
+
export async function fetchMe(opts) {
|
|
80
|
+
const base = opts.baseUrl ?? DEFAULT_CIVITAI_BASE_URL;
|
|
81
|
+
const res = await fetch(`${base}/api/v1/me`, {
|
|
82
|
+
headers: { Authorization: `Bearer ${opts.accessToken}` },
|
|
83
|
+
});
|
|
84
|
+
if (!res.ok) {
|
|
85
|
+
throw new OAuthError(`/api/v1/me failed: ${res.status}`, res.status, await res.text());
|
|
86
|
+
}
|
|
87
|
+
return res.json();
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/oauth/token.ts"],"names":[],"mappings":"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"}
|