memory-journal-mcp 6.1.2 → 6.2.1
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 +44 -28
- package/dist/{chunk-X4SWFATC.js → chunk-BI4ZNSKA.js} +38 -24
- package/dist/{chunk-HCEWINSB.js → chunk-N6EBIDN7.js} +99 -102
- package/dist/cli.js +2 -2
- package/dist/index.js +2 -2
- package/dist/tools-WPRY5MJ6.js +2 -0
- package/package.json +10 -1
- package/skills/github-commander/SKILL.md +151 -0
- package/skills/github-commander/config/project-config.example.md +125 -0
- package/skills/github-commander/workflows/code-quality-audit.md +80 -0
- package/skills/github-commander/workflows/full-audit.md +134 -0
- package/skills/github-commander/workflows/issue-triage.md +239 -0
- package/skills/github-commander/workflows/milestone-sprint.md +81 -0
- package/skills/github-commander/workflows/perf-audit.md +142 -0
- package/skills/github-commander/workflows/pr-review.md +123 -0
- package/skills/github-commander/workflows/security-audit.md +170 -0
- package/skills/github-commander/workflows/update-deps.md +109 -0
- package/.dockerignore +0 -139
- package/.gitattributes +0 -20
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -95
- package/.github/ISSUE_TEMPLATE/config.yml +0 -11
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -110
- package/.github/ISSUE_TEMPLATE/question.md +0 -78
- package/.github/aw/actions-lock.json +0 -14
- package/.github/copilot-instructions.md +0 -122
- package/.github/dependabot.yml +0 -93
- package/.github/pull_request_template.md +0 -135
- package/.github/workflows/README.md +0 -133
- package/.github/workflows/agentics-maintenance.yml +0 -141
- package/.github/workflows/auto-release.yml +0 -68
- package/.github/workflows/ci-health-monitor.lock.yml +0 -1121
- package/.github/workflows/ci-health-monitor.md +0 -87
- package/.github/workflows/codeql.yml +0 -41
- package/.github/workflows/dependabot-auto-merge.yml +0 -42
- package/.github/workflows/dependency-maintenance.lock.yml +0 -1182
- package/.github/workflows/dependency-maintenance.md +0 -147
- package/.github/workflows/docker-publish.yml +0 -254
- package/.github/workflows/docs-drift-detector.lock.yml +0 -1142
- package/.github/workflows/docs-drift-detector.md +0 -115
- package/.github/workflows/lint-and-test.yml +0 -60
- package/.github/workflows/publish-npm.yml +0 -85
- package/.github/workflows/secrets-scanning.yml +0 -32
- package/.github/workflows/security-update.yml +0 -127
- package/.gitleaks.toml +0 -9
- package/.prettierignore +0 -21
- package/.prettierrc +0 -33
- package/.scout-ignore +0 -12
- package/.trivyignore +0 -21
- package/CHANGELOG.md +0 -1814
- package/CODE_OF_CONDUCT.md +0 -133
- package/CONTRIBUTING.md +0 -263
- package/DOCKER_README.md +0 -331
- package/Dockerfile +0 -128
- package/SECURITY.md +0 -227
- package/UNRELEASED.md +0 -1
- package/dist/tools-T4U5A3X4.js +0 -2
- package/docker-compose.yml +0 -71
- package/docs/README.md +0 -18
- package/docs/agentic-journal-synergy.md +0 -175
- package/docs/copilot-setup.md +0 -72
- package/eslint.config.js +0 -110
- package/mcp-config-example.json +0 -21
- package/playwright.config.ts +0 -35
- package/releases/v2.1.0.md +0 -220
- package/releases/v2.2.0.md +0 -168
- package/releases/v3.0.0.md +0 -237
- package/releases/v3.1.0.md +0 -104
- package/releases/v3.1.1.md +0 -42
- package/releases/v3.1.2.md +0 -40
- package/releases/v3.1.3.md +0 -64
- package/releases/v3.1.4.md +0 -32
- package/releases/v3.1.5.md +0 -44
- package/releases/v4.0.0.md +0 -71
- package/releases/v4.1.0.md +0 -88
- package/releases/v4.2.0.md +0 -90
- package/releases/v4.3.0.md +0 -92
- package/releases/v4.3.1.md +0 -69
- package/releases/v4.4.0.md +0 -120
- package/releases/v4.4.1.md +0 -33
- package/releases/v4.4.2.md +0 -31
- package/releases/v4.5.0.md +0 -116
- package/releases/v5.0.0.md +0 -105
- package/releases/v5.0.1.md +0 -25
- package/releases/v5.1.0.md +0 -83
- package/releases/v5.1.1.md +0 -10
- package/releases/v6.0.0.md +0 -48
- package/releases/v6.0.1.md +0 -36
- package/releases/v6.1.0.md +0 -68
- package/releases/v6.1.1.md +0 -30
- package/releases/v6.1.2.md +0 -23
- package/scripts/generate-server-instructions.ts +0 -306
- package/scripts/server-instructions-function-body.ts +0 -107
- package/scripts/server-instructions-gotchas.ts +0 -45
- package/server.json +0 -42
- package/social-preview.png +0 -0
- package/src/auth/auth-context.ts +0 -78
- package/src/auth/authorization-server-discovery.ts +0 -263
- package/src/auth/errors.ts +0 -215
- package/src/auth/index.ts +0 -58
- package/src/auth/middleware.ts +0 -392
- package/src/auth/oauth-resource-server.ts +0 -170
- package/src/auth/scope-map.ts +0 -46
- package/src/auth/scopes.ts +0 -256
- package/src/auth/token-validator.ts +0 -293
- package/src/auth/transport-agnostic.ts +0 -164
- package/src/auth/types.ts +0 -372
- package/src/cli.ts +0 -279
- package/src/codemode/api-constants.ts +0 -263
- package/src/codemode/api.ts +0 -302
- package/src/codemode/auto-return.ts +0 -65
- package/src/codemode/index.ts +0 -47
- package/src/codemode/sandbox-factory.ts +0 -144
- package/src/codemode/sandbox.ts +0 -220
- package/src/codemode/security.ts +0 -155
- package/src/codemode/types.ts +0 -228
- package/src/codemode/worker-sandbox.ts +0 -277
- package/src/codemode/worker-script.ts +0 -239
- package/src/constants/icons.ts +0 -183
- package/src/constants/server-instructions.md +0 -166
- package/src/constants/server-instructions.ts +0 -514
- package/src/database/adapter-factory.ts +0 -16
- package/src/database/core/entry-columns.ts +0 -10
- package/src/database/core/interfaces.ts +0 -188
- package/src/database/core/schema.ts +0 -152
- package/src/database/sqlite-adapter/backup.ts +0 -167
- package/src/database/sqlite-adapter/entries/crud.ts +0 -233
- package/src/database/sqlite-adapter/entries/importance.ts +0 -76
- package/src/database/sqlite-adapter/entries/index.ts +0 -142
- package/src/database/sqlite-adapter/entries/search.ts +0 -294
- package/src/database/sqlite-adapter/entries/shared.ts +0 -102
- package/src/database/sqlite-adapter/entries/statistics.ts +0 -162
- package/src/database/sqlite-adapter/index.ts +0 -265
- package/src/database/sqlite-adapter/native-connection.ts +0 -301
- package/src/database/sqlite-adapter/relationships.ts +0 -70
- package/src/database/sqlite-adapter/tags.ts +0 -182
- package/src/filtering/tool-filter.ts +0 -312
- package/src/github/github-integration/client.ts +0 -114
- package/src/github/github-integration/index.ts +0 -297
- package/src/github/github-integration/insights.ts +0 -155
- package/src/github/github-integration/issues.ts +0 -213
- package/src/github/github-integration/milestones.ts +0 -262
- package/src/github/github-integration/projects.ts +0 -414
- package/src/github/github-integration/pull-requests.ts +0 -235
- package/src/github/github-integration/repository.ts +0 -110
- package/src/github/github-integration/types.ts +0 -43
- package/src/handlers/prompts/github.ts +0 -210
- package/src/handlers/prompts/index.ts +0 -97
- package/src/handlers/prompts/workflow.ts +0 -361
- package/src/handlers/resources/core/briefing/context-section.ts +0 -182
- package/src/handlers/resources/core/briefing/github-section.ts +0 -354
- package/src/handlers/resources/core/briefing/index.ts +0 -106
- package/src/handlers/resources/core/briefing/user-message.ts +0 -114
- package/src/handlers/resources/core/health.ts +0 -75
- package/src/handlers/resources/core/index.ts +0 -31
- package/src/handlers/resources/core/instructions.ts +0 -45
- package/src/handlers/resources/core/utilities.ts +0 -310
- package/src/handlers/resources/github.ts +0 -340
- package/src/handlers/resources/graph.ts +0 -218
- package/src/handlers/resources/help.ts +0 -410
- package/src/handlers/resources/index.ts +0 -143
- package/src/handlers/resources/shared.ts +0 -219
- package/src/handlers/resources/team.ts +0 -134
- package/src/handlers/resources/templates.ts +0 -334
- package/src/handlers/tools/admin.ts +0 -351
- package/src/handlers/tools/analytics.ts +0 -346
- package/src/handlers/tools/backup.ts +0 -272
- package/src/handlers/tools/codemode.ts +0 -188
- package/src/handlers/tools/core.ts +0 -359
- package/src/handlers/tools/error-fields-mixin.ts +0 -10
- package/src/handlers/tools/export.ts +0 -150
- package/src/handlers/tools/github/copilot-tools.ts +0 -72
- package/src/handlers/tools/github/helpers.ts +0 -125
- package/src/handlers/tools/github/insights-tools.ts +0 -112
- package/src/handlers/tools/github/issue-tools.ts +0 -442
- package/src/handlers/tools/github/kanban-tools.ts +0 -153
- package/src/handlers/tools/github/milestone-tools.ts +0 -371
- package/src/handlers/tools/github/mutation-tools.ts +0 -17
- package/src/handlers/tools/github/read-tools.ts +0 -302
- package/src/handlers/tools/github/schemas.ts +0 -435
- package/src/handlers/tools/github.ts +0 -39
- package/src/handlers/tools/index.ts +0 -255
- package/src/handlers/tools/relationships.ts +0 -390
- package/src/handlers/tools/schemas.ts +0 -165
- package/src/handlers/tools/search.ts +0 -448
- package/src/handlers/tools/team/admin-tools.ts +0 -164
- package/src/handlers/tools/team/analytics-tools.ts +0 -233
- package/src/handlers/tools/team/backup-tools.ts +0 -83
- package/src/handlers/tools/team/core-tools.ts +0 -197
- package/src/handlers/tools/team/export-tools.ts +0 -130
- package/src/handlers/tools/team/helpers.ts +0 -66
- package/src/handlers/tools/team/index.ts +0 -45
- package/src/handlers/tools/team/relationship-tools.ts +0 -219
- package/src/handlers/tools/team/schemas.ts +0 -558
- package/src/handlers/tools/team/search-tools.ts +0 -145
- package/src/handlers/tools/team/vector-tools.ts +0 -261
- package/src/index.ts +0 -57
- package/src/server/mcp-server.ts +0 -446
- package/src/server/registration.ts +0 -141
- package/src/server/scheduler.ts +0 -283
- package/src/transports/http/handlers.ts +0 -78
- package/src/transports/http/index.ts +0 -8
- package/src/transports/http/security.ts +0 -147
- package/src/transports/http/server/index.ts +0 -397
- package/src/transports/http/server/legacy-sse.ts +0 -87
- package/src/transports/http/server/stateful.ts +0 -222
- package/src/transports/http/server/stateless.ts +0 -42
- package/src/transports/http/types.ts +0 -132
- package/src/types/entities.ts +0 -145
- package/src/types/error-types.ts +0 -92
- package/src/types/errors.ts +0 -200
- package/src/types/filtering.ts +0 -55
- package/src/types/github.ts +0 -216
- package/src/types/index.ts +0 -348
- package/src/utils/error-helpers.ts +0 -78
- package/src/utils/errors/error-response-fields.ts +0 -29
- package/src/utils/errors/suggestions.ts +0 -94
- package/src/utils/github-helpers.ts +0 -33
- package/src/utils/logger.ts +0 -107
- package/src/utils/mcp-logger.ts +0 -155
- package/src/utils/progress-utils.ts +0 -100
- package/src/utils/query-helpers.ts +0 -78
- package/src/utils/resource-annotations.ts +0 -75
- package/src/utils/security-utils.ts +0 -198
- package/src/utils/vector-index-helpers.ts +0 -24
- package/src/vector/vector-search-manager.ts +0 -409
- package/src/version.ts +0 -15
- package/test-server/README.md +0 -193
- package/test-server/code-map.md +0 -399
- package/test-server/test-agent-experience.md +0 -213
- package/test-server/test-filter-instructions.mjs +0 -295
- package/test-server/test-instruction-levels.mjs +0 -102
- package/test-server/test-preflight.md +0 -55
- package/test-server/test-prompts.mjs +0 -185
- package/test-server/test-scheduler.mjs +0 -174
- package/test-server/test-tool-annotations.mjs +0 -115
- package/test-server/test-tools-codemode.md +0 -632
- package/test-server/test-tools-codemode2.md +0 -1218
- package/test-server/test-tools-team.md +0 -215
- package/test-server/test-tools.md +0 -429
- package/test-server/test-tools2.md +0 -361
- package/test-server/test-tools3.md +0 -396
- package/test-server/tool-reference.md +0 -231
- package/tests/README.md +0 -54
- package/tests/auth/auth-context.test.ts +0 -162
- package/tests/auth/authorization-server-discovery.test.ts +0 -265
- package/tests/auth/errors.test.ts +0 -170
- package/tests/auth/middleware.test.ts +0 -585
- package/tests/auth/oauth-resource-server.test.ts +0 -173
- package/tests/auth/scope-map.test.ts +0 -66
- package/tests/auth/scopes.test.ts +0 -347
- package/tests/auth/token-validator.test.ts +0 -271
- package/tests/codemode/api.test.ts +0 -396
- package/tests/codemode/auto-return.test.ts +0 -167
- package/tests/codemode/codemode-tool-handlers.test.ts +0 -197
- package/tests/codemode/sandbox-factory.test.ts +0 -152
- package/tests/codemode/sandbox.test.ts +0 -190
- package/tests/codemode/security.test.ts +0 -242
- package/tests/codemode/worker-sandbox.test.ts +0 -106
- package/tests/constants/icons.test.ts +0 -101
- package/tests/constants/server-instructions.test.ts +0 -514
- package/tests/database/crud-workflow-branches.test.ts +0 -418
- package/tests/database/database-branches.test.ts +0 -132
- package/tests/database/entries-auth-branches.test.ts +0 -390
- package/tests/database/native-connection.test.ts +0 -249
- package/tests/database/shared-helpers.test.ts +0 -103
- package/tests/database/sqlite-adapter.bench.ts +0 -63
- package/tests/database/sqlite-adapter.test.ts +0 -690
- package/tests/database/tags.test.ts +0 -134
- package/tests/e2e/README.md +0 -39
- package/tests/e2e/auth.spec.ts +0 -106
- package/tests/e2e/codemode-abuse.spec.ts +0 -75
- package/tests/e2e/health.spec.ts +0 -63
- package/tests/e2e/helpers.ts +0 -139
- package/tests/e2e/oauth-discovery.spec.ts +0 -102
- package/tests/e2e/oauth-scopes.spec.ts +0 -222
- package/tests/e2e/payloads-admin.spec.ts +0 -76
- package/tests/e2e/payloads-analytics.spec.ts +0 -37
- package/tests/e2e/payloads-backup-restore.spec.ts +0 -102
- package/tests/e2e/payloads-backup.spec.ts +0 -44
- package/tests/e2e/payloads-codemode-api.spec.ts +0 -131
- package/tests/e2e/payloads-codemode-readonly.spec.ts +0 -116
- package/tests/e2e/payloads-codemode.spec.ts +0 -116
- package/tests/e2e/payloads-core.spec.ts +0 -82
- package/tests/e2e/payloads-error-contracts.spec.ts +0 -159
- package/tests/e2e/payloads-export.spec.ts +0 -46
- package/tests/e2e/payloads-github-degradation.spec.ts +0 -73
- package/tests/e2e/payloads-github.spec.ts +0 -176
- package/tests/e2e/payloads-relationships.spec.ts +0 -56
- package/tests/e2e/payloads-search.spec.ts +0 -64
- package/tests/e2e/payloads-team-happy.spec.ts +0 -231
- package/tests/e2e/payloads-team.spec.ts +0 -174
- package/tests/e2e/prompts-expanded.spec.ts +0 -137
- package/tests/e2e/prompts.spec.ts +0 -62
- package/tests/e2e/protocols.spec.ts +0 -134
- package/tests/e2e/rate-limiting.spec.ts +0 -291
- package/tests/e2e/resources-briefing-env.spec.ts +0 -106
- package/tests/e2e/resources-complete.spec.ts +0 -180
- package/tests/e2e/resources-expanded.spec.ts +0 -83
- package/tests/e2e/resources-instructions-levels.spec.ts +0 -145
- package/tests/e2e/resources-templates.spec.ts +0 -123
- package/tests/e2e/resources.spec.ts +0 -103
- package/tests/e2e/scheduler.spec.ts +0 -79
- package/tests/e2e/security.spec.ts +0 -112
- package/tests/e2e/session-advanced.spec.ts +0 -152
- package/tests/e2e/sessions.spec.ts +0 -95
- package/tests/e2e/stateless.spec.ts +0 -79
- package/tests/e2e/streaming.spec.ts +0 -176
- package/tests/e2e/tool-filtering-presets.spec.ts +0 -192
- package/tests/e2e/tool-filtering.spec.ts +0 -77
- package/tests/e2e/tools.spec.ts +0 -111
- package/tests/filtering/tool-filter.test.ts +0 -314
- package/tests/github/client-issues-errors.test.ts +0 -433
- package/tests/github/github-integration-branches.test.ts +0 -490
- package/tests/github/github-integration.test.ts +0 -1015
- package/tests/github/github-managers-branches.test.ts +0 -907
- package/tests/github/pull-requests.test.ts +0 -334
- package/tests/handlers/analytics-branches.test.ts +0 -222
- package/tests/handlers/backup-branches.test.ts +0 -270
- package/tests/handlers/briefing-context-section.test.ts +0 -388
- package/tests/handlers/briefing-github-section.test.ts +0 -392
- package/tests/handlers/briefing-user-message.test.ts +0 -405
- package/tests/handlers/codemode-tools.test.ts +0 -85
- package/tests/handlers/copilot-tools.test.ts +0 -126
- package/tests/handlers/error-path-coverage.test.ts +0 -324
- package/tests/handlers/export-tools.test.ts +0 -203
- package/tests/handlers/github-resource-handlers.test.ts +0 -929
- package/tests/handlers/github-tool-handlers.test.ts +0 -1452
- package/tests/handlers/handler-error-branches.test.ts +0 -346
- package/tests/handlers/help-resource.test.ts +0 -92
- package/tests/handlers/prompt-handler-coverage.test.ts +0 -108
- package/tests/handlers/prompt-handlers.test.ts +0 -131
- package/tests/handlers/resource-handler-coverage.test.ts +0 -281
- package/tests/handlers/resource-handlers.test.ts +0 -357
- package/tests/handlers/resource-prompt-branches.test.ts +0 -495
- package/tests/handlers/search-tool-handlers.test.ts +0 -379
- package/tests/handlers/targeted-gap-closure.test.ts +0 -387
- package/tests/handlers/team-admin.test.ts +0 -291
- package/tests/handlers/team-analytics.test.ts +0 -220
- package/tests/handlers/team-core.test.ts +0 -148
- package/tests/handlers/team-data.test.ts +0 -198
- package/tests/handlers/team-relationships.test.ts +0 -271
- package/tests/handlers/team-resource-handlers.test.ts +0 -161
- package/tests/handlers/team-search.test.ts +0 -134
- package/tests/handlers/team-tool-handlers.test.ts +0 -301
- package/tests/handlers/team-vector.test.ts +0 -213
- package/tests/handlers/template-github-branches.test.ts +0 -676
- package/tests/handlers/tool-annotations.test.ts +0 -90
- package/tests/handlers/tool-handler-coverage.test.ts +0 -514
- package/tests/handlers/tool-handlers.test.ts +0 -510
- package/tests/handlers/tool-output-schemas.test.ts +0 -116
- package/tests/handlers/vector-tool-handlers.test.ts +0 -238
- package/tests/security/sql-injection.test.ts +0 -284
- package/tests/server/mcp-server.bench.ts +0 -55
- package/tests/server/mcp-server.test.ts +0 -1326
- package/tests/server/scheduler.test.ts +0 -400
- package/tests/transports/http-legacy-sse.test.ts +0 -275
- package/tests/transports/http-security.test.ts +0 -322
- package/tests/transports/http-stateful.test.ts +0 -487
- package/tests/transports/http-transport-server.test.ts +0 -301
- package/tests/transports/http-transport.test.ts +0 -771
- package/tests/utils/github-helpers.test.ts +0 -58
- package/tests/utils/logger.test.ts +0 -180
- package/tests/utils/mcp-logger.test.ts +0 -211
- package/tests/utils/progress-utils.test.ts +0 -156
- package/tests/utils/query-helpers.test.ts +0 -80
- package/tests/utils/security-utils.test.ts +0 -82
- package/tests/vector/vector-search-branches.test.ts +0 -111
- package/tests/vector/vector-search-manager.test.ts +0 -375
- package/tests/vector/vector-search.bench.ts +0 -48
- package/tsconfig.json +0 -42
- package/tsup.config.ts +0 -19
- package/vitest.config.ts +0 -25
package/src/auth/scopes.ts
DELETED
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* memory-journal-mcp — OAuth Scopes
|
|
3
|
-
*
|
|
4
|
-
* Scope definitions and enforcement utilities for
|
|
5
|
-
* granular access control.
|
|
6
|
-
*
|
|
7
|
-
* Scope Hierarchy: full ⊃ admin ⊃ write ⊃ read
|
|
8
|
-
*
|
|
9
|
-
* Note: memory-journal-mcp uses only base scopes (no wildcard patterns).
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import type { ToolGroup } from '../types/index.js'
|
|
13
|
-
import { TOOL_GROUPS } from '../filtering/tool-filter.js'
|
|
14
|
-
|
|
15
|
-
// =============================================================================
|
|
16
|
-
// Scope Constants
|
|
17
|
-
// =============================================================================
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Standard OAuth scopes for memory-journal-mcp
|
|
21
|
-
*/
|
|
22
|
-
export const SCOPES = {
|
|
23
|
-
/** Read-only access */
|
|
24
|
-
READ: 'read',
|
|
25
|
-
/** Read and write access */
|
|
26
|
-
WRITE: 'write',
|
|
27
|
-
/** Administrative access */
|
|
28
|
-
ADMIN: 'admin',
|
|
29
|
-
/** Unrestricted access to all operations */
|
|
30
|
-
FULL: 'full',
|
|
31
|
-
} as const
|
|
32
|
-
|
|
33
|
-
export type StandardScope = (typeof SCOPES)[keyof typeof SCOPES]
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Base scopes supported by the server
|
|
37
|
-
*/
|
|
38
|
-
export const BASE_SCOPES = ['read', 'write', 'admin', 'full'] as const
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* All supported scope patterns for metadata
|
|
42
|
-
*/
|
|
43
|
-
export const SUPPORTED_SCOPES = ['read', 'write', 'admin', 'full'] as const
|
|
44
|
-
|
|
45
|
-
// =============================================================================
|
|
46
|
-
// Scope to Tool Group Mapping
|
|
47
|
-
// =============================================================================
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Declarative mapping from tool group to required minimum scope.
|
|
51
|
-
* Single source of truth — all other scope-group arrays derive from this.
|
|
52
|
-
*/
|
|
53
|
-
export const TOOL_GROUP_SCOPES: Record<ToolGroup, StandardScope> = {
|
|
54
|
-
core: SCOPES.READ,
|
|
55
|
-
search: SCOPES.READ,
|
|
56
|
-
analytics: SCOPES.READ,
|
|
57
|
-
relationships: SCOPES.READ,
|
|
58
|
-
export: SCOPES.READ,
|
|
59
|
-
admin: SCOPES.ADMIN,
|
|
60
|
-
github: SCOPES.WRITE,
|
|
61
|
-
backup: SCOPES.ADMIN,
|
|
62
|
-
team: SCOPES.WRITE,
|
|
63
|
-
codemode: SCOPES.ADMIN,
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Get the required scope for a tool group.
|
|
68
|
-
*/
|
|
69
|
-
export function getScopeForToolGroup(group: ToolGroup): StandardScope {
|
|
70
|
-
return TOOL_GROUP_SCOPES[group] ?? SCOPES.READ
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Derived arrays for backward compatibility
|
|
74
|
-
const groupsForScope = (maxScope: StandardScope): ToolGroup[] => {
|
|
75
|
-
const hierarchy: Record<StandardScope, number> = {
|
|
76
|
-
read: 0,
|
|
77
|
-
write: 1,
|
|
78
|
-
admin: 2,
|
|
79
|
-
full: 3,
|
|
80
|
-
}
|
|
81
|
-
const maxLevel = hierarchy[maxScope]
|
|
82
|
-
return (Object.entries(TOOL_GROUP_SCOPES) as [ToolGroup, StandardScope][])
|
|
83
|
-
.filter(([, scope]) => hierarchy[scope] <= maxLevel)
|
|
84
|
-
.map(([group]) => group)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Tool groups accessible with read scope (read-only operations)
|
|
89
|
-
*/
|
|
90
|
-
export const READ_SCOPE_GROUPS: ToolGroup[] = groupsForScope(SCOPES.READ)
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Tool groups accessible with write scope (read + write operations)
|
|
94
|
-
*/
|
|
95
|
-
export const WRITE_SCOPE_GROUPS: ToolGroup[] = groupsForScope(SCOPES.WRITE)
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Tool groups accessible with admin scope (all operations)
|
|
99
|
-
*/
|
|
100
|
-
export const ADMIN_SCOPE_GROUPS: ToolGroup[] = groupsForScope(SCOPES.ADMIN)
|
|
101
|
-
|
|
102
|
-
// =============================================================================
|
|
103
|
-
// Scope Parsing
|
|
104
|
-
// =============================================================================
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Parse a scope string (space-delimited) into an array
|
|
108
|
-
*/
|
|
109
|
-
export function parseScopes(scopeString: string): string[] {
|
|
110
|
-
return scopeString
|
|
111
|
-
.split(/\s+/)
|
|
112
|
-
.map((s) => s.trim())
|
|
113
|
-
.filter((s) => s.length > 0)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// =============================================================================
|
|
117
|
-
// Scope Validation
|
|
118
|
-
// =============================================================================
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Check if a scope is valid (matches known scopes)
|
|
122
|
-
*/
|
|
123
|
-
export function isValidScope(scope: string): boolean {
|
|
124
|
-
return (BASE_SCOPES as readonly string[]).includes(scope)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Check if granted scopes include the required scope.
|
|
129
|
-
* Respects the scope hierarchy: full ⊃ admin ⊃ write ⊃ read
|
|
130
|
-
*/
|
|
131
|
-
export function hasScope(grantedScopes: string[], requiredScope: string): boolean {
|
|
132
|
-
// Full scope grants everything
|
|
133
|
-
if (grantedScopes.includes(SCOPES.FULL)) {
|
|
134
|
-
return true
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Direct match
|
|
138
|
-
if (grantedScopes.includes(requiredScope)) {
|
|
139
|
-
return true
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Admin scope includes write and read
|
|
143
|
-
if (requiredScope === SCOPES.READ || requiredScope === SCOPES.WRITE) {
|
|
144
|
-
if (grantedScopes.includes(SCOPES.ADMIN)) {
|
|
145
|
-
return true
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Write scope includes read
|
|
150
|
-
if (requiredScope === SCOPES.READ) {
|
|
151
|
-
if (grantedScopes.includes(SCOPES.WRITE)) {
|
|
152
|
-
return true
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return false
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Check if granted scopes include any of the required scopes
|
|
161
|
-
*/
|
|
162
|
-
export function hasAnyScope(grantedScopes: string[], requiredScopes: string[]): boolean {
|
|
163
|
-
return requiredScopes.some((scope) => hasScope(grantedScopes, scope))
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Check if granted scopes include all of the required scopes
|
|
168
|
-
*/
|
|
169
|
-
export function hasAllScopes(grantedScopes: string[], requiredScopes: string[]): boolean {
|
|
170
|
-
return requiredScopes.every((scope) => hasScope(grantedScopes, scope))
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Check if scopes include admin access
|
|
175
|
-
*/
|
|
176
|
-
export function hasAdminScope(scopes: string[]): boolean {
|
|
177
|
-
return scopes.includes('admin') || scopes.includes('full')
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Check if scopes include write access
|
|
182
|
-
*/
|
|
183
|
-
export function hasWriteScope(scopes: string[]): boolean {
|
|
184
|
-
return scopes.includes('write') || hasAdminScope(scopes)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Check if scopes include read access
|
|
189
|
-
*/
|
|
190
|
-
export function hasReadScope(scopes: string[]): boolean {
|
|
191
|
-
return scopes.includes('read') || hasWriteScope(scopes)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// =============================================================================
|
|
195
|
-
// Tool Group Utilities
|
|
196
|
-
// =============================================================================
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Get the required minimum scope for a tool group
|
|
200
|
-
*/
|
|
201
|
-
export function getRequiredScopeForGroup(group: ToolGroup): string {
|
|
202
|
-
return TOOL_GROUP_SCOPES[group] ?? SCOPES.READ
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Get tool groups accessible with given scopes
|
|
207
|
-
*/
|
|
208
|
-
export function getAccessibleToolGroups(scopes: string[]): ToolGroup[] {
|
|
209
|
-
if (scopes.includes(SCOPES.FULL) || hasAdminScope(scopes)) {
|
|
210
|
-
return [...ADMIN_SCOPE_GROUPS]
|
|
211
|
-
}
|
|
212
|
-
if (hasWriteScope(scopes)) {
|
|
213
|
-
return [...WRITE_SCOPE_GROUPS]
|
|
214
|
-
}
|
|
215
|
-
if (hasReadScope(scopes)) {
|
|
216
|
-
return [...READ_SCOPE_GROUPS]
|
|
217
|
-
}
|
|
218
|
-
return []
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Get all tools accessible with given scopes
|
|
223
|
-
*/
|
|
224
|
-
export function getAccessibleTools(scopes: string[]): string[] {
|
|
225
|
-
const groups = getAccessibleToolGroups(scopes)
|
|
226
|
-
const allTools: string[] = []
|
|
227
|
-
|
|
228
|
-
for (const group of groups) {
|
|
229
|
-
const groupTools = TOOL_GROUPS[group] ?? []
|
|
230
|
-
allTools.push(...groupTools)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return [...new Set(allTools)]
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// =============================================================================
|
|
237
|
-
// Display Utilities
|
|
238
|
-
// =============================================================================
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Get human-readable display name for a scope
|
|
242
|
-
*/
|
|
243
|
-
export function getScopeDisplayName(scope: string): string {
|
|
244
|
-
switch (scope) {
|
|
245
|
-
case SCOPES.READ:
|
|
246
|
-
return 'Read Only'
|
|
247
|
-
case SCOPES.WRITE:
|
|
248
|
-
return 'Read/Write'
|
|
249
|
-
case SCOPES.ADMIN:
|
|
250
|
-
return 'Administrative'
|
|
251
|
-
case SCOPES.FULL:
|
|
252
|
-
return 'Full Access'
|
|
253
|
-
default:
|
|
254
|
-
return scope
|
|
255
|
-
}
|
|
256
|
-
}
|
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* memory-journal-mcp — Token Validator
|
|
3
|
-
*
|
|
4
|
-
* JWT access token validation using JWKS for signature verification.
|
|
5
|
-
* Supports RSA and EC algorithms commonly used with OAuth 2.0.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import * as jose from 'jose'
|
|
9
|
-
import type { TokenValidationResult, TokenClaims, TokenValidatorConfig } from './types.js'
|
|
10
|
-
import {
|
|
11
|
-
InvalidTokenError,
|
|
12
|
-
TokenExpiredError,
|
|
13
|
-
InvalidSignatureError,
|
|
14
|
-
JwksFetchError,
|
|
15
|
-
AUTH_ERROR_CODES,
|
|
16
|
-
} from './errors.js'
|
|
17
|
-
import { parseScopes } from './scopes.js'
|
|
18
|
-
import { logger } from '../utils/logger.js'
|
|
19
|
-
|
|
20
|
-
// =============================================================================
|
|
21
|
-
// Token Validator
|
|
22
|
-
// =============================================================================
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* JWT Token Validator
|
|
26
|
-
*
|
|
27
|
-
* Validates OAuth 2.0 access tokens using JWKS for signature verification.
|
|
28
|
-
*/
|
|
29
|
-
export class TokenValidator {
|
|
30
|
-
/** Resolved configuration with all defaults applied */
|
|
31
|
-
private readonly jwksUri: string
|
|
32
|
-
private readonly issuer: string
|
|
33
|
-
private readonly audience: string
|
|
34
|
-
private readonly clockTolerance: number
|
|
35
|
-
private readonly jwksCacheTtl: number
|
|
36
|
-
|
|
37
|
-
private jwks: jose.JWTVerifyGetKey | null = null
|
|
38
|
-
private jwksExpiry = 0
|
|
39
|
-
|
|
40
|
-
constructor(config: TokenValidatorConfig) {
|
|
41
|
-
this.jwksUri = config.jwksUri
|
|
42
|
-
this.issuer = config.issuer
|
|
43
|
-
this.audience = config.audience
|
|
44
|
-
this.clockTolerance = config.clockTolerance ?? 60
|
|
45
|
-
this.jwksCacheTtl = config.jwksCacheTtl ?? 3600
|
|
46
|
-
|
|
47
|
-
const issuerHost = (() => {
|
|
48
|
-
try {
|
|
49
|
-
return new URL(this.issuer).hostname
|
|
50
|
-
} catch {
|
|
51
|
-
return '[configured]'
|
|
52
|
-
}
|
|
53
|
-
})()
|
|
54
|
-
logger.info(`Token Validator initialized for issuer: ${issuerHost}`, {
|
|
55
|
-
module: 'AUTH',
|
|
56
|
-
operation: 'init',
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Validate an access token
|
|
62
|
-
*
|
|
63
|
-
* @param token - The JWT access token
|
|
64
|
-
* @returns Validation result with claims or error
|
|
65
|
-
*/
|
|
66
|
-
async validate(token: string): Promise<TokenValidationResult> {
|
|
67
|
-
try {
|
|
68
|
-
// Get or refresh JWKS
|
|
69
|
-
const jwks = this.getJwks()
|
|
70
|
-
|
|
71
|
-
// Verify the token
|
|
72
|
-
const { payload } = await jose.jwtVerify(token, jwks, {
|
|
73
|
-
issuer: this.issuer,
|
|
74
|
-
audience: this.audience,
|
|
75
|
-
clockTolerance: this.clockTolerance,
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
// Extract and normalize claims
|
|
79
|
-
const claims = this.extractClaims(payload)
|
|
80
|
-
|
|
81
|
-
logger.info(`Token validated for subject: ${claims.sub}`, {
|
|
82
|
-
module: 'AUTH',
|
|
83
|
-
operation: 'validate',
|
|
84
|
-
entityId: claims.sub,
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
valid: true,
|
|
89
|
-
claims,
|
|
90
|
-
}
|
|
91
|
-
} catch (error) {
|
|
92
|
-
return this.handleValidationError(error)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Get or refresh the JWKS
|
|
98
|
-
*/
|
|
99
|
-
private getJwks(): jose.JWTVerifyGetKey {
|
|
100
|
-
// Check if JWKS is cached and valid
|
|
101
|
-
if (this.jwks && Date.now() < this.jwksExpiry) {
|
|
102
|
-
return this.jwks
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const jwksHost = (() => {
|
|
106
|
-
try {
|
|
107
|
-
return new URL(this.jwksUri).hostname
|
|
108
|
-
} catch {
|
|
109
|
-
return '[configured]'
|
|
110
|
-
}
|
|
111
|
-
})()
|
|
112
|
-
logger.info(`Fetching JWKS from: ${jwksHost}`, {
|
|
113
|
-
module: 'AUTH',
|
|
114
|
-
operation: 'jwks-fetch',
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
// Create JWKS remote key set
|
|
119
|
-
this.jwks = jose.createRemoteJWKSet(new URL(this.jwksUri), {
|
|
120
|
-
cooldownDuration: 30000, // 30 seconds between retries
|
|
121
|
-
cacheMaxAge: this.jwksCacheTtl * 1000,
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
this.jwksExpiry = Date.now() + this.jwksCacheTtl * 1000
|
|
125
|
-
|
|
126
|
-
logger.info(`JWKS cached for ${String(this.jwksCacheTtl)}s`, {
|
|
127
|
-
module: 'AUTH',
|
|
128
|
-
operation: 'jwks-cache',
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
return this.jwks
|
|
132
|
-
} catch (error) {
|
|
133
|
-
const cause = error instanceof Error ? error : new Error(String(error))
|
|
134
|
-
|
|
135
|
-
logger.error('Failed to fetch JWKS', {
|
|
136
|
-
module: 'AUTH',
|
|
137
|
-
operation: 'jwks-fetch',
|
|
138
|
-
error: cause.message,
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
throw new JwksFetchError(this.jwksUri, cause)
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Extract and normalize token claims
|
|
147
|
-
*/
|
|
148
|
-
private extractClaims(payload: jose.JWTPayload): TokenClaims {
|
|
149
|
-
// Get scopes from 'scope' claim (space-delimited) or 'scopes' claim (array)
|
|
150
|
-
let scopes: string[] = []
|
|
151
|
-
|
|
152
|
-
if (typeof payload['scope'] === 'string') {
|
|
153
|
-
scopes = parseScopes(payload['scope'])
|
|
154
|
-
} else if (Array.isArray(payload['scopes'])) {
|
|
155
|
-
scopes = payload['scopes'].filter((s): s is string => typeof s === 'string')
|
|
156
|
-
} else if (Array.isArray(payload['scope'])) {
|
|
157
|
-
scopes = payload['scope'].filter((s): s is string => typeof s === 'string')
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
sub: payload.sub ?? 'unknown',
|
|
162
|
-
scopes,
|
|
163
|
-
exp: payload.exp ?? 0,
|
|
164
|
-
iat: payload.iat ?? 0,
|
|
165
|
-
iss: payload.iss,
|
|
166
|
-
aud: payload.aud,
|
|
167
|
-
nbf: payload.nbf ?? undefined,
|
|
168
|
-
jti: payload.jti,
|
|
169
|
-
client_id: payload['client_id'] as string | undefined,
|
|
170
|
-
// Include all other claims
|
|
171
|
-
...payload,
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Handle validation errors and convert to TokenValidationResult
|
|
177
|
-
*/
|
|
178
|
-
private handleValidationError(error: unknown): TokenValidationResult {
|
|
179
|
-
// Handle jose-specific errors
|
|
180
|
-
if (error instanceof jose.errors.JWTExpired) {
|
|
181
|
-
logger.warning('Token has expired', {
|
|
182
|
-
module: 'AUTH',
|
|
183
|
-
operation: 'validate',
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
valid: false,
|
|
188
|
-
error: 'Token has expired',
|
|
189
|
-
errorCode: AUTH_ERROR_CODES.TOKEN_EXPIRED,
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (error instanceof jose.errors.JWTClaimValidationFailed) {
|
|
194
|
-
logger.warning(`Token claim validation failed: ${error.message}`, {
|
|
195
|
-
module: 'AUTH',
|
|
196
|
-
operation: 'validate',
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
return {
|
|
200
|
-
valid: false,
|
|
201
|
-
error: `Token claim validation failed: ${error.message}`,
|
|
202
|
-
errorCode: AUTH_ERROR_CODES.TOKEN_INVALID,
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (error instanceof jose.errors.JWSSignatureVerificationFailed) {
|
|
207
|
-
logger.warning('Token signature verification failed', {
|
|
208
|
-
module: 'AUTH',
|
|
209
|
-
operation: 'validate',
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
return {
|
|
213
|
-
valid: false,
|
|
214
|
-
error: 'Token signature verification failed',
|
|
215
|
-
errorCode: AUTH_ERROR_CODES.SIGNATURE_INVALID,
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (error instanceof jose.errors.JWKSNoMatchingKey) {
|
|
220
|
-
logger.warning('No matching key found in JWKS', {
|
|
221
|
-
module: 'AUTH',
|
|
222
|
-
operation: 'validate',
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
return {
|
|
226
|
-
valid: false,
|
|
227
|
-
error: 'No matching key found in JWKS',
|
|
228
|
-
errorCode: AUTH_ERROR_CODES.TOKEN_INVALID,
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Handle other errors
|
|
233
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
234
|
-
|
|
235
|
-
logger.error(`Token validation failed: ${message}`, {
|
|
236
|
-
module: 'AUTH',
|
|
237
|
-
operation: 'validate',
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
return {
|
|
241
|
-
valid: false,
|
|
242
|
-
error: `Token validation failed: ${message}`,
|
|
243
|
-
errorCode: AUTH_ERROR_CODES.TOKEN_INVALID,
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Refresh the JWKS cache
|
|
249
|
-
*/
|
|
250
|
-
refreshJwks(): void {
|
|
251
|
-
this.jwks = null
|
|
252
|
-
this.jwksExpiry = 0
|
|
253
|
-
this.getJwks()
|
|
254
|
-
logger.info('JWKS cache refreshed', { module: 'AUTH', operation: 'jwks-refresh' })
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Clear the JWKS cache
|
|
259
|
-
*/
|
|
260
|
-
clearCache(): void {
|
|
261
|
-
this.jwks = null
|
|
262
|
-
this.jwksExpiry = 0
|
|
263
|
-
logger.info('Token validator cache cleared', { module: 'AUTH', operation: 'cache-clear' })
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Convert a validation error to the appropriate OAuth error class
|
|
268
|
-
*/
|
|
269
|
-
static toOAuthError(
|
|
270
|
-
result: TokenValidationResult
|
|
271
|
-
): InvalidTokenError | TokenExpiredError | InvalidSignatureError {
|
|
272
|
-
if (result.errorCode === AUTH_ERROR_CODES.TOKEN_EXPIRED) {
|
|
273
|
-
return new TokenExpiredError()
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (result.errorCode === AUTH_ERROR_CODES.SIGNATURE_INVALID) {
|
|
277
|
-
return new InvalidSignatureError()
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return new InvalidTokenError(result.error)
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// =============================================================================
|
|
285
|
-
// Factory Function
|
|
286
|
-
// =============================================================================
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Create a Token Validator instance
|
|
290
|
-
*/
|
|
291
|
-
export function createTokenValidator(config: TokenValidatorConfig): TokenValidator {
|
|
292
|
-
return new TokenValidator(config)
|
|
293
|
-
}
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* memory-journal-mcp — Transport-Agnostic Auth
|
|
3
|
-
*
|
|
4
|
-
* Authentication utilities that work across any transport layer
|
|
5
|
-
* (Express, Streamable HTTP, or future transports).
|
|
6
|
-
* Split from middleware.ts to keep files under ~500 lines.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { TokenClaims } from './types.js'
|
|
10
|
-
import type { TokenValidator } from './token-validator.js'
|
|
11
|
-
import { TokenMissingError, InvalidTokenError, InsufficientScopeError } from './errors.js'
|
|
12
|
-
import { hasScope as checkScope } from './scopes.js'
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Extract a Bearer token from an Authorization header.
|
|
16
|
-
* Local copy to avoid circular dependency with middleware.ts.
|
|
17
|
-
*/
|
|
18
|
-
function extractBearerToken(authHeader: string | undefined): string | null {
|
|
19
|
-
if (!authHeader) return null
|
|
20
|
-
const parts = authHeader.split(' ')
|
|
21
|
-
const scheme = parts[0]
|
|
22
|
-
const tokenPart = parts[1]
|
|
23
|
-
if (parts.length !== 2 || scheme?.toLowerCase() !== 'bearer') return null
|
|
24
|
-
if (tokenPart === undefined) return null
|
|
25
|
-
const token = tokenPart.trim()
|
|
26
|
-
return token.length > 0 ? token : null
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// =============================================================================
|
|
30
|
-
// Transport-Agnostic Auth Context
|
|
31
|
-
// =============================================================================
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Transport-agnostic authenticated request context.
|
|
35
|
-
* Usable by Express middleware, Streamable HTTP, or any future transport.
|
|
36
|
-
*/
|
|
37
|
-
export interface AuthenticatedContext {
|
|
38
|
-
/** Whether request is authenticated */
|
|
39
|
-
authenticated: boolean
|
|
40
|
-
|
|
41
|
-
/** Token claims (if authenticated) */
|
|
42
|
-
claims?: TokenClaims
|
|
43
|
-
|
|
44
|
-
/** Token scopes (convenience) */
|
|
45
|
-
scopes: string[]
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Create authentication context from an Authorization header.
|
|
50
|
-
* Does not throw — returns unauthenticated context when token is missing/invalid.
|
|
51
|
-
*/
|
|
52
|
-
export async function createAuthenticatedContext(
|
|
53
|
-
authHeader: string | undefined,
|
|
54
|
-
tokenValidator: TokenValidator
|
|
55
|
-
): Promise<AuthenticatedContext> {
|
|
56
|
-
const token = extractBearerToken(authHeader)
|
|
57
|
-
|
|
58
|
-
if (!token) {
|
|
59
|
-
return { authenticated: false, scopes: [] }
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const result = await tokenValidator.validate(token)
|
|
63
|
-
|
|
64
|
-
if (!result.valid || !result.claims) {
|
|
65
|
-
return { authenticated: false, scopes: [] }
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
authenticated: true,
|
|
70
|
-
claims: result.claims,
|
|
71
|
-
scopes: result.claims.scopes,
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Validate authentication and authorization.
|
|
77
|
-
* Throws OAuth errors when token missing, invalid, or insufficient scope.
|
|
78
|
-
*/
|
|
79
|
-
export async function validateAuth(
|
|
80
|
-
authHeader: string | undefined,
|
|
81
|
-
tokenValidator: TokenValidator,
|
|
82
|
-
options: { required?: boolean; requiredScopes?: string[] } = {}
|
|
83
|
-
): Promise<AuthenticatedContext> {
|
|
84
|
-
const { required = true, requiredScopes } = options
|
|
85
|
-
const token = extractBearerToken(authHeader)
|
|
86
|
-
|
|
87
|
-
if (!token) {
|
|
88
|
-
if (required) {
|
|
89
|
-
throw new TokenMissingError()
|
|
90
|
-
}
|
|
91
|
-
return { authenticated: false, scopes: [] }
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const result = await tokenValidator.validate(token)
|
|
95
|
-
|
|
96
|
-
if (!result.valid || !result.claims) {
|
|
97
|
-
throw new InvalidTokenError(result.error ?? 'Invalid token')
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const context: AuthenticatedContext = {
|
|
101
|
-
authenticated: true,
|
|
102
|
-
claims: result.claims,
|
|
103
|
-
scopes: result.claims.scopes,
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (requiredScopes && requiredScopes.length > 0) {
|
|
107
|
-
const hasRequired = requiredScopes.some((scope) => checkScope(context.scopes, scope))
|
|
108
|
-
if (!hasRequired) {
|
|
109
|
-
throw new InsufficientScopeError(requiredScopes, context.scopes)
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return context
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Format an OAuth error for HTTP response.
|
|
118
|
-
* Transport-agnostic — returns status and body without Express dependency.
|
|
119
|
-
*/
|
|
120
|
-
export function formatOAuthError(error: unknown): {
|
|
121
|
-
status: number
|
|
122
|
-
body: object
|
|
123
|
-
} {
|
|
124
|
-
if (error instanceof TokenMissingError) {
|
|
125
|
-
return {
|
|
126
|
-
status: 401,
|
|
127
|
-
body: {
|
|
128
|
-
error: 'invalid_token',
|
|
129
|
-
error_description: error.message,
|
|
130
|
-
},
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (error instanceof InvalidTokenError) {
|
|
135
|
-
return {
|
|
136
|
-
status: 401,
|
|
137
|
-
body: {
|
|
138
|
-
error: 'invalid_token',
|
|
139
|
-
error_description: error.message,
|
|
140
|
-
},
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (error instanceof InsufficientScopeError) {
|
|
145
|
-
const required = error.details?.['requiredScope'] as string[] | undefined
|
|
146
|
-
return {
|
|
147
|
-
status: 403,
|
|
148
|
-
body: {
|
|
149
|
-
error: 'insufficient_scope',
|
|
150
|
-
error_description: error.message,
|
|
151
|
-
scope: required ? required.join(' ') : undefined,
|
|
152
|
-
},
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Generic error
|
|
157
|
-
return {
|
|
158
|
-
status: 500,
|
|
159
|
-
body: {
|
|
160
|
-
error: 'server_error',
|
|
161
|
-
error_description: 'Internal server error',
|
|
162
|
-
},
|
|
163
|
-
}
|
|
164
|
-
}
|