@tumbaland/backend-core 1.36.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 +9 -5
- package/dist/apiKeys/middleware.d.ts.map +1 -1
- package/dist/apiKeys/middleware.js +125 -59
- 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/middleware/authMiddleware.d.ts +7 -0
- package/dist/middleware/authMiddleware.d.ts.map +1 -1
- package/dist/middleware/authMiddleware.js +25 -4
- package/dist/middleware/authMiddleware.js.map +1 -1
- package/dist/oauth/index.d.ts +1 -1
- package/dist/oauth/index.d.ts.map +1 -1
- package/dist/oauth/index.js +3 -1
- package/dist/oauth/index.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 +34 -8
- package/dist/oauth/tokens.d.ts.map +1 -1
- package/dist/oauth/tokens.js +22 -6
- 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 +207 -16
- package/src/apiKeys/middleware.ts +159 -66
- 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 +52 -0
- package/src/middleware/authMiddleware.ts +26 -4
- package/src/oauth/index.ts +7 -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 +51 -11
package/dist/apiKeys/ApiKey.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import mongoose, { Document } from 'mongoose';
|
|
2
|
-
import type
|
|
2
|
+
import { type ApiKeyScope, type Tenant } from './types';
|
|
3
3
|
/**
|
|
4
4
|
* One API key. Lives in backend-core rather than auth-service because both
|
|
5
5
|
* halves need it: auth-service issues and revokes keys, and every other service
|
|
@@ -30,8 +30,17 @@ export interface IApiKey extends Document {
|
|
|
30
30
|
sealedIv: string;
|
|
31
31
|
sealedTag: string;
|
|
32
32
|
scopes: ApiKeyScope[];
|
|
33
|
-
/**
|
|
34
|
-
|
|
33
|
+
/** every tenant this key may act in; see `Tenant` */
|
|
34
|
+
tenants: Tenant[];
|
|
35
|
+
/** the one it acts in when a request names none; always a member of `tenants` */
|
|
36
|
+
defaultTenant: Tenant;
|
|
37
|
+
/**
|
|
38
|
+
* The single tenant this key was pinned to, before tenants were a set.
|
|
39
|
+
*
|
|
40
|
+
* Kept so keys issued under the old model keep working; `readTenants` falls
|
|
41
|
+
* back to it. Nothing writes it any more.
|
|
42
|
+
*/
|
|
43
|
+
groupId?: string | null;
|
|
35
44
|
lastUsedAt?: Date;
|
|
36
45
|
expiresAt?: Date;
|
|
37
46
|
revokedAt?: Date;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiKey.d.ts","sourceRoot":"","sources":["../../src/apiKeys/ApiKey.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,EAAE,EAAE,QAAQ,EAAU,MAAM,UAAU,CAAC;AACtD,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"ApiKey.d.ts","sourceRoot":"","sources":["../../src/apiKeys/ApiKey.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,EAAE,EAAE,QAAQ,EAAU,MAAM,UAAU,CAAC;AACtD,OAAO,EAAmB,KAAK,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,SAAS,CAAC;AAEzE;;;;;GAKG;AACH,MAAM,WAAW,OAAQ,SAAQ,QAAQ;IACvC,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;OAQG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,qDAAqD;IACrD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,iFAAiF;IACjF,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,CAAC,EAAE,IAAI,CAAC;IAClB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,uFAAuF;IACvF,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,IAAI,CAAC;IACtB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAgCD,eAAO,MAAM,MAAM;;;;;;gBAEgC,CAAC"}
|
package/dist/apiKeys/ApiKey.js
CHANGED
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.ApiKey = void 0;
|
|
37
37
|
const mongoose_1 = __importStar(require("mongoose"));
|
|
38
|
+
const types_1 = require("./types");
|
|
38
39
|
const ApiKeySchema = new mongoose_1.Schema({
|
|
39
40
|
userId: { type: String, required: true, index: true },
|
|
40
41
|
userEmail: { type: String, required: true },
|
|
@@ -46,8 +47,11 @@ const ApiKeySchema = new mongoose_1.Schema({
|
|
|
46
47
|
sealedIv: { type: String, required: true },
|
|
47
48
|
sealedTag: { type: String, required: true },
|
|
48
49
|
scopes: { type: [String], default: [] },
|
|
49
|
-
//
|
|
50
|
+
// Defaulted rather than optional: which data a key reaches is a decision the
|
|
50
51
|
// creator made, and it must not be indistinguishable from a field nobody set.
|
|
52
|
+
tenants: { type: [String], default: () => [types_1.PERSONAL_TENANT] },
|
|
53
|
+
defaultTenant: { type: String, default: types_1.PERSONAL_TENANT },
|
|
54
|
+
// Written by the single-tenant model this replaced; read-only now.
|
|
51
55
|
groupId: { type: String, default: null },
|
|
52
56
|
lastUsedAt: { type: Date },
|
|
53
57
|
expiresAt: { type: Date },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiKey.js","sourceRoot":"","sources":["../../src/apiKeys/ApiKey.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qDAAsD;
|
|
1
|
+
{"version":3,"file":"ApiKey.js","sourceRoot":"","sources":["../../src/apiKeys/ApiKey.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qDAAsD;AACtD,mCAAyE;AAqDzE,MAAM,YAAY,GAAG,IAAI,iBAAM,CAC7B;IACE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;IACrD,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;IAC3C,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;IACvD,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE;IACjE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;IAClE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;IACtC,gBAAgB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;IAClD,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;IAC1C,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;IAC3C,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE;IACvC,6EAA6E;IAC7E,8EAA8E;IAC9E,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,uBAAe,CAAC,EAAE;IAC7D,aAAa,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,uBAAe,EAAE;IACzD,mEAAmE;IACnE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;IACxC,UAAU,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;IAC1B,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;IACzB,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;IACzB,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE;IACzC,cAAc,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;CAC/B,EACD,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,CAC7C,CAAC;AAEF,kDAAkD;AAClD,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAEpC,QAAA,MAAM,GAAG,kBAAQ,CAAC,MAAM,CAAC,MAAM;IAC1C,CAAC,CAAE,kBAAQ,CAAC,MAAM,CAAC,MAAkC;IACrD,CAAC,CAAC,kBAAQ,CAAC,KAAK,CAAU,QAAQ,EAAE,YAAY,CAAC,CAAC"}
|
package/dist/apiKeys/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { createApiKey, listApiKeys, revealApiKey, revokeApiKey, deleteApiKey, ve
|
|
|
4
4
|
export type { CreateApiKeyInput, CreatedApiKey } from './service';
|
|
5
5
|
export { authenticateAgent, requireScope, denyApiKeys } from './middleware';
|
|
6
6
|
export type { ApiKeyContext } from './middleware';
|
|
7
|
-
export { API_KEY_SCOPES, isApiKeyScope } from './types';
|
|
8
|
-
export type { ApiKeyScope, ApiKeySummary, ApiKeyVerification, ApiKeyRejection } from './types';
|
|
7
|
+
export { API_KEY_SCOPES, isApiKeyScope, PERSONAL_TENANT, groupIdOf, groupIdsOf, readTenants } from './types';
|
|
8
|
+
export type { ApiKeyScope, ApiKeySummary, ApiKeyVerification, ApiKeyRejection, ApiKeyTenant, Tenant } from './types';
|
|
9
9
|
export { looksLikeApiKey, displayPrefix, isEncryptionConfigured, resetEncryptionKeyCache } from './crypto';
|
|
10
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/apiKeys/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EACL,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,YAAY,EACb,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC5E,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/apiKeys/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EACL,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,YAAY,EACb,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC5E,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EACL,cAAc,EACd,aAAa,EACb,eAAe,EACf,SAAS,EACT,UAAU,EACV,WAAW,EACZ,MAAM,SAAS,CAAC;AACjB,YAAY,EACV,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,MAAM,EACP,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,eAAe,EACf,aAAa,EACb,sBAAsB,EACtB,uBAAuB,EACxB,MAAM,UAAU,CAAC"}
|
package/dist/apiKeys/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resetEncryptionKeyCache = exports.isEncryptionConfigured = exports.displayPrefix = exports.looksLikeApiKey = exports.isApiKeyScope = exports.API_KEY_SCOPES = exports.denyApiKeys = exports.requireScope = exports.authenticateAgent = exports.verifyApiKey = exports.deleteApiKey = exports.revokeApiKey = exports.revealApiKey = exports.listApiKeys = exports.createApiKey = exports.ApiKey = void 0;
|
|
3
|
+
exports.resetEncryptionKeyCache = exports.isEncryptionConfigured = exports.displayPrefix = exports.looksLikeApiKey = exports.readTenants = exports.groupIdsOf = exports.groupIdOf = exports.PERSONAL_TENANT = exports.isApiKeyScope = exports.API_KEY_SCOPES = exports.denyApiKeys = exports.requireScope = exports.authenticateAgent = exports.verifyApiKey = exports.deleteApiKey = exports.revokeApiKey = exports.revealApiKey = exports.listApiKeys = exports.createApiKey = exports.ApiKey = void 0;
|
|
4
4
|
var ApiKey_1 = require("./ApiKey");
|
|
5
5
|
Object.defineProperty(exports, "ApiKey", { enumerable: true, get: function () { return ApiKey_1.ApiKey; } });
|
|
6
6
|
var service_1 = require("./service");
|
|
@@ -17,6 +17,10 @@ Object.defineProperty(exports, "denyApiKeys", { enumerable: true, get: function
|
|
|
17
17
|
var types_1 = require("./types");
|
|
18
18
|
Object.defineProperty(exports, "API_KEY_SCOPES", { enumerable: true, get: function () { return types_1.API_KEY_SCOPES; } });
|
|
19
19
|
Object.defineProperty(exports, "isApiKeyScope", { enumerable: true, get: function () { return types_1.isApiKeyScope; } });
|
|
20
|
+
Object.defineProperty(exports, "PERSONAL_TENANT", { enumerable: true, get: function () { return types_1.PERSONAL_TENANT; } });
|
|
21
|
+
Object.defineProperty(exports, "groupIdOf", { enumerable: true, get: function () { return types_1.groupIdOf; } });
|
|
22
|
+
Object.defineProperty(exports, "groupIdsOf", { enumerable: true, get: function () { return types_1.groupIdsOf; } });
|
|
23
|
+
Object.defineProperty(exports, "readTenants", { enumerable: true, get: function () { return types_1.readTenants; } });
|
|
20
24
|
var crypto_1 = require("./crypto");
|
|
21
25
|
Object.defineProperty(exports, "looksLikeApiKey", { enumerable: true, get: function () { return crypto_1.looksLikeApiKey; } });
|
|
22
26
|
Object.defineProperty(exports, "displayPrefix", { enumerable: true, get: function () { return crypto_1.displayPrefix; } });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/apiKeys/index.ts"],"names":[],"mappings":";;;AAAA,mCAAkC;AAAzB,gGAAA,MAAM,OAAA;AAEf,qCAOmB;AANjB,uGAAA,YAAY,OAAA;AACZ,sGAAA,WAAW,OAAA;AACX,uGAAA,YAAY,OAAA;AACZ,uGAAA,YAAY,OAAA;AACZ,uGAAA,YAAY,OAAA;AACZ,uGAAA,YAAY,OAAA;AAGd,2CAA4E;AAAnE,+GAAA,iBAAiB,OAAA;AAAE,0GAAA,YAAY,OAAA;AAAE,yGAAA,WAAW,OAAA;AAErD,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/apiKeys/index.ts"],"names":[],"mappings":";;;AAAA,mCAAkC;AAAzB,gGAAA,MAAM,OAAA;AAEf,qCAOmB;AANjB,uGAAA,YAAY,OAAA;AACZ,sGAAA,WAAW,OAAA;AACX,uGAAA,YAAY,OAAA;AACZ,uGAAA,YAAY,OAAA;AACZ,uGAAA,YAAY,OAAA;AACZ,uGAAA,YAAY,OAAA;AAGd,2CAA4E;AAAnE,+GAAA,iBAAiB,OAAA;AAAE,0GAAA,YAAY,OAAA;AAAE,yGAAA,WAAW,OAAA;AAErD,iCAOiB;AANf,uGAAA,cAAc,OAAA;AACd,sGAAA,aAAa,OAAA;AACb,wGAAA,eAAe,OAAA;AACf,kGAAA,SAAS,OAAA;AACT,mGAAA,UAAU,OAAA;AACV,oGAAA,WAAW,OAAA;AAUb,mCAKkB;AAJhB,yGAAA,eAAe,OAAA;AACf,uGAAA,aAAa,OAAA;AACb,gHAAA,sBAAsB,OAAA;AACtB,iHAAA,uBAAuB,OAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RequestHandler } from 'express';
|
|
2
|
-
import type
|
|
2
|
+
import { type ApiKeyScope, type ApiKeyTenant, type Tenant } from './types';
|
|
3
3
|
/**
|
|
4
4
|
* Request-scoped facts about the key a request arrived on. Absent on ordinary
|
|
5
5
|
* session requests, which is itself the signal a handler needs: `req.apiKey`
|
|
@@ -8,8 +8,10 @@ import type { ApiKeyScope } from './types';
|
|
|
8
8
|
export interface ApiKeyContext {
|
|
9
9
|
keyId: string;
|
|
10
10
|
scopes: ApiKeyScope[];
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/** every tenant this credential may act in, and the one it acts in by default */
|
|
12
|
+
tenants: ApiKeyTenant;
|
|
13
|
+
/** the tenant this particular request resolved to */
|
|
14
|
+
actingAs: Tenant;
|
|
13
15
|
}
|
|
14
16
|
declare global {
|
|
15
17
|
namespace Express {
|
|
@@ -40,11 +42,13 @@ export declare const authenticateAgent: (...requiredScopes: ApiKeyScope[]) => Re
|
|
|
40
42
|
* door for lacking a scope half the router never uses.
|
|
41
43
|
*
|
|
42
44
|
* A session passes unconditionally: scopes narrow what software may do on a
|
|
43
|
-
* person's behalf, not what the person may do themselves.
|
|
45
|
+
* person's behalf, not what the person may do themselves. Everything that is
|
|
46
|
+
* not a session — an API key or an OAuth access token alike — arrives with
|
|
47
|
+
* `req.apiKey` set and is held to it.
|
|
44
48
|
*/
|
|
45
49
|
export declare const requireScope: (...requiredScopes: ApiKeyScope[]) => RequestHandler;
|
|
46
50
|
/**
|
|
47
|
-
* Refuse
|
|
51
|
+
* Refuse every non-session credential on a route that a session may still use.
|
|
48
52
|
*
|
|
49
53
|
* For the handful of operations that should stay a person's to perform — key
|
|
50
54
|
* management itself, most obviously, since a key that can mint keys is a key
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/apiKeys/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,cAAc,EAAY,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/apiKeys/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,cAAc,EAAY,MAAM,SAAS,CAAC;AAQ1E,OAAO,EAKL,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,MAAM,EACZ,MAAM,SAAS,CAAC;AAEjB;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,iFAAiF;IACjF,OAAO,EAAE,YAAY,CAAC;IACtB,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO,CAAC;QAChB,UAAU,OAAO;YACf,MAAM,CAAC,EAAE,aAAa,CAAC;SACxB;KACF;CACF;AAuID;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,GAAI,GAAG,gBAAgB,WAAW,EAAE,KAAG,cA+DlE,CAAC;AAEJ;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,GAAI,GAAG,gBAAgB,WAAW,EAAE,KAAG,cAiB7D,CAAC;AAEJ;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,EAAE,cASzB,CAAC"}
|
|
@@ -7,8 +7,10 @@ exports.denyApiKeys = exports.requireScope = exports.authenticateAgent = void 0;
|
|
|
7
7
|
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
8
8
|
const env_1 = require("../config/env");
|
|
9
9
|
const logger_1 = __importDefault(require("../logging/logger"));
|
|
10
|
+
const tokens_1 = require("../oauth/tokens");
|
|
10
11
|
const crypto_1 = require("./crypto");
|
|
11
12
|
const service_1 = require("./service");
|
|
13
|
+
const types_1 = require("./types");
|
|
12
14
|
/** Both auth paths read the token from the same two places. */
|
|
13
15
|
const extractToken = (req) => req.cookies?.access_token || req.headers.authorization?.replace('Bearer ', '');
|
|
14
16
|
const unauthorized = (res) => {
|
|
@@ -18,10 +20,12 @@ const unauthorized = (res) => {
|
|
|
18
20
|
* The tenant a request is asking to act in, wherever it named one.
|
|
19
21
|
*
|
|
20
22
|
* Handlers read `groupId` from either the query string or the body depending on
|
|
21
|
-
* the verb, so both are checked — a
|
|
22
|
-
* no
|
|
23
|
+
* the verb, so both are checked — a rule that only covered one of them would be
|
|
24
|
+
* no rule at all. A caller names the personal tenant with the literal
|
|
25
|
+
* `personal`, since "no group" is what the resolved default gets written into
|
|
26
|
+
* and would otherwise be indistinguishable from not having asked.
|
|
23
27
|
*/
|
|
24
|
-
const
|
|
28
|
+
const requestedTenant = (req) => {
|
|
25
29
|
const fromQuery = req.query?.groupId;
|
|
26
30
|
if (typeof fromQuery === 'string' && fromQuery.length > 0)
|
|
27
31
|
return fromQuery;
|
|
@@ -30,34 +34,90 @@ const requestedGroupId = (req) => {
|
|
|
30
34
|
return fromBody;
|
|
31
35
|
return undefined;
|
|
32
36
|
};
|
|
37
|
+
/** Express 5 makes `req.query` a getter, so it is redefined rather than assigned. */
|
|
38
|
+
const setQueryGroupId = (req, groupId) => {
|
|
39
|
+
const query = { ...req.query };
|
|
40
|
+
if (groupId === undefined)
|
|
41
|
+
delete query.groupId;
|
|
42
|
+
else
|
|
43
|
+
query.groupId = groupId;
|
|
44
|
+
Object.defineProperty(req, 'query', {
|
|
45
|
+
value: query,
|
|
46
|
+
writable: true,
|
|
47
|
+
configurable: true,
|
|
48
|
+
enumerable: true
|
|
49
|
+
});
|
|
50
|
+
};
|
|
33
51
|
/**
|
|
34
|
-
* Hold a
|
|
52
|
+
* Hold a credential to the tenants it was granted, and settle which one this
|
|
53
|
+
* request is acting in.
|
|
54
|
+
*
|
|
55
|
+
* A request naming a tenant outside the grant is refused outright — that
|
|
56
|
+
* boundary is the whole reason a credential is safe to hand to an agent, and it
|
|
57
|
+
* is unchanged by the grant being a set rather than one. What the set adds is a
|
|
58
|
+
* choice *inside* it, which is what lets one connection reach a shared journal
|
|
59
|
+
* and a private photo library without reconnecting.
|
|
35
60
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* none has the pinned tenant written in for it, so the agent never has to know a
|
|
40
|
-
* group id exists, and a handler's `if (groupId) ... else personal` branch lands
|
|
41
|
-
* where the key's owner intended.
|
|
61
|
+
* A request that names nothing gets the default written in for it, so an agent
|
|
62
|
+
* granted a single tenant never has to know a group id exists and a handler's
|
|
63
|
+
* `if (groupId) … else personal` branch lands where the owner intended.
|
|
42
64
|
*/
|
|
43
|
-
const
|
|
44
|
-
const requested =
|
|
45
|
-
if (requested
|
|
46
|
-
return
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
65
|
+
const applyTenantGrant = (req, tenants) => {
|
|
66
|
+
const requested = requestedTenant(req) ?? tenants.default;
|
|
67
|
+
if (!tenants.allowed.includes(requested))
|
|
68
|
+
return null;
|
|
69
|
+
// Resolved either way, including when the caller asked for exactly what the
|
|
70
|
+
// default already was: handlers read the personal tenant as an absent
|
|
71
|
+
// `groupId`, so `personal` has to be erased rather than passed through.
|
|
72
|
+
const groupId = requested === types_1.PERSONAL_TENANT ? undefined : requested;
|
|
73
|
+
setQueryGroupId(req, groupId);
|
|
74
|
+
if (req.body && typeof req.body === 'object') {
|
|
75
|
+
if (groupId === undefined)
|
|
76
|
+
delete req.body.groupId;
|
|
77
|
+
else
|
|
56
78
|
req.body.groupId = groupId;
|
|
57
|
-
}
|
|
58
79
|
}
|
|
59
|
-
return
|
|
80
|
+
return requested;
|
|
60
81
|
};
|
|
82
|
+
/**
|
|
83
|
+
* Admit software acting for a user, on the terms its credential carries.
|
|
84
|
+
*
|
|
85
|
+
* Shared by both non-session credentials on purpose. An API key and an OAuth
|
|
86
|
+
* access token differ entirely in how they are issued and verified, and not at
|
|
87
|
+
* all in what they mean once they are: a user, a set of scopes, and one tenant.
|
|
88
|
+
* Settling that in one place is what keeps the two from drifting into subtly
|
|
89
|
+
* different amounts of access.
|
|
90
|
+
*/
|
|
91
|
+
function admitAgent(req, res, next, requiredScopes, credential) {
|
|
92
|
+
const missing = requiredScopes.filter((scope) => !credential.scopes.includes(scope));
|
|
93
|
+
if (missing.length > 0) {
|
|
94
|
+
res.status(403).json({
|
|
95
|
+
success: false,
|
|
96
|
+
message: `Credential is missing required scope: ${missing.join(', ')}`
|
|
97
|
+
});
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const actingAs = applyTenantGrant(req, credential.tenants);
|
|
101
|
+
if (actingAs === null) {
|
|
102
|
+
res.status(403).json({
|
|
103
|
+
success: false,
|
|
104
|
+
message: 'Credential is not permitted to act in the requested group'
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
req.user = { id: credential.userId, email: credential.email, name: credential.name };
|
|
109
|
+
// Only the granted groups, never the owner's full membership: this is what
|
|
110
|
+
// stops a credential reaching a group it was not issued for through any
|
|
111
|
+
// handler that consults `userGroups` instead of the `groupId` parameter.
|
|
112
|
+
req.userGroups = (0, types_1.groupIdsOf)(credential.tenants.allowed);
|
|
113
|
+
req.apiKey = {
|
|
114
|
+
keyId: credential.credentialId,
|
|
115
|
+
scopes: credential.scopes,
|
|
116
|
+
tenants: credential.tenants,
|
|
117
|
+
actingAs
|
|
118
|
+
};
|
|
119
|
+
next();
|
|
120
|
+
}
|
|
61
121
|
/**
|
|
62
122
|
* Authenticate a request that software may legitimately be making.
|
|
63
123
|
*
|
|
@@ -77,15 +137,39 @@ const authenticateAgent = (...requiredScopes) => async function authenticateAgen
|
|
|
77
137
|
return;
|
|
78
138
|
}
|
|
79
139
|
if (!(0, crypto_1.looksLikeApiKey)(token)) {
|
|
140
|
+
let claims;
|
|
80
141
|
try {
|
|
81
|
-
|
|
82
|
-
req.user = user;
|
|
83
|
-
req.userGroups = user.groups ?? [];
|
|
84
|
-
next();
|
|
142
|
+
claims = jsonwebtoken_1.default.verify(token, (0, env_1.requireEnv)('JWT_SECRET'));
|
|
85
143
|
}
|
|
86
144
|
catch {
|
|
87
145
|
unauthorized(res);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (!(0, tokens_1.isAccessTokenClaims)(claims)) {
|
|
149
|
+
const user = claims;
|
|
150
|
+
req.user = user;
|
|
151
|
+
req.userGroups = user.groups ?? [];
|
|
152
|
+
next();
|
|
153
|
+
return;
|
|
88
154
|
}
|
|
155
|
+
// An OAuth access token: signed with the same secret as a session and
|
|
156
|
+
// arriving in the same header, but nothing like one. It names its user in
|
|
157
|
+
// `sub`, carries scopes, and is pinned to a tenant — so it is admitted
|
|
158
|
+
// the way a key is, not the way a person is.
|
|
159
|
+
//
|
|
160
|
+
// Reading it as a session was the original bug and it failed quietly: the
|
|
161
|
+
// claims have no `id` and no `groups`, so `req.user.id` arrived as
|
|
162
|
+
// `undefined`, scoped writes were refused as inaccessible, and a read
|
|
163
|
+
// whose scope collapsed to nothing fell through to "no user context" and
|
|
164
|
+
// answered from the whole collection.
|
|
165
|
+
admitAgent(req, res, next, requiredScopes, {
|
|
166
|
+
credentialId: claims.jti,
|
|
167
|
+
userId: claims.sub,
|
|
168
|
+
email: claims.email ?? '',
|
|
169
|
+
name: claims.name ?? '',
|
|
170
|
+
scopes: (claims.scope ?? '').split(' ').filter(types_1.isApiKeyScope),
|
|
171
|
+
tenants: (0, types_1.readTenants)(claims)
|
|
172
|
+
});
|
|
89
173
|
return;
|
|
90
174
|
}
|
|
91
175
|
const result = await (0, service_1.verifyApiKey)(token);
|
|
@@ -96,34 +180,14 @@ const authenticateAgent = (...requiredScopes) => async function authenticateAgen
|
|
|
96
180
|
unauthorized(res);
|
|
97
181
|
return;
|
|
98
182
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
res.status(403).json({
|
|
103
|
-
success: false,
|
|
104
|
-
message: `API key is missing required scope: ${missing.join(', ')}`
|
|
105
|
-
});
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
const groupId = result.groupId ?? null;
|
|
109
|
-
if (!applyTenantPin(req, groupId)) {
|
|
110
|
-
res.status(403).json({
|
|
111
|
-
success: false,
|
|
112
|
-
message: 'API key is not permitted to act in the requested group'
|
|
113
|
-
});
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
req.user = {
|
|
117
|
-
id: result.userId,
|
|
183
|
+
admitAgent(req, res, next, requiredScopes, {
|
|
184
|
+
credentialId: result.keyId,
|
|
185
|
+
userId: result.userId,
|
|
118
186
|
email: result.userEmail ?? '',
|
|
119
|
-
name: result.userName ?? ''
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// that consults `userGroups` instead of the `groupId` parameter.
|
|
124
|
-
req.userGroups = groupId ? [groupId] : [];
|
|
125
|
-
req.apiKey = { keyId: result.keyId, scopes, groupId };
|
|
126
|
-
next();
|
|
187
|
+
name: result.userName ?? '',
|
|
188
|
+
scopes: result.scopes ?? [],
|
|
189
|
+
tenants: result.tenants ?? (0, types_1.readTenants)({})
|
|
190
|
+
});
|
|
127
191
|
};
|
|
128
192
|
exports.authenticateAgent = authenticateAgent;
|
|
129
193
|
/**
|
|
@@ -135,7 +199,9 @@ exports.authenticateAgent = authenticateAgent;
|
|
|
135
199
|
* door for lacking a scope half the router never uses.
|
|
136
200
|
*
|
|
137
201
|
* A session passes unconditionally: scopes narrow what software may do on a
|
|
138
|
-
* person's behalf, not what the person may do themselves.
|
|
202
|
+
* person's behalf, not what the person may do themselves. Everything that is
|
|
203
|
+
* not a session — an API key or an OAuth access token alike — arrives with
|
|
204
|
+
* `req.apiKey` set and is held to it.
|
|
139
205
|
*/
|
|
140
206
|
const requireScope = (...requiredScopes) => function requireScopeHandler(req, res, next) {
|
|
141
207
|
if (!req.apiKey) {
|
|
@@ -146,7 +212,7 @@ const requireScope = (...requiredScopes) => function requireScopeHandler(req, re
|
|
|
146
212
|
if (missing.length > 0) {
|
|
147
213
|
res.status(403).json({
|
|
148
214
|
success: false,
|
|
149
|
-
message: `
|
|
215
|
+
message: `Credential is missing required scope: ${missing.join(', ')}`
|
|
150
216
|
});
|
|
151
217
|
return;
|
|
152
218
|
}
|
|
@@ -154,7 +220,7 @@ const requireScope = (...requiredScopes) => function requireScopeHandler(req, re
|
|
|
154
220
|
};
|
|
155
221
|
exports.requireScope = requireScope;
|
|
156
222
|
/**
|
|
157
|
-
* Refuse
|
|
223
|
+
* Refuse every non-session credential on a route that a session may still use.
|
|
158
224
|
*
|
|
159
225
|
* For the handful of operations that should stay a person's to perform — key
|
|
160
226
|
* management itself, most obviously, since a key that can mint keys is a key
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/apiKeys/middleware.ts"],"names":[],"mappings":";;;;;;AACA,gEAA+B;AAC/B,uCAA2C;AAC3C,+DAAuC;
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/apiKeys/middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ApiKeyScope, type ApiKeySummary, type ApiKeyVerification, type Tenant } from './types';
|
|
2
2
|
export interface CreateApiKeyInput {
|
|
3
3
|
userId: string;
|
|
4
4
|
userEmail: string;
|
|
5
5
|
userName: string;
|
|
6
6
|
name: string;
|
|
7
7
|
scopes: ApiKeyScope[];
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/** every tenant the key may act in; defaults to the user's own data alone */
|
|
9
|
+
tenants?: Tenant[];
|
|
10
|
+
/** which of them it acts in when a request names none */
|
|
11
|
+
defaultTenant?: Tenant;
|
|
10
12
|
expiresAt?: Date | null;
|
|
11
13
|
}
|
|
12
14
|
export interface CreatedApiKey {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/apiKeys/service.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/apiKeys/service.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,MAAM,EACZ,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,aAAa,CAAC;IACvB,+EAA+E;IAC/E,KAAK,EAAE,MAAM,CAAC;CACf;AAgBD,eAAO,MAAM,YAAY,GAAU,OAAO,iBAAiB,KAAG,OAAO,CAAC,aAAa,CA2BlF,CAAC;AAEF,eAAO,MAAM,WAAW,GAAU,QAAQ,MAAM,KAAG,OAAO,CAAC,aAAa,EAAE,CAGzE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GAAU,QAAQ,MAAM,EAAE,IAAI,MAAM,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAepF,CAAC;AAEF,+EAA+E;AAC/E,eAAO,MAAM,YAAY,GAAU,QAAQ,MAAM,EAAE,IAAI,MAAM,KAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAU3F,CAAC;AAEF,eAAO,MAAM,YAAY,GAAU,QAAQ,MAAM,EAAE,IAAI,MAAM,KAAG,OAAO,CAAC,OAAO,CAG9E,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,GAAU,OAAO,OAAO,KAAG,OAAO,CAAC,kBAAkB,CA2B7E,CAAC"}
|
package/dist/apiKeys/service.js
CHANGED
|
@@ -3,18 +3,22 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.verifyApiKey = exports.deleteApiKey = exports.revokeApiKey = exports.revealApiKey = exports.listApiKeys = exports.createApiKey = void 0;
|
|
4
4
|
const ApiKey_1 = require("./ApiKey");
|
|
5
5
|
const crypto_1 = require("./crypto");
|
|
6
|
+
const types_1 = require("./types");
|
|
6
7
|
const toSummary = (key) => ({
|
|
7
8
|
id: String(key._id),
|
|
8
9
|
name: key.name,
|
|
9
10
|
prefix: (0, crypto_1.displayPrefix)(key.keyId),
|
|
10
11
|
scopes: key.scopes,
|
|
11
|
-
|
|
12
|
+
...(({ allowed, default: fallback }) => ({ tenants: allowed, defaultTenant: fallback }))((0, types_1.readTenants)(key)),
|
|
12
13
|
createdAt: key.createdAt.toISOString(),
|
|
13
14
|
lastUsedAt: key.lastUsedAt?.toISOString() ?? null,
|
|
14
15
|
expiresAt: key.expiresAt?.toISOString() ?? null,
|
|
15
16
|
revokedAt: key.revokedAt?.toISOString() ?? null
|
|
16
17
|
});
|
|
17
18
|
const createApiKey = async (input) => {
|
|
19
|
+
// A key that reaches nowhere would authenticate and then fail every call, so
|
|
20
|
+
// an empty grant is read as the safest non-empty one rather than stored.
|
|
21
|
+
const tenants = input.tenants?.length ? input.tenants : [types_1.PERSONAL_TENANT];
|
|
18
22
|
const { token, id, hash } = (0, crypto_1.generateKey)();
|
|
19
23
|
const sealed = (0, crypto_1.encryptSecret)(token);
|
|
20
24
|
const key = await ApiKey_1.ApiKey.create({
|
|
@@ -28,7 +32,10 @@ const createApiKey = async (input) => {
|
|
|
28
32
|
sealedIv: sealed.iv,
|
|
29
33
|
sealedTag: sealed.tag,
|
|
30
34
|
scopes: input.scopes,
|
|
31
|
-
|
|
35
|
+
tenants,
|
|
36
|
+
defaultTenant: tenants.includes(input.defaultTenant ?? '')
|
|
37
|
+
? input.defaultTenant
|
|
38
|
+
: tenants[0],
|
|
32
39
|
expiresAt: input.expiresAt ?? undefined
|
|
33
40
|
});
|
|
34
41
|
return { summary: toSummary(key), token };
|
|
@@ -115,7 +122,7 @@ const verifyApiKey = async (token) => {
|
|
|
115
122
|
userName: key.userName,
|
|
116
123
|
keyId: String(key._id),
|
|
117
124
|
scopes: key.scopes,
|
|
118
|
-
|
|
125
|
+
tenants: (0, types_1.readTenants)(key)
|
|
119
126
|
};
|
|
120
127
|
};
|
|
121
128
|
exports.verifyApiKey = verifyApiKey;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/apiKeys/service.ts"],"names":[],"mappings":";;;AAAA,qCAA2C;AAC3C,qCAOkB;
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/apiKeys/service.ts"],"names":[],"mappings":"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"}
|
package/dist/apiKeys/types.d.ts
CHANGED
|
@@ -12,16 +12,48 @@ export declare const API_KEY_SCOPES: readonly ["relationship:read", "relationshi
|
|
|
12
12
|
export type ApiKeyScope = (typeof API_KEY_SCOPES)[number];
|
|
13
13
|
export declare const isApiKeyScope: (value: unknown) => value is ApiKeyScope;
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* A tenant a credential may act in: a group id, or the user's own data.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* Personal is a named sentinel rather than `null` so that a tenant is always a
|
|
18
|
+
* plain string — storable in an array, sendable in a JWT claim, comparable
|
|
19
|
+
* without a special case at every layer. It cannot collide with a real tenant:
|
|
20
|
+
* group ids are 24-character hex.
|
|
21
|
+
*/
|
|
22
|
+
export declare const PERSONAL_TENANT = "personal";
|
|
23
|
+
export type Tenant = string;
|
|
24
|
+
/** The `groupId` a tenant corresponds to, as handlers have always read it. */
|
|
25
|
+
export declare const groupIdOf: (tenant: Tenant) => string | null;
|
|
26
|
+
/**
|
|
27
|
+
* Which tenants a credential may act in, and which one it acts in by default.
|
|
28
|
+
*
|
|
29
|
+
* The web app picks a tenant per request from a selector; an agent has no such
|
|
30
|
+
* UI. So the *set* is chosen once, by a person, and the credential cannot escape
|
|
31
|
+
* it — but within that set the caller may name one per request, which is what
|
|
32
|
+
* lets a single connection reach both a shared journal and a private photo
|
|
33
|
+
* library without reconnecting.
|
|
34
|
+
*
|
|
35
|
+
* `allowed` is never empty and always contains `default`. A credential granted
|
|
36
|
+
* exactly one tenant behaves precisely as a pinned one did: nothing to name,
|
|
37
|
+
* nothing to get wrong.
|
|
21
38
|
*/
|
|
22
39
|
export interface ApiKeyTenant {
|
|
23
|
-
|
|
40
|
+
allowed: Tenant[];
|
|
41
|
+
default: Tenant;
|
|
24
42
|
}
|
|
43
|
+
/** Just the groups, for the `req.userGroups` every handler already reads. */
|
|
44
|
+
export declare const groupIdsOf: (tenants: Tenant[]) => string[];
|
|
45
|
+
/**
|
|
46
|
+
* Read a stored tenant grant, tolerating one written before tenants were a set.
|
|
47
|
+
*
|
|
48
|
+
* Keys and grants issued under the old single-tenant model carry `groupId`
|
|
49
|
+
* alone. Falling back to it here means they keep working across the deploy
|
|
50
|
+
* rather than every connected assistant breaking at once.
|
|
51
|
+
*/
|
|
52
|
+
export declare const readTenants: (stored: {
|
|
53
|
+
tenants?: Tenant[] | null;
|
|
54
|
+
defaultTenant?: Tenant | null;
|
|
55
|
+
groupId?: string | null;
|
|
56
|
+
}) => ApiKeyTenant;
|
|
25
57
|
/** A key as the API hands it back — never including the secret. */
|
|
26
58
|
export interface ApiKeySummary {
|
|
27
59
|
id: string;
|
|
@@ -29,7 +61,8 @@ export interface ApiKeySummary {
|
|
|
29
61
|
/** the public half, shown in listings so a key is identifiable at a glance */
|
|
30
62
|
prefix: string;
|
|
31
63
|
scopes: ApiKeyScope[];
|
|
32
|
-
|
|
64
|
+
tenants: Tenant[];
|
|
65
|
+
defaultTenant: Tenant;
|
|
33
66
|
createdAt: string;
|
|
34
67
|
lastUsedAt: string | null;
|
|
35
68
|
expiresAt: string | null;
|
|
@@ -45,6 +78,6 @@ export interface ApiKeyVerification {
|
|
|
45
78
|
userName?: string;
|
|
46
79
|
keyId?: string;
|
|
47
80
|
scopes?: ApiKeyScope[];
|
|
48
|
-
|
|
81
|
+
tenants?: ApiKeyTenant;
|
|
49
82
|
}
|
|
50
83
|
//# sourceMappingURL=types.d.ts.map
|