@tumbaland/backend-core 1.33.0 → 1.34.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/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/oauth/index.d.ts +7 -0
- package/dist/oauth/index.d.ts.map +1 -0
- package/dist/oauth/index.js +22 -0
- package/dist/oauth/index.js.map +1 -0
- package/dist/oauth/models.d.ts +93 -0
- package/dist/oauth/models.d.ts.map +1 -0
- package/dist/oauth/models.js +81 -0
- package/dist/oauth/models.js.map +1 -0
- package/dist/oauth/service.d.ts +111 -0
- package/dist/oauth/service.d.ts.map +1 -0
- package/dist/oauth/service.js +159 -0
- package/dist/oauth/service.js.map +1 -0
- package/dist/oauth/tokens.d.ts +69 -0
- package/dist/oauth/tokens.d.ts.map +1 -0
- package/dist/oauth/tokens.js +79 -0
- package/dist/oauth/tokens.js.map +1 -0
- package/jest.config.js +11 -1
- package/package.json +1 -1
- package/src/index.ts +3 -0
- package/src/oauth/index.ts +23 -0
- package/src/oauth/models.ts +134 -0
- package/src/oauth/service.test.ts +325 -0
- package/src/oauth/service.ts +245 -0
- package/src/oauth/tokens.test.ts +129 -0
- package/src/oauth/tokens.ts +127 -0
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export type { PaymentRequiredDetails } from './errors/HttpError';
|
|
|
13
13
|
export * from './entitlements';
|
|
14
14
|
export * from './groups';
|
|
15
15
|
export * from './apiKeys';
|
|
16
|
+
export * from './oauth';
|
|
16
17
|
export { authenticateToken, optionalAuth } from './middleware/authMiddleware';
|
|
17
18
|
export { requireInternalServiceToken, internalServiceTokenBypass, allowUserOrInternalService, verifyInternalToken, INTERNAL_TOKEN_HEADER, SERVICE_ID_HEADER } from './middleware/internalServiceAuth';
|
|
18
19
|
export { createCorsMiddleware } from './middleware/corsMiddleware';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,YAAY,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAG9D,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AACpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,YAAY,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAGpE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAGhE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,iBAAiB,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC7K,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAGjE,cAAc,gBAAgB,CAAC;AAG/B,cAAc,UAAU,CAAC;AAGzB,cAAc,WAAW,CAAC;AAG1B,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC9E,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,EAC1B,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,EAClB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAC1G,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACnH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGjD,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACnF,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7E,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAG9E,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,eAAe,EACf,QAAQ,EACT,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,UAAU,EACV,SAAS,EACT,SAAS,EACT,iBAAiB,EACjB,eAAe,EACf,SAAS,EACT,UAAU,EACV,aAAa,EACb,kBAAkB,EACnB,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,YAAY,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAG9D,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AACpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,YAAY,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAGpE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAGhE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,iBAAiB,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC7K,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAGjE,cAAc,gBAAgB,CAAC;AAG/B,cAAc,UAAU,CAAC;AAGzB,cAAc,WAAW,CAAC;AAG1B,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC9E,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,EAC1B,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,EAClB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAC1G,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACnH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGjD,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACnF,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7E,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAG9E,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,eAAe,EACf,QAAQ,EACT,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,UAAU,EACV,SAAS,EACT,SAAS,EACT,iBAAiB,EACjB,eAAe,EACf,SAAS,EACT,UAAU,EACV,aAAa,EACb,kBAAkB,EACnB,MAAM,WAAW,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -58,6 +58,8 @@ __exportStar(require("./entitlements"), exports);
|
|
|
58
58
|
__exportStar(require("./groups"), exports);
|
|
59
59
|
// API keys (non-browser clients: MCP servers, scripts, agents)
|
|
60
60
|
__exportStar(require("./apiKeys"), exports);
|
|
61
|
+
// OAuth 2.1 authorization server, for assistants connecting over MCP
|
|
62
|
+
__exportStar(require("./oauth"), exports);
|
|
61
63
|
// Middleware
|
|
62
64
|
var authMiddleware_1 = require("./middleware/authMiddleware");
|
|
63
65
|
Object.defineProperty(exports, "authenticateToken", { enumerable: true, get: function () { return authMiddleware_1.authenticateToken; } });
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,qFAAqF;;;;;;;;;;;;;;;;;;;;;AAErF,gBAAgB;AAChB,qDAAoD;AAA3C,8GAAA,aAAa,OAAA;AAEtB,2CAAkD;AAAzC,4GAAA,gBAAgB,OAAA;AAGzB,UAAU;AACV,2CAAqD;AAA5C,iHAAA,OAAO,OAAU;AAE1B,gBAAgB;AAChB,8DAAoF;AAA3E,+GAAA,WAAW,OAAA;AAAE,8GAAA,UAAU,OAAA;AAAE,kHAAA,cAAc,OAAA;AAChD,gEAA+D;AAAtD,sHAAA,iBAAiB,OAAA;AAG1B,WAAW;AACX,oDAAgE;AAAvD,uGAAA,SAAS,OAAA;AAAE,0GAAA,YAAY,OAAA;AAEhC,SAAS;AACT,oCAA0C;AAAjC,iGAAA,UAAU,OAAA;AAEnB,SAAS;AACT,gDAA6K;AAApK,sGAAA,SAAS,OAAA;AAAE,4GAAA,eAAe,OAAA;AAAE,8GAAA,iBAAiB,OAAA;AAAE,2GAAA,cAAc,OAAA;AAAE,0GAAA,aAAa,OAAA;AAAE,0GAAA,aAAa,OAAA;AAAE,iHAAA,oBAAoB,OAAA;AAAE,iHAAA,oBAAoB,OAAA;AAGhJ,wDAAwD;AACxD,iDAA+B;AAE/B,0DAA0D;AAC1D,2CAAyB;AAEzB,+DAA+D;AAC/D,4CAA0B;AAE1B,aAAa;AACb,8DAA8E;AAArE,mHAAA,iBAAiB,OAAA;AAAE,8GAAA,YAAY,OAAA;AACxC,wEAO0C;AANxC,kIAAA,2BAA2B,OAAA;AAC3B,iIAAA,0BAA0B,OAAA;AAC1B,iIAAA,0BAA0B,OAAA;AAC1B,0HAAA,mBAAmB,OAAA;AACnB,4HAAA,qBAAqB,OAAA;AACrB,wHAAA,iBAAiB,OAAA;AAEnB,8DAAmE;AAA1D,sHAAA,oBAAoB,OAAA;AAC7B,0DAAyD;AAAhD,4GAAA,YAAY,OAAA;AACrB,4DAA0G;AAAjG,8GAAA,aAAa,OAAA;AAAE,yHAAA,wBAAwB,OAAA;AAAE,oHAAA,mBAAmB,OAAA;AACrE,kDAAmH;AAA1G,2GAAA,eAAe,OAAA;AAAE,6GAAA,iBAAiB,OAAA;AAAE,+GAAA,mBAAmB,OAAA;AAAE,6GAAA,iBAAiB,OAAA;AACnF,kDAAiD;AAAxC,oGAAA,QAAQ,OAAA;AAKjB,QAAQ;AACR,mDAAmF;AAA1E,oHAAA,qBAAqB,OAAA;AAAE,oHAAA,qBAAqB,OAAA;AACrD,6CAA6E;AAApE,6GAAA,iBAAiB,OAAA;AAAE,uGAAA,WAAW,OAAA;AAAE,qGAAA,SAAS,OAAA;AAElD,2DAA8E;AAArE,mHAAA,gBAAgB,OAAA;AAAE,oHAAA,iBAAiB,OAAA;AAE5C,UAAU;AACV,qCAQmB;AAPjB,4GAAA,iBAAiB,OAAA;AACjB,8GAAA,mBAAmB,OAAA;AACnB,4GAAA,iBAAiB,OAAA;AACjB,gHAAA,qBAAqB,OAAA;AACrB,+GAAA,oBAAoB,OAAA;AACpB,0GAAA,eAAe,OAAA;AACf,mGAAA,QAAQ,OAAA;AAGV,UAAU;AACV,qCAUmB;AATjB,qGAAA,UAAU,OAAA;AACV,oGAAA,SAAS,OAAA;AACT,oGAAA,SAAS,OAAA;AACT,4GAAA,iBAAiB,OAAA;AACjB,0GAAA,eAAe,OAAA;AACf,oGAAA,SAAS,OAAA;AACT,qGAAA,UAAU,OAAA;AACV,wGAAA,aAAa,OAAA;AACb,6GAAA,kBAAkB,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,qFAAqF;;;;;;;;;;;;;;;;;;;;;AAErF,gBAAgB;AAChB,qDAAoD;AAA3C,8GAAA,aAAa,OAAA;AAEtB,2CAAkD;AAAzC,4GAAA,gBAAgB,OAAA;AAGzB,UAAU;AACV,2CAAqD;AAA5C,iHAAA,OAAO,OAAU;AAE1B,gBAAgB;AAChB,8DAAoF;AAA3E,+GAAA,WAAW,OAAA;AAAE,8GAAA,UAAU,OAAA;AAAE,kHAAA,cAAc,OAAA;AAChD,gEAA+D;AAAtD,sHAAA,iBAAiB,OAAA;AAG1B,WAAW;AACX,oDAAgE;AAAvD,uGAAA,SAAS,OAAA;AAAE,0GAAA,YAAY,OAAA;AAEhC,SAAS;AACT,oCAA0C;AAAjC,iGAAA,UAAU,OAAA;AAEnB,SAAS;AACT,gDAA6K;AAApK,sGAAA,SAAS,OAAA;AAAE,4GAAA,eAAe,OAAA;AAAE,8GAAA,iBAAiB,OAAA;AAAE,2GAAA,cAAc,OAAA;AAAE,0GAAA,aAAa,OAAA;AAAE,0GAAA,aAAa,OAAA;AAAE,iHAAA,oBAAoB,OAAA;AAAE,iHAAA,oBAAoB,OAAA;AAGhJ,wDAAwD;AACxD,iDAA+B;AAE/B,0DAA0D;AAC1D,2CAAyB;AAEzB,+DAA+D;AAC/D,4CAA0B;AAE1B,qEAAqE;AACrE,0CAAwB;AAExB,aAAa;AACb,8DAA8E;AAArE,mHAAA,iBAAiB,OAAA;AAAE,8GAAA,YAAY,OAAA;AACxC,wEAO0C;AANxC,kIAAA,2BAA2B,OAAA;AAC3B,iIAAA,0BAA0B,OAAA;AAC1B,iIAAA,0BAA0B,OAAA;AAC1B,0HAAA,mBAAmB,OAAA;AACnB,4HAAA,qBAAqB,OAAA;AACrB,wHAAA,iBAAiB,OAAA;AAEnB,8DAAmE;AAA1D,sHAAA,oBAAoB,OAAA;AAC7B,0DAAyD;AAAhD,4GAAA,YAAY,OAAA;AACrB,4DAA0G;AAAjG,8GAAA,aAAa,OAAA;AAAE,yHAAA,wBAAwB,OAAA;AAAE,oHAAA,mBAAmB,OAAA;AACrE,kDAAmH;AAA1G,2GAAA,eAAe,OAAA;AAAE,6GAAA,iBAAiB,OAAA;AAAE,+GAAA,mBAAmB,OAAA;AAAE,6GAAA,iBAAiB,OAAA;AACnF,kDAAiD;AAAxC,oGAAA,QAAQ,OAAA;AAKjB,QAAQ;AACR,mDAAmF;AAA1E,oHAAA,qBAAqB,OAAA;AAAE,oHAAA,qBAAqB,OAAA;AACrD,6CAA6E;AAApE,6GAAA,iBAAiB,OAAA;AAAE,uGAAA,WAAW,OAAA;AAAE,qGAAA,SAAS,OAAA;AAElD,2DAA8E;AAArE,mHAAA,gBAAgB,OAAA;AAAE,oHAAA,iBAAiB,OAAA;AAE5C,UAAU;AACV,qCAQmB;AAPjB,4GAAA,iBAAiB,OAAA;AACjB,8GAAA,mBAAmB,OAAA;AACnB,4GAAA,iBAAiB,OAAA;AACjB,gHAAA,qBAAqB,OAAA;AACrB,+GAAA,oBAAoB,OAAA;AACpB,0GAAA,eAAe,OAAA;AACf,mGAAA,QAAQ,OAAA;AAGV,UAAU;AACV,qCAUmB;AATjB,qGAAA,UAAU,OAAA;AACV,oGAAA,SAAS,OAAA;AACT,oGAAA,SAAS,OAAA;AACT,4GAAA,iBAAiB,OAAA;AACjB,0GAAA,eAAe,OAAA;AACf,oGAAA,SAAS,OAAA;AACT,qGAAA,UAAU,OAAA;AACV,wGAAA,aAAa,OAAA;AACb,6GAAA,kBAAkB,OAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { OAuthClient, AuthorizationCode, RefreshToken } from './models';
|
|
2
|
+
export type { IOAuthClient, IAuthorizationCode, IRefreshToken } from './models';
|
|
3
|
+
export { registerClient, findClient, isRegisteredRedirect, issueAuthorizationCode, redeemAuthorizationCode, issueRefreshToken, redeemRefreshToken, revokeRefreshTokensForUser, listConnections } from './service';
|
|
4
|
+
export type { RegisterClientInput, RegisteredClient, IssueCodeInput, CodeRedemption, CodeRejection, RefreshRedemption } from './service';
|
|
5
|
+
export { mintAccessToken, verifyAccessToken, parseScopes } from './tokens';
|
|
6
|
+
export type { AccessTokenClaims, MintAccessTokenInput, MintedAccessToken, AccessTokenVerification } from './tokens';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/oauth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxE,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAChF,OAAO,EACL,cAAc,EACd,UAAU,EACV,oBAAoB,EACpB,sBAAsB,EACtB,uBAAuB,EACvB,iBAAiB,EACjB,kBAAkB,EAClB,0BAA0B,EAC1B,eAAe,EAChB,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,aAAa,EACb,iBAAiB,EAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC3E,YAAY,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseScopes = exports.verifyAccessToken = exports.mintAccessToken = exports.listConnections = exports.revokeRefreshTokensForUser = exports.redeemRefreshToken = exports.issueRefreshToken = exports.redeemAuthorizationCode = exports.issueAuthorizationCode = exports.isRegisteredRedirect = exports.findClient = exports.registerClient = exports.RefreshToken = exports.AuthorizationCode = exports.OAuthClient = void 0;
|
|
4
|
+
var models_1 = require("./models");
|
|
5
|
+
Object.defineProperty(exports, "OAuthClient", { enumerable: true, get: function () { return models_1.OAuthClient; } });
|
|
6
|
+
Object.defineProperty(exports, "AuthorizationCode", { enumerable: true, get: function () { return models_1.AuthorizationCode; } });
|
|
7
|
+
Object.defineProperty(exports, "RefreshToken", { enumerable: true, get: function () { return models_1.RefreshToken; } });
|
|
8
|
+
var service_1 = require("./service");
|
|
9
|
+
Object.defineProperty(exports, "registerClient", { enumerable: true, get: function () { return service_1.registerClient; } });
|
|
10
|
+
Object.defineProperty(exports, "findClient", { enumerable: true, get: function () { return service_1.findClient; } });
|
|
11
|
+
Object.defineProperty(exports, "isRegisteredRedirect", { enumerable: true, get: function () { return service_1.isRegisteredRedirect; } });
|
|
12
|
+
Object.defineProperty(exports, "issueAuthorizationCode", { enumerable: true, get: function () { return service_1.issueAuthorizationCode; } });
|
|
13
|
+
Object.defineProperty(exports, "redeemAuthorizationCode", { enumerable: true, get: function () { return service_1.redeemAuthorizationCode; } });
|
|
14
|
+
Object.defineProperty(exports, "issueRefreshToken", { enumerable: true, get: function () { return service_1.issueRefreshToken; } });
|
|
15
|
+
Object.defineProperty(exports, "redeemRefreshToken", { enumerable: true, get: function () { return service_1.redeemRefreshToken; } });
|
|
16
|
+
Object.defineProperty(exports, "revokeRefreshTokensForUser", { enumerable: true, get: function () { return service_1.revokeRefreshTokensForUser; } });
|
|
17
|
+
Object.defineProperty(exports, "listConnections", { enumerable: true, get: function () { return service_1.listConnections; } });
|
|
18
|
+
var tokens_1 = require("./tokens");
|
|
19
|
+
Object.defineProperty(exports, "mintAccessToken", { enumerable: true, get: function () { return tokens_1.mintAccessToken; } });
|
|
20
|
+
Object.defineProperty(exports, "verifyAccessToken", { enumerable: true, get: function () { return tokens_1.verifyAccessToken; } });
|
|
21
|
+
Object.defineProperty(exports, "parseScopes", { enumerable: true, get: function () { return tokens_1.parseScopes; } });
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/oauth/index.ts"],"names":[],"mappings":";;;AAAA,mCAAwE;AAA/D,qGAAA,WAAW,OAAA;AAAE,2GAAA,iBAAiB,OAAA;AAAE,sGAAA,YAAY,OAAA;AAErD,qCAUmB;AATjB,yGAAA,cAAc,OAAA;AACd,qGAAA,UAAU,OAAA;AACV,+GAAA,oBAAoB,OAAA;AACpB,iHAAA,sBAAsB,OAAA;AACtB,kHAAA,uBAAuB,OAAA;AACvB,4GAAA,iBAAiB,OAAA;AACjB,6GAAA,kBAAkB,OAAA;AAClB,qHAAA,0BAA0B,OAAA;AAC1B,0GAAA,eAAe,OAAA;AAUjB,mCAA2E;AAAlE,yGAAA,eAAe,OAAA;AAAE,2GAAA,iBAAiB,OAAA;AAAE,qGAAA,WAAW,OAAA"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import mongoose, { Document } from 'mongoose';
|
|
2
|
+
/**
|
|
3
|
+
* A client registered through RFC 7591 Dynamic Client Registration.
|
|
4
|
+
*
|
|
5
|
+
* Assistants register themselves — nobody is going to pre-provision Claude,
|
|
6
|
+
* ChatGPT and Gemini by hand, and the MCP spec expects a server to accept
|
|
7
|
+
* registrations. Clients are public (no secret): the caller is a browser-driven
|
|
8
|
+
* flow that cannot keep one, which is exactly why PKCE is mandatory here.
|
|
9
|
+
*/
|
|
10
|
+
export interface IOAuthClient extends Document {
|
|
11
|
+
clientId: string;
|
|
12
|
+
clientName: string;
|
|
13
|
+
redirectUris: string[];
|
|
14
|
+
createdAt: Date;
|
|
15
|
+
updatedAt: Date;
|
|
16
|
+
}
|
|
17
|
+
export declare const OAuthClient: mongoose.Model<IOAuthClient, {}, {}, {}, mongoose.Document<unknown, {}, IOAuthClient, {}, mongoose.DefaultSchemaOptions> & IOAuthClient & Required<{
|
|
18
|
+
_id: mongoose.Types.ObjectId;
|
|
19
|
+
}> & {
|
|
20
|
+
__v: number;
|
|
21
|
+
} & {
|
|
22
|
+
id: string;
|
|
23
|
+
}, any, IOAuthClient>;
|
|
24
|
+
/**
|
|
25
|
+
* A one-time authorization code, alive for the seconds between the user
|
|
26
|
+
* approving and the client exchanging it.
|
|
27
|
+
*
|
|
28
|
+
* Everything the token will assert is fixed here, at the moment consent is
|
|
29
|
+
* given: which scopes, which tenant, which resource. The exchange cannot widen
|
|
30
|
+
* any of it, so a client that asks for more at the token endpoint than the user
|
|
31
|
+
* agreed to gets what the user agreed to.
|
|
32
|
+
*/
|
|
33
|
+
export interface IAuthorizationCode extends Document {
|
|
34
|
+
code: string;
|
|
35
|
+
clientId: string;
|
|
36
|
+
userId: string;
|
|
37
|
+
userEmail: string;
|
|
38
|
+
userName: string;
|
|
39
|
+
redirectUri: string;
|
|
40
|
+
scopes: string[];
|
|
41
|
+
groupId: string | null;
|
|
42
|
+
/** RFC 8707: the MCP server this will be minted for */
|
|
43
|
+
resource: string;
|
|
44
|
+
/** PKCE, always S256 — the spec does not allow `plain` */
|
|
45
|
+
codeChallenge: string;
|
|
46
|
+
/** set once the code is exchanged, so a replay is detectable rather than silent */
|
|
47
|
+
usedAt?: Date;
|
|
48
|
+
expiresAt: Date;
|
|
49
|
+
createdAt: Date;
|
|
50
|
+
}
|
|
51
|
+
export declare const AuthorizationCode: mongoose.Model<IAuthorizationCode, {}, {}, {}, mongoose.Document<unknown, {}, IAuthorizationCode, {}, mongoose.DefaultSchemaOptions> & IAuthorizationCode & Required<{
|
|
52
|
+
_id: mongoose.Types.ObjectId;
|
|
53
|
+
}> & {
|
|
54
|
+
__v: number;
|
|
55
|
+
} & {
|
|
56
|
+
id: string;
|
|
57
|
+
}, any, IAuthorizationCode>;
|
|
58
|
+
/**
|
|
59
|
+
* A refresh token: the part that actually persists, and therefore the part
|
|
60
|
+
* revocation acts on.
|
|
61
|
+
*
|
|
62
|
+
* Access tokens are unrevokable JWTs by design, kept short so that matters
|
|
63
|
+
* little. Cutting off an assistant means deleting its refresh token, after
|
|
64
|
+
* which it can obtain nothing new.
|
|
65
|
+
*/
|
|
66
|
+
export interface IRefreshToken extends Document {
|
|
67
|
+
tokenHash: string;
|
|
68
|
+
clientId: string;
|
|
69
|
+
userId: string;
|
|
70
|
+
scopes: string[];
|
|
71
|
+
groupId: string | null;
|
|
72
|
+
resource: string;
|
|
73
|
+
/**
|
|
74
|
+
* When the user actually connected this assistant.
|
|
75
|
+
*
|
|
76
|
+
* Distinct from `createdAt`, which rotation resets on every refresh — showing
|
|
77
|
+
* that in the UI would tell someone they connected Claude an hour ago when
|
|
78
|
+
* they did it in March.
|
|
79
|
+
*/
|
|
80
|
+
grantedAt: Date;
|
|
81
|
+
revokedAt?: Date;
|
|
82
|
+
lastUsedAt?: Date;
|
|
83
|
+
createdAt: Date;
|
|
84
|
+
updatedAt: Date;
|
|
85
|
+
}
|
|
86
|
+
export declare const RefreshToken: mongoose.Model<IRefreshToken, {}, {}, {}, mongoose.Document<unknown, {}, IRefreshToken, {}, mongoose.DefaultSchemaOptions> & IRefreshToken & Required<{
|
|
87
|
+
_id: mongoose.Types.ObjectId;
|
|
88
|
+
}> & {
|
|
89
|
+
__v: number;
|
|
90
|
+
} & {
|
|
91
|
+
id: string;
|
|
92
|
+
}, any, IRefreshToken>;
|
|
93
|
+
//# sourceMappingURL=models.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/oauth/models.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,EAAE,EAAE,QAAQ,EAAU,MAAM,UAAU,CAAC;AAEtD;;;;;;;GAOG;AACH,MAAM,WAAW,YAAa,SAAQ,QAAQ;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAWD,eAAO,MAAM,WAAW;;;;;;qBAE0C,CAAC;AAEnE;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAmB,SAAQ,QAAQ;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAC;IACtB,mFAAmF;IACnF,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAwBD,eAAO,MAAM,iBAAiB;;;;;;2BAEsD,CAAC;AAErF;;;;;;;GAOG;AACH,MAAM,WAAW,aAAc,SAAQ,QAAQ;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,UAAU,CAAC,EAAE,IAAI,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAmBD,eAAO,MAAM,YAAY;;;;;;sBAE4C,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.RefreshToken = exports.AuthorizationCode = exports.OAuthClient = void 0;
|
|
37
|
+
const mongoose_1 = __importStar(require("mongoose"));
|
|
38
|
+
const OAuthClientSchema = new mongoose_1.Schema({
|
|
39
|
+
clientId: { type: String, required: true, unique: true, index: true },
|
|
40
|
+
clientName: { type: String, required: true, maxlength: 200 },
|
|
41
|
+
redirectUris: { type: [String], required: true }
|
|
42
|
+
}, { timestamps: true, collection: 'oauth_clients' });
|
|
43
|
+
exports.OAuthClient = mongoose_1.default.models.OAuthClient
|
|
44
|
+
? mongoose_1.default.models.OAuthClient
|
|
45
|
+
: mongoose_1.default.model('OAuthClient', OAuthClientSchema);
|
|
46
|
+
const AuthorizationCodeSchema = new mongoose_1.Schema({
|
|
47
|
+
code: { type: String, required: true, unique: true, index: true },
|
|
48
|
+
clientId: { type: String, required: true },
|
|
49
|
+
userId: { type: String, required: true },
|
|
50
|
+
userEmail: { type: String, required: true },
|
|
51
|
+
userName: { type: String, default: '' },
|
|
52
|
+
redirectUri: { type: String, required: true },
|
|
53
|
+
scopes: { type: [String], default: [] },
|
|
54
|
+
groupId: { type: String, default: null },
|
|
55
|
+
resource: { type: String, required: true },
|
|
56
|
+
codeChallenge: { type: String, required: true },
|
|
57
|
+
usedAt: { type: Date },
|
|
58
|
+
expiresAt: { type: Date, required: true }
|
|
59
|
+
}, { timestamps: { createdAt: true, updatedAt: false }, collection: 'oauth_authorization_codes' });
|
|
60
|
+
// Codes are short-lived and never read after expiry; let Mongo sweep them so a
|
|
61
|
+
// replay cannot be attempted against a record nobody is cleaning up.
|
|
62
|
+
AuthorizationCodeSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
|
|
63
|
+
exports.AuthorizationCode = mongoose_1.default.models.AuthorizationCode
|
|
64
|
+
? mongoose_1.default.models.AuthorizationCode
|
|
65
|
+
: mongoose_1.default.model('AuthorizationCode', AuthorizationCodeSchema);
|
|
66
|
+
const RefreshTokenSchema = new mongoose_1.Schema({
|
|
67
|
+
tokenHash: { type: String, required: true, unique: true, index: true },
|
|
68
|
+
clientId: { type: String, required: true },
|
|
69
|
+
userId: { type: String, required: true, index: true },
|
|
70
|
+
scopes: { type: [String], default: [] },
|
|
71
|
+
groupId: { type: String, default: null },
|
|
72
|
+
resource: { type: String, required: true },
|
|
73
|
+
grantedAt: { type: Date, default: Date.now },
|
|
74
|
+
revokedAt: { type: Date },
|
|
75
|
+
lastUsedAt: { type: Date }
|
|
76
|
+
}, { timestamps: true, collection: 'oauth_refresh_tokens' });
|
|
77
|
+
RefreshTokenSchema.index({ userId: 1, createdAt: -1 });
|
|
78
|
+
exports.RefreshToken = mongoose_1.default.models.RefreshToken
|
|
79
|
+
? mongoose_1.default.models.RefreshToken
|
|
80
|
+
: mongoose_1.default.model('RefreshToken', RefreshTokenSchema);
|
|
81
|
+
//# sourceMappingURL=models.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/oauth/models.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { IAuthorizationCode } from './models';
|
|
2
|
+
import type { ApiKeyScope } from '../apiKeys/types';
|
|
3
|
+
export interface RegisterClientInput {
|
|
4
|
+
clientName: string;
|
|
5
|
+
redirectUris: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface RegisteredClient {
|
|
8
|
+
clientId: string;
|
|
9
|
+
clientName: string;
|
|
10
|
+
redirectUris: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare const registerClient: (input: RegisterClientInput) => Promise<RegisteredClient>;
|
|
13
|
+
export declare const findClient: (clientId: string) => import("mongoose").Query<(import("mongoose").Document<unknown, {}, import("./models").IOAuthClient, {}, import("mongoose").DefaultSchemaOptions> & import("./models").IOAuthClient & Required<{
|
|
14
|
+
_id: import("mongoose").Types.ObjectId;
|
|
15
|
+
}> & {
|
|
16
|
+
__v: number;
|
|
17
|
+
} & {
|
|
18
|
+
id: string;
|
|
19
|
+
}) | null, import("mongoose").Document<unknown, {}, import("./models").IOAuthClient, {}, import("mongoose").DefaultSchemaOptions> & import("./models").IOAuthClient & Required<{
|
|
20
|
+
_id: import("mongoose").Types.ObjectId;
|
|
21
|
+
}> & {
|
|
22
|
+
__v: number;
|
|
23
|
+
} & {
|
|
24
|
+
id: string;
|
|
25
|
+
}, {}, import("./models").IOAuthClient, "findOne", {}>;
|
|
26
|
+
/**
|
|
27
|
+
* Whether a client may be sent back to this URI.
|
|
28
|
+
*
|
|
29
|
+
* Exact string match, deliberately. Prefix or wildcard matching on redirect URIs
|
|
30
|
+
* is the classic way an authorization code ends up delivered to somewhere the
|
|
31
|
+
* client never controlled.
|
|
32
|
+
*/
|
|
33
|
+
export declare const isRegisteredRedirect: (client: {
|
|
34
|
+
redirectUris: string[];
|
|
35
|
+
}, uri: string) => boolean;
|
|
36
|
+
export interface IssueCodeInput {
|
|
37
|
+
clientId: string;
|
|
38
|
+
userId: string;
|
|
39
|
+
userEmail: string;
|
|
40
|
+
userName: string;
|
|
41
|
+
redirectUri: string;
|
|
42
|
+
scopes: ApiKeyScope[];
|
|
43
|
+
groupId: string | null;
|
|
44
|
+
resource: string;
|
|
45
|
+
codeChallenge: string;
|
|
46
|
+
}
|
|
47
|
+
export declare const issueAuthorizationCode: (input: IssueCodeInput) => Promise<string>;
|
|
48
|
+
export type CodeRejection = 'unknown' | 'expired' | 'already-used' | 'client-mismatch' | 'redirect-mismatch' | 'pkce-failed';
|
|
49
|
+
export interface CodeRedemption {
|
|
50
|
+
ok: boolean;
|
|
51
|
+
rejection?: CodeRejection;
|
|
52
|
+
code?: IAuthorizationCode;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Exchange a code, once.
|
|
56
|
+
*
|
|
57
|
+
* The `usedAt` stamp is set by the same atomic update that fetches the code, so
|
|
58
|
+
* two simultaneous exchanges cannot both succeed — a replayed code loses the
|
|
59
|
+
* race rather than being caught by a check that ran a moment earlier.
|
|
60
|
+
*/
|
|
61
|
+
export declare const redeemAuthorizationCode: (code: string, clientId: string, redirectUri: string, codeVerifier: string) => Promise<CodeRedemption>;
|
|
62
|
+
export interface IssuedRefreshToken {
|
|
63
|
+
token: string;
|
|
64
|
+
}
|
|
65
|
+
export declare const issueRefreshToken: (input: {
|
|
66
|
+
clientId: string;
|
|
67
|
+
userId: string;
|
|
68
|
+
scopes: ApiKeyScope[];
|
|
69
|
+
groupId: string | null;
|
|
70
|
+
resource: string;
|
|
71
|
+
}) => Promise<IssuedRefreshToken>;
|
|
72
|
+
export interface RefreshRedemption {
|
|
73
|
+
ok: boolean;
|
|
74
|
+
/** true when a retired token was presented again — treated as a compromise */
|
|
75
|
+
reused?: boolean;
|
|
76
|
+
userId?: string;
|
|
77
|
+
scopes?: ApiKeyScope[];
|
|
78
|
+
groupId?: string | null;
|
|
79
|
+
resource?: string;
|
|
80
|
+
/** the replacement the client must store; the presented one is now dead */
|
|
81
|
+
rotatedToken?: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Exchange a refresh token for a new one, rotating as we go.
|
|
85
|
+
*
|
|
86
|
+
* Rotation matters because these are the long-lived half: an access token is
|
|
87
|
+
* gone within the hour, but a refresh token that never changes is a permanent
|
|
88
|
+
* credential for whoever obtains a copy. Each use retires the old token and
|
|
89
|
+
* issues a fresh one, so a stolen copy stops working as soon as the legitimate
|
|
90
|
+
* client refreshes.
|
|
91
|
+
*
|
|
92
|
+
* Reuse of an already-rotated token is treated as theft rather than as an
|
|
93
|
+
* ordinary failure: the honest client and the thief now both hold tokens
|
|
94
|
+
* descended from the same grant, and there is no way to tell which just called.
|
|
95
|
+
* So the whole chain is revoked and the user reconnects — noisy, but the
|
|
96
|
+
* alternative is leaving an attacker with working access.
|
|
97
|
+
*/
|
|
98
|
+
export declare const redeemRefreshToken: (token: string, clientId: string) => Promise<RefreshRedemption>;
|
|
99
|
+
/** Cut off an assistant: without a refresh token it can obtain nothing new. */
|
|
100
|
+
export declare const revokeRefreshTokensForUser: (userId: string, clientId?: string) => Promise<number>;
|
|
101
|
+
export declare const listConnections: (userId: string) => Promise<{
|
|
102
|
+
id: string;
|
|
103
|
+
clientId: string;
|
|
104
|
+
clientName: string;
|
|
105
|
+
scopes: string[];
|
|
106
|
+
groupId: string | null;
|
|
107
|
+
createdAt: string;
|
|
108
|
+
lastUsedAt: string | null;
|
|
109
|
+
revokedAt: string | null;
|
|
110
|
+
}[]>;
|
|
111
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/oauth/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listConnections = exports.revokeRefreshTokensForUser = exports.redeemRefreshToken = exports.issueRefreshToken = exports.redeemAuthorizationCode = exports.issueAuthorizationCode = exports.isRegisteredRedirect = exports.findClient = exports.registerClient = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
const models_1 = require("./models");
|
|
6
|
+
/** How long a user has between approving and the client exchanging the code. */
|
|
7
|
+
const CODE_TTL_MS = 60 * 1000;
|
|
8
|
+
const sha256 = (value) => (0, crypto_1.createHash)('sha256').update(value).digest('hex');
|
|
9
|
+
/** base64url of the SHA-256 digest, which is what PKCE `S256` specifies. */
|
|
10
|
+
const s256 = (verifier) => (0, crypto_1.createHash)('sha256').update(verifier).digest('base64url');
|
|
11
|
+
const constantTimeEquals = (a, b) => {
|
|
12
|
+
const left = Buffer.from(a);
|
|
13
|
+
const right = Buffer.from(b);
|
|
14
|
+
if (left.length !== right.length)
|
|
15
|
+
return false;
|
|
16
|
+
return (0, crypto_1.timingSafeEqual)(left, right);
|
|
17
|
+
};
|
|
18
|
+
const registerClient = async (input) => {
|
|
19
|
+
const client = await models_1.OAuthClient.create({
|
|
20
|
+
clientId: `tmb-client-${(0, crypto_1.randomBytes)(16).toString('hex')}`,
|
|
21
|
+
clientName: input.clientName.slice(0, 200),
|
|
22
|
+
redirectUris: input.redirectUris
|
|
23
|
+
});
|
|
24
|
+
return {
|
|
25
|
+
clientId: client.clientId,
|
|
26
|
+
clientName: client.clientName,
|
|
27
|
+
redirectUris: client.redirectUris
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
exports.registerClient = registerClient;
|
|
31
|
+
const findClient = (clientId) => models_1.OAuthClient.findOne({ clientId });
|
|
32
|
+
exports.findClient = findClient;
|
|
33
|
+
/**
|
|
34
|
+
* Whether a client may be sent back to this URI.
|
|
35
|
+
*
|
|
36
|
+
* Exact string match, deliberately. Prefix or wildcard matching on redirect URIs
|
|
37
|
+
* is the classic way an authorization code ends up delivered to somewhere the
|
|
38
|
+
* client never controlled.
|
|
39
|
+
*/
|
|
40
|
+
const isRegisteredRedirect = (client, uri) => client.redirectUris.includes(uri);
|
|
41
|
+
exports.isRegisteredRedirect = isRegisteredRedirect;
|
|
42
|
+
const issueAuthorizationCode = async (input) => {
|
|
43
|
+
const code = (0, crypto_1.randomBytes)(32).toString('base64url');
|
|
44
|
+
await models_1.AuthorizationCode.create({
|
|
45
|
+
...input,
|
|
46
|
+
code,
|
|
47
|
+
expiresAt: new Date(Date.now() + CODE_TTL_MS)
|
|
48
|
+
});
|
|
49
|
+
return code;
|
|
50
|
+
};
|
|
51
|
+
exports.issueAuthorizationCode = issueAuthorizationCode;
|
|
52
|
+
/**
|
|
53
|
+
* Exchange a code, once.
|
|
54
|
+
*
|
|
55
|
+
* The `usedAt` stamp is set by the same atomic update that fetches the code, so
|
|
56
|
+
* two simultaneous exchanges cannot both succeed — a replayed code loses the
|
|
57
|
+
* race rather than being caught by a check that ran a moment earlier.
|
|
58
|
+
*/
|
|
59
|
+
const redeemAuthorizationCode = async (code, clientId, redirectUri, codeVerifier) => {
|
|
60
|
+
const record = await models_1.AuthorizationCode.findOneAndUpdate({ code, usedAt: { $exists: false } }, { $set: { usedAt: new Date() } }, { new: true });
|
|
61
|
+
if (!record) {
|
|
62
|
+
// Either it never existed or it has already been redeemed; the client is
|
|
63
|
+
// told the same thing for both.
|
|
64
|
+
const existed = await models_1.AuthorizationCode.exists({ code });
|
|
65
|
+
return { ok: false, rejection: existed ? 'already-used' : 'unknown' };
|
|
66
|
+
}
|
|
67
|
+
if (record.expiresAt.getTime() <= Date.now())
|
|
68
|
+
return { ok: false, rejection: 'expired' };
|
|
69
|
+
if (record.clientId !== clientId)
|
|
70
|
+
return { ok: false, rejection: 'client-mismatch' };
|
|
71
|
+
if (record.redirectUri !== redirectUri)
|
|
72
|
+
return { ok: false, rejection: 'redirect-mismatch' };
|
|
73
|
+
if (!constantTimeEquals(s256(codeVerifier), record.codeChallenge)) {
|
|
74
|
+
return { ok: false, rejection: 'pkce-failed' };
|
|
75
|
+
}
|
|
76
|
+
return { ok: true, code: record };
|
|
77
|
+
};
|
|
78
|
+
exports.redeemAuthorizationCode = redeemAuthorizationCode;
|
|
79
|
+
const issueRefreshToken = async (input) => {
|
|
80
|
+
const token = (0, crypto_1.randomBytes)(32).toString('base64url');
|
|
81
|
+
await models_1.RefreshToken.create({ ...input, tokenHash: sha256(token) });
|
|
82
|
+
return { token };
|
|
83
|
+
};
|
|
84
|
+
exports.issueRefreshToken = issueRefreshToken;
|
|
85
|
+
/**
|
|
86
|
+
* Exchange a refresh token for a new one, rotating as we go.
|
|
87
|
+
*
|
|
88
|
+
* Rotation matters because these are the long-lived half: an access token is
|
|
89
|
+
* gone within the hour, but a refresh token that never changes is a permanent
|
|
90
|
+
* credential for whoever obtains a copy. Each use retires the old token and
|
|
91
|
+
* issues a fresh one, so a stolen copy stops working as soon as the legitimate
|
|
92
|
+
* client refreshes.
|
|
93
|
+
*
|
|
94
|
+
* Reuse of an already-rotated token is treated as theft rather than as an
|
|
95
|
+
* ordinary failure: the honest client and the thief now both hold tokens
|
|
96
|
+
* descended from the same grant, and there is no way to tell which just called.
|
|
97
|
+
* So the whole chain is revoked and the user reconnects — noisy, but the
|
|
98
|
+
* alternative is leaving an attacker with working access.
|
|
99
|
+
*/
|
|
100
|
+
const redeemRefreshToken = async (token, clientId) => {
|
|
101
|
+
const record = await models_1.RefreshToken.findOne({ tokenHash: sha256(token), clientId });
|
|
102
|
+
if (!record)
|
|
103
|
+
return { ok: false };
|
|
104
|
+
if (record.revokedAt) {
|
|
105
|
+
// Already rotated or explicitly revoked. If something is still presenting
|
|
106
|
+
// it, a copy is loose — cut every token in this grant.
|
|
107
|
+
await models_1.RefreshToken.updateMany({ userId: record.userId, clientId, revokedAt: { $exists: false } }, { $set: { revokedAt: new Date() } });
|
|
108
|
+
return { ok: false, reused: true };
|
|
109
|
+
}
|
|
110
|
+
record.revokedAt = new Date();
|
|
111
|
+
record.lastUsedAt = new Date();
|
|
112
|
+
await record.save();
|
|
113
|
+
const rotated = (0, crypto_1.randomBytes)(32).toString('base64url');
|
|
114
|
+
await models_1.RefreshToken.create({
|
|
115
|
+
tokenHash: sha256(rotated),
|
|
116
|
+
clientId,
|
|
117
|
+
userId: record.userId,
|
|
118
|
+
scopes: record.scopes,
|
|
119
|
+
groupId: record.groupId ?? null,
|
|
120
|
+
resource: record.resource,
|
|
121
|
+
// Carried, not reset: this is still the grant the user approved.
|
|
122
|
+
grantedAt: record.grantedAt ?? record.createdAt
|
|
123
|
+
});
|
|
124
|
+
return {
|
|
125
|
+
ok: true,
|
|
126
|
+
userId: record.userId,
|
|
127
|
+
scopes: record.scopes,
|
|
128
|
+
groupId: record.groupId ?? null,
|
|
129
|
+
resource: record.resource,
|
|
130
|
+
rotatedToken: rotated
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
exports.redeemRefreshToken = redeemRefreshToken;
|
|
134
|
+
/** Cut off an assistant: without a refresh token it can obtain nothing new. */
|
|
135
|
+
const revokeRefreshTokensForUser = async (userId, clientId) => {
|
|
136
|
+
const filter = { userId, revokedAt: { $exists: false } };
|
|
137
|
+
if (clientId)
|
|
138
|
+
filter.clientId = clientId;
|
|
139
|
+
const result = await models_1.RefreshToken.updateMany(filter, { $set: { revokedAt: new Date() } });
|
|
140
|
+
return result.modifiedCount;
|
|
141
|
+
};
|
|
142
|
+
exports.revokeRefreshTokensForUser = revokeRefreshTokensForUser;
|
|
143
|
+
const listConnections = async (userId) => {
|
|
144
|
+
const tokens = await models_1.RefreshToken.find({ userId }).sort({ createdAt: -1 });
|
|
145
|
+
const clients = await models_1.OAuthClient.find({ clientId: { $in: tokens.map((t) => t.clientId) } });
|
|
146
|
+
const nameById = new Map(clients.map((client) => [client.clientId, client.clientName]));
|
|
147
|
+
return tokens.map((token) => ({
|
|
148
|
+
id: String(token._id),
|
|
149
|
+
clientId: token.clientId,
|
|
150
|
+
clientName: nameById.get(token.clientId) ?? 'Unknown app',
|
|
151
|
+
scopes: token.scopes,
|
|
152
|
+
groupId: token.groupId ?? null,
|
|
153
|
+
createdAt: (token.grantedAt ?? token.createdAt).toISOString(),
|
|
154
|
+
lastUsedAt: token.lastUsedAt?.toISOString() ?? null,
|
|
155
|
+
revokedAt: token.revokedAt?.toISOString() ?? null
|
|
156
|
+
}));
|
|
157
|
+
};
|
|
158
|
+
exports.listConnections = listConnections;
|
|
159
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/oauth/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type ApiKeyScope } from '../apiKeys/types';
|
|
2
|
+
/**
|
|
3
|
+
* Access tokens for the OAuth flow that lets an assistant connect to Tumbaland.
|
|
4
|
+
*
|
|
5
|
+
* These are JWTs rather than database rows so the MCP server can validate one
|
|
6
|
+
* without a query on every tool call, and because OAuth expects short-lived
|
|
7
|
+
* bearer tokens with a refresh path rather than the indefinite credentials an
|
|
8
|
+
* API key is. The trade is that a token cannot be revoked before it expires —
|
|
9
|
+
* hence the deliberately short life, with revocation applied at the refresh
|
|
10
|
+
* token, which is the thing that actually persists.
|
|
11
|
+
*/
|
|
12
|
+
/** Marks a token as issued by the OAuth flow, for the MCP resource specifically. */
|
|
13
|
+
declare const TOKEN_TYPE = "mcp_access";
|
|
14
|
+
export interface AccessTokenClaims {
|
|
15
|
+
/** the user the assistant is acting for */
|
|
16
|
+
sub: string;
|
|
17
|
+
/** the MCP server this token may be used against — RFC 8707 audience binding */
|
|
18
|
+
aud: string;
|
|
19
|
+
iss: string;
|
|
20
|
+
scope: string;
|
|
21
|
+
/** the tenant the token is pinned to; null means the user's personal data */
|
|
22
|
+
groupId: string | null;
|
|
23
|
+
email: string;
|
|
24
|
+
name: string;
|
|
25
|
+
typ: typeof TOKEN_TYPE;
|
|
26
|
+
jti: string;
|
|
27
|
+
exp: number;
|
|
28
|
+
iat: number;
|
|
29
|
+
}
|
|
30
|
+
export interface MintAccessTokenInput {
|
|
31
|
+
userId: string;
|
|
32
|
+
email: string;
|
|
33
|
+
name: string;
|
|
34
|
+
/** the canonical URI of the MCP server the token is for */
|
|
35
|
+
resource: string;
|
|
36
|
+
issuer: string;
|
|
37
|
+
scopes: ApiKeyScope[];
|
|
38
|
+
groupId: string | null;
|
|
39
|
+
}
|
|
40
|
+
export interface MintedAccessToken {
|
|
41
|
+
accessToken: string;
|
|
42
|
+
expiresIn: number;
|
|
43
|
+
scope: string;
|
|
44
|
+
}
|
|
45
|
+
export declare const mintAccessToken: (input: MintAccessTokenInput) => MintedAccessToken;
|
|
46
|
+
export interface AccessTokenVerification {
|
|
47
|
+
ok: boolean;
|
|
48
|
+
rejection?: 'malformed' | 'expired' | 'wrong-audience' | 'wrong-type' | 'bad-signature';
|
|
49
|
+
userId?: string;
|
|
50
|
+
email?: string;
|
|
51
|
+
name?: string;
|
|
52
|
+
scopes?: ApiKeyScope[];
|
|
53
|
+
groupId?: string | null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Check a bearer token presented to the MCP server.
|
|
57
|
+
*
|
|
58
|
+
* Two checks beyond the signature carry real weight. The audience must match
|
|
59
|
+
* this server: a token minted for one resource must not work against another,
|
|
60
|
+
* which is what RFC 8707 binding is for and what stops a token obtained for
|
|
61
|
+
* somewhere else being replayed here. And the type must be `mcp_access`, so an
|
|
62
|
+
* ordinary session JWT — same secret, same issuer, but no scopes and no tenant
|
|
63
|
+
* pin — cannot be presented as an access token and quietly get everything.
|
|
64
|
+
*/
|
|
65
|
+
export declare const verifyAccessToken: (token: string, expectedAudience: string) => AccessTokenVerification;
|
|
66
|
+
/** Parse a space-separated `scope` parameter, dropping anything we do not define. */
|
|
67
|
+
export declare const parseScopes: (scope: unknown) => ApiKeyScope[];
|
|
68
|
+
export {};
|
|
69
|
+
//# sourceMappingURL=tokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/oauth/tokens.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEnE;;;;;;;;;GASG;AAEH,oFAAoF;AACpF,QAAA,MAAM,UAAU,eAAe,CAAC;AAKhC,MAAM,WAAW,iBAAiB;IAChC,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,gFAAgF;IAChF,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,OAAO,UAAU,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,eAAe,GAAI,OAAO,oBAAoB,KAAG,iBAoB7D,CAAC;AAEF,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,CAAC,EAAE,WAAW,GAAG,SAAS,GAAG,gBAAgB,GAAG,YAAY,GAAG,eAAe,CAAC;IACxF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAC5B,OAAO,MAAM,EACb,kBAAkB,MAAM,KACvB,uBAqBF,CAAC;AAEF,qFAAqF;AACrF,eAAO,MAAM,WAAW,GAAI,OAAO,OAAO,KAAG,WAAW,EACmB,CAAC"}
|