garmin-mcp-auth 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/dist/auth-service.d.ts +21 -0
- package/dist/auth-service.d.ts.map +1 -0
- package/dist/auth-service.js +318 -0
- package/dist/auth-service.js.map +1 -0
- package/dist/browser-login.d.ts +72 -0
- package/dist/browser-login.d.ts.map +1 -0
- package/dist/browser-login.js +374 -0
- package/dist/browser-login.js.map +1 -0
- package/dist/cookie-jar.d.ts +10 -0
- package/dist/cookie-jar.d.ts.map +1 -0
- package/dist/cookie-jar.js +80 -0
- package/dist/cookie-jar.js.map +1 -0
- package/dist/curl-transport.d.ts +40 -0
- package/dist/curl-transport.d.ts.map +1 -0
- package/dist/curl-transport.js +152 -0
- package/dist/curl-transport.js.map +1 -0
- package/dist/diagnose.d.ts +24 -0
- package/dist/diagnose.d.ts.map +1 -0
- package/dist/diagnose.js +38 -0
- package/dist/diagnose.js.map +1 -0
- package/dist/errors.d.ts +36 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +68 -0
- package/dist/errors.js.map +1 -0
- package/dist/fetch-transport.d.ts +15 -0
- package/dist/fetch-transport.d.ts.map +1 -0
- package/dist/fetch-transport.js +66 -0
- package/dist/fetch-transport.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/jwt.d.ts +14 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +39 -0
- package/dist/jwt.js.map +1 -0
- package/dist/mobile-flow.d.ts +53 -0
- package/dist/mobile-flow.d.ts.map +1 -0
- package/dist/mobile-flow.js +216 -0
- package/dist/mobile-flow.js.map +1 -0
- package/dist/python-fallback.d.ts +12 -0
- package/dist/python-fallback.d.ts.map +1 -0
- package/dist/python-fallback.js +17 -0
- package/dist/python-fallback.js.map +1 -0
- package/dist/token-store.d.ts +20 -0
- package/dist/token-store.d.ts.map +1 -0
- package/dist/token-store.js +89 -0
- package/dist/token-store.js.map +1 -0
- package/dist/transport.d.ts +61 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +60 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +36 -3
- package/scripts/python-login.py +45 -0
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marcin Milewicz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { GarminConfig, Logger } from 'garmin-mcp-config';
|
|
2
|
+
import { browserSignIn } from './browser-login.js';
|
|
3
|
+
import type { SsoTransport } from './transport.js';
|
|
4
|
+
import type { AuthService } from './types.js';
|
|
5
|
+
/** Refresh when the access token expires within this window. */
|
|
6
|
+
export declare const REFRESH_MARGIN_MS = 900000;
|
|
7
|
+
export interface AuthServiceOptions {
|
|
8
|
+
logger?: Logger;
|
|
9
|
+
/** SSO transports in cascade order; defaults from `config.ssoStrategy`. */
|
|
10
|
+
transports?: SsoTransport[];
|
|
11
|
+
/** fetch used for token verification (and the default fetch transport). */
|
|
12
|
+
fetch?: typeof fetch;
|
|
13
|
+
now?: () => number;
|
|
14
|
+
sleep?: (ms: number) => Promise<void>;
|
|
15
|
+
/** Minimum gap between SSO requests of one session; default 3000 ms. */
|
|
16
|
+
minIntervalMs?: number;
|
|
17
|
+
/** Browser sign-in returning a service ticket; injectable for tests. */
|
|
18
|
+
browserSignIn?: typeof browserSignIn;
|
|
19
|
+
}
|
|
20
|
+
export declare function createAuthService(config: GarminConfig, opts?: AuthServiceOptions): AuthService;
|
|
21
|
+
//# sourceMappingURL=auth-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-service.d.ts","sourceRoot":"","sources":["../src/auth-service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAa9D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAE,WAAW,EAAwF,MAAM,YAAY,CAAC;AAEpI,gEAAgE;AAChE,eAAO,MAAM,iBAAiB,SAAU,CAAC;AAIzC,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;IAC5B,2EAA2E;IAC3E,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wEAAwE;IACxE,aAAa,CAAC,EAAE,OAAO,aAAa,CAAC;CACtC;AAID,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,GAAE,kBAAuB,GAAG,WAAW,CAElG"}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { assertPrivate, nativeAppHeaders, silentLogger } from 'garmin-mcp-config';
|
|
4
|
+
import { CurlTransport } from './curl-transport.js';
|
|
5
|
+
import { AuthExpiredError, CloudflareBlockedError, InvalidCredentialsError, MfaRequiredError, NetworkError, NotLoggedInError, } from './errors.js';
|
|
6
|
+
import { FetchTransport } from './fetch-transport.js';
|
|
7
|
+
import { jwtClientId, jwtExpiry } from './jwt.js';
|
|
8
|
+
import { browserSignIn } from './browser-login.js';
|
|
9
|
+
import { exchangeServiceTicket, mobileLogin, refreshDiToken } from './mobile-flow.js';
|
|
10
|
+
import { deleteTokens, importTokenFile, readTokens, writeTokens } from './token-store.js';
|
|
11
|
+
/** Refresh when the access token expires within this window. */
|
|
12
|
+
export const REFRESH_MARGIN_MS = 900_000;
|
|
13
|
+
/** A 401 right after a refresh is not the token's fault; don't refresh again. */
|
|
14
|
+
const INVALIDATE_GRACE_MS = 30_000;
|
|
15
|
+
export function createAuthService(config, opts = {}) {
|
|
16
|
+
return new GarminAuthService(config, opts);
|
|
17
|
+
}
|
|
18
|
+
class GarminAuthService {
|
|
19
|
+
config;
|
|
20
|
+
opts;
|
|
21
|
+
logger;
|
|
22
|
+
now;
|
|
23
|
+
fetchImpl;
|
|
24
|
+
tokens;
|
|
25
|
+
loadedMtime;
|
|
26
|
+
refreshing;
|
|
27
|
+
forceRefresh = false;
|
|
28
|
+
lastRefreshAt = 0;
|
|
29
|
+
transportCache;
|
|
30
|
+
constructor(config, opts) {
|
|
31
|
+
this.config = config;
|
|
32
|
+
this.opts = opts;
|
|
33
|
+
this.logger = opts.logger ?? silentLogger;
|
|
34
|
+
this.now = opts.now ?? Date.now;
|
|
35
|
+
this.fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
36
|
+
}
|
|
37
|
+
// ---- TokenProvider -------------------------------------------------------
|
|
38
|
+
async getAccessToken() {
|
|
39
|
+
if (this.refreshing)
|
|
40
|
+
return this.refreshing;
|
|
41
|
+
const tokens = this.load();
|
|
42
|
+
if (!tokens)
|
|
43
|
+
throw new NotLoggedInError(this.config.tokensFile);
|
|
44
|
+
const expiry = jwtExpiry(tokens.di_token);
|
|
45
|
+
const expiring = expiry !== undefined && expiry.getTime() - this.now() < REFRESH_MARGIN_MS;
|
|
46
|
+
if (!this.forceRefresh && !expiring)
|
|
47
|
+
return tokens.di_token;
|
|
48
|
+
this.refreshing ??= this.refresh(tokens).finally(() => {
|
|
49
|
+
this.refreshing = undefined;
|
|
50
|
+
});
|
|
51
|
+
return this.refreshing;
|
|
52
|
+
}
|
|
53
|
+
invalidate() {
|
|
54
|
+
if (!this.refreshing && this.now() - this.lastRefreshAt > INVALIDATE_GRACE_MS)
|
|
55
|
+
this.forceRefresh = true;
|
|
56
|
+
return Promise.resolve();
|
|
57
|
+
}
|
|
58
|
+
// ---- AuthService ---------------------------------------------------------
|
|
59
|
+
async login(creds, mfa) {
|
|
60
|
+
if (this.config.ssoStrategy === 'import') {
|
|
61
|
+
throw new CloudflareBlockedError('import', 0, 'Network login is disabled (GARMIN_SSO_STRATEGY=import)', 'Import tokens instead: `garmin import-tokens <file>`.');
|
|
62
|
+
}
|
|
63
|
+
const ctxBase = { config: this.config, logger: this.logger, now: this.now };
|
|
64
|
+
let blocked;
|
|
65
|
+
let lastError;
|
|
66
|
+
let tried = 0;
|
|
67
|
+
for (const transport of this.transports()) {
|
|
68
|
+
if (!(await transport.isAvailable())) {
|
|
69
|
+
this.logger.info({ strategy: transport.name }, 'SSO transport unavailable, skipping');
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
for (const profile of transport.profiles) {
|
|
73
|
+
tried++;
|
|
74
|
+
const ctx = { ...ctxBase, strategy: transport.name };
|
|
75
|
+
const session = transport.createSession(profile);
|
|
76
|
+
this.logger.debug({ strategy: transport.name, profile }, 'trying SSO login');
|
|
77
|
+
try {
|
|
78
|
+
const tokens = await mobileLogin(session, creds, mfa, ctx);
|
|
79
|
+
const verdict = await this.verify(tokens.accessToken);
|
|
80
|
+
if (verdict === 'rejected') {
|
|
81
|
+
this.logger.warn({ strategy: transport.name, profile }, 'token rejected by connectapi, trying next strategy');
|
|
82
|
+
lastError = new NetworkError(`${transport.name}: token obtained but rejected by connectapi`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
this.persist(tokens);
|
|
86
|
+
return {
|
|
87
|
+
strategy: transport.name,
|
|
88
|
+
clientId: tokens.clientId,
|
|
89
|
+
expiresAt: tokens.expiresAt ?? new Date(this.now() + 3_600_000),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
if (err instanceof InvalidCredentialsError || err instanceof MfaRequiredError)
|
|
94
|
+
throw err;
|
|
95
|
+
if (err instanceof CloudflareBlockedError) {
|
|
96
|
+
this.logger.warn({ strategy: transport.name, profile, status: err.status }, 'SSO blocked');
|
|
97
|
+
blocked = err;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
101
|
+
this.logger.warn({ strategy: transport.name, profile, err: msg }, 'SSO login attempt failed');
|
|
102
|
+
lastError = err instanceof Error ? err : new NetworkError(msg);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
await session.close().catch(() => undefined);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (blocked) {
|
|
111
|
+
throw new CloudflareBlockedError(blocked.strategy, blocked.status, `All login strategies were blocked by Garmin/Cloudflare (last: ${blocked.strategy}, HTTP ${blocked.status})`);
|
|
112
|
+
}
|
|
113
|
+
if (lastError instanceof NetworkError)
|
|
114
|
+
throw lastError;
|
|
115
|
+
if (tried === 0)
|
|
116
|
+
throw new NetworkError('No SSO transport is available (curl-impersonate missing and fetch disabled)');
|
|
117
|
+
throw new NetworkError(`Login failed: ${lastError?.message ?? 'unknown error'}`, { cause: lastError });
|
|
118
|
+
}
|
|
119
|
+
async loginWithBrowser(opts = {}) {
|
|
120
|
+
const signIn = this.opts.browserSignIn ?? browserSignIn;
|
|
121
|
+
const ticket = await signIn(this.config, {
|
|
122
|
+
...(opts.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
|
|
123
|
+
...(opts.onStatus ? { onStatus: opts.onStatus } : {}),
|
|
124
|
+
logger: this.logger,
|
|
125
|
+
});
|
|
126
|
+
opts.onStatus?.('Signed in; exchanging the ticket for tokens...');
|
|
127
|
+
const serviceUrl = this.config.hosts.portalServiceUrl;
|
|
128
|
+
let tokens;
|
|
129
|
+
let lastError;
|
|
130
|
+
for (const transport of await this.diauthTransports()) {
|
|
131
|
+
const session = transport.createSession(transport.profiles[0]);
|
|
132
|
+
try {
|
|
133
|
+
tokens = await exchangeServiceTicket(session, ticket, { config: this.config, logger: this.logger, strategy: 'browser', now: this.now }, serviceUrl);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
lastError = err instanceof Error ? err : new NetworkError(String(err));
|
|
138
|
+
// Only a Cloudflare block is worth another transport; other failures mean the ticket was refused.
|
|
139
|
+
if (!(err instanceof CloudflareBlockedError))
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
await session.close().catch(() => undefined);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (!tokens)
|
|
147
|
+
throw lastError ?? new NetworkError('Ticket exchange failed');
|
|
148
|
+
if ((await this.verify(tokens.accessToken)) === 'rejected') {
|
|
149
|
+
throw new NetworkError('Garmin issued a token that connectapi rejects for this account', {
|
|
150
|
+
cause: new Error('try `garmin login --strategy curl`'),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
this.persist(tokens, serviceUrl);
|
|
154
|
+
return { strategy: 'browser', clientId: tokens.clientId, expiresAt: tokens.expiresAt ?? new Date(this.now() + 3_600_000) };
|
|
155
|
+
}
|
|
156
|
+
importTokens(source) {
|
|
157
|
+
const imported = importTokenFile(source);
|
|
158
|
+
writeTokens(this.config.tokensFile, {
|
|
159
|
+
...imported,
|
|
160
|
+
di_client_id: imported.di_client_id ?? jwtClientId(imported.di_token) ?? null,
|
|
161
|
+
obtained_at: imported.obtained_at ?? new Date(this.now()).toISOString(),
|
|
162
|
+
schema_version: imported.schema_version ?? 1,
|
|
163
|
+
});
|
|
164
|
+
this.reset();
|
|
165
|
+
return Promise.resolve();
|
|
166
|
+
}
|
|
167
|
+
status() {
|
|
168
|
+
const tokens = this.load();
|
|
169
|
+
const base = { loggedIn: tokens !== undefined, tokensFile: this.config.tokensFile };
|
|
170
|
+
if (!tokens)
|
|
171
|
+
return Promise.resolve(base);
|
|
172
|
+
const expiresAt = jwtExpiry(tokens.di_token);
|
|
173
|
+
const clientId = tokens.di_client_id ?? jwtClientId(tokens.di_token);
|
|
174
|
+
return Promise.resolve({
|
|
175
|
+
...base,
|
|
176
|
+
...(expiresAt ? { expiresAt } : {}),
|
|
177
|
+
...(clientId ? { clientId } : {}),
|
|
178
|
+
...(tokens.obtained_at ? { obtainedAt: new Date(tokens.obtained_at) } : {}),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
logout() {
|
|
182
|
+
deleteTokens(this.config.tokensFile);
|
|
183
|
+
this.reset();
|
|
184
|
+
return Promise.resolve();
|
|
185
|
+
}
|
|
186
|
+
// ---- internals -----------------------------------------------------------
|
|
187
|
+
transports() {
|
|
188
|
+
if (this.transportCache)
|
|
189
|
+
return this.transportCache;
|
|
190
|
+
const pacing = {
|
|
191
|
+
...(this.opts.minIntervalMs === undefined ? {} : { minIntervalMs: this.opts.minIntervalMs }),
|
|
192
|
+
...(this.opts.sleep ? { sleep: this.opts.sleep } : {}),
|
|
193
|
+
now: this.now,
|
|
194
|
+
};
|
|
195
|
+
const all = this.opts.transports ?? [
|
|
196
|
+
new CurlTransport({ ...pacing, logger: this.logger }),
|
|
197
|
+
new FetchTransport({ ...pacing, fetch: this.fetchImpl }),
|
|
198
|
+
];
|
|
199
|
+
const wanted = this.config.ssoStrategy;
|
|
200
|
+
this.transportCache = wanted === 'auto' ? all : all.filter((t) => t.name === wanted);
|
|
201
|
+
return this.transportCache;
|
|
202
|
+
}
|
|
203
|
+
reset() {
|
|
204
|
+
this.tokens = undefined;
|
|
205
|
+
this.loadedMtime = undefined;
|
|
206
|
+
this.forceRefresh = false;
|
|
207
|
+
}
|
|
208
|
+
/** Lazily read the tokens file; re-read when another process (e.g. `garmin login`) rewrote it. */
|
|
209
|
+
load() {
|
|
210
|
+
const file = this.config.tokensFile;
|
|
211
|
+
let mtime;
|
|
212
|
+
try {
|
|
213
|
+
mtime = fs.statSync(file).mtimeMs;
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
mtime = undefined;
|
|
217
|
+
}
|
|
218
|
+
if (mtime === undefined) {
|
|
219
|
+
this.tokens = undefined;
|
|
220
|
+
this.loadedMtime = undefined;
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
if (this.tokens && this.loadedMtime === mtime)
|
|
224
|
+
return this.tokens;
|
|
225
|
+
if (path.dirname(file) === this.config.dataDir)
|
|
226
|
+
assertPrivate(this.config.dataDir, 'dir');
|
|
227
|
+
const replacing = this.tokens !== undefined;
|
|
228
|
+
this.tokens = readTokens(file);
|
|
229
|
+
this.loadedMtime = mtime;
|
|
230
|
+
// Fresh tokens from another process supersede a pending invalidate().
|
|
231
|
+
if (replacing)
|
|
232
|
+
this.forceRefresh = false;
|
|
233
|
+
return this.tokens;
|
|
234
|
+
}
|
|
235
|
+
persist(tokens, serviceUrl = this.config.hosts.mobileServiceUrl) {
|
|
236
|
+
const stored = {
|
|
237
|
+
di_token: tokens.accessToken,
|
|
238
|
+
di_refresh_token: tokens.refreshToken,
|
|
239
|
+
di_client_id: tokens.clientId,
|
|
240
|
+
obtained_at: new Date(this.now()).toISOString(),
|
|
241
|
+
service_url: serviceUrl,
|
|
242
|
+
schema_version: 1,
|
|
243
|
+
};
|
|
244
|
+
writeTokens(this.config.tokensFile, stored);
|
|
245
|
+
this.tokens = stored;
|
|
246
|
+
this.loadedMtime = fs.statSync(this.config.tokensFile).mtimeMs;
|
|
247
|
+
this.forceRefresh = false;
|
|
248
|
+
}
|
|
249
|
+
async refresh(current) {
|
|
250
|
+
const refreshToken = current.di_refresh_token;
|
|
251
|
+
const clientId = current.di_client_id ?? jwtClientId(current.di_token);
|
|
252
|
+
if (!refreshToken || !clientId) {
|
|
253
|
+
throw new AuthExpiredError('The access token expired and no refresh token is stored');
|
|
254
|
+
}
|
|
255
|
+
let lastStatus = 0;
|
|
256
|
+
for (const transport of await this.diauthTransports()) {
|
|
257
|
+
const session = transport.createSession(transport.profiles[0]);
|
|
258
|
+
try {
|
|
259
|
+
const outcome = await refreshDiToken(session, refreshToken, clientId, {
|
|
260
|
+
config: this.config,
|
|
261
|
+
logger: this.logger,
|
|
262
|
+
strategy: transport.name,
|
|
263
|
+
now: this.now,
|
|
264
|
+
});
|
|
265
|
+
if (outcome.ok) {
|
|
266
|
+
this.persist(outcome.tokens);
|
|
267
|
+
this.lastRefreshAt = this.now();
|
|
268
|
+
this.logger.debug({ strategy: transport.name }, 'access token refreshed');
|
|
269
|
+
return outcome.tokens.accessToken;
|
|
270
|
+
}
|
|
271
|
+
lastStatus = outcome.status;
|
|
272
|
+
if (!outcome.blocked)
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
if (err instanceof AuthExpiredError)
|
|
277
|
+
throw err;
|
|
278
|
+
lastStatus = 0;
|
|
279
|
+
this.logger.warn({ strategy: transport.name, err: err instanceof Error ? err.message : String(err) }, 'refresh failed');
|
|
280
|
+
}
|
|
281
|
+
finally {
|
|
282
|
+
await session.close().catch(() => undefined);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (lastStatus === 429 || lastStatus === 403) {
|
|
286
|
+
throw new CloudflareBlockedError('fetch', lastStatus, `Token refresh blocked by Garmin/Cloudflare (HTTP ${lastStatus})`);
|
|
287
|
+
}
|
|
288
|
+
throw new NetworkError(`Token refresh failed${lastStatus ? ` (HTTP ${lastStatus})` : ''}`);
|
|
289
|
+
}
|
|
290
|
+
/** diauth is less protected than sso: plain fetch first, then curl-impersonate when available. */
|
|
291
|
+
async diauthTransports() {
|
|
292
|
+
const all = this.opts.transports ?? [new CurlTransport({ logger: this.logger }), new FetchTransport({ fetch: this.fetchImpl, minIntervalMs: 0 })];
|
|
293
|
+
const fetchT = all.find((t) => t.name === 'fetch') ?? new FetchTransport({ fetch: this.fetchImpl, minIntervalMs: 0 });
|
|
294
|
+
const others = [];
|
|
295
|
+
// curl only when the user allowed it (auto/curl); it downloads a third-party binary on first use.
|
|
296
|
+
const curlAllowed = this.config.ssoStrategy === 'auto' || this.config.ssoStrategy === 'curl';
|
|
297
|
+
for (const t of all)
|
|
298
|
+
if (t.name !== 'fetch' && curlAllowed && (await t.isAvailable()))
|
|
299
|
+
others.push(t);
|
|
300
|
+
return [fetchT, ...others];
|
|
301
|
+
}
|
|
302
|
+
/** One cheap connectapi call so a token the API tier rejects is never saved. */
|
|
303
|
+
async verify(accessToken) {
|
|
304
|
+
try {
|
|
305
|
+
const res = await this.fetchImpl(`${this.config.hosts.connectapi}/userprofile-service/socialProfile`, {
|
|
306
|
+
headers: { ...nativeAppHeaders(this.config.app), Authorization: `Bearer ${accessToken}`, Accept: 'application/json' },
|
|
307
|
+
signal: AbortSignal.timeout(15_000),
|
|
308
|
+
});
|
|
309
|
+
if (res.status === 401 || res.status === 403)
|
|
310
|
+
return 'rejected';
|
|
311
|
+
return res.ok ? 'ok' : 'inconclusive';
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
return 'inconclusive';
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
//# sourceMappingURL=auth-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-service.js","sourceRoot":"","sources":["../src/auth-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Readable, Writable } from 'node:stream';
|
|
2
|
+
import { GarminError, type GarminConfig, type Logger } from 'garmin-mcp-config';
|
|
3
|
+
/** Raised when no Chromium-based browser can be found or started. */
|
|
4
|
+
export declare class BrowserUnavailableError extends GarminError {
|
|
5
|
+
readonly kind: "browser_unavailable";
|
|
6
|
+
constructor(detail: string);
|
|
7
|
+
}
|
|
8
|
+
/** The user closed the window or did not finish in time. */
|
|
9
|
+
export declare class BrowserLoginAbortedError extends GarminError {
|
|
10
|
+
readonly kind: "browser_login_aborted";
|
|
11
|
+
constructor(reason: string);
|
|
12
|
+
}
|
|
13
|
+
/** Path of an installed Chromium-based browser, or undefined. `configured` wins when it exists. */
|
|
14
|
+
export declare function findBrowser(configured?: string, platform?: NodeJS.Platform): string | undefined;
|
|
15
|
+
export declare function portalSignInUrl(config: Pick<GarminConfig, 'hosts'>): string;
|
|
16
|
+
/** `ST-...` from a redirect URL to the service, or undefined. */
|
|
17
|
+
export declare function ticketFromUrl(url: string): string | undefined;
|
|
18
|
+
type CdpEvent = {
|
|
19
|
+
method: string;
|
|
20
|
+
params: Record<string, unknown>;
|
|
21
|
+
sessionId?: string;
|
|
22
|
+
};
|
|
23
|
+
/** The subset of a Chrome DevTools Protocol connection the capture needs. */
|
|
24
|
+
export interface CdpConnection {
|
|
25
|
+
send(method: string, params?: Record<string, unknown>, sessionId?: string): Promise<Record<string, unknown>>;
|
|
26
|
+
onEvent(listener: (e: CdpEvent) => void): void;
|
|
27
|
+
onClose(listener: () => void): void;
|
|
28
|
+
close(): void;
|
|
29
|
+
}
|
|
30
|
+
/** CDP over the browser's DevTools WebSocket (`--remote-debugging-port`). */
|
|
31
|
+
export declare function connectCdp(wsUrl: string): Promise<CdpConnection>;
|
|
32
|
+
/**
|
|
33
|
+
* CDP over `--remote-debugging-pipe`: the browser reads commands from its fd 3
|
|
34
|
+
* and writes replies to its fd 4, each message terminated by a NUL byte. Only
|
|
35
|
+
* the parent process holds these descriptors, so nothing else on the machine
|
|
36
|
+
* can attach to the session.
|
|
37
|
+
*/
|
|
38
|
+
export declare function pipeCdp(toBrowser: Writable, fromBrowser: Readable): CdpConnection;
|
|
39
|
+
/**
|
|
40
|
+
* Drive one page: open the sign-in URL, pause the redirect to the service, take
|
|
41
|
+
* the ticket from it and answer with a local page so the single-use ticket never
|
|
42
|
+
* reaches Garmin. Resolves with the ticket.
|
|
43
|
+
*/
|
|
44
|
+
export declare function captureTicket(cdp: CdpConnection, signInUrl: string, serviceUrl: string, timeoutMs: number): Promise<string>;
|
|
45
|
+
export interface LaunchedBrowser {
|
|
46
|
+
/** `pipe` unless the browser did not answer on the pipe and the port fallback was used. */
|
|
47
|
+
transport: 'pipe' | 'port';
|
|
48
|
+
connect(): Promise<CdpConnection>;
|
|
49
|
+
close(): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Start the browser with a throw-away profile. DevTools goes over a private
|
|
53
|
+
* pipe (`--remote-debugging-pipe`), so no other local process can attach to the
|
|
54
|
+
* session and take the ticket. Only a browser that does not answer on the pipe
|
|
55
|
+
* is restarted with a localhost DevTools port, with a warning in the log.
|
|
56
|
+
* No automation flags, so the window is an ordinary browser.
|
|
57
|
+
*/
|
|
58
|
+
export declare function launchBrowser(executable: string, opts?: {
|
|
59
|
+
headless?: boolean;
|
|
60
|
+
logger?: Logger;
|
|
61
|
+
startTimeoutMs?: number;
|
|
62
|
+
}): Promise<LaunchedBrowser>;
|
|
63
|
+
/** Full browser sign-in: launch, capture the ticket, always clean up. */
|
|
64
|
+
export declare function browserSignIn(config: Pick<GarminConfig, 'hosts' | 'browserPath'>, opts?: {
|
|
65
|
+
timeoutMs?: number;
|
|
66
|
+
headless?: boolean;
|
|
67
|
+
signInUrl?: string;
|
|
68
|
+
logger?: Logger;
|
|
69
|
+
onStatus?: (m: string) => void;
|
|
70
|
+
}): Promise<string>;
|
|
71
|
+
export {};
|
|
72
|
+
//# sourceMappingURL=browser-login.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-login.d.ts","sourceRoot":"","sources":["../src/browser-login.ts"],"names":[],"mappings":"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"}
|