@glubean/oauth-code 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 +152 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +373 -0
- package/dist/index.js.map +1 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# @glubean/oauth-code
|
|
2
|
+
|
|
3
|
+
OAuth Authorization Code flow plugin for [Glubean](https://glubean.dev) — interactive token acquisition for explore mode.
|
|
4
|
+
|
|
5
|
+
On first HTTP request, opens the system browser for OAuth login, starts a local callback server on `127.0.0.1`, exchanges the authorization code for tokens, and caches them to disk. Subsequent requests use the cached token, refreshing automatically when expired.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @glubean/oauth-code
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { test, configure } from "@glubean/sdk";
|
|
17
|
+
import { oauthCode } from "@glubean/oauth-code";
|
|
18
|
+
|
|
19
|
+
const { http } = configure({
|
|
20
|
+
http: oauthCode({
|
|
21
|
+
prefixUrl: "https://api.github.com",
|
|
22
|
+
authorizeUrl: "https://github.com/login/oauth/authorize",
|
|
23
|
+
tokenUrl: "https://github.com/login/oauth/access_token",
|
|
24
|
+
clientId: "{{GITHUB_CLIENT_ID}}",
|
|
25
|
+
clientSecret: "{{GITHUB_CLIENT_SECRET}}",
|
|
26
|
+
scopes: ["repo", "read:user"],
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const me = test("github-me", async (ctx) => {
|
|
31
|
+
const res = await http.get("user").json<{ login: string }>();
|
|
32
|
+
ctx.assert(res.login, "got user login");
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
First run opens the browser for login. Subsequent runs use the cached token.
|
|
37
|
+
|
|
38
|
+
## How It Works
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
1. Test calls http.get("/some-endpoint")
|
|
42
|
+
2. beforeRequest hook checks for cached token
|
|
43
|
+
├─ Memory cache valid? → use it
|
|
44
|
+
├─ Disk cache valid? → use it
|
|
45
|
+
├─ Has refresh_token? → refresh automatically
|
|
46
|
+
└─ Nothing cached? → start browser flow:
|
|
47
|
+
a. Start local HTTP server on 127.0.0.1 (random port)
|
|
48
|
+
b. Open system browser with authorize URL
|
|
49
|
+
c. User logs in and authorizes
|
|
50
|
+
d. Provider redirects to 127.0.0.1/callback?code=xxx
|
|
51
|
+
e. Exchange code for access_token + refresh_token
|
|
52
|
+
f. Cache to .glubean/tokens/, close server
|
|
53
|
+
3. Set Authorization: Bearer <token> header
|
|
54
|
+
4. Request proceeds
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## API
|
|
58
|
+
|
|
59
|
+
### `oauthCode(options)` — ConfigureHttpOptions factory
|
|
60
|
+
|
|
61
|
+
Returns `ConfigureHttpOptions` for use with `configure({ http })`. All string options support `{{template}}` placeholders resolved from Glubean vars and secrets.
|
|
62
|
+
|
|
63
|
+
| Option | Type | Default | Description |
|
|
64
|
+
|--------|------|---------|-------------|
|
|
65
|
+
| `prefixUrl` | `string` | — | Base URL for API requests |
|
|
66
|
+
| `authorizeUrl` | `string` | — | OAuth authorization endpoint |
|
|
67
|
+
| `tokenUrl` | `string` | — | OAuth token endpoint |
|
|
68
|
+
| `clientId` | `string` | — | Client ID |
|
|
69
|
+
| `clientSecret` | `string?` | — | Client secret (optional for public clients with PKCE) |
|
|
70
|
+
| `scopes` | `string[]?` | — | OAuth scopes |
|
|
71
|
+
| `pkce` | `boolean` | `true` | Enable PKCE with S256 challenge |
|
|
72
|
+
| `cacheDir` | `string` | `".glubean/tokens"` | Token cache directory |
|
|
73
|
+
| `redirectUri` | `string?` | — | Override redirect URI (for tunnel setups, see below) |
|
|
74
|
+
| `port` | `number?` | random | Fixed port for callback server (required with `redirectUri`) |
|
|
75
|
+
| `authorizeParams` | `Record<string, string>?` | — | Extra query parameters for authorize URL |
|
|
76
|
+
| `openBrowser` | `(url: string) => void` | system default | Custom browser opener |
|
|
77
|
+
|
|
78
|
+
## Token Caching
|
|
79
|
+
|
|
80
|
+
Tokens are cached to `{cacheDir}/{hash}.json` (default: `.glubean/tokens/`). The hash is derived from `clientId + authorizeUrl + scopes` to avoid collisions between providers or different scope sets.
|
|
81
|
+
|
|
82
|
+
Cache files are written with `0600` permissions (owner read/write only).
|
|
83
|
+
|
|
84
|
+
Add `.glubean/tokens/` to your `.gitignore`.
|
|
85
|
+
|
|
86
|
+
## PKCE
|
|
87
|
+
|
|
88
|
+
PKCE (S256) is enabled by default. This is required by some providers (e.g., Twitter/X) and recommended by RFC 7636 for all public clients. Set `pkce: false` to disable for providers that don't support it.
|
|
89
|
+
|
|
90
|
+
## Provider Compatibility
|
|
91
|
+
|
|
92
|
+
| Provider | Localhost redirect | Tunnel needed | Notes |
|
|
93
|
+
|----------|-------------------|---------------|-------|
|
|
94
|
+
| GitHub | `127.0.0.1` ✅ | No | Port-flexible |
|
|
95
|
+
| Google | `127.0.0.1` ✅ | No | Port > 1024 |
|
|
96
|
+
| Microsoft | `127.0.0.1` ✅ | No | Port ignored in matching |
|
|
97
|
+
| Spotify | `127.0.0.1` ✅ | No | Port-flexible |
|
|
98
|
+
| Twitter/X | ❌ | Yes | No loopback redirect support |
|
|
99
|
+
| Slack | ❌ | Yes | Requires HTTPS for all redirect URIs |
|
|
100
|
+
|
|
101
|
+
### Providers that require a tunnel (Twitter/X, Slack)
|
|
102
|
+
|
|
103
|
+
These providers reject `http://127.0.0.1` as a redirect URI. Use an HTTPS tunnel like [ngrok](https://ngrok.com) to forward traffic to the local callback server:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# 1. Start a tunnel on a fixed port
|
|
107
|
+
ngrok http 9876
|
|
108
|
+
# → https://abc123.ngrok-free.app
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// 2. Register the ngrok URL as a redirect URI with the provider, then:
|
|
113
|
+
const { http } = configure({
|
|
114
|
+
http: oauthCode({
|
|
115
|
+
prefixUrl: "https://api.x.com/2",
|
|
116
|
+
authorizeUrl: "https://twitter.com/i/oauth2/authorize",
|
|
117
|
+
tokenUrl: "https://api.x.com/2/oauth2/token",
|
|
118
|
+
clientId: "{{TWITTER_CLIENT_ID}}",
|
|
119
|
+
scopes: ["tweet.read", "users.read"],
|
|
120
|
+
redirectUri: "https://abc123.ngrok-free.app/callback",
|
|
121
|
+
port: 9876,
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The local server still listens on `127.0.0.1:9876`; ngrok tunnels the HTTPS callback back to it.
|
|
127
|
+
|
|
128
|
+
> **Note:** Postman solves this differently — it hosts its own cloud relay at `oauth.pstmn.io/callback` so users never need a tunnel. A similar Glubean Cloud relay might be added in the future, though this introduces a security trade-off: the relay becomes a credential intermediary that handles authorization codes on behalf of users, significantly expanding the trust boundary.
|
|
129
|
+
|
|
130
|
+
## Promoting to CI
|
|
131
|
+
|
|
132
|
+
This plugin is designed for **explore mode** (interactive local development). When promoting tests to CI:
|
|
133
|
+
|
|
134
|
+
1. Replace `oauthCode()` with a non-interactive auth method from `@glubean/auth`:
|
|
135
|
+
- `oauth2.clientCredentials()` — if the provider supports it
|
|
136
|
+
- `oauth2.refreshToken()` — with a pre-provisioned refresh token
|
|
137
|
+
- `bearer()` — with a pre-provisioned access token
|
|
138
|
+
2. Test logic stays the same — only the `configure({ http })` line changes.
|
|
139
|
+
|
|
140
|
+
## Scope
|
|
141
|
+
|
|
142
|
+
This is a v1 focused on the authorization code flow for explore mode. Not included:
|
|
143
|
+
|
|
144
|
+
- Device code flow (RFC 8628)
|
|
145
|
+
- Implicit grant (deprecated)
|
|
146
|
+
- Token revocation
|
|
147
|
+
- Multi-account support
|
|
148
|
+
- Custom TLS / proxy configuration
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { ConfigureHttpOptions } from "@glubean/sdk";
|
|
2
|
+
export interface OAuthCodeOptions {
|
|
3
|
+
/** Base URL for API requests — var key or literal */
|
|
4
|
+
prefixUrl: string;
|
|
5
|
+
/** OAuth authorization endpoint URL */
|
|
6
|
+
authorizeUrl: string;
|
|
7
|
+
/** OAuth token endpoint URL */
|
|
8
|
+
tokenUrl: string;
|
|
9
|
+
/** Client ID — literal or `{{SECRET}}` reference */
|
|
10
|
+
clientId: string;
|
|
11
|
+
/** Client secret — literal or `{{SECRET}}` reference (optional for public clients with PKCE) */
|
|
12
|
+
clientSecret?: string;
|
|
13
|
+
/** OAuth scopes */
|
|
14
|
+
scopes?: string[];
|
|
15
|
+
/** Enable PKCE with S256 (default: true) */
|
|
16
|
+
pkce?: boolean;
|
|
17
|
+
/** Token cache directory (default: ".glubean/tokens") */
|
|
18
|
+
cacheDir?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Override the redirect URI sent to the provider.
|
|
21
|
+
* Use with `port` for providers that reject `http://127.0.0.1` (e.g., Slack, Twitter/X).
|
|
22
|
+
* The local callback server still listens on 127.0.0.1; set this to the external URL
|
|
23
|
+
* that tunnels traffic back (e.g., an ngrok HTTPS URL).
|
|
24
|
+
*
|
|
25
|
+
* @example ngrok tunnel
|
|
26
|
+
* ```ts
|
|
27
|
+
* // 1. Run: ngrok http 9876
|
|
28
|
+
* // 2. Register https://abc123.ngrok.io/callback with the provider
|
|
29
|
+
* oauthCode({
|
|
30
|
+
* redirectUri: "https://abc123.ngrok.io/callback",
|
|
31
|
+
* port: 9876,
|
|
32
|
+
* ...
|
|
33
|
+
* })
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
redirectUri?: string;
|
|
37
|
+
/** Fixed port for the local callback server (default: random). Required when using `redirectUri` with a tunnel. */
|
|
38
|
+
port?: number;
|
|
39
|
+
/** Extra query parameters for the authorize URL */
|
|
40
|
+
authorizeParams?: Record<string, string>;
|
|
41
|
+
/** Custom function to open a URL in the browser (default: system browser via `open`/`xdg-open`) */
|
|
42
|
+
openBrowser?: (url: string) => void;
|
|
43
|
+
}
|
|
44
|
+
export declare function generateCodeVerifier(): string;
|
|
45
|
+
export declare function generateCodeChallenge(verifier: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* OAuth Authorization Code flow for Glubean explore mode.
|
|
48
|
+
*
|
|
49
|
+
* On first HTTP request, opens the system browser for OAuth login,
|
|
50
|
+
* starts a local server to receive the callback, exchanges the code
|
|
51
|
+
* for tokens, and caches them to disk. Subsequent requests use the
|
|
52
|
+
* cached token, refreshing automatically when expired.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* import { oauthCode } from "@glubean/oauth-code";
|
|
57
|
+
*
|
|
58
|
+
* const { http } = configure({
|
|
59
|
+
* http: oauthCode({
|
|
60
|
+
* prefixUrl: "https://api.github.com",
|
|
61
|
+
* authorizeUrl: "https://github.com/login/oauth/authorize",
|
|
62
|
+
* tokenUrl: "https://github.com/login/oauth/access_token",
|
|
63
|
+
* clientId: "{{GITHUB_CLIENT_ID}}",
|
|
64
|
+
* clientSecret: "{{GITHUB_CLIENT_SECRET}}",
|
|
65
|
+
* scopes: ["repo", "read:user"],
|
|
66
|
+
* }),
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function oauthCode(opts: OAuthCodeOptions): ConfigureHttpOptions;
|
|
71
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAsB,MAAM,cAAc,CAAC;AAS7E,MAAM,WAAW,gBAAgB;IAC/B,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,YAAY,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,gGAAgG;IAChG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB;IACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,4CAA4C;IAC5C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;;;;;;OAgBG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mHAAmH;IACnH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,mGAAmG;IACnG,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC;AAmBD,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE9D;AA2ND;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,oBAAoB,CA0MtE"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { exec } from "node:child_process";
|
|
4
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
// ── Marker Headers ───────────────────────────────────────────────────────────
|
|
7
|
+
const AUTH_URL_H = "X-Glubean-OAuthCode-AuthUrl";
|
|
8
|
+
const TOKEN_URL_H = "X-Glubean-OAuthCode-TokenUrl";
|
|
9
|
+
const CLIENT_ID_H = "X-Glubean-OAuthCode-ClientId";
|
|
10
|
+
const CLIENT_SECRET_H = "X-Glubean-OAuthCode-ClientSecret";
|
|
11
|
+
const ALL_MARKERS = [AUTH_URL_H, TOKEN_URL_H, CLIENT_ID_H, CLIENT_SECRET_H];
|
|
12
|
+
function cleanMarkers(request) {
|
|
13
|
+
const h = new Headers(request.headers);
|
|
14
|
+
for (const m of ALL_MARKERS)
|
|
15
|
+
h.delete(m);
|
|
16
|
+
return h;
|
|
17
|
+
}
|
|
18
|
+
// ── PKCE ─────────────────────────────────────────────────────────────────────
|
|
19
|
+
export function generateCodeVerifier() {
|
|
20
|
+
return randomBytes(32).toString("base64url");
|
|
21
|
+
}
|
|
22
|
+
export function generateCodeChallenge(verifier) {
|
|
23
|
+
return createHash("sha256").update(verifier).digest("base64url");
|
|
24
|
+
}
|
|
25
|
+
// ── Request Rebuild ──────────────────────────────────────────────────────────
|
|
26
|
+
async function rebuildRequest(request, headers) {
|
|
27
|
+
const bodyBuffer = request.body
|
|
28
|
+
? await request.clone().arrayBuffer()
|
|
29
|
+
: null;
|
|
30
|
+
return new Request(request.url, {
|
|
31
|
+
method: request.method,
|
|
32
|
+
headers,
|
|
33
|
+
body: bodyBuffer,
|
|
34
|
+
redirect: request.redirect,
|
|
35
|
+
signal: request.signal,
|
|
36
|
+
...(bodyBuffer ? { duplex: "half" } : {}),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function cacheKey(clientId, authorizeUrl, scopes, authorizeParams) {
|
|
40
|
+
const paramsStr = authorizeParams
|
|
41
|
+
? Object.keys(authorizeParams).sort().map((k) => `${k}=${authorizeParams[k]}`).join("&")
|
|
42
|
+
: "";
|
|
43
|
+
return createHash("sha256")
|
|
44
|
+
.update(`${clientId}:${authorizeUrl}:${scopes ?? ""}:${paramsStr}`)
|
|
45
|
+
.digest("hex")
|
|
46
|
+
.slice(0, 12);
|
|
47
|
+
}
|
|
48
|
+
async function readCache(dir, key) {
|
|
49
|
+
try {
|
|
50
|
+
const data = await readFile(join(dir, `${key}.json`), "utf-8");
|
|
51
|
+
return JSON.parse(data);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function writeCache(dir, key, token) {
|
|
58
|
+
await mkdir(dir, { recursive: true });
|
|
59
|
+
await writeFile(join(dir, `${key}.json`), JSON.stringify(token, null, 2), { mode: 0o600 });
|
|
60
|
+
}
|
|
61
|
+
function startCallbackServer(port) {
|
|
62
|
+
return new Promise((resolveServer) => {
|
|
63
|
+
let resolveCode;
|
|
64
|
+
let rejectCode;
|
|
65
|
+
let codePromise = null;
|
|
66
|
+
let expectedState = null;
|
|
67
|
+
const server = createServer((req, res) => {
|
|
68
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
69
|
+
if (url.pathname !== "/callback") {
|
|
70
|
+
res.writeHead(404);
|
|
71
|
+
res.end();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const error = url.searchParams.get("error");
|
|
75
|
+
if (error) {
|
|
76
|
+
const desc = url.searchParams.get("error_description") ?? error;
|
|
77
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
78
|
+
res.end(html("Authorization Failed", `Error: ${desc}. You can close this window.`));
|
|
79
|
+
rejectCode(new Error(`OAuth authorization failed: ${desc}`));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const code = url.searchParams.get("code");
|
|
83
|
+
const state = url.searchParams.get("state");
|
|
84
|
+
if (!code) {
|
|
85
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
86
|
+
res.end(html("Missing Code", "No authorization code received."));
|
|
87
|
+
rejectCode(new Error("OAuth callback missing code parameter"));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (expectedState && state !== expectedState) {
|
|
91
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
92
|
+
res.end(html("Invalid State", "State mismatch — possible CSRF attack."));
|
|
93
|
+
rejectCode(new Error("OAuth state mismatch"));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
97
|
+
res.end(html("Authorization Successful", "You can close this window."));
|
|
98
|
+
resolveCode(code);
|
|
99
|
+
});
|
|
100
|
+
server.listen(port ?? 0, "127.0.0.1", () => {
|
|
101
|
+
const addr = server.address();
|
|
102
|
+
resolveServer({
|
|
103
|
+
port: addr.port,
|
|
104
|
+
waitForCode(state) {
|
|
105
|
+
if (!codePromise) {
|
|
106
|
+
expectedState = state;
|
|
107
|
+
codePromise = new Promise((res, rej) => {
|
|
108
|
+
resolveCode = res;
|
|
109
|
+
rejectCode = rej;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return codePromise;
|
|
113
|
+
},
|
|
114
|
+
close: () => server.close(),
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function html(title, message) {
|
|
120
|
+
return `<!DOCTYPE html><html><head><title>${title}</title></head><body><h2>${title}</h2><p>${message}</p></body></html>`;
|
|
121
|
+
}
|
|
122
|
+
// ── Browser Open ─────────────────────────────────────────────────────────────
|
|
123
|
+
function openBrowser(url) {
|
|
124
|
+
const cmd = process.platform === "darwin" ? "open" :
|
|
125
|
+
process.platform === "win32" ? "start" :
|
|
126
|
+
"xdg-open";
|
|
127
|
+
exec(`${cmd} ${JSON.stringify(url)}`);
|
|
128
|
+
}
|
|
129
|
+
async function exchangeCode(params) {
|
|
130
|
+
const body = new URLSearchParams({
|
|
131
|
+
grant_type: "authorization_code",
|
|
132
|
+
code: params.code,
|
|
133
|
+
redirect_uri: params.redirectUri,
|
|
134
|
+
client_id: params.clientId,
|
|
135
|
+
});
|
|
136
|
+
if (params.clientSecret)
|
|
137
|
+
body.set("client_secret", params.clientSecret);
|
|
138
|
+
if (params.codeVerifier)
|
|
139
|
+
body.set("code_verifier", params.codeVerifier);
|
|
140
|
+
const res = await fetch(params.tokenUrl, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
headers: {
|
|
143
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
144
|
+
Accept: "application/json",
|
|
145
|
+
},
|
|
146
|
+
body,
|
|
147
|
+
});
|
|
148
|
+
if (!res.ok) {
|
|
149
|
+
throw new Error(`OAuth token exchange failed (${res.status}): ${await res.text()}`);
|
|
150
|
+
}
|
|
151
|
+
return (await res.json());
|
|
152
|
+
}
|
|
153
|
+
async function refreshAccessToken(params) {
|
|
154
|
+
const body = new URLSearchParams({
|
|
155
|
+
grant_type: "refresh_token",
|
|
156
|
+
refresh_token: params.refreshToken,
|
|
157
|
+
client_id: params.clientId,
|
|
158
|
+
});
|
|
159
|
+
if (params.clientSecret)
|
|
160
|
+
body.set("client_secret", params.clientSecret);
|
|
161
|
+
const res = await fetch(params.tokenUrl, {
|
|
162
|
+
method: "POST",
|
|
163
|
+
headers: {
|
|
164
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
165
|
+
Accept: "application/json",
|
|
166
|
+
},
|
|
167
|
+
body,
|
|
168
|
+
});
|
|
169
|
+
if (!res.ok) {
|
|
170
|
+
throw new Error(`OAuth token refresh failed (${res.status}): ${await res.text()}`);
|
|
171
|
+
}
|
|
172
|
+
return (await res.json());
|
|
173
|
+
}
|
|
174
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
175
|
+
/**
|
|
176
|
+
* OAuth Authorization Code flow for Glubean explore mode.
|
|
177
|
+
*
|
|
178
|
+
* On first HTTP request, opens the system browser for OAuth login,
|
|
179
|
+
* starts a local server to receive the callback, exchanges the code
|
|
180
|
+
* for tokens, and caches them to disk. Subsequent requests use the
|
|
181
|
+
* cached token, refreshing automatically when expired.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* import { oauthCode } from "@glubean/oauth-code";
|
|
186
|
+
*
|
|
187
|
+
* const { http } = configure({
|
|
188
|
+
* http: oauthCode({
|
|
189
|
+
* prefixUrl: "https://api.github.com",
|
|
190
|
+
* authorizeUrl: "https://github.com/login/oauth/authorize",
|
|
191
|
+
* tokenUrl: "https://github.com/login/oauth/access_token",
|
|
192
|
+
* clientId: "{{GITHUB_CLIENT_ID}}",
|
|
193
|
+
* clientSecret: "{{GITHUB_CLIENT_SECRET}}",
|
|
194
|
+
* scopes: ["repo", "read:user"],
|
|
195
|
+
* }),
|
|
196
|
+
* });
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
export function oauthCode(opts) {
|
|
200
|
+
const usePkce = opts.pkce !== false;
|
|
201
|
+
const cacheDir = opts.cacheDir ?? ".glubean/tokens";
|
|
202
|
+
const scopes = opts.scopes?.join(" ");
|
|
203
|
+
const open = opts.openBrowser ?? openBrowser;
|
|
204
|
+
let cached = null;
|
|
205
|
+
let diskChecked = false;
|
|
206
|
+
let inflight = null;
|
|
207
|
+
const headers = {
|
|
208
|
+
[AUTH_URL_H]: opts.authorizeUrl,
|
|
209
|
+
[TOKEN_URL_H]: opts.tokenUrl,
|
|
210
|
+
[CLIENT_ID_H]: opts.clientId,
|
|
211
|
+
};
|
|
212
|
+
if (opts.clientSecret)
|
|
213
|
+
headers[CLIENT_SECRET_H] = opts.clientSecret;
|
|
214
|
+
/** Read resolved marker values from the request (template-resolved by SDK). */
|
|
215
|
+
function readMarkers(request) {
|
|
216
|
+
return {
|
|
217
|
+
authorizeUrl: request.headers.get(AUTH_URL_H) ?? opts.authorizeUrl,
|
|
218
|
+
tokenUrl: request.headers.get(TOKEN_URL_H) ?? opts.tokenUrl,
|
|
219
|
+
clientId: request.headers.get(CLIENT_ID_H) ?? opts.clientId,
|
|
220
|
+
clientSecret: request.headers.get(CLIENT_SECRET_H) ?? undefined,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function toCache(data, prev) {
|
|
224
|
+
return {
|
|
225
|
+
accessToken: data.access_token,
|
|
226
|
+
refreshToken: data.refresh_token ?? prev?.refreshToken,
|
|
227
|
+
expiresAt: Date.now() + (data.expires_in ?? 3600) * 1000,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
async function tryRefresh(tokenUrl, refreshToken, clientId, clientSecret) {
|
|
231
|
+
try {
|
|
232
|
+
const data = await refreshAccessToken({ tokenUrl, refreshToken, clientId, clientSecret });
|
|
233
|
+
return toCache(data, cached ?? undefined);
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async function acquireToken(request) {
|
|
240
|
+
const m = readMarkers(request);
|
|
241
|
+
const key = cacheKey(m.clientId, m.authorizeUrl, scopes, opts.authorizeParams);
|
|
242
|
+
// 1. Memory cache — still valid
|
|
243
|
+
if (cached && cached.expiresAt > Date.now() + 30_000) {
|
|
244
|
+
return cached;
|
|
245
|
+
}
|
|
246
|
+
// 2. Disk cache
|
|
247
|
+
if (!diskChecked) {
|
|
248
|
+
diskChecked = true;
|
|
249
|
+
const disk = await readCache(cacheDir, key);
|
|
250
|
+
if (disk) {
|
|
251
|
+
if (disk.expiresAt > Date.now() + 30_000) {
|
|
252
|
+
cached = disk;
|
|
253
|
+
return cached;
|
|
254
|
+
}
|
|
255
|
+
// Expired but has refresh token
|
|
256
|
+
if (disk.refreshToken) {
|
|
257
|
+
const refreshed = await tryRefresh(m.tokenUrl, disk.refreshToken, m.clientId, m.clientSecret);
|
|
258
|
+
if (refreshed) {
|
|
259
|
+
cached = refreshed;
|
|
260
|
+
await writeCache(cacheDir, key, cached);
|
|
261
|
+
return cached;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// 3. Memory cache has refresh token
|
|
267
|
+
if (cached?.refreshToken) {
|
|
268
|
+
const refreshed = await tryRefresh(m.tokenUrl, cached.refreshToken, m.clientId, m.clientSecret);
|
|
269
|
+
if (refreshed) {
|
|
270
|
+
cached = refreshed;
|
|
271
|
+
await writeCache(cacheDir, key, cached);
|
|
272
|
+
return cached;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// 4. Browser flow
|
|
276
|
+
const server = await startCallbackServer(opts.port);
|
|
277
|
+
const redirectUri = opts.redirectUri ?? `http://127.0.0.1:${server.port}/callback`;
|
|
278
|
+
const state = randomBytes(16).toString("hex");
|
|
279
|
+
const authUrl = new URL(m.authorizeUrl);
|
|
280
|
+
authUrl.searchParams.set("response_type", "code");
|
|
281
|
+
authUrl.searchParams.set("client_id", m.clientId);
|
|
282
|
+
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
283
|
+
authUrl.searchParams.set("state", state);
|
|
284
|
+
if (scopes)
|
|
285
|
+
authUrl.searchParams.set("scope", scopes);
|
|
286
|
+
// Extra authorize params
|
|
287
|
+
if (opts.authorizeParams) {
|
|
288
|
+
for (const [k, v] of Object.entries(opts.authorizeParams)) {
|
|
289
|
+
authUrl.searchParams.set(k, v);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
let codeVerifier;
|
|
293
|
+
if (usePkce) {
|
|
294
|
+
codeVerifier = generateCodeVerifier();
|
|
295
|
+
authUrl.searchParams.set("code_challenge", generateCodeChallenge(codeVerifier));
|
|
296
|
+
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
297
|
+
}
|
|
298
|
+
process.stderr.write(`\n OAuth login required. Opening browser...\n ${authUrl.toString()}\n\n`);
|
|
299
|
+
open(authUrl.toString());
|
|
300
|
+
try {
|
|
301
|
+
const code = await server.waitForCode(state);
|
|
302
|
+
const data = await exchangeCode({
|
|
303
|
+
tokenUrl: m.tokenUrl,
|
|
304
|
+
code,
|
|
305
|
+
redirectUri,
|
|
306
|
+
clientId: m.clientId,
|
|
307
|
+
clientSecret: m.clientSecret,
|
|
308
|
+
codeVerifier,
|
|
309
|
+
});
|
|
310
|
+
cached = toCache(data);
|
|
311
|
+
await writeCache(cacheDir, key, cached);
|
|
312
|
+
return cached;
|
|
313
|
+
}
|
|
314
|
+
finally {
|
|
315
|
+
server.close();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/** Serialize token acquisition — concurrent callers share the same promise. */
|
|
319
|
+
function ensureToken(request) {
|
|
320
|
+
// Fast path: valid memory cache, no serialization needed
|
|
321
|
+
if (cached && cached.expiresAt > Date.now() + 30_000) {
|
|
322
|
+
return Promise.resolve(cached);
|
|
323
|
+
}
|
|
324
|
+
// Slow path: serialize so only one browser flow / refresh runs at a time
|
|
325
|
+
if (inflight)
|
|
326
|
+
return inflight;
|
|
327
|
+
inflight = acquireToken(request).finally(() => { inflight = null; });
|
|
328
|
+
return inflight;
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
prefixUrl: opts.prefixUrl,
|
|
332
|
+
headers,
|
|
333
|
+
hooks: {
|
|
334
|
+
beforeRequest: [
|
|
335
|
+
async (request) => {
|
|
336
|
+
const token = await ensureToken(request);
|
|
337
|
+
const h = cleanMarkers(request);
|
|
338
|
+
h.set("Authorization", `Bearer ${token.accessToken}`);
|
|
339
|
+
return rebuildRequest(request, h);
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
afterResponse: [
|
|
343
|
+
async (request, _options, response) => {
|
|
344
|
+
if (response.status !== 401 || !cached?.refreshToken)
|
|
345
|
+
return;
|
|
346
|
+
const m = readMarkers(request);
|
|
347
|
+
const refreshed = await tryRefresh(m.tokenUrl, cached.refreshToken, m.clientId, m.clientSecret);
|
|
348
|
+
if (!refreshed)
|
|
349
|
+
return;
|
|
350
|
+
cached = refreshed;
|
|
351
|
+
const key = cacheKey(m.clientId, m.authorizeUrl, scopes, opts.authorizeParams);
|
|
352
|
+
await writeCache(cacheDir, key, cached);
|
|
353
|
+
// Retry the original request with new token
|
|
354
|
+
const h = cleanMarkers(request);
|
|
355
|
+
h.set("Authorization", `Bearer ${refreshed.accessToken}`);
|
|
356
|
+
const rebuilt = await rebuildRequest(request, h);
|
|
357
|
+
const bodyBuffer = request.body
|
|
358
|
+
? await request.clone().arrayBuffer()
|
|
359
|
+
: null;
|
|
360
|
+
return fetch(rebuilt.url, {
|
|
361
|
+
method: rebuilt.method,
|
|
362
|
+
headers: h,
|
|
363
|
+
body: bodyBuffer,
|
|
364
|
+
redirect: rebuilt.redirect,
|
|
365
|
+
signal: rebuilt.signal,
|
|
366
|
+
...(bodyBuffer ? { duplex: "half" } : {}),
|
|
367
|
+
});
|
|
368
|
+
},
|
|
369
|
+
],
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@glubean/oauth-code",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"import": "./dist/index.js",
|
|
9
|
+
"default": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@glubean/sdk": "0.1.25"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^22.0.0",
|
|
23
|
+
"vitest": "^3.2.1",
|
|
24
|
+
"typescript": "^5.8.3"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/glubean/glubean.git",
|
|
29
|
+
"directory": "packages/oauth-code"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -p tsconfig.build.json",
|
|
33
|
+
"test": "vitest run"
|
|
34
|
+
}
|
|
35
|
+
}
|