@knpkv/atlassian-common 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +31 -0
- package/dist/cli-auth/AtlassianCliAuth.d.ts +110 -0
- package/dist/cli-auth/AtlassianCliAuth.d.ts.map +1 -0
- package/dist/cli-auth/AtlassianCliAuth.js +298 -0
- package/dist/cli-auth/AtlassianCliAuth.js.map +1 -0
- package/dist/cli-auth/authCommand.d.ts +34 -0
- package/dist/cli-auth/authCommand.d.ts.map +1 -0
- package/dist/cli-auth/authCommand.js +127 -0
- package/dist/cli-auth/authCommand.js.map +1 -0
- package/dist/cli-auth/index.d.ts +14 -0
- package/dist/cli-auth/index.d.ts.map +1 -0
- package/dist/cli-auth/index.js +14 -0
- package/dist/cli-auth/index.js.map +1 -0
- package/dist/cli-auth/internal/NodeLayers.d.ts +5 -0
- package/dist/cli-auth/internal/NodeLayers.d.ts.map +1 -0
- package/dist/cli-auth/internal/NodeLayers.js +17 -0
- package/dist/cli-auth/internal/NodeLayers.js.map +1 -0
- package/dist/cli-auth/internal/oauthServer.d.ts +54 -0
- package/dist/cli-auth/internal/oauthServer.d.ts.map +1 -0
- package/dist/cli-auth/internal/oauthServer.js +94 -0
- package/dist/cli-auth/internal/oauthServer.js.map +1 -0
- package/dist/cli-auth/openBrowser.d.ts +16 -0
- package/dist/cli-auth/openBrowser.d.ts.map +1 -0
- package/dist/cli-auth/openBrowser.js +30 -0
- package/dist/cli-auth/openBrowser.js.map +1 -0
- package/package.json +5 -1
- package/src/cli-auth/AtlassianCliAuth.ts +539 -0
- package/src/cli-auth/authCommand.ts +227 -0
- package/src/cli-auth/index.ts +20 -0
- package/src/cli-auth/internal/NodeLayers.ts +25 -0
- package/src/cli-auth/internal/oauthServer.ts +168 -0
- package/src/cli-auth/openBrowser.ts +47 -0
- package/test/ApiUpdateWorkflows.test.ts +78 -0
- package/test/cliAuth.authCommand.test.ts +153 -0
- package/test/cliAuth.guardrails.test.ts +76 -0
- package/test/cliAuth.login.test.ts +319 -0
- package/test/cliAuth.oauthServer.test.ts +276 -0
- package/test/cliAuth.openBrowser.test.ts +70 -0
- package/test/cliAuth.refresh.test.ts +304 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `auth` command group every Atlassian CLI exposes, built once over its {@link AtlassianCliAuth}.
|
|
3
|
+
*
|
|
4
|
+
* **Mental model**
|
|
5
|
+
*
|
|
6
|
+
* - **One descriptor, two readers.** The CLI passes the same {@link AtlassianCliDescriptor} it built
|
|
7
|
+
* its auth from, so the setup instructions list exactly the scopes `auth login` requests. A second,
|
|
8
|
+
* hand-written list is how a setup that cannot log in happens: Atlassian rejects an authorization
|
|
9
|
+
* request naming a scope the app lacks.
|
|
10
|
+
* - **Only presentation is configured.** The heading the product's scopes sit under in the
|
|
11
|
+
* developer console, and what to call a site in `--site`.
|
|
12
|
+
* - **A browser that does not open is an error.** The URL is printed first, so a headless or SSH
|
|
13
|
+
* user can still finish by hand; the exit code just stops claiming it worked.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import { Argument, Command, Flag, Prompt } from "effect/cli";
|
|
18
|
+
import * as Console from "effect/Console";
|
|
19
|
+
import * as Effect from "effect/Effect";
|
|
20
|
+
import * as Option from "effect/Option";
|
|
21
|
+
import { openBrowser } from "./openBrowser.js";
|
|
22
|
+
const CREATE_APP_URL = "https://developer.atlassian.com/console/myapps/create-3lo-app/";
|
|
23
|
+
const CONSOLE_APPS_URL = "https://developer.atlassian.com/console/myapps/";
|
|
24
|
+
const CALLBACK_URL = "http://localhost:8585/callback";
|
|
25
|
+
/**
|
|
26
|
+
* The scopes to enable on the OAuth app, by console section. `read:me` is a User Identity scope;
|
|
27
|
+
* `offline_access` is requested at authorize time but is not an app permission, so it is omitted.
|
|
28
|
+
*/
|
|
29
|
+
export const scopeInstructions = (descriptor, apiSection) => {
|
|
30
|
+
const section = (heading, scopes) => scopes.length === 0 ? [] : [` - ${heading}:`, ...scopes.map((scope) => ` ${scope}`)];
|
|
31
|
+
const permissions = descriptor.scopes.filter((scope) => scope !== "offline_access");
|
|
32
|
+
return [
|
|
33
|
+
...section(apiSection, permissions.filter((scope) => scope !== "read:me")),
|
|
34
|
+
...section("User Identity API", permissions.filter((scope) => scope === "read:me"))
|
|
35
|
+
].join("\n");
|
|
36
|
+
};
|
|
37
|
+
/** Print the URL, then try to open it; a browser that never opened fails the command. */
|
|
38
|
+
const visit = (url) => Console.log(`Opening ${url}`).pipe(Effect.andThen(openBrowser(url)));
|
|
39
|
+
/** Build `<cli> auth` with create, manage, configure, login, logout, status, profiles, use, remove. */
|
|
40
|
+
export const makeAuthCommand = (service, descriptor, options) => {
|
|
41
|
+
const withAuth = (f) => Effect.flatMap(Effect.service(service), f);
|
|
42
|
+
const loginHint = `${descriptor.commandName} auth login`;
|
|
43
|
+
const create = Command.make("create", {}, () => Effect.gen(function* () {
|
|
44
|
+
yield* Console.log(`
|
|
45
|
+
Creating OAuth app in Atlassian Developer Console...
|
|
46
|
+
|
|
47
|
+
1. Browser will open to create a new OAuth 2.0 (3LO) app
|
|
48
|
+
2. Enter app name (e.g., "${descriptor.productName} CLI")
|
|
49
|
+
3. After creation, go to "Permissions" and add:
|
|
50
|
+
${scopeInstructions(descriptor, options.apiSection)}
|
|
51
|
+
4. Go to "Authorization" and set callback URL:
|
|
52
|
+
${CALLBACK_URL}
|
|
53
|
+
5. Go to "Settings" and copy Client ID and Secret
|
|
54
|
+
6. Run: ${descriptor.commandName} auth configure --client-id <ID> --client-secret <SECRET>
|
|
55
|
+
`);
|
|
56
|
+
yield* visit(CREATE_APP_URL);
|
|
57
|
+
})).pipe(Command.withDescription("Create OAuth app in Atlassian Developer Console"));
|
|
58
|
+
// Opens the app list, not the app: the console addresses an app by an id that is not the OAuth
|
|
59
|
+
// client id, and the client id is the only app identifier the CLI stores.
|
|
60
|
+
const manage = Command.make("manage", {}, () => Effect.gen(function* () {
|
|
61
|
+
yield* Console.log(`
|
|
62
|
+
Opening the Atlassian Developer Console app list...
|
|
63
|
+
|
|
64
|
+
Select your OAuth app, then under "Permissions" make sure every scope below is
|
|
65
|
+
enabled. \`${loginHint}\` requests all of them, and Atlassian rejects an
|
|
66
|
+
authorization request naming a scope the app does not have — so a missing scope
|
|
67
|
+
here fails the login itself, not just the command that needed it.
|
|
68
|
+
|
|
69
|
+
${scopeInstructions(descriptor, options.apiSection)}
|
|
70
|
+
|
|
71
|
+
Under "Authorization", the callback URL must be:
|
|
72
|
+
${CALLBACK_URL}
|
|
73
|
+
|
|
74
|
+
After adding scopes, run: ${loginHint}
|
|
75
|
+
`);
|
|
76
|
+
yield* visit(CONSOLE_APPS_URL);
|
|
77
|
+
})).pipe(Command.withDescription("Open the Atlassian Developer Console to edit the OAuth app's scopes"));
|
|
78
|
+
const configure = Command.make("configure", {
|
|
79
|
+
clientId: Flag.String("client-id").pipe(Flag.withDescription("OAuth client ID from Atlassian Developer Console"), Flag.optional),
|
|
80
|
+
clientSecret: Flag.String("client-secret").pipe(Flag.withDescription("OAuth client secret"), Flag.optional)
|
|
81
|
+
}, ({ clientId, clientSecret }) => withAuth((auth) => Effect.gen(function* () {
|
|
82
|
+
const id = Option.isSome(clientId)
|
|
83
|
+
? clientId.value
|
|
84
|
+
: yield* Prompt.String({ message: "Enter OAuth client ID:" });
|
|
85
|
+
const secret = Option.isSome(clientSecret)
|
|
86
|
+
? clientSecret.value
|
|
87
|
+
: yield* Prompt.String({ message: "Enter OAuth client secret:" });
|
|
88
|
+
yield* auth.configure({ clientId: id, clientSecret: secret });
|
|
89
|
+
yield* Console.log(`OAuth configured. Run '${loginHint}' to authenticate.`);
|
|
90
|
+
}))).pipe(Command.withDescription("Configure OAuth client credentials"));
|
|
91
|
+
const login = Command.make("login", {
|
|
92
|
+
site: Flag.String("site").pipe(Flag.withDescription(`${descriptor.productName} site URL to use (for accounts with multiple sites)`), Flag.optional)
|
|
93
|
+
}, ({ site }) => withAuth((auth) => Effect.gen(function* () {
|
|
94
|
+
const sites = yield* auth.login(Option.isSome(site) ? { siteUrl: site.value } : undefined);
|
|
95
|
+
if (Array.isArray(sites) && sites.length > 0) {
|
|
96
|
+
yield* Console.log("\nRe-run with --site to select a specific site.");
|
|
97
|
+
}
|
|
98
|
+
}))).pipe(Command.withDescription("Authenticate with Atlassian via OAuth"));
|
|
99
|
+
const logout = Command.make("logout", {}, () => withAuth((auth) => auth.logout().pipe(Effect.andThen(Console.log("Logged out"))))).pipe(Command.withDescription("Remove stored authentication"));
|
|
100
|
+
const status = Command.make("status", {}, () => withAuth((auth) => Effect.gen(function* () {
|
|
101
|
+
const profile = yield* auth.getActiveProfile();
|
|
102
|
+
if (profile === null) {
|
|
103
|
+
return yield* Console.log(`Not logged in. Use '${loginHint}' to authenticate.`);
|
|
104
|
+
}
|
|
105
|
+
const user = profile.token.user;
|
|
106
|
+
yield* Console.log(`Active profile: ${profile.name}`);
|
|
107
|
+
yield* Console.log(`Account: ${user !== undefined ? `${user.name} (${user.email})` : "unknown user"}`);
|
|
108
|
+
yield* Console.log(`Site: ${profile.token.site_url}`);
|
|
109
|
+
yield* Console.log(`Profile ID: ${profile.id}`);
|
|
110
|
+
}))).pipe(Command.withDescription("Show authentication status"));
|
|
111
|
+
const profiles = Command.make("profiles", {}, () => withAuth((auth) => Effect.gen(function* () {
|
|
112
|
+
const [stored, active] = yield* Effect.all([auth.listProfiles(), auth.getActiveProfile()]);
|
|
113
|
+
if (stored.length === 0) {
|
|
114
|
+
return yield* Console.log(`No auth profiles. Use '${loginHint}' to authenticate.`);
|
|
115
|
+
}
|
|
116
|
+
for (const profile of stored) {
|
|
117
|
+
yield* Console.log(`${active?.id === profile.id ? "*" : " "} ${profile.id}`);
|
|
118
|
+
yield* Console.log(` ${profile.name}`);
|
|
119
|
+
yield* Console.log(` ${profile.token.site_url}`);
|
|
120
|
+
}
|
|
121
|
+
}))).pipe(Command.withDescription("List stored auth profiles"));
|
|
122
|
+
const profile = Argument.String("profile").pipe(Argument.withDescription("Profile ID, name, site URL, cloud ID, or account ID"));
|
|
123
|
+
const use = Command.make("use", { profile }, ({ profile }) => withAuth((auth) => Effect.flatMap(auth.switchProfile(profile), (selected) => Console.log(selected === null ? `Profile not found: ${profile}` : `Active profile: ${selected.name}`)))).pipe(Command.withDescription("Switch active auth profile"));
|
|
124
|
+
const remove = Command.make("remove", { profile }, ({ profile }) => withAuth((auth) => Effect.flatMap(auth.removeProfile(profile), (removed) => Console.log(removed === null ? `Profile not found: ${profile}` : `Removed profile: ${removed.name}`)))).pipe(Command.withDescription("Remove stored auth profile"));
|
|
125
|
+
return Command.make("auth").pipe(Command.withDescription("Manage OAuth authentication"), Command.withSubcommands([create, manage, configure, login, logout, status, profiles, use, remove]));
|
|
126
|
+
};
|
|
127
|
+
//# sourceMappingURL=authCommand.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authCommand.js","sourceRoot":"","sources":["../../src/cli-auth/authCommand.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth2 login and token refresh for Atlassian command-line tools.
|
|
3
|
+
*
|
|
4
|
+
* Separate from `./auth` because it carries a Node callback server and a
|
|
5
|
+
* browser launcher; `./auth` stays platform-neutral for server-side consumers.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
export { type AccessibleSite, type AtlassianCliAuth, type AtlassianCliAuthOptions, type AtlassianCliDescriptor, type LoginOptions, makeAtlassianCliAuth } from "./AtlassianCliAuth.js";
|
|
10
|
+
export { type AuthCommandOptions, type AuthCommandService, makeAuthCommand, scopeInstructions } from "./authCommand.js";
|
|
11
|
+
export { HttpServerFactoryLive, NodeCliAuthLive } from "./internal/NodeLayers.js";
|
|
12
|
+
export { type HttpServerFactory, HttpServerFactoryTag, makeHttpServerFactory } from "./internal/oauthServer.js";
|
|
13
|
+
export { BrowserOpenError, openBrowser } from "./openBrowser.js";
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli-auth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,YAAY,EACjB,oBAAoB,EACrB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACvH,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AACjF,OAAO,EAAE,KAAK,iBAAiB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAA;AAC/G,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth2 login and token refresh for Atlassian command-line tools.
|
|
3
|
+
*
|
|
4
|
+
* Separate from `./auth` because it carries a Node callback server and a
|
|
5
|
+
* browser launcher; `./auth` stays platform-neutral for server-side consumers.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
export { makeAtlassianCliAuth } from "./AtlassianCliAuth.js";
|
|
10
|
+
export { makeAuthCommand, scopeInstructions } from "./authCommand.js";
|
|
11
|
+
export { HttpServerFactoryLive, NodeCliAuthLive } from "./internal/NodeLayers.js";
|
|
12
|
+
export { HttpServerFactoryTag, makeHttpServerFactory } from "./internal/oauthServer.js";
|
|
13
|
+
export { BrowserOpenError, openBrowser } from "./openBrowser.js";
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli-auth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAML,oBAAoB,EACrB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAoD,eAAe,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACvH,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AACjF,OAAO,EAA0B,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAA;AAC/G,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import * as Layer from "effect/Layer";
|
|
2
|
+
export declare const HttpServerFactoryLive: Layer.Layer<import("./oauthServer.js").HttpServerFactoryTag, never, never>;
|
|
3
|
+
/** Everything `makeAtlassianCliAuth` needs on Node besides HTTP client, spawner and Crypto. */
|
|
4
|
+
export declare const NodeCliAuthLive: Layer.Layer<import("effect/FileSystem").FileSystem | import("../../config/ConfigPaths.js").HomeDirectoryTag | import("./oauthServer.js").HttpServerFactoryTag | import("effect/Path").Path, never, never>;
|
|
5
|
+
//# sourceMappingURL=NodeLayers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeLayers.d.ts","sourceRoot":"","sources":["../../../src/cli-auth/internal/NodeLayers.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAKrC,eAAO,MAAM,qBAAqB,4EAEjC,CAAA;AAED,+FAA+F;AAC/F,eAAO,MAAM,eAAe,2MAK3B,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js adapters for cli-auth: the OAuth callback server and token storage — the
|
|
3
|
+
* only cli-auth file importing `@effect/platform-node`.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
|
|
8
|
+
import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
|
|
9
|
+
import * as NodePath from "@effect/platform-node/NodePath";
|
|
10
|
+
import * as Layer from "effect/Layer";
|
|
11
|
+
import { createServer } from "node:http";
|
|
12
|
+
import { HomeDirectoryLive } from "../../config/ConfigPaths.js";
|
|
13
|
+
import { makeHttpServerFactory } from "./oauthServer.js";
|
|
14
|
+
export const HttpServerFactoryLive = makeHttpServerFactory((options) => NodeHttpServer.layerServer(createServer, options));
|
|
15
|
+
/** Everything `makeAtlassianCliAuth` needs on Node besides HTTP client, spawner and Crypto. */
|
|
16
|
+
export const NodeCliAuthLive = Layer.mergeAll(HttpServerFactoryLive, NodeFileSystem.layer, NodePath.layer, HomeDirectoryLive);
|
|
17
|
+
//# sourceMappingURL=NodeLayers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeLayers.js","sourceRoot":"","sources":["../../../src/cli-auth/internal/NodeLayers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,cAAc,MAAM,sCAAsC,CAAA;AACtE,OAAO,KAAK,cAAc,MAAM,sCAAsC,CAAA;AACtE,OAAO,KAAK,QAAQ,MAAM,gCAAgC,CAAA;AAC1D,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AAExD,MAAM,CAAC,MAAM,qBAAqB,GAAG,qBAAqB,CACxD,CAAC,OAAO,EAAE,EAAE,CAAC,cAAc,CAAC,WAAW,CAAC,YAAY,EAAE,OAAO,CAAC,CAC/D,CAAA;AAED,+FAA+F;AAC/F,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC,QAAQ,CAC3C,qBAAqB,EACrB,cAAc,CAAC,KAAK,EACpB,QAAQ,CAAC,KAAK,EACd,iBAAiB,CAClB,CAAA"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local HTTP callback server for OAuth2 authorization code capture.
|
|
3
|
+
*
|
|
4
|
+
* **Mental model**
|
|
5
|
+
*
|
|
6
|
+
* - **Scope-owned lifecycle**: {@link startCallbackServer} returns a `codePromise`
|
|
7
|
+
* (Deferred). The server validates the CSRF `state` parameter, resolves the
|
|
8
|
+
* Deferred with the authorization code, and stops when its enclosing scope closes.
|
|
9
|
+
* - **Port auto-discovery**: Tries default port 8585, increments on conflict up to 8594.
|
|
10
|
+
* - **Ready before return**: the handler is installed before the port is handed back,
|
|
11
|
+
* so the caller never advertises a callback URL nothing answers.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
import * as Context from "effect/Context";
|
|
16
|
+
import * as Effect from "effect/Effect";
|
|
17
|
+
import { HttpServer } from "effect/http";
|
|
18
|
+
import type * as HttpServerError from "effect/http/HttpServerError";
|
|
19
|
+
import * as Layer from "effect/Layer";
|
|
20
|
+
import * as Scope from "effect/Scope";
|
|
21
|
+
import { OAuthError } from "../../auth/OAuthErrors.js";
|
|
22
|
+
/**
|
|
23
|
+
* Creates the platform HTTP server for one listen attempt. The Node adapter is
|
|
24
|
+
* {@link HttpServerFactoryLive}; tests substitute ephemeral or failing servers.
|
|
25
|
+
*/
|
|
26
|
+
export interface HttpServerFactory {
|
|
27
|
+
readonly createServerLayer: (options: CallbackServerListenOptions) => Layer.Layer<HttpServer.HttpServer, HttpServerError.ServeError, never>;
|
|
28
|
+
}
|
|
29
|
+
export interface CallbackServerListenOptions {
|
|
30
|
+
readonly host: "localhost";
|
|
31
|
+
readonly port: number;
|
|
32
|
+
}
|
|
33
|
+
export declare const callbackServerListenOptions: (port: number) => CallbackServerListenOptions;
|
|
34
|
+
export declare const callbackUrl: (port: number) => string;
|
|
35
|
+
declare const HttpServerFactoryTag_base: Context.ServiceClass<HttpServerFactoryTag, "@knpkv/atlassian-common/cli-auth/HttpServerFactory", HttpServerFactory>;
|
|
36
|
+
export declare class HttpServerFactoryTag extends HttpServerFactoryTag_base {
|
|
37
|
+
}
|
|
38
|
+
/** Builds a {@link HttpServerFactoryTag} layer from a per-attempt server layer. */
|
|
39
|
+
export declare const makeHttpServerFactory: (createLayerFn: (options: CallbackServerListenOptions) => Layer.Layer<HttpServer.HttpServer, HttpServerError.ServeError, never>) => Layer.Layer<HttpServerFactoryTag>;
|
|
40
|
+
export interface CallbackServerResult {
|
|
41
|
+
/** Resolves with the authorization code, or fails with the provider's error. */
|
|
42
|
+
readonly codePromise: Effect.Effect<string, OAuthError>;
|
|
43
|
+
/** The port the server is listening on */
|
|
44
|
+
readonly port: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Start a local HTTP server to receive the OAuth callback for `expectedState`.
|
|
48
|
+
*
|
|
49
|
+
* Fails with one `OAuthError` (step `authorize`) when no port in range can be
|
|
50
|
+
* bound; the server stops when the enclosing scope closes.
|
|
51
|
+
*/
|
|
52
|
+
export declare const startCallbackServer: (expectedState: string) => Effect.Effect<CallbackServerResult, OAuthError, HttpServerFactoryTag | Scope.Scope>;
|
|
53
|
+
export {};
|
|
54
|
+
//# sourceMappingURL=oauthServer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauthServer.d.ts","sourceRoot":"","sources":["../../../src/cli-auth/internal/oauthServer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AAEzC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAAc,UAAU,EAAyC,MAAM,aAAa,CAAA;AAC3F,OAAO,KAAK,KAAK,eAAe,MAAM,6BAA6B,CAAA;AACnE,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAGrC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAMtD;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,iBAAiB,EAAE,CAAC,OAAO,EAAE,2BAA2B,KAAK,KAAK,CAAC,KAAK,CAC/E,UAAU,CAAC,UAAU,EACrB,eAAe,CAAC,UAAU,EAC1B,KAAK,CACN,CAAA;CACF;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,eAAO,MAAM,2BAA2B,SAAU,MAAM,KAAG,2BAGzD,CAAA;AAEF,eAAO,MAAM,WAAW,SAAU,MAAM,KAAG,MAA6C,CAAA;;AAExF,qBAAa,oBAAqB,SAAQ,yBAGe;CAAG;AAE5D,mFAAmF;AACnF,eAAO,MAAM,qBAAqB,kBACjB,CACb,OAAO,EAAE,2BAA2B,KACjC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,EAAE,eAAe,CAAC,UAAU,EAAE,KAAK,CAAC,KACzE,KAAK,CAAC,KAAK,CAAC,oBAAoB,CAG/B,CAAA;AAEJ,MAAM,WAAW,oBAAoB;IACnC,gFAAgF;IAChF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IACvD,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAWD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,kBACf,MAAM,KACpB,MAAM,CAAC,MAAM,CAAC,oBAAoB,EAAE,UAAU,EAAE,oBAAoB,GAAG,KAAK,CAAC,KAAK,CA2EjF,CAAA"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local HTTP callback server for OAuth2 authorization code capture.
|
|
3
|
+
*
|
|
4
|
+
* **Mental model**
|
|
5
|
+
*
|
|
6
|
+
* - **Scope-owned lifecycle**: {@link startCallbackServer} returns a `codePromise`
|
|
7
|
+
* (Deferred). The server validates the CSRF `state` parameter, resolves the
|
|
8
|
+
* Deferred with the authorization code, and stops when its enclosing scope closes.
|
|
9
|
+
* - **Port auto-discovery**: Tries default port 8585, increments on conflict up to 8594.
|
|
10
|
+
* - **Ready before return**: the handler is installed before the port is handed back,
|
|
11
|
+
* so the caller never advertises a callback URL nothing answers.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
import * as Context from "effect/Context";
|
|
16
|
+
import * as Deferred from "effect/Deferred";
|
|
17
|
+
import * as Effect from "effect/Effect";
|
|
18
|
+
import { HttpRouter, HttpServer, HttpServerRequest, HttpServerResponse } from "effect/http";
|
|
19
|
+
import * as Layer from "effect/Layer";
|
|
20
|
+
import { NetAddress } from "effect/net";
|
|
21
|
+
import * as Schema from "effect/Schema";
|
|
22
|
+
import * as Scope from "effect/Scope";
|
|
23
|
+
import { OAuthError } from "../../auth/OAuthErrors.js";
|
|
24
|
+
const DEFAULT_PORT = 8585;
|
|
25
|
+
const MAX_PORT = 8594;
|
|
26
|
+
export const callbackServerListenOptions = (port) => ({
|
|
27
|
+
host: "localhost",
|
|
28
|
+
port
|
|
29
|
+
});
|
|
30
|
+
export const callbackUrl = (port) => `http://localhost:${port}/callback`;
|
|
31
|
+
export class HttpServerFactoryTag extends Context.Service()("@knpkv/atlassian-common/cli-auth/HttpServerFactory") {
|
|
32
|
+
}
|
|
33
|
+
/** Builds a {@link HttpServerFactoryTag} layer from a per-attempt server layer. */
|
|
34
|
+
export const makeHttpServerFactory = (createLayerFn) => Layer.succeed(HttpServerFactoryTag, {
|
|
35
|
+
createServerLayer: createLayerFn
|
|
36
|
+
});
|
|
37
|
+
const AddressInUseCause = Schema.Struct({
|
|
38
|
+
code: Schema.Literal("EADDRINUSE")
|
|
39
|
+
});
|
|
40
|
+
const isAddressInUse = (error) => Schema.is(AddressInUseCause)(error.cause);
|
|
41
|
+
const page = (heading, body) => HttpServerResponse.html(`<html><body><h1>${heading}</h1><p>${body}</p></body></html>`);
|
|
42
|
+
/**
|
|
43
|
+
* Start a local HTTP server to receive the OAuth callback for `expectedState`.
|
|
44
|
+
*
|
|
45
|
+
* Fails with one `OAuthError` (step `authorize`) when no port in range can be
|
|
46
|
+
* bound; the server stops when the enclosing scope closes.
|
|
47
|
+
*/
|
|
48
|
+
export const startCallbackServer = (expectedState) => Effect.gen(function* () {
|
|
49
|
+
const factory = yield* HttpServerFactoryTag;
|
|
50
|
+
const deferred = yield* Deferred.make();
|
|
51
|
+
const scope = yield* Effect.scope;
|
|
52
|
+
// Map to OAuthError once, outside the retry, so a run of occupied ports
|
|
53
|
+
// does not nest one OAuthError inside another.
|
|
54
|
+
const buildServer = (port) => Layer.build(factory.createServerLayer(callbackServerListenOptions(port))).pipe(Scope.provide(scope), Effect.map((context) => Context.get(context, HttpServer.HttpServer)), Effect.catchIf((error) => isAddressInUse(error) && port < MAX_PORT, () => buildServer(port + 1)));
|
|
55
|
+
const server = yield* buildServer(DEFAULT_PORT).pipe(Effect.mapError((cause) => new OAuthError({ step: "authorize", cause })));
|
|
56
|
+
if (!NetAddress.isInetAddress(server.address)) {
|
|
57
|
+
return yield* new OAuthError({ step: "authorize", cause: "OAuth callback server must listen on a TCP port" });
|
|
58
|
+
}
|
|
59
|
+
const port = server.address.port;
|
|
60
|
+
const router = yield* HttpRouter.make;
|
|
61
|
+
yield* router.add("GET", "/callback", Effect.gen(function* () {
|
|
62
|
+
const req = yield* HttpServerRequest.HttpServerRequest;
|
|
63
|
+
const url = new URL(req.url, callbackUrl(port));
|
|
64
|
+
const code = url.searchParams.get("code");
|
|
65
|
+
const state = url.searchParams.get("state");
|
|
66
|
+
const error = url.searchParams.get("error");
|
|
67
|
+
const errorDescription = url.searchParams.get("error_description");
|
|
68
|
+
if (state !== expectedState) {
|
|
69
|
+
return page("Security Error", "State verification failed.").pipe(HttpServerResponse.setStatus(403));
|
|
70
|
+
}
|
|
71
|
+
if (error !== null && error !== "") {
|
|
72
|
+
// An empty description is no description: report the error code instead.
|
|
73
|
+
yield* Deferred.fail(deferred, new OAuthError({
|
|
74
|
+
step: "authorize",
|
|
75
|
+
cause: errorDescription === null || errorDescription === "" ? error : errorDescription
|
|
76
|
+
}));
|
|
77
|
+
return page("Authorization Failed", "You can close this window.");
|
|
78
|
+
}
|
|
79
|
+
if (code === null || code === "") {
|
|
80
|
+
yield* Deferred.fail(deferred, new OAuthError({ step: "authorize", cause: "No authorization code received" }));
|
|
81
|
+
return page("Error", "No authorization code received.");
|
|
82
|
+
}
|
|
83
|
+
yield* Deferred.succeed(deferred, code);
|
|
84
|
+
return page("Success!", "You can close this window and return to the terminal.");
|
|
85
|
+
}));
|
|
86
|
+
// Installs the handler in this scope and returns; a failure to install
|
|
87
|
+
// surfaces here rather than leaving the caller waiting on a dead server.
|
|
88
|
+
yield* HttpServer.serveEffect(router.asHttpEffect()).pipe(Effect.provideService(HttpServer.HttpServer, server));
|
|
89
|
+
return {
|
|
90
|
+
codePromise: Deferred.await(deferred),
|
|
91
|
+
port
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
//# sourceMappingURL=oauthServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauthServer.js","sourceRoot":"","sources":["../../../src/cli-auth/internal/oauthServer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import type * as PlatformError from "effect/PlatformError";
|
|
3
|
+
import { ChildProcessSpawner } from "effect/process";
|
|
4
|
+
declare const BrowserOpenError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
5
|
+
readonly _tag: "BrowserOpenError";
|
|
6
|
+
} & Readonly<A>;
|
|
7
|
+
/** A launcher ran but reported failure (for example `xdg-open` with no browser on a headless host). */
|
|
8
|
+
export declare class BrowserOpenError extends BrowserOpenError_base<{
|
|
9
|
+
readonly command: string;
|
|
10
|
+
readonly exitCode: number;
|
|
11
|
+
}> {
|
|
12
|
+
}
|
|
13
|
+
/** Open `url` in the user's browser. */
|
|
14
|
+
export declare const openBrowser: (url: string) => Effect.Effect<void, BrowserOpenError | PlatformError.PlatformError, ChildProcessSpawner.ChildProcessSpawner>;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=openBrowser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openBrowser.d.ts","sourceRoot":"","sources":["../../src/cli-auth/openBrowser.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,aAAa,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAgB,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;;;;AAElE,uGAAuG;AACvG,qBAAa,gBAAiB,SAAQ,sBAAqC;IACzE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B,CAAC;CAAG;AAoBL,wCAAwC;AACxC,eAAO,MAAM,WAAW,QACjB,MAAM,KACV,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,GAAG,aAAa,CAAC,aAAa,EAAE,mBAAmB,CAAC,mBAAmB,CAI3G,CAAA"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform browser launcher backed by Effect child process services.
|
|
3
|
+
*
|
|
4
|
+
* Tries `open`, then `xdg-open`, then `rundll32.exe`, stopping at the first that
|
|
5
|
+
* exits 0. A launcher that is missing or exits non-zero falls through to the next;
|
|
6
|
+
* when none succeeds the last failure is reported, typed, so the caller decides
|
|
7
|
+
* whether a browser that never opened is fatal.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import * as Data from "effect/Data";
|
|
12
|
+
import * as Effect from "effect/Effect";
|
|
13
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/process";
|
|
14
|
+
/** A launcher ran but reported failure (for example `xdg-open` with no browser on a headless host). */
|
|
15
|
+
export class BrowserOpenError extends Data.TaggedError("BrowserOpenError") {
|
|
16
|
+
}
|
|
17
|
+
const run = (command, args) => Effect.gen(function* () {
|
|
18
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
19
|
+
const exitCode = yield* spawner.exitCode(ChildProcess.make(command, args, {
|
|
20
|
+
stdin: "ignore",
|
|
21
|
+
stdout: "ignore",
|
|
22
|
+
stderr: "ignore"
|
|
23
|
+
}));
|
|
24
|
+
if (exitCode !== 0) {
|
|
25
|
+
return yield* new BrowserOpenError({ command, exitCode });
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
/** Open `url` in the user's browser. */
|
|
29
|
+
export const openBrowser = (url) => run("open", [url]).pipe(Effect.catch(() => run("xdg-open", [url])), Effect.catch(() => run("rundll32.exe", ["url.dll,FileProtocolHandler", url])));
|
|
30
|
+
//# sourceMappingURL=openBrowser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openBrowser.js","sourceRoot":"","sources":["../../src/cli-auth/openBrowser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,IAAI,MAAM,aAAa,CAAA;AACnC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAElE,uGAAuG;AACvG,MAAM,OAAO,gBAAiB,SAAQ,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAGvE;CAAG;AAEL,MAAM,GAAG,GAAG,CACV,OAAe,EACf,IAA2B,EACmF,EAAE,CAChH,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,mBAAmB,CAAC,mBAAmB,CAAA;IAC9D,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,CACtC,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE;QAC/B,KAAK,EAAE,QAAQ;QACf,MAAM,EAAE,QAAQ;QAChB,MAAM,EAAE,QAAQ;KACjB,CAAC,CACH,CAAA;IACD,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;QACnB,OAAO,KAAK,CAAC,CAAC,IAAI,gBAAgB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAA;IAC3D,CAAC;AACH,CAAC,CAAC,CAAA;AAEJ,wCAAwC;AACxC,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,GAAW,EACmG,EAAE,CAChH,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CACrB,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAC1C,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC,CAAC,CAC9E,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knpkv/atlassian-common",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Shared utilities for Atlassian tools (AST, serializers, auth, config)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "knpkv",
|
|
@@ -42,6 +42,10 @@
|
|
|
42
42
|
"import": "./dist/auth/index.js",
|
|
43
43
|
"types": "./dist/auth/index.d.ts"
|
|
44
44
|
},
|
|
45
|
+
"./cli-auth": {
|
|
46
|
+
"import": "./dist/cli-auth/index.js",
|
|
47
|
+
"types": "./dist/cli-auth/index.d.ts"
|
|
48
|
+
},
|
|
45
49
|
"./config": {
|
|
46
50
|
"import": "./dist/config/index.js",
|
|
47
51
|
"types": "./dist/config/index.d.ts"
|