@rolino/local-auth 0.5.0 → 0.6.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/CHANGELOG.md +14 -0
- package/README.md +6 -3
- package/dist/index.cjs +135 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -21
- package/dist/index.d.ts +35 -21
- package/dist/index.js +128 -49
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @rolino/local-auth
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a56f834: Replace custom interactive CLI authorization with exact-resource OAuth, add the stateless MCP 2026-07-28 transport foundation, and provide safe connection instructions for workspace discovery and confirmed actions.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [d162f85]
|
|
12
|
+
- Updated dependencies [50cb9fc]
|
|
13
|
+
- Updated dependencies [1ac7ed5]
|
|
14
|
+
- Updated dependencies [a56f834]
|
|
15
|
+
- @rolino/sdk@0.6.0
|
|
16
|
+
|
|
3
17
|
## 0.5.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# `@rolino/local-auth`
|
|
2
2
|
|
|
3
|
-
Node-only
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Node-only OAuth token storage shared by Rolino's CLI and local stdio MCP
|
|
4
|
+
adapter. Token sets are scoped by host, issuer, client, and exact resource.
|
|
5
|
+
The store refreshes short-lived access tokens under one rotation lock and
|
|
6
|
+
writes the owner-only file atomically. Environment API keys still take
|
|
7
|
+
precedence for headless automation. The removed interactive credential is
|
|
8
|
+
never read or converted.
|
|
6
9
|
|
|
7
10
|
This package must not be used by the browser, public SDK, application routes,
|
|
8
11
|
or server domain services. It is published only as a supported transitive
|
package/dist/index.cjs
CHANGED
|
@@ -20,98 +20,179 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
ROLINO_CLI_OAUTH_CLIENT_ID: () => ROLINO_CLI_OAUTH_CLIENT_ID,
|
|
24
|
+
clearOAuthTokenSet: () => clearOAuthTokenSet,
|
|
25
|
+
createOAuthAccessTokenProvider: () => createOAuthAccessTokenProvider,
|
|
26
|
+
loadOAuthTokenSet: () => loadOAuthTokenSet,
|
|
27
|
+
oauthConfiguration: () => oauthConfiguration,
|
|
28
|
+
oauthTokenStorePath: () => oauthTokenStorePath,
|
|
29
|
+
resolveLocalAuthentication: () => resolveLocalAuthentication,
|
|
30
|
+
saveOAuthTokenSet: () => saveOAuthTokenSet
|
|
28
31
|
});
|
|
29
32
|
module.exports = __toCommonJS(index_exports);
|
|
30
33
|
var import_node_fs = require("fs");
|
|
31
34
|
var import_node_os = require("os");
|
|
32
35
|
var import_node_path = require("path");
|
|
33
36
|
var import_sdk = require("@rolino/sdk");
|
|
37
|
+
var ROLINO_CLI_OAUTH_CLIENT_ID = "rolino-cli";
|
|
38
|
+
var refreshLocks = /* @__PURE__ */ new Map();
|
|
34
39
|
function defaultConfigDirectory(env) {
|
|
35
40
|
if (env.ROLINO_CONFIG_DIR) return env.ROLINO_CONFIG_DIR;
|
|
36
|
-
if (process.platform === "win32" && env.APPDATA)
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
if (process.platform === "darwin") {
|
|
40
|
-
return (0, import_node_path.join)((0, import_node_os.homedir)(), "Library", "Application Support", "Rolino");
|
|
41
|
-
}
|
|
41
|
+
if (process.platform === "win32" && env.APPDATA) return (0, import_node_path.join)(env.APPDATA, "Rolino");
|
|
42
|
+
if (process.platform === "darwin") return (0, import_node_path.join)((0, import_node_os.homedir)(), "Library", "Application Support", "Rolino");
|
|
42
43
|
return (0, import_node_path.join)(env.XDG_CONFIG_HOME ?? (0, import_node_path.join)((0, import_node_os.homedir)(), ".config"), "rolino");
|
|
43
44
|
}
|
|
44
|
-
function
|
|
45
|
-
return (0, import_node_path.join)(defaultConfigDirectory(env), "
|
|
45
|
+
function oauthTokenStorePath(env = process.env) {
|
|
46
|
+
return (0, import_node_path.join)(defaultConfigDirectory(env), "oauth-tokens.json");
|
|
47
|
+
}
|
|
48
|
+
function oauthConfiguration(baseUrl) {
|
|
49
|
+
const host = (0, import_sdk.normalizeRolinoBaseUrl)(baseUrl);
|
|
50
|
+
return {
|
|
51
|
+
host,
|
|
52
|
+
issuer: `${host}/api/auth`,
|
|
53
|
+
clientId: ROLINO_CLI_OAUTH_CLIENT_ID,
|
|
54
|
+
resource: `${host}/api/v1`
|
|
55
|
+
};
|
|
46
56
|
}
|
|
47
|
-
function
|
|
57
|
+
function tokenKey(input) {
|
|
58
|
+
return JSON.stringify([input.host, input.issuer, input.clientId, input.resource]);
|
|
59
|
+
}
|
|
60
|
+
function isTokenSet(value) {
|
|
48
61
|
if (!value || typeof value !== "object") return false;
|
|
49
|
-
const
|
|
50
|
-
return
|
|
62
|
+
const token = value;
|
|
63
|
+
return [
|
|
64
|
+
token.host,
|
|
65
|
+
token.issuer,
|
|
66
|
+
token.clientId,
|
|
67
|
+
token.resource,
|
|
68
|
+
token.accessToken,
|
|
69
|
+
token.accessTokenExpiresAt,
|
|
70
|
+
token.refreshToken,
|
|
71
|
+
token.refreshTokenExpiresAt
|
|
72
|
+
].every((item) => typeof item === "string" && item.length > 0) && Array.isArray(token.scopes) && token.scopes.every((scope) => typeof scope === "string");
|
|
51
73
|
}
|
|
52
74
|
function readStore(env) {
|
|
53
|
-
const path =
|
|
54
|
-
if (!(0, import_node_fs.existsSync)(path)) return { version:
|
|
55
|
-
let parsed;
|
|
75
|
+
const path = oauthTokenStorePath(env);
|
|
76
|
+
if (!(0, import_node_fs.existsSync)(path)) return { version: 2, tokens: {} };
|
|
56
77
|
try {
|
|
57
|
-
parsed = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
|
|
78
|
+
const parsed = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
|
|
79
|
+
if (parsed.version !== 2 || !parsed.tokens || typeof parsed.tokens !== "object" || !Object.values(parsed.tokens).every(isTokenSet)) throw new Error("invalid");
|
|
80
|
+
return parsed;
|
|
58
81
|
} catch {
|
|
59
|
-
throw new TypeError(`Rolino could not read its
|
|
60
|
-
}
|
|
61
|
-
if (!parsed || typeof parsed !== "object") {
|
|
62
|
-
throw new TypeError(`Rolino's credential store at ${path} is invalid.`);
|
|
82
|
+
throw new TypeError(`Rolino could not read its OAuth token store at ${path}.`);
|
|
63
83
|
}
|
|
64
|
-
const candidate = parsed;
|
|
65
|
-
if (candidate.version !== 1 || !candidate.credentials || typeof candidate.credentials !== "object" || !Object.values(candidate.credentials).every(isStoredCredential)) {
|
|
66
|
-
throw new TypeError(`Rolino's credential store at ${path} is invalid.`);
|
|
67
|
-
}
|
|
68
|
-
return candidate;
|
|
69
84
|
}
|
|
70
85
|
function writeStore(store, env) {
|
|
71
|
-
const path =
|
|
86
|
+
const path = oauthTokenStorePath(env);
|
|
87
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
72
88
|
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true, mode: 448 });
|
|
73
|
-
(0, import_node_fs.writeFileSync)(
|
|
89
|
+
(0, import_node_fs.writeFileSync)(temporary, `${JSON.stringify(store, null, 2)}
|
|
74
90
|
`, {
|
|
75
91
|
encoding: "utf8",
|
|
76
92
|
mode: 384
|
|
77
93
|
});
|
|
94
|
+
(0, import_node_fs.chmodSync)(temporary, 384);
|
|
95
|
+
(0, import_node_fs.renameSync)(temporary, path);
|
|
78
96
|
(0, import_node_fs.chmodSync)(path, 384);
|
|
97
|
+
return path;
|
|
79
98
|
}
|
|
80
|
-
function
|
|
81
|
-
|
|
99
|
+
function loadOAuthTokenSet(baseUrl, env = process.env) {
|
|
100
|
+
const configuration = oauthConfiguration(baseUrl);
|
|
101
|
+
return readStore(env).tokens[tokenKey(configuration)] ?? null;
|
|
82
102
|
}
|
|
83
|
-
function
|
|
103
|
+
function saveOAuthTokenSet(tokenSet, env = process.env) {
|
|
104
|
+
const configuration = oauthConfiguration(tokenSet.host);
|
|
105
|
+
if (tokenSet.host !== configuration.host || tokenSet.issuer !== configuration.issuer || tokenSet.clientId !== configuration.clientId || tokenSet.resource !== configuration.resource) throw new TypeError("The OAuth token set does not match the Rolino host.");
|
|
84
106
|
const store = readStore(env);
|
|
85
|
-
store.
|
|
86
|
-
writeStore(store, env);
|
|
87
|
-
|
|
107
|
+
store.tokens[tokenKey(tokenSet)] = tokenSet;
|
|
108
|
+
const path = writeStore(store, env);
|
|
109
|
+
const retiredInteractiveCredential = (0, import_node_path.join)(defaultConfigDirectory(env), "credentials.json");
|
|
110
|
+
if ((0, import_node_fs.existsSync)(retiredInteractiveCredential)) (0, import_node_fs.unlinkSync)(retiredInteractiveCredential);
|
|
111
|
+
return path;
|
|
88
112
|
}
|
|
89
|
-
function
|
|
90
|
-
const path =
|
|
113
|
+
function clearOAuthTokenSet(baseUrl, env = process.env) {
|
|
114
|
+
const path = oauthTokenStorePath(env);
|
|
91
115
|
const store = readStore(env);
|
|
92
|
-
const key = (
|
|
93
|
-
const existed = Boolean(store.
|
|
94
|
-
delete store.
|
|
95
|
-
if (Object.keys(store.
|
|
116
|
+
const key = tokenKey(oauthConfiguration(baseUrl));
|
|
117
|
+
const existed = Boolean(store.tokens[key]);
|
|
118
|
+
delete store.tokens[key];
|
|
119
|
+
if (!Object.keys(store.tokens).length) {
|
|
96
120
|
if ((0, import_node_fs.existsSync)(path)) (0, import_node_fs.unlinkSync)(path);
|
|
97
121
|
} else {
|
|
98
122
|
writeStore(store, env);
|
|
99
123
|
}
|
|
100
124
|
return existed;
|
|
101
125
|
}
|
|
102
|
-
function
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
126
|
+
async function refreshOAuthTokenSet(options) {
|
|
127
|
+
const current = loadOAuthTokenSet(options.baseUrl, options.env);
|
|
128
|
+
if (!current) throw new TypeError("OAuth sign-in is required. Run `rolino auth login`.");
|
|
129
|
+
const key = tokenKey(current);
|
|
130
|
+
const existing = refreshLocks.get(key);
|
|
131
|
+
if (existing) return existing;
|
|
132
|
+
const refresh = (async () => {
|
|
133
|
+
const body = new URLSearchParams({
|
|
134
|
+
grant_type: "refresh_token",
|
|
135
|
+
refresh_token: current.refreshToken,
|
|
136
|
+
client_id: current.clientId,
|
|
137
|
+
resource: current.resource
|
|
138
|
+
});
|
|
139
|
+
const response = await options.fetch(`${current.issuer}/oauth2/token`, {
|
|
140
|
+
method: "POST",
|
|
141
|
+
headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
|
|
142
|
+
body
|
|
143
|
+
});
|
|
144
|
+
const payload = await response.json().catch(() => null);
|
|
145
|
+
if (!response.ok || !payload || typeof payload.access_token !== "string") {
|
|
146
|
+
throw new TypeError("OAuth sign-in is required. Run `rolino auth login`.");
|
|
147
|
+
}
|
|
148
|
+
const expiresIn = typeof payload.expires_in === "number" ? payload.expires_in : 300;
|
|
149
|
+
const refreshed = {
|
|
150
|
+
...current,
|
|
151
|
+
accessToken: payload.access_token,
|
|
152
|
+
accessTokenExpiresAt: new Date(Date.now() + expiresIn * 1e3).toISOString(),
|
|
153
|
+
refreshToken: typeof payload.refresh_token === "string" ? payload.refresh_token : current.refreshToken,
|
|
154
|
+
scopes: typeof payload.scope === "string" ? payload.scope.split(/\s+/).filter(Boolean) : current.scopes
|
|
155
|
+
};
|
|
156
|
+
saveOAuthTokenSet(refreshed, options.env);
|
|
157
|
+
return refreshed;
|
|
158
|
+
})().finally(() => refreshLocks.delete(key));
|
|
159
|
+
refreshLocks.set(key, refresh);
|
|
160
|
+
return refresh;
|
|
161
|
+
}
|
|
162
|
+
function createOAuthAccessTokenProvider(options) {
|
|
163
|
+
const env = options.env ?? process.env;
|
|
164
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
165
|
+
const obtain = async (force) => {
|
|
166
|
+
if (env.ROLINO_TOKEN) return env.ROLINO_TOKEN;
|
|
167
|
+
const token = loadOAuthTokenSet(options.baseUrl, env);
|
|
168
|
+
if (!token) return void 0;
|
|
169
|
+
if (!force && Date.parse(token.accessTokenExpiresAt) - Date.now() > 3e4) {
|
|
170
|
+
return token.accessToken;
|
|
171
|
+
}
|
|
172
|
+
return (await refreshOAuthTokenSet({
|
|
173
|
+
baseUrl: options.baseUrl,
|
|
174
|
+
env,
|
|
175
|
+
fetch: fetchImplementation
|
|
176
|
+
})).accessToken;
|
|
177
|
+
};
|
|
178
|
+
const provider = (() => obtain(false));
|
|
179
|
+
provider.refresh = () => obtain(true);
|
|
180
|
+
return provider;
|
|
181
|
+
}
|
|
182
|
+
function resolveLocalAuthentication(baseUrl, env = process.env) {
|
|
183
|
+
if (env.ROLINO_TOKEN) return { source: "environment", tokenSet: null };
|
|
184
|
+
const tokenSet = loadOAuthTokenSet(baseUrl, env);
|
|
185
|
+
return tokenSet ? { source: "oauth", tokenSet } : { source: "none", tokenSet: null };
|
|
108
186
|
}
|
|
109
187
|
// Annotate the CommonJS export names for ESM import in node:
|
|
110
188
|
0 && (module.exports = {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
189
|
+
ROLINO_CLI_OAUTH_CLIENT_ID,
|
|
190
|
+
clearOAuthTokenSet,
|
|
191
|
+
createOAuthAccessTokenProvider,
|
|
192
|
+
loadOAuthTokenSet,
|
|
193
|
+
oauthConfiguration,
|
|
194
|
+
oauthTokenStorePath,
|
|
195
|
+
resolveLocalAuthentication,
|
|
196
|
+
saveOAuthTokenSet
|
|
116
197
|
});
|
|
117
198
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\r\n chmodSync,\r\n existsSync,\r\n mkdirSync,\r\n readFileSync,\r\n unlinkSync,\r\n writeFileSync,\r\n} from \"node:fs\";\r\nimport { homedir } from \"node:os\";\r\nimport { dirname, join } from \"node:path\";\r\n\r\nimport { normalizeRolinoBaseUrl } from \"@rolino/sdk\";\r\n\r\nexport type StoredCredential = {\r\n token: string;\r\n createdAt: string;\r\n expiresAt: string;\r\n organization: { id: string; name: string };\r\n};\r\n\r\ntype CredentialStore = {\r\n version: 1;\r\n credentials: Record<string, StoredCredential>;\r\n};\r\n\r\nfunction defaultConfigDirectory(env: Record<string, string | undefined>) {\r\n if (env.ROLINO_CONFIG_DIR) return env.ROLINO_CONFIG_DIR;\r\n if (process.platform === \"win32\" && env.APPDATA) {\r\n return join(env.APPDATA, \"Rolino\");\r\n }\r\n if (process.platform === \"darwin\") {\r\n return join(homedir(), \"Library\", \"Application Support\", \"Rolino\");\r\n }\r\n return join(env.XDG_CONFIG_HOME ?? join(homedir(), \".config\"), \"rolino\");\r\n}\r\n\r\nexport function credentialStorePath(\r\n env: Record<string, string | undefined> = process.env,\r\n) {\r\n return join(defaultConfigDirectory(env), \"credentials.json\");\r\n}\r\n\r\nfunction isStoredCredential(value: unknown): value is StoredCredential {\r\n if (!value || typeof value !== \"object\") return false;\r\n const credential = value as Partial<StoredCredential>;\r\n return typeof credential.token === \"string\"\r\n && credential.token.length > 0\r\n && typeof credential.createdAt === \"string\"\r\n && typeof credential.expiresAt === \"string\"\r\n && Boolean(credential.organization)\r\n && typeof credential.organization?.id === \"string\"\r\n && typeof credential.organization?.name === \"string\";\r\n}\r\n\r\nfunction readStore(env: Record<string, string | undefined>): CredentialStore {\r\n const path = credentialStorePath(env);\r\n if (!existsSync(path)) return { version: 1, credentials: {} };\r\n\r\n let parsed: unknown;\r\n try {\r\n parsed = JSON.parse(readFileSync(path, \"utf8\"));\r\n } catch {\r\n throw new TypeError(`Rolino could not read its credential store at ${path}.`);\r\n }\r\n if (!parsed || typeof parsed !== \"object\") {\r\n throw new TypeError(`Rolino's credential store at ${path} is invalid.`);\r\n }\r\n const candidate = parsed as Partial<CredentialStore>;\r\n if (\r\n candidate.version !== 1\r\n || !candidate.credentials\r\n || typeof candidate.credentials !== \"object\"\r\n || !Object.values(candidate.credentials).every(isStoredCredential)\r\n ) {\r\n throw new TypeError(`Rolino's credential store at ${path} is invalid.`);\r\n }\r\n return candidate as CredentialStore;\r\n}\r\n\r\nfunction writeStore(\r\n store: CredentialStore,\r\n env: Record<string, string | undefined>,\r\n) {\r\n const path = credentialStorePath(env);\r\n mkdirSync(dirname(path), { recursive: true, mode: 0o700 });\r\n writeFileSync(path, `${JSON.stringify(store, null, 2)}\\n`, {\r\n encoding: \"utf8\",\r\n mode: 0o600,\r\n });\r\n chmodSync(path, 0o600);\r\n}\r\n\r\nexport function loadStoredCredential(\r\n baseUrl: string,\r\n env: Record<string, string | undefined> = process.env,\r\n) {\r\n return readStore(env).credentials[normalizeRolinoBaseUrl(baseUrl)] ?? null;\r\n}\r\n\r\nexport function saveStoredCredential(\r\n baseUrl: string,\r\n credential: StoredCredential,\r\n env: Record<string, string | undefined> = process.env,\r\n) {\r\n const store = readStore(env);\r\n store.credentials[normalizeRolinoBaseUrl(baseUrl)] = credential;\r\n writeStore(store, env);\r\n return credentialStorePath(env);\r\n}\r\n\r\nexport function clearStoredCredential(\r\n baseUrl: string,\r\n env: Record<string, string | undefined> = process.env,\r\n) {\r\n const path = credentialStorePath(env);\r\n const store = readStore(env);\r\n const key = normalizeRolinoBaseUrl(baseUrl);\r\n const existed = Boolean(store.credentials[key]);\r\n delete store.credentials[key];\r\n\r\n if (Object.keys(store.credentials).length === 0) {\r\n if (existsSync(path)) unlinkSync(path);\r\n } else {\r\n writeStore(store, env);\r\n }\r\n return existed;\r\n}\r\n\r\nexport function resolveCredential(\r\n baseUrl: string,\r\n env: Record<string, string | undefined> = process.env,\r\n) {\r\n if (env.ROLINO_TOKEN) {\r\n return { source: \"environment\" as const, token: env.ROLINO_TOKEN };\r\n }\r\n const stored = loadStoredCredential(baseUrl, env);\r\n return stored\r\n ? { source: \"stored\" as const, token: stored.token, credential: stored }\r\n : { source: \"none\" as const, token: null };\r\n}\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n chmodSync,\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nimport { normalizeRolinoBaseUrl } from \"@rolino/sdk\";\n\nexport const ROLINO_CLI_OAUTH_CLIENT_ID = \"rolino-cli\";\n\nexport type OAuthTokenSet = {\n host: string;\n issuer: string;\n clientId: string;\n resource: string;\n accessToken: string;\n accessTokenExpiresAt: string;\n refreshToken: string;\n refreshTokenExpiresAt: string;\n scopes: string[];\n};\n\ntype OAuthTokenStore = {\n version: 2;\n tokens: Record<string, OAuthTokenSet>;\n};\n\ntype RefreshableTokenProvider = (() => Promise<string | undefined>) & {\n refresh: () => Promise<string | undefined>;\n};\n\nconst refreshLocks = new Map<string, Promise<OAuthTokenSet>>();\n\nfunction defaultConfigDirectory(env: Record<string, string | undefined>) {\n if (env.ROLINO_CONFIG_DIR) return env.ROLINO_CONFIG_DIR;\n if (process.platform === \"win32\" && env.APPDATA) return join(env.APPDATA, \"Rolino\");\n if (process.platform === \"darwin\") return join(homedir(), \"Library\", \"Application Support\", \"Rolino\");\n return join(env.XDG_CONFIG_HOME ?? join(homedir(), \".config\"), \"rolino\");\n}\n\nexport function oauthTokenStorePath(\n env: Record<string, string | undefined> = process.env,\n) {\n return join(defaultConfigDirectory(env), \"oauth-tokens.json\");\n}\n\nexport function oauthConfiguration(baseUrl: string) {\n const host = normalizeRolinoBaseUrl(baseUrl);\n return {\n host,\n issuer: `${host}/api/auth`,\n clientId: ROLINO_CLI_OAUTH_CLIENT_ID,\n resource: `${host}/api/v1`,\n };\n}\n\nfunction tokenKey(input: Pick<OAuthTokenSet, \"host\" | \"issuer\" | \"clientId\" | \"resource\">) {\n return JSON.stringify([input.host, input.issuer, input.clientId, input.resource]);\n}\n\nfunction isTokenSet(value: unknown): value is OAuthTokenSet {\n if (!value || typeof value !== \"object\") return false;\n const token = value as Partial<OAuthTokenSet>;\n return [token.host, token.issuer, token.clientId, token.resource, token.accessToken,\n token.accessTokenExpiresAt, token.refreshToken, token.refreshTokenExpiresAt]\n .every((item) => typeof item === \"string\" && item.length > 0)\n && Array.isArray(token.scopes)\n && token.scopes.every((scope) => typeof scope === \"string\");\n}\n\nfunction readStore(env: Record<string, string | undefined>): OAuthTokenStore {\n const path = oauthTokenStorePath(env);\n if (!existsSync(path)) return { version: 2, tokens: {} };\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as Partial<OAuthTokenStore>;\n if (\n parsed.version !== 2\n || !parsed.tokens\n || typeof parsed.tokens !== \"object\"\n || !Object.values(parsed.tokens).every(isTokenSet)\n ) throw new Error(\"invalid\");\n return parsed as OAuthTokenStore;\n } catch {\n throw new TypeError(`Rolino could not read its OAuth token store at ${path}.`);\n }\n}\n\nfunction writeStore(store: OAuthTokenStore, env: Record<string, string | undefined>) {\n const path = oauthTokenStorePath(env);\n const temporary = `${path}.${process.pid}.tmp`;\n mkdirSync(dirname(path), { recursive: true, mode: 0o700 });\n writeFileSync(temporary, `${JSON.stringify(store, null, 2)}\\n`, {\n encoding: \"utf8\",\n mode: 0o600,\n });\n chmodSync(temporary, 0o600);\n renameSync(temporary, path);\n chmodSync(path, 0o600);\n return path;\n}\n\nexport function loadOAuthTokenSet(\n baseUrl: string,\n env: Record<string, string | undefined> = process.env,\n) {\n const configuration = oauthConfiguration(baseUrl);\n return readStore(env).tokens[tokenKey(configuration)] ?? null;\n}\n\nexport function saveOAuthTokenSet(\n tokenSet: OAuthTokenSet,\n env: Record<string, string | undefined> = process.env,\n) {\n const configuration = oauthConfiguration(tokenSet.host);\n if (\n tokenSet.host !== configuration.host\n || tokenSet.issuer !== configuration.issuer\n || tokenSet.clientId !== configuration.clientId\n || tokenSet.resource !== configuration.resource\n ) throw new TypeError(\"The OAuth token set does not match the Rolino host.\");\n const store = readStore(env);\n store.tokens[tokenKey(tokenSet)] = tokenSet;\n const path = writeStore(store, env);\n const retiredInteractiveCredential = join(defaultConfigDirectory(env), \"credentials.json\");\n if (existsSync(retiredInteractiveCredential)) unlinkSync(retiredInteractiveCredential);\n return path;\n}\n\nexport function clearOAuthTokenSet(\n baseUrl: string,\n env: Record<string, string | undefined> = process.env,\n) {\n const path = oauthTokenStorePath(env);\n const store = readStore(env);\n const key = tokenKey(oauthConfiguration(baseUrl));\n const existed = Boolean(store.tokens[key]);\n delete store.tokens[key];\n if (!Object.keys(store.tokens).length) {\n if (existsSync(path)) unlinkSync(path);\n } else {\n writeStore(store, env);\n }\n return existed;\n}\n\nasync function refreshOAuthTokenSet(options: {\n baseUrl: string;\n env: Record<string, string | undefined>;\n fetch: typeof globalThis.fetch;\n}) {\n const current = loadOAuthTokenSet(options.baseUrl, options.env);\n if (!current) throw new TypeError(\"OAuth sign-in is required. Run `rolino auth login`.\");\n const key = tokenKey(current);\n const existing = refreshLocks.get(key);\n if (existing) return existing;\n\n const refresh = (async () => {\n const body = new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: current.refreshToken,\n client_id: current.clientId,\n resource: current.resource,\n });\n const response = await options.fetch(`${current.issuer}/oauth2/token`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\", accept: \"application/json\" },\n body,\n });\n const payload = await response.json().catch(() => null) as Record<string, unknown> | null;\n if (!response.ok || !payload || typeof payload.access_token !== \"string\") {\n throw new TypeError(\"OAuth sign-in is required. Run `rolino auth login`.\");\n }\n const expiresIn = typeof payload.expires_in === \"number\" ? payload.expires_in : 300;\n const refreshed: OAuthTokenSet = {\n ...current,\n accessToken: payload.access_token,\n accessTokenExpiresAt: new Date(Date.now() + expiresIn * 1_000).toISOString(),\n refreshToken: typeof payload.refresh_token === \"string\" ? payload.refresh_token : current.refreshToken,\n scopes: typeof payload.scope === \"string\" ? payload.scope.split(/\\s+/).filter(Boolean) : current.scopes,\n };\n saveOAuthTokenSet(refreshed, options.env);\n return refreshed;\n })().finally(() => refreshLocks.delete(key));\n refreshLocks.set(key, refresh);\n return refresh;\n}\n\nexport function createOAuthAccessTokenProvider(options: {\n baseUrl: string;\n env?: Record<string, string | undefined>;\n fetch?: typeof globalThis.fetch;\n}): RefreshableTokenProvider {\n const env = options.env ?? process.env;\n const fetchImplementation = options.fetch ?? globalThis.fetch;\n const obtain = async (force: boolean) => {\n if (env.ROLINO_TOKEN) return env.ROLINO_TOKEN;\n const token = loadOAuthTokenSet(options.baseUrl, env);\n if (!token) return undefined;\n if (!force && Date.parse(token.accessTokenExpiresAt) - Date.now() > 30_000) {\n return token.accessToken;\n }\n return (await refreshOAuthTokenSet({\n baseUrl: options.baseUrl,\n env,\n fetch: fetchImplementation,\n })).accessToken;\n };\n const provider = (() => obtain(false)) as RefreshableTokenProvider;\n provider.refresh = () => obtain(true);\n return provider;\n}\n\nexport function resolveLocalAuthentication(\n baseUrl: string,\n env: Record<string, string | undefined> = process.env,\n) {\n if (env.ROLINO_TOKEN) return { source: \"environment\" as const, tokenSet: null };\n const tokenSet = loadOAuthTokenSet(baseUrl, env);\n return tokenSet\n ? { source: \"oauth\" as const, tokenSet }\n : { source: \"none\" as const, tokenSet: null };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAQO;AACP,qBAAwB;AACxB,uBAA8B;AAE9B,iBAAuC;AAEhC,IAAM,6BAA6B;AAuB1C,IAAM,eAAe,oBAAI,IAAoC;AAE7D,SAAS,uBAAuB,KAAyC;AACvE,MAAI,IAAI,kBAAmB,QAAO,IAAI;AACtC,MAAI,QAAQ,aAAa,WAAW,IAAI,QAAS,YAAO,uBAAK,IAAI,SAAS,QAAQ;AAClF,MAAI,QAAQ,aAAa,SAAU,YAAO,2BAAK,wBAAQ,GAAG,WAAW,uBAAuB,QAAQ;AACpG,aAAO,uBAAK,IAAI,uBAAmB,2BAAK,wBAAQ,GAAG,SAAS,GAAG,QAAQ;AACzE;AAEO,SAAS,oBACd,MAA0C,QAAQ,KAClD;AACA,aAAO,uBAAK,uBAAuB,GAAG,GAAG,mBAAmB;AAC9D;AAEO,SAAS,mBAAmB,SAAiB;AAClD,QAAM,WAAO,mCAAuB,OAAO;AAC3C,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,GAAG,IAAI;AAAA,IACf,UAAU;AAAA,IACV,UAAU,GAAG,IAAI;AAAA,EACnB;AACF;AAEA,SAAS,SAAS,OAAyE;AACzF,SAAO,KAAK,UAAU,CAAC,MAAM,MAAM,MAAM,QAAQ,MAAM,UAAU,MAAM,QAAQ,CAAC;AAClF;AAEA,SAAS,WAAW,OAAwC;AAC1D,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,QAAQ;AACd,SAAO;AAAA,IAAC,MAAM;AAAA,IAAM,MAAM;AAAA,IAAQ,MAAM;AAAA,IAAU,MAAM;AAAA,IAAU,MAAM;AAAA,IACtE,MAAM;AAAA,IAAsB,MAAM;AAAA,IAAc,MAAM;AAAA,EAAqB,EAC1E,MAAM,CAAC,SAAS,OAAO,SAAS,YAAY,KAAK,SAAS,CAAC,KACzD,MAAM,QAAQ,MAAM,MAAM,KAC1B,MAAM,OAAO,MAAM,CAAC,UAAU,OAAO,UAAU,QAAQ;AAC9D;AAEA,SAAS,UAAU,KAA0D;AAC3E,QAAM,OAAO,oBAAoB,GAAG;AACpC,MAAI,KAAC,2BAAW,IAAI,EAAG,QAAO,EAAE,SAAS,GAAG,QAAQ,CAAC,EAAE;AACvD,MAAI;AACF,UAAM,SAAS,KAAK,UAAM,6BAAa,MAAM,MAAM,CAAC;AACpD,QACE,OAAO,YAAY,KAChB,CAAC,OAAO,UACR,OAAO,OAAO,WAAW,YACzB,CAAC,OAAO,OAAO,OAAO,MAAM,EAAE,MAAM,UAAU,EACjD,OAAM,IAAI,MAAM,SAAS;AAC3B,WAAO;AAAA,EACT,QAAQ;AACN,UAAM,IAAI,UAAU,kDAAkD,IAAI,GAAG;AAAA,EAC/E;AACF;AAEA,SAAS,WAAW,OAAwB,KAAyC;AACnF,QAAM,OAAO,oBAAoB,GAAG;AACpC,QAAM,YAAY,GAAG,IAAI,IAAI,QAAQ,GAAG;AACxC,oCAAU,0BAAQ,IAAI,GAAG,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AACzD,oCAAc,WAAW,GAAG,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAAA,GAAM;AAAA,IAC9D,UAAU;AAAA,IACV,MAAM;AAAA,EACR,CAAC;AACD,gCAAU,WAAW,GAAK;AAC1B,iCAAW,WAAW,IAAI;AAC1B,gCAAU,MAAM,GAAK;AACrB,SAAO;AACT;AAEO,SAAS,kBACd,SACA,MAA0C,QAAQ,KAClD;AACA,QAAM,gBAAgB,mBAAmB,OAAO;AAChD,SAAO,UAAU,GAAG,EAAE,OAAO,SAAS,aAAa,CAAC,KAAK;AAC3D;AAEO,SAAS,kBACd,UACA,MAA0C,QAAQ,KAClD;AACA,QAAM,gBAAgB,mBAAmB,SAAS,IAAI;AACtD,MACE,SAAS,SAAS,cAAc,QAC7B,SAAS,WAAW,cAAc,UAClC,SAAS,aAAa,cAAc,YACpC,SAAS,aAAa,cAAc,SACvC,OAAM,IAAI,UAAU,qDAAqD;AAC3E,QAAM,QAAQ,UAAU,GAAG;AAC3B,QAAM,OAAO,SAAS,QAAQ,CAAC,IAAI;AACnC,QAAM,OAAO,WAAW,OAAO,GAAG;AAClC,QAAM,mCAA+B,uBAAK,uBAAuB,GAAG,GAAG,kBAAkB;AACzF,UAAI,2BAAW,4BAA4B,EAAG,gCAAW,4BAA4B;AACrF,SAAO;AACT;AAEO,SAAS,mBACd,SACA,MAA0C,QAAQ,KAClD;AACA,QAAM,OAAO,oBAAoB,GAAG;AACpC,QAAM,QAAQ,UAAU,GAAG;AAC3B,QAAM,MAAM,SAAS,mBAAmB,OAAO,CAAC;AAChD,QAAM,UAAU,QAAQ,MAAM,OAAO,GAAG,CAAC;AACzC,SAAO,MAAM,OAAO,GAAG;AACvB,MAAI,CAAC,OAAO,KAAK,MAAM,MAAM,EAAE,QAAQ;AACrC,YAAI,2BAAW,IAAI,EAAG,gCAAW,IAAI;AAAA,EACvC,OAAO;AACL,eAAW,OAAO,GAAG;AAAA,EACvB;AACA,SAAO;AACT;AAEA,eAAe,qBAAqB,SAIjC;AACD,QAAM,UAAU,kBAAkB,QAAQ,SAAS,QAAQ,GAAG;AAC9D,MAAI,CAAC,QAAS,OAAM,IAAI,UAAU,qDAAqD;AACvF,QAAM,MAAM,SAAS,OAAO;AAC5B,QAAM,WAAW,aAAa,IAAI,GAAG;AACrC,MAAI,SAAU,QAAO;AAErB,QAAM,WAAW,YAAY;AAC3B,UAAM,OAAO,IAAI,gBAAgB;AAAA,MAC/B,YAAY;AAAA,MACZ,eAAe,QAAQ;AAAA,MACvB,WAAW,QAAQ;AAAA,MACnB,UAAU,QAAQ;AAAA,IACpB,CAAC;AACD,UAAM,WAAW,MAAM,QAAQ,MAAM,GAAG,QAAQ,MAAM,iBAAiB;AAAA,MACrE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,qCAAqC,QAAQ,mBAAmB;AAAA,MAC3F;AAAA,IACF,CAAC;AACD,UAAM,UAAU,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,IAAI;AACtD,QAAI,CAAC,SAAS,MAAM,CAAC,WAAW,OAAO,QAAQ,iBAAiB,UAAU;AACxE,YAAM,IAAI,UAAU,qDAAqD;AAAA,IAC3E;AACA,UAAM,YAAY,OAAO,QAAQ,eAAe,WAAW,QAAQ,aAAa;AAChF,UAAM,YAA2B;AAAA,MAC/B,GAAG;AAAA,MACH,aAAa,QAAQ;AAAA,MACrB,sBAAsB,IAAI,KAAK,KAAK,IAAI,IAAI,YAAY,GAAK,EAAE,YAAY;AAAA,MAC3E,cAAc,OAAO,QAAQ,kBAAkB,WAAW,QAAQ,gBAAgB,QAAQ;AAAA,MAC1F,QAAQ,OAAO,QAAQ,UAAU,WAAW,QAAQ,MAAM,MAAM,KAAK,EAAE,OAAO,OAAO,IAAI,QAAQ;AAAA,IACnG;AACA,sBAAkB,WAAW,QAAQ,GAAG;AACxC,WAAO;AAAA,EACT,GAAG,EAAE,QAAQ,MAAM,aAAa,OAAO,GAAG,CAAC;AAC3C,eAAa,IAAI,KAAK,OAAO;AAC7B,SAAO;AACT;AAEO,SAAS,+BAA+B,SAIlB;AAC3B,QAAM,MAAM,QAAQ,OAAO,QAAQ;AACnC,QAAM,sBAAsB,QAAQ,SAAS,WAAW;AACxD,QAAM,SAAS,OAAO,UAAmB;AACvC,QAAI,IAAI,aAAc,QAAO,IAAI;AACjC,UAAM,QAAQ,kBAAkB,QAAQ,SAAS,GAAG;AACpD,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI,CAAC,SAAS,KAAK,MAAM,MAAM,oBAAoB,IAAI,KAAK,IAAI,IAAI,KAAQ;AAC1E,aAAO,MAAM;AAAA,IACf;AACA,YAAQ,MAAM,qBAAqB;AAAA,MACjC,SAAS,QAAQ;AAAA,MACjB;AAAA,MACA,OAAO;AAAA,IACT,CAAC,GAAG;AAAA,EACN;AACA,QAAM,YAAY,MAAM,OAAO,KAAK;AACpC,WAAS,UAAU,MAAM,OAAO,IAAI;AACpC,SAAO;AACT;AAEO,SAAS,2BACd,SACA,MAA0C,QAAQ,KAClD;AACA,MAAI,IAAI,aAAc,QAAO,EAAE,QAAQ,eAAwB,UAAU,KAAK;AAC9E,QAAM,WAAW,kBAAkB,SAAS,GAAG;AAC/C,SAAO,WACH,EAAE,QAAQ,SAAkB,SAAS,IACrC,EAAE,QAAQ,QAAiB,UAAU,KAAK;AAChD;","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,28 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
declare const ROLINO_CLI_OAUTH_CLIENT_ID = "rolino-cli";
|
|
2
|
+
type OAuthTokenSet = {
|
|
3
|
+
host: string;
|
|
4
|
+
issuer: string;
|
|
5
|
+
clientId: string;
|
|
6
|
+
resource: string;
|
|
7
|
+
accessToken: string;
|
|
8
|
+
accessTokenExpiresAt: string;
|
|
9
|
+
refreshToken: string;
|
|
10
|
+
refreshTokenExpiresAt: string;
|
|
11
|
+
scopes: string[];
|
|
9
12
|
};
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
declare function
|
|
14
|
-
declare function
|
|
13
|
+
type RefreshableTokenProvider = (() => Promise<string | undefined>) & {
|
|
14
|
+
refresh: () => Promise<string | undefined>;
|
|
15
|
+
};
|
|
16
|
+
declare function oauthTokenStorePath(env?: Record<string, string | undefined>): string;
|
|
17
|
+
declare function oauthConfiguration(baseUrl: string): {
|
|
18
|
+
host: string;
|
|
19
|
+
issuer: string;
|
|
20
|
+
clientId: string;
|
|
21
|
+
resource: string;
|
|
22
|
+
};
|
|
23
|
+
declare function loadOAuthTokenSet(baseUrl: string, env?: Record<string, string | undefined>): OAuthTokenSet | null;
|
|
24
|
+
declare function saveOAuthTokenSet(tokenSet: OAuthTokenSet, env?: Record<string, string | undefined>): string;
|
|
25
|
+
declare function clearOAuthTokenSet(baseUrl: string, env?: Record<string, string | undefined>): boolean;
|
|
26
|
+
declare function createOAuthAccessTokenProvider(options: {
|
|
27
|
+
baseUrl: string;
|
|
28
|
+
env?: Record<string, string | undefined>;
|
|
29
|
+
fetch?: typeof globalThis.fetch;
|
|
30
|
+
}): RefreshableTokenProvider;
|
|
31
|
+
declare function resolveLocalAuthentication(baseUrl: string, env?: Record<string, string | undefined>): {
|
|
15
32
|
source: "environment";
|
|
16
|
-
|
|
17
|
-
credential?: undefined;
|
|
33
|
+
tokenSet: null;
|
|
18
34
|
} | {
|
|
19
|
-
source: "
|
|
20
|
-
|
|
21
|
-
credential: StoredCredential;
|
|
35
|
+
source: "oauth";
|
|
36
|
+
tokenSet: OAuthTokenSet;
|
|
22
37
|
} | {
|
|
23
38
|
source: "none";
|
|
24
|
-
|
|
25
|
-
credential?: undefined;
|
|
39
|
+
tokenSet: null;
|
|
26
40
|
};
|
|
27
41
|
|
|
28
|
-
export { type
|
|
42
|
+
export { type OAuthTokenSet, ROLINO_CLI_OAUTH_CLIENT_ID, clearOAuthTokenSet, createOAuthAccessTokenProvider, loadOAuthTokenSet, oauthConfiguration, oauthTokenStorePath, resolveLocalAuthentication, saveOAuthTokenSet };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,28 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
declare const ROLINO_CLI_OAUTH_CLIENT_ID = "rolino-cli";
|
|
2
|
+
type OAuthTokenSet = {
|
|
3
|
+
host: string;
|
|
4
|
+
issuer: string;
|
|
5
|
+
clientId: string;
|
|
6
|
+
resource: string;
|
|
7
|
+
accessToken: string;
|
|
8
|
+
accessTokenExpiresAt: string;
|
|
9
|
+
refreshToken: string;
|
|
10
|
+
refreshTokenExpiresAt: string;
|
|
11
|
+
scopes: string[];
|
|
9
12
|
};
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
declare function
|
|
14
|
-
declare function
|
|
13
|
+
type RefreshableTokenProvider = (() => Promise<string | undefined>) & {
|
|
14
|
+
refresh: () => Promise<string | undefined>;
|
|
15
|
+
};
|
|
16
|
+
declare function oauthTokenStorePath(env?: Record<string, string | undefined>): string;
|
|
17
|
+
declare function oauthConfiguration(baseUrl: string): {
|
|
18
|
+
host: string;
|
|
19
|
+
issuer: string;
|
|
20
|
+
clientId: string;
|
|
21
|
+
resource: string;
|
|
22
|
+
};
|
|
23
|
+
declare function loadOAuthTokenSet(baseUrl: string, env?: Record<string, string | undefined>): OAuthTokenSet | null;
|
|
24
|
+
declare function saveOAuthTokenSet(tokenSet: OAuthTokenSet, env?: Record<string, string | undefined>): string;
|
|
25
|
+
declare function clearOAuthTokenSet(baseUrl: string, env?: Record<string, string | undefined>): boolean;
|
|
26
|
+
declare function createOAuthAccessTokenProvider(options: {
|
|
27
|
+
baseUrl: string;
|
|
28
|
+
env?: Record<string, string | undefined>;
|
|
29
|
+
fetch?: typeof globalThis.fetch;
|
|
30
|
+
}): RefreshableTokenProvider;
|
|
31
|
+
declare function resolveLocalAuthentication(baseUrl: string, env?: Record<string, string | undefined>): {
|
|
15
32
|
source: "environment";
|
|
16
|
-
|
|
17
|
-
credential?: undefined;
|
|
33
|
+
tokenSet: null;
|
|
18
34
|
} | {
|
|
19
|
-
source: "
|
|
20
|
-
|
|
21
|
-
credential: StoredCredential;
|
|
35
|
+
source: "oauth";
|
|
36
|
+
tokenSet: OAuthTokenSet;
|
|
22
37
|
} | {
|
|
23
38
|
source: "none";
|
|
24
|
-
|
|
25
|
-
credential?: undefined;
|
|
39
|
+
tokenSet: null;
|
|
26
40
|
};
|
|
27
41
|
|
|
28
|
-
export { type
|
|
42
|
+
export { type OAuthTokenSet, ROLINO_CLI_OAUTH_CLIENT_ID, clearOAuthTokenSet, createOAuthAccessTokenProvider, loadOAuthTokenSet, oauthConfiguration, oauthTokenStorePath, resolveLocalAuthentication, saveOAuthTokenSet };
|
package/dist/index.js
CHANGED
|
@@ -4,92 +4,171 @@ import {
|
|
|
4
4
|
existsSync,
|
|
5
5
|
mkdirSync,
|
|
6
6
|
readFileSync,
|
|
7
|
+
renameSync,
|
|
7
8
|
unlinkSync,
|
|
8
9
|
writeFileSync
|
|
9
10
|
} from "fs";
|
|
10
11
|
import { homedir } from "os";
|
|
11
12
|
import { dirname, join } from "path";
|
|
12
13
|
import { normalizeRolinoBaseUrl } from "@rolino/sdk";
|
|
14
|
+
var ROLINO_CLI_OAUTH_CLIENT_ID = "rolino-cli";
|
|
15
|
+
var refreshLocks = /* @__PURE__ */ new Map();
|
|
13
16
|
function defaultConfigDirectory(env) {
|
|
14
17
|
if (env.ROLINO_CONFIG_DIR) return env.ROLINO_CONFIG_DIR;
|
|
15
|
-
if (process.platform === "win32" && env.APPDATA)
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
if (process.platform === "darwin") {
|
|
19
|
-
return join(homedir(), "Library", "Application Support", "Rolino");
|
|
20
|
-
}
|
|
18
|
+
if (process.platform === "win32" && env.APPDATA) return join(env.APPDATA, "Rolino");
|
|
19
|
+
if (process.platform === "darwin") return join(homedir(), "Library", "Application Support", "Rolino");
|
|
21
20
|
return join(env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "rolino");
|
|
22
21
|
}
|
|
23
|
-
function
|
|
24
|
-
return join(defaultConfigDirectory(env), "
|
|
22
|
+
function oauthTokenStorePath(env = process.env) {
|
|
23
|
+
return join(defaultConfigDirectory(env), "oauth-tokens.json");
|
|
24
|
+
}
|
|
25
|
+
function oauthConfiguration(baseUrl) {
|
|
26
|
+
const host = normalizeRolinoBaseUrl(baseUrl);
|
|
27
|
+
return {
|
|
28
|
+
host,
|
|
29
|
+
issuer: `${host}/api/auth`,
|
|
30
|
+
clientId: ROLINO_CLI_OAUTH_CLIENT_ID,
|
|
31
|
+
resource: `${host}/api/v1`
|
|
32
|
+
};
|
|
25
33
|
}
|
|
26
|
-
function
|
|
34
|
+
function tokenKey(input) {
|
|
35
|
+
return JSON.stringify([input.host, input.issuer, input.clientId, input.resource]);
|
|
36
|
+
}
|
|
37
|
+
function isTokenSet(value) {
|
|
27
38
|
if (!value || typeof value !== "object") return false;
|
|
28
|
-
const
|
|
29
|
-
return
|
|
39
|
+
const token = value;
|
|
40
|
+
return [
|
|
41
|
+
token.host,
|
|
42
|
+
token.issuer,
|
|
43
|
+
token.clientId,
|
|
44
|
+
token.resource,
|
|
45
|
+
token.accessToken,
|
|
46
|
+
token.accessTokenExpiresAt,
|
|
47
|
+
token.refreshToken,
|
|
48
|
+
token.refreshTokenExpiresAt
|
|
49
|
+
].every((item) => typeof item === "string" && item.length > 0) && Array.isArray(token.scopes) && token.scopes.every((scope) => typeof scope === "string");
|
|
30
50
|
}
|
|
31
51
|
function readStore(env) {
|
|
32
|
-
const path =
|
|
33
|
-
if (!existsSync(path)) return { version:
|
|
34
|
-
let parsed;
|
|
52
|
+
const path = oauthTokenStorePath(env);
|
|
53
|
+
if (!existsSync(path)) return { version: 2, tokens: {} };
|
|
35
54
|
try {
|
|
36
|
-
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
55
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
56
|
+
if (parsed.version !== 2 || !parsed.tokens || typeof parsed.tokens !== "object" || !Object.values(parsed.tokens).every(isTokenSet)) throw new Error("invalid");
|
|
57
|
+
return parsed;
|
|
37
58
|
} catch {
|
|
38
|
-
throw new TypeError(`Rolino could not read its
|
|
39
|
-
}
|
|
40
|
-
if (!parsed || typeof parsed !== "object") {
|
|
41
|
-
throw new TypeError(`Rolino's credential store at ${path} is invalid.`);
|
|
59
|
+
throw new TypeError(`Rolino could not read its OAuth token store at ${path}.`);
|
|
42
60
|
}
|
|
43
|
-
const candidate = parsed;
|
|
44
|
-
if (candidate.version !== 1 || !candidate.credentials || typeof candidate.credentials !== "object" || !Object.values(candidate.credentials).every(isStoredCredential)) {
|
|
45
|
-
throw new TypeError(`Rolino's credential store at ${path} is invalid.`);
|
|
46
|
-
}
|
|
47
|
-
return candidate;
|
|
48
61
|
}
|
|
49
62
|
function writeStore(store, env) {
|
|
50
|
-
const path =
|
|
63
|
+
const path = oauthTokenStorePath(env);
|
|
64
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
51
65
|
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
52
|
-
writeFileSync(
|
|
66
|
+
writeFileSync(temporary, `${JSON.stringify(store, null, 2)}
|
|
53
67
|
`, {
|
|
54
68
|
encoding: "utf8",
|
|
55
69
|
mode: 384
|
|
56
70
|
});
|
|
71
|
+
chmodSync(temporary, 384);
|
|
72
|
+
renameSync(temporary, path);
|
|
57
73
|
chmodSync(path, 384);
|
|
74
|
+
return path;
|
|
58
75
|
}
|
|
59
|
-
function
|
|
60
|
-
|
|
76
|
+
function loadOAuthTokenSet(baseUrl, env = process.env) {
|
|
77
|
+
const configuration = oauthConfiguration(baseUrl);
|
|
78
|
+
return readStore(env).tokens[tokenKey(configuration)] ?? null;
|
|
61
79
|
}
|
|
62
|
-
function
|
|
80
|
+
function saveOAuthTokenSet(tokenSet, env = process.env) {
|
|
81
|
+
const configuration = oauthConfiguration(tokenSet.host);
|
|
82
|
+
if (tokenSet.host !== configuration.host || tokenSet.issuer !== configuration.issuer || tokenSet.clientId !== configuration.clientId || tokenSet.resource !== configuration.resource) throw new TypeError("The OAuth token set does not match the Rolino host.");
|
|
63
83
|
const store = readStore(env);
|
|
64
|
-
store.
|
|
65
|
-
writeStore(store, env);
|
|
66
|
-
|
|
84
|
+
store.tokens[tokenKey(tokenSet)] = tokenSet;
|
|
85
|
+
const path = writeStore(store, env);
|
|
86
|
+
const retiredInteractiveCredential = join(defaultConfigDirectory(env), "credentials.json");
|
|
87
|
+
if (existsSync(retiredInteractiveCredential)) unlinkSync(retiredInteractiveCredential);
|
|
88
|
+
return path;
|
|
67
89
|
}
|
|
68
|
-
function
|
|
69
|
-
const path =
|
|
90
|
+
function clearOAuthTokenSet(baseUrl, env = process.env) {
|
|
91
|
+
const path = oauthTokenStorePath(env);
|
|
70
92
|
const store = readStore(env);
|
|
71
|
-
const key =
|
|
72
|
-
const existed = Boolean(store.
|
|
73
|
-
delete store.
|
|
74
|
-
if (Object.keys(store.
|
|
93
|
+
const key = tokenKey(oauthConfiguration(baseUrl));
|
|
94
|
+
const existed = Boolean(store.tokens[key]);
|
|
95
|
+
delete store.tokens[key];
|
|
96
|
+
if (!Object.keys(store.tokens).length) {
|
|
75
97
|
if (existsSync(path)) unlinkSync(path);
|
|
76
98
|
} else {
|
|
77
99
|
writeStore(store, env);
|
|
78
100
|
}
|
|
79
101
|
return existed;
|
|
80
102
|
}
|
|
81
|
-
function
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
103
|
+
async function refreshOAuthTokenSet(options) {
|
|
104
|
+
const current = loadOAuthTokenSet(options.baseUrl, options.env);
|
|
105
|
+
if (!current) throw new TypeError("OAuth sign-in is required. Run `rolino auth login`.");
|
|
106
|
+
const key = tokenKey(current);
|
|
107
|
+
const existing = refreshLocks.get(key);
|
|
108
|
+
if (existing) return existing;
|
|
109
|
+
const refresh = (async () => {
|
|
110
|
+
const body = new URLSearchParams({
|
|
111
|
+
grant_type: "refresh_token",
|
|
112
|
+
refresh_token: current.refreshToken,
|
|
113
|
+
client_id: current.clientId,
|
|
114
|
+
resource: current.resource
|
|
115
|
+
});
|
|
116
|
+
const response = await options.fetch(`${current.issuer}/oauth2/token`, {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
|
|
119
|
+
body
|
|
120
|
+
});
|
|
121
|
+
const payload = await response.json().catch(() => null);
|
|
122
|
+
if (!response.ok || !payload || typeof payload.access_token !== "string") {
|
|
123
|
+
throw new TypeError("OAuth sign-in is required. Run `rolino auth login`.");
|
|
124
|
+
}
|
|
125
|
+
const expiresIn = typeof payload.expires_in === "number" ? payload.expires_in : 300;
|
|
126
|
+
const refreshed = {
|
|
127
|
+
...current,
|
|
128
|
+
accessToken: payload.access_token,
|
|
129
|
+
accessTokenExpiresAt: new Date(Date.now() + expiresIn * 1e3).toISOString(),
|
|
130
|
+
refreshToken: typeof payload.refresh_token === "string" ? payload.refresh_token : current.refreshToken,
|
|
131
|
+
scopes: typeof payload.scope === "string" ? payload.scope.split(/\s+/).filter(Boolean) : current.scopes
|
|
132
|
+
};
|
|
133
|
+
saveOAuthTokenSet(refreshed, options.env);
|
|
134
|
+
return refreshed;
|
|
135
|
+
})().finally(() => refreshLocks.delete(key));
|
|
136
|
+
refreshLocks.set(key, refresh);
|
|
137
|
+
return refresh;
|
|
138
|
+
}
|
|
139
|
+
function createOAuthAccessTokenProvider(options) {
|
|
140
|
+
const env = options.env ?? process.env;
|
|
141
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
142
|
+
const obtain = async (force) => {
|
|
143
|
+
if (env.ROLINO_TOKEN) return env.ROLINO_TOKEN;
|
|
144
|
+
const token = loadOAuthTokenSet(options.baseUrl, env);
|
|
145
|
+
if (!token) return void 0;
|
|
146
|
+
if (!force && Date.parse(token.accessTokenExpiresAt) - Date.now() > 3e4) {
|
|
147
|
+
return token.accessToken;
|
|
148
|
+
}
|
|
149
|
+
return (await refreshOAuthTokenSet({
|
|
150
|
+
baseUrl: options.baseUrl,
|
|
151
|
+
env,
|
|
152
|
+
fetch: fetchImplementation
|
|
153
|
+
})).accessToken;
|
|
154
|
+
};
|
|
155
|
+
const provider = (() => obtain(false));
|
|
156
|
+
provider.refresh = () => obtain(true);
|
|
157
|
+
return provider;
|
|
158
|
+
}
|
|
159
|
+
function resolveLocalAuthentication(baseUrl, env = process.env) {
|
|
160
|
+
if (env.ROLINO_TOKEN) return { source: "environment", tokenSet: null };
|
|
161
|
+
const tokenSet = loadOAuthTokenSet(baseUrl, env);
|
|
162
|
+
return tokenSet ? { source: "oauth", tokenSet } : { source: "none", tokenSet: null };
|
|
87
163
|
}
|
|
88
164
|
export {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
165
|
+
ROLINO_CLI_OAUTH_CLIENT_ID,
|
|
166
|
+
clearOAuthTokenSet,
|
|
167
|
+
createOAuthAccessTokenProvider,
|
|
168
|
+
loadOAuthTokenSet,
|
|
169
|
+
oauthConfiguration,
|
|
170
|
+
oauthTokenStorePath,
|
|
171
|
+
resolveLocalAuthentication,
|
|
172
|
+
saveOAuthTokenSet
|
|
94
173
|
};
|
|
95
174
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\r\n chmodSync,\r\n existsSync,\r\n mkdirSync,\r\n readFileSync,\r\n unlinkSync,\r\n writeFileSync,\r\n} from \"node:fs\";\r\nimport { homedir } from \"node:os\";\r\nimport { dirname, join } from \"node:path\";\r\n\r\nimport { normalizeRolinoBaseUrl } from \"@rolino/sdk\";\r\n\r\nexport type StoredCredential = {\r\n token: string;\r\n createdAt: string;\r\n expiresAt: string;\r\n organization: { id: string; name: string };\r\n};\r\n\r\ntype CredentialStore = {\r\n version: 1;\r\n credentials: Record<string, StoredCredential>;\r\n};\r\n\r\nfunction defaultConfigDirectory(env: Record<string, string | undefined>) {\r\n if (env.ROLINO_CONFIG_DIR) return env.ROLINO_CONFIG_DIR;\r\n if (process.platform === \"win32\" && env.APPDATA) {\r\n return join(env.APPDATA, \"Rolino\");\r\n }\r\n if (process.platform === \"darwin\") {\r\n return join(homedir(), \"Library\", \"Application Support\", \"Rolino\");\r\n }\r\n return join(env.XDG_CONFIG_HOME ?? join(homedir(), \".config\"), \"rolino\");\r\n}\r\n\r\nexport function credentialStorePath(\r\n env: Record<string, string | undefined> = process.env,\r\n) {\r\n return join(defaultConfigDirectory(env), \"credentials.json\");\r\n}\r\n\r\nfunction isStoredCredential(value: unknown): value is StoredCredential {\r\n if (!value || typeof value !== \"object\") return false;\r\n const credential = value as Partial<StoredCredential>;\r\n return typeof credential.token === \"string\"\r\n && credential.token.length > 0\r\n && typeof credential.createdAt === \"string\"\r\n && typeof credential.expiresAt === \"string\"\r\n && Boolean(credential.organization)\r\n && typeof credential.organization?.id === \"string\"\r\n && typeof credential.organization?.name === \"string\";\r\n}\r\n\r\nfunction readStore(env: Record<string, string | undefined>): CredentialStore {\r\n const path = credentialStorePath(env);\r\n if (!existsSync(path)) return { version: 1, credentials: {} };\r\n\r\n let parsed: unknown;\r\n try {\r\n parsed = JSON.parse(readFileSync(path, \"utf8\"));\r\n } catch {\r\n throw new TypeError(`Rolino could not read its credential store at ${path}.`);\r\n }\r\n if (!parsed || typeof parsed !== \"object\") {\r\n throw new TypeError(`Rolino's credential store at ${path} is invalid.`);\r\n }\r\n const candidate = parsed as Partial<CredentialStore>;\r\n if (\r\n candidate.version !== 1\r\n || !candidate.credentials\r\n || typeof candidate.credentials !== \"object\"\r\n || !Object.values(candidate.credentials).every(isStoredCredential)\r\n ) {\r\n throw new TypeError(`Rolino's credential store at ${path} is invalid.`);\r\n }\r\n return candidate as CredentialStore;\r\n}\r\n\r\nfunction writeStore(\r\n store: CredentialStore,\r\n env: Record<string, string | undefined>,\r\n) {\r\n const path = credentialStorePath(env);\r\n mkdirSync(dirname(path), { recursive: true, mode: 0o700 });\r\n writeFileSync(path, `${JSON.stringify(store, null, 2)}\\n`, {\r\n encoding: \"utf8\",\r\n mode: 0o600,\r\n });\r\n chmodSync(path, 0o600);\r\n}\r\n\r\nexport function loadStoredCredential(\r\n baseUrl: string,\r\n env: Record<string, string | undefined> = process.env,\r\n) {\r\n return readStore(env).credentials[normalizeRolinoBaseUrl(baseUrl)] ?? null;\r\n}\r\n\r\nexport function saveStoredCredential(\r\n baseUrl: string,\r\n credential: StoredCredential,\r\n env: Record<string, string | undefined> = process.env,\r\n) {\r\n const store = readStore(env);\r\n store.credentials[normalizeRolinoBaseUrl(baseUrl)] = credential;\r\n writeStore(store, env);\r\n return credentialStorePath(env);\r\n}\r\n\r\nexport function clearStoredCredential(\r\n baseUrl: string,\r\n env: Record<string, string | undefined> = process.env,\r\n) {\r\n const path = credentialStorePath(env);\r\n const store = readStore(env);\r\n const key = normalizeRolinoBaseUrl(baseUrl);\r\n const existed = Boolean(store.credentials[key]);\r\n delete store.credentials[key];\r\n\r\n if (Object.keys(store.credentials).length === 0) {\r\n if (existsSync(path)) unlinkSync(path);\r\n } else {\r\n writeStore(store, env);\r\n }\r\n return existed;\r\n}\r\n\r\nexport function resolveCredential(\r\n baseUrl: string,\r\n env: Record<string, string | undefined> = process.env,\r\n) {\r\n if (env.ROLINO_TOKEN) {\r\n return { source: \"environment\" as const, token: env.ROLINO_TOKEN };\r\n }\r\n const stored = loadStoredCredential(baseUrl, env);\r\n return stored\r\n ? { source: \"stored\" as const, token: stored.token, credential: stored }\r\n : { source: \"none\" as const, token: null };\r\n}\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n chmodSync,\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nimport { normalizeRolinoBaseUrl } from \"@rolino/sdk\";\n\nexport const ROLINO_CLI_OAUTH_CLIENT_ID = \"rolino-cli\";\n\nexport type OAuthTokenSet = {\n host: string;\n issuer: string;\n clientId: string;\n resource: string;\n accessToken: string;\n accessTokenExpiresAt: string;\n refreshToken: string;\n refreshTokenExpiresAt: string;\n scopes: string[];\n};\n\ntype OAuthTokenStore = {\n version: 2;\n tokens: Record<string, OAuthTokenSet>;\n};\n\ntype RefreshableTokenProvider = (() => Promise<string | undefined>) & {\n refresh: () => Promise<string | undefined>;\n};\n\nconst refreshLocks = new Map<string, Promise<OAuthTokenSet>>();\n\nfunction defaultConfigDirectory(env: Record<string, string | undefined>) {\n if (env.ROLINO_CONFIG_DIR) return env.ROLINO_CONFIG_DIR;\n if (process.platform === \"win32\" && env.APPDATA) return join(env.APPDATA, \"Rolino\");\n if (process.platform === \"darwin\") return join(homedir(), \"Library\", \"Application Support\", \"Rolino\");\n return join(env.XDG_CONFIG_HOME ?? join(homedir(), \".config\"), \"rolino\");\n}\n\nexport function oauthTokenStorePath(\n env: Record<string, string | undefined> = process.env,\n) {\n return join(defaultConfigDirectory(env), \"oauth-tokens.json\");\n}\n\nexport function oauthConfiguration(baseUrl: string) {\n const host = normalizeRolinoBaseUrl(baseUrl);\n return {\n host,\n issuer: `${host}/api/auth`,\n clientId: ROLINO_CLI_OAUTH_CLIENT_ID,\n resource: `${host}/api/v1`,\n };\n}\n\nfunction tokenKey(input: Pick<OAuthTokenSet, \"host\" | \"issuer\" | \"clientId\" | \"resource\">) {\n return JSON.stringify([input.host, input.issuer, input.clientId, input.resource]);\n}\n\nfunction isTokenSet(value: unknown): value is OAuthTokenSet {\n if (!value || typeof value !== \"object\") return false;\n const token = value as Partial<OAuthTokenSet>;\n return [token.host, token.issuer, token.clientId, token.resource, token.accessToken,\n token.accessTokenExpiresAt, token.refreshToken, token.refreshTokenExpiresAt]\n .every((item) => typeof item === \"string\" && item.length > 0)\n && Array.isArray(token.scopes)\n && token.scopes.every((scope) => typeof scope === \"string\");\n}\n\nfunction readStore(env: Record<string, string | undefined>): OAuthTokenStore {\n const path = oauthTokenStorePath(env);\n if (!existsSync(path)) return { version: 2, tokens: {} };\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as Partial<OAuthTokenStore>;\n if (\n parsed.version !== 2\n || !parsed.tokens\n || typeof parsed.tokens !== \"object\"\n || !Object.values(parsed.tokens).every(isTokenSet)\n ) throw new Error(\"invalid\");\n return parsed as OAuthTokenStore;\n } catch {\n throw new TypeError(`Rolino could not read its OAuth token store at ${path}.`);\n }\n}\n\nfunction writeStore(store: OAuthTokenStore, env: Record<string, string | undefined>) {\n const path = oauthTokenStorePath(env);\n const temporary = `${path}.${process.pid}.tmp`;\n mkdirSync(dirname(path), { recursive: true, mode: 0o700 });\n writeFileSync(temporary, `${JSON.stringify(store, null, 2)}\\n`, {\n encoding: \"utf8\",\n mode: 0o600,\n });\n chmodSync(temporary, 0o600);\n renameSync(temporary, path);\n chmodSync(path, 0o600);\n return path;\n}\n\nexport function loadOAuthTokenSet(\n baseUrl: string,\n env: Record<string, string | undefined> = process.env,\n) {\n const configuration = oauthConfiguration(baseUrl);\n return readStore(env).tokens[tokenKey(configuration)] ?? null;\n}\n\nexport function saveOAuthTokenSet(\n tokenSet: OAuthTokenSet,\n env: Record<string, string | undefined> = process.env,\n) {\n const configuration = oauthConfiguration(tokenSet.host);\n if (\n tokenSet.host !== configuration.host\n || tokenSet.issuer !== configuration.issuer\n || tokenSet.clientId !== configuration.clientId\n || tokenSet.resource !== configuration.resource\n ) throw new TypeError(\"The OAuth token set does not match the Rolino host.\");\n const store = readStore(env);\n store.tokens[tokenKey(tokenSet)] = tokenSet;\n const path = writeStore(store, env);\n const retiredInteractiveCredential = join(defaultConfigDirectory(env), \"credentials.json\");\n if (existsSync(retiredInteractiveCredential)) unlinkSync(retiredInteractiveCredential);\n return path;\n}\n\nexport function clearOAuthTokenSet(\n baseUrl: string,\n env: Record<string, string | undefined> = process.env,\n) {\n const path = oauthTokenStorePath(env);\n const store = readStore(env);\n const key = tokenKey(oauthConfiguration(baseUrl));\n const existed = Boolean(store.tokens[key]);\n delete store.tokens[key];\n if (!Object.keys(store.tokens).length) {\n if (existsSync(path)) unlinkSync(path);\n } else {\n writeStore(store, env);\n }\n return existed;\n}\n\nasync function refreshOAuthTokenSet(options: {\n baseUrl: string;\n env: Record<string, string | undefined>;\n fetch: typeof globalThis.fetch;\n}) {\n const current = loadOAuthTokenSet(options.baseUrl, options.env);\n if (!current) throw new TypeError(\"OAuth sign-in is required. Run `rolino auth login`.\");\n const key = tokenKey(current);\n const existing = refreshLocks.get(key);\n if (existing) return existing;\n\n const refresh = (async () => {\n const body = new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: current.refreshToken,\n client_id: current.clientId,\n resource: current.resource,\n });\n const response = await options.fetch(`${current.issuer}/oauth2/token`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\", accept: \"application/json\" },\n body,\n });\n const payload = await response.json().catch(() => null) as Record<string, unknown> | null;\n if (!response.ok || !payload || typeof payload.access_token !== \"string\") {\n throw new TypeError(\"OAuth sign-in is required. Run `rolino auth login`.\");\n }\n const expiresIn = typeof payload.expires_in === \"number\" ? payload.expires_in : 300;\n const refreshed: OAuthTokenSet = {\n ...current,\n accessToken: payload.access_token,\n accessTokenExpiresAt: new Date(Date.now() + expiresIn * 1_000).toISOString(),\n refreshToken: typeof payload.refresh_token === \"string\" ? payload.refresh_token : current.refreshToken,\n scopes: typeof payload.scope === \"string\" ? payload.scope.split(/\\s+/).filter(Boolean) : current.scopes,\n };\n saveOAuthTokenSet(refreshed, options.env);\n return refreshed;\n })().finally(() => refreshLocks.delete(key));\n refreshLocks.set(key, refresh);\n return refresh;\n}\n\nexport function createOAuthAccessTokenProvider(options: {\n baseUrl: string;\n env?: Record<string, string | undefined>;\n fetch?: typeof globalThis.fetch;\n}): RefreshableTokenProvider {\n const env = options.env ?? process.env;\n const fetchImplementation = options.fetch ?? globalThis.fetch;\n const obtain = async (force: boolean) => {\n if (env.ROLINO_TOKEN) return env.ROLINO_TOKEN;\n const token = loadOAuthTokenSet(options.baseUrl, env);\n if (!token) return undefined;\n if (!force && Date.parse(token.accessTokenExpiresAt) - Date.now() > 30_000) {\n return token.accessToken;\n }\n return (await refreshOAuthTokenSet({\n baseUrl: options.baseUrl,\n env,\n fetch: fetchImplementation,\n })).accessToken;\n };\n const provider = (() => obtain(false)) as RefreshableTokenProvider;\n provider.refresh = () => obtain(true);\n return provider;\n}\n\nexport function resolveLocalAuthentication(\n baseUrl: string,\n env: Record<string, string | undefined> = process.env,\n) {\n if (env.ROLINO_TOKEN) return { source: \"environment\" as const, tokenSet: null };\n const tokenSet = loadOAuthTokenSet(baseUrl, env);\n return tokenSet\n ? { source: \"oauth\" as const, tokenSet }\n : { source: \"none\" as const, tokenSet: null };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rolino/local-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Host-scoped local credential storage shared by Rolino's Node.js adapters",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"typecheck": "tsc --noEmit"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@rolino/sdk": "0.
|
|
57
|
+
"@rolino/sdk": "0.6.0"
|
|
58
58
|
},
|
|
59
59
|
"engines": {
|
|
60
60
|
"node": ">=20.19.0"
|