@gitscrum-studio/mcp-server 1.0.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/LICENSE +21 -0
- package/README.md +250 -0
- package/dist/auth/DeviceAuthenticator.d.ts +51 -0
- package/dist/auth/DeviceAuthenticator.d.ts.map +1 -0
- package/dist/auth/DeviceAuthenticator.js +89 -0
- package/dist/auth/DeviceAuthenticator.js.map +1 -0
- package/dist/auth/RateLimiter.d.ts +58 -0
- package/dist/auth/RateLimiter.d.ts.map +1 -0
- package/dist/auth/RateLimiter.js +181 -0
- package/dist/auth/RateLimiter.js.map +1 -0
- package/dist/auth/TokenManager.d.ts +62 -0
- package/dist/auth/TokenManager.d.ts.map +1 -0
- package/dist/auth/TokenManager.js +164 -0
- package/dist/auth/TokenManager.js.map +1 -0
- package/dist/client/GitScrumClient.d.ts +1002 -0
- package/dist/client/GitScrumClient.d.ts.map +1 -0
- package/dist/client/GitScrumClient.js +1835 -0
- package/dist/client/GitScrumClient.js.map +1 -0
- package/dist/context/ActiveContext.d.ts +79 -0
- package/dist/context/ActiveContext.d.ts.map +1 -0
- package/dist/context/ActiveContext.js +151 -0
- package/dist/context/ActiveContext.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +152 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/activity.d.ts +12 -0
- package/dist/tools/activity.d.ts.map +1 -0
- package/dist/tools/activity.js +106 -0
- package/dist/tools/activity.js.map +1 -0
- package/dist/tools/analytics.d.ts +11 -0
- package/dist/tools/analytics.d.ts.map +1 -0
- package/dist/tools/analytics.js +117 -0
- package/dist/tools/analytics.js.map +1 -0
- package/dist/tools/auth.d.ts +17 -0
- package/dist/tools/auth.d.ts.map +1 -0
- package/dist/tools/auth.js +173 -0
- package/dist/tools/auth.js.map +1 -0
- package/dist/tools/budget.d.ts +12 -0
- package/dist/tools/budget.d.ts.map +1 -0
- package/dist/tools/budget.js +109 -0
- package/dist/tools/budget.js.map +1 -0
- package/dist/tools/clientflow.d.ts +12 -0
- package/dist/tools/clientflow.d.ts.map +1 -0
- package/dist/tools/clientflow.js +476 -0
- package/dist/tools/clientflow.js.map +1 -0
- package/dist/tools/comments.d.ts +11 -0
- package/dist/tools/comments.d.ts.map +1 -0
- package/dist/tools/comments.js +99 -0
- package/dist/tools/comments.js.map +1 -0
- package/dist/tools/discussions.d.ts +12 -0
- package/dist/tools/discussions.d.ts.map +1 -0
- package/dist/tools/discussions.js +200 -0
- package/dist/tools/discussions.js.map +1 -0
- package/dist/tools/epics.d.ts +11 -0
- package/dist/tools/epics.d.ts.map +1 -0
- package/dist/tools/epics.js +122 -0
- package/dist/tools/epics.js.map +1 -0
- package/dist/tools/labels.d.ts +11 -0
- package/dist/tools/labels.d.ts.map +1 -0
- package/dist/tools/labels.js +138 -0
- package/dist/tools/labels.js.map +1 -0
- package/dist/tools/notes.d.ts +11 -0
- package/dist/tools/notes.d.ts.map +1 -0
- package/dist/tools/notes.js +201 -0
- package/dist/tools/notes.js.map +1 -0
- package/dist/tools/projects.d.ts +12 -0
- package/dist/tools/projects.d.ts.map +1 -0
- package/dist/tools/projects.js +272 -0
- package/dist/tools/projects.js.map +1 -0
- package/dist/tools/search.d.ts +17 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +56 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/shared/actionHandler.d.ts +67 -0
- package/dist/tools/shared/actionHandler.d.ts.map +1 -0
- package/dist/tools/shared/actionHandler.js +98 -0
- package/dist/tools/shared/actionHandler.js.map +1 -0
- package/dist/tools/shared/initModules.d.ts +6 -0
- package/dist/tools/shared/initModules.d.ts.map +1 -0
- package/dist/tools/shared/initModules.js +166 -0
- package/dist/tools/shared/initModules.js.map +1 -0
- package/dist/tools/shared/toolRegistry.d.ts +45 -0
- package/dist/tools/shared/toolRegistry.d.ts.map +1 -0
- package/dist/tools/shared/toolRegistry.js +59 -0
- package/dist/tools/shared/toolRegistry.js.map +1 -0
- package/dist/tools/sprints.d.ts +11 -0
- package/dist/tools/sprints.d.ts.map +1 -0
- package/dist/tools/sprints.js +271 -0
- package/dist/tools/sprints.js.map +1 -0
- package/dist/tools/standup.d.ts +11 -0
- package/dist/tools/standup.d.ts.map +1 -0
- package/dist/tools/standup.js +89 -0
- package/dist/tools/standup.js.map +1 -0
- package/dist/tools/taskTypes.d.ts +11 -0
- package/dist/tools/taskTypes.d.ts.map +1 -0
- package/dist/tools/taskTypes.js +132 -0
- package/dist/tools/taskTypes.js.map +1 -0
- package/dist/tools/tasks.d.ts +12 -0
- package/dist/tools/tasks.d.ts.map +1 -0
- package/dist/tools/tasks.js +470 -0
- package/dist/tools/tasks.js.map +1 -0
- package/dist/tools/timeTracking.d.ts +11 -0
- package/dist/tools/timeTracking.d.ts.map +1 -0
- package/dist/tools/timeTracking.js +174 -0
- package/dist/tools/timeTracking.js.map +1 -0
- package/dist/tools/userStories.d.ts +11 -0
- package/dist/tools/userStories.d.ts.map +1 -0
- package/dist/tools/userStories.js +166 -0
- package/dist/tools/userStories.js.map +1 -0
- package/dist/tools/wiki.d.ts +11 -0
- package/dist/tools/wiki.d.ts.map +1 -0
- package/dist/tools/wiki.js +154 -0
- package/dist/tools/wiki.js.map +1 -0
- package/dist/tools/workflows.d.ts +11 -0
- package/dist/tools/workflows.d.ts.map +1 -0
- package/dist/tools/workflows.js +144 -0
- package/dist/tools/workflows.js.map +1 -0
- package/package.json +89 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 GitScrum
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://site-assets.gitscrum.com/vscode/gitscrum-white.png" alt="GitScrum" width="160"/>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">GitScrum Studio MCP Server</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
Model Context Protocol server for GitScrum.<br/>
|
|
9
|
+
Ship faster with AI-powered project management.
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-000?style=flat-square" alt="MIT License"></a>
|
|
14
|
+
<a href="https://github.com/gitscrum-core/mcp-server/actions"><img src="https://img.shields.io/badge/tests-378_passing-000?style=flat-square" alt="Tests"></a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
<br/>
|
|
18
|
+
|
|
19
|
+
## Overview
|
|
20
|
+
|
|
21
|
+
GitScrum Studio MCP Server connects AI assistants to your [GitScrum](https://gitscrum.com) workspace via the [Model Context Protocol](https://modelcontextprotocol.io). It gives Claude, GitHub Copilot, Cursor, and any MCP-compatible client full operational access to your project management stack — tasks, sprints, time tracking, user stories, epics, kanban workflows, team discussions, wiki, notes, client CRM, invoicing, proposals, budget tracking, analytics dashboards, standup reports, and activity feeds.
|
|
22
|
+
|
|
23
|
+
Everything your team does in the GitScrum web app, your AI assistant can now do through conversation.
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
You: "What's on my plate today?"
|
|
27
|
+
Assistant: Fetches your tasks due today across all projects.
|
|
28
|
+
|
|
29
|
+
You: "Create a sprint for next week with the top 5 backlog items"
|
|
30
|
+
Assistant: Creates the sprint, assigns tasks, and sets the timeline.
|
|
31
|
+
|
|
32
|
+
You: "Show me which projects are over budget"
|
|
33
|
+
Assistant: Returns burn-down data and flags at-risk projects.
|
|
34
|
+
|
|
35
|
+
You: "Send the Q1 proposal to Acme Corp"
|
|
36
|
+
Assistant: Creates the proposal, attaches the client, and sends it.
|
|
37
|
+
|
|
38
|
+
You: "What did the team ship this week?"
|
|
39
|
+
Assistant: Generates a standup digest with completed work and blockers.
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**29 tools. 160+ operations. Zero context switching.**
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Quick start
|
|
47
|
+
|
|
48
|
+
### Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx -y @gitscrum-studio/mcp-server
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Configure your client
|
|
55
|
+
|
|
56
|
+
<details>
|
|
57
|
+
<summary><strong>Claude Desktop</strong></summary>
|
|
58
|
+
|
|
59
|
+
Edit the configuration file:
|
|
60
|
+
|
|
61
|
+
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
62
|
+
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"gitscrum": {
|
|
68
|
+
"command": "npx",
|
|
69
|
+
"args": ["-y", "@gitscrum-studio/mcp-server"]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
</details>
|
|
75
|
+
|
|
76
|
+
<details>
|
|
77
|
+
<summary><strong>VS Code / Cursor</strong></summary>
|
|
78
|
+
|
|
79
|
+
Add to `.vscode/mcp.json` or your MCP settings:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"servers": {
|
|
84
|
+
"gitscrum": {
|
|
85
|
+
"command": "npx",
|
|
86
|
+
"args": ["-y", "@gitscrum-studio/mcp-server"]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
</details>
|
|
92
|
+
|
|
93
|
+
### Authenticate
|
|
94
|
+
|
|
95
|
+
Tell your AI assistant: **"Login to GitScrum"**
|
|
96
|
+
|
|
97
|
+
The server initiates an [OAuth 2.0 Device Authorization Grant](https://datatracker.ietf.org/doc/html/rfc8628) flow. You authorize in the browser — credentials are never shared with the MCP server.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Tools
|
|
102
|
+
|
|
103
|
+
Each tool uses a consolidated `action` parameter, reducing LLM context tokens by ~80% compared to individual tool definitions.
|
|
104
|
+
|
|
105
|
+
### Core
|
|
106
|
+
|
|
107
|
+
| Tool | Actions | Docs |
|
|
108
|
+
|:-----|:--------|:-----|
|
|
109
|
+
| `task` | `my` `today` `get` `create` `update` `complete` `subtasks` `filter` `by_code` `duplicate` `move` `notifications` | [tasks](docs/tools/tasks.md) |
|
|
110
|
+
| `sprint` | `list` `all` `get` `kpis` `create` `update` `stats` `reports` `progress` `metrics` | [sprints](docs/tools/sprints.md) |
|
|
111
|
+
| `workspace` | `list` `get` | [projects](docs/tools/projects.md) |
|
|
112
|
+
| `project` | `list` `get` `stats` `tasks` `workflows` `types` `efforts` `labels` `members` | [projects](docs/tools/projects.md) |
|
|
113
|
+
| `time` | `active` `start` `stop` `logs` `analytics` `team` `reports` `productivity` `timeline` | [time-tracking](docs/tools/time-tracking.md) |
|
|
114
|
+
|
|
115
|
+
### Planning
|
|
116
|
+
|
|
117
|
+
| Tool | Actions | Docs |
|
|
118
|
+
|:-----|:--------|:-----|
|
|
119
|
+
| `user_story` | `list` `get` `create` `update` `all` | [user-stories](docs/tools/user-stories.md) |
|
|
120
|
+
| `epic` | `list` `create` `update` | [epics](docs/tools/epics.md) |
|
|
121
|
+
| `label` | `list` `create` `update` `attach` `detach` `toggle` | [labels](docs/tools/labels.md) |
|
|
122
|
+
| `task_type` | `list` `create` `update` `assign` | [task-types](docs/tools/task-types.md) |
|
|
123
|
+
| `workflow` | `create` `update` | [workflows](docs/tools/workflows.md) |
|
|
124
|
+
|
|
125
|
+
### Collaboration
|
|
126
|
+
|
|
127
|
+
| Tool | Actions | Docs |
|
|
128
|
+
|:-----|:--------|:-----|
|
|
129
|
+
| `discussion` | `all` `channels` `channel` `messages` `send` `search` `unread` `mark_read` `create_channel` `update_channel` | [discussions](docs/tools/discussions.md) |
|
|
130
|
+
| `comment` | `list` `add` `update` | [comments](docs/tools/comments.md) |
|
|
131
|
+
| `wiki` | `list` `get` `create` `update` `search` | [wiki](docs/tools/wiki.md) |
|
|
132
|
+
| `note` | `list` `get` `create` `update` `share` `revisions` | [notevault](docs/tools/notevault.md) |
|
|
133
|
+
| `note_folder` | `list` `create` `update` `move` | [notevault](docs/tools/notevault.md) |
|
|
134
|
+
| `search` | — | [search](docs/tools/search.md) |
|
|
135
|
+
|
|
136
|
+
### ClientFlow CRM
|
|
137
|
+
|
|
138
|
+
| Tool | Actions | Docs |
|
|
139
|
+
|:-----|:--------|:-----|
|
|
140
|
+
| `client` | `list` `get` `create` `update` `contacts` `interactions` `add_interaction` | [clientflow](docs/tools/clientflow.md) |
|
|
141
|
+
| `invoice` | `list` `get` `stats` `create` `update` `issue` `send` `mark_paid` | [clientflow](docs/tools/clientflow.md) |
|
|
142
|
+
| `proposal` | `list` `get` `stats` `create` `update` `send` `approve` `reject` `convert` | [clientflow](docs/tools/clientflow.md) |
|
|
143
|
+
| `clientflow_dashboard` | 8 reports | [clientflow](docs/tools/clientflow.md) |
|
|
144
|
+
| `clientflow_cross_workspace` | 4 reports | [clientflow](docs/tools/clientflow.md) |
|
|
145
|
+
|
|
146
|
+
### Insights <sup>PRO</sup>
|
|
147
|
+
|
|
148
|
+
| Tool | Actions | Docs |
|
|
149
|
+
|:-----|:--------|:-----|
|
|
150
|
+
| `standup` | `summary` `completed` `blockers` `team` `stuck` `digest` `contributors` | [standup](docs/tools/standup.md) |
|
|
151
|
+
| `analytics` | 10 reports | [analytics](docs/tools/analytics.md) |
|
|
152
|
+
| `activity` | `feed` `user_feed` `notifications` `activities` `task_workflow` | [activity](docs/tools/activity.md) |
|
|
153
|
+
| `budget` | `projects_at_risk` `overview` `consumption` `burn_down` `alerts` `events` | [budget](docs/tools/budget.md) |
|
|
154
|
+
|
|
155
|
+
### Authentication
|
|
156
|
+
|
|
157
|
+
| Tool | Description | Docs |
|
|
158
|
+
|:-----|:------------|:-----|
|
|
159
|
+
| `auth_login` | Initiate device code flow | [auth](docs/tools/auth.md) |
|
|
160
|
+
| `auth_complete` | Complete authorization | [auth](docs/tools/auth.md) |
|
|
161
|
+
| `auth_status` | Check session status | [auth](docs/tools/auth.md) |
|
|
162
|
+
| `auth_logout` | Clear stored credentials | [auth](docs/tools/auth.md) |
|
|
163
|
+
|
|
164
|
+
Full reference: [docs/TOOLS.md](docs/TOOLS.md)
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Security
|
|
169
|
+
|
|
170
|
+
The server is designed around the **principle of least privilege**.
|
|
171
|
+
|
|
172
|
+
| Layer | Protection |
|
|
173
|
+
|:------|:-----------|
|
|
174
|
+
| **Operations** | Only CREATE, READ, UPDATE. DELETE is blocked at MCP and API layers. |
|
|
175
|
+
| **Authentication** | OAuth 2.0 Device Grant — credentials never touch the server. |
|
|
176
|
+
| **Token storage** | Local filesystem with restricted permissions. |
|
|
177
|
+
| **Rate limiting** | Automatic lockout after failed auth attempts. |
|
|
178
|
+
|
|
179
|
+
Destructive operations must be performed in the [GitScrum Studio](https://studio.gitscrum.com).
|
|
180
|
+
|
|
181
|
+
Full details: [docs/SECURITY.md](docs/SECURITY.md)
|
|
182
|
+
|
|
183
|
+
Found a vulnerability? Report privately to **security@gitscrum.com**.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Documentation
|
|
188
|
+
|
|
189
|
+
| | |
|
|
190
|
+
|:--|:--|
|
|
191
|
+
| **[Usage Guide](docs/USAGE.md)** | Practical examples and common workflows |
|
|
192
|
+
| **[Tools Reference](docs/TOOLS.md)** | All 29 tools with parameters and response shapes |
|
|
193
|
+
| **[Per-Tool Guides](docs/tools/)** | Deep-dive into each tool module |
|
|
194
|
+
| **[Security](docs/SECURITY.md)** | Security model, token handling, threat mitigations |
|
|
195
|
+
| **[Development](docs/DEVELOPMENT.md)** | Local setup, architecture, testing, contribution |
|
|
196
|
+
| **[Changelog](CHANGELOG.md)** | Version history and migration notes |
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Development
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
git clone https://github.com/gitscrum-core/mcp-server.git
|
|
204
|
+
cd mcp-server
|
|
205
|
+
npm install
|
|
206
|
+
npm run build
|
|
207
|
+
npm test # 378 tests across 22 suites
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Inspect locally with the MCP Inspector:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
npx @modelcontextprotocol/inspector node dist/index.js
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
| Requirement | Version |
|
|
217
|
+
|:------------|:--------|
|
|
218
|
+
| Node.js | >= 18.0.0 |
|
|
219
|
+
| npm | >= 8.0.0 |
|
|
220
|
+
|
|
221
|
+
Full guide: [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Contributing
|
|
226
|
+
|
|
227
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
git checkout -b feature/my-feature
|
|
231
|
+
# make changes, add tests
|
|
232
|
+
npm test
|
|
233
|
+
git commit -m "feat: describe your change"
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
MIT — see [LICENSE](LICENSE).
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
<p align="center">
|
|
245
|
+
<a href="https://gitscrum.com">Website</a> · <a href="https://docs.gitscrum.com/en/mcp">Docs</a> · <a href="https://github.com/gitscrum-core/mcp-server/issues">Issues</a> · <a href="CHANGELOG.md">Changelog</a>
|
|
246
|
+
</p>
|
|
247
|
+
|
|
248
|
+
<p align="center">
|
|
249
|
+
Built by <a href="https://gitscrum.com/en">GitScrum</a>
|
|
250
|
+
</p>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Authorization Flow
|
|
3
|
+
*
|
|
4
|
+
* Implements OAuth 2.0 Device Authorization Grant (RFC 8628)
|
|
5
|
+
* Allows MCP to authenticate users via browser login.
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. MCP requests device code from API
|
|
9
|
+
* 2. User opens verification URL in browser (contains device_code)
|
|
10
|
+
* 3. User logs in (Google, GitHub, or email/password)
|
|
11
|
+
* 4. User clicks "Authorize" - no manual code entry needed
|
|
12
|
+
* 5. MCP polls for token until authorized
|
|
13
|
+
*
|
|
14
|
+
* @module @gitscrum-studio/mcp-server/auth
|
|
15
|
+
*/
|
|
16
|
+
export interface DeviceCodeResponse {
|
|
17
|
+
device_code: string;
|
|
18
|
+
user_code: string;
|
|
19
|
+
verification_uri: string;
|
|
20
|
+
verification_uri_complete: string;
|
|
21
|
+
expires_in: number;
|
|
22
|
+
interval: number;
|
|
23
|
+
}
|
|
24
|
+
export interface TokenResponse {
|
|
25
|
+
access_token: string;
|
|
26
|
+
token_type: string;
|
|
27
|
+
expires_in: number;
|
|
28
|
+
scope: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Handles Device Authorization Flow for CLI/MCP authentication
|
|
32
|
+
*/
|
|
33
|
+
export declare class DeviceAuthenticator {
|
|
34
|
+
private apiUrl;
|
|
35
|
+
constructor(apiUrl?: string);
|
|
36
|
+
/**
|
|
37
|
+
* MCP Client ID - must match OAuthClientRegistry on the API
|
|
38
|
+
*/
|
|
39
|
+
private static readonly MCP_CLIENT_ID;
|
|
40
|
+
/**
|
|
41
|
+
* Start the device authorization flow
|
|
42
|
+
* Returns the device code info that should be shown to the user
|
|
43
|
+
*/
|
|
44
|
+
requestDeviceCode(): Promise<DeviceCodeResponse>;
|
|
45
|
+
/**
|
|
46
|
+
* Poll for the access token
|
|
47
|
+
* Should be called repeatedly until success or error (other than pending)
|
|
48
|
+
*/
|
|
49
|
+
pollForToken(deviceCode: string): Promise<TokenResponse | null>;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=DeviceAuthenticator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeviceAuthenticator.d.ts","sourceRoot":"","sources":["../../src/auth/DeviceAuthenticator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,yBAAyB,EAAE,MAAM,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAOD;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,CAAC,EAAE,MAAM;IAI3B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAA0C;IAE/E;;;OAGG;IACG,iBAAiB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IA4BtD;;;OAGG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;CAgCtE"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Authorization Flow
|
|
3
|
+
*
|
|
4
|
+
* Implements OAuth 2.0 Device Authorization Grant (RFC 8628)
|
|
5
|
+
* Allows MCP to authenticate users via browser login.
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. MCP requests device code from API
|
|
9
|
+
* 2. User opens verification URL in browser (contains device_code)
|
|
10
|
+
* 3. User logs in (Google, GitHub, or email/password)
|
|
11
|
+
* 4. User clicks "Authorize" - no manual code entry needed
|
|
12
|
+
* 5. MCP polls for token until authorized
|
|
13
|
+
*
|
|
14
|
+
* @module @gitscrum-studio/mcp-server/auth
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Handles Device Authorization Flow for CLI/MCP authentication
|
|
18
|
+
*/
|
|
19
|
+
export class DeviceAuthenticator {
|
|
20
|
+
apiUrl;
|
|
21
|
+
constructor(apiUrl) {
|
|
22
|
+
this.apiUrl = apiUrl || process.env.GITSCRUM_API_URL || "https://services.gitscrum.com";
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* MCP Client ID - must match OAuthClientRegistry on the API
|
|
26
|
+
*/
|
|
27
|
+
static MCP_CLIENT_ID = "9e8d7c6b-5a4f-3e2d-1c0b-a9b8c7d6e5f4";
|
|
28
|
+
/**
|
|
29
|
+
* Start the device authorization flow
|
|
30
|
+
* Returns the device code info that should be shown to the user
|
|
31
|
+
*/
|
|
32
|
+
async requestDeviceCode() {
|
|
33
|
+
const url = `${this.apiUrl}/oauth/device/code`;
|
|
34
|
+
try {
|
|
35
|
+
const response = await fetch(url, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: {
|
|
38
|
+
"Content-Type": "application/json",
|
|
39
|
+
"Accept": "application/json",
|
|
40
|
+
"X-Client-Source": "mcp-server",
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify({ client_id: DeviceAuthenticator.MCP_CLIENT_ID }),
|
|
43
|
+
});
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
const error = await response.json();
|
|
46
|
+
throw new Error(error.error_description || `API error: ${response.status} ${response.statusText}`);
|
|
47
|
+
}
|
|
48
|
+
return response.json();
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
52
|
+
throw new Error(`Network error: Unable to connect to ${url}. Is the API server running?`);
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Poll for the access token
|
|
59
|
+
* Should be called repeatedly until success or error (other than pending)
|
|
60
|
+
*/
|
|
61
|
+
async pollForToken(deviceCode) {
|
|
62
|
+
const response = await fetch(`${this.apiUrl}/oauth/device/token`, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: {
|
|
65
|
+
"Content-Type": "application/json",
|
|
66
|
+
"Accept": "application/json",
|
|
67
|
+
"X-Client-Source": "mcp-server",
|
|
68
|
+
},
|
|
69
|
+
body: JSON.stringify({
|
|
70
|
+
device_code: deviceCode,
|
|
71
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
72
|
+
}),
|
|
73
|
+
});
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
const error = await response.json();
|
|
76
|
+
// These are expected while waiting for user authorization
|
|
77
|
+
if (error.error === "authorization_pending") {
|
|
78
|
+
return null; // Keep polling
|
|
79
|
+
}
|
|
80
|
+
if (error.error === "slow_down") {
|
|
81
|
+
return null; // Keep polling, but slower
|
|
82
|
+
}
|
|
83
|
+
// These are terminal errors
|
|
84
|
+
throw new Error(error.error_description || error.error);
|
|
85
|
+
}
|
|
86
|
+
return response.json();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=DeviceAuthenticator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeviceAuthenticator.js","sourceRoot":"","sources":["../../src/auth/DeviceAuthenticator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAuBH;;GAEG;AACH,MAAM,OAAO,mBAAmB;IACtB,MAAM,CAAS;IAEvB,YAAY,MAAe;QACzB,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,+BAA+B,CAAC;IAC1F,CAAC;IAED;;OAEG;IACK,MAAM,CAAU,aAAa,GAAG,sCAAsC,CAAC;IAE/E;;;OAGG;IACH,KAAK,CAAC,iBAAiB;QACrB,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,oBAAoB,CAAC;QAE/C,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAChC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,QAAQ,EAAE,kBAAkB;oBAC5B,iBAAiB,EAAE,YAAY;iBAChC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,mBAAmB,CAAC,aAAa,EAAE,CAAC;aACvE,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAe,CAAC;gBACjD,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,iBAAiB,IAAI,cAAc,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;YACrG,CAAC;YAED,OAAO,QAAQ,CAAC,IAAI,EAAiC,CAAC;QACxD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBAClE,MAAM,IAAI,KAAK,CAAC,uCAAuC,GAAG,8BAA8B,CAAC,CAAC;YAC5F,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,YAAY,CAAC,UAAkB;QACnC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,qBAAqB,EAAE;YAChE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,QAAQ,EAAE,kBAAkB;gBAC5B,iBAAiB,EAAE,YAAY;aAChC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,WAAW,EAAE,UAAU;gBACvB,UAAU,EAAE,8CAA8C;aAC3D,CAAC;SACH,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAe,CAAC;YAEjD,0DAA0D;YAC1D,IAAI,KAAK,CAAC,KAAK,KAAK,uBAAuB,EAAE,CAAC;gBAC5C,OAAO,IAAI,CAAC,CAAC,eAAe;YAC9B,CAAC;YAED,IAAI,KAAK,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;gBAChC,OAAO,IAAI,CAAC,CAAC,2BAA2B;YAC1C,CAAC;YAED,4BAA4B;YAC5B,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,iBAAiB,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1D,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,EAA4B,CAAC;IACnD,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limiter for Authentication
|
|
3
|
+
*
|
|
4
|
+
* Protects against brute force attacks by limiting login attempts.
|
|
5
|
+
* Uses in-memory storage (resets on server restart).
|
|
6
|
+
*/
|
|
7
|
+
export declare class RateLimiter {
|
|
8
|
+
private static readonly MAX_ATTEMPTS;
|
|
9
|
+
private static readonly WINDOW_MS;
|
|
10
|
+
private static readonly LOCKOUT_MS;
|
|
11
|
+
private static readonly CLEANUP_INTERVAL_MS;
|
|
12
|
+
private storage;
|
|
13
|
+
private storageFile;
|
|
14
|
+
constructor();
|
|
15
|
+
/**
|
|
16
|
+
* Load rate limit data from disk
|
|
17
|
+
*/
|
|
18
|
+
private loadStorage;
|
|
19
|
+
/**
|
|
20
|
+
* Save rate limit data to disk
|
|
21
|
+
*/
|
|
22
|
+
private saveStorage;
|
|
23
|
+
/**
|
|
24
|
+
* Normalize identifier (lowercase email)
|
|
25
|
+
*/
|
|
26
|
+
private normalizeKey;
|
|
27
|
+
/**
|
|
28
|
+
* Clean up old records
|
|
29
|
+
*/
|
|
30
|
+
private cleanup;
|
|
31
|
+
/**
|
|
32
|
+
* Check if login is allowed for the given identifier
|
|
33
|
+
* Returns: { allowed: boolean, retryAfter?: number, message?: string }
|
|
34
|
+
*/
|
|
35
|
+
check(identifier: string): {
|
|
36
|
+
allowed: boolean;
|
|
37
|
+
retryAfter?: number;
|
|
38
|
+
remainingAttempts?: number;
|
|
39
|
+
message?: string;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Record a failed login attempt
|
|
43
|
+
*/
|
|
44
|
+
recordFailure(identifier: string): void;
|
|
45
|
+
/**
|
|
46
|
+
* Clear attempts on successful login
|
|
47
|
+
*/
|
|
48
|
+
recordSuccess(identifier: string): void;
|
|
49
|
+
/**
|
|
50
|
+
* Get rate limit info for display
|
|
51
|
+
*/
|
|
52
|
+
getInfo(): {
|
|
53
|
+
maxAttempts: number;
|
|
54
|
+
windowMinutes: number;
|
|
55
|
+
lockoutMinutes: number;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=RateLimiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RateLimiter.d.ts","sourceRoot":"","sources":["../../src/auth/RateLimiter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAiBH,qBAAa,WAAW;IAEtB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAK;IACzC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAkB;IACnD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAkB;IACpD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAkB;IAE7D,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,WAAW,CAAS;;IAS5B;;OAEG;IACH,OAAO,CAAC,WAAW;IAenB;;OAEG;IACH,OAAO,CAAC,WAAW;IAgBnB;;OAEG;IACH,OAAO,CAAC,YAAY;IAIpB;;OAEG;IACH,OAAO,CAAC,OAAO;IAwBf;;;OAGG;IACH,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG;QACzB,OAAO,EAAE,OAAO,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB;IA0DD;;OAEG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IA0BvC;;OAEG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAMvC;;OAEG;IACH,OAAO,IAAI;QACT,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;KACxB;CAOF"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limiter for Authentication
|
|
3
|
+
*
|
|
4
|
+
* Protects against brute force attacks by limiting login attempts.
|
|
5
|
+
* Uses in-memory storage (resets on server restart).
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
import * as os from "os";
|
|
10
|
+
export class RateLimiter {
|
|
11
|
+
// Configuration
|
|
12
|
+
static MAX_ATTEMPTS = 5;
|
|
13
|
+
static WINDOW_MS = 15 * 60 * 1000; // 15 minutes
|
|
14
|
+
static LOCKOUT_MS = 30 * 60 * 1000; // 30 minutes lockout
|
|
15
|
+
static CLEANUP_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
|
|
16
|
+
storage;
|
|
17
|
+
storageFile;
|
|
18
|
+
constructor() {
|
|
19
|
+
// Persist rate limit data to prevent bypass via restart
|
|
20
|
+
const configDir = path.join(os.homedir(), ".gitscrum");
|
|
21
|
+
this.storageFile = path.join(configDir, "rate-limit.json");
|
|
22
|
+
this.storage = this.loadStorage();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Load rate limit data from disk
|
|
26
|
+
*/
|
|
27
|
+
loadStorage() {
|
|
28
|
+
try {
|
|
29
|
+
if (fs.existsSync(this.storageFile)) {
|
|
30
|
+
const data = JSON.parse(fs.readFileSync(this.storageFile, "utf-8"));
|
|
31
|
+
return {
|
|
32
|
+
attempts: data.attempts || {},
|
|
33
|
+
lastCleanup: data.lastCleanup || Date.now(),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Ignore errors, start fresh
|
|
39
|
+
}
|
|
40
|
+
return { attempts: {}, lastCleanup: Date.now() };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Save rate limit data to disk
|
|
44
|
+
*/
|
|
45
|
+
saveStorage() {
|
|
46
|
+
try {
|
|
47
|
+
const configDir = path.dirname(this.storageFile);
|
|
48
|
+
if (!fs.existsSync(configDir)) {
|
|
49
|
+
fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
|
50
|
+
}
|
|
51
|
+
fs.writeFileSync(this.storageFile, JSON.stringify(this.storage, null, 2), { mode: 0o600 });
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Ignore save errors - rate limiting still works in memory
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Normalize identifier (lowercase email)
|
|
59
|
+
*/
|
|
60
|
+
normalizeKey(identifier) {
|
|
61
|
+
return identifier.toLowerCase().trim();
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Clean up old records
|
|
65
|
+
*/
|
|
66
|
+
cleanup() {
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
// Only cleanup once per hour
|
|
69
|
+
if (now - this.storage.lastCleanup < RateLimiter.CLEANUP_INTERVAL_MS) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const windowStart = now - RateLimiter.WINDOW_MS;
|
|
73
|
+
for (const key of Object.keys(this.storage.attempts)) {
|
|
74
|
+
const record = this.storage.attempts[key];
|
|
75
|
+
// Remove if outside window AND not locked
|
|
76
|
+
if (record.firstAttempt < windowStart &&
|
|
77
|
+
(!record.lockedUntil || record.lockedUntil < now)) {
|
|
78
|
+
delete this.storage.attempts[key];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
this.storage.lastCleanup = now;
|
|
82
|
+
this.saveStorage();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Check if login is allowed for the given identifier
|
|
86
|
+
* Returns: { allowed: boolean, retryAfter?: number, message?: string }
|
|
87
|
+
*/
|
|
88
|
+
check(identifier) {
|
|
89
|
+
this.cleanup();
|
|
90
|
+
const key = this.normalizeKey(identifier);
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
const record = this.storage.attempts[key];
|
|
93
|
+
// No previous attempts
|
|
94
|
+
if (!record) {
|
|
95
|
+
return {
|
|
96
|
+
allowed: true,
|
|
97
|
+
remainingAttempts: RateLimiter.MAX_ATTEMPTS
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
// Check if currently locked out
|
|
101
|
+
if (record.lockedUntil && record.lockedUntil > now) {
|
|
102
|
+
const retryAfter = Math.ceil((record.lockedUntil - now) / 1000);
|
|
103
|
+
const minutes = Math.ceil(retryAfter / 60);
|
|
104
|
+
return {
|
|
105
|
+
allowed: false,
|
|
106
|
+
retryAfter,
|
|
107
|
+
message: `Too many failed login attempts. Please try again in ${minutes} minute${minutes > 1 ? 's' : ''}.`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// Check if window has expired (reset counter)
|
|
111
|
+
if (now - record.firstAttempt > RateLimiter.WINDOW_MS) {
|
|
112
|
+
delete this.storage.attempts[key];
|
|
113
|
+
this.saveStorage();
|
|
114
|
+
return {
|
|
115
|
+
allowed: true,
|
|
116
|
+
remainingAttempts: RateLimiter.MAX_ATTEMPTS
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// Check remaining attempts
|
|
120
|
+
const remainingAttempts = RateLimiter.MAX_ATTEMPTS - record.count;
|
|
121
|
+
if (remainingAttempts <= 0) {
|
|
122
|
+
// Lock the account
|
|
123
|
+
record.lockedUntil = now + RateLimiter.LOCKOUT_MS;
|
|
124
|
+
this.saveStorage();
|
|
125
|
+
const minutes = Math.ceil(RateLimiter.LOCKOUT_MS / 1000 / 60);
|
|
126
|
+
return {
|
|
127
|
+
allowed: false,
|
|
128
|
+
retryAfter: Math.ceil(RateLimiter.LOCKOUT_MS / 1000),
|
|
129
|
+
message: `Too many failed login attempts. Account locked for ${minutes} minutes.`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
allowed: true,
|
|
134
|
+
remainingAttempts
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Record a failed login attempt
|
|
139
|
+
*/
|
|
140
|
+
recordFailure(identifier) {
|
|
141
|
+
const key = this.normalizeKey(identifier);
|
|
142
|
+
const now = Date.now();
|
|
143
|
+
let record = this.storage.attempts[key];
|
|
144
|
+
if (!record || now - record.firstAttempt > RateLimiter.WINDOW_MS) {
|
|
145
|
+
// Start new window
|
|
146
|
+
record = {
|
|
147
|
+
count: 1,
|
|
148
|
+
firstAttempt: now,
|
|
149
|
+
lockedUntil: null,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
record.count++;
|
|
154
|
+
// Check if should lock
|
|
155
|
+
if (record.count >= RateLimiter.MAX_ATTEMPTS) {
|
|
156
|
+
record.lockedUntil = now + RateLimiter.LOCKOUT_MS;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
this.storage.attempts[key] = record;
|
|
160
|
+
this.saveStorage();
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Clear attempts on successful login
|
|
164
|
+
*/
|
|
165
|
+
recordSuccess(identifier) {
|
|
166
|
+
const key = this.normalizeKey(identifier);
|
|
167
|
+
delete this.storage.attempts[key];
|
|
168
|
+
this.saveStorage();
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get rate limit info for display
|
|
172
|
+
*/
|
|
173
|
+
getInfo() {
|
|
174
|
+
return {
|
|
175
|
+
maxAttempts: RateLimiter.MAX_ATTEMPTS,
|
|
176
|
+
windowMinutes: Math.ceil(RateLimiter.WINDOW_MS / 1000 / 60),
|
|
177
|
+
lockoutMinutes: Math.ceil(RateLimiter.LOCKOUT_MS / 1000 / 60),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=RateLimiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RateLimiter.js","sourceRoot":"","sources":["../../src/auth/RateLimiter.ts"],"names":[],"mappings":"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"}
|