@abapify/adt-auth 0.4.1 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/dist/basic.d.mts +1 -2
- package/dist/basic.d.mts.map +1 -1
- package/dist/{env-CNa9YA_U.d.mts → env-Co2ABx6K.d.mts} +3 -4
- package/dist/env-Co2ABx6K.d.mts.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +8 -6
- package/dist/index.mjs.map +1 -1
- package/dist/plugins/basic.mjs.map +1 -1
- package/dist/plugins/service-key.mjs +1 -1
- package/dist/{service-key-D_fQRV_K.mjs → service-key-2AKKsYxf.mjs} +3 -3
- package/dist/service-key-2AKKsYxf.mjs.map +1 -0
- package/dist/{service-key-DiAQtcf7.mjs → service-key-eMRV2aSt.mjs} +1 -1
- package/dist/{service-key-DiAQtcf7.mjs.map → service-key-eMRV2aSt.mjs.map} +1 -1
- package/dist/service-key.d.mts +1 -2
- package/dist/service-key.d.mts.map +1 -1
- package/dist/service-key.mjs +1 -1
- package/dist/{types-C2mcG0xY.d.mts → types-C6-wI7SQ.d.mts} +1 -1
- package/dist/types-C6-wI7SQ.d.mts.map +1 -0
- package/dist/utils/env.d.mts +1 -1
- package/dist/utils/env.mjs +1 -1
- package/dist/utils/env.mjs.map +1 -1
- package/package.json +3 -3
- package/dist/env-CNa9YA_U.d.mts.map +0 -1
- package/dist/service-key-D_fQRV_K.mjs.map +0 -1
- package/dist/types-C2mcG0xY.d.mts.map +0 -1
package/README.md
CHANGED
|
@@ -39,13 +39,13 @@ const session = await authManager.login(
|
|
|
39
39
|
password: 'pass',
|
|
40
40
|
client: '100',
|
|
41
41
|
},
|
|
42
|
-
'
|
|
42
|
+
'TRL',
|
|
43
43
|
);
|
|
44
44
|
|
|
45
45
|
console.log('✅ Logged in!', session.sid);
|
|
46
46
|
|
|
47
47
|
// Later: get credentials
|
|
48
|
-
const credentials = authManager.getCredentials('
|
|
48
|
+
const credentials = authManager.getCredentials('TRL');
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
### SAP Secure Login Client (SLC)
|
|
@@ -67,7 +67,7 @@ const session = await authManager.login(
|
|
|
67
67
|
},
|
|
68
68
|
client: '200',
|
|
69
69
|
},
|
|
70
|
-
'
|
|
70
|
+
'TRL',
|
|
71
71
|
);
|
|
72
72
|
|
|
73
73
|
console.log('✅ Logged in via SLC!');
|
|
@@ -77,8 +77,8 @@ console.log('✅ Logged in via SLC!');
|
|
|
77
77
|
|
|
78
78
|
```typescript
|
|
79
79
|
// Login to multiple systems
|
|
80
|
-
await authManager.login(config1, '
|
|
81
|
-
await authManager.login(config2, '
|
|
80
|
+
await authManager.login(config1, 'TRL');
|
|
81
|
+
await authManager.login(config2, 'QAS');
|
|
82
82
|
await authManager.login(config3, 'PRD');
|
|
83
83
|
|
|
84
84
|
// List all systems
|
|
@@ -86,7 +86,7 @@ const sids = authManager.listSids();
|
|
|
86
86
|
console.log('Available systems:', sids);
|
|
87
87
|
|
|
88
88
|
// Set default
|
|
89
|
-
authManager.setDefaultSid('
|
|
89
|
+
authManager.setDefaultSid('TRL');
|
|
90
90
|
|
|
91
91
|
// Get default system credentials
|
|
92
92
|
const creds = authManager.getCredentials(); // Uses default SID
|
|
@@ -163,8 +163,8 @@ Sessions are stored in `~/.adt/sessions/` with file permissions `0600` (owner re
|
|
|
163
163
|
```
|
|
164
164
|
~/.adt/
|
|
165
165
|
└── sessions/
|
|
166
|
-
├──
|
|
167
|
-
├──
|
|
166
|
+
├── TRL.json
|
|
167
|
+
├── QAS.json
|
|
168
168
|
└── PRD.json
|
|
169
169
|
```
|
|
170
170
|
|
|
@@ -172,7 +172,7 @@ Sessions are stored in `~/.adt/sessions/` with file permissions `0600` (owner re
|
|
|
172
172
|
|
|
173
173
|
```json
|
|
174
174
|
{
|
|
175
|
-
"sid": "
|
|
175
|
+
"sid": "TRL",
|
|
176
176
|
"credentials": {
|
|
177
177
|
"method": "basic",
|
|
178
178
|
"baseUrl": "https://sap.example.com",
|
|
@@ -194,7 +194,7 @@ import { AuthManager, BasicAuthMethod } from '@abapify/adt-auth';
|
|
|
194
194
|
// Get credentials
|
|
195
195
|
const authManager = new AuthManager();
|
|
196
196
|
authManager.registerMethod(new BasicAuthMethod());
|
|
197
|
-
const credentials = authManager.getCredentials('
|
|
197
|
+
const credentials = authManager.getCredentials('TRL');
|
|
198
198
|
|
|
199
199
|
if (!credentials) {
|
|
200
200
|
throw new Error('Not authenticated. Run: adt login');
|
package/dist/basic.d.mts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { i as AuthPluginOptions, r as AuthPlugin } from "./types-
|
|
2
|
-
|
|
1
|
+
import { i as AuthPluginOptions, r as AuthPlugin } from "./types-C6-wI7SQ.mjs";
|
|
3
2
|
//#region src/plugins/basic.d.ts
|
|
4
3
|
interface BasicAuthOptions extends AuthPluginOptions {
|
|
5
4
|
username: string;
|
package/dist/basic.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"basic.d.mts","names":[],"sources":["../src/plugins/basic.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"basic.d.mts","names":[],"sources":["../src/plugins/basic.ts"],"mappings":";;UASiB,yBAAyB;EACxC;EACA;;;;;cAMI,YAAY"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { i as AuthPluginOptions, o as AuthSession } from "./types-
|
|
2
|
-
|
|
1
|
+
import { i as AuthPluginOptions, o as AuthSession } from "./types-C6-wI7SQ.mjs";
|
|
3
2
|
//#region src/storage/file-storage.d.ts
|
|
4
3
|
declare class FileStorage {
|
|
5
4
|
private readonly baseDir;
|
|
@@ -62,7 +61,7 @@ declare class AuthManager {
|
|
|
62
61
|
*
|
|
63
62
|
* Dynamically loads the plugin specified in destination.type and authenticates.
|
|
64
63
|
*
|
|
65
|
-
* @param sid - System ID (e.g., '
|
|
64
|
+
* @param sid - System ID (e.g., 'TRL')
|
|
66
65
|
* @param destination - Destination config with plugin type and options
|
|
67
66
|
*/
|
|
68
67
|
login(sid: string, destination: Destination): Promise<AuthSession>;
|
|
@@ -200,4 +199,4 @@ declare function readServiceKey(filePath: string): BTPServiceKey;
|
|
|
200
199
|
declare function resolveServiceKeyFromEnv(): Destination | null;
|
|
201
200
|
//#endregion
|
|
202
201
|
export { UAACredentials as a, FileStorage as c, ServiceKeyPluginOptions as i, resolveServiceKeyFromEnv as n, AuthManager as o, BTPServiceKey as r, Destination as s, readServiceKey as t };
|
|
203
|
-
//# sourceMappingURL=env-
|
|
202
|
+
//# sourceMappingURL=env-Co2ABx6K.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-Co2ABx6K.d.mts","names":[],"sources":["../src/storage/file-storage.ts","../src/auth-manager.ts","../src/types/service-key.ts","../src/utils/env.ts"],"mappings":";;cAuBa;mBACM;mBACA;mBACA;EAEL,YAAA;;;;EAcZ,KAAK,SAAS;;;;EASd,KAAK,cAAc;;;;EAoBnB,OAAO;;;;EAUP;;;;EAaA,OAAO;;;;EAWP,cAAc;;;;EASd;;;;EAQA;UAUQ;UAIA;UAMA;UAWA;;;;;;;UCpIO;;EAEf;;EAEA,SAAS;;cAGE;UACH;EAEI,YAAA,UAAU;;;;;;;;;EAgBhB,MAAM,aAAa,aAAa,cAAc,QAAQ;;;;UAgCpD;;;;EA0CR,YAAY,SAAS;;;;EAOrB,WAAW,eAAe;;;;EAW1B,cAAc;;;;EAOd;;;;EAWA,cAAc;;;;EAOd;;;;EAOA;;;;EAWA,UAAU,SAAS;;;;;;EAab,mBACJ,SAAS,aACT;IAAY,OAAO;MAClB,QAAQ;;;;EAyFX,gBAAgB,SAAS;;;;EAWzB,aACE,SAAS;IACN;IAAkB;;;;;UC1SR;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;;UAGe;EACf;EACA;EACA;EACA;;UAGe;EACf,KAAK;EACL;EACA;EACA;EACA,WAAW;EACX,UAAU,eAAe;EACzB,SAAS;EACT;;UAYe;EACf;EACA;EACA;;EAEA,eAAe,gBAAgB;;EAE/B;;EAEA;;EAEA;GACC;;;;;;;;;;;;iBCrDa,eAAe,mBAAmB;;;;;;;;;;;iBA6BlC,4BAA4B"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as AuthPluginResult, c as BasicCredentials, d as CookieCredentials, f as Credentials, i as AuthPluginOptions, l as ConnectionTestResult, n as AuthMethod, o as AuthSession, r as AuthPlugin, s as BasicAuthResult, t as AuthConfig, u as CookieAuthResult } from "./types-
|
|
2
|
-
import { a as UAACredentials, c as FileStorage, i as ServiceKeyPluginOptions, n as resolveServiceKeyFromEnv, o as AuthManager, r as BTPServiceKey, s as Destination, t as readServiceKey } from "./env-
|
|
1
|
+
import { a as AuthPluginResult, c as BasicCredentials, d as CookieCredentials, f as Credentials, i as AuthPluginOptions, l as ConnectionTestResult, n as AuthMethod, o as AuthSession, r as AuthPlugin, s as BasicAuthResult, t as AuthConfig, u as CookieAuthResult } from "./types-C6-wI7SQ.mjs";
|
|
2
|
+
import { a as UAACredentials, c as FileStorage, i as ServiceKeyPluginOptions, n as resolveServiceKeyFromEnv, o as AuthManager, r as BTPServiceKey, s as Destination, t as readServiceKey } from "./env-Co2ABx6K.mjs";
|
|
3
3
|
import authPlugin from "./basic.mjs";
|
|
4
4
|
import authPlugin$1 from "./service-key.mjs";
|
|
5
5
|
export { type AuthConfig, AuthManager, type AuthMethod, type AuthPlugin, type AuthPluginOptions, type AuthPluginResult, type AuthSession, type BTPServiceKey, type BasicAuthResult, type BasicCredentials, type ConnectionTestResult, type CookieAuthResult, type CookieCredentials, type Credentials, type Destination, FileStorage, type ServiceKeyPluginOptions, type UAACredentials, authPlugin as basicAuthPlugin, readServiceKey, resolveServiceKeyFromEnv, authPlugin$1 as serviceKeyAuthPlugin };
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import authPlugin from "./plugins/basic.mjs";
|
|
2
|
-
import { t as authPlugin$1 } from "./service-key-
|
|
2
|
+
import { t as authPlugin$1 } from "./service-key-2AKKsYxf.mjs";
|
|
3
3
|
import { readServiceKey, resolveServiceKeyFromEnv } from "./utils/env.mjs";
|
|
4
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "fs";
|
|
5
|
-
import { dirname, join } from "path";
|
|
6
|
-
import { homedir } from "os";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
7
|
|
|
8
8
|
//#region src/storage/file-storage.ts
|
|
9
9
|
/**
|
|
@@ -26,7 +26,9 @@ var FileStorage = class {
|
|
|
26
26
|
* Save session to file
|
|
27
27
|
*/
|
|
28
28
|
save(session) {
|
|
29
|
-
|
|
29
|
+
const filePath = this.getSessionPath(session.sid);
|
|
30
|
+
const data = JSON.stringify(session, null, 2);
|
|
31
|
+
writeFileSync(filePath, data, { mode: 384 });
|
|
30
32
|
}
|
|
31
33
|
/**
|
|
32
34
|
* Load session from file
|
|
@@ -118,7 +120,7 @@ var AuthManager = class {
|
|
|
118
120
|
*
|
|
119
121
|
* Dynamically loads the plugin specified in destination.type and authenticates.
|
|
120
122
|
*
|
|
121
|
-
* @param sid - System ID (e.g., '
|
|
123
|
+
* @param sid - System ID (e.g., 'TRL')
|
|
122
124
|
* @param destination - Destination config with plugin type and options
|
|
123
125
|
*/
|
|
124
126
|
async login(sid, destination) {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/storage/file-storage.ts","../src/auth-manager.ts"],"sourcesContent":["/**\n * File-based session storage\n *\n * Stores auth sessions in ~/.adt/sessions/\n * Stores config in ~/.adt/config.json\n */\n\nimport {\n readFileSync,\n writeFileSync,\n existsSync,\n mkdirSync,\n readdirSync,\n unlinkSync,\n} from 'fs';\nimport { join, dirname } from 'path';\nimport { homedir } from 'os';\nimport type { AuthSession } from '../types';\n\ninterface Config {\n defaultSid?: string;\n}\n\nexport class FileStorage {\n private readonly baseDir: string;\n private readonly storageDir: string;\n private readonly configPath: string;\n\n constructor(baseDir?: string) {\n this.baseDir = baseDir || join(homedir(), '.adt');\n this.storageDir = join(this.baseDir, 'sessions');\n this.configPath = join(this.baseDir, 'config.json');\n this.ensureStorageDir();\n }\n\n // ===========================================================================\n // Session Storage\n // ===========================================================================\n\n /**\n * Save session to file\n */\n save(session: AuthSession): void {\n const filePath = this.getSessionPath(session.sid);\n const data = JSON.stringify(session, null, 2);\n writeFileSync(filePath, data, { mode: 0o600 }); // Owner read/write only\n }\n\n /**\n * Load session from file\n */\n load(sid: string): AuthSession | null {\n const filePath = this.getSessionPath(sid);\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n const data = readFileSync(filePath, 'utf8');\n return JSON.parse(data) as AuthSession;\n } catch (error) {\n throw new Error(\n `Failed to load session ${sid}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n }\n\n /**\n * Delete session file\n */\n delete(sid: string): void {\n const filePath = this.getSessionPath(sid);\n if (existsSync(filePath)) {\n unlinkSync(filePath);\n }\n }\n\n /**\n * List all available SIDs\n */\n list(): string[] {\n if (!existsSync(this.storageDir)) {\n return [];\n }\n\n return readdirSync(this.storageDir)\n .filter((file) => file.endsWith('.json'))\n .map((file) => file.replace('.json', ''));\n }\n\n /**\n * Check if session exists\n */\n exists(sid: string): boolean {\n return existsSync(this.getSessionPath(sid));\n }\n\n // ===========================================================================\n // Default SID Management\n // ===========================================================================\n\n /**\n * Set default SID\n */\n setDefaultSid(sid: string): void {\n const config = this.loadConfig();\n config.defaultSid = sid;\n this.saveConfig(config);\n }\n\n /**\n * Get default SID\n */\n getDefaultSid(): string | null {\n const config = this.loadConfig();\n return config.defaultSid ?? null;\n }\n\n /**\n * Clear default SID\n */\n clearDefaultSid(): void {\n const config = this.loadConfig();\n delete config.defaultSid;\n this.saveConfig(config);\n }\n\n // ===========================================================================\n // Private Helpers\n // ===========================================================================\n\n private getSessionPath(sid: string): string {\n return join(this.storageDir, `${sid}.json`);\n }\n\n private ensureStorageDir(): void {\n if (!existsSync(this.storageDir)) {\n mkdirSync(this.storageDir, { recursive: true, mode: 0o700 }); // Owner only\n }\n }\n\n private loadConfig(): Config {\n if (!existsSync(this.configPath)) {\n return {};\n }\n try {\n return JSON.parse(readFileSync(this.configPath, 'utf8'));\n } catch {\n return {};\n }\n }\n\n private saveConfig(config: Config): void {\n mkdirSync(dirname(this.configPath), { recursive: true });\n writeFileSync(this.configPath, JSON.stringify(config, null, 2), {\n mode: 0o600,\n });\n }\n}\n","/**\n * Authentication Manager\n *\n * Single source of truth for authentication across all consumers:\n * - CLI\n * - MCP Server\n * - Other tools\n */\n\nimport type {\n AuthSession,\n CookieCredentials,\n AuthPlugin,\n AuthPluginOptions,\n AuthPluginResult,\n} from './types';\nimport { FileStorage } from './storage/file-storage';\n\n/**\n * Destination configuration (from adt.config.ts)\n */\nexport interface Destination {\n /** Plugin package to use (e.g., '@abapify/adt-puppeteer', '@abapify/adt-auth/basic') */\n type: string;\n /** Plugin-specific options */\n options: AuthPluginOptions;\n}\n\nexport class AuthManager {\n private storage: FileStorage;\n\n constructor(storage?: FileStorage) {\n this.storage = storage || new FileStorage();\n }\n\n // ===========================================================================\n // Login - Main entry point\n // ===========================================================================\n\n /**\n * Login using a destination configuration\n *\n * Dynamically loads the plugin specified in destination.type and authenticates.\n *\n * @param sid - System ID (e.g., 'BHF', 'S0D')\n * @param destination - Destination config with plugin type and options\n */\n async login(sid: string, destination: Destination): Promise<AuthSession> {\n // Dynamic import of the auth plugin\n const pluginModule = (await import(destination.type)) as {\n default?: AuthPlugin;\n };\n\n if (!pluginModule.default?.authenticate) {\n throw new Error(\n `Plugin ${destination.type} does not have a default export with authenticate method`,\n );\n }\n\n // Authenticate using the plugin\n const result = await pluginModule.default.authenticate(destination.options);\n\n // Build session based on result type\n const session = this.buildSession(sid, destination, result);\n\n // Save session\n this.saveSession(session);\n\n // Set as default if first system\n if (this.listSids().length === 1) {\n this.setDefaultSid(sid);\n }\n\n return session;\n }\n\n /**\n * Build session from plugin result\n */\n private buildSession(\n sid: string,\n destination: Destination,\n result: AuthPluginResult,\n ): AuthSession {\n if (result.method === 'cookie') {\n return {\n sid: sid.toUpperCase(),\n host: destination.options.url,\n client: destination.options.client,\n auth: {\n method: 'cookie',\n plugin: destination.type,\n pluginOptions: destination.options, // Store full plugin options for refresh fallback\n credentials: {\n cookies: result.credentials.cookies,\n expiresAt: result.credentials.expiresAt.toISOString(),\n },\n },\n };\n } else {\n return {\n sid: sid.toUpperCase(),\n host: destination.options.url,\n client: destination.options.client,\n auth: {\n method: 'basic',\n plugin: destination.type,\n pluginOptions: destination.options, // Store full plugin options for consistency\n credentials: result.credentials,\n },\n };\n }\n }\n\n // ===========================================================================\n // Session Management\n // ===========================================================================\n\n /**\n * Save a session (after successful authentication)\n */\n saveSession(session: AuthSession): void {\n this.storage.save(session);\n }\n\n /**\n * Get session by SID\n */\n getSession(sid?: string): AuthSession | null {\n const targetSid = sid || this.getDefaultSid();\n if (!targetSid) {\n return null;\n }\n return this.storage.load(targetSid);\n }\n\n /**\n * Delete session (logout)\n */\n deleteSession(sid: string): void {\n this.storage.delete(sid);\n }\n\n /**\n * List all available SIDs\n */\n listSids(): string[] {\n return this.storage.list();\n }\n\n // ===========================================================================\n // Default SID Management\n // ===========================================================================\n\n /**\n * Set default SID (persisted to config)\n */\n setDefaultSid(sid: string): void {\n this.storage.setDefaultSid(sid);\n }\n\n /**\n * Get default SID\n */\n getDefaultSid(): string | null {\n return this.storage.getDefaultSid() || (this.storage.list()[0] ?? null);\n }\n\n /**\n * Clear default SID\n */\n clearDefaultSid(): void {\n this.storage.clearDefaultSid();\n }\n\n // ===========================================================================\n // Credential Helpers\n // ===========================================================================\n\n /**\n * Check if session credentials are expired\n */\n isExpired(session: AuthSession): boolean {\n if (session.auth.method !== 'cookie') {\n return false; // Basic auth doesn't expire\n }\n const creds = session.auth.credentials as CookieCredentials;\n return new Date(creds.expiresAt) < new Date();\n }\n\n /**\n * Refresh credentials using the auth plugin\n *\n * @returns Updated session with new credentials, or null if refresh not supported/failed\n */\n async refreshCredentials(\n session: AuthSession,\n options?: { log?: (message: string) => void },\n ): Promise<AuthSession | null> {\n const log = options?.log ?? console.log;\n\n if (!session.auth.plugin) {\n return null; // No plugin = can't refresh\n }\n\n // Dynamic import of the auth plugin (expects default export)\n const pluginModule = (await import(session.auth.plugin)) as {\n default?: AuthPlugin;\n };\n\n if (!pluginModule.default) {\n throw new Error(\n `Plugin ${session.auth.plugin} does not have a default export`,\n );\n }\n\n const plugin = pluginModule.default;\n\n // Try plugin's refresh method first (preferred for session-based auth)\n if (plugin.refresh) {\n const sessionWithLog: AuthSession = {\n ...session,\n auth: {\n ...session.auth,\n pluginOptions: {\n url: session.host,\n client: session.client,\n ...session.auth.pluginOptions,\n log,\n },\n },\n };\n const result = await plugin.refresh(sessionWithLog);\n\n if (result) {\n // Refresh succeeded - build and save updated session\n // Preserve original plugin options (including userDataDir)\n const destination: Destination = {\n type: session.auth.plugin,\n options: session.auth.pluginOptions || {\n url: session.host,\n client: session.client,\n },\n };\n\n const updatedSession = this.buildSession(\n session.sid,\n destination,\n result,\n );\n this.saveSession(updatedSession);\n\n return updatedSession;\n }\n\n // Refresh failed - fall through to interactive authenticate\n log(\n '⚠️ Silent refresh failed - falling back to interactive authentication...',\n );\n }\n\n // Fallback: Call authenticate (full re-auth with browser interaction)\n // Use stored plugin options to preserve settings like userDataDir\n const baseOptions: AuthPluginOptions = session.auth.pluginOptions || {\n url: session.host,\n client: session.client,\n };\n const runtimeOptions = { ...baseOptions, log };\n\n const result = await plugin.authenticate(runtimeOptions);\n\n // Build destination for buildSession\n const destination: Destination = {\n type: session.auth.plugin,\n options: baseOptions,\n };\n\n // Build and save updated session\n const updatedSession = this.buildSession(session.sid, destination, result);\n this.saveSession(updatedSession);\n\n return updatedSession;\n }\n\n /**\n * Get cookie header for HTTP client (convenience method)\n */\n getCookieHeader(session: AuthSession): string | null {\n if (session.auth.method !== 'cookie') {\n return null;\n }\n const creds = session.auth.credentials as CookieCredentials;\n return creds.cookies;\n }\n\n /**\n * Get basic auth credentials (convenience method)\n */\n getBasicAuth(\n session: AuthSession,\n ): { username: string; password: string } | null {\n if (session.auth.method !== 'basic') {\n return null;\n }\n return session.auth.credentials as { username: string; password: string };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/storage/file-storage.ts","../src/auth-manager.ts"],"sourcesContent":["/**\n * File-based session storage\n *\n * Stores auth sessions in ~/.adt/sessions/\n * Stores config in ~/.adt/config.json\n */\n\nimport {\n readFileSync,\n writeFileSync,\n existsSync,\n mkdirSync,\n readdirSync,\n unlinkSync,\n} from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { AuthSession } from '../types';\n\ninterface Config {\n defaultSid?: string;\n}\n\nexport class FileStorage {\n private readonly baseDir: string;\n private readonly storageDir: string;\n private readonly configPath: string;\n\n constructor(baseDir?: string) {\n this.baseDir = baseDir || join(homedir(), '.adt');\n this.storageDir = join(this.baseDir, 'sessions');\n this.configPath = join(this.baseDir, 'config.json');\n this.ensureStorageDir();\n }\n\n // ===========================================================================\n // Session Storage\n // ===========================================================================\n\n /**\n * Save session to file\n */\n save(session: AuthSession): void {\n const filePath = this.getSessionPath(session.sid);\n const data = JSON.stringify(session, null, 2);\n writeFileSync(filePath, data, { mode: 0o600 }); // Owner read/write only\n }\n\n /**\n * Load session from file\n */\n load(sid: string): AuthSession | null {\n const filePath = this.getSessionPath(sid);\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n const data = readFileSync(filePath, 'utf8');\n return JSON.parse(data) as AuthSession;\n } catch (error) {\n throw new Error(\n `Failed to load session ${sid}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n }\n\n /**\n * Delete session file\n */\n delete(sid: string): void {\n const filePath = this.getSessionPath(sid);\n if (existsSync(filePath)) {\n unlinkSync(filePath);\n }\n }\n\n /**\n * List all available SIDs\n */\n list(): string[] {\n if (!existsSync(this.storageDir)) {\n return [];\n }\n\n return readdirSync(this.storageDir)\n .filter((file) => file.endsWith('.json'))\n .map((file) => file.replace('.json', ''));\n }\n\n /**\n * Check if session exists\n */\n exists(sid: string): boolean {\n return existsSync(this.getSessionPath(sid));\n }\n\n // ===========================================================================\n // Default SID Management\n // ===========================================================================\n\n /**\n * Set default SID\n */\n setDefaultSid(sid: string): void {\n const config = this.loadConfig();\n config.defaultSid = sid;\n this.saveConfig(config);\n }\n\n /**\n * Get default SID\n */\n getDefaultSid(): string | null {\n const config = this.loadConfig();\n return config.defaultSid ?? null;\n }\n\n /**\n * Clear default SID\n */\n clearDefaultSid(): void {\n const config = this.loadConfig();\n delete config.defaultSid;\n this.saveConfig(config);\n }\n\n // ===========================================================================\n // Private Helpers\n // ===========================================================================\n\n private getSessionPath(sid: string): string {\n return join(this.storageDir, `${sid}.json`);\n }\n\n private ensureStorageDir(): void {\n if (!existsSync(this.storageDir)) {\n mkdirSync(this.storageDir, { recursive: true, mode: 0o700 }); // Owner only\n }\n }\n\n private loadConfig(): Config {\n if (!existsSync(this.configPath)) {\n return {};\n }\n try {\n return JSON.parse(readFileSync(this.configPath, 'utf8'));\n } catch {\n return {};\n }\n }\n\n private saveConfig(config: Config): void {\n mkdirSync(dirname(this.configPath), { recursive: true });\n writeFileSync(this.configPath, JSON.stringify(config, null, 2), {\n mode: 0o600,\n });\n }\n}\n","/**\n * Authentication Manager\n *\n * Single source of truth for authentication across all consumers:\n * - CLI\n * - MCP Server\n * - Other tools\n */\n\nimport type {\n AuthSession,\n CookieCredentials,\n AuthPlugin,\n AuthPluginOptions,\n AuthPluginResult,\n} from './types';\nimport { FileStorage } from './storage/file-storage';\n\n/**\n * Destination configuration (from adt.config.ts)\n */\nexport interface Destination {\n /** Plugin package to use (e.g., '@abapify/adt-puppeteer', '@abapify/adt-auth/basic') */\n type: string;\n /** Plugin-specific options */\n options: AuthPluginOptions;\n}\n\nexport class AuthManager {\n private storage: FileStorage;\n\n constructor(storage?: FileStorage) {\n this.storage = storage || new FileStorage();\n }\n\n // ===========================================================================\n // Login - Main entry point\n // ===========================================================================\n\n /**\n * Login using a destination configuration\n *\n * Dynamically loads the plugin specified in destination.type and authenticates.\n *\n * @param sid - System ID (e.g., 'TRL')\n * @param destination - Destination config with plugin type and options\n */\n async login(sid: string, destination: Destination): Promise<AuthSession> {\n // Dynamic import of the auth plugin\n const pluginModule = (await import(destination.type)) as {\n default?: AuthPlugin;\n };\n\n if (!pluginModule.default?.authenticate) {\n throw new Error(\n `Plugin ${destination.type} does not have a default export with authenticate method`,\n );\n }\n\n // Authenticate using the plugin\n const result = await pluginModule.default.authenticate(destination.options);\n\n // Build session based on result type\n const session = this.buildSession(sid, destination, result);\n\n // Save session\n this.saveSession(session);\n\n // Set as default if first system\n if (this.listSids().length === 1) {\n this.setDefaultSid(sid);\n }\n\n return session;\n }\n\n /**\n * Build session from plugin result\n */\n private buildSession(\n sid: string,\n destination: Destination,\n result: AuthPluginResult,\n ): AuthSession {\n if (result.method === 'cookie') {\n return {\n sid: sid.toUpperCase(),\n host: destination.options.url,\n client: destination.options.client,\n auth: {\n method: 'cookie',\n plugin: destination.type,\n pluginOptions: destination.options, // Store full plugin options for refresh fallback\n credentials: {\n cookies: result.credentials.cookies,\n expiresAt: result.credentials.expiresAt.toISOString(),\n },\n },\n };\n } else {\n return {\n sid: sid.toUpperCase(),\n host: destination.options.url,\n client: destination.options.client,\n auth: {\n method: 'basic',\n plugin: destination.type,\n pluginOptions: destination.options, // Store full plugin options for consistency\n credentials: result.credentials,\n },\n };\n }\n }\n\n // ===========================================================================\n // Session Management\n // ===========================================================================\n\n /**\n * Save a session (after successful authentication)\n */\n saveSession(session: AuthSession): void {\n this.storage.save(session);\n }\n\n /**\n * Get session by SID\n */\n getSession(sid?: string): AuthSession | null {\n const targetSid = sid || this.getDefaultSid();\n if (!targetSid) {\n return null;\n }\n return this.storage.load(targetSid);\n }\n\n /**\n * Delete session (logout)\n */\n deleteSession(sid: string): void {\n this.storage.delete(sid);\n }\n\n /**\n * List all available SIDs\n */\n listSids(): string[] {\n return this.storage.list();\n }\n\n // ===========================================================================\n // Default SID Management\n // ===========================================================================\n\n /**\n * Set default SID (persisted to config)\n */\n setDefaultSid(sid: string): void {\n this.storage.setDefaultSid(sid);\n }\n\n /**\n * Get default SID\n */\n getDefaultSid(): string | null {\n return this.storage.getDefaultSid() || (this.storage.list()[0] ?? null);\n }\n\n /**\n * Clear default SID\n */\n clearDefaultSid(): void {\n this.storage.clearDefaultSid();\n }\n\n // ===========================================================================\n // Credential Helpers\n // ===========================================================================\n\n /**\n * Check if session credentials are expired\n */\n isExpired(session: AuthSession): boolean {\n if (session.auth.method !== 'cookie') {\n return false; // Basic auth doesn't expire\n }\n const creds = session.auth.credentials as CookieCredentials;\n return new Date(creds.expiresAt) < new Date();\n }\n\n /**\n * Refresh credentials using the auth plugin\n *\n * @returns Updated session with new credentials, or null if refresh not supported/failed\n */\n async refreshCredentials(\n session: AuthSession,\n options?: { log?: (message: string) => void },\n ): Promise<AuthSession | null> {\n const log = options?.log ?? console.log;\n\n if (!session.auth.plugin) {\n return null; // No plugin = can't refresh\n }\n\n // Dynamic import of the auth plugin (expects default export)\n const pluginModule = (await import(session.auth.plugin)) as {\n default?: AuthPlugin;\n };\n\n if (!pluginModule.default) {\n throw new Error(\n `Plugin ${session.auth.plugin} does not have a default export`,\n );\n }\n\n const plugin = pluginModule.default;\n\n // Try plugin's refresh method first (preferred for session-based auth)\n if (plugin.refresh) {\n const sessionWithLog: AuthSession = {\n ...session,\n auth: {\n ...session.auth,\n pluginOptions: {\n url: session.host,\n client: session.client,\n ...session.auth.pluginOptions,\n log,\n },\n },\n };\n const result = await plugin.refresh(sessionWithLog);\n\n if (result) {\n // Refresh succeeded - build and save updated session\n // Preserve original plugin options (including userDataDir)\n const destination: Destination = {\n type: session.auth.plugin,\n options: session.auth.pluginOptions || {\n url: session.host,\n client: session.client,\n },\n };\n\n const updatedSession = this.buildSession(\n session.sid,\n destination,\n result,\n );\n this.saveSession(updatedSession);\n\n return updatedSession;\n }\n\n // Refresh failed - fall through to interactive authenticate\n log(\n '⚠️ Silent refresh failed - falling back to interactive authentication...',\n );\n }\n\n // Fallback: Call authenticate (full re-auth with browser interaction)\n // Use stored plugin options to preserve settings like userDataDir\n const baseOptions: AuthPluginOptions = session.auth.pluginOptions || {\n url: session.host,\n client: session.client,\n };\n const runtimeOptions = { ...baseOptions, log };\n\n const result = await plugin.authenticate(runtimeOptions);\n\n // Build destination for buildSession\n const destination: Destination = {\n type: session.auth.plugin,\n options: baseOptions,\n };\n\n // Build and save updated session\n const updatedSession = this.buildSession(session.sid, destination, result);\n this.saveSession(updatedSession);\n\n return updatedSession;\n }\n\n /**\n * Get cookie header for HTTP client (convenience method)\n */\n getCookieHeader(session: AuthSession): string | null {\n if (session.auth.method !== 'cookie') {\n return null;\n }\n const creds = session.auth.credentials as CookieCredentials;\n return creds.cookies;\n }\n\n /**\n * Get basic auth credentials (convenience method)\n */\n getBasicAuth(\n session: AuthSession,\n ): { username: string; password: string } | null {\n if (session.auth.method !== 'basic') {\n return null;\n }\n return session.auth.credentials as { username: string; password: string };\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"basic.mjs","names":[],"sources":["../../src/plugins/basic.ts"],"sourcesContent":["/**\n * Built-in Basic Auth Plugin\n *\n * Simple username/password authentication\n * No external dependencies\n */\n\nimport type { AuthPlugin, AuthPluginOptions, BasicAuthResult } from '../types';\n\nexport interface BasicAuthOptions extends AuthPluginOptions {\n username: string;\n password: string;\n}\n\n/**\n * Basic auth plugin - uses provided credentials\n */\nconst authPlugin: AuthPlugin = {\n async authenticate(options: BasicAuthOptions): Promise<BasicAuthResult> {\n const { username, password } = options;\n\n if (!username || !password) {\n throw new Error('Basic auth requires username and password');\n }\n\n return {\n method: 'basic',\n credentials: {\n username,\n password,\n },\n };\n },\n};\n\nexport default authPlugin;\n"],"mappings":";;;;AAiBA,MAAM,aAAyB,EAC7B,MAAM,aAAa,SAAqD;CACtE,MAAM,EAAE,UAAU,aAAa;
|
|
1
|
+
{"version":3,"file":"basic.mjs","names":[],"sources":["../../src/plugins/basic.ts"],"sourcesContent":["/**\n * Built-in Basic Auth Plugin\n *\n * Simple username/password authentication\n * No external dependencies\n */\n\nimport type { AuthPlugin, AuthPluginOptions, BasicAuthResult } from '../types';\n\nexport interface BasicAuthOptions extends AuthPluginOptions {\n username: string;\n password: string;\n}\n\n/**\n * Basic auth plugin - uses provided credentials\n */\nconst authPlugin: AuthPlugin = {\n async authenticate(options: BasicAuthOptions): Promise<BasicAuthResult> {\n const { username, password } = options;\n\n if (!username || !password) {\n throw new Error('Basic auth requires username and password');\n }\n\n return {\n method: 'basic',\n credentials: {\n username,\n password,\n },\n };\n },\n};\n\nexport default authPlugin;\n"],"mappings":";;;;AAiBA,MAAM,aAAyB,EAC7B,MAAM,aAAa,SAAqD;CACtE,MAAM,EAAE,UAAU,aAAa;CAE/B,IAAI,CAAC,YAAY,CAAC,UAChB,MAAM,IAAI,MAAM,2CAA2C;CAG7D,OAAO;EACL,QAAQ;EACR,aAAa;GACX;GACA;EACF;CACF;AACF,EACF"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { t as ServiceKeyParser } from "./service-key-
|
|
1
|
+
import { t as ServiceKeyParser } from "./service-key-eMRV2aSt.mjs";
|
|
2
2
|
import { createServer } from "node:http";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
import { parse } from "node:url";
|
|
5
|
-
import { createHash, randomBytes } from "crypto";
|
|
5
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
6
6
|
|
|
7
7
|
//#region src/utils/pkce.ts
|
|
8
8
|
/**
|
|
@@ -231,4 +231,4 @@ const authPlugin = { async authenticate(options) {
|
|
|
231
231
|
|
|
232
232
|
//#endregion
|
|
233
233
|
export { authPlugin as t };
|
|
234
|
-
//# sourceMappingURL=service-key-
|
|
234
|
+
//# sourceMappingURL=service-key-2AKKsYxf.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-key-2AKKsYxf.mjs","names":["parseUrl"],"sources":["../src/utils/pkce.ts","../src/plugins/service-key.ts"],"sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\n\n/**\n * Generates a cryptographically secure code verifier for PKCE flow\n */\nexport function generateCodeVerifier(): string {\n return randomBytes(32).toString('base64url');\n}\n\n/**\n * Generates a code challenge from the verifier using SHA256\n */\nexport function generateCodeChallenge(verifier: string): string {\n return createHash('sha256').update(verifier).digest('base64url');\n}\n\n/**\n * Generates a random state parameter for OAuth flow\n */\nexport function generateState(): string {\n return randomBytes(16).toString('hex');\n}\n","import { createServer, type Server } from 'node:http';\nimport { spawn } from 'node:child_process';\nimport { parse as parseUrl } from 'node:url';\nimport type { AuthPlugin, AuthPluginOptions, CookieAuthResult } from '../types';\nimport {\n ServiceKeyParser,\n type BTPServiceKey,\n type ServiceKeyPluginOptions,\n} from '../types/service-key';\nimport {\n generateCodeVerifier,\n generateCodeChallenge,\n generateState,\n} from '../utils/pkce';\n\nconst DEFAULT_PORT = 3000;\nconst DEFAULT_REDIRECT_PATH = '/callback';\nconst DEFAULT_TIMEOUT_MS = 300_000; // 5 minutes\nconst MAX_PORT_RETRIES = 5; // Try up to 5 consecutive ports on EADDRINUSE\n\n/**\n * Copy text to system clipboard (best-effort, never throws).\n * Tries platform-appropriate commands: pbcopy (macOS), clip.exe (WSL), xclip/xsel (Linux).\n */\nasync function copyToClipboard(text: string): Promise<void> {\n const candidates =\n process.platform === 'darwin'\n ? [['pbcopy']]\n : process.platform === 'win32'\n ? [['clip']]\n : [\n ['clip.exe'],\n ['xclip', '-selection', 'clipboard'],\n ['xsel', '--clipboard'],\n ];\n\n for (const [cmd, ...args] of candidates) {\n try {\n await new Promise<void>((resolve, reject) => {\n const child = spawn(cmd, args, {\n stdio: ['pipe', 'ignore', 'ignore'],\n });\n child.on('error', reject);\n child.on('close', (code) =>\n code === 0\n ? resolve()\n : reject(new Error(`${cmd} exited with code ${code}`)),\n );\n child.stdin.write(text);\n child.stdin.end();\n });\n return;\n } catch {\n continue;\n }\n }\n throw new Error('No clipboard utility available');\n}\n\ninterface OAuthTokenResponse {\n access_token: string;\n token_type: string;\n expires_in: number;\n scope?: string;\n refresh_token?: string;\n}\n\n/**\n * Exchange an authorization code for tokens using the PKCE flow.\n */\nasync function exchangeCodeForToken(\n serviceKey: BTPServiceKey,\n code: string,\n codeVerifier: string,\n redirectUri: string,\n): Promise<OAuthTokenResponse> {\n const { clientid, clientsecret, url: uaaUrl } = serviceKey.uaa;\n const credentials = Buffer.from(`${clientid}:${clientsecret}`).toString(\n 'base64',\n );\n\n const response = await fetch(`${uaaUrl}/oauth/token`, {\n method: 'POST',\n headers: {\n Authorization: `Basic ${credentials}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n redirect_uri: redirectUri,\n code_verifier: codeVerifier,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`Token exchange failed (${response.status}): ${text}`);\n }\n\n const data = (await response.json()) as OAuthTokenResponse;\n\n if (!data.access_token) {\n throw new Error('Token response missing access_token');\n }\n\n return data;\n}\n\n/**\n * Run the OAuth PKCE authorization code flow:\n * 1. Start local callback server\n * 2. Build XSUAA authorize URL with PKCE\n * 3. Open browser for user login\n * 4. Receive auth code via redirect\n * 5. Exchange code for user token\n */\nasync function performPkceFlow(\n serviceKey: BTPServiceKey,\n openBrowser: (url: string) => Promise<void>,\n port: number,\n timeoutMs: number,\n redirectUri?: string,\n): Promise<OAuthTokenResponse> {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = generateCodeChallenge(codeVerifier);\n const state = generateState();\n\n const resolvedRedirectUri =\n redirectUri ?? `http://localhost:${port}${DEFAULT_REDIRECT_PATH}`;\n const callbackPath = (() => {\n try {\n const pathname = new URL(resolvedRedirectUri).pathname;\n return pathname && pathname.length > 0 ? pathname : '/';\n } catch {\n return DEFAULT_REDIRECT_PATH;\n }\n })();\n\n // Build XSUAA authorize URL\n const authUrl = new URL(`${serviceKey.uaa.url}/oauth/authorize`);\n authUrl.searchParams.set('client_id', serviceKey.uaa.clientid);\n authUrl.searchParams.set('response_type', 'code');\n authUrl.searchParams.set('redirect_uri', resolvedRedirectUri);\n authUrl.searchParams.set('code_challenge', codeChallenge);\n authUrl.searchParams.set('code_challenge_method', 'S256');\n authUrl.searchParams.set('state', state);\n authUrl.searchParams.set('scope', 'openid');\n\n return new Promise<OAuthTokenResponse>((resolve, reject) => {\n // eslint-disable-next-line prefer-const -- server must be declared before timeout but assigned after\n let server: Server;\n\n const timeout = setTimeout(() => {\n server?.close();\n reject(new Error('Authentication timed out'));\n }, timeoutMs);\n\n server = createServer(async (req, res) => {\n try {\n const url = parseUrl(req.url || '', true);\n\n if (url.pathname !== callbackPath) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n return;\n }\n\n const {\n code,\n state: returnedState,\n error,\n error_description,\n } = url.query;\n\n if (error) {\n const errorSuffix = error_description\n ? ` - ${error_description}`\n : '';\n throw new Error(`OAuth error: ${error}${errorSuffix}`);\n }\n\n if (returnedState !== state) {\n throw new Error('State mismatch - possible CSRF attack');\n }\n\n if (!code) {\n throw new Error('No authorization code received');\n }\n\n const tokenData = await exchangeCodeForToken(\n serviceKey,\n code as string,\n codeVerifier,\n resolvedRedirectUri,\n );\n\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(\n '<html><body style=\"font-family:Arial;text-align:center;padding:50px\">' +\n '<h2>Authentication successful!</h2>' +\n '<p>You can close this window and return to the terminal.</p>' +\n '<script>setTimeout(()=>window.close(),2000)</script>' +\n '</body></html>',\n );\n\n clearTimeout(timeout);\n setTimeout(() => {\n server.close();\n resolve(tokenData);\n }, 500);\n } catch (err) {\n // Escape error message before embedding into HTML response to prevent\n // reflected XSS / XSS-through-exception. The error message may contain\n // data derived from the (attacker-controllable) OAuth callback URL\n // (e.g. error_description) and must never be treated as trusted.\n const rawMessage = err instanceof Error ? err.message : String(err);\n const escaped = rawMessage.replaceAll(/[&<>\"']/g, (c) => {\n switch (c) {\n case '&':\n return '&';\n case '<':\n return '<';\n case '>':\n return '>';\n case '\"':\n return '"';\n default:\n return ''';\n }\n });\n res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(\n '<html><body style=\"font-family:Arial;text-align:center;padding:50px\">' +\n `<h2>Authentication failed</h2><p>${escaped}</p>` +\n '</body></html>',\n );\n\n clearTimeout(timeout);\n server.close(() => reject(err));\n }\n });\n\n server.on('error', (err) => {\n clearTimeout(timeout);\n reject(err);\n });\n\n server.listen(port, () => {\n openBrowser(authUrl.toString()).catch((err) => {\n clearTimeout(timeout);\n server.close();\n reject(\n new Error('Could not open browser for authentication', {\n cause: err,\n }),\n );\n });\n });\n });\n}\n\nconst authPlugin: AuthPlugin = {\n async authenticate(options: AuthPluginOptions): Promise<CookieAuthResult> {\n const { serviceKey, openBrowser, callbackPort, timeoutMs, redirectUri } =\n options as ServiceKeyPluginOptions;\n\n if (!serviceKey) {\n throw new Error('service-key plugin requires a serviceKey option');\n }\n\n const parsed = ServiceKeyParser.parse(serviceKey);\n\n // Resolve openBrowser: use provided callback, or dynamically import 'open'\n let browserOpener: (url: string) => Promise<void>;\n if (openBrowser) {\n browserOpener = openBrowser;\n } else {\n // Fallback: try dynamic import of 'open' package\n try {\n const openModule = await import('open');\n const openFn = openModule.default;\n browserOpener = async (url: string) => {\n await openFn(url);\n };\n } catch {\n throw new Error(\n 'No openBrowser callback provided and \"open\" package not available. ' +\n 'Install \"open\" or pass an openBrowser function in plugin options.',\n );\n }\n }\n\n const startPort =\n typeof callbackPort === 'number' ? callbackPort : DEFAULT_PORT;\n const timeout =\n typeof timeoutMs === 'number' ? timeoutMs : DEFAULT_TIMEOUT_MS;\n\n // Extract log from options (passed by AuthManager during refresh, or by CLI)\n const log = (options as { log?: (msg: string) => void }).log ?? console.log;\n\n // Wrap browser opener: always print the URL and try clipboard copy before opening\n const wrappedBrowserOpener = async (url: string) => {\n log(`🔗 Open this URL to authenticate:\\n ${url}`);\n\n try {\n await copyToClipboard(url);\n log('📋 URL copied to clipboard');\n } catch {\n // Clipboard not available — URL is already printed above\n }\n\n await browserOpener(url);\n };\n\n // Try ports starting from startPort, retrying on EADDRINUSE\n const hasExplicitRedirect = typeof redirectUri === 'string';\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < MAX_PORT_RETRIES; attempt++) {\n const port = startPort + attempt;\n // When redirectUri is explicitly set, use it as-is (don't rewrite for new port)\n const effectiveRedirectUri = hasExplicitRedirect\n ? redirectUri\n : undefined;\n\n try {\n const tokenData = await performPkceFlow(\n parsed,\n wrappedBrowserOpener,\n port,\n timeout,\n effectiveRedirectUri,\n );\n\n const expiresAt = new Date(Date.now() + tokenData.expires_in * 1000);\n\n return {\n method: 'cookie',\n credentials: {\n cookies: `Authorization: Bearer ${tokenData.access_token}`,\n expiresAt,\n },\n };\n } catch (err) {\n if (\n err instanceof Error &&\n 'code' in err &&\n (err as NodeJS.ErrnoException).code === 'EADDRINUSE'\n ) {\n // When an explicit redirectUri is set, retrying on a different port\n // is pointless because the OAuth callback will still go to the\n // original port encoded in the redirect URI.\n if (hasExplicitRedirect) {\n throw new Error(\n `Port ${port} is in use and redirectUri is explicitly set — cannot retry on a different port`,\n { cause: err },\n );\n }\n lastError = err;\n log(`⚠️ Port ${port} in use, trying ${port + 1}...`);\n continue;\n }\n throw err; // Non-port error — rethrow immediately\n }\n }\n\n throw new Error(\n `All ports ${startPort}-${startPort + MAX_PORT_RETRIES - 1} are in use. ` +\n `Free a port or set callbackPort in plugin options.`,\n { cause: lastError },\n );\n },\n};\n\nexport default authPlugin;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-key-
|
|
1
|
+
{"version":3,"file":"service-key-eMRV2aSt.mjs","names":[],"sources":["../src/types/service-key.ts"],"sourcesContent":["// BTP Service Key types\n\nexport interface UAACredentials {\n tenantmode: string;\n sburl: string;\n subaccountid: string;\n 'credential-type': string;\n clientid: string;\n xsappname: string;\n clientsecret: string;\n serviceInstanceId: string;\n url: string;\n uaadomain: string;\n verificationkey: string;\n apiurl: string;\n identityzone: string;\n identityzoneid: string;\n tenantid: string;\n zoneid: string;\n}\n\nexport interface Catalog {\n path: string;\n type: string;\n}\n\nexport interface Binding {\n env: string;\n version: string;\n type: string;\n id: string;\n}\n\nexport interface BTPServiceKey {\n uaa: UAACredentials;\n url: string;\n 'sap.cloud.service': string;\n systemid: string;\n endpoints: Record<string, string>;\n catalogs: Record<string, Catalog>;\n binding: Binding;\n preserve_host_header: boolean;\n}\n\nexport interface OAuthToken {\n access_token: string;\n token_type: string;\n expires_in: number;\n scope: string;\n refresh_token?: string;\n expires_at: Date;\n}\n\nexport interface ServiceKeyPluginOptions {\n url: string;\n client?: string;\n serviceKey: string | object;\n /** Callback to open a URL in the user's browser. Falls back to dynamic import of 'open'. */\n openBrowser?: (url: string) => Promise<void>;\n /** Port for the local OAuth callback server (default: 3000) */\n callbackPort?: number;\n /** Timeout for the auth flow in ms (default: 300000 = 5 min) */\n timeoutMs?: number;\n /** Full redirect URI for the OAuth callback (e.g. in tunnelled/forwarded environments). Defaults to http://localhost:<port>/callback */\n redirectUri?: string;\n [key: string]: unknown;\n}\n\nexport class ServiceKeyParser {\n static parse(serviceKeyJson: string | object): BTPServiceKey {\n let parsed: unknown;\n\n if (typeof serviceKeyJson === 'string') {\n try {\n parsed = JSON.parse(serviceKeyJson);\n } catch (error) {\n throw new Error('Invalid JSON format in service key', { cause: error });\n }\n } else {\n parsed = serviceKeyJson;\n }\n\n if (!parsed || typeof parsed !== 'object') {\n throw new Error('Invalid service key format');\n }\n\n const serviceKey = parsed as Record<string, unknown>;\n\n if (!serviceKey['uaa'] || !serviceKey['url'] || !serviceKey['systemid']) {\n throw new Error('Missing required fields in service key');\n }\n\n return serviceKey as unknown as BTPServiceKey;\n }\n}\n"],"mappings":";AAoEA,IAAa,mBAAb,MAA8B;CAC5B,OAAO,MAAM,gBAAgD;EAC3D,IAAI;EAEJ,IAAI,OAAO,mBAAmB,UAC5B,IAAI;GACF,SAAS,KAAK,MAAM,cAAc;EACpC,SAAS,OAAO;GACd,MAAM,IAAI,MAAM,sCAAsC,EAAE,OAAO,MAAM,CAAC;EACxE;OAEA,SAAS;EAGX,IAAI,CAAC,UAAU,OAAO,WAAW,UAC/B,MAAM,IAAI,MAAM,4BAA4B;EAG9C,MAAM,aAAa;EAEnB,IAAI,CAAC,WAAW,UAAU,CAAC,WAAW,UAAU,CAAC,WAAW,aAC1D,MAAM,IAAI,MAAM,wCAAwC;EAG1D,OAAO;CACT;AACF"}
|
package/dist/service-key.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-key.d.mts","names":[],"sources":["../src/plugins/service-key.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"service-key.d.mts","names":[],"sources":["../src/plugins/service-key.ts"],"mappings":";;cAsQM,YAAY"}
|
package/dist/service-key.mjs
CHANGED
|
@@ -104,4 +104,4 @@ interface ConnectionTestResult {
|
|
|
104
104
|
}
|
|
105
105
|
//#endregion
|
|
106
106
|
export { AuthPluginResult as a, BasicCredentials as c, CookieCredentials as d, Credentials as f, AuthPluginOptions as i, ConnectionTestResult as l, AuthMethod as n, AuthSession as o, AuthPlugin as r, BasicAuthResult as s, AuthConfig as t, CookieAuthResult as u };
|
|
107
|
-
//# sourceMappingURL=types-
|
|
107
|
+
//# sourceMappingURL=types-C6-wI7SQ.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types-C6-wI7SQ.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;;;;;KAgBY;;;;UAKK;EACf;EACA;;;;;UAMe;EACf;EACA;;;;;KAMU,cAAc,mBAAmB;;;;;;;;;UAc5B;EACf,QAAQ;EACR;EACA,gBAAgB;EAChB,aAAa;;;;;;;;;;UAeE;EACf;EACA;EACA;EACA,MAAM;;;;;;;;;UAcS;;EAEf,aAAa,SAAS,oBAAoB,QAAQ;;EAGlD,SAAS,SAAS,cAAc,QAAQ;;UAGzB;EACf;EACA;GACC;;;UAIc;EACf;EACA;IACE;IACA,WAAW;;;;UAKE;EACf;EACA;IACE;IACA;;;;KAKQ,mBAAmB,mBAAmB;;;;UASjC;EACf;EACA;EACA;EACA"}
|
package/dist/utils/env.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as resolveServiceKeyFromEnv, t as readServiceKey } from "../env-
|
|
1
|
+
import { n as resolveServiceKeyFromEnv, t as readServiceKey } from "../env-Co2ABx6K.mjs";
|
|
2
2
|
export { readServiceKey, resolveServiceKeyFromEnv };
|
package/dist/utils/env.mjs
CHANGED
package/dist/utils/env.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env.mjs","names":[],"sources":["../../src/utils/env.ts"],"sourcesContent":["import { readFileSync, existsSync } from 'node:fs';\nimport type { Destination } from '../auth-manager';\nimport { ServiceKeyParser, type BTPServiceKey } from '../types/service-key';\n\n/**\n * Read a BTP service key from a file path.\n *\n * @param filePath - Path to a JSON file containing the BTP service key\n * @returns Parsed and validated `BTPServiceKey`\n * @throws Error if the input looks like raw JSON (a file path is required)\n * @throws Error if the file does not exist\n */\nexport function readServiceKey(filePath: string): BTPServiceKey {\n const trimmed = filePath.trim();\n\n if (trimmed.startsWith('{')) {\n throw new Error(\n 'Expected a path to a JSON file, but received a raw JSON string.\\n' +\n 'Write the service key JSON to a file and pass the file path instead.\\n' +\n 'Example: /path/to/service-key.json',\n );\n }\n\n if (!existsSync(trimmed)) {\n throw new Error(`Service key file not found: ${trimmed}`);\n }\n\n const raw = readFileSync(trimmed, 'utf-8');\n return ServiceKeyParser.parse(raw);\n}\n\n/**\n * Resolves a service key destination from the `ADT_SERVICE_KEY` environment\n * variable.\n *\n * The value can be a path to a JSON file or a raw JSON string (for backward\n * compatibility with CI pipelines that store the key as a secret JSON value).\n *\n * @returns A `Destination` object for the service-key plugin, or `null` if\n * `ADT_SERVICE_KEY` is not set.\n */\nexport function resolveServiceKeyFromEnv(): Destination | null {\n const raw = process.env['ADT_SERVICE_KEY'];\n\n if (!raw) {\n return null;\n }\n\n const trimmed = raw.trim();\n\n // Accept both a raw JSON string and a file path so that CI environments that\n // store the key as a secret JSON value continue to work without changes.\n let serviceKey: BTPServiceKey;\n if (trimmed.startsWith('{')) {\n serviceKey = ServiceKeyParser.parse(trimmed);\n } else {\n serviceKey = readServiceKey(trimmed);\n }\n\n return {\n type: '@abapify/adt-auth/plugins/service-key',\n options: {\n url: serviceKey.url,\n serviceKey,\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,eAAe,UAAiC;CAC9D,MAAM,UAAU,SAAS,
|
|
1
|
+
{"version":3,"file":"env.mjs","names":[],"sources":["../../src/utils/env.ts"],"sourcesContent":["import { readFileSync, existsSync } from 'node:fs';\nimport type { Destination } from '../auth-manager';\nimport { ServiceKeyParser, type BTPServiceKey } from '../types/service-key';\n\n/**\n * Read a BTP service key from a file path.\n *\n * @param filePath - Path to a JSON file containing the BTP service key\n * @returns Parsed and validated `BTPServiceKey`\n * @throws Error if the input looks like raw JSON (a file path is required)\n * @throws Error if the file does not exist\n */\nexport function readServiceKey(filePath: string): BTPServiceKey {\n const trimmed = filePath.trim();\n\n if (trimmed.startsWith('{')) {\n throw new Error(\n 'Expected a path to a JSON file, but received a raw JSON string.\\n' +\n 'Write the service key JSON to a file and pass the file path instead.\\n' +\n 'Example: /path/to/service-key.json',\n );\n }\n\n if (!existsSync(trimmed)) {\n throw new Error(`Service key file not found: ${trimmed}`);\n }\n\n const raw = readFileSync(trimmed, 'utf-8');\n return ServiceKeyParser.parse(raw);\n}\n\n/**\n * Resolves a service key destination from the `ADT_SERVICE_KEY` environment\n * variable.\n *\n * The value can be a path to a JSON file or a raw JSON string (for backward\n * compatibility with CI pipelines that store the key as a secret JSON value).\n *\n * @returns A `Destination` object for the service-key plugin, or `null` if\n * `ADT_SERVICE_KEY` is not set.\n */\nexport function resolveServiceKeyFromEnv(): Destination | null {\n const raw = process.env['ADT_SERVICE_KEY'];\n\n if (!raw) {\n return null;\n }\n\n const trimmed = raw.trim();\n\n // Accept both a raw JSON string and a file path so that CI environments that\n // store the key as a secret JSON value continue to work without changes.\n let serviceKey: BTPServiceKey;\n if (trimmed.startsWith('{')) {\n serviceKey = ServiceKeyParser.parse(trimmed);\n } else {\n serviceKey = readServiceKey(trimmed);\n }\n\n return {\n type: '@abapify/adt-auth/plugins/service-key',\n options: {\n url: serviceKey.url,\n serviceKey,\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,eAAe,UAAiC;CAC9D,MAAM,UAAU,SAAS,KAAK;CAE9B,IAAI,QAAQ,WAAW,GAAG,GACxB,MAAM,IAAI,MACR,2KAGF;CAGF,IAAI,CAAC,WAAW,OAAO,GACrB,MAAM,IAAI,MAAM,+BAA+B,SAAS;CAG1D,MAAM,MAAM,aAAa,SAAS,OAAO;CACzC,OAAO,iBAAiB,MAAM,GAAG;AACnC;;;;;;;;;;;AAYA,SAAgB,2BAA+C;CAC7D,MAAM,MAAM,QAAQ,IAAI;CAExB,IAAI,CAAC,KACH,OAAO;CAGT,MAAM,UAAU,IAAI,KAAK;CAIzB,IAAI;CACJ,IAAI,QAAQ,WAAW,GAAG,GACxB,aAAa,iBAAiB,MAAM,OAAO;MAE3C,aAAa,eAAe,OAAO;CAGrC,OAAO;EACL,MAAM;EACN,SAAS;GACP,KAAK,WAAW;GAChB;EACF;CACF;AACF"}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.4.
|
|
6
|
+
"version": "0.4.4",
|
|
7
7
|
"description": "Authentication package for SAP ADT systems - supports multiple auth methods",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"types": "./dist/index.d.mts",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"README.md"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@abapify/logger": "0.4.
|
|
24
|
+
"@abapify/logger": "0.4.4",
|
|
25
25
|
"proxy-agent": "^6.4.0"
|
|
26
26
|
},
|
|
27
27
|
"keywords": [
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"slc",
|
|
33
33
|
"oauth"
|
|
34
34
|
],
|
|
35
|
-
"author": "
|
|
35
|
+
"author": "abapify",
|
|
36
36
|
"license": "MIT",
|
|
37
37
|
"repository": {
|
|
38
38
|
"type": "git",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"env-CNa9YA_U.d.mts","names":[],"sources":["../src/storage/file-storage.ts","../src/auth-manager.ts","../src/types/service-key.ts","../src/utils/env.ts"],"mappings":";;;cAuBa,WAAA;EAAA,iBACM,OAAA;EAAA,iBACA,UAAA;EAAA,iBACA,UAAA;cAEL,OAAA;EAcZ;;;EAAA,IAAA,CAAK,OAAA,EAAS,WAAA;EAST;;;EAAL,IAAA,CAAK,GAAA,WAAc,WAAA;EA8BnB;;;EAVA,MAAA,CAAO,GAAA;EAkCO;;;EAxBd,IAAA,CAAA;EAuDQ;;;EA1CR,MAAA,CAAO,GAAA;EA2DW;;;EAhDlB,aAAA,CAAc,GAAA;ECpFC;;;ED6Ff,aAAA,CAAA;EC3FA;;;EDmGA,eAAA,CAAA;EAAA,QAUQ,cAAA;EAAA,QAIA,gBAAA;EAAA,QAMA,UAAA;EAAA,QAWA,UAAA;AAAA;;;;;;UCpIO,WAAA;EDqBV;ECnBL,IAAA;ED4BK;EC1BL,OAAA,EAAS,iBAAA;AAAA;AAAA,cAGE,WAAA;EAAA,QACH,OAAA;cAEI,OAAA,GAAU,WAAA;ED+Df;;;;;;;;EC/CD,KAAA,CAAM,GAAA,UAAa,WAAA,EAAa,WAAA,GAAc,OAAA,CAAQ,WAAA;ED0G1C;;;EAAA,QC1EV,YAAA;;AA1DV;;EAoGE,WAAA,CAAY,OAAA,EAAS,WAAA;EAhGK;;;EAuG1B,UAAA,CAAW,GAAA,YAAe,WAAA;EAvGA;;AAG5B;EA+GE,aAAA,CAAc,GAAA;;;;EAOd,QAAA,CAAA;EAnGoD;;;EA8GpD,aAAA,CAAc,GAAA;EAuCH;;;EAhCX,aAAA,CAAA;EAuIW;;;EAhIX,eAAA,CAAA;;;;EAWA,SAAA,CAAU,OAAA,EAAS,WAAA;EAvIP;;;;;EAoJN,kBAAA,CACJ,OAAA,EAAS,WAAA,EACT,OAAA;IAAY,GAAA,IAAO,OAAA;EAAA,IAClB,OAAA,CAAQ,WAAA;EA7EC;;;EAsKZ,eAAA,CAAgB,OAAA,EAAS,WAAA;EApJzB;;;EA+JA,YAAA,CACE,OAAA,EAAS,WAAA;IACN,QAAA;IAAkB,QAAA;EAAA;AAAA;;;UC1SR,cAAA;EACf,UAAA;EACA,KAAA;EACA,YAAA;EACA,iBAAA;EACA,QAAA;EACA,SAAA;EACA,YAAA;EACA,iBAAA;EACA,GAAA;EACA,SAAA;EACA,eAAA;EACA,MAAA;EACA,YAAA;EACA,cAAA;EACA,QAAA;EACA,MAAA;AAAA;AAAA,UAGe,OAAA;EACf,IAAA;EACA,IAAA;AAAA;AAAA,UAGe,OAAA;EACf,GAAA;EACA,OAAA;EACA,IAAA;EACA,EAAA;AAAA;AAAA,UAGe,aAAA;EACf,GAAA,EAAK,cAAA;EACL,GAAA;EACA,mBAAA;EACA,QAAA;EACA,SAAA,EAAW,MAAA;EACX,QAAA,EAAU,MAAA,SAAe,OAAA;EACzB,OAAA,EAAS,OAAA;EACT,oBAAA;AAAA;AAAA,UAYe,uBAAA;EACf,GAAA;EACA,MAAA;EACA,UAAA;EDT4D;ECW5D,WAAA,IAAe,GAAA,aAAgB,OAAA;ED+DV;EC7DrB,YAAA;ED0HmB;ECxHnB,SAAA;EDwIW;ECtIX,WAAA;EAAA,CACC,GAAA;AAAA;;;;AF1CH;;;;;;;iBGXgB,cAAA,CAAe,QAAA,WAAmB,aAAA;;;;;;;;;;;iBA6BlC,wBAAA,CAAA,GAA4B,WAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"service-key-D_fQRV_K.mjs","names":["parseUrl"],"sources":["../src/utils/pkce.ts","../src/plugins/service-key.ts"],"sourcesContent":["import { createHash, randomBytes } from 'crypto';\n\n/**\n * Generates a cryptographically secure code verifier for PKCE flow\n */\nexport function generateCodeVerifier(): string {\n return randomBytes(32).toString('base64url');\n}\n\n/**\n * Generates a code challenge from the verifier using SHA256\n */\nexport function generateCodeChallenge(verifier: string): string {\n return createHash('sha256').update(verifier).digest('base64url');\n}\n\n/**\n * Generates a random state parameter for OAuth flow\n */\nexport function generateState(): string {\n return randomBytes(16).toString('hex');\n}\n","import { createServer, type Server } from 'node:http';\nimport { spawn } from 'node:child_process';\nimport { parse as parseUrl } from 'node:url';\nimport type { AuthPlugin, AuthPluginOptions, CookieAuthResult } from '../types';\nimport {\n ServiceKeyParser,\n type BTPServiceKey,\n type ServiceKeyPluginOptions,\n} from '../types/service-key';\nimport {\n generateCodeVerifier,\n generateCodeChallenge,\n generateState,\n} from '../utils/pkce';\n\nconst DEFAULT_PORT = 3000;\nconst DEFAULT_REDIRECT_PATH = '/callback';\nconst DEFAULT_TIMEOUT_MS = 300_000; // 5 minutes\nconst MAX_PORT_RETRIES = 5; // Try up to 5 consecutive ports on EADDRINUSE\n\n/**\n * Copy text to system clipboard (best-effort, never throws).\n * Tries platform-appropriate commands: pbcopy (macOS), clip.exe (WSL), xclip/xsel (Linux).\n */\nasync function copyToClipboard(text: string): Promise<void> {\n const candidates =\n process.platform === 'darwin'\n ? [['pbcopy']]\n : process.platform === 'win32'\n ? [['clip']]\n : [\n ['clip.exe'],\n ['xclip', '-selection', 'clipboard'],\n ['xsel', '--clipboard'],\n ];\n\n for (const [cmd, ...args] of candidates) {\n try {\n await new Promise<void>((resolve, reject) => {\n const child = spawn(cmd, args, {\n stdio: ['pipe', 'ignore', 'ignore'],\n });\n child.on('error', reject);\n child.on('close', (code) =>\n code === 0\n ? resolve()\n : reject(new Error(`${cmd} exited with code ${code}`)),\n );\n child.stdin.write(text);\n child.stdin.end();\n });\n return;\n } catch {\n continue;\n }\n }\n throw new Error('No clipboard utility available');\n}\n\ninterface OAuthTokenResponse {\n access_token: string;\n token_type: string;\n expires_in: number;\n scope?: string;\n refresh_token?: string;\n}\n\n/**\n * Exchange an authorization code for tokens using the PKCE flow.\n */\nasync function exchangeCodeForToken(\n serviceKey: BTPServiceKey,\n code: string,\n codeVerifier: string,\n redirectUri: string,\n): Promise<OAuthTokenResponse> {\n const { clientid, clientsecret, url: uaaUrl } = serviceKey.uaa;\n const credentials = Buffer.from(`${clientid}:${clientsecret}`).toString(\n 'base64',\n );\n\n const response = await fetch(`${uaaUrl}/oauth/token`, {\n method: 'POST',\n headers: {\n Authorization: `Basic ${credentials}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n redirect_uri: redirectUri,\n code_verifier: codeVerifier,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`Token exchange failed (${response.status}): ${text}`);\n }\n\n const data = (await response.json()) as OAuthTokenResponse;\n\n if (!data.access_token) {\n throw new Error('Token response missing access_token');\n }\n\n return data;\n}\n\n/**\n * Run the OAuth PKCE authorization code flow:\n * 1. Start local callback server\n * 2. Build XSUAA authorize URL with PKCE\n * 3. Open browser for user login\n * 4. Receive auth code via redirect\n * 5. Exchange code for user token\n */\nasync function performPkceFlow(\n serviceKey: BTPServiceKey,\n openBrowser: (url: string) => Promise<void>,\n port: number,\n timeoutMs: number,\n redirectUri?: string,\n): Promise<OAuthTokenResponse> {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = generateCodeChallenge(codeVerifier);\n const state = generateState();\n\n const resolvedRedirectUri =\n redirectUri ?? `http://localhost:${port}${DEFAULT_REDIRECT_PATH}`;\n const callbackPath = (() => {\n try {\n const pathname = new URL(resolvedRedirectUri).pathname;\n return pathname && pathname.length > 0 ? pathname : '/';\n } catch {\n return DEFAULT_REDIRECT_PATH;\n }\n })();\n\n // Build XSUAA authorize URL\n const authUrl = new URL(`${serviceKey.uaa.url}/oauth/authorize`);\n authUrl.searchParams.set('client_id', serviceKey.uaa.clientid);\n authUrl.searchParams.set('response_type', 'code');\n authUrl.searchParams.set('redirect_uri', resolvedRedirectUri);\n authUrl.searchParams.set('code_challenge', codeChallenge);\n authUrl.searchParams.set('code_challenge_method', 'S256');\n authUrl.searchParams.set('state', state);\n authUrl.searchParams.set('scope', 'openid');\n\n return new Promise<OAuthTokenResponse>((resolve, reject) => {\n // eslint-disable-next-line prefer-const -- server must be declared before timeout but assigned after\n let server: Server;\n\n const timeout = setTimeout(() => {\n server?.close();\n reject(new Error('Authentication timed out'));\n }, timeoutMs);\n\n server = createServer(async (req, res) => {\n try {\n const url = parseUrl(req.url || '', true);\n\n if (url.pathname !== callbackPath) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n return;\n }\n\n const {\n code,\n state: returnedState,\n error,\n error_description,\n } = url.query;\n\n if (error) {\n const errorSuffix = error_description\n ? ` - ${error_description}`\n : '';\n throw new Error(`OAuth error: ${error}${errorSuffix}`);\n }\n\n if (returnedState !== state) {\n throw new Error('State mismatch - possible CSRF attack');\n }\n\n if (!code) {\n throw new Error('No authorization code received');\n }\n\n const tokenData = await exchangeCodeForToken(\n serviceKey,\n code as string,\n codeVerifier,\n resolvedRedirectUri,\n );\n\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(\n '<html><body style=\"font-family:Arial;text-align:center;padding:50px\">' +\n '<h2>Authentication successful!</h2>' +\n '<p>You can close this window and return to the terminal.</p>' +\n '<script>setTimeout(()=>window.close(),2000)</script>' +\n '</body></html>',\n );\n\n clearTimeout(timeout);\n setTimeout(() => {\n server.close();\n resolve(tokenData);\n }, 500);\n } catch (err) {\n // Escape error message before embedding into HTML response to prevent\n // reflected XSS / XSS-through-exception. The error message may contain\n // data derived from the (attacker-controllable) OAuth callback URL\n // (e.g. error_description) and must never be treated as trusted.\n const rawMessage = err instanceof Error ? err.message : String(err);\n const escaped = rawMessage.replaceAll(/[&<>\"']/g, (c) => {\n switch (c) {\n case '&':\n return '&';\n case '<':\n return '<';\n case '>':\n return '>';\n case '\"':\n return '"';\n default:\n return ''';\n }\n });\n res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(\n '<html><body style=\"font-family:Arial;text-align:center;padding:50px\">' +\n `<h2>Authentication failed</h2><p>${escaped}</p>` +\n '</body></html>',\n );\n\n clearTimeout(timeout);\n server.close(() => reject(err));\n }\n });\n\n server.on('error', (err) => {\n clearTimeout(timeout);\n reject(err);\n });\n\n server.listen(port, () => {\n openBrowser(authUrl.toString()).catch((err) => {\n clearTimeout(timeout);\n server.close();\n reject(\n new Error('Could not open browser for authentication', {\n cause: err,\n }),\n );\n });\n });\n });\n}\n\nconst authPlugin: AuthPlugin = {\n async authenticate(options: AuthPluginOptions): Promise<CookieAuthResult> {\n const { serviceKey, openBrowser, callbackPort, timeoutMs, redirectUri } =\n options as ServiceKeyPluginOptions;\n\n if (!serviceKey) {\n throw new Error('service-key plugin requires a serviceKey option');\n }\n\n const parsed = ServiceKeyParser.parse(serviceKey);\n\n // Resolve openBrowser: use provided callback, or dynamically import 'open'\n let browserOpener: (url: string) => Promise<void>;\n if (openBrowser) {\n browserOpener = openBrowser;\n } else {\n // Fallback: try dynamic import of 'open' package\n try {\n const openModule = await import('open');\n const openFn = openModule.default;\n browserOpener = async (url: string) => {\n await openFn(url);\n };\n } catch {\n throw new Error(\n 'No openBrowser callback provided and \"open\" package not available. ' +\n 'Install \"open\" or pass an openBrowser function in plugin options.',\n );\n }\n }\n\n const startPort =\n typeof callbackPort === 'number' ? callbackPort : DEFAULT_PORT;\n const timeout =\n typeof timeoutMs === 'number' ? timeoutMs : DEFAULT_TIMEOUT_MS;\n\n // Extract log from options (passed by AuthManager during refresh, or by CLI)\n const log = (options as { log?: (msg: string) => void }).log ?? console.log;\n\n // Wrap browser opener: always print the URL and try clipboard copy before opening\n const wrappedBrowserOpener = async (url: string) => {\n log(`🔗 Open this URL to authenticate:\\n ${url}`);\n\n try {\n await copyToClipboard(url);\n log('📋 URL copied to clipboard');\n } catch {\n // Clipboard not available — URL is already printed above\n }\n\n await browserOpener(url);\n };\n\n // Try ports starting from startPort, retrying on EADDRINUSE\n const hasExplicitRedirect = typeof redirectUri === 'string';\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < MAX_PORT_RETRIES; attempt++) {\n const port = startPort + attempt;\n // When redirectUri is explicitly set, use it as-is (don't rewrite for new port)\n const effectiveRedirectUri = hasExplicitRedirect\n ? redirectUri\n : undefined;\n\n try {\n const tokenData = await performPkceFlow(\n parsed,\n wrappedBrowserOpener,\n port,\n timeout,\n effectiveRedirectUri,\n );\n\n const expiresAt = new Date(Date.now() + tokenData.expires_in * 1000);\n\n return {\n method: 'cookie',\n credentials: {\n cookies: `Authorization: Bearer ${tokenData.access_token}`,\n expiresAt,\n },\n };\n } catch (err) {\n if (\n err instanceof Error &&\n 'code' in err &&\n (err as NodeJS.ErrnoException).code === 'EADDRINUSE'\n ) {\n // When an explicit redirectUri is set, retrying on a different port\n // is pointless because the OAuth callback will still go to the\n // original port encoded in the redirect URI.\n if (hasExplicitRedirect) {\n throw new Error(\n `Port ${port} is in use and redirectUri is explicitly set — cannot retry on a different port`,\n { cause: err },\n );\n }\n lastError = err;\n log(`⚠️ Port ${port} in use, trying ${port + 1}...`);\n continue;\n }\n throw err; // Non-port error — rethrow immediately\n }\n }\n\n throw new Error(\n `All ports ${startPort}-${startPort + MAX_PORT_RETRIES - 1} are in use. ` +\n `Free a port or set callbackPort in plugin options.`,\n { cause: lastError },\n );\n },\n};\n\nexport default authPlugin;\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types-C2mcG0xY.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;AAgBA;;;;;AAKA;;;;;AAAA,KALY,UAAA;;;;UAKK,gBAAA;EACf,QAAA;EACA,QAAA;AAAA;;;AA4BF;UAtBiB,iBAAA;EACf,OAAA;EACA,SAAA;AAAA;;;;KAMU,WAAA,GAAc,gBAAA,GAAmB,iBAAA;;;;;;;;;UAc5B,UAAA;EACf,MAAA,EAAQ,UAAA;EACR,MAAA;EACA,aAAA,GAAgB,iBAAA;EAChB,WAAA,EAAa,WAAA;AAAA;;;;;;AAiCf;;;UAlBiB,WAAA;EACf,GAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA,EAAM,UAAA;AAAA;;;;;;;;UAcS,UAAA;EAKG;EAHlB,YAAA,CAAa,OAAA,EAAS,iBAAA,GAAoB,OAAA,CAAQ,gBAAA;EAGlB;EAAhC,OAAA,EAAS,OAAA,EAAS,WAAA,GAAc,OAAA,CAAQ,gBAAA;AAAA;AAAA,UAGzB,iBAAA;EACf,GAAA;EACA,MAAA;EAAA,CACC,GAAA;AAAA;;UAIc,gBAAA;EACf,MAAA;EACA,WAAA;IACE,OAAA;IACA,SAAA,EAAW,IAAA;EAAA;AAAA;;UAKE,eAAA;EACf,MAAA;EACA,WAAA;IACE,QAAA;IACA,QAAA;EAAA;AAAA;AAJJ;AAAA,KASY,gBAAA,GAAmB,gBAAA,GAAmB,eAAA;;;;UASjC,oBAAA;EACf,OAAA;EACA,KAAA;EACA,UAAA;EACA,YAAA;AAAA"}
|