@portaidentity/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/dist/auth/browser-flow.d.ts +42 -0
  2. package/dist/auth/browser-flow.d.ts.map +1 -0
  3. package/dist/auth/browser-flow.js +193 -0
  4. package/dist/auth/browser-flow.js.map +1 -0
  5. package/dist/auth/callback-server.d.ts +81 -0
  6. package/dist/auth/callback-server.d.ts.map +1 -0
  7. package/dist/auth/callback-server.js +193 -0
  8. package/dist/auth/callback-server.js.map +1 -0
  9. package/dist/auth/metadata.d.ts +43 -0
  10. package/dist/auth/metadata.d.ts.map +1 -0
  11. package/dist/auth/metadata.js +66 -0
  12. package/dist/auth/metadata.js.map +1 -0
  13. package/dist/auth/pkce.d.ts +42 -0
  14. package/dist/auth/pkce.d.ts.map +1 -0
  15. package/dist/auth/pkce.js +52 -0
  16. package/dist/auth/pkce.js.map +1 -0
  17. package/dist/auth/types.d.ts +72 -0
  18. package/dist/auth/types.d.ts.map +1 -0
  19. package/dist/auth/types.js +11 -0
  20. package/dist/auth/types.js.map +1 -0
  21. package/dist/client-factory.d.ts +29 -0
  22. package/dist/client-factory.d.ts.map +1 -0
  23. package/dist/client-factory.js +43 -0
  24. package/dist/client-factory.js.map +1 -0
  25. package/dist/commands/app-claim.d.ts +9 -0
  26. package/dist/commands/app-claim.d.ts.map +1 -0
  27. package/dist/commands/app-claim.js +128 -0
  28. package/dist/commands/app-claim.js.map +1 -0
  29. package/dist/commands/app-module.d.ts +9 -0
  30. package/dist/commands/app-module.d.ts.map +1 -0
  31. package/dist/commands/app-module.js +104 -0
  32. package/dist/commands/app-module.js.map +1 -0
  33. package/dist/commands/app-permission.d.ts +9 -0
  34. package/dist/commands/app-permission.d.ts.map +1 -0
  35. package/dist/commands/app-permission.js +118 -0
  36. package/dist/commands/app-permission.js.map +1 -0
  37. package/dist/commands/app-role.d.ts +9 -0
  38. package/dist/commands/app-role.d.ts.map +1 -0
  39. package/dist/commands/app-role.js +166 -0
  40. package/dist/commands/app-role.js.map +1 -0
  41. package/dist/commands/app.d.ts +12 -0
  42. package/dist/commands/app.d.ts.map +1 -0
  43. package/dist/commands/app.js +255 -0
  44. package/dist/commands/app.js.map +1 -0
  45. package/dist/commands/audit.d.ts +12 -0
  46. package/dist/commands/audit.d.ts.map +1 -0
  47. package/dist/commands/audit.js +96 -0
  48. package/dist/commands/audit.js.map +1 -0
  49. package/dist/commands/bulk.d.ts +12 -0
  50. package/dist/commands/bulk.d.ts.map +1 -0
  51. package/dist/commands/bulk.js +77 -0
  52. package/dist/commands/bulk.js.map +1 -0
  53. package/dist/commands/client-secret.d.ts +18 -0
  54. package/dist/commands/client-secret.d.ts.map +1 -0
  55. package/dist/commands/client-secret.js +126 -0
  56. package/dist/commands/client-secret.js.map +1 -0
  57. package/dist/commands/client.d.ts +27 -0
  58. package/dist/commands/client.d.ts.map +1 -0
  59. package/dist/commands/client.js +385 -0
  60. package/dist/commands/client.js.map +1 -0
  61. package/dist/commands/completion.d.ts +27 -0
  62. package/dist/commands/completion.d.ts.map +1 -0
  63. package/dist/commands/completion.js +42 -0
  64. package/dist/commands/completion.js.map +1 -0
  65. package/dist/commands/config.d.ts +14 -0
  66. package/dist/commands/config.d.ts.map +1 -0
  67. package/dist/commands/config.js +85 -0
  68. package/dist/commands/config.js.map +1 -0
  69. package/dist/commands/doctor.d.ts +25 -0
  70. package/dist/commands/doctor.d.ts.map +1 -0
  71. package/dist/commands/doctor.js +198 -0
  72. package/dist/commands/doctor.js.map +1 -0
  73. package/dist/commands/exports.d.ts +12 -0
  74. package/dist/commands/exports.d.ts.map +1 -0
  75. package/dist/commands/exports.js +80 -0
  76. package/dist/commands/exports.js.map +1 -0
  77. package/dist/commands/health.d.ts +12 -0
  78. package/dist/commands/health.d.ts.map +1 -0
  79. package/dist/commands/health.js +53 -0
  80. package/dist/commands/health.js.map +1 -0
  81. package/dist/commands/keys.d.ts +14 -0
  82. package/dist/commands/keys.d.ts.map +1 -0
  83. package/dist/commands/keys.js +91 -0
  84. package/dist/commands/keys.js.map +1 -0
  85. package/dist/commands/login.d.ts +36 -0
  86. package/dist/commands/login.d.ts.map +1 -0
  87. package/dist/commands/login.js +78 -0
  88. package/dist/commands/login.js.map +1 -0
  89. package/dist/commands/logout.d.ts +25 -0
  90. package/dist/commands/logout.d.ts.map +1 -0
  91. package/dist/commands/logout.js +43 -0
  92. package/dist/commands/logout.js.map +1 -0
  93. package/dist/commands/org.d.ts +26 -0
  94. package/dist/commands/org.d.ts.map +1 -0
  95. package/dist/commands/org.js +396 -0
  96. package/dist/commands/org.js.map +1 -0
  97. package/dist/commands/provision.d.ts +47 -0
  98. package/dist/commands/provision.d.ts.map +1 -0
  99. package/dist/commands/provision.js +400 -0
  100. package/dist/commands/provision.js.map +1 -0
  101. package/dist/commands/sessions.d.ts +14 -0
  102. package/dist/commands/sessions.d.ts.map +1 -0
  103. package/dist/commands/sessions.js +122 -0
  104. package/dist/commands/sessions.js.map +1 -0
  105. package/dist/commands/stats.d.ts +12 -0
  106. package/dist/commands/stats.d.ts.map +1 -0
  107. package/dist/commands/stats.js +46 -0
  108. package/dist/commands/stats.js.map +1 -0
  109. package/dist/commands/user-claim.d.ts +17 -0
  110. package/dist/commands/user-claim.d.ts.map +1 -0
  111. package/dist/commands/user-claim.js +123 -0
  112. package/dist/commands/user-claim.js.map +1 -0
  113. package/dist/commands/user-role.d.ts +17 -0
  114. package/dist/commands/user-role.d.ts.map +1 -0
  115. package/dist/commands/user-role.js +118 -0
  116. package/dist/commands/user-role.js.map +1 -0
  117. package/dist/commands/user.d.ts +26 -0
  118. package/dist/commands/user.d.ts.map +1 -0
  119. package/dist/commands/user.js +352 -0
  120. package/dist/commands/user.js.map +1 -0
  121. package/dist/commands/version.d.ts +25 -0
  122. package/dist/commands/version.d.ts.map +1 -0
  123. package/dist/commands/version.js +83 -0
  124. package/dist/commands/version.js.map +1 -0
  125. package/dist/commands/whoami.d.ts +26 -0
  126. package/dist/commands/whoami.d.ts.map +1 -0
  127. package/dist/commands/whoami.js +66 -0
  128. package/dist/commands/whoami.js.map +1 -0
  129. package/dist/credential-store.d.ts +101 -0
  130. package/dist/credential-store.d.ts.map +1 -0
  131. package/dist/credential-store.js +121 -0
  132. package/dist/credential-store.js.map +1 -0
  133. package/dist/error-handler.d.ts +47 -0
  134. package/dist/error-handler.d.ts.map +1 -0
  135. package/dist/error-handler.js +166 -0
  136. package/dist/error-handler.js.map +1 -0
  137. package/dist/global-options.d.ts +50 -0
  138. package/dist/global-options.d.ts.map +1 -0
  139. package/dist/global-options.js +62 -0
  140. package/dist/global-options.js.map +1 -0
  141. package/dist/index.d.ts +29 -0
  142. package/dist/index.d.ts.map +1 -0
  143. package/dist/index.js +122 -0
  144. package/dist/index.js.map +1 -0
  145. package/dist/output.d.ts +75 -0
  146. package/dist/output.d.ts.map +1 -0
  147. package/dist/output.js +100 -0
  148. package/dist/output.js.map +1 -0
  149. package/dist/parsers.d.ts +74 -0
  150. package/dist/parsers.d.ts.map +1 -0
  151. package/dist/parsers.js +125 -0
  152. package/dist/parsers.js.map +1 -0
  153. package/dist/prompt.d.ts +50 -0
  154. package/dist/prompt.d.ts.map +1 -0
  155. package/dist/prompt.js +98 -0
  156. package/dist/prompt.js.map +1 -0
  157. package/package.json +46 -0
@@ -0,0 +1,42 @@
1
+ /**
2
+ * OIDC Authorization Code + PKCE browser flow orchestration.
3
+ *
4
+ * Orchestrates the complete CLI login flow:
5
+ * 1. Discover admin metadata (client_id, issuer) from the server
6
+ * 2. Generate PKCE code_verifier + code_challenge (S256)
7
+ * 3. Start a temporary localhost HTTP server to receive the callback
8
+ * 4. Open the user's browser to the authorization endpoint
9
+ * 5. Wait for the callback with the authorization code
10
+ * 6. Exchange the code for tokens at the token endpoint
11
+ * 7. Decode the ID token for user identity
12
+ * 8. Return the complete AuthFlowResult
13
+ *
14
+ * Supports two modes:
15
+ * - **Browser mode**: Opens browser, starts callback server
16
+ * - **Manual mode**: Prints URL, user pastes callback URL
17
+ *
18
+ * @module auth/browser-flow
19
+ */
20
+ import type { AuthFlowResult } from './types.js';
21
+ /** Options for the browser flow */
22
+ export interface BrowserFlowOptions {
23
+ /** Porta server URL */
24
+ server: string;
25
+ /** Override the auto-discovered client ID */
26
+ clientId?: string;
27
+ /** Force manual mode (print URL instead of opening browser) */
28
+ noBrowser?: boolean;
29
+ }
30
+ /**
31
+ * Execute the OIDC Authorization Code + PKCE login flow.
32
+ *
33
+ * This is the main entry point for CLI authentication. It handles
34
+ * both browser-based and manual (Docker/headless) login modes.
35
+ *
36
+ * @param options - Login flow options
37
+ * @param log - Logging callback for status messages
38
+ * @returns Complete auth flow result ready to store as credentials
39
+ * @throws Error on any failure (connectivity, auth, token exchange)
40
+ */
41
+ export declare function executeBrowserFlow(options: BrowserFlowOptions, log?: (message: string) => void): Promise<AuthFlowResult>;
42
+ //# sourceMappingURL=browser-flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-flow.d.ts","sourceRoot":"","sources":["../../src/auth/browser-flow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAaH,OAAO,KAAK,EAAE,cAAc,EAAiB,MAAM,YAAY,CAAC;AAahE,mCAAmC;AACnC,MAAM,WAAW,kBAAkB;IACjC,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAsDD;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,kBAAkB,EAC3B,GAAG,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAkB,GAC3C,OAAO,CAAC,cAAc,CAAC,CA2HzB"}
@@ -0,0 +1,193 @@
1
+ /**
2
+ * OIDC Authorization Code + PKCE browser flow orchestration.
3
+ *
4
+ * Orchestrates the complete CLI login flow:
5
+ * 1. Discover admin metadata (client_id, issuer) from the server
6
+ * 2. Generate PKCE code_verifier + code_challenge (S256)
7
+ * 3. Start a temporary localhost HTTP server to receive the callback
8
+ * 4. Open the user's browser to the authorization endpoint
9
+ * 5. Wait for the callback with the authorization code
10
+ * 6. Exchange the code for tokens at the token endpoint
11
+ * 7. Decode the ID token for user identity
12
+ * 8. Return the complete AuthFlowResult
13
+ *
14
+ * Supports two modes:
15
+ * - **Browser mode**: Opens browser, starts callback server
16
+ * - **Manual mode**: Prints URL, user pastes callback URL
17
+ *
18
+ * @module auth/browser-flow
19
+ */
20
+ import { URL } from 'node:url';
21
+ import { decodeJwt } from 'jose';
22
+ import { generateCodeVerifier, generateCodeChallenge, generateState } from './pkce.js';
23
+ import { fetchAdminMetadata } from './metadata.js';
24
+ import { startCallbackServer, parseCallbackUrl, MANUAL_REDIRECT_URI, isContainerized, } from './callback-server.js';
25
+ import { question } from '../prompt.js';
26
+ // ---------------------------------------------------------------------------
27
+ // Constants
28
+ // ---------------------------------------------------------------------------
29
+ /** OIDC scopes requested during the login flow */
30
+ const SCOPES = 'openid profile email offline_access';
31
+ // ---------------------------------------------------------------------------
32
+ // Token Exchange
33
+ // ---------------------------------------------------------------------------
34
+ /**
35
+ * Exchange an authorization code for tokens at the OIDC token endpoint.
36
+ *
37
+ * Sends a POST request with the authorization code, PKCE code_verifier,
38
+ * and redirect URI to complete the Authorization Code flow.
39
+ *
40
+ * @param params - Token exchange parameters
41
+ * @returns Token response with access, refresh, and ID tokens
42
+ * @throws Error if the token exchange fails
43
+ */
44
+ async function exchangeCode(params) {
45
+ const tokenUrl = `${params.server}/${params.orgSlug}/token`;
46
+ const response = await fetch(tokenUrl, {
47
+ method: 'POST',
48
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
49
+ body: new URLSearchParams({
50
+ grant_type: 'authorization_code',
51
+ code: params.code,
52
+ redirect_uri: params.redirectUri,
53
+ client_id: params.clientId,
54
+ code_verifier: params.codeVerifier,
55
+ }),
56
+ });
57
+ if (!response.ok) {
58
+ const errorData = await response.json().catch(() => ({}));
59
+ const desc = errorData.error_description ||
60
+ errorData.error ||
61
+ `HTTP ${response.status}`;
62
+ throw new Error(`Token exchange failed: ${desc}`);
63
+ }
64
+ return response.json();
65
+ }
66
+ // ---------------------------------------------------------------------------
67
+ // Flow Orchestration
68
+ // ---------------------------------------------------------------------------
69
+ /**
70
+ * Execute the OIDC Authorization Code + PKCE login flow.
71
+ *
72
+ * This is the main entry point for CLI authentication. It handles
73
+ * both browser-based and manual (Docker/headless) login modes.
74
+ *
75
+ * @param options - Login flow options
76
+ * @param log - Logging callback for status messages
77
+ * @returns Complete auth flow result ready to store as credentials
78
+ * @throws Error on any failure (connectivity, auth, token exchange)
79
+ */
80
+ export async function executeBrowserFlow(options, log = console.log) {
81
+ const { server, noBrowser } = options;
82
+ // ---------------------------------------------------------------
83
+ // Step 1: Determine login mode (browser vs. manual)
84
+ // ---------------------------------------------------------------
85
+ const manualMode = noBrowser || isContainerized();
86
+ if (manualMode && !noBrowser) {
87
+ log('Container environment detected — using manual login mode.\n');
88
+ }
89
+ // ---------------------------------------------------------------
90
+ // Step 2: Discover admin metadata (client ID + org slug)
91
+ // ---------------------------------------------------------------
92
+ let clientId;
93
+ let orgSlug;
94
+ if (options.clientId) {
95
+ clientId = options.clientId;
96
+ orgSlug = 'porta-admin';
97
+ }
98
+ else {
99
+ const metadata = await fetchAdminMetadata(server);
100
+ clientId = metadata.clientId;
101
+ orgSlug = metadata.orgSlug;
102
+ }
103
+ // ---------------------------------------------------------------
104
+ // Step 3: Generate PKCE parameters
105
+ // ---------------------------------------------------------------
106
+ const codeVerifier = generateCodeVerifier();
107
+ const codeChallenge = generateCodeChallenge(codeVerifier);
108
+ const state = generateState();
109
+ // ---------------------------------------------------------------
110
+ // Step 4: Set up redirect URI — mode-dependent
111
+ // ---------------------------------------------------------------
112
+ let redirectUri;
113
+ let authCode;
114
+ if (manualMode) {
115
+ redirectUri = MANUAL_REDIRECT_URI;
116
+ authCode = undefined;
117
+ }
118
+ else {
119
+ const callbackServer = await startCallbackServer(state);
120
+ redirectUri = `http://127.0.0.1:${callbackServer.port}/callback`;
121
+ authCode = callbackServer.authCode;
122
+ }
123
+ // ---------------------------------------------------------------
124
+ // Step 5: Build the authorization URL
125
+ // ---------------------------------------------------------------
126
+ const authUrl = new URL(`${server}/${orgSlug}/auth`);
127
+ authUrl.searchParams.set('response_type', 'code');
128
+ authUrl.searchParams.set('client_id', clientId);
129
+ authUrl.searchParams.set('redirect_uri', redirectUri);
130
+ authUrl.searchParams.set('scope', SCOPES);
131
+ authUrl.searchParams.set('code_challenge', codeChallenge);
132
+ authUrl.searchParams.set('code_challenge_method', 'S256');
133
+ authUrl.searchParams.set('state', state);
134
+ // ---------------------------------------------------------------
135
+ // Step 6: Open browser or print URL + collect auth code
136
+ // ---------------------------------------------------------------
137
+ let code;
138
+ if (manualMode) {
139
+ log('Open this URL in your browser to log in:\n');
140
+ log(` ${authUrl.toString()}\n`);
141
+ log('After logging in, your browser will redirect to a page that won\'t load.');
142
+ log('Copy the full URL from your browser\'s address bar and paste it below.\n');
143
+ const pastedUrl = await question('Paste the callback URL: ');
144
+ code = parseCallbackUrl(pastedUrl, state);
145
+ }
146
+ else {
147
+ log('Opening browser for authentication...');
148
+ try {
149
+ // Dynamic import — `open` is an ESM-only package
150
+ const { default: openUrl } = await import('open');
151
+ await openUrl(authUrl.toString());
152
+ }
153
+ catch {
154
+ log('\nCould not open browser. Open this URL manually:\n');
155
+ log(` ${authUrl.toString()}\n`);
156
+ }
157
+ log('Waiting for authentication...');
158
+ code = await authCode;
159
+ }
160
+ // ---------------------------------------------------------------
161
+ // Step 7: Exchange the authorization code for tokens
162
+ // ---------------------------------------------------------------
163
+ const tokens = await exchangeCode({
164
+ server,
165
+ orgSlug,
166
+ code,
167
+ redirectUri,
168
+ clientId,
169
+ codeVerifier,
170
+ });
171
+ // ---------------------------------------------------------------
172
+ // Step 8: Decode the ID token to extract user identity
173
+ // ---------------------------------------------------------------
174
+ const claims = decodeJwt(tokens.id_token);
175
+ // ---------------------------------------------------------------
176
+ // Step 9: Build and return the auth flow result
177
+ // ---------------------------------------------------------------
178
+ return {
179
+ server,
180
+ orgSlug,
181
+ clientId,
182
+ accessToken: tokens.access_token,
183
+ refreshToken: tokens.refresh_token,
184
+ idToken: tokens.id_token,
185
+ expiresAt: new Date(Date.now() + tokens.expires_in * 1000).toISOString(),
186
+ userInfo: {
187
+ sub: claims.sub ?? '',
188
+ email: claims.email ?? '',
189
+ name: claims.name ?? undefined,
190
+ },
191
+ };
192
+ }
193
+ //# sourceMappingURL=browser-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-flow.js","sourceRoot":"","sources":["../../src/auth/browser-flow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * OAuth callback server and manual URL parsing.
3
+ *
4
+ * Provides two mechanisms for receiving the OAuth authorization code:
5
+ *
6
+ * 1. **Browser mode**: A temporary localhost HTTP server that receives
7
+ * the redirect callback. Binds to 127.0.0.1 on a random port.
8
+ *
9
+ * 2. **Manual mode**: URL parsing for Docker/headless environments where
10
+ * the user pastes the callback URL from their browser's address bar.
11
+ *
12
+ * Security:
13
+ * - Callback server binds to 127.0.0.1 only (loopback)
14
+ * - State parameter validated on every callback (CSRF protection)
15
+ * - 5-minute timeout prevents abandoned login sessions
16
+ *
17
+ * @module auth/callback-server
18
+ */
19
+ /**
20
+ * Fixed redirect URI used in manual (no-browser) mode.
21
+ *
22
+ * The port number is arbitrary — nothing actually listens on it. The user's
23
+ * browser will fail to load this page after authentication, but the full URL
24
+ * (including the authorization code in the query string) will be visible in
25
+ * the browser's address bar for the user to copy and paste back.
26
+ *
27
+ * Per RFC 8252 §7.3, node-oidc-provider allows flexible port matching for
28
+ * native clients using loopback redirect URIs, so any port works as long as
29
+ * the base pattern (`http://127.0.0.1/callback`) is registered.
30
+ */
31
+ export declare const MANUAL_REDIRECT_URI = "http://127.0.0.1:11111/callback";
32
+ /**
33
+ * Result from starting the callback server.
34
+ */
35
+ export interface CallbackServerResult {
36
+ /** Port the server is listening on */
37
+ port: number;
38
+ /** Promise that resolves with the authorization code */
39
+ authCode: Promise<string>;
40
+ /** Close the server (for cleanup on error) */
41
+ close: () => void;
42
+ }
43
+ /**
44
+ * Start a temporary HTTP server to receive the OAuth callback.
45
+ *
46
+ * Binds to 127.0.0.1 on a random available port. Validates the
47
+ * state parameter and extracts the authorization code from the
48
+ * callback URL query parameters.
49
+ *
50
+ * The server automatically shuts down after receiving a callback
51
+ * (success or error) or after the 5-minute timeout.
52
+ *
53
+ * @param expectedState - The state parameter to validate against
54
+ * @returns Object with the assigned port and a promise that resolves with the auth code
55
+ */
56
+ export declare function startCallbackServer(expectedState: string): Promise<CallbackServerResult>;
57
+ /**
58
+ * Parse the authorization code and state from a pasted callback URL.
59
+ *
60
+ * In manual mode the user pastes the full URL from their browser's address
61
+ * bar after authentication. This function extracts the `code` and `state`
62
+ * query parameters and validates the state against the expected value.
63
+ *
64
+ * @param pastedUrl - The full callback URL pasted by the user
65
+ * @param expectedState - The state value to validate against (CSRF protection)
66
+ * @returns The authorization code extracted from the URL
67
+ * @throws Error if the URL is malformed, state mismatches, or code is missing
68
+ */
69
+ export declare function parseCallbackUrl(pastedUrl: string, expectedState: string): string;
70
+ /**
71
+ * Detect whether the process is running inside a Docker container.
72
+ *
73
+ * Checks for the `/.dockerenv` sentinel file that Docker creates in every
74
+ * container. Also honours the `PORTA_CONTAINER` environment variable so
75
+ * users can force manual mode in other containerized runtimes (Podman,
76
+ * Kubernetes, etc.) by setting `PORTA_CONTAINER=1`.
77
+ *
78
+ * @returns true if running inside a container
79
+ */
80
+ export declare function isContainerized(): boolean;
81
+ //# sourceMappingURL=callback-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callback-server.d.ts","sourceRoot":"","sources":["../../src/auth/callback-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAaH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,oCAAoC,CAAC;AAMrE;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1B,8CAA8C;IAC9C,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,oBAAoB,CAAC,CA+F/B;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,MAAM,CA8BR;AAMD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAUzC"}
@@ -0,0 +1,193 @@
1
+ /**
2
+ * OAuth callback server and manual URL parsing.
3
+ *
4
+ * Provides two mechanisms for receiving the OAuth authorization code:
5
+ *
6
+ * 1. **Browser mode**: A temporary localhost HTTP server that receives
7
+ * the redirect callback. Binds to 127.0.0.1 on a random port.
8
+ *
9
+ * 2. **Manual mode**: URL parsing for Docker/headless environments where
10
+ * the user pastes the callback URL from their browser's address bar.
11
+ *
12
+ * Security:
13
+ * - Callback server binds to 127.0.0.1 only (loopback)
14
+ * - State parameter validated on every callback (CSRF protection)
15
+ * - 5-minute timeout prevents abandoned login sessions
16
+ *
17
+ * @module auth/callback-server
18
+ */
19
+ import { createServer } from 'node:http';
20
+ import { existsSync } from 'node:fs';
21
+ import { URL } from 'node:url';
22
+ // ---------------------------------------------------------------------------
23
+ // Constants
24
+ // ---------------------------------------------------------------------------
25
+ /** Login flow timeout — 5 minutes to complete browser authentication */
26
+ const LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
27
+ /**
28
+ * Fixed redirect URI used in manual (no-browser) mode.
29
+ *
30
+ * The port number is arbitrary — nothing actually listens on it. The user's
31
+ * browser will fail to load this page after authentication, but the full URL
32
+ * (including the authorization code in the query string) will be visible in
33
+ * the browser's address bar for the user to copy and paste back.
34
+ *
35
+ * Per RFC 8252 §7.3, node-oidc-provider allows flexible port matching for
36
+ * native clients using loopback redirect URIs, so any port works as long as
37
+ * the base pattern (`http://127.0.0.1/callback`) is registered.
38
+ */
39
+ export const MANUAL_REDIRECT_URI = 'http://127.0.0.1:11111/callback';
40
+ /**
41
+ * Start a temporary HTTP server to receive the OAuth callback.
42
+ *
43
+ * Binds to 127.0.0.1 on a random available port. Validates the
44
+ * state parameter and extracts the authorization code from the
45
+ * callback URL query parameters.
46
+ *
47
+ * The server automatically shuts down after receiving a callback
48
+ * (success or error) or after the 5-minute timeout.
49
+ *
50
+ * @param expectedState - The state parameter to validate against
51
+ * @returns Object with the assigned port and a promise that resolves with the auth code
52
+ */
53
+ export function startCallbackServer(expectedState) {
54
+ return new Promise((resolveSetup) => {
55
+ let resolveCode;
56
+ let rejectCode;
57
+ // Promise that resolves when the callback is received with a valid code
58
+ const authCode = new Promise((resolve, reject) => {
59
+ resolveCode = resolve;
60
+ rejectCode = reject;
61
+ });
62
+ const server = createServer((req, res) => {
63
+ const url = new URL(req.url ?? '/', 'http://127.0.0.1');
64
+ // Only handle the /callback path — reject anything else
65
+ if (url.pathname !== '/callback') {
66
+ res.writeHead(404);
67
+ res.end('Not Found');
68
+ return;
69
+ }
70
+ const code = url.searchParams.get('code');
71
+ const returnedState = url.searchParams.get('state');
72
+ const errorParam = url.searchParams.get('error');
73
+ // Case 1: OIDC provider returned an error (user cancelled, etc.)
74
+ if (errorParam) {
75
+ const desc = url.searchParams.get('error_description') || errorParam;
76
+ res.writeHead(200, { 'Content-Type': 'text/html' });
77
+ res.end('<html><body><h1>Login Failed</h1>' +
78
+ '<p>You can close this tab.</p></body></html>');
79
+ server.close();
80
+ rejectCode(new Error(`Authentication failed: ${desc}`));
81
+ return;
82
+ }
83
+ // Case 2: State mismatch — possible CSRF attack
84
+ if (returnedState !== expectedState) {
85
+ res.writeHead(200, { 'Content-Type': 'text/html' });
86
+ res.end('<html><body><h1>Security Error</h1>' +
87
+ '<p>State mismatch. Please try again.</p></body></html>');
88
+ server.close();
89
+ rejectCode(new Error('Security error: state mismatch. Login aborted.'));
90
+ return;
91
+ }
92
+ // Case 3: No authorization code in the callback
93
+ if (!code) {
94
+ res.writeHead(200, { 'Content-Type': 'text/html' });
95
+ res.end('<html><body><h1>Error</h1>' +
96
+ '<p>No authorization code received.</p></body></html>');
97
+ server.close();
98
+ rejectCode(new Error('No authorization code received'));
99
+ return;
100
+ }
101
+ // Case 4: Success — valid code and matching state
102
+ res.writeHead(200, { 'Content-Type': 'text/html' });
103
+ res.end('<html><body><h1>Login Successful</h1>' +
104
+ '<p>You can close this tab and return to the terminal.</p></body></html>');
105
+ server.close();
106
+ resolveCode(code);
107
+ });
108
+ // Timeout: abort the login after 5 minutes of inactivity
109
+ const timeout = setTimeout(() => {
110
+ server.close();
111
+ rejectCode(new Error('Login timed out after 5 minutes'));
112
+ }, LOGIN_TIMEOUT_MS);
113
+ // Unref the timeout so it doesn't keep the Node.js process alive
114
+ timeout.unref();
115
+ // Listen on random available port, loopback interface only
116
+ server.listen(0, '127.0.0.1', () => {
117
+ const addr = server.address();
118
+ const port = typeof addr === 'object' && addr ? addr.port : 0;
119
+ resolveSetup({
120
+ port,
121
+ authCode,
122
+ close: () => server.close(),
123
+ });
124
+ });
125
+ });
126
+ }
127
+ // ---------------------------------------------------------------------------
128
+ // Manual URL Parsing (Docker / headless mode)
129
+ // ---------------------------------------------------------------------------
130
+ /**
131
+ * Parse the authorization code and state from a pasted callback URL.
132
+ *
133
+ * In manual mode the user pastes the full URL from their browser's address
134
+ * bar after authentication. This function extracts the `code` and `state`
135
+ * query parameters and validates the state against the expected value.
136
+ *
137
+ * @param pastedUrl - The full callback URL pasted by the user
138
+ * @param expectedState - The state value to validate against (CSRF protection)
139
+ * @returns The authorization code extracted from the URL
140
+ * @throws Error if the URL is malformed, state mismatches, or code is missing
141
+ */
142
+ export function parseCallbackUrl(pastedUrl, expectedState) {
143
+ let url;
144
+ try {
145
+ url = new URL(pastedUrl.trim());
146
+ }
147
+ catch {
148
+ throw new Error('Invalid URL. Please copy the full URL from your browser\'s address bar.');
149
+ }
150
+ // Check for OIDC error response in the callback URL
151
+ const errorParam = url.searchParams.get('error');
152
+ if (errorParam) {
153
+ const desc = url.searchParams.get('error_description') || errorParam;
154
+ throw new Error(`Authentication failed: ${desc}`);
155
+ }
156
+ // Validate state parameter (CSRF protection)
157
+ const returnedState = url.searchParams.get('state');
158
+ if (returnedState !== expectedState) {
159
+ throw new Error('Security error: state mismatch. Login aborted.');
160
+ }
161
+ // Extract the authorization code
162
+ const code = url.searchParams.get('code');
163
+ if (!code) {
164
+ throw new Error('No authorization code found in the URL.');
165
+ }
166
+ return code;
167
+ }
168
+ // ---------------------------------------------------------------------------
169
+ // Environment Detection
170
+ // ---------------------------------------------------------------------------
171
+ /**
172
+ * Detect whether the process is running inside a Docker container.
173
+ *
174
+ * Checks for the `/.dockerenv` sentinel file that Docker creates in every
175
+ * container. Also honours the `PORTA_CONTAINER` environment variable so
176
+ * users can force manual mode in other containerized runtimes (Podman,
177
+ * Kubernetes, etc.) by setting `PORTA_CONTAINER=1`.
178
+ *
179
+ * @returns true if running inside a container
180
+ */
181
+ export function isContainerized() {
182
+ // Explicit override via environment variable
183
+ if (process.env.PORTA_CONTAINER === '1')
184
+ return true;
185
+ // Docker sentinel file — present in every Docker container
186
+ try {
187
+ return existsSync('/.dockerenv');
188
+ }
189
+ catch {
190
+ return false;
191
+ }
192
+ }
193
+ //# sourceMappingURL=callback-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callback-server.js","sourceRoot":"","sources":["../../src/auth/callback-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Admin metadata and server discovery.
3
+ *
4
+ * Fetches server metadata from the unauthenticated endpoint
5
+ * `GET /api/admin/metadata` to discover the OIDC client_id,
6
+ * issuer URL, and organization slug needed for the login flow.
7
+ *
8
+ * Also provides a health check fetch for the `doctor` command.
9
+ *
10
+ * @module auth/metadata
11
+ */
12
+ import type { AdminMetadata } from './types.js';
13
+ /**
14
+ * Fetch admin metadata from the Porta server.
15
+ *
16
+ * Calls `GET /api/admin/metadata` — an unauthenticated endpoint
17
+ * that only exposes public info needed to initiate the login flow.
18
+ *
19
+ * @param server - Porta server base URL (e.g., "https://porta.local:3443")
20
+ * @returns Admin metadata (issuer, clientId, orgSlug)
21
+ * @throws Error if the server is not reachable or not initialized
22
+ */
23
+ export declare function fetchAdminMetadata(server: string): Promise<AdminMetadata>;
24
+ /**
25
+ * Health check response from `GET /health`.
26
+ */
27
+ export interface HealthResponse {
28
+ /** Overall status */
29
+ status: string;
30
+ /** Individual service statuses */
31
+ services?: Record<string, string>;
32
+ }
33
+ /**
34
+ * Check server health via `GET /health`.
35
+ *
36
+ * This is an unauthenticated endpoint — no credentials needed.
37
+ * Used by the `doctor` command to verify server connectivity.
38
+ *
39
+ * @param server - Porta server base URL
40
+ * @returns Health response or null if server is unreachable
41
+ */
42
+ export declare function fetchHealthStatus(server: string): Promise<HealthResponse | null>;
43
+ //# sourceMappingURL=metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../src/auth/metadata.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAMhD;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,aAAa,CAAC,CAsBxB;AAMD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,qBAAqB;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAahC"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Admin metadata and server discovery.
3
+ *
4
+ * Fetches server metadata from the unauthenticated endpoint
5
+ * `GET /api/admin/metadata` to discover the OIDC client_id,
6
+ * issuer URL, and organization slug needed for the login flow.
7
+ *
8
+ * Also provides a health check fetch for the `doctor` command.
9
+ *
10
+ * @module auth/metadata
11
+ */
12
+ // ---------------------------------------------------------------------------
13
+ // Metadata Fetch
14
+ // ---------------------------------------------------------------------------
15
+ /**
16
+ * Fetch admin metadata from the Porta server.
17
+ *
18
+ * Calls `GET /api/admin/metadata` — an unauthenticated endpoint
19
+ * that only exposes public info needed to initiate the login flow.
20
+ *
21
+ * @param server - Porta server base URL (e.g., "https://porta.local:3443")
22
+ * @returns Admin metadata (issuer, clientId, orgSlug)
23
+ * @throws Error if the server is not reachable or not initialized
24
+ */
25
+ export async function fetchAdminMetadata(server) {
26
+ let response;
27
+ try {
28
+ response = await fetch(`${server}/api/admin/metadata`, {
29
+ signal: AbortSignal.timeout(10_000), // 10s timeout
30
+ });
31
+ }
32
+ catch {
33
+ throw new Error(`Cannot connect to ${server}. Is the server running?`);
34
+ }
35
+ if (!response.ok) {
36
+ if (response.status === 503) {
37
+ throw new Error('Server not initialized. Run "porta init" on the server first.');
38
+ }
39
+ throw new Error(`Cannot fetch admin metadata: HTTP ${response.status}`);
40
+ }
41
+ return response.json();
42
+ }
43
+ /**
44
+ * Check server health via `GET /health`.
45
+ *
46
+ * This is an unauthenticated endpoint — no credentials needed.
47
+ * Used by the `doctor` command to verify server connectivity.
48
+ *
49
+ * @param server - Porta server base URL
50
+ * @returns Health response or null if server is unreachable
51
+ */
52
+ export async function fetchHealthStatus(server) {
53
+ try {
54
+ const response = await fetch(`${server}/health`, {
55
+ signal: AbortSignal.timeout(5_000), // 5s timeout
56
+ });
57
+ if (response.ok) {
58
+ return response.json();
59
+ }
60
+ return null;
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ }
66
+ //# sourceMappingURL=metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.js","sourceRoot":"","sources":["../../src/auth/metadata.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAAc;IAEd,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,qBAAqB,EAAE;YACrD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,cAAc;SACpD,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,qBAAqB,MAAM,0BAA0B,CACtD,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CACb,+DAA+D,CAChE,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,qCAAqC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED,OAAO,QAAQ,CAAC,IAAI,EAA4B,CAAC;AACnD,CAAC;AAgBD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAAc;IAEd,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,SAAS,EAAE;YAC/C,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,aAAa;SAClD,CAAC,CAAC;QAEH,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;YAChB,OAAO,QAAQ,CAAC,IAAI,EAA6B,CAAC;QACpD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}