@anura-gate/watcher-jira 0.2.0 → 0.2.2
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 +197 -0
- package/lib/oauth.js +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# GATE Watcher — Jira
|
|
2
|
+
|
|
3
|
+
Self-hosted daemon that monitors your Jira Cloud projects for issue updates, comments, and transitions, pushing them to GATE for security processing. **Your Jira credentials never leave your machine.**
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Your Machine (Watcher) GATE Cloud
|
|
9
|
+
┌─────────────────────┐ ┌──────────────────┐
|
|
10
|
+
│ Jira API polling │───────>│ Security pipeline │
|
|
11
|
+
│ (creds stay HERE) │<───────│ (redact, policy, │
|
|
12
|
+
│ │ poll │ audit, forward) │
|
|
13
|
+
└─────────────────────┘ └──────────────────┘
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick Start (CLI)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
cd gate-watcher-jira
|
|
20
|
+
npm install
|
|
21
|
+
|
|
22
|
+
# Create .env (or pass env vars directly)
|
|
23
|
+
cp .env.example .env
|
|
24
|
+
# Fill in GATE_KEY, GATE_INTEGRATION_ID, and Jira credentials
|
|
25
|
+
|
|
26
|
+
npm start
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
On first run with OAuth, the browser opens automatically — click **Allow**, and you're connected.
|
|
30
|
+
|
|
31
|
+
## Auth Modes
|
|
32
|
+
|
|
33
|
+
### OAuth 2.0 (recommended)
|
|
34
|
+
|
|
35
|
+
Browser-based authorization. No manual tokens or emails needed.
|
|
36
|
+
|
|
37
|
+
1. Register an app at [developer.atlassian.com/console/myapps](https://developer.atlassian.com/console/myapps)
|
|
38
|
+
2. Set callback URL to `http://localhost:*/callback`
|
|
39
|
+
3. Enable scopes: `read:jira-work`, `write:jira-work`, `read:jira-user`, `offline_access`
|
|
40
|
+
4. Set `JIRA_CLIENT_ID` and `JIRA_CLIENT_SECRET` in `.env`
|
|
41
|
+
5. Run the watcher — browser opens, click Allow, done
|
|
42
|
+
|
|
43
|
+
Tokens are stored in `~/.gate/jira-oauth.json` and auto-refresh. Use `--auth` to re-authorize or `--logout` to clear stored tokens.
|
|
44
|
+
|
|
45
|
+
### API Token (headless / CI)
|
|
46
|
+
|
|
47
|
+
For environments without a browser.
|
|
48
|
+
|
|
49
|
+
1. Create an API token at [id.atlassian.com/manage-profile/security/api-tokens](https://id.atlassian.com/manage-profile/security/api-tokens)
|
|
50
|
+
2. Set `JIRA_DOMAIN`, `JIRA_EMAIL`, and `JIRA_TOKEN` in `.env`
|
|
51
|
+
|
|
52
|
+
## Embed in Your App (SDK)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install @anura-gate/watcher-jira
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### With OAuth
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
const { GateJiraWatcher } = require("@anura-gate/watcher-jira");
|
|
62
|
+
|
|
63
|
+
const watcher = new GateJiraWatcher({
|
|
64
|
+
gateKey: "gk-xxx",
|
|
65
|
+
integrationId: "int_xxx",
|
|
66
|
+
oauth: {
|
|
67
|
+
accessToken: "eyJ...",
|
|
68
|
+
cloudId: "abcd-1234-...",
|
|
69
|
+
siteUrl: "https://yourcompany.atlassian.net",
|
|
70
|
+
},
|
|
71
|
+
projects: ["PROJ", "ENG"], // optional — omit to watch all projects
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await watcher.start();
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### With API Token
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
const watcher = new GateJiraWatcher({
|
|
81
|
+
gateKey: "gk-xxx",
|
|
82
|
+
integrationId: "int_xxx",
|
|
83
|
+
jiraDomain: "yourcompany.atlassian.net",
|
|
84
|
+
jiraEmail: "you@company.com",
|
|
85
|
+
jiraToken: "ATATT3x...",
|
|
86
|
+
projects: ["PROJ", "ENG"],
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await watcher.start();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Listening to Events
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
watcher.on("ready", (displayName) => {
|
|
96
|
+
console.log(`Jira connected: ${displayName}`);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
watcher.on("event", (event, result) => {
|
|
100
|
+
console.log(`Event: ${event.eventType}, Issue: ${event.content.metadata.issueKey}`);
|
|
101
|
+
console.log(`Security actions: ${result.securityActions}`);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
watcher.on("action_result", ({ action, success, error }) => {
|
|
105
|
+
console.log(`${action}: ${success ? "done" : error}`);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Later...
|
|
109
|
+
await watcher.stop();
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Detected Events
|
|
113
|
+
|
|
114
|
+
| Event Type | Trigger |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `issue_transitioned` | Status changed (e.g. To Do → In Progress) |
|
|
117
|
+
| `issue_assigned` | Assignee changed |
|
|
118
|
+
| `issue_resolved` | Resolution set (Done, Won't Fix, etc.) |
|
|
119
|
+
| `comment_added` | New comment on an issue |
|
|
120
|
+
| `priority_changed` | Priority changed |
|
|
121
|
+
| `labels_changed` | Labels added or removed |
|
|
122
|
+
| `sprint_changed` | Issue moved to a different sprint |
|
|
123
|
+
| `issue_updated` | Summary, description, or other field changed |
|
|
124
|
+
|
|
125
|
+
## Outbound Actions
|
|
126
|
+
|
|
127
|
+
GATE can queue actions for the watcher to execute locally:
|
|
128
|
+
|
|
129
|
+
| Action | Required Params | Optional Params |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| `create_issue` | `project`, `summary` | `description`, `issueType`, `assignee`, `priority`, `labels` |
|
|
132
|
+
| `add_comment` | `issueKey`, `body` | — |
|
|
133
|
+
| `transition_issue` | `issueKey`, `transition` | — |
|
|
134
|
+
| `assign_issue` | `issueKey` | `accountId` (null = unassign) |
|
|
135
|
+
| `update_issue` | `issueKey` | `summary`, `description`, `priority`, `labels` |
|
|
136
|
+
|
|
137
|
+
## SDK Events
|
|
138
|
+
|
|
139
|
+
| Event | Args | Description |
|
|
140
|
+
|---|---|---|
|
|
141
|
+
| `ready` | `(displayName)` | Connected to Jira |
|
|
142
|
+
| `event` | `(event, result)` | Jira event processed by GATE |
|
|
143
|
+
| `action` | `(action)` | Outbound action received from GATE queue |
|
|
144
|
+
| `action_result` | `({ actionId, action, success, result, error })` | Outbound action completed |
|
|
145
|
+
| `gate_error` | `({ path, status, error })` | GATE API call failed |
|
|
146
|
+
| `jira_error` | `({ path, error })` | Jira API call failed |
|
|
147
|
+
| `limit_reached` | `(type)` | Plan limit hit |
|
|
148
|
+
| `stopped` | — | Watcher fully shut down |
|
|
149
|
+
|
|
150
|
+
## SDK Options
|
|
151
|
+
|
|
152
|
+
| Option | Required | Default | Description |
|
|
153
|
+
|---|---|---|---|
|
|
154
|
+
| `gateKey` | Yes | — | Virtual key (`gk-xxx`) |
|
|
155
|
+
| `integrationId` | Yes | — | Integration ID (`int_xxx`) |
|
|
156
|
+
| `oauth` | * | — | OAuth credentials (`{ accessToken, cloudId, siteUrl }`) |
|
|
157
|
+
| `jiraDomain` | * | — | Jira Cloud domain (`yourcompany.atlassian.net`) |
|
|
158
|
+
| `jiraEmail` | * | — | Atlassian account email |
|
|
159
|
+
| `jiraToken` | * | — | Jira API token |
|
|
160
|
+
| `gateUrl` | No | `"https://anuragate.com"` | GATE cloud URL |
|
|
161
|
+
| `projects` | No | `[]` | Project keys to watch. Empty = all |
|
|
162
|
+
| `pollInterval` | No | `30000` | ms between Jira API polls |
|
|
163
|
+
| `heartbeatInterval` | No | `30000` | ms between heartbeats |
|
|
164
|
+
| `sessionId` | No | — | Session ID for multi-tenant use |
|
|
165
|
+
| `sessionLabel` | No | — | Human-readable session label |
|
|
166
|
+
| `sessionMetadata` | No | `{}` | Arbitrary metadata for the session |
|
|
167
|
+
|
|
168
|
+
\* Either `oauth` or all three of `jiraDomain` + `jiraEmail` + `jiraToken` are required.
|
|
169
|
+
|
|
170
|
+
## Environment Variables
|
|
171
|
+
|
|
172
|
+
| Variable | Required | Description |
|
|
173
|
+
|---|---|---|
|
|
174
|
+
| `GATE_KEY` | Yes | Your GATE virtual key |
|
|
175
|
+
| `GATE_INTEGRATION_ID` | Yes | Integration ID from the dashboard |
|
|
176
|
+
| `JIRA_CLIENT_ID` | OAuth | Atlassian OAuth app client ID |
|
|
177
|
+
| `JIRA_CLIENT_SECRET` | OAuth | Atlassian OAuth app client secret |
|
|
178
|
+
| `JIRA_DOMAIN` | API Token | Jira Cloud domain (`yourcompany.atlassian.net`) |
|
|
179
|
+
| `JIRA_EMAIL` | API Token | Atlassian account email |
|
|
180
|
+
| `JIRA_TOKEN` | API Token | Jira API token |
|
|
181
|
+
| `JIRA_PROJECTS` | No | Comma-separated project keys: `PROJ,ENG` |
|
|
182
|
+
| `GATE_URL` | No | Custom GATE cloud URL |
|
|
183
|
+
| `WEB_PORT` | No | Port for the dev dashboard (CLI only) |
|
|
184
|
+
|
|
185
|
+
## CLI Flags
|
|
186
|
+
|
|
187
|
+
| Flag | Description |
|
|
188
|
+
|---|---|
|
|
189
|
+
| `--auth` | Force re-authorization (discard stored OAuth tokens) |
|
|
190
|
+
| `--logout` | Clear stored OAuth tokens and exit |
|
|
191
|
+
|
|
192
|
+
## Security Model
|
|
193
|
+
|
|
194
|
+
- Jira credentials stored locally on YOUR machine (`.env` or `~/.gate/jira-oauth.json`)
|
|
195
|
+
- GATE cloud never sees or stores your Jira credentials
|
|
196
|
+
- All event content passes through GATE's security pipeline (redaction, policies, audit)
|
|
197
|
+
- Billing, limits, and security enforced server-side
|
package/lib/oauth.js
CHANGED
|
@@ -53,7 +53,7 @@ const TOKEN_FILE = path.join(TOKEN_DIR, "jira-oauth.json");
|
|
|
53
53
|
* @param {number} [opts.port] — Local callback port (0 = random)
|
|
54
54
|
* @returns {Promise<OAuthResult>}
|
|
55
55
|
*/
|
|
56
|
-
async function authorize({ clientId, clientSecret, port =
|
|
56
|
+
async function authorize({ clientId, clientSecret, port = 9876 }) {
|
|
57
57
|
const state = crypto.randomBytes(16).toString("hex");
|
|
58
58
|
|
|
59
59
|
// Start local server to receive the callback
|