gt 2.21.4 → 2.22.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 +46 -0
- package/dist/api/collectUserEditDiffs.js +1 -1
- package/dist/auth/callbackPage.d.ts +1 -0
- package/dist/auth/callbackPage.js +77 -0
- package/dist/auth/callbackPage.js.map +1 -0
- package/dist/auth/credentialStore.d.ts +15 -0
- package/dist/auth/credentialStore.js +107 -0
- package/dist/auth/credentialStore.js.map +1 -0
- package/dist/auth/errors.d.ts +7 -0
- package/dist/auth/errors.js +23 -0
- package/dist/auth/errors.js.map +1 -0
- package/dist/auth/loopback.d.ts +9 -0
- package/dist/auth/loopback.js +73 -0
- package/dist/auth/loopback.js.map +1 -0
- package/dist/auth/loopback.test.d.ts +1 -0
- package/dist/auth/oauth.d.ts +39 -0
- package/dist/auth/oauth.js +260 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/bin/bin-entry.js +2 -10
- package/dist/bin/bin-entry.js.map +1 -1
- package/dist/cli/base.d.ts +4 -8
- package/dist/cli/base.js +119 -60
- package/dist/cli/base.js.map +1 -1
- package/dist/cli/commands/api.js +6 -2
- package/dist/cli/commands/api.js.map +1 -1
- package/dist/cli/commands/upload.js +2 -2
- package/dist/cli/commands/upload.js.map +1 -1
- package/dist/cli/commands/utils/validation.d.ts +7 -7
- package/dist/cli/commands/utils/validation.js +7 -20
- package/dist/cli/commands/utils/validation.js.map +1 -1
- package/dist/config/generateSettings.js +4 -9
- package/dist/config/generateSettings.js.map +1 -1
- package/dist/config/optionPresets.js +16 -6
- package/dist/config/optionPresets.js.map +1 -1
- package/dist/config/validateSettings.d.ts +4 -2
- package/dist/config/validateSettings.js +9 -0
- package/dist/config/validateSettings.js.map +1 -1
- package/dist/console/index.d.ts +0 -2
- package/dist/console/index.js +2 -4
- package/dist/console/index.js.map +1 -1
- package/dist/console/inkPrompts.js +8 -6
- package/dist/console/inkPrompts.js.map +1 -1
- package/dist/console/inkTypes.d.ts +3 -0
- package/dist/console/logger.d.ts +13 -0
- package/dist/console/logger.js +60 -20
- package/dist/console/logger.js.map +1 -1
- package/dist/console/logging.d.ts +5 -3
- package/dist/console/logging.js +8 -6
- package/dist/console/logging.js.map +1 -1
- package/dist/console/promptParsing.d.ts +4 -3
- package/dist/console/promptParsing.js +7 -7
- package/dist/console/promptParsing.js.map +1 -1
- package/dist/formats/files/aggregateFiles.js +4 -4
- package/dist/formats/files/aggregateFiles.js.map +1 -1
- package/dist/formats/files/localeContent.js +2 -2
- package/dist/formats/files/localeContent.js.map +1 -1
- package/dist/formats/files/transformFormat.d.ts +2 -0
- package/dist/formats/files/transformFormat.js +8 -2
- package/dist/formats/files/transformFormat.js.map +1 -1
- package/dist/formats/json/extractJson.js +3 -3
- package/dist/formats/json/extractJson.js.map +1 -1
- package/dist/formats/json/mergeJson.js +6 -6
- package/dist/formats/json/mergeJson.js.map +1 -1
- package/dist/formats/xcstrings/mergeXcstrings.js +2 -2
- package/dist/formats/xcstrings/mergeXcstrings.js.map +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/generated/version.js.map +1 -1
- package/dist/git/setupMergeDrivers.js +1 -1
- package/dist/git/setupMergeDrivers.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/main.js +2 -10
- package/dist/main.js.map +1 -1
- package/dist/react/jsx/utils/jsxParsing/addGTIdentifierToSyntaxTree.js +1 -1
- package/dist/react/jsx/utils/jsxParsing/parseJsx.js +1 -1
- package/dist/react/jsx/utils/parseString.js +1 -1
- package/dist/react/jsx/utils/stringParsing/derivation/index.js +1 -1
- package/dist/setup/developmentCredentials.d.ts +8 -0
- package/dist/setup/developmentCredentials.js +82 -0
- package/dist/setup/developmentCredentials.js.map +1 -0
- package/dist/setup/setupViteSPA.js +1 -1
- package/dist/setup/setupViteSPA.js.map +1 -1
- package/dist/setup/userInput.d.ts +1 -0
- package/dist/setup/userInput.js +7 -2
- package/dist/setup/userInput.js.map +1 -1
- package/dist/translation/extractInline.js +1 -1
- package/dist/translation/extractInline.js.map +1 -1
- package/dist/types/index.d.ts +7 -2
- package/dist/utils/api.d.ts +146 -67
- package/dist/utils/api.js +5 -94
- package/dist/utils/api.js.map +1 -1
- package/dist/utils/credentials.d.ts +37 -10
- package/dist/utils/credentials.js +267 -58
- package/dist/utils/credentials.js.map +1 -1
- package/dist/utils/loadEnv.d.ts +2 -0
- package/dist/utils/loadEnv.js +24 -0
- package/dist/utils/loadEnv.js.map +1 -0
- package/dist/utils/localizeStaticUrls.js +34 -6
- package/dist/utils/localizeStaticUrls.js.map +1 -1
- package/dist/utils/processOpenApi.js +2 -2
- package/dist/utils/processOpenApi.js.map +1 -1
- package/dist/utils/reactPackageCompatibility.js +1 -1
- package/dist/utils/reactPackageCompatibility.js.map +1 -1
- package/dist/workflows/steps/PollJobsStep.js +40 -53
- package/dist/workflows/steps/PollJobsStep.js.map +1 -1
- package/dist/workflows/utils/syncFonts.js +2 -2
- package/dist/workflows/utils/syncFonts.js.map +1 -1
- package/package.json +8 -7
- package/dist/next/config/parseNextConfig.d.ts +0 -10
- package/dist/next/config/parseNextConfig.js +0 -34
- package/dist/next/config/parseNextConfig.js.map +0 -1
- package/dist/utils/fetch.d.ts +0 -11
- package/dist/utils/fetch.js +0 -24
- package/dist/utils/fetch.js.map +0 -1
- package/dist/utils/gt.d.ts +0 -2
- package/dist/utils/gt.js +0 -7
- package/dist/utils/gt.js.map +0 -1
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { logger } from "../console/logger.js";
|
|
2
|
+
import "../utils/constants.js";
|
|
3
|
+
import { UserAuthError, loginRequiredError } from "./errors.js";
|
|
4
|
+
import { deleteOAuthTokens, readOAuthTokens, writeOAuthTokens } from "./credentialStore.js";
|
|
5
|
+
import { startLoopbackServer } from "./loopback.js";
|
|
6
|
+
import { createDiagnosticMessage } from "generaltranslation/diagnostics";
|
|
7
|
+
import { defaultBaseUrl } from "generaltranslation/internal";
|
|
8
|
+
import open from "open";
|
|
9
|
+
import * as oidc from "openid-client";
|
|
10
|
+
//#region src/auth/oauth.ts
|
|
11
|
+
/** Public native client seeded by the authorization server; never a secret. */
|
|
12
|
+
const OAUTH_CLIENT_ID = "gt-cli";
|
|
13
|
+
/** Explicit on both grants: identity, refresh and the user's dashboard permissions.
|
|
14
|
+
* Must match the seeded gt-cli client; the device endpoint does not default scope.
|
|
15
|
+
*/
|
|
16
|
+
const OAUTH_SCOPE = "openid profile offline_access gt:*";
|
|
17
|
+
const TOKEN_REFRESH_BUFFER_MS = 3e4;
|
|
18
|
+
function oauthFailure(whatHappened, fix, details) {
|
|
19
|
+
return new UserAuthError("oauth", whatHappened, fix, details);
|
|
20
|
+
}
|
|
21
|
+
function oauthError(error, fallback) {
|
|
22
|
+
if (error instanceof oidc.AuthorizationResponseError || error instanceof oidc.ResponseBodyError) return oauthFailure({
|
|
23
|
+
access_denied: "Sign in was denied in the browser",
|
|
24
|
+
expired_token: "The sign-in code expired before it was approved",
|
|
25
|
+
invalid_scope: "The authorization server rejected the requested scopes",
|
|
26
|
+
invalid_grant: "Your login or sign-in code expired or was already used",
|
|
27
|
+
invalid_client: "This authorization server does not recognize the gt CLI",
|
|
28
|
+
unauthorized_client: "This authorization server does not recognize the gt CLI"
|
|
29
|
+
}[error.error] ?? fallback, ["invalid_client", "unauthorized_client"].includes(error.error) ? "Check GT_AUTH_URL or update the server" : "Run `gt login` again", error.error === "invalid_scope" ? error.error_description : void 0);
|
|
30
|
+
if (error instanceof oidc.ClientError) return oauthFailure(`${fallback}${error.cause instanceof Response ? ` (HTTP ${error.cause.status})` : ""}`, "Check the authorization server and try again", error.code);
|
|
31
|
+
if (error instanceof Error && ["AbortError", "TimeoutError"].includes(error.name)) return oauthFailure("Sign in was cancelled or timed out", "Run `gt login` again");
|
|
32
|
+
return oauthFailure(fallback, "Check the authorization server and try again");
|
|
33
|
+
}
|
|
34
|
+
function assertEndpoint(url, issuer) {
|
|
35
|
+
if (url.username || url.password || url.hash || url.protocol !== "https:" && !(issuer.protocol === "http:" && url.origin === issuer.origin)) throw oauthFailure("The authorization server supplied an unsafe endpoint", "Use HTTPS, or same-origin local development HTTP");
|
|
36
|
+
}
|
|
37
|
+
async function configuration({ authBaseUrl = getAuthBaseUrl(), fetch: fetchImplementation = globalThis.fetch }, signal) {
|
|
38
|
+
const issuer = new URL(authBaseUrl);
|
|
39
|
+
const local = issuer.hostname === "localhost" || issuer.hostname.endsWith(".localhost") || issuer.hostname === "127.0.0.1" || issuer.hostname === "[::1]";
|
|
40
|
+
if (issuer.username || issuer.password || issuer.search || issuer.hash || issuer.protocol !== "https:" && !(issuer.protocol === "http:" && local)) throw oauthFailure("GT_AUTH_URL is not a trusted issuer URL", "Use HTTPS, or a local development HTTP issuer without credentials, query or fragment");
|
|
41
|
+
return oidc.discovery(issuer, OAUTH_CLIENT_ID, void 0, oidc.None(), {
|
|
42
|
+
execute: issuer.protocol === "http:" ? [oidc.enableNonRepudiationChecks, oidc.allowInsecureRequests] : [oidc.enableNonRepudiationChecks],
|
|
43
|
+
[oidc.customFetch]: (url, init) => {
|
|
44
|
+
assertEndpoint(new URL(url), issuer);
|
|
45
|
+
return fetchImplementation(url, {
|
|
46
|
+
...init,
|
|
47
|
+
signal: signal ? AbortSignal.any([signal, ...init.signal ? [init.signal] : []]) : init.signal,
|
|
48
|
+
body: init.body instanceof Uint8Array ? new Uint8Array(init.body) : init.body
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function parseTokens(result, previous) {
|
|
54
|
+
const subject = result.claims()?.sub ?? previous?.subject;
|
|
55
|
+
if (!subject || previous && subject !== previous.subject) throw oauthFailure("The authorization server did not confirm the expected account identity", "Run `gt login` again");
|
|
56
|
+
if (result.token_type !== "bearer" || typeof result.expires_in !== "number" || !Number.isFinite(result.expires_in) || result.expires_in <= 0) throw oauthFailure("The authorization server returned an unsupported token type or lifetime");
|
|
57
|
+
return {
|
|
58
|
+
accessToken: result.access_token,
|
|
59
|
+
expiresAt: Date.now() + result.expires_in * 1e3,
|
|
60
|
+
refreshToken: result.refresh_token ?? previous?.refreshToken ?? "",
|
|
61
|
+
scope: result.scope ?? previous?.scope ?? OAUTH_SCOPE,
|
|
62
|
+
tokenType: result.token_type,
|
|
63
|
+
subject
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function getAuthBaseUrl() {
|
|
67
|
+
return (process.env.GT_AUTH_URL ?? `https://dash.generaltranslation.com/api/auth`).replace(/\/$/, "");
|
|
68
|
+
}
|
|
69
|
+
/** Resource indicators are the API origin serialized as a URL href (trailing slash). */
|
|
70
|
+
function toApiResource(baseUrl) {
|
|
71
|
+
return new URL(baseUrl).href;
|
|
72
|
+
}
|
|
73
|
+
function loginResource(options) {
|
|
74
|
+
return toApiResource(process.env.GT_API_URL ?? options.baseUrl ?? defaultBaseUrl);
|
|
75
|
+
}
|
|
76
|
+
async function loginWithDeviceCode(options) {
|
|
77
|
+
if (options.noBrowser && !options.onDeviceCode) throw oauthFailure("Device login needs a way to display the verification code", "Provide onDeviceCode when using noBrowser");
|
|
78
|
+
const authBaseUrl = options.authBaseUrl ?? getAuthBaseUrl();
|
|
79
|
+
const resource = loginResource(options);
|
|
80
|
+
const lifetime = new AbortController();
|
|
81
|
+
const signal = options.signal ? AbortSignal.any([options.signal, lifetime.signal]) : lifetime.signal;
|
|
82
|
+
let timer;
|
|
83
|
+
let result;
|
|
84
|
+
try {
|
|
85
|
+
signal.throwIfAborted();
|
|
86
|
+
const config = await configuration({
|
|
87
|
+
...options,
|
|
88
|
+
authBaseUrl
|
|
89
|
+
}, signal);
|
|
90
|
+
const started = Date.now();
|
|
91
|
+
const response = await oidc.initiateDeviceAuthorization(config, {
|
|
92
|
+
scope: OAUTH_SCOPE,
|
|
93
|
+
resource
|
|
94
|
+
});
|
|
95
|
+
const deadline = started + response.expires_in * 1e3;
|
|
96
|
+
const checkLifetime = () => {
|
|
97
|
+
if (Date.now() >= deadline) lifetime.abort(new DOMException("Device code expired", "TimeoutError"));
|
|
98
|
+
signal.throwIfAborted();
|
|
99
|
+
};
|
|
100
|
+
checkLifetime();
|
|
101
|
+
timer = setTimeout(() => lifetime.abort(new DOMException("Device code expired", "TimeoutError")), deadline - Date.now());
|
|
102
|
+
assertEndpoint(new URL(response.verification_uri), new URL(authBaseUrl));
|
|
103
|
+
if (response.verification_uri_complete) assertEndpoint(new URL(response.verification_uri_complete), new URL(authBaseUrl));
|
|
104
|
+
options.onDeviceCode?.({
|
|
105
|
+
userCode: response.user_code,
|
|
106
|
+
verificationUri: response.verification_uri,
|
|
107
|
+
verificationUriComplete: response.verification_uri_complete
|
|
108
|
+
});
|
|
109
|
+
checkLifetime();
|
|
110
|
+
if (!options.noBrowser) (options.openBrowser ?? open)(response.verification_uri_complete ?? response.verification_uri).catch(() => void 0);
|
|
111
|
+
result = await oidc.pollDeviceAuthorizationGrant(config, response, { resource }, { signal });
|
|
112
|
+
checkLifetime();
|
|
113
|
+
} catch (error) {
|
|
114
|
+
throw oauthError(error, "Could not complete device sign in");
|
|
115
|
+
} finally {
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
}
|
|
118
|
+
const tokens = {
|
|
119
|
+
...parseTokens(result),
|
|
120
|
+
resource
|
|
121
|
+
};
|
|
122
|
+
await writeOAuthTokens(tokens, authBaseUrl);
|
|
123
|
+
return tokens;
|
|
124
|
+
}
|
|
125
|
+
/** Browser S256/loopback, or device login for --no-browser and bind failure. */
|
|
126
|
+
async function login(options = {}) {
|
|
127
|
+
if (options.noBrowser) return loginWithDeviceCode(options);
|
|
128
|
+
const authBaseUrl = options.authBaseUrl ?? getAuthBaseUrl();
|
|
129
|
+
const resource = loginResource(options);
|
|
130
|
+
const config = await configuration({
|
|
131
|
+
...options,
|
|
132
|
+
authBaseUrl
|
|
133
|
+
}).catch((error) => {
|
|
134
|
+
throw oauthError(error, "Could not discover the authorization server");
|
|
135
|
+
});
|
|
136
|
+
const loopback = await startLoopbackServer().catch(() => void 0);
|
|
137
|
+
if (!loopback) return loginWithDeviceCode(options);
|
|
138
|
+
let result;
|
|
139
|
+
try {
|
|
140
|
+
const codeVerifier = oidc.randomPKCECodeVerifier();
|
|
141
|
+
const state = oidc.randomState();
|
|
142
|
+
const authorizationUrl = oidc.buildAuthorizationUrl(config, {
|
|
143
|
+
redirect_uri: loopback.redirectUri,
|
|
144
|
+
scope: OAUTH_SCOPE,
|
|
145
|
+
resource,
|
|
146
|
+
state,
|
|
147
|
+
code_challenge: await oidc.calculatePKCECodeChallenge(codeVerifier),
|
|
148
|
+
code_challenge_method: "S256"
|
|
149
|
+
});
|
|
150
|
+
assertEndpoint(authorizationUrl, new URL(authBaseUrl));
|
|
151
|
+
const callback = loopback.waitForCallback(options.timeoutMs);
|
|
152
|
+
callback.catch(() => void 0);
|
|
153
|
+
options.onAuthorizationUrl?.(authorizationUrl.href);
|
|
154
|
+
(options.openBrowser ?? open)(authorizationUrl.href).catch(() => void 0);
|
|
155
|
+
const callbackUrl = await callback;
|
|
156
|
+
result = await oidc.authorizationCodeGrant(config, callbackUrl, {
|
|
157
|
+
pkceCodeVerifier: codeVerifier,
|
|
158
|
+
expectedState: state,
|
|
159
|
+
idTokenExpected: true
|
|
160
|
+
}, { resource }).catch((error) => {
|
|
161
|
+
throw oauthError(error, "Could not validate the sign-in response");
|
|
162
|
+
});
|
|
163
|
+
} finally {
|
|
164
|
+
loopback.close();
|
|
165
|
+
}
|
|
166
|
+
const tokens = {
|
|
167
|
+
...parseTokens(result),
|
|
168
|
+
resource
|
|
169
|
+
};
|
|
170
|
+
await writeOAuthTokens(tokens, authBaseUrl);
|
|
171
|
+
return tokens;
|
|
172
|
+
}
|
|
173
|
+
const pendingRefreshes = /* @__PURE__ */ new Map();
|
|
174
|
+
function refreshTokens(options) {
|
|
175
|
+
const authBaseUrl = options.authBaseUrl ?? getAuthBaseUrl();
|
|
176
|
+
const pending = pendingRefreshes.get(authBaseUrl);
|
|
177
|
+
if (pending) return pending;
|
|
178
|
+
const refresh = exchangeRefreshToken({
|
|
179
|
+
...options,
|
|
180
|
+
authBaseUrl
|
|
181
|
+
}).finally(() => pendingRefreshes.delete(authBaseUrl));
|
|
182
|
+
pendingRefreshes.set(authBaseUrl, refresh);
|
|
183
|
+
return refresh;
|
|
184
|
+
}
|
|
185
|
+
async function exchangeRefreshToken(options) {
|
|
186
|
+
const current = await readOAuthTokens(options.authBaseUrl);
|
|
187
|
+
if (!current?.refreshToken) throw loginRequiredError();
|
|
188
|
+
const tokens = {
|
|
189
|
+
...parseTokens(await (async () => oidc.refreshTokenGrant(await configuration(options), current.refreshToken))().catch((error) => {
|
|
190
|
+
throw oauthError(error, "Could not refresh your login");
|
|
191
|
+
}), current),
|
|
192
|
+
resource: current.resource
|
|
193
|
+
};
|
|
194
|
+
await writeOAuthTokens(tokens, options.authBaseUrl);
|
|
195
|
+
return tokens;
|
|
196
|
+
}
|
|
197
|
+
async function validTokens(options) {
|
|
198
|
+
const tokens = await readOAuthTokens(options.authBaseUrl ?? getAuthBaseUrl());
|
|
199
|
+
if (!tokens || tokens.expiresAt > Date.now() + TOKEN_REFRESH_BUFFER_MS) return tokens;
|
|
200
|
+
return refreshTokens(options);
|
|
201
|
+
}
|
|
202
|
+
/** Whether a stored login exists for this API; does not refresh or contact the server. */
|
|
203
|
+
async function hasLogin(options) {
|
|
204
|
+
return (await readOAuthTokens(options.authBaseUrl ?? getAuthBaseUrl()))?.resource === toApiResource(options.baseUrl);
|
|
205
|
+
}
|
|
206
|
+
/** Construction is deliberately free of auth/storage/discovery I/O. */
|
|
207
|
+
function createUserTokenProvider(options) {
|
|
208
|
+
const storedTokens = async () => {
|
|
209
|
+
const tokens = await readOAuthTokens(options.authBaseUrl ?? getAuthBaseUrl());
|
|
210
|
+
if (!tokens) throw loginRequiredError();
|
|
211
|
+
const resource = toApiResource(options.baseUrl);
|
|
212
|
+
if (tokens.resource !== resource) throw new UserAuthError("resource_mismatch", `You are signed in to ${tokens.resource}, but this project uses ${resource}`, "Run `gt login` in this project to sign in to its API");
|
|
213
|
+
return tokens;
|
|
214
|
+
};
|
|
215
|
+
return {
|
|
216
|
+
getAccessToken: async () => {
|
|
217
|
+
const tokens = await storedTokens();
|
|
218
|
+
if (tokens.expiresAt > Date.now() + TOKEN_REFRESH_BUFFER_MS) return tokens.accessToken;
|
|
219
|
+
return (await refreshTokens(options)).accessToken;
|
|
220
|
+
},
|
|
221
|
+
refreshAccessToken: async () => {
|
|
222
|
+
await storedTokens();
|
|
223
|
+
return (await refreshTokens(options)).accessToken;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
async function logout(options = {}) {
|
|
228
|
+
const authBaseUrl = options.authBaseUrl ?? getAuthBaseUrl();
|
|
229
|
+
try {
|
|
230
|
+
const tokens = await readOAuthTokens(authBaseUrl);
|
|
231
|
+
if (tokens?.refreshToken) await oidc.tokenRevocation(await configuration(options), tokens.refreshToken, { token_type_hint: "refresh_token" });
|
|
232
|
+
} catch {
|
|
233
|
+
logger.warn(createDiagnosticMessage({
|
|
234
|
+
source: "gt",
|
|
235
|
+
severity: "Warning",
|
|
236
|
+
whatHappened: "Signed out locally, but remote session revocation could not be confirmed",
|
|
237
|
+
fix: "Revoke the session in the dashboard if needed"
|
|
238
|
+
}));
|
|
239
|
+
} finally {
|
|
240
|
+
await deleteOAuthTokens(authBaseUrl);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function whoAmI(options = {}) {
|
|
244
|
+
const tokens = await validTokens(options);
|
|
245
|
+
if (!tokens) throw loginRequiredError();
|
|
246
|
+
try {
|
|
247
|
+
const result = await oidc.fetchUserInfo(await configuration(options), tokens.accessToken, tokens.subject);
|
|
248
|
+
return {
|
|
249
|
+
sub: result.sub,
|
|
250
|
+
email: result.email,
|
|
251
|
+
name: result.name
|
|
252
|
+
};
|
|
253
|
+
} catch (error) {
|
|
254
|
+
throw oauthError(error, "Could not validate your account identity");
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
//#endregion
|
|
258
|
+
export { createUserTokenProvider, hasLogin, login, logout, whoAmI };
|
|
259
|
+
|
|
260
|
+
//# sourceMappingURL=oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.js","names":[],"sources":["../../src/auth/oauth.ts"],"sourcesContent":["import open from 'open';\nimport * as oidc from 'openid-client';\nimport type { UserTokenProvider } from 'generaltranslation/api';\nimport { createDiagnosticMessage } from 'generaltranslation/diagnostics';\nimport { defaultBaseUrl } from 'generaltranslation/internal';\nimport { GT_DASHBOARD_URL } from '../utils/constants.js';\nimport { logger } from '../console/logger.js';\nimport {\n deleteOAuthTokens,\n readOAuthTokens,\n writeOAuthTokens,\n} from './credentialStore.js';\nimport type { OAuthTokens } from './credentialStore.js';\nimport { UserAuthError, loginRequiredError } from './errors.js';\nimport { startLoopbackServer } from './loopback.js';\n\n/** Public native client seeded by the authorization server; never a secret. */\nconst OAUTH_CLIENT_ID = 'gt-cli';\n/** Explicit on both grants: identity, refresh and the user's dashboard permissions.\n * Must match the seeded gt-cli client; the device endpoint does not default scope.\n */\nconst OAUTH_SCOPE = 'openid profile offline_access gt:*';\n// Refresh before expiry so an in-flight request does not carry an expired token.\nconst TOKEN_REFRESH_BUFFER_MS = 30_000;\n\nexport type DeviceCode = {\n userCode: string;\n verificationUri: string;\n verificationUriComplete?: string;\n};\nexport type UserInfo = { email?: string; name?: string; sub: string };\ntype OAuthRequestOptions = { authBaseUrl?: string; fetch?: typeof fetch };\nexport type UserTokenProviderOptions = OAuthRequestOptions & {\n /** API the requests go to; must match the resource the login was issued for. */\n baseUrl: string;\n};\nexport type LoginOptions = OAuthRequestOptions & {\n /** API the token is issued for; GT_API_URL overrides, then the public API. */\n baseUrl?: string;\n noBrowser?: boolean;\n onDeviceCode?: (deviceCode: DeviceCode) => void;\n signal?: AbortSignal;\n onAuthorizationUrl?: (url: string) => void;\n openBrowser?: (url: string) => Promise<unknown>;\n timeoutMs?: number;\n};\n\nfunction oauthFailure(\n whatHappened: string,\n fix?: string,\n details?: string\n): UserAuthError {\n return new UserAuthError('oauth', whatHappened, fix, details);\n}\n\nfunction oauthError(error: unknown, fallback: string): UserAuthError {\n if (\n error instanceof oidc.AuthorizationResponseError ||\n error instanceof oidc.ResponseBodyError\n ) {\n const descriptions: Record<string, string> = {\n access_denied: 'Sign in was denied in the browser',\n expired_token: 'The sign-in code expired before it was approved',\n invalid_scope: 'The authorization server rejected the requested scopes',\n invalid_grant: 'Your login or sign-in code expired or was already used',\n invalid_client: 'This authorization server does not recognize the gt CLI',\n unauthorized_client:\n 'This authorization server does not recognize the gt CLI',\n };\n return oauthFailure(\n descriptions[error.error] ?? fallback,\n ['invalid_client', 'unauthorized_client'].includes(error.error)\n ? 'Check GT_AUTH_URL or update the server'\n : 'Run `gt login` again',\n // Scope descriptions are useful configuration diagnostics, not token data.\n error.error === 'invalid_scope' ? error.error_description : undefined\n );\n }\n if (error instanceof oidc.ClientError) {\n const status =\n error.cause instanceof Response ? ` (HTTP ${error.cause.status})` : '';\n return oauthFailure(\n `${fallback}${status}`,\n 'Check the authorization server and try again',\n error.code\n );\n }\n if (\n error instanceof Error &&\n ['AbortError', 'TimeoutError'].includes(error.name)\n ) {\n return oauthFailure(\n 'Sign in was cancelled or timed out',\n 'Run `gt login` again'\n );\n }\n // Do not expose transport causes, callback URLs or token responses.\n return oauthFailure(fallback, 'Check the authorization server and try again');\n}\n\nfunction assertEndpoint(url: URL, issuer: URL): void {\n if (\n url.username ||\n url.password ||\n url.hash ||\n (url.protocol !== 'https:' &&\n !(issuer.protocol === 'http:' && url.origin === issuer.origin))\n ) {\n throw oauthFailure(\n 'The authorization server supplied an unsafe endpoint',\n 'Use HTTPS, or same-origin local development HTTP'\n );\n }\n}\n\nasync function configuration(\n {\n authBaseUrl = getAuthBaseUrl(),\n fetch: fetchImplementation = globalThis.fetch,\n }: OAuthRequestOptions,\n signal?: AbortSignal\n): Promise<oidc.Configuration> {\n const issuer = new URL(authBaseUrl);\n const local =\n issuer.hostname === 'localhost' ||\n issuer.hostname.endsWith('.localhost') ||\n issuer.hostname === '127.0.0.1' ||\n issuer.hostname === '[::1]';\n if (\n issuer.username ||\n issuer.password ||\n issuer.search ||\n issuer.hash ||\n (issuer.protocol !== 'https:' && !(issuer.protocol === 'http:' && local))\n ) {\n throw oauthFailure(\n 'GT_AUTH_URL is not a trusted issuer URL',\n 'Use HTTPS, or a local development HTTP issuer without credentials, query or fragment'\n );\n }\n return oidc.discovery(issuer, OAUTH_CLIENT_ID, undefined, oidc.None(), {\n execute:\n issuer.protocol === 'http:'\n ? [oidc.enableNonRepudiationChecks, oidc.allowInsecureRequests]\n : [oidc.enableNonRepudiationChecks],\n [oidc.customFetch]: (url, init) => {\n assertEndpoint(new URL(url), issuer);\n return fetchImplementation(url, {\n ...init,\n // Include operation cancellation in JWKS fetches as well as token requests.\n signal: signal\n ? AbortSignal.any([signal, ...(init.signal ? [init.signal] : [])])\n : init.signal,\n body:\n init.body instanceof Uint8Array\n ? new Uint8Array(init.body)\n : init.body,\n });\n },\n });\n}\n\nfunction parseTokens(\n result: oidc.TokenEndpointResponse & oidc.TokenEndpointResponseHelpers,\n previous?: OAuthTokens\n): Omit<OAuthTokens, 'resource'> {\n const subject = result.claims()?.sub ?? previous?.subject;\n if (!subject || (previous && subject !== previous.subject)) {\n throw oauthFailure(\n 'The authorization server did not confirm the expected account identity',\n 'Run `gt login` again'\n );\n }\n if (\n result.token_type !== 'bearer' ||\n typeof result.expires_in !== 'number' ||\n !Number.isFinite(result.expires_in) ||\n result.expires_in <= 0\n ) {\n throw oauthFailure(\n 'The authorization server returned an unsupported token type or lifetime'\n );\n }\n return {\n accessToken: result.access_token,\n expiresAt: Date.now() + result.expires_in * 1000,\n refreshToken: result.refresh_token ?? previous?.refreshToken ?? '',\n scope: result.scope ?? previous?.scope ?? OAUTH_SCOPE,\n tokenType: result.token_type,\n subject,\n };\n}\n\nfunction getAuthBaseUrl(): string {\n return (process.env.GT_AUTH_URL ?? `${GT_DASHBOARD_URL}/api/auth`).replace(\n /\\/$/,\n ''\n );\n}\n\n/** Resource indicators are the API origin serialized as a URL href (trailing slash). */\nfunction toApiResource(baseUrl: string): string {\n return new URL(baseUrl).href;\n}\n\nfunction loginResource(options: LoginOptions): string {\n return toApiResource(\n process.env.GT_API_URL ?? options.baseUrl ?? defaultBaseUrl\n );\n}\n\nasync function loginWithDeviceCode(\n options: LoginOptions\n): Promise<OAuthTokens> {\n if (options.noBrowser && !options.onDeviceCode) {\n throw oauthFailure(\n 'Device login needs a way to display the verification code',\n 'Provide onDeviceCode when using noBrowser'\n );\n }\n const authBaseUrl = options.authBaseUrl ?? getAuthBaseUrl();\n const resource = loginResource(options);\n const lifetime = new AbortController();\n const signal = options.signal\n ? AbortSignal.any([options.signal, lifetime.signal])\n : lifetime.signal;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let result: oidc.TokenEndpointResponse & oidc.TokenEndpointResponseHelpers;\n try {\n signal.throwIfAborted();\n const config = await configuration({ ...options, authBaseUrl }, signal);\n const started = Date.now();\n const response = await oidc.initiateDeviceAuthorization(config, {\n scope: OAUTH_SCOPE,\n resource,\n });\n const deadline = started + response.expires_in * 1000;\n const checkLifetime = () => {\n if (Date.now() >= deadline)\n lifetime.abort(new DOMException('Device code expired', 'TimeoutError'));\n signal.throwIfAborted();\n };\n checkLifetime();\n timer = setTimeout(\n () =>\n lifetime.abort(new DOMException('Device code expired', 'TimeoutError')),\n deadline - Date.now()\n );\n assertEndpoint(new URL(response.verification_uri), new URL(authBaseUrl));\n if (response.verification_uri_complete)\n assertEndpoint(\n new URL(response.verification_uri_complete),\n new URL(authBaseUrl)\n );\n options.onDeviceCode?.({\n userCode: response.user_code,\n verificationUri: response.verification_uri,\n verificationUriComplete: response.verification_uri_complete,\n });\n checkLifetime();\n if (!options.noBrowser) {\n void (options.openBrowser ?? open)(\n response.verification_uri_complete ?? response.verification_uri\n ).catch(() => undefined);\n }\n result = await oidc.pollDeviceAuthorizationGrant(\n config,\n response,\n { resource },\n { signal }\n );\n checkLifetime();\n } catch (error) {\n throw oauthError(error, 'Could not complete device sign in');\n } finally {\n clearTimeout(timer);\n }\n const tokens = { ...parseTokens(result), resource };\n await writeOAuthTokens(tokens, authBaseUrl);\n return tokens;\n}\n\n/** Browser S256/loopback, or device login for --no-browser and bind failure. */\nexport async function login(options: LoginOptions = {}): Promise<OAuthTokens> {\n if (options.noBrowser) return loginWithDeviceCode(options);\n const authBaseUrl = options.authBaseUrl ?? getAuthBaseUrl();\n const resource = loginResource(options);\n const config = await configuration({ ...options, authBaseUrl }).catch(\n (error: unknown) => {\n throw oauthError(error, 'Could not discover the authorization server');\n }\n );\n const loopback = await startLoopbackServer().catch(() => undefined);\n if (!loopback) return loginWithDeviceCode(options);\n let result: oidc.TokenEndpointResponse & oidc.TokenEndpointResponseHelpers;\n try {\n const codeVerifier = oidc.randomPKCECodeVerifier();\n const state = oidc.randomState();\n const authorizationUrl = oidc.buildAuthorizationUrl(config, {\n redirect_uri: loopback.redirectUri,\n scope: OAUTH_SCOPE,\n resource,\n state,\n code_challenge: await oidc.calculatePKCECodeChallenge(codeVerifier),\n code_challenge_method: 'S256',\n });\n assertEndpoint(authorizationUrl, new URL(authBaseUrl));\n const callback = loopback.waitForCallback(options.timeoutMs);\n // Printing/launching may fail before we await the listener.\n callback.catch(() => undefined);\n options.onAuthorizationUrl?.(authorizationUrl.href);\n void (options.openBrowser ?? open)(authorizationUrl.href).catch(\n () => undefined\n );\n const callbackUrl = await callback;\n result = await oidc\n .authorizationCodeGrant(\n config,\n callbackUrl,\n {\n pkceCodeVerifier: codeVerifier,\n expectedState: state,\n idTokenExpected: true,\n },\n { resource }\n )\n .catch((error: unknown) => {\n throw oauthError(error, 'Could not validate the sign-in response');\n });\n } finally {\n loopback.close();\n }\n const tokens = { ...parseTokens(result), resource };\n await writeOAuthTokens(tokens, authBaseUrl);\n return tokens;\n}\n\nconst pendingRefreshes = new Map<string, Promise<OAuthTokens>>();\nfunction refreshTokens(options: OAuthRequestOptions): Promise<OAuthTokens> {\n const authBaseUrl = options.authBaseUrl ?? getAuthBaseUrl();\n const pending = pendingRefreshes.get(authBaseUrl);\n if (pending) return pending;\n const refresh = exchangeRefreshToken({ ...options, authBaseUrl }).finally(\n () => pendingRefreshes.delete(authBaseUrl)\n );\n pendingRefreshes.set(authBaseUrl, refresh);\n return refresh;\n}\n\nasync function exchangeRefreshToken(\n options: OAuthRequestOptions & { authBaseUrl: string }\n): Promise<OAuthTokens> {\n const current = await readOAuthTokens(options.authBaseUrl);\n if (!current?.refreshToken) throw loginRequiredError();\n const result = await (async () =>\n oidc.refreshTokenGrant(\n await configuration(options),\n current.refreshToken\n ))().catch((error: unknown) => {\n throw oauthError(error, 'Could not refresh your login');\n });\n // The grant reuses the bound resource, so the refreshed token keeps its audience.\n const tokens = {\n ...parseTokens(result, current),\n resource: current.resource,\n };\n await writeOAuthTokens(tokens, options.authBaseUrl);\n return tokens;\n}\n\nasync function validTokens(\n options: OAuthRequestOptions\n): Promise<OAuthTokens | undefined> {\n const tokens = await readOAuthTokens(options.authBaseUrl ?? getAuthBaseUrl());\n if (!tokens || tokens.expiresAt > Date.now() + TOKEN_REFRESH_BUFFER_MS)\n return tokens;\n return refreshTokens(options);\n}\n\n/** Whether a stored login exists for this API; does not refresh or contact the server. */\nexport async function hasLogin(\n options: UserTokenProviderOptions\n): Promise<boolean> {\n const tokens = await readOAuthTokens(options.authBaseUrl ?? getAuthBaseUrl());\n return tokens?.resource === toApiResource(options.baseUrl);\n}\n\n/** Construction is deliberately free of auth/storage/discovery I/O. */\nexport function createUserTokenProvider(\n options: UserTokenProviderOptions\n): UserTokenProvider {\n // Check the audience before any refresh so a stale mismatched login is\n // never rotated on behalf of the wrong API.\n const storedTokens = async (): Promise<OAuthTokens> => {\n const tokens = await readOAuthTokens(\n options.authBaseUrl ?? getAuthBaseUrl()\n );\n if (!tokens) throw loginRequiredError();\n const resource = toApiResource(options.baseUrl);\n if (tokens.resource !== resource) {\n throw new UserAuthError(\n 'resource_mismatch',\n `You are signed in to ${tokens.resource}, but this project uses ${resource}`,\n 'Run `gt login` in this project to sign in to its API'\n );\n }\n return tokens;\n };\n return {\n getAccessToken: async () => {\n const tokens = await storedTokens();\n if (tokens.expiresAt > Date.now() + TOKEN_REFRESH_BUFFER_MS)\n return tokens.accessToken;\n return (await refreshTokens(options)).accessToken;\n },\n refreshAccessToken: async () => {\n await storedTokens();\n return (await refreshTokens(options)).accessToken;\n },\n };\n}\n\nexport async function logout(options: OAuthRequestOptions = {}): Promise<void> {\n const authBaseUrl = options.authBaseUrl ?? getAuthBaseUrl();\n try {\n const tokens = await readOAuthTokens(authBaseUrl);\n if (tokens?.refreshToken)\n await oidc.tokenRevocation(\n await configuration(options),\n tokens.refreshToken,\n { token_type_hint: 'refresh_token' }\n );\n } catch {\n logger.warn(\n createDiagnosticMessage({\n source: 'gt',\n severity: 'Warning',\n whatHappened:\n 'Signed out locally, but remote session revocation could not be confirmed',\n fix: 'Revoke the session in the dashboard if needed',\n })\n );\n } finally {\n await deleteOAuthTokens(authBaseUrl);\n }\n}\n\nexport async function whoAmI(\n options: OAuthRequestOptions = {}\n): Promise<UserInfo> {\n const tokens = await validTokens(options);\n if (!tokens) throw loginRequiredError();\n try {\n const result = await oidc.fetchUserInfo(\n await configuration(options),\n tokens.accessToken,\n tokens.subject\n );\n return { sub: result.sub, email: result.email, name: result.name };\n } catch (error) {\n throw oauthError(error, 'Could not validate your account identity');\n }\n}\n"],"mappings":"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"}
|
package/dist/bin/bin-entry.js
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
import { main } from "../index.js";
|
|
2
|
-
import
|
|
2
|
+
import { loadEnv } from "../utils/loadEnv.js";
|
|
3
3
|
import { program } from "commander";
|
|
4
4
|
//#region src/bin/bin-entry.ts
|
|
5
|
-
|
|
6
|
-
dotenv.config({
|
|
7
|
-
path: ".env.local",
|
|
8
|
-
override: true
|
|
9
|
-
});
|
|
10
|
-
dotenv.config({
|
|
11
|
-
path: ".env.production",
|
|
12
|
-
override: true
|
|
13
|
-
});
|
|
5
|
+
loadEnv();
|
|
14
6
|
main(program);
|
|
15
7
|
program.parse();
|
|
16
8
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bin-entry.js","names":[],"sources":["../../src/bin/bin-entry.ts"],"sourcesContent":["// Entry point for binaries\n\nimport { main } from '../index.js';\nimport
|
|
1
|
+
{"version":3,"file":"bin-entry.js","names":[],"sources":["../../src/bin/bin-entry.ts"],"sourcesContent":["// Entry point for binaries\n\nimport { main } from '../index.js';\nimport { loadEnv } from '../utils/loadEnv.js';\nimport { program } from 'commander';\n\nloadEnv();\n\nmain(program);\nprogram.parse();\n"],"mappings":";;;;AAMA,SAAS;AAET,KAAK,QAAQ;AACb,QAAQ,OAAO"}
|
package/dist/cli/base.d.ts
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { Settings, SupportedLibraries, SetupOptions, TranslateFlags } from '../types/index.js';
|
|
2
|
+
import { Settings, SupportedFrameworks, SupportedLibraries, SetupOptions, TranslateFlags } from '../types/index.js';
|
|
3
3
|
export type UploadOptions = {
|
|
4
4
|
config?: string;
|
|
5
5
|
apiKey?: string;
|
|
6
6
|
projectId?: string;
|
|
7
7
|
defaultLocale?: string;
|
|
8
8
|
};
|
|
9
|
-
export type LoginOptions = {
|
|
10
|
-
config?: string;
|
|
11
|
-
keyType?: 'development' | 'production' | 'all';
|
|
12
|
-
};
|
|
13
9
|
export type GitSetupOptions = {
|
|
14
10
|
config?: string;
|
|
15
11
|
dryRun?: boolean;
|
|
@@ -51,6 +47,7 @@ export declare class BaseCLI {
|
|
|
51
47
|
protected setupSendDiffsCommand(): void;
|
|
52
48
|
protected setupApiCommand(): void;
|
|
53
49
|
protected setupProjectCommands(): void;
|
|
50
|
+
protected setupApiKeyCommands(): void;
|
|
54
51
|
protected setupGitCommand(): void;
|
|
55
52
|
protected resolveGitSetupOmitConfigIds(options: GitSetupOptions, settings: Settings): Promise<boolean>;
|
|
56
53
|
protected handleSetupProject(initOptions: TranslateFlags): Promise<void>;
|
|
@@ -69,7 +66,7 @@ export declare class BaseCLI {
|
|
|
69
66
|
protected handleDownload(initOptions: TranslateFlags): Promise<void>;
|
|
70
67
|
protected handleTranslate(initOptions: TranslateFlags): Promise<void>;
|
|
71
68
|
protected setupUploadCommand(): void;
|
|
72
|
-
protected
|
|
69
|
+
protected setupUserAuthCommands(): void;
|
|
73
70
|
protected setupInitCommand(): void;
|
|
74
71
|
protected setupConfigureCommand(): void;
|
|
75
72
|
protected handleConfigureCommand(options?: SetupOptions): Promise<void>;
|
|
@@ -84,7 +81,6 @@ export declare class BaseCLI {
|
|
|
84
81
|
protected shouldGenerateLocalTranslationLoader(isVite: boolean, runtimeSetup: InlineRuntimeSetup): boolean;
|
|
85
82
|
/** Returns framework guidance after selecting local translation storage. */
|
|
86
83
|
protected getLocalTranslationGuidance({ generatedLoader, runtimeSetup, translationsDir, }: LocalTranslationGuidanceOptions): string | undefined;
|
|
87
|
-
protected handleInitCommand(ranReactSetup: boolean, useDefaults?: boolean, isVite?: boolean, options?: SetupOptions): Promise<void>;
|
|
88
|
-
protected handleLoginCommand(options: LoginOptions): Promise<void>;
|
|
84
|
+
protected handleInitCommand(ranReactSetup: boolean, useDefaults?: boolean, isVite?: boolean, options?: SetupOptions, framework?: SupportedFrameworks): Promise<void>;
|
|
89
85
|
}
|
|
90
86
|
export {};
|