@tumbaland/backend-core 1.37.0 → 1.38.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/apiKeys/ApiKey.d.ts +12 -3
- package/dist/apiKeys/ApiKey.d.ts.map +1 -1
- package/dist/apiKeys/ApiKey.js +5 -1
- package/dist/apiKeys/ApiKey.js.map +1 -1
- package/dist/apiKeys/index.d.ts +2 -2
- package/dist/apiKeys/index.d.ts.map +1 -1
- package/dist/apiKeys/index.js +5 -1
- package/dist/apiKeys/index.js.map +1 -1
- package/dist/apiKeys/middleware.d.ts +5 -3
- package/dist/apiKeys/middleware.d.ts.map +1 -1
- package/dist/apiKeys/middleware.js +56 -31
- package/dist/apiKeys/middleware.js.map +1 -1
- package/dist/apiKeys/service.d.ts +5 -3
- package/dist/apiKeys/service.d.ts.map +1 -1
- package/dist/apiKeys/service.js +10 -3
- package/dist/apiKeys/service.js.map +1 -1
- package/dist/apiKeys/types.d.ts +41 -8
- package/dist/apiKeys/types.d.ts.map +1 -1
- package/dist/apiKeys/types.js +35 -1
- package/dist/apiKeys/types.js.map +1 -1
- package/dist/oauth/models.d.ts +13 -2
- package/dist/oauth/models.d.ts.map +1 -1
- package/dist/oauth/models.js +9 -0
- package/dist/oauth/models.js.map +1 -1
- package/dist/oauth/service.d.ts +15 -8
- package/dist/oauth/service.d.ts.map +1 -1
- package/dist/oauth/service.js +10 -3
- package/dist/oauth/service.js.map +1 -1
- package/dist/oauth/tokens.d.ts +22 -5
- package/dist/oauth/tokens.d.ts.map +1 -1
- package/dist/oauth/tokens.js +5 -2
- package/dist/oauth/tokens.js.map +1 -1
- package/package.json +1 -1
- package/src/apiKeys/ApiKey.ts +16 -4
- package/src/apiKeys/index.ts +16 -2
- package/src/apiKeys/middleware.test.ts +103 -20
- package/src/apiKeys/middleware.ts +68 -35
- package/src/apiKeys/service.test.ts +56 -9
- package/src/apiKeys/service.ts +24 -6
- package/src/apiKeys/types.ts +61 -8
- package/src/middleware/authMiddleware.test.ts +4 -1
- package/src/oauth/models.ts +21 -2
- package/src/oauth/service.test.ts +58 -6
- package/src/oauth/service.ts +25 -7
- package/src/oauth/tokens.test.ts +20 -5
- package/src/oauth/tokens.ts +33 -7
package/dist/oauth/models.js
CHANGED
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.RefreshToken = exports.AuthorizationCode = exports.OAuthClient = void 0;
|
|
37
37
|
const mongoose_1 = __importStar(require("mongoose"));
|
|
38
|
+
const types_1 = require("../apiKeys/types");
|
|
38
39
|
const OAuthClientSchema = new mongoose_1.Schema({
|
|
39
40
|
clientId: { type: String, required: true, unique: true, index: true },
|
|
40
41
|
clientName: { type: String, required: true, maxlength: 200 },
|
|
@@ -51,6 +52,10 @@ const AuthorizationCodeSchema = new mongoose_1.Schema({
|
|
|
51
52
|
userName: { type: String, default: '' },
|
|
52
53
|
redirectUri: { type: String, required: true },
|
|
53
54
|
scopes: { type: [String], default: [] },
|
|
55
|
+
tenants: { type: [String], default: () => [types_1.PERSONAL_TENANT] },
|
|
56
|
+
defaultTenant: { type: String, default: types_1.PERSONAL_TENANT },
|
|
57
|
+
tenantNames: { type: mongoose_1.Schema.Types.Mixed, default: () => ({}) },
|
|
58
|
+
// Written by the single-tenant model this replaced; read-only now.
|
|
54
59
|
groupId: { type: String, default: null },
|
|
55
60
|
resource: { type: String, required: true },
|
|
56
61
|
codeChallenge: { type: String, required: true },
|
|
@@ -68,6 +73,10 @@ const RefreshTokenSchema = new mongoose_1.Schema({
|
|
|
68
73
|
clientId: { type: String, required: true },
|
|
69
74
|
userId: { type: String, required: true, index: true },
|
|
70
75
|
scopes: { type: [String], default: [] },
|
|
76
|
+
tenants: { type: [String], default: () => [types_1.PERSONAL_TENANT] },
|
|
77
|
+
defaultTenant: { type: String, default: types_1.PERSONAL_TENANT },
|
|
78
|
+
tenantNames: { type: mongoose_1.Schema.Types.Mixed, default: () => ({}) },
|
|
79
|
+
// Written by the single-tenant model this replaced; read-only now.
|
|
71
80
|
groupId: { type: String, default: null },
|
|
72
81
|
resource: { type: String, required: true },
|
|
73
82
|
grantedAt: { type: Date, default: Date.now },
|
package/dist/oauth/models.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/oauth/models.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qDAAsD;
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/oauth/models.ts"],"names":[],"mappings":"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"}
|
package/dist/oauth/service.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IAuthorizationCode } from './models';
|
|
2
|
-
import type
|
|
2
|
+
import { type ApiKeyScope, type ApiKeyTenant, type Tenant } from '../apiKeys/types';
|
|
3
3
|
export interface RegisterClientInput {
|
|
4
4
|
clientName: string;
|
|
5
5
|
redirectUris: string[];
|
|
@@ -54,7 +54,9 @@ export interface IssueCodeInput {
|
|
|
54
54
|
userName: string;
|
|
55
55
|
redirectUri: string;
|
|
56
56
|
scopes: ApiKeyScope[];
|
|
57
|
-
|
|
57
|
+
tenants: Tenant[];
|
|
58
|
+
defaultTenant: Tenant;
|
|
59
|
+
tenantNames: Record<Tenant, string>;
|
|
58
60
|
resource: string;
|
|
59
61
|
codeChallenge: string;
|
|
60
62
|
}
|
|
@@ -87,7 +89,9 @@ export declare const issueRefreshToken: (input: {
|
|
|
87
89
|
clientId: string;
|
|
88
90
|
userId: string;
|
|
89
91
|
scopes: ApiKeyScope[];
|
|
90
|
-
|
|
92
|
+
tenants: Tenant[];
|
|
93
|
+
defaultTenant: Tenant;
|
|
94
|
+
tenantNames: Record<Tenant, string>;
|
|
91
95
|
resource: string;
|
|
92
96
|
}) => Promise<IssuedRefreshToken>;
|
|
93
97
|
export interface RefreshRedemption {
|
|
@@ -96,7 +100,8 @@ export interface RefreshRedemption {
|
|
|
96
100
|
reused?: boolean;
|
|
97
101
|
userId?: string;
|
|
98
102
|
scopes?: ApiKeyScope[];
|
|
99
|
-
|
|
103
|
+
tenants?: ApiKeyTenant;
|
|
104
|
+
tenantNames?: Record<Tenant, string>;
|
|
100
105
|
resource?: string;
|
|
101
106
|
/** the replacement the client must store; the presented one is now dead */
|
|
102
107
|
rotatedToken?: string;
|
|
@@ -120,13 +125,15 @@ export declare const redeemRefreshToken: (token: string, clientId: string) => Pr
|
|
|
120
125
|
/** Cut off an assistant: without a refresh token it can obtain nothing new. */
|
|
121
126
|
export declare const revokeRefreshTokensForUser: (userId: string, clientId?: string) => Promise<number>;
|
|
122
127
|
export declare const listConnections: (userId: string) => Promise<{
|
|
128
|
+
tenantNames: Record<string, string>;
|
|
129
|
+
createdAt: string;
|
|
130
|
+
lastRenewedAt: string | null;
|
|
131
|
+
revokedAt: string | null;
|
|
132
|
+
tenants: string[];
|
|
133
|
+
defaultTenant: string;
|
|
123
134
|
id: string;
|
|
124
135
|
clientId: string;
|
|
125
136
|
clientName: string;
|
|
126
137
|
scopes: string[];
|
|
127
|
-
groupId: string | null;
|
|
128
|
-
createdAt: string;
|
|
129
|
-
lastRenewedAt: string | null;
|
|
130
|
-
revokedAt: string | null;
|
|
131
138
|
}[]>;
|
|
132
139
|
//# sourceMappingURL=service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/oauth/service.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,kBAAkB,EAA6B,MAAM,UAAU,CAAC;AAC5F,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/oauth/service.ts"],"names":[],"mappings":"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"}
|
package/dist/oauth/service.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.listConnections = exports.revokeRefreshTokensForUser = exports.redeemRefreshToken = exports.issueRefreshToken = exports.redeemAuthorizationCode = exports.issueAuthorizationCode = exports.isRegisteredRedirect = exports.findClient = exports.registerClient = void 0;
|
|
4
4
|
const crypto_1 = require("crypto");
|
|
5
5
|
const models_1 = require("./models");
|
|
6
|
+
const types_1 = require("../apiKeys/types");
|
|
6
7
|
/** How long a user has between approving and the client exchanging the code. */
|
|
7
8
|
const CODE_TTL_MS = 60 * 1000;
|
|
8
9
|
const sha256 = (value) => (0, crypto_1.createHash)('sha256').update(value).digest('hex');
|
|
@@ -143,7 +144,11 @@ const redeemRefreshToken = async (token, clientId) => {
|
|
|
143
144
|
clientId,
|
|
144
145
|
userId: record.userId,
|
|
145
146
|
scopes: record.scopes,
|
|
146
|
-
|
|
147
|
+
// Carried, not reset: rotation reissues the same grant, and re-deriving the
|
|
148
|
+
// tenants would quietly narrow a connection every time it refreshed.
|
|
149
|
+
tenants: record.tenants,
|
|
150
|
+
defaultTenant: record.defaultTenant,
|
|
151
|
+
tenantNames: record.tenantNames,
|
|
147
152
|
resource: record.resource,
|
|
148
153
|
// Carried, not reset: this is still the grant the user approved.
|
|
149
154
|
grantedAt: record.grantedAt ?? record.createdAt
|
|
@@ -152,7 +157,8 @@ const redeemRefreshToken = async (token, clientId) => {
|
|
|
152
157
|
ok: true,
|
|
153
158
|
userId: record.userId,
|
|
154
159
|
scopes: record.scopes,
|
|
155
|
-
|
|
160
|
+
tenants: (0, types_1.readTenants)(record),
|
|
161
|
+
tenantNames: record.tenantNames ?? {},
|
|
156
162
|
resource: record.resource,
|
|
157
163
|
rotatedToken: rotated
|
|
158
164
|
};
|
|
@@ -176,7 +182,8 @@ const listConnections = async (userId) => {
|
|
|
176
182
|
clientId: token.clientId,
|
|
177
183
|
clientName: nameById.get(token.clientId) ?? 'Unknown app',
|
|
178
184
|
scopes: token.scopes,
|
|
179
|
-
|
|
185
|
+
...(({ allowed, default: fallback }) => ({ tenants: allowed, defaultTenant: fallback }))((0, types_1.readTenants)(token)),
|
|
186
|
+
tenantNames: token.tenantNames ?? {},
|
|
180
187
|
createdAt: (token.grantedAt ?? token.createdAt).toISOString(),
|
|
181
188
|
// Set when the refresh token is exchanged, not when a tool runs — access
|
|
182
189
|
// tokens are validated statelessly, so the server never sees ordinary use.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/oauth/service.ts"],"names":[],"mappings":";;;AAAA,mCAAkE;AAClE,qCAA4F;
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/oauth/service.ts"],"names":[],"mappings":";;;AAAA,mCAAkE;AAClE,qCAA4F;AAC5F,4CAK0B;AAE1B,gFAAgF;AAChF,MAAM,WAAW,GAAG,EAAE,GAAG,IAAI,CAAC;AAE9B,MAAM,MAAM,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAE3F,4EAA4E;AAC5E,MAAM,IAAI,GAAG,CAAC,QAAgB,EAAU,EAAE,CACxC,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAE5D,MAAM,kBAAkB,GAAG,CAAC,CAAS,EAAE,CAAS,EAAW,EAAE;IAC3D,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7B,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC/C,OAAO,IAAA,wBAAe,EAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACtC,CAAC,CAAC;AAaF;;;;;;;;;;;;;GAaG;AACI,MAAM,cAAc,GAAG,KAAK,EAAE,KAA0B,EAA6B,EAAE;IAC5F,MAAM,YAAY,GAAG,CAAC,GAAG,KAAK,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC;IAEpD,MAAM,QAAQ,GAAG,MAAM,oBAAW,CAAC,OAAO,CAAC;QACzC,YAAY,EAAE,EAAE,KAAK,EAAE,YAAY,CAAC,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE;KACjE,CAAC,CAAC;IAEH,MAAM,MAAM,GACV,QAAQ;QACR,CAAC,MAAM,oBAAW,CAAC,MAAM,CAAC;YACxB,QAAQ,EAAE,cAAc,IAAA,oBAAW,EAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE;YACzD,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;YAC1C,YAAY;SACb,CAAC,CAAC,CAAC;IAEN,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,YAAY,EAAE,MAAM,CAAC,YAAY;KAClC,CAAC;AACJ,CAAC,CAAC;AApBW,QAAA,cAAc,kBAoBzB;AAEK,MAAM,UAAU,GAAG,CAAC,QAAgB,EAAE,EAAE,CAAC,oBAAW,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;AAArE,QAAA,UAAU,cAA2D;AAElF;;;;;;GAMG;AACI,MAAM,oBAAoB,GAAG,CAAC,MAAkC,EAAE,GAAW,EAAW,EAAE,CAC/F,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AADvB,QAAA,oBAAoB,wBACG;AAgB7B,MAAM,sBAAsB,GAAG,KAAK,EAAE,KAAqB,EAAmB,EAAE;IACrF,MAAM,IAAI,GAAG,IAAA,oBAAW,EAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAEnD,MAAM,0BAAiB,CAAC,MAAM,CAAC;QAC7B,GAAG,KAAK;QACR,IAAI;QACJ,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,WAAW,CAAC;KAC9C,CAAC,CAAC;IAEH,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAVW,QAAA,sBAAsB,0BAUjC;AAgBF;;;;;;GAMG;AACI,MAAM,uBAAuB,GAAG,KAAK,EAC1C,IAAY,EACZ,QAAgB,EAChB,WAAmB,EACnB,YAAoB,EACK,EAAE;IAC3B,MAAM,MAAM,GAAG,MAAM,0BAAiB,CAAC,gBAAgB,CACrD,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EACpC,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,IAAI,EAAE,EAAE,EAAE,EAChC,EAAE,GAAG,EAAE,IAAI,EAAE,CACd,CAAC;IAEF,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,yEAAyE;QACzE,gCAAgC;QAChC,MAAM,OAAO,GAAG,MAAM,0BAAiB,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QACzD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;IACxE,CAAC;IAED,IAAI,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;IACzF,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAC;IACrF,IAAI,MAAM,CAAC,WAAW,KAAK,WAAW;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,CAAC;IAC7F,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC;QAClE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC;IACjD,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AACpC,CAAC,CAAC;AA3BW,QAAA,uBAAuB,2BA2BlC;AAMF;;;;;;GAMG;AACI,MAAM,iBAAiB,GAAG,KAAK,EAAE,KAQvC,EAA+B,EAAE;IAChC,MAAM,qBAAY,CAAC,UAAU,CAC3B,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EACjF,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE,EAAE,CACpC,CAAC;IAEF,MAAM,KAAK,GAAG,IAAA,oBAAW,EAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACpD,MAAM,qBAAY,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClE,OAAO,EAAE,KAAK,EAAE,CAAC;AACnB,CAAC,CAAC;AAjBW,QAAA,iBAAiB,qBAiB5B;AAeF;;;;;;;;;;;;;;GAcG;AACI,MAAM,kBAAkB,GAAG,KAAK,EACrC,KAAa,EACb,QAAgB,EACY,EAAE;IAC9B,MAAM,MAAM,GAAG,MAAM,qBAAY,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;IAClF,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;IAElC,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QACrB,0EAA0E;QAC1E,uDAAuD;QACvD,MAAM,qBAAY,CAAC,UAAU,CAC3B,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAClE,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE,EAAE,CACpC,CAAC;QACF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACrC,CAAC;IAED,MAAM,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC;IAC9B,MAAM,CAAC,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC;IAC/B,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;IAEpB,MAAM,OAAO,GAAG,IAAA,oBAAW,EAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACtD,MAAM,qBAAY,CAAC,MAAM,CAAC;QACxB,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC;QAC1B,QAAQ;QACR,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,4EAA4E;QAC5E,qEAAqE;QACrE,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,iEAAiE;QACjE,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,SAAS;KAChD,CAAC,CAAC;IAEH,OAAO;QACL,EAAE,EAAE,IAAI;QACR,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM,EAAE,MAAM,CAAC,MAAuB;QACtC,OAAO,EAAE,IAAA,mBAAW,EAAC,MAAM,CAAC;QAC5B,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,EAAE;QACrC,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,YAAY,EAAE,OAAO;KACtB,CAAC;AACJ,CAAC,CAAC;AA9CW,QAAA,kBAAkB,sBA8C7B;AAEF,+EAA+E;AACxE,MAAM,0BAA0B,GAAG,KAAK,EAC7C,MAAc,EACd,QAAiB,EACA,EAAE;IACnB,MAAM,MAAM,GAA4B,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC;IAClF,IAAI,QAAQ;QAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAEzC,MAAM,MAAM,GAAG,MAAM,qBAAY,CAAC,UAAU,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAC1F,OAAO,MAAM,CAAC,aAAa,CAAC;AAC9B,CAAC,CAAC;AATW,QAAA,0BAA0B,8BASrC;AAEK,MAAM,eAAe,GAAG,KAAK,EAAE,MAAc,EAAE,EAAE;IACtD,MAAM,MAAM,GAAG,MAAM,qBAAY,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAC3E,MAAM,OAAO,GAAG,MAAM,oBAAW,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;IAC7F,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAExF,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5B,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;QACrB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,UAAU,EAAE,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,aAAa;QACzD,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,CAAC,CACtF,IAAA,mBAAW,EAAC,KAAK,CAAC,CACnB;QACD,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,EAAE;QACpC,SAAS,EAAE,CAAC,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE;QAC7D,yEAAyE;QACzE,2EAA2E;QAC3E,aAAa,EAAE,KAAK,CAAC,UAAU,EAAE,WAAW,EAAE,IAAI,IAAI;QACtD,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,WAAW,EAAE,IAAI,IAAI;KAClD,CAAC,CAAC,CAAC;AACN,CAAC,CAAC;AApBW,QAAA,eAAe,mBAoB1B"}
|
package/dist/oauth/tokens.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ApiKeyScope } from '../apiKeys/types';
|
|
1
|
+
import { type ApiKeyScope, type ApiKeyTenant, type Tenant } from '../apiKeys/types';
|
|
2
2
|
/**
|
|
3
3
|
* Access tokens for the OAuth flow that lets an assistant connect to Tumbaland.
|
|
4
4
|
*
|
|
@@ -18,8 +18,22 @@ export interface AccessTokenClaims {
|
|
|
18
18
|
aud: string;
|
|
19
19
|
iss: string;
|
|
20
20
|
scope: string;
|
|
21
|
-
/**
|
|
22
|
-
|
|
21
|
+
/** every tenant this token may act in */
|
|
22
|
+
tenants: Tenant[];
|
|
23
|
+
/** the one it acts in when a call names none */
|
|
24
|
+
defaultTenant: Tenant;
|
|
25
|
+
/**
|
|
26
|
+
* What each tenant is called, for a client that has to offer the choice.
|
|
27
|
+
*
|
|
28
|
+
* Carried on the token because the only place these names are known is the
|
|
29
|
+
* consent screen that rendered them, and the MCP server — which has to name
|
|
30
|
+
* the choice to a model — cannot reach group-service. A snapshot: a group
|
|
31
|
+
* renamed after the connection was made shows its old name until reconnect,
|
|
32
|
+
* which is the same trade the owner's name on an API key already makes.
|
|
33
|
+
*/
|
|
34
|
+
tenantNames: Record<Tenant, string>;
|
|
35
|
+
/** the single tenant an older token was pinned to; read by `readTenants` */
|
|
36
|
+
groupId?: string | null;
|
|
23
37
|
email: string;
|
|
24
38
|
name: string;
|
|
25
39
|
typ: typeof ACCESS_TOKEN_TYPE;
|
|
@@ -35,7 +49,9 @@ export interface MintAccessTokenInput {
|
|
|
35
49
|
resource: string;
|
|
36
50
|
issuer: string;
|
|
37
51
|
scopes: ApiKeyScope[];
|
|
38
|
-
|
|
52
|
+
tenants: Tenant[];
|
|
53
|
+
defaultTenant: Tenant;
|
|
54
|
+
tenantNames: Record<Tenant, string>;
|
|
39
55
|
}
|
|
40
56
|
export interface MintedAccessToken {
|
|
41
57
|
accessToken: string;
|
|
@@ -50,7 +66,8 @@ export interface AccessTokenVerification {
|
|
|
50
66
|
email?: string;
|
|
51
67
|
name?: string;
|
|
52
68
|
scopes?: ApiKeyScope[];
|
|
53
|
-
|
|
69
|
+
tenants?: ApiKeyTenant;
|
|
70
|
+
tenantNames?: Record<Tenant, string>;
|
|
54
71
|
}
|
|
55
72
|
/**
|
|
56
73
|
* Check a bearer token presented to the MCP server.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/oauth/tokens.ts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/oauth/tokens.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,MAAM,EACZ,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;GASG;AAEH,oFAAoF;AACpF,eAAO,MAAM,iBAAiB,eAAe,CAAC;AAK9C,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,yCAAyC;IACzC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,gDAAgD;IAChD,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;;;;OAQG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,OAAO,iBAAiB,CAAC;IAC9B,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,EAAE,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;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,iBAsB7D,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,YAAY,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAC5B,OAAO,MAAM,EACb,kBAAkB,MAAM,KACvB,uBAsBF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAAI,QAAQ,OAAO,KAAG,MAAM,IAAI,iBAGP,CAAC;AAE1D,qFAAqF;AACrF,eAAO,MAAM,WAAW,GAAI,OAAO,OAAO,KAAG,WAAW,EACmB,CAAC"}
|
package/dist/oauth/tokens.js
CHANGED
|
@@ -29,7 +29,9 @@ const mintAccessToken = (input) => {
|
|
|
29
29
|
aud: input.resource,
|
|
30
30
|
iss: input.issuer,
|
|
31
31
|
scope,
|
|
32
|
-
|
|
32
|
+
tenants: input.tenants,
|
|
33
|
+
defaultTenant: input.defaultTenant,
|
|
34
|
+
tenantNames: input.tenantNames,
|
|
33
35
|
email: input.email,
|
|
34
36
|
name: input.name,
|
|
35
37
|
typ: exports.ACCESS_TOKEN_TYPE,
|
|
@@ -69,7 +71,8 @@ const verifyAccessToken = (token, expectedAudience) => {
|
|
|
69
71
|
email: claims.email ?? '',
|
|
70
72
|
name: claims.name ?? '',
|
|
71
73
|
scopes: (claims.scope ?? '').split(' ').filter(types_1.isApiKeyScope),
|
|
72
|
-
|
|
74
|
+
tenants: (0, types_1.readTenants)(claims),
|
|
75
|
+
tenantNames: claims.tenantNames ?? {}
|
|
73
76
|
};
|
|
74
77
|
};
|
|
75
78
|
exports.verifyAccessToken = verifyAccessToken;
|
package/dist/oauth/tokens.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/oauth/tokens.ts"],"names":[],"mappings":";;;;;;AAAA,mCAAoC;AACpC,gEAA+B;AAC/B,uCAA2C;AAC3C,
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/oauth/tokens.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/apiKeys/ApiKey.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import mongoose, { Document, Schema } from 'mongoose';
|
|
2
|
-
import type
|
|
2
|
+
import { PERSONAL_TENANT, type ApiKeyScope, type Tenant } from './types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* One API key. Lives in backend-core rather than auth-service because both
|
|
@@ -31,8 +31,17 @@ export interface IApiKey extends Document {
|
|
|
31
31
|
sealedIv: string;
|
|
32
32
|
sealedTag: string;
|
|
33
33
|
scopes: ApiKeyScope[];
|
|
34
|
-
/**
|
|
35
|
-
|
|
34
|
+
/** every tenant this key may act in; see `Tenant` */
|
|
35
|
+
tenants: Tenant[];
|
|
36
|
+
/** the one it acts in when a request names none; always a member of `tenants` */
|
|
37
|
+
defaultTenant: Tenant;
|
|
38
|
+
/**
|
|
39
|
+
* The single tenant this key was pinned to, before tenants were a set.
|
|
40
|
+
*
|
|
41
|
+
* Kept so keys issued under the old model keep working; `readTenants` falls
|
|
42
|
+
* back to it. Nothing writes it any more.
|
|
43
|
+
*/
|
|
44
|
+
groupId?: string | null;
|
|
36
45
|
lastUsedAt?: Date;
|
|
37
46
|
expiresAt?: Date;
|
|
38
47
|
revokedAt?: Date;
|
|
@@ -55,8 +64,11 @@ const ApiKeySchema = new Schema<IApiKey>(
|
|
|
55
64
|
sealedIv: { type: String, required: true },
|
|
56
65
|
sealedTag: { type: String, required: true },
|
|
57
66
|
scopes: { type: [String], default: [] },
|
|
58
|
-
//
|
|
67
|
+
// Defaulted rather than optional: which data a key reaches is a decision the
|
|
59
68
|
// creator made, and it must not be indistinguishable from a field nobody set.
|
|
69
|
+
tenants: { type: [String], default: () => [PERSONAL_TENANT] },
|
|
70
|
+
defaultTenant: { type: String, default: PERSONAL_TENANT },
|
|
71
|
+
// Written by the single-tenant model this replaced; read-only now.
|
|
60
72
|
groupId: { type: String, default: null },
|
|
61
73
|
lastUsedAt: { type: Date },
|
|
62
74
|
expiresAt: { type: Date },
|
package/src/apiKeys/index.ts
CHANGED
|
@@ -11,8 +11,22 @@ export {
|
|
|
11
11
|
export type { CreateApiKeyInput, CreatedApiKey } from './service';
|
|
12
12
|
export { authenticateAgent, requireScope, denyApiKeys } from './middleware';
|
|
13
13
|
export type { ApiKeyContext } from './middleware';
|
|
14
|
-
export {
|
|
15
|
-
|
|
14
|
+
export {
|
|
15
|
+
API_KEY_SCOPES,
|
|
16
|
+
isApiKeyScope,
|
|
17
|
+
PERSONAL_TENANT,
|
|
18
|
+
groupIdOf,
|
|
19
|
+
groupIdsOf,
|
|
20
|
+
readTenants
|
|
21
|
+
} from './types';
|
|
22
|
+
export type {
|
|
23
|
+
ApiKeyScope,
|
|
24
|
+
ApiKeySummary,
|
|
25
|
+
ApiKeyVerification,
|
|
26
|
+
ApiKeyRejection,
|
|
27
|
+
ApiKeyTenant,
|
|
28
|
+
Tenant
|
|
29
|
+
} from './types';
|
|
16
30
|
export {
|
|
17
31
|
looksLikeApiKey,
|
|
18
32
|
displayPrefix,
|
|
@@ -9,6 +9,7 @@ jest.mock('../logging/logger', () => ({
|
|
|
9
9
|
import jwt from 'jsonwebtoken';
|
|
10
10
|
import { verifyApiKey } from './service';
|
|
11
11
|
import { mintAccessToken, type MintAccessTokenInput } from '../oauth/tokens';
|
|
12
|
+
import { PERSONAL_TENANT } from './types';
|
|
12
13
|
import { authenticateAgent, requireScope, denyApiKeys } from './middleware';
|
|
13
14
|
|
|
14
15
|
const mockedVerify = verifyApiKey as jest.Mock;
|
|
@@ -27,6 +28,9 @@ const req = (over: Partial<Request> = {}): Request =>
|
|
|
27
28
|
const withKey = (token = 'tmb_live_abcdef123456_secretsecretsecret', over: Partial<Request> = {}) =>
|
|
28
29
|
req({ headers: { authorization: `Bearer ${token}` }, ...over });
|
|
29
30
|
|
|
31
|
+
/** A grant of one or more tenants; the first is the default unless said otherwise. */
|
|
32
|
+
const grant = (...allowed: string[]) => ({ allowed, default: allowed[0] });
|
|
33
|
+
|
|
30
34
|
const okVerification = (over: Record<string, unknown> = {}) => ({
|
|
31
35
|
ok: true,
|
|
32
36
|
userId: 'u1',
|
|
@@ -34,7 +38,7 @@ const okVerification = (over: Record<string, unknown> = {}) => ({
|
|
|
34
38
|
userName: 'Tester',
|
|
35
39
|
keyId: 'k1',
|
|
36
40
|
scopes: ['relationship:read', 'relationship:write'],
|
|
37
|
-
|
|
41
|
+
tenants: grant(PERSONAL_TENANT),
|
|
38
42
|
...over
|
|
39
43
|
});
|
|
40
44
|
|
|
@@ -111,7 +115,8 @@ describe('authenticateAgent — API keys', () => {
|
|
|
111
115
|
expect(request.apiKey).toEqual({
|
|
112
116
|
keyId: 'k1',
|
|
113
117
|
scopes: ['relationship:read', 'relationship:write'],
|
|
114
|
-
|
|
118
|
+
tenants: grant(PERSONAL_TENANT),
|
|
119
|
+
actingAs: PERSONAL_TENANT
|
|
115
120
|
});
|
|
116
121
|
});
|
|
117
122
|
|
|
@@ -167,7 +172,7 @@ it('authenticates a key stored before the owner snapshot existed', async () => {
|
|
|
167
172
|
userId: 'u1',
|
|
168
173
|
keyId: 'k1',
|
|
169
174
|
scopes: ['relationship:read'],
|
|
170
|
-
|
|
175
|
+
tenants: grant(PERSONAL_TENANT)
|
|
171
176
|
});
|
|
172
177
|
const request = withKey();
|
|
173
178
|
const next = jest.fn();
|
|
@@ -179,7 +184,7 @@ it('authenticates a key stored before the owner snapshot existed', async () => {
|
|
|
179
184
|
});
|
|
180
185
|
|
|
181
186
|
it('treats a verification result with no scopes as granting nothing', async () => {
|
|
182
|
-
mockedVerify.mockResolvedValue({ ok: true, userId: 'u1', keyId: 'k1',
|
|
187
|
+
mockedVerify.mockResolvedValue({ ok: true, userId: 'u1', keyId: 'k1', tenants: grant(PERSONAL_TENANT) });
|
|
183
188
|
const res = mockRes();
|
|
184
189
|
const next = jest.fn();
|
|
185
190
|
|
|
@@ -189,9 +194,14 @@ it('treats a verification result with no scopes as granting nothing', async () =
|
|
|
189
194
|
expect(next).not.toHaveBeenCalled();
|
|
190
195
|
});
|
|
191
196
|
|
|
192
|
-
describe('authenticateAgent — tenant
|
|
197
|
+
describe('authenticateAgent — a single-tenant grant', () => {
|
|
198
|
+
/**
|
|
199
|
+
* The behaviour a pinned credential always had, kept intact now that a grant
|
|
200
|
+
* is a set. An agent given one tenant should never learn that a tenant is a
|
|
201
|
+
* thing it could name — nothing to choose, nothing to get wrong.
|
|
202
|
+
*/
|
|
193
203
|
it('grants a personal key no groups at all', async () => {
|
|
194
|
-
mockedVerify.mockResolvedValue(okVerification({
|
|
204
|
+
mockedVerify.mockResolvedValue(okVerification({ tenants: grant(PERSONAL_TENANT) }));
|
|
195
205
|
const request = withKey();
|
|
196
206
|
|
|
197
207
|
await authenticateAgent()(request, mockRes(), jest.fn());
|
|
@@ -200,7 +210,7 @@ describe('authenticateAgent — tenant pinning', () => {
|
|
|
200
210
|
});
|
|
201
211
|
|
|
202
212
|
it('grants a group key exactly its own group, not the owner’s whole membership', async () => {
|
|
203
|
-
mockedVerify.mockResolvedValue(okVerification({
|
|
213
|
+
mockedVerify.mockResolvedValue(okVerification({ tenants: grant('g1') }));
|
|
204
214
|
const request = withKey();
|
|
205
215
|
|
|
206
216
|
await authenticateAgent()(request, mockRes(), jest.fn());
|
|
@@ -208,8 +218,8 @@ describe('authenticateAgent — tenant pinning', () => {
|
|
|
208
218
|
expect(request.userGroups).toEqual(['g1']);
|
|
209
219
|
});
|
|
210
220
|
|
|
211
|
-
it('fills in the
|
|
212
|
-
mockedVerify.mockResolvedValue(okVerification({
|
|
221
|
+
it('fills in the group so the caller never has to know it', async () => {
|
|
222
|
+
mockedVerify.mockResolvedValue(okVerification({ tenants: grant('g1') }));
|
|
213
223
|
const request = withKey();
|
|
214
224
|
const next = jest.fn();
|
|
215
225
|
|
|
@@ -221,7 +231,7 @@ describe('authenticateAgent — tenant pinning', () => {
|
|
|
221
231
|
});
|
|
222
232
|
|
|
223
233
|
it('refuses a query that names a different group', async () => {
|
|
224
|
-
mockedVerify.mockResolvedValue(okVerification({
|
|
234
|
+
mockedVerify.mockResolvedValue(okVerification({ tenants: grant('g1') }));
|
|
225
235
|
const res = mockRes();
|
|
226
236
|
const next = jest.fn();
|
|
227
237
|
|
|
@@ -232,7 +242,7 @@ describe('authenticateAgent — tenant pinning', () => {
|
|
|
232
242
|
});
|
|
233
243
|
|
|
234
244
|
it('refuses a body that names a different group', async () => {
|
|
235
|
-
mockedVerify.mockResolvedValue(okVerification({
|
|
245
|
+
mockedVerify.mockResolvedValue(okVerification({ tenants: grant('g1') }));
|
|
236
246
|
const res = mockRes();
|
|
237
247
|
const next = jest.fn();
|
|
238
248
|
|
|
@@ -243,7 +253,7 @@ describe('authenticateAgent — tenant pinning', () => {
|
|
|
243
253
|
});
|
|
244
254
|
|
|
245
255
|
it('refuses a personal key that tries to reach into a group', async () => {
|
|
246
|
-
mockedVerify.mockResolvedValue(okVerification({
|
|
256
|
+
mockedVerify.mockResolvedValue(okVerification({ tenants: grant(PERSONAL_TENANT) }));
|
|
247
257
|
const res = mockRes();
|
|
248
258
|
const next = jest.fn();
|
|
249
259
|
|
|
@@ -253,8 +263,8 @@ describe('authenticateAgent — tenant pinning', () => {
|
|
|
253
263
|
expect(next).not.toHaveBeenCalled();
|
|
254
264
|
});
|
|
255
265
|
|
|
256
|
-
it('allows a request that names the
|
|
257
|
-
mockedVerify.mockResolvedValue(okVerification({
|
|
266
|
+
it('allows a request that names the granted group explicitly', async () => {
|
|
267
|
+
mockedVerify.mockResolvedValue(okVerification({ tenants: grant('g1') }));
|
|
258
268
|
const next = jest.fn();
|
|
259
269
|
|
|
260
270
|
await authenticateAgent()(withKey(undefined, { query: { groupId: 'g1' } }), mockRes(), next);
|
|
@@ -263,9 +273,76 @@ describe('authenticateAgent — tenant pinning', () => {
|
|
|
263
273
|
});
|
|
264
274
|
});
|
|
265
275
|
|
|
276
|
+
describe('authenticateAgent — a multi-tenant grant', () => {
|
|
277
|
+
/**
|
|
278
|
+
* What the set buys: one connection reaching a shared journal and private data
|
|
279
|
+
* without reconnecting. The boundary is unchanged — a tenant outside the grant
|
|
280
|
+
* is still refused — so these tests are about the choice *inside* it.
|
|
281
|
+
*/
|
|
282
|
+
const both = { allowed: [PERSONAL_TENANT, 'g1'], default: 'g1' };
|
|
283
|
+
|
|
284
|
+
it('acts in the default when the request names nothing', async () => {
|
|
285
|
+
mockedVerify.mockResolvedValue(okVerification({ tenants: both }));
|
|
286
|
+
const request = withKey();
|
|
287
|
+
|
|
288
|
+
await authenticateAgent()(request, mockRes(), jest.fn());
|
|
289
|
+
|
|
290
|
+
expect(request.query.groupId).toBe('g1');
|
|
291
|
+
expect(request.apiKey?.actingAs).toBe('g1');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('acts in another granted tenant when the request names it', async () => {
|
|
295
|
+
mockedVerify.mockResolvedValue(okVerification({ tenants: both }));
|
|
296
|
+
const request = withKey(undefined, { query: { groupId: PERSONAL_TENANT } });
|
|
297
|
+
|
|
298
|
+
await authenticateAgent()(request, mockRes(), jest.fn());
|
|
299
|
+
|
|
300
|
+
// `personal` is erased rather than passed on: handlers have always read the
|
|
301
|
+
// personal tenant as an absent groupId, and a literal would reach a query as
|
|
302
|
+
// a group that cannot exist.
|
|
303
|
+
expect(request.query.groupId).toBeUndefined();
|
|
304
|
+
expect(request.apiKey?.actingAs).toBe(PERSONAL_TENANT);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('erases a stale groupId from the body too, not only the query', async () => {
|
|
308
|
+
mockedVerify.mockResolvedValue(okVerification({ tenants: both }));
|
|
309
|
+
const request = withKey(undefined, { body: { groupId: PERSONAL_TENANT } });
|
|
310
|
+
|
|
311
|
+
await authenticateAgent()(request, mockRes(), jest.fn());
|
|
312
|
+
|
|
313
|
+
expect((request.body as Record<string, unknown>).groupId).toBeUndefined();
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('still refuses a tenant outside the grant', async () => {
|
|
317
|
+
mockedVerify.mockResolvedValue(okVerification({ tenants: both }));
|
|
318
|
+
const res = mockRes();
|
|
319
|
+
const next = jest.fn();
|
|
320
|
+
|
|
321
|
+
await authenticateAgent()(withKey(undefined, { query: { groupId: 'g2' } }), res, next);
|
|
322
|
+
|
|
323
|
+
expect(res.status).toHaveBeenCalledWith(403);
|
|
324
|
+
expect(next).not.toHaveBeenCalled();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('carries every granted group into userGroups, and only those', async () => {
|
|
328
|
+
mockedVerify.mockResolvedValue(
|
|
329
|
+
okVerification({ tenants: { allowed: [PERSONAL_TENANT, 'g1', 'g2'], default: 'g1' } })
|
|
330
|
+
);
|
|
331
|
+
const request = withKey();
|
|
332
|
+
|
|
333
|
+
await authenticateAgent()(request, mockRes(), jest.fn());
|
|
334
|
+
|
|
335
|
+
// The personal tenant is not a group and must not appear here, or it would
|
|
336
|
+
// reach a `groupId: { $in: ... }` clause as a group nobody belongs to.
|
|
337
|
+
expect(request.userGroups).toEqual(['g1', 'g2']);
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
266
341
|
describe('requireScope', () => {
|
|
267
342
|
const keyReq = (scopes: string[]) =>
|
|
268
|
-
req({
|
|
343
|
+
req({
|
|
344
|
+
apiKey: { keyId: 'k1', scopes, tenants: grant(PERSONAL_TENANT), actingAs: PERSONAL_TENANT }
|
|
345
|
+
} as Partial<Request>);
|
|
269
346
|
|
|
270
347
|
it('lets a session through unconditionally', () => {
|
|
271
348
|
// Scopes narrow what software may do on a person's behalf, not what the
|
|
@@ -343,7 +420,9 @@ describe('denyApiKeys', () => {
|
|
|
343
420
|
const next = jest.fn();
|
|
344
421
|
|
|
345
422
|
denyApiKeys(
|
|
346
|
-
req({
|
|
423
|
+
req({
|
|
424
|
+
apiKey: { keyId: 'k1', scopes: [], tenants: grant(PERSONAL_TENANT), actingAs: PERSONAL_TENANT }
|
|
425
|
+
}) as Request,
|
|
347
426
|
res,
|
|
348
427
|
next
|
|
349
428
|
);
|
|
@@ -369,7 +448,9 @@ describe('authenticateAgent — OAuth access tokens', () => {
|
|
|
369
448
|
resource: 'https://mcp.example.com',
|
|
370
449
|
issuer: 'https://auth.example.com',
|
|
371
450
|
scopes: ['relationship:read', 'relationship:write'],
|
|
372
|
-
|
|
451
|
+
tenants: [PERSONAL_TENANT],
|
|
452
|
+
defaultTenant: PERSONAL_TENANT,
|
|
453
|
+
tenantNames: { [PERSONAL_TENANT]: 'My own data' },
|
|
373
454
|
...over
|
|
374
455
|
}).accessToken;
|
|
375
456
|
|
|
@@ -418,8 +499,8 @@ describe('authenticateAgent — OAuth access tokens', () => {
|
|
|
418
499
|
expect(next).not.toHaveBeenCalled();
|
|
419
500
|
});
|
|
420
501
|
|
|
421
|
-
it('
|
|
422
|
-
const request = withToken(mint({
|
|
502
|
+
it('acts in the tenant chosen at consent', async () => {
|
|
503
|
+
const request = withToken(mint({ tenants: ['g1'], defaultTenant: 'g1' }));
|
|
423
504
|
const next = jest.fn();
|
|
424
505
|
|
|
425
506
|
await authenticateAgent()(request, mockRes(), next);
|
|
@@ -434,7 +515,9 @@ describe('authenticateAgent — OAuth access tokens', () => {
|
|
|
434
515
|
it('refuses a request naming a different tenant', async () => {
|
|
435
516
|
const res = mockRes();
|
|
436
517
|
const next = jest.fn();
|
|
437
|
-
const request = withToken(mint({
|
|
518
|
+
const request = withToken(mint({ tenants: ['g1'], defaultTenant: 'g1' }), {
|
|
519
|
+
query: { groupId: 'g2' }
|
|
520
|
+
});
|
|
438
521
|
|
|
439
522
|
await authenticateAgent()(request, res, next);
|
|
440
523
|
|