@eliya-oss/agent-slack 0.1.8 → 0.1.10
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/.agents/cli-api.md
CHANGED
|
@@ -55,6 +55,7 @@ Auth behavior:
|
|
|
55
55
|
- Browser login uses Agent Slack's public Slack app.
|
|
56
56
|
- Development and self-hosted OAuth can use Slack app credentials. The **Client ID** and **Client Secret** come from the Slack app's **Basic Information > App Credentials** section.
|
|
57
57
|
- OAuth opens the Slack OAuth URL in the default browser by default. Use `--no-open` to print the URL only, or `--auth-url-out PATH` for headless agents.
|
|
58
|
+
- Distributed Slack apps use `https://aslk.vercel.app/oauth/slack/callback` as the HTTPS relay redirect URI. The relay forwards Slack's `code` and `state` to the local callback.
|
|
58
59
|
- PKCE uses `user_scope=...`; OAuth with app credentials uses `scope=...` for bot scopes.
|
|
59
60
|
- Default local callback: `http://localhost:45454/oauth/slack/callback`.
|
|
60
61
|
- Stores profiles outside the project directory. The default store is `~/.config/agent-slack/profiles.json`; set `AGENT_SLACK_TOKEN_STORE=keychain` on macOS to keep token secrets in Keychain and only profile metadata on disk.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @eliya-oss/agent-slack
|
|
2
2
|
|
|
3
|
+
## 0.1.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Use the hosted Agent Slack OAuth relay for default browser login.
|
|
8
|
+
|
|
9
|
+
## 0.1.9
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Support hosted HTTPS OAuth redirect relays for distributed Slack apps.
|
|
14
|
+
|
|
3
15
|
## 0.1.8
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -15,10 +15,14 @@ export class NodeLocalhostOAuthFlow {
|
|
|
15
15
|
const redirectHost = env.AGENT_SLACK_OAUTH_REDIRECT_HOST ?? env.SLK_OAUTH_REDIRECT_HOST;
|
|
16
16
|
const redirectPort = env.AGENT_SLACK_OAUTH_REDIRECT_PORT ?? env.SLK_OAUTH_REDIRECT_PORT;
|
|
17
17
|
const redirectPath = env.AGENT_SLACK_OAUTH_REDIRECT_PATH ?? env.SLK_OAUTH_REDIRECT_PATH;
|
|
18
|
+
const slackRedirectUri = env.AGENT_SLACK_OAUTH_REDIRECT_URI ?? env.AGENT_SLACK_OAUTH_PUBLIC_REDIRECT_URI;
|
|
19
|
+
const localCallbackUri = env.AGENT_SLACK_OAUTH_LOCAL_CALLBACK_URI;
|
|
18
20
|
const timeoutMs = env.AGENT_SLACK_OAUTH_TIMEOUT_MS ?? env.SLK_OAUTH_TIMEOUT_MS;
|
|
19
21
|
return new NodeLocalhostOAuthFlow({
|
|
20
22
|
authorizeUrl: env.AGENT_SLACK_OAUTH_AUTHORIZE_URL ?? env.SLK_SLACK_OAUTH_AUTHORIZE_URL ?? (emulatorBaseUrl === undefined ? "https://slack.com/oauth/v2/authorize" : `${emulatorBaseUrl}/oauth/v2/authorize`),
|
|
21
23
|
tokenUrl: env.AGENT_SLACK_OAUTH_ACCESS_URL ?? env.SLK_SLACK_OAUTH_ACCESS_URL ?? (apiBaseUrl === undefined ? "https://slack.com/api/oauth.v2.access" : `${apiBaseUrl.replace(/\/?$/, "/")}oauth.v2.access`),
|
|
24
|
+
...(slackRedirectUri === undefined ? {} : { defaultSlackRedirectUri: slackRedirectUri }),
|
|
25
|
+
...(localCallbackUri === undefined ? {} : { defaultLocalCallbackUri: localCallbackUri }),
|
|
22
26
|
...(redirectHost === undefined ? {} : { defaultRedirectHost: redirectHost }),
|
|
23
27
|
...(redirectPort === undefined ? {} : { defaultRedirectPort: Number(redirectPort) }),
|
|
24
28
|
...(redirectPath === undefined ? {} : { defaultRedirectPath: redirectPath }),
|
|
@@ -30,15 +34,20 @@ export class NodeLocalhostOAuthFlow {
|
|
|
30
34
|
const codeVerifier = input.pkce === true ? randomBytes(32).toString("base64url") : undefined;
|
|
31
35
|
const timeoutMs = input.timeoutMs ?? this.options.defaultTimeoutMs ?? 120_000;
|
|
32
36
|
const server = createServer();
|
|
33
|
-
const
|
|
37
|
+
const slackRedirectUri = input.redirectUri ?? this.options.defaultSlackRedirectUri;
|
|
38
|
+
const localCallbackUri = input.localCallbackUri ??
|
|
39
|
+
this.options.defaultLocalCallbackUri ??
|
|
40
|
+
(slackRedirectUri !== undefined && isLocalCallbackUri(slackRedirectUri) ? slackRedirectUri : undefined);
|
|
41
|
+
const started = await listen(server, localCallbackUri, {
|
|
34
42
|
host: this.options.defaultRedirectHost ?? defaultRedirectHost,
|
|
35
43
|
port: this.options.defaultRedirectPort ?? defaultRedirectPort,
|
|
36
44
|
path: this.options.defaultRedirectPath ?? "/oauth/slack/callback"
|
|
37
45
|
});
|
|
46
|
+
const redirectUri = slackRedirectUri ?? started.redirectUri;
|
|
38
47
|
const authorizationUrl = buildAuthorizationUrl({
|
|
39
48
|
authorizeUrl: this.options.authorizeUrl,
|
|
40
49
|
clientId: input.clientId,
|
|
41
|
-
redirectUri
|
|
50
|
+
redirectUri,
|
|
42
51
|
scopes: input.scopes,
|
|
43
52
|
userScopes: input.userScopes,
|
|
44
53
|
state,
|
|
@@ -85,7 +94,7 @@ export class NodeLocalhostOAuthFlow {
|
|
|
85
94
|
clientId: input.clientId,
|
|
86
95
|
clientSecret: input.clientSecret,
|
|
87
96
|
codeVerifier,
|
|
88
|
-
redirectUri
|
|
97
|
+
redirectUri
|
|
89
98
|
});
|
|
90
99
|
const profile = toAuthProfile(input.profileName, access, input.pkce === true ? "user" : "bot");
|
|
91
100
|
response.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end("<html><body>Slack auth complete. You can close this tab.</body></html>");
|
|
@@ -124,6 +133,13 @@ const listen = (server, redirectUri, fallback) => new Promise((resolve, reject)
|
|
|
124
133
|
});
|
|
125
134
|
const defaultRedirectHost = "localhost";
|
|
126
135
|
const defaultRedirectPort = 45454;
|
|
136
|
+
const isLocalCallbackUri = (value) => {
|
|
137
|
+
const parsed = new URL(value);
|
|
138
|
+
return parsed.protocol === "http:" && (parsed.hostname === "localhost" ||
|
|
139
|
+
parsed.hostname === "127.0.0.1" ||
|
|
140
|
+
parsed.hostname === "[::1]" ||
|
|
141
|
+
parsed.hostname === "::1");
|
|
142
|
+
};
|
|
127
143
|
const buildAuthorizationUrl = (input) => {
|
|
128
144
|
const url = new URL(input.authorizeUrl);
|
|
129
145
|
url.searchParams.set("client_id", input.clientId);
|
|
@@ -207,13 +207,14 @@ const authCommand = async (parsed, services) => {
|
|
|
207
207
|
const scopes = splitCsv(flagString(parsed, "scopes"));
|
|
208
208
|
const userScopes = splitCsv(flagString(parsed, "user-scopes"));
|
|
209
209
|
const pkceUserScopes = userScopes.length > 0 ? userScopes : scopes.length > 0 ? scopes : defaultPkceUserScopes;
|
|
210
|
+
const redirectUri = flagString(parsed, "redirect-uri") ?? (!byoOAuth && clientId === defaultPkceClientId ? defaultPkceRedirectUri : undefined);
|
|
210
211
|
const profile = await services.oauthFlow.login({
|
|
211
212
|
profileName,
|
|
212
213
|
clientId,
|
|
213
214
|
...(byoOAuth ? { clientSecret } : { pkce: true }),
|
|
214
215
|
scopes: byoOAuth ? (scopes.length === 0 ? defaultOAuthScopes : scopes) : [],
|
|
215
216
|
userScopes: byoOAuth ? userScopes : pkceUserScopes,
|
|
216
|
-
redirectUri
|
|
217
|
+
redirectUri,
|
|
217
218
|
authUrlOut: flagString(parsed, "auth-url-out"),
|
|
218
219
|
timeoutMs: numberFlag(parsed, "timeout-ms"),
|
|
219
220
|
openBrowser: !flagBoolean(parsed, "no-open")
|
|
@@ -241,6 +242,7 @@ const defaultOAuthScopes = [
|
|
|
241
242
|
];
|
|
242
243
|
const defaultPkceUserScopes = defaultOAuthScopes;
|
|
243
244
|
const defaultPkceClientId = "11499810382723.11506074725874";
|
|
245
|
+
const defaultPkceRedirectUri = "https://aslk.vercel.app/oauth/slack/callback";
|
|
244
246
|
const apiCall = async (parsed, services) => {
|
|
245
247
|
const method = requirePositional(parsed.positionals, 2, "METHOD");
|
|
246
248
|
const payload = await parseJsonPayload({
|