commit 70cf86889eecbe9c4649bb072cd971c3a560e889 Author: ivanberry Date: Thu Mar 12 07:33:43 2026 +0800 feat: initial auth-runtime module diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..c517061 --- /dev/null +++ b/README.md @@ -0,0 +1,103 @@ +# @clawd/auth-runtime + +Shared TypeScript auth runtime for OpenClaw skills. + +## Features + +- Token caching with configurable TTL +- Automatic token refresh +- Session-expired auto retry (401/403) +- Environment-based configuration +- Type-safe API + +## Installation + +```bash +bun add file:../../_shared/auth-runtime +``` + +## Usage + +```typescript +import { + createEnvConfig, + getAccessToken, + requestApiWithAutoRefresh, +} from '@clawd/auth-runtime'; + +const config = createEnvConfig(); +const token = await getAccessToken(false, config); + +const result = await requestApiWithAutoRefresh( + 'POST', + `${config.authBase}/ecom/tasks/scrape`, + false, + config, + JSON.stringify({ url: 'https://detail.1688.com/offer/123.html' }), + token, +); +``` + +## API + +### Functions + +#### `createEnvConfig(): EnvConfig` + +Create configuration from environment variables: +- `AUTH_BASE`: Auth base URL (default: https://api-gw-test.yuanwei-lnc.com) +- `CLIENT_KEY`: Client key (required) +- `AUTH_CACHE_DIR`: Cache directory (default: /tmp/skill-auth-cache) +- `AUTH_MIN_TTL_SEC`: Minimum token TTL in seconds (default: 60) + +#### `getAccessToken(dryRun, config): Promise` + +Get access token with caching. + +#### `refreshAccessToken(dryRun, config): Promise` + +Refresh access token (bypass cache). + +#### `fetchSessionJson(dryRun, config): Promise` + +Fetch session JSON from auth endpoint. + +#### `requestApi(method, url, authToken?, body?): Promise` + +Make HTTP request with optional authorization header. + +#### `isRetryableSessionError(response): boolean` + +Check whether response likely indicates expired runtime session. + +#### `requestApiWithAutoRefresh(method, url, dryRun, config, body?, accessToken?): Promise` + +Call API with one automatic token refresh + retry on session-expired style errors. + +### Types + +#### `EnvConfig` + +Environment configuration. + +#### `SessionResponse` + +Session response from auth endpoint. + +#### `CachedTokenData` + +Cached token data. + +#### `HttpMethod` + +Supported HTTP methods. + +#### `ApiResponse` + +HTTP response. + +## Building + +```bash +bun run build +``` diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..348762d --- /dev/null +++ b/bun.lock @@ -0,0 +1,20 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "@clawd/auth-runtime", + "devDependencies": { + "@types/node": "^25.3.3", + "typescript": "^5.9.3", + }, + }, + }, + "packages": { + "@types/node": ["@types/node@25.3.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + } +} diff --git a/dist/auth.d.ts b/dist/auth.d.ts new file mode 100644 index 0000000..c000956 --- /dev/null +++ b/dist/auth.d.ts @@ -0,0 +1,26 @@ +import type { ApiResponse, EnvConfig, HttpMethod, SessionResponse } from './types.js'; +/** + * Create environment configuration from process.env + */ +export declare function createEnvConfig(): EnvConfig; +/** + * Fetch session JSON from auth endpoint + */ +export declare function fetchSessionJson(dryRun: boolean, config: EnvConfig): Promise; +/** + * Get access token with caching + */ +export declare function getAccessToken(dryRun: boolean, config: EnvConfig): Promise; +/** + * Refresh access token (bypass cache) + */ +export declare function refreshAccessToken(dryRun: boolean, config: EnvConfig): Promise; +/** + * Check whether response likely indicates expired/invalid runtime session. + */ +export declare function isRetryableSessionError(response: ApiResponse): boolean; +/** + * Make API request with automatic runtime token refresh and one retry. + */ +export declare function requestApiWithAutoRefresh(method: HttpMethod, url: string, dryRun: boolean, config: EnvConfig, body?: string, accessToken?: string): Promise; +//# sourceMappingURL=auth.d.ts.map \ No newline at end of file diff --git a/dist/auth.d.ts.map b/dist/auth.d.ts.map new file mode 100644 index 0000000..2eb5ee1 --- /dev/null +++ b/dist/auth.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAatF;;GAEG;AACH,wBAAgB,eAAe,IAAI,SAAS,CAO3C;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,eAAe,CAAC,CAiC1B;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,MAAM,CAAC,CAoBjB;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,MAAM,CAAC,CAejB;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,WAAW,GAAG,OAAO,CAWtE;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,SAAS,EACjB,IAAI,CAAC,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,WAAW,CAAC,CAUtB"} \ No newline at end of file diff --git a/dist/auth.js b/dist/auth.js new file mode 100644 index 0000000..84c2cb8 --- /dev/null +++ b/dist/auth.js @@ -0,0 +1,107 @@ +import { requestApi } from './http.js'; +import { getCacheFile, readCachedToken, writeCache, deleteCache } from './cache.js'; +const SESSION_RETRYABLE_STATUS = new Set([401, 403]); +const SESSION_RETRYABLE_BODY_MARKERS = [ + 'session not found or expired', + 'invalid or expired token', + 'unauthorized', + 'client key expired', + 'client key revoked', +]; +/** + * Create environment configuration from process.env + */ +export function createEnvConfig() { + return { + authBase: (process.env.AUTH_BASE || 'https://api-gw-test.yuanwei-lnc.com').replace(/\/$/, ''), + clientKey: process.env.CLIENT_KEY || '', + authCacheDir: process.env.AUTH_CACHE_DIR || '/tmp/skill-auth-cache', + authMinTtlSec: parseInt(process.env.AUTH_MIN_TTL_SEC || '60', 10), + }; +} +/** + * Fetch session JSON from auth endpoint + */ +export async function fetchSessionJson(dryRun, config) { + if (dryRun) { + return { + accessToken: '', + hookUrl: '', + hookToken: '', + expiresIn: 900, + }; + } + if (!config.clientKey) { + throw new Error('CLIENT_KEY is required'); + } + const payload = JSON.stringify({ clientKey: config.clientKey }); + const result = await requestApi('POST', `${config.authBase}/auth/skill-credit/session`, undefined, payload); + if (result.status < 200 || result.status >= 300) { + throw new Error(`Auth session request failed: HTTP ${result.status} - ${result.body}`); + } + const session = JSON.parse(result.body); + if (!session.accessToken) { + throw new Error(`Missing accessToken in session response: ${result.body}`); + } + return session; +} +/** + * Get access token with caching + */ +export async function getAccessToken(dryRun, config) { + if (dryRun) { + return ''; + } + if (!config.clientKey) { + throw new Error('CLIENT_KEY is required'); + } + const cacheFile = getCacheFile(config.authBase, config.clientKey, config.authCacheDir); + const cachedToken = readCachedToken(cacheFile, config.authMinTtlSec); + if (cachedToken) { + return cachedToken; + } + const session = await fetchSessionJson(dryRun, config); + writeCache(cacheFile, session); + return session.accessToken; +} +/** + * Refresh access token (bypass cache) + */ +export async function refreshAccessToken(dryRun, config) { + if (dryRun) { + return ''; + } + if (!config.clientKey) { + throw new Error('CLIENT_KEY is required'); + } + const cacheFile = getCacheFile(config.authBase, config.clientKey, config.authCacheDir); + // Remove cache file if exists + deleteCache(cacheFile); + return getAccessToken(dryRun, config); +} +/** + * Check whether response likely indicates expired/invalid runtime session. + */ +export function isRetryableSessionError(response) { + if (!SESSION_RETRYABLE_STATUS.has(response.status)) { + return false; + } + const body = (response.body || '').toLowerCase(); + if (!body) { + return true; + } + return SESSION_RETRYABLE_BODY_MARKERS.some((marker) => body.includes(marker)); +} +/** + * Make API request with automatic runtime token refresh and one retry. + */ +export async function requestApiWithAutoRefresh(method, url, dryRun, config, body, accessToken) { + const token = accessToken || await getAccessToken(dryRun, config); + const first = await requestApi(method, url, token, body); + if (!isRetryableSessionError(first)) { + return first; + } + const freshToken = await refreshAccessToken(dryRun, config); + return requestApi(method, url, freshToken, body); +} +//# sourceMappingURL=auth.js.map \ No newline at end of file diff --git a/dist/auth.js.map b/dist/auth.js.map new file mode 100644 index 0000000..5921621 --- /dev/null +++ b/dist/auth.js.map @@ -0,0 +1 @@ +{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/cache.d.ts b/dist/cache.d.ts new file mode 100644 index 0000000..b024408 --- /dev/null +++ b/dist/cache.d.ts @@ -0,0 +1,24 @@ +/** + * Generate SHA256 hash for cache key + */ +export declare function sha256(input: string): string; +/** + * Get cache file path for current AUTH_BASE and CLIENT_KEY + */ +export declare function getCacheFile(authBase: string, clientKey: string, cacheDir: string): string; +/** + * Read cached token if valid + */ +export declare function readCachedToken(cacheFile: string, minTtlSec: number): string | null; +/** + * Write token to cache + */ +export declare function writeCache(cacheFile: string, sessionJson: { + accessToken: string; + expiresIn: number; +}): void; +/** + * Delete cache file if exists + */ +export declare function deleteCache(cacheFile: string): void; +//# sourceMappingURL=cache.d.ts.map \ No newline at end of file diff --git a/dist/cache.d.ts.map b/dist/cache.d.ts.map new file mode 100644 index 0000000..b4f5f6e --- /dev/null +++ b/dist/cache.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQ1F;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,IAAI,CAsBf;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACtD,IAAI,CAYN;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAInD"} \ No newline at end of file diff --git a/dist/cache.js b/dist/cache.js new file mode 100644 index 0000000..16b8d5e --- /dev/null +++ b/dist/cache.js @@ -0,0 +1,65 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as crypto from 'crypto'; +/** + * Generate SHA256 hash for cache key + */ +export function sha256(input) { + return crypto.createHash('sha256').update(input).digest('hex'); +} +/** + * Get cache file path for current AUTH_BASE and CLIENT_KEY + */ +export function getCacheFile(authBase, clientKey, cacheDir) { + const key = sha256(`${authBase}|${clientKey}`); + if (!fs.existsSync(cacheDir)) { + fs.mkdirSync(cacheDir, { recursive: true }); + } + return path.join(cacheDir, `session_${key}.json`); +} +/** + * Read cached token if valid + */ +export function readCachedToken(cacheFile, minTtlSec) { + if (!fs.existsSync(cacheFile)) { + return null; + } + try { + const data = JSON.parse(fs.readFileSync(cacheFile, 'utf-8')); + const now = Math.floor(Date.now() / 1000); + if (!data.accessToken || data.expiresAtEpoch <= 0) { + return null; + } + // Check if token is still valid (with min TTL buffer) + if (now + minTtlSec >= data.expiresAtEpoch) { + return null; + } + return data.accessToken; + } + catch (error) { + return null; + } +} +/** + * Write token to cache + */ +export function writeCache(cacheFile, sessionJson) { + const nowEpoch = Math.floor(Date.now() / 1000); + const expiresIn = sessionJson.expiresIn > 0 ? sessionJson.expiresIn : 900; + const expiresAtEpoch = nowEpoch + expiresIn; + const cacheData = { + accessToken: sessionJson.accessToken, + expiresAtEpoch, + createdAtEpoch: nowEpoch, + }; + fs.writeFileSync(cacheFile, JSON.stringify(cacheData, null, 2), 'utf-8'); +} +/** + * Delete cache file if exists + */ +export function deleteCache(cacheFile) { + if (fs.existsSync(cacheFile)) { + fs.unlinkSync(cacheFile); + } +} +//# sourceMappingURL=cache.js.map \ No newline at end of file diff --git a/dist/cache.js.map b/dist/cache.js.map new file mode 100644 index 0000000..e90db4f --- /dev/null +++ b/dist/cache.js.map @@ -0,0 +1 @@ +{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAGjC;;GAEG;AACH,MAAM,UAAU,MAAM,CAAC,KAAa;IAClC,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACjE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,SAAiB,EAAE,QAAgB;IAChF,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,QAAQ,IAAI,SAAS,EAAE,CAAC,CAAC;IAE/C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC;AACpD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,SAAiB,EACjB,SAAiB;IAEjB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAoB,CAAC;QAChF,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAE1C,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,cAAc,IAAI,CAAC,EAAE,CAAC;YAClD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,sDAAsD;QACtD,IAAI,GAAG,GAAG,SAAS,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YAC3C,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CACxB,SAAiB,EACjB,WAAuD;IAEvD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC/C,MAAM,SAAS,GAAG,WAAW,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC;IAC1E,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAC;IAE5C,MAAM,SAAS,GAAoB;QACjC,WAAW,EAAE,WAAW,CAAC,WAAW;QACpC,cAAc;QACd,cAAc,EAAE,QAAQ;KACzB,CAAC;IAEF,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AAC3E,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,SAAiB;IAC3C,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7B,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC"} \ No newline at end of file diff --git a/dist/http.d.ts b/dist/http.d.ts new file mode 100644 index 0000000..aeb95a4 --- /dev/null +++ b/dist/http.d.ts @@ -0,0 +1,6 @@ +import type { ApiResponse, HttpMethod } from './types.js'; +/** + * Make HTTP request to API + */ +export declare function requestApi(method: HttpMethod, url: string, authToken?: string, body?: string): Promise; +//# sourceMappingURL=http.d.ts.map \ No newline at end of file diff --git a/dist/http.d.ts.map b/dist/http.d.ts.map new file mode 100644 index 0000000..2d8b662 --- /dev/null +++ b/dist/http.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE1D;;GAEG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,MAAM,EACX,SAAS,CAAC,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,CAAC,CAyBtB"} \ No newline at end of file diff --git a/dist/http.js b/dist/http.js new file mode 100644 index 0000000..2d611f9 --- /dev/null +++ b/dist/http.js @@ -0,0 +1,25 @@ +/** + * Make HTTP request to API + */ +export async function requestApi(method, url, authToken, body) { + const headers = { + 'Content-Type': 'application/json', + }; + if (authToken) { + headers['Authorization'] = `Bearer ${authToken}`; + } + const options = { + method, + headers, + }; + if (body) { + options.body = body; + } + const response = await fetch(url, options); + const responseBody = await response.text(); + return { + status: response.status, + body: responseBody, + }; +} +//# sourceMappingURL=http.js.map \ No newline at end of file diff --git a/dist/http.js.map b/dist/http.js.map new file mode 100644 index 0000000..06f4844 --- /dev/null +++ b/dist/http.js.map @@ -0,0 +1 @@ +{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,MAAkB,EAClB,GAAW,EACX,SAAkB,EAClB,IAAa;IAEb,MAAM,OAAO,GAA2B;QACtC,cAAc,EAAE,kBAAkB;KACnC,CAAC;IAEF,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,CAAC,eAAe,CAAC,GAAG,UAAU,SAAS,EAAE,CAAC;IACnD,CAAC;IAED,MAAM,OAAO,GAAgB;QAC3B,MAAM;QACN,OAAO;KACR,CAAC;IAEF,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IACtB,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IAE3C,OAAO;QACL,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,IAAI,EAAE,YAAY;KACnB,CAAC;AACJ,CAAC"} \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..30c3218 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,12 @@ +/** + * @clawd/auth-runtime + * + * Shared TypeScript auth runtime for OpenClaw skills. + * + * Provides authentication, token caching, and HTTP utilities. + */ +export { createEnvConfig, fetchSessionJson, getAccessToken, refreshAccessToken, isRetryableSessionError, requestApiWithAutoRefresh, } from './auth.js'; +export { requestApi } from './http.js'; +export { sha256, getCacheFile, readCachedToken, writeCache, deleteCache, } from './cache.js'; +export type { EnvConfig, SessionResponse, CachedTokenData, ApiResponse, HttpMethod, } from './types.js'; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/index.d.ts.map b/dist/index.d.ts.map new file mode 100644 index 0000000..ee312b5 --- /dev/null +++ b/dist/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,uBAAuB,EACvB,yBAAyB,GAC1B,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAGvC,OAAO,EACL,MAAM,EACN,YAAY,EACZ,eAAe,EACf,UAAU,EACV,WAAW,GACZ,MAAM,YAAY,CAAC;AAGpB,YAAY,EACV,SAAS,EACT,eAAe,EACf,eAAe,EACf,WAAW,EACX,UAAU,GACX,MAAM,YAAY,CAAC"} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..1a3c8ad --- /dev/null +++ b/dist/index.js @@ -0,0 +1,14 @@ +/** + * @clawd/auth-runtime + * + * Shared TypeScript auth runtime for OpenClaw skills. + * + * Provides authentication, token caching, and HTTP utilities. + */ +// Auth functions +export { createEnvConfig, fetchSessionJson, getAccessToken, refreshAccessToken, isRetryableSessionError, requestApiWithAutoRefresh, } from './auth.js'; +// HTTP utilities +export { requestApi } from './http.js'; +// Cache utilities +export { sha256, getCacheFile, readCachedToken, writeCache, deleteCache, } from './cache.js'; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map new file mode 100644 index 0000000..d1f0e2b --- /dev/null +++ b/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,iBAAiB;AACjB,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,uBAAuB,EACvB,yBAAyB,GAC1B,MAAM,WAAW,CAAC;AAEnB,iBAAiB;AACjB,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,kBAAkB;AAClB,OAAO,EACL,MAAM,EACN,YAAY,EACZ,eAAe,EACf,UAAU,EACV,WAAW,GACZ,MAAM,YAAY,CAAC"} \ No newline at end of file diff --git a/dist/types.d.ts b/dist/types.d.ts new file mode 100644 index 0000000..6e5e687 --- /dev/null +++ b/dist/types.d.ts @@ -0,0 +1,43 @@ +/** + * Environment configuration for auth runtime + */ +export interface EnvConfig { + /** Authentication base URL */ + authBase: string; + /** Client key for authentication */ + clientKey: string; + /** Directory for storing auth cache files */ + authCacheDir: string; + /** Minimum TTL for cached tokens in seconds */ + authMinTtlSec: number; +} +/** + * Session response from /auth/skill-credit/session + */ +export interface SessionResponse { + accessToken: string; + ownerSessionToken?: string; + hookUrl?: string; + hookToken?: string; + expiresIn: number; +} +/** + * Cached token data stored on disk + */ +export interface CachedTokenData { + accessToken: string; + expiresAtEpoch: number; + createdAtEpoch: number; +} +/** + * HTTP method used by requestApi + */ +export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD'; +/** + * HTTP API response + */ +export interface ApiResponse { + status: number; + body: string; +} +//# sourceMappingURL=types.d.ts.map \ No newline at end of file diff --git a/dist/types.d.ts.map b/dist/types.d.ts.map new file mode 100644 index 0000000..13fdd48 --- /dev/null +++ b/dist/types.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,YAAY,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE9E;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd"} \ No newline at end of file diff --git a/dist/types.js b/dist/types.js new file mode 100644 index 0000000..718fd38 --- /dev/null +++ b/dist/types.js @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/dist/types.js.map b/dist/types.js.map new file mode 100644 index 0000000..c768b79 --- /dev/null +++ b/dist/types.js.map @@ -0,0 +1 @@ +{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..b55d973 --- /dev/null +++ b/install.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Bootstrap auth-runtime into the skill store. +# Run this ONCE before installing any skills. +# +# Usage: +# curl -fsSL https://your-forgejo/raw/auth-runtime/main/install.sh | bash +# # or: +# ./install.sh [skill-store-dir] + +set -euo pipefail + +STORE="${1:-$HOME/.openclaw/skills}" +DEST="$STORE/_shared/auth-runtime" +REPO="${AUTH_RUNTIME_REPO:-}" # set via env or hardcode below + +# Hardcode your Forgejo URL here: +# REPO="https://git.yourserver.com/you/auth-runtime.git" + +if [[ -z "$REPO" ]]; then + echo "Error: set AUTH_RUNTIME_REPO=https://git.yourserver.com/you/auth-runtime.git" + exit 1 +fi + +if [[ -d "$DEST/.git" ]]; then + echo "Updating auth-runtime..." + git -C "$DEST" pull --ff-only +else + echo "Installing auth-runtime to $DEST..." + mkdir -p "$STORE/_shared" + git clone "$REPO" "$DEST" +fi + +echo "auth-runtime ready at $DEST" diff --git a/package.json b/package.json new file mode 100644 index 0000000..9a6093c --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "@clawd/auth-runtime", + "version": "1.0.0", + "description": "Shared TypeScript auth runtime for OpenClaw skills", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "scripts": { + "build": "tsc", + "clean": "rm -rf dist" + }, + "keywords": [ + "auth", + "runtime", + "openclaw" + ], + "author": "", + "license": "MIT", + "devDependencies": { + "@types/node": "^25.3.3", + "typescript": "^5.9.3" + } +} diff --git a/src/auth.ts b/src/auth.ts new file mode 100644 index 0000000..d54e338 --- /dev/null +++ b/src/auth.ts @@ -0,0 +1,154 @@ +import type { ApiResponse, EnvConfig, HttpMethod, SessionResponse } from './types.js'; +import { requestApi } from './http.js'; +import { getCacheFile, readCachedToken, writeCache, deleteCache } from './cache.js'; + +const SESSION_RETRYABLE_STATUS = new Set([401, 403]); +const SESSION_RETRYABLE_BODY_MARKERS = [ + 'session not found or expired', + 'invalid or expired token', + 'unauthorized', + 'client key expired', + 'client key revoked', +]; + +/** + * Create environment configuration from process.env + */ +export function createEnvConfig(): EnvConfig { + return { + authBase: (process.env.AUTH_BASE || 'https://api-gw-test.yuanwei-lnc.com').replace(/\/$/, ''), + clientKey: process.env.CLIENT_KEY || '', + authCacheDir: process.env.AUTH_CACHE_DIR || '/tmp/skill-auth-cache', + authMinTtlSec: parseInt(process.env.AUTH_MIN_TTL_SEC || '60', 10), + }; +} + +/** + * Fetch session JSON from auth endpoint + */ +export async function fetchSessionJson( + dryRun: boolean, + config: EnvConfig +): Promise { + if (dryRun) { + return { + accessToken: '', + hookUrl: '', + hookToken: '', + expiresIn: 900, + }; + } + + if (!config.clientKey) { + throw new Error('CLIENT_KEY is required'); + } + + const payload = JSON.stringify({ clientKey: config.clientKey }); + const result = await requestApi( + 'POST', + `${config.authBase}/auth/skill-credit/session`, + undefined, + payload + ); + + if (result.status < 200 || result.status >= 300) { + throw new Error(`Auth session request failed: HTTP ${result.status} - ${result.body}`); + } + + const session = JSON.parse(result.body) as SessionResponse; + + if (!session.accessToken) { + throw new Error(`Missing accessToken in session response: ${result.body}`); + } + + return session; +} + +/** + * Get access token with caching + */ +export async function getAccessToken( + dryRun: boolean, + config: EnvConfig +): Promise { + if (dryRun) { + return ''; + } + + if (!config.clientKey) { + throw new Error('CLIENT_KEY is required'); + } + + const cacheFile = getCacheFile(config.authBase, config.clientKey, config.authCacheDir); + const cachedToken = readCachedToken(cacheFile, config.authMinTtlSec); + + if (cachedToken) { + return cachedToken; + } + + const session = await fetchSessionJson(dryRun, config); + writeCache(cacheFile, session); + + return session.accessToken; +} + +/** + * Refresh access token (bypass cache) + */ +export async function refreshAccessToken( + dryRun: boolean, + config: EnvConfig +): Promise { + if (dryRun) { + return ''; + } + + if (!config.clientKey) { + throw new Error('CLIENT_KEY is required'); + } + + const cacheFile = getCacheFile(config.authBase, config.clientKey, config.authCacheDir); + + // Remove cache file if exists + deleteCache(cacheFile); + + return getAccessToken(dryRun, config); +} + +/** + * Check whether response likely indicates expired/invalid runtime session. + */ +export function isRetryableSessionError(response: ApiResponse): boolean { + if (!SESSION_RETRYABLE_STATUS.has(response.status)) { + return false; + } + + const body = (response.body || '').toLowerCase(); + if (!body) { + return true; + } + + return SESSION_RETRYABLE_BODY_MARKERS.some((marker) => body.includes(marker)); +} + +/** + * Make API request with automatic runtime token refresh and one retry. + */ +export async function requestApiWithAutoRefresh( + method: HttpMethod, + url: string, + dryRun: boolean, + config: EnvConfig, + body?: string, + accessToken?: string, +): Promise { + const token = accessToken || await getAccessToken(dryRun, config); + const first = await requestApi(method, url, token, body); + + if (!isRetryableSessionError(first)) { + return first; + } + + const freshToken = await refreshAccessToken(dryRun, config); + return requestApi(method, url, freshToken, body); +} diff --git a/src/cache.ts b/src/cache.ts new file mode 100644 index 0000000..5cf0fb6 --- /dev/null +++ b/src/cache.ts @@ -0,0 +1,83 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as crypto from 'crypto'; +import type { CachedTokenData } from './types.js'; + +/** + * Generate SHA256 hash for cache key + */ +export function sha256(input: string): string { + return crypto.createHash('sha256').update(input).digest('hex'); +} + +/** + * Get cache file path for current AUTH_BASE and CLIENT_KEY + */ +export function getCacheFile(authBase: string, clientKey: string, cacheDir: string): string { + const key = sha256(`${authBase}|${clientKey}`); + + if (!fs.existsSync(cacheDir)) { + fs.mkdirSync(cacheDir, { recursive: true }); + } + + return path.join(cacheDir, `session_${key}.json`); +} + +/** + * Read cached token if valid + */ +export function readCachedToken( + cacheFile: string, + minTtlSec: number +): string | null { + if (!fs.existsSync(cacheFile)) { + return null; + } + + try { + const data = JSON.parse(fs.readFileSync(cacheFile, 'utf-8')) as CachedTokenData; + const now = Math.floor(Date.now() / 1000); + + if (!data.accessToken || data.expiresAtEpoch <= 0) { + return null; + } + + // Check if token is still valid (with min TTL buffer) + if (now + minTtlSec >= data.expiresAtEpoch) { + return null; + } + + return data.accessToken; + } catch (error) { + return null; + } +} + +/** + * Write token to cache + */ +export function writeCache( + cacheFile: string, + sessionJson: { accessToken: string; expiresIn: number } +): void { + const nowEpoch = Math.floor(Date.now() / 1000); + const expiresIn = sessionJson.expiresIn > 0 ? sessionJson.expiresIn : 900; + const expiresAtEpoch = nowEpoch + expiresIn; + + const cacheData: CachedTokenData = { + accessToken: sessionJson.accessToken, + expiresAtEpoch, + createdAtEpoch: nowEpoch, + }; + + fs.writeFileSync(cacheFile, JSON.stringify(cacheData, null, 2), 'utf-8'); +} + +/** + * Delete cache file if exists + */ +export function deleteCache(cacheFile: string): void { + if (fs.existsSync(cacheFile)) { + fs.unlinkSync(cacheFile); + } +} diff --git a/src/http.ts b/src/http.ts new file mode 100644 index 0000000..b2ed16b --- /dev/null +++ b/src/http.ts @@ -0,0 +1,36 @@ +import type { ApiResponse, HttpMethod } from './types.js'; + +/** + * Make HTTP request to API + */ +export async function requestApi( + method: HttpMethod, + url: string, + authToken?: string, + body?: string +): Promise { + const headers: Record = { + 'Content-Type': 'application/json', + }; + + if (authToken) { + headers['Authorization'] = `Bearer ${authToken}`; + } + + const options: RequestInit = { + method, + headers, + }; + + if (body) { + options.body = body; + } + + const response = await fetch(url, options); + const responseBody = await response.text(); + + return { + status: response.status, + body: responseBody, + }; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..4c15754 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,38 @@ +/** + * @clawd/auth-runtime + * + * Shared TypeScript auth runtime for OpenClaw skills. + * + * Provides authentication, token caching, and HTTP utilities. + */ + +// Auth functions +export { + createEnvConfig, + fetchSessionJson, + getAccessToken, + refreshAccessToken, + isRetryableSessionError, + requestApiWithAutoRefresh, +} from './auth.js'; + +// HTTP utilities +export { requestApi } from './http.js'; + +// Cache utilities +export { + sha256, + getCacheFile, + readCachedToken, + writeCache, + deleteCache, +} from './cache.js'; + +// Types +export type { + EnvConfig, + SessionResponse, + CachedTokenData, + ApiResponse, + HttpMethod, +} from './types.js'; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..d6e7304 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,46 @@ +/** + * Environment configuration for auth runtime + */ +export interface EnvConfig { + /** Authentication base URL */ + authBase: string; + /** Client key for authentication */ + clientKey: string; + /** Directory for storing auth cache files */ + authCacheDir: string; + /** Minimum TTL for cached tokens in seconds */ + authMinTtlSec: number; +} + +/** + * Session response from /auth/skill-credit/session + */ +export interface SessionResponse { + accessToken: string; + ownerSessionToken?: string; + hookUrl?: string; + hookToken?: string; + expiresIn: number; +} + +/** + * Cached token data stored on disk + */ +export interface CachedTokenData { + accessToken: string; + expiresAtEpoch: number; + createdAtEpoch: number; +} + +/** + * HTTP method used by requestApi + */ +export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD'; + +/** + * HTTP API response + */ +export interface ApiResponse { + status: number; + body: string; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..fb7cd24 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "lib": ["ES2022"], + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}