@tumbaland/backend-core 1.49.0 → 1.51.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/activity/UserDay.d.ts +42 -0
- package/dist/activity/UserDay.d.ts.map +1 -0
- package/dist/activity/UserDay.js +30 -0
- package/dist/activity/UserDay.js.map +1 -0
- package/dist/activity/index.d.ts +7 -0
- package/dist/activity/index.d.ts.map +1 -0
- package/dist/activity/index.js +13 -0
- package/dist/activity/index.js.map +1 -0
- package/dist/activity/modules.d.ts +19 -0
- package/dist/activity/modules.d.ts.map +1 -0
- package/dist/activity/modules.js +31 -0
- package/dist/activity/modules.js.map +1 -0
- package/dist/activity/recordActivity.d.ts +28 -0
- package/dist/activity/recordActivity.d.ts.map +1 -0
- package/dist/activity/recordActivity.js +51 -0
- package/dist/activity/recordActivity.js.map +1 -0
- package/dist/audit/AuditEvent.d.ts.map +1 -1
- package/dist/audit/AuditEvent.js +3 -0
- package/dist/audit/AuditEvent.js.map +1 -1
- package/dist/groups/access.d.ts +13 -0
- package/dist/groups/access.d.ts.map +1 -0
- package/dist/groups/access.js +22 -0
- package/dist/groups/access.js.map +1 -0
- package/dist/groups/index.d.ts +1 -0
- package/dist/groups/index.d.ts.map +1 -1
- package/dist/groups/index.js +4 -1
- package/dist/groups/index.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/middleware/requireAdmin.d.ts +16 -0
- package/dist/middleware/requireAdmin.d.ts.map +1 -0
- package/dist/middleware/requireAdmin.js +25 -0
- package/dist/middleware/requireAdmin.js.map +1 -0
- package/dist/testing/openapi.d.ts +34 -0
- package/dist/testing/openapi.d.ts.map +1 -0
- package/dist/testing/openapi.js +110 -0
- package/dist/testing/openapi.js.map +1 -0
- package/dist/testing/serviceTestSetup.js +3 -0
- package/dist/testing/serviceTestSetup.js.map +1 -1
- package/package.json +4 -2
- package/testing.d.ts +1 -0
- package/testing.js +3 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Document, Model } from 'mongoose';
|
|
2
|
+
import type { ActivityModule, ActivityPlatform } from './modules';
|
|
3
|
+
/**
|
|
4
|
+
* One person's use of the app on one UTC day.
|
|
5
|
+
*
|
|
6
|
+
* A counter, not a log: a visit is one `$inc` on a document that already
|
|
7
|
+
* exists, so the collection grows by one small row per active person per day
|
|
8
|
+
* and never by the number of clicks. That is what makes keeping it forever
|
|
9
|
+
* reasonable — there is no TTL, unlike `audit_events`, because it holds no
|
|
10
|
+
* content, only which days someone came and which pages they opened.
|
|
11
|
+
*
|
|
12
|
+
* Days are UTC, like the usage meters, so "today" means the same thing in every
|
|
13
|
+
* service and does not depend on where the server happens to run.
|
|
14
|
+
*/
|
|
15
|
+
export interface IUserDay extends Document {
|
|
16
|
+
userId: string;
|
|
17
|
+
/** `YYYY-MM-DD`, UTC. */
|
|
18
|
+
day: string;
|
|
19
|
+
/** Times the app was opened or came back after a long absence (see shell-front's activity hook). */
|
|
20
|
+
visits: number;
|
|
21
|
+
/** Opens per page, keyed by `ActivityModule`. */
|
|
22
|
+
modules: Partial<Record<ActivityModule, number>>;
|
|
23
|
+
/** Visits per kind of device. */
|
|
24
|
+
platform: Partial<Record<ActivityPlatform, number>>;
|
|
25
|
+
/** Cloudflare's CF-IPCountry at the last visit that day. */
|
|
26
|
+
country?: string;
|
|
27
|
+
firstAt: Date;
|
|
28
|
+
lastAt: Date;
|
|
29
|
+
/**
|
|
30
|
+
* Written by the one-off backfill from the audit trail, not by a visit.
|
|
31
|
+
* Such a day knows only that the person wrote something, so it undercounts,
|
|
32
|
+
* and the admin screens say so wherever they show one.
|
|
33
|
+
*/
|
|
34
|
+
backfilled?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Registered through `models` first: several services import this module into
|
|
38
|
+
* the same mongoose singleton during tests, and a second `model()` call with
|
|
39
|
+
* the same name throws `OverwriteModelError`.
|
|
40
|
+
*/
|
|
41
|
+
export declare const UserDay: Model<IUserDay>;
|
|
42
|
+
//# sourceMappingURL=UserDay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UserDay.d.ts","sourceRoot":"","sources":["../../src/activity/UserDay.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,QAAQ,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAElE;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,QAAS,SAAQ,QAAQ;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,oGAAoG;IACpG,MAAM,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;IACjD,iCAAiC;IACjC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,IAAI,CAAC;IACd,MAAM,EAAE,IAAI,CAAC;IACb;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AA0BD;;;;GAIG;AACH,eAAO,MAAM,OAAO,EAAE,KAAK,CAAC,QAAQ,CAC8C,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UserDay = void 0;
|
|
4
|
+
const mongoose_1 = require("mongoose");
|
|
5
|
+
const UserDaySchema = new mongoose_1.Schema({
|
|
6
|
+
userId: { type: String, required: true },
|
|
7
|
+
day: { type: String, required: true },
|
|
8
|
+
visits: { type: Number, default: 0 },
|
|
9
|
+
modules: { type: mongoose_1.Schema.Types.Mixed, default: undefined },
|
|
10
|
+
platform: {
|
|
11
|
+
web: { type: Number },
|
|
12
|
+
pwa: { type: Number },
|
|
13
|
+
ios: { type: Number },
|
|
14
|
+
android: { type: Number }
|
|
15
|
+
},
|
|
16
|
+
country: { type: String, maxlength: 2 },
|
|
17
|
+
firstAt: { type: Date, required: true },
|
|
18
|
+
lastAt: { type: Date, required: true },
|
|
19
|
+
backfilled: { type: Boolean }
|
|
20
|
+
}, { collection: 'user_days', minimize: true });
|
|
21
|
+
// A person's days, and one day across everybody ("who was here on the 3rd").
|
|
22
|
+
UserDaySchema.index({ userId: 1, day: 1 }, { unique: true });
|
|
23
|
+
UserDaySchema.index({ day: 1 });
|
|
24
|
+
/**
|
|
25
|
+
* Registered through `models` first: several services import this module into
|
|
26
|
+
* the same mongoose singleton during tests, and a second `model()` call with
|
|
27
|
+
* the same name throws `OverwriteModelError`.
|
|
28
|
+
*/
|
|
29
|
+
exports.UserDay = mongoose_1.models.UserDay || (0, mongoose_1.model)('UserDay', UserDaySchema);
|
|
30
|
+
//# sourceMappingURL=UserDay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UserDay.js","sourceRoot":"","sources":["../../src/activity/UserDay.ts"],"names":[],"mappings":";;;AAAA,uCAAkE;AAqClE,MAAM,aAAa,GAAG,IAAI,iBAAM,CAC9B;IACE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;IACxC,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;IACrC,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE;IACpC,OAAO,EAAE,EAAE,IAAI,EAAE,iBAAM,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE;IACzD,QAAQ,EAAE;QACR,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;QACrB,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;QACrB,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;QACrB,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;KAC1B;IACD,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE;IACvC,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE;IACvC,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE;IACtC,UAAU,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;CAC9B,EACD,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,CAC5C,CAAC;AAEF,6EAA6E;AAC7E,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AAC7D,aAAa,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;AAEhC;;;;GAIG;AACU,QAAA,OAAO,GACjB,iBAAM,CAAC,OAA2B,IAAI,IAAA,gBAAK,EAAW,SAAS,EAAE,aAAa,CAAC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { ACTIVITY_MODULES, ACTIVITY_PLATFORMS, isActivityModule } from './modules';
|
|
2
|
+
export type { ActivityModule, ActivityPlatform } from './modules';
|
|
3
|
+
export { UserDay } from './UserDay';
|
|
4
|
+
export type { IUserDay } from './UserDay';
|
|
5
|
+
export { recordActivity, dayOf } from './recordActivity';
|
|
6
|
+
export type { ActivityInput } from './recordActivity';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/activity/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AACnF,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAClE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dayOf = exports.recordActivity = exports.UserDay = exports.isActivityModule = exports.ACTIVITY_PLATFORMS = exports.ACTIVITY_MODULES = void 0;
|
|
4
|
+
var modules_1 = require("./modules");
|
|
5
|
+
Object.defineProperty(exports, "ACTIVITY_MODULES", { enumerable: true, get: function () { return modules_1.ACTIVITY_MODULES; } });
|
|
6
|
+
Object.defineProperty(exports, "ACTIVITY_PLATFORMS", { enumerable: true, get: function () { return modules_1.ACTIVITY_PLATFORMS; } });
|
|
7
|
+
Object.defineProperty(exports, "isActivityModule", { enumerable: true, get: function () { return modules_1.isActivityModule; } });
|
|
8
|
+
var UserDay_1 = require("./UserDay");
|
|
9
|
+
Object.defineProperty(exports, "UserDay", { enumerable: true, get: function () { return UserDay_1.UserDay; } });
|
|
10
|
+
var recordActivity_1 = require("./recordActivity");
|
|
11
|
+
Object.defineProperty(exports, "recordActivity", { enumerable: true, get: function () { return recordActivity_1.recordActivity; } });
|
|
12
|
+
Object.defineProperty(exports, "dayOf", { enumerable: true, get: function () { return recordActivity_1.dayOf; } });
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/activity/index.ts"],"names":[],"mappings":";;;AAAA,qCAAmF;AAA1E,2GAAA,gBAAgB,OAAA;AAAE,6GAAA,kBAAkB,OAAA;AAAE,2GAAA,gBAAgB,OAAA;AAE/D,qCAAoC;AAA3B,kGAAA,OAAO,OAAA;AAEhB,mDAAyD;AAAhD,gHAAA,cAAc,OAAA;AAAE,uGAAA,KAAK,OAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pages whose opening is counted, named by the route segment shell-front
|
|
3
|
+
* mounts them at (`/relationship` is the Journal, `/finance` is Money).
|
|
4
|
+
*
|
|
5
|
+
* The route, not the display name, because routes are what stay put: the
|
|
6
|
+
* Journal has been called "relationship" in every URL and service since before
|
|
7
|
+
* it was renamed, and a counter keyed by a label would split in two the day a
|
|
8
|
+
* label changed. The admin screens translate these back to names.
|
|
9
|
+
*
|
|
10
|
+
* A fixed list rather than any string a client sends, so a bug or a hostile
|
|
11
|
+
* browser cannot grow a day's document one invented key at a time.
|
|
12
|
+
*/
|
|
13
|
+
export declare const ACTIVITY_MODULES: readonly ["home", "relationship", "album", "finance", "chat", "account", "groups", "payments", "admin"];
|
|
14
|
+
export type ActivityModule = (typeof ACTIVITY_MODULES)[number];
|
|
15
|
+
/** Where the app was running: a browser tab, an installed PWA, or a native wrapper (see shell-front's platform.ts). */
|
|
16
|
+
export declare const ACTIVITY_PLATFORMS: readonly ["web", "pwa", "ios", "android"];
|
|
17
|
+
export type ActivityPlatform = (typeof ACTIVITY_PLATFORMS)[number];
|
|
18
|
+
export declare const isActivityModule: (value: unknown) => value is ActivityModule;
|
|
19
|
+
//# sourceMappingURL=modules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../../src/activity/modules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,yGAUnB,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D,uHAAuH;AACvH,eAAO,MAAM,kBAAkB,2CAA4C,CAAC;AAE5E,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnE,eAAO,MAAM,gBAAgB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,cAC2B,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isActivityModule = exports.ACTIVITY_PLATFORMS = exports.ACTIVITY_MODULES = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The pages whose opening is counted, named by the route segment shell-front
|
|
6
|
+
* mounts them at (`/relationship` is the Journal, `/finance` is Money).
|
|
7
|
+
*
|
|
8
|
+
* The route, not the display name, because routes are what stay put: the
|
|
9
|
+
* Journal has been called "relationship" in every URL and service since before
|
|
10
|
+
* it was renamed, and a counter keyed by a label would split in two the day a
|
|
11
|
+
* label changed. The admin screens translate these back to names.
|
|
12
|
+
*
|
|
13
|
+
* A fixed list rather than any string a client sends, so a bug or a hostile
|
|
14
|
+
* browser cannot grow a day's document one invented key at a time.
|
|
15
|
+
*/
|
|
16
|
+
exports.ACTIVITY_MODULES = [
|
|
17
|
+
'home',
|
|
18
|
+
'relationship',
|
|
19
|
+
'album',
|
|
20
|
+
'finance',
|
|
21
|
+
'chat',
|
|
22
|
+
'account',
|
|
23
|
+
'groups',
|
|
24
|
+
'payments',
|
|
25
|
+
'admin'
|
|
26
|
+
];
|
|
27
|
+
/** Where the app was running: a browser tab, an installed PWA, or a native wrapper (see shell-front's platform.ts). */
|
|
28
|
+
exports.ACTIVITY_PLATFORMS = ['web', 'pwa', 'ios', 'android'];
|
|
29
|
+
const isActivityModule = (value) => typeof value === 'string' && exports.ACTIVITY_MODULES.includes(value);
|
|
30
|
+
exports.isActivityModule = isActivityModule;
|
|
31
|
+
//# sourceMappingURL=modules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modules.js","sourceRoot":"","sources":["../../src/activity/modules.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;GAWG;AACU,QAAA,gBAAgB,GAAG;IAC9B,MAAM;IACN,cAAc;IACd,OAAO;IACP,SAAS;IACT,MAAM;IACN,SAAS;IACT,QAAQ;IACR,UAAU;IACV,OAAO;CACC,CAAC;AAIX,uHAAuH;AAC1G,QAAA,kBAAkB,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,CAAU,CAAC;AAIrE,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAA2B,EAAE,CAC1E,OAAO,KAAK,KAAK,QAAQ,IAAK,wBAAsC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAD1E,QAAA,gBAAgB,oBAC0D"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ActivityModule, type ActivityPlatform } from './modules';
|
|
2
|
+
export interface ActivityInput {
|
|
3
|
+
userId: string;
|
|
4
|
+
/** `visit`: the app was opened, or came back after a long absence. `open`: a page was opened. */
|
|
5
|
+
kind: 'visit' | 'open';
|
|
6
|
+
/** Required for `open`. */
|
|
7
|
+
module?: ActivityModule;
|
|
8
|
+
platform: ActivityPlatform;
|
|
9
|
+
country?: string;
|
|
10
|
+
/** For tests. */
|
|
11
|
+
now?: Date;
|
|
12
|
+
}
|
|
13
|
+
/** The UTC day a moment falls on, as it is stored. */
|
|
14
|
+
export declare const dayOf: (at: Date) => string;
|
|
15
|
+
/**
|
|
16
|
+
* Counts one visit or one page open against the person's day.
|
|
17
|
+
*
|
|
18
|
+
* A single upsert, so the first event of a day creates it and every later one
|
|
19
|
+
* only increments, with no read in between for two tabs to race on.
|
|
20
|
+
*
|
|
21
|
+
* Never throws, for the reason the audit writes never do: a counter that can
|
|
22
|
+
* fail the request it describes is worse than no counter. A failure is logged
|
|
23
|
+
* and the day is simply undercounted.
|
|
24
|
+
*
|
|
25
|
+
* Returns whether it was recorded, which only the endpoint's tests care about.
|
|
26
|
+
*/
|
|
27
|
+
export declare function recordActivity(input: ActivityInput): Promise<boolean>;
|
|
28
|
+
//# sourceMappingURL=recordActivity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recordActivity.d.ts","sourceRoot":"","sources":["../../src/activity/recordActivity.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,KAAK,cAAc,EAAE,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAEzF,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,iGAAiG;IACjG,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IACvB,2BAA2B;IAC3B,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB;IACjB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,sDAAsD;AACtD,eAAO,MAAM,KAAK,GAAI,IAAI,IAAI,KAAG,MAAuC,CAAC;AAEzE;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CA4B3E"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.dayOf = void 0;
|
|
7
|
+
exports.recordActivity = recordActivity;
|
|
8
|
+
const logger_1 = __importDefault(require("../logging/logger"));
|
|
9
|
+
const UserDay_1 = require("./UserDay");
|
|
10
|
+
const modules_1 = require("./modules");
|
|
11
|
+
/** The UTC day a moment falls on, as it is stored. */
|
|
12
|
+
const dayOf = (at) => at.toISOString().slice(0, 10);
|
|
13
|
+
exports.dayOf = dayOf;
|
|
14
|
+
/**
|
|
15
|
+
* Counts one visit or one page open against the person's day.
|
|
16
|
+
*
|
|
17
|
+
* A single upsert, so the first event of a day creates it and every later one
|
|
18
|
+
* only increments, with no read in between for two tabs to race on.
|
|
19
|
+
*
|
|
20
|
+
* Never throws, for the reason the audit writes never do: a counter that can
|
|
21
|
+
* fail the request it describes is worse than no counter. A failure is logged
|
|
22
|
+
* and the day is simply undercounted.
|
|
23
|
+
*
|
|
24
|
+
* Returns whether it was recorded, which only the endpoint's tests care about.
|
|
25
|
+
*/
|
|
26
|
+
async function recordActivity(input) {
|
|
27
|
+
try {
|
|
28
|
+
if (!input.userId)
|
|
29
|
+
return false;
|
|
30
|
+
if (input.kind === 'open' && !(0, modules_1.isActivityModule)(input.module))
|
|
31
|
+
return false;
|
|
32
|
+
const now = input.now ?? new Date();
|
|
33
|
+
// A page open is not another device, so only a visit adds to the platform split.
|
|
34
|
+
const inc = input.kind === 'visit' ? { visits: 1, [`platform.${input.platform}`]: 1 } : { [`modules.${input.module}`]: 1 };
|
|
35
|
+
await UserDay_1.UserDay.updateOne({ userId: input.userId, day: (0, exports.dayOf)(now) }, {
|
|
36
|
+
$inc: inc,
|
|
37
|
+
$min: { firstAt: now },
|
|
38
|
+
$max: { lastAt: now },
|
|
39
|
+
...(input.country ? { $set: { country: input.country } } : {}),
|
|
40
|
+
// A day the backfill wrote is a guess; once someone is seen that day it
|
|
41
|
+
// is a counted one, and should no longer be drawn as an undercount.
|
|
42
|
+
...(input.kind === 'visit' ? { $unset: { backfilled: '' } } : {})
|
|
43
|
+
}, { upsert: true });
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
logger_1.default.warn('Could not record activity', { error: error?.message, kind: input.kind });
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=recordActivity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recordActivity.js","sourceRoot":"","sources":["../../src/activity/recordActivity.ts"],"names":[],"mappings":";;;;;;AA+BA,wCA4BC;AA3DD,+DAAuC;AACvC,uCAAoC;AACpC,uCAAyF;AAczF,sDAAsD;AAC/C,MAAM,KAAK,GAAG,CAAC,EAAQ,EAAU,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAA5D,QAAA,KAAK,SAAuD;AAEzE;;;;;;;;;;;GAWG;AACI,KAAK,UAAU,cAAc,CAAC,KAAoB;IACvD,IAAI,CAAC;QACH,IAAI,CAAC,KAAK,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAChC,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,IAAA,0BAAgB,EAAC,KAAK,CAAC,MAAM,CAAC;YAAE,OAAO,KAAK,CAAC;QAE3E,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;QACpC,iFAAiF;QACjF,MAAM,GAAG,GACP,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,YAAY,KAAK,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;QAEjH,MAAM,iBAAO,CAAC,SAAS,CACrB,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,IAAA,aAAK,EAAC,GAAG,CAAC,EAAE,EACzC;YACE,IAAI,EAAE,GAAG;YACT,IAAI,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE;YACtB,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE;YACrB,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9D,wEAAwE;YACxE,oEAAoE;YACpE,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClE,EACD,EAAE,MAAM,EAAE,IAAI,EAAE,CACjB,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,gBAAM,CAAC,IAAI,CAAC,2BAA2B,EAAE,EAAE,KAAK,EAAG,KAAe,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QACjG,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuditEvent.d.ts","sourceRoot":"","sources":["../../src/audit/AuditEvent.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,EAAE,EAAE,QAAQ,EAAU,MAAM,UAAU,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC;;;;;;;GAOG;AACH;;;;;;;;;;GAUG;AACH,MAAM,WAAW,QAAQ;IACvB,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,EAAE,EAAE,MAAM,CAAC;IACX,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,WAAY,SAAQ,QAAQ;IAC3C,EAAE,EAAE,IAAI,CAAC;IACT,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;IACzD,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,SAAS,CAAC;IACrB,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACzD,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"AuditEvent.d.ts","sourceRoot":"","sources":["../../src/audit/AuditEvent.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,EAAE,EAAE,QAAQ,EAAU,MAAM,UAAU,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC;;;;;;;GAOG;AACH;;;;;;;;;;GAUG;AACH,MAAM,WAAW,QAAQ;IACvB,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,EAAE,EAAE,MAAM,CAAC;IACX,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,WAAY,SAAQ,QAAQ;IAC3C,EAAE,EAAE,IAAI,CAAC;IACT,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;IACzD,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,SAAS,CAAC;IACrB,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACzD,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAoDD,eAAO,MAAM,UAAU;;;;;;;;;;EAEwC,CAAC"}
|
package/dist/audit/AuditEvent.js
CHANGED
|
@@ -60,6 +60,9 @@ const AuditEventSchema = new mongoose_1.Schema({
|
|
|
60
60
|
}, { timestamps: { createdAt: true, updatedAt: false }, collection: 'audit_events' });
|
|
61
61
|
// Every listing is one account's trail, newest first.
|
|
62
62
|
AuditEventSchema.index({ userId: 1, at: -1 });
|
|
63
|
+
// The admin screens ask across every account — "all assistant calls this month" —
|
|
64
|
+
// which the per-user index above cannot serve.
|
|
65
|
+
AuditEventSchema.index({ action: 1, at: -1 });
|
|
63
66
|
/**
|
|
64
67
|
* Rows expire on their own.
|
|
65
68
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuditEvent.js","sourceRoot":"","sources":["../../src/audit/AuditEvent.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qDAAsD;AA0EtD,MAAM,cAAc,GAAG,IAAI,iBAAM,CAC/B;IACE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;IACtC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;IACpC,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;IACvC,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;IACxC,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;CAC1B,EACD,EAAE,GAAG,EAAE,KAAK,EAAE,CACf,CAAC;AAEF,MAAM,gBAAgB,GAAG,IAAI,iBAAM,CACjC;IACE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE;IACrD,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;IACzC,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE;IAC9F,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;IAC1C,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;IAC5B,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,WAAW,CAAC,EAAE;IACjF,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;IACrD,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;IAC5C,iBAAiB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;IACnC,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;IACxC,OAAO,EAAE,EAAE,IAAI,EAAE,iBAAM,CAAC,KAAK,CAAC,KAAK,EAAE;IACrC,QAAQ,EAAE,EAAE,IAAI,EAAE,iBAAM,CAAC,KAAK,CAAC,KAAK,EAAE;IACtC,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;IACvB,IAAI,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE;CAC/B,EACD,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,cAAc,EAAE,CAClF,CAAC;AAEF,sDAAsD;AACtD,gBAAgB,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAE9C;;;;;;;;GAQG;AACH,MAAM,cAAc,GAAG,GAAG,CAAC;AAC3B,gBAAgB,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,kBAAkB,EAAE,cAAc,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;AAE5E,QAAA,UAAU,GAAG,kBAAQ,CAAC,MAAM,CAAC,UAAU;IAClD,CAAC,CAAE,kBAAQ,CAAC,MAAM,CAAC,UAA0C;IAC7D,CAAC,CAAC,kBAAQ,CAAC,KAAK,CAAc,YAAY,EAAE,gBAAgB,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"AuditEvent.js","sourceRoot":"","sources":["../../src/audit/AuditEvent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Whether `groupId` is one of the groups the person belongs to. */
|
|
2
|
+
export declare const isMemberOf: (groupId: string, userGroups: readonly string[] | null | undefined) => boolean;
|
|
3
|
+
/**
|
|
4
|
+
* The one rule that keeps a group's data inside the group: a `groupId` that
|
|
5
|
+
* arrives in a request is never trusted, it must be one of the caller's own.
|
|
6
|
+
*
|
|
7
|
+
* No group named means the caller's personal data, which is always theirs, so
|
|
8
|
+
* `undefined` and `null` pass. A group they are not in throws a 403, never
|
|
9
|
+
* falls back to personal: reading someone else's group as "personal" would
|
|
10
|
+
* quietly show them nothing, and writing to it would not.
|
|
11
|
+
*/
|
|
12
|
+
export declare function assertMemberOf(groupId: string | null | undefined, userGroups: readonly string[] | null | undefined, message?: string): void;
|
|
13
|
+
//# sourceMappingURL=access.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/groups/access.ts"],"names":[],"mappings":"AAEA,oEAAoE;AACpE,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,EAAE,YAAY,SAAS,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,KAAG,OACvD,CAAC;AAEzC;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAClC,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,EAChD,OAAO,SAAkB,GACxB,IAAI,CAEN"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isMemberOf = void 0;
|
|
4
|
+
exports.assertMemberOf = assertMemberOf;
|
|
5
|
+
const HttpError_1 = require("../errors/HttpError");
|
|
6
|
+
/** Whether `groupId` is one of the groups the person belongs to. */
|
|
7
|
+
const isMemberOf = (groupId, userGroups) => Boolean(userGroups?.includes(groupId));
|
|
8
|
+
exports.isMemberOf = isMemberOf;
|
|
9
|
+
/**
|
|
10
|
+
* The one rule that keeps a group's data inside the group: a `groupId` that
|
|
11
|
+
* arrives in a request is never trusted, it must be one of the caller's own.
|
|
12
|
+
*
|
|
13
|
+
* No group named means the caller's personal data, which is always theirs, so
|
|
14
|
+
* `undefined` and `null` pass. A group they are not in throws a 403, never
|
|
15
|
+
* falls back to personal: reading someone else's group as "personal" would
|
|
16
|
+
* quietly show them nothing, and writing to it would not.
|
|
17
|
+
*/
|
|
18
|
+
function assertMemberOf(groupId, userGroups, message = 'Access denied') {
|
|
19
|
+
if (groupId && !(0, exports.isMemberOf)(groupId, userGroups))
|
|
20
|
+
throw new HttpError_1.ForbiddenError(message);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=access.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access.js","sourceRoot":"","sources":["../../src/groups/access.ts"],"names":[],"mappings":";;;AAeA,wCAMC;AArBD,mDAAqD;AAErD,oEAAoE;AAC7D,MAAM,UAAU,GAAG,CAAC,OAAe,EAAE,UAAgD,EAAW,EAAE,CACvG,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;AAD5B,QAAA,UAAU,cACkB;AAEzC;;;;;;;;GAQG;AACH,SAAgB,cAAc,CAC5B,OAAkC,EAClC,UAAgD,EAChD,OAAO,GAAG,eAAe;IAEzB,IAAI,OAAO,IAAI,CAAC,IAAA,kBAAU,EAAC,OAAO,EAAE,UAAU,CAAC;QAAE,MAAM,IAAI,0BAAc,CAAC,OAAO,CAAC,CAAC;AACrF,CAAC"}
|
package/dist/groups/index.d.ts
CHANGED
|
@@ -3,4 +3,5 @@ export type { GroupOwner } from './client';
|
|
|
3
3
|
export { groupOwnerSubject } from './subject';
|
|
4
4
|
export type { GroupIdResolver } from './subject';
|
|
5
5
|
export { getUserGroupIds, currentGroupsOf, clearMembershipCache } from './membership';
|
|
6
|
+
export { assertMemberOf, isMemberOf } from './access';
|
|
6
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/groups/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/groups/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/groups/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.clearMembershipCache = exports.currentGroupsOf = exports.getUserGroupIds = exports.groupOwnerSubject = exports.clearGroupOwnerCache = exports.invalidateGroupOwner = exports.getGroupOwner = void 0;
|
|
3
|
+
exports.isMemberOf = exports.assertMemberOf = exports.clearMembershipCache = exports.currentGroupsOf = exports.getUserGroupIds = exports.groupOwnerSubject = exports.clearGroupOwnerCache = exports.invalidateGroupOwner = exports.getGroupOwner = void 0;
|
|
4
4
|
var client_1 = require("./client");
|
|
5
5
|
Object.defineProperty(exports, "getGroupOwner", { enumerable: true, get: function () { return client_1.getGroupOwner; } });
|
|
6
6
|
Object.defineProperty(exports, "invalidateGroupOwner", { enumerable: true, get: function () { return client_1.invalidateGroupOwner; } });
|
|
@@ -11,4 +11,7 @@ var membership_1 = require("./membership");
|
|
|
11
11
|
Object.defineProperty(exports, "getUserGroupIds", { enumerable: true, get: function () { return membership_1.getUserGroupIds; } });
|
|
12
12
|
Object.defineProperty(exports, "currentGroupsOf", { enumerable: true, get: function () { return membership_1.currentGroupsOf; } });
|
|
13
13
|
Object.defineProperty(exports, "clearMembershipCache", { enumerable: true, get: function () { return membership_1.clearMembershipCache; } });
|
|
14
|
+
var access_1 = require("./access");
|
|
15
|
+
Object.defineProperty(exports, "assertMemberOf", { enumerable: true, get: function () { return access_1.assertMemberOf; } });
|
|
16
|
+
Object.defineProperty(exports, "isMemberOf", { enumerable: true, get: function () { return access_1.isMemberOf; } });
|
|
14
17
|
//# sourceMappingURL=index.js.map
|
package/dist/groups/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/groups/index.ts"],"names":[],"mappings":";;;AAAA,mCAAqF;AAA5E,uGAAA,aAAa,OAAA;AAAE,8GAAA,oBAAoB,OAAA;AAAE,8GAAA,oBAAoB,OAAA;AAGlE,qCAA8C;AAArC,4GAAA,iBAAiB,OAAA;AAG1B,2CAAsF;AAA7E,6GAAA,eAAe,OAAA;AAAE,6GAAA,eAAe,OAAA;AAAE,kHAAA,oBAAoB,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/groups/index.ts"],"names":[],"mappings":";;;AAAA,mCAAqF;AAA5E,uGAAA,aAAa,OAAA;AAAE,8GAAA,oBAAoB,OAAA;AAAE,8GAAA,oBAAoB,OAAA;AAGlE,qCAA8C;AAArC,4GAAA,iBAAiB,OAAA;AAG1B,2CAAsF;AAA7E,6GAAA,eAAe,OAAA;AAAE,6GAAA,eAAe,OAAA;AAAE,kHAAA,oBAAoB,OAAA;AAC/D,mCAAsD;AAA7C,wGAAA,cAAc,OAAA;AAAE,oGAAA,UAAU,OAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -17,7 +17,9 @@ export * from './fileService';
|
|
|
17
17
|
export * from './oauth';
|
|
18
18
|
export * from './audit';
|
|
19
19
|
export * from './sse';
|
|
20
|
+
export * from './activity';
|
|
20
21
|
export { authenticateToken, optionalAuth } from './middleware/authMiddleware';
|
|
22
|
+
export { requireAdmin } from './middleware/requireAdmin';
|
|
21
23
|
export { USER_SESSIONS_COLLECTION, isCurrentSession } from './auth/session';
|
|
22
24
|
export { requireInternalServiceToken, internalServiceTokenBypass, allowUserOrInternalService, verifyInternalToken, INTERNAL_TOKEN_HEADER, SERVICE_ID_HEADER } from './middleware/internalServiceAuth';
|
|
23
25
|
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,cAAc,eAAe,CAAC;AAG9B,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AAGxB,cAAc,OAAO,CAAC;AAGtB,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC9E,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,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;AAC9E,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGlD,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,eAAe,CAAC;AAG9B,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AAGxB,cAAc,OAAO,CAAC;AAGtB,cAAc,YAAY,CAAC;AAG3B,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC9E,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,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;AAC9E,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGlD,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
|
@@ -18,8 +18,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
18
18
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
19
|
};
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.
|
|
22
|
-
exports.extractSpanContext = exports.injectHeaders = exports.setSpanTag = exports.logToSpan = exports.createChildSpan = exports.tracingMiddleware = exports.startSpan = exports.getTracer = exports.initTracer = exports.register = exports.businessMetrics = exports.databaseQueriesTotal = void 0;
|
|
21
|
+
exports.httpRequestsTotal = exports.httpRequestDuration = exports.metricsMiddleware = exports.escapeRegex = exports.canAccessResource = exports.buildAccessQuery = exports.sendError = exports.sendSuccess = exports.createApiResponse = exports.correlationMiddleware = exports.generateCorrelationId = exports.validate = exports.strictRateLimiter = exports.standardRateLimiter = exports.createRateLimiter = exports.securityHeaders = exports.simpleRequestLogger = exports.requestLoggerWithMetrics = exports.requestLogger = exports.errorHandler = exports.createCorsMiddleware = exports.SERVICE_ID_HEADER = exports.INTERNAL_TOKEN_HEADER = exports.verifyInternalToken = exports.allowUserOrInternalService = exports.internalServiceTokenBypass = exports.requireInternalServiceToken = exports.isCurrentSession = exports.USER_SESSIONS_COLLECTION = exports.requireAdmin = exports.optionalAuth = exports.authenticateToken = exports.PaymentRequiredError = exports.TooManyRequestsError = exports.ConflictError = exports.NotFoundError = exports.ForbiddenError = exports.UnauthorizedError = exports.BadRequestError = exports.HttpError = exports.requireEnv = exports.disconnectDB = exports.connectDB = exports.createHealthCheck = exports.metricsHandler = exports.healthLive = exports.healthCheck = exports.logger = exports.registerShutdown = exports.createBaseApp = void 0;
|
|
22
|
+
exports.extractSpanContext = exports.injectHeaders = exports.setSpanTag = exports.logToSpan = exports.createChildSpan = exports.tracingMiddleware = exports.startSpan = exports.getTracer = exports.initTracer = exports.register = exports.businessMetrics = exports.databaseQueriesTotal = exports.databaseQueryDuration = void 0;
|
|
23
23
|
// App bootstrap
|
|
24
24
|
var createBaseApp_1 = require("./app/createBaseApp");
|
|
25
25
|
Object.defineProperty(exports, "createBaseApp", { enumerable: true, get: function () { return createBaseApp_1.createBaseApp; } });
|
|
@@ -65,10 +65,14 @@ __exportStar(require("./oauth"), exports);
|
|
|
65
65
|
__exportStar(require("./audit"), exports);
|
|
66
66
|
// Server-Sent Events streams (headers, heartbeat, lifetime, shutdown)
|
|
67
67
|
__exportStar(require("./sse"), exports);
|
|
68
|
+
// Visit counts (user_days), for the admin screens
|
|
69
|
+
__exportStar(require("./activity"), exports);
|
|
68
70
|
// Middleware
|
|
69
71
|
var authMiddleware_1 = require("./middleware/authMiddleware");
|
|
70
72
|
Object.defineProperty(exports, "authenticateToken", { enumerable: true, get: function () { return authMiddleware_1.authenticateToken; } });
|
|
71
73
|
Object.defineProperty(exports, "optionalAuth", { enumerable: true, get: function () { return authMiddleware_1.optionalAuth; } });
|
|
74
|
+
var requireAdmin_1 = require("./middleware/requireAdmin");
|
|
75
|
+
Object.defineProperty(exports, "requireAdmin", { enumerable: true, get: function () { return requireAdmin_1.requireAdmin; } });
|
|
72
76
|
var session_1 = require("./auth/session");
|
|
73
77
|
Object.defineProperty(exports, "USER_SESSIONS_COLLECTION", { enumerable: true, get: function () { return session_1.USER_SESSIONS_COLLECTION; } });
|
|
74
78
|
Object.defineProperty(exports, "isCurrentSession", { enumerable: true, get: function () { return session_1.isCurrentSession; } });
|
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,+EAA+E;AAC/E,gDAA8B;AAE9B,qEAAqE;AACrE,0CAAwB;AACxB,0CAAwB;AAExB,sEAAsE;AACtE,wCAAsB;AAEtB,aAAa;AACb,8DAA8E;AAArE,mHAAA,iBAAiB,OAAA;AAAE,8GAAA,YAAY,OAAA;AACxC,0CAA4E;AAAnE,mHAAA,wBAAwB,OAAA;AAAE,2GAAA,gBAAgB,OAAA;AACnD,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;AAC5C,mDAAkD;AAAzC,0GAAA,WAAW,OAAA;AAEpB,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":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
/**
|
|
3
|
+
* Lets only an administrator through, after authentication.
|
|
4
|
+
*
|
|
5
|
+
* Reads the role off the session token, which is re-signed with fresh roles at
|
|
6
|
+
* every renewal, so a role granted or taken away applies within minutes rather
|
|
7
|
+
* than at the next sign-in. Routes that change an account (as opposed to
|
|
8
|
+
* reading it) should additionally check the role against the database, which
|
|
9
|
+
* is what auth-service's `requireRole` does.
|
|
10
|
+
*
|
|
11
|
+
* A key or a connected assistant is refused even if the account behind it is an
|
|
12
|
+
* administrator: software acting for the owner is exactly what a trail of what
|
|
13
|
+
* the owner did should be able to tell apart from the owner.
|
|
14
|
+
*/
|
|
15
|
+
export declare const requireAdmin: (req: Request, res: Response, next: NextFunction) => void;
|
|
16
|
+
//# sourceMappingURL=requireAdmin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requireAdmin.d.ts","sourceRoot":"","sources":["../../src/middleware/requireAdmin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE1D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,KAAG,IAM9E,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requireAdmin = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Lets only an administrator through, after authentication.
|
|
6
|
+
*
|
|
7
|
+
* Reads the role off the session token, which is re-signed with fresh roles at
|
|
8
|
+
* every renewal, so a role granted or taken away applies within minutes rather
|
|
9
|
+
* than at the next sign-in. Routes that change an account (as opposed to
|
|
10
|
+
* reading it) should additionally check the role against the database, which
|
|
11
|
+
* is what auth-service's `requireRole` does.
|
|
12
|
+
*
|
|
13
|
+
* A key or a connected assistant is refused even if the account behind it is an
|
|
14
|
+
* administrator: software acting for the owner is exactly what a trail of what
|
|
15
|
+
* the owner did should be able to tell apart from the owner.
|
|
16
|
+
*/
|
|
17
|
+
const requireAdmin = (req, res, next) => {
|
|
18
|
+
if (req.apiKey || !req.user?.roles?.includes('admin')) {
|
|
19
|
+
res.status(403).json({ success: false, message: 'admin required' });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
next();
|
|
23
|
+
};
|
|
24
|
+
exports.requireAdmin = requireAdmin;
|
|
25
|
+
//# sourceMappingURL=requireAdmin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requireAdmin.js","sourceRoot":"","sources":["../../src/middleware/requireAdmin.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;GAYG;AACI,MAAM,YAAY,GAAG,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAQ,EAAE;IACpF,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACtD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,CAAC;QACpE,OAAO;IACT,CAAC;IACD,IAAI,EAAE,CAAC;AACT,CAAC,CAAC;AANW,QAAA,YAAY,gBAMvB"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type Application } from 'express';
|
|
2
|
+
/** Makes every `use()` remember its path on the layers it adds. Idempotent. */
|
|
3
|
+
export declare function recordMountPaths(): void;
|
|
4
|
+
export interface Endpoint {
|
|
5
|
+
method: string;
|
|
6
|
+
/** In OpenAPI form: `/api/things/{id}`. */
|
|
7
|
+
path: string;
|
|
8
|
+
}
|
|
9
|
+
/** Every method and path the app answers, as OpenAPI would write them. */
|
|
10
|
+
export declare function listRoutes(app: Application): Endpoint[];
|
|
11
|
+
/**
|
|
12
|
+
* Every method and path a spec documents. A path item may live in a file of its
|
|
13
|
+
* own (`$ref: 'paths/thing.yml'`), as file-service's source spec does, so a
|
|
14
|
+
* relative `$ref` is followed.
|
|
15
|
+
*/
|
|
16
|
+
export declare function listDocumented(specFile: string): Endpoint[];
|
|
17
|
+
/**
|
|
18
|
+
* Served by backend-core in every service, for the platform rather than for API
|
|
19
|
+
* callers: Kubernetes-style liveness and Prometheus. Not expected in each spec.
|
|
20
|
+
*/
|
|
21
|
+
export declare const PLATFORM_ROUTES: string[];
|
|
22
|
+
export interface OpenApiDrift {
|
|
23
|
+
/** Routes the app serves that the spec does not mention. */
|
|
24
|
+
undocumented: string[];
|
|
25
|
+
/** Operations the spec describes that the app does not serve. */
|
|
26
|
+
stale: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* What the spec and the app disagree on. `ignore` takes routes that are
|
|
30
|
+
* deliberately left out of the docs (`'GET /docs'`, or a pattern), on top of
|
|
31
|
+
* the platform routes.
|
|
32
|
+
*/
|
|
33
|
+
export declare function openApiDrift(app: Application, specFile: string, ignore?: Array<string | RegExp>): OpenApiDrift;
|
|
34
|
+
//# sourceMappingURL=openapi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../../src/testing/openapi.ts"],"names":[],"mappings":"AAEA,OAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAyBpD,+EAA+E;AAC/E,wBAAgB,gBAAgB,IAAI,IAAI,CAavC;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAYD,0EAA0E;AAC1E,wBAAgB,UAAU,CAAC,GAAG,EAAE,WAAW,GAAG,QAAQ,EAAE,CAsBvD;AAID;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,CAY3D;AAMD;;;GAGG;AACH,eAAO,MAAM,eAAe,UAAuC,CAAC;AAEpE,MAAM,WAAW,YAAY;IAC3B,4DAA4D;IAC5D,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,iEAAiE;IACjE,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAM,GAAG,YAAY,CAalH"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PLATFORM_ROUTES = void 0;
|
|
7
|
+
exports.recordMountPaths = recordMountPaths;
|
|
8
|
+
exports.listRoutes = listRoutes;
|
|
9
|
+
exports.listDocumented = listDocumented;
|
|
10
|
+
exports.openApiDrift = openApiDrift;
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const express_1 = __importDefault(require("express"));
|
|
14
|
+
const yaml_1 = require("yaml");
|
|
15
|
+
let recording = false;
|
|
16
|
+
/** Makes every `use()` remember its path on the layers it adds. Idempotent. */
|
|
17
|
+
function recordMountPaths() {
|
|
18
|
+
if (recording)
|
|
19
|
+
return;
|
|
20
|
+
recording = true;
|
|
21
|
+
const proto = express_1.default.Router.prototype;
|
|
22
|
+
const use = proto.use;
|
|
23
|
+
proto.use = function (...args) {
|
|
24
|
+
const before = this.stack.length;
|
|
25
|
+
const result = use.apply(this, args);
|
|
26
|
+
const first = args[0];
|
|
27
|
+
const mountPath = typeof first === 'string' || Array.isArray(first) ? first : '/';
|
|
28
|
+
for (const layer of this.stack.slice(before))
|
|
29
|
+
layer.mountPath = mountPath;
|
|
30
|
+
return result;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const METHODS = ['get', 'post', 'put', 'patch', 'delete'];
|
|
34
|
+
const join = (prefix, path) => {
|
|
35
|
+
const joined = `${prefix}/${path}`.replace(/\/+/g, '/');
|
|
36
|
+
return joined.length > 1 ? joined.replace(/\/$/, '') : joined;
|
|
37
|
+
};
|
|
38
|
+
/** `/:id` and Express 5's `/*fileId` as OpenAPI writes them: `/{id}`, `/{fileId}`. */
|
|
39
|
+
const toOpenApiPath = (path) => path.replace(/[:*](\w+)/g, '{$1}');
|
|
40
|
+
/** Every method and path the app answers, as OpenAPI would write them. */
|
|
41
|
+
function listRoutes(app) {
|
|
42
|
+
const router = app.router ?? app._router;
|
|
43
|
+
if (!router)
|
|
44
|
+
throw new Error('The app has no router yet: build it before listing its routes');
|
|
45
|
+
const found = [];
|
|
46
|
+
const walk = (stack, prefix) => {
|
|
47
|
+
for (const layer of stack) {
|
|
48
|
+
if (layer.route) {
|
|
49
|
+
const paths = Array.isArray(layer.route.path) ? layer.route.path : [layer.route.path];
|
|
50
|
+
for (const path of paths) {
|
|
51
|
+
for (const method of Object.keys(layer.route.methods).filter((m) => METHODS.includes(m))) {
|
|
52
|
+
found.push({ method, path: toOpenApiPath(join(prefix, path)) });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else if (layer.handle?.stack) {
|
|
57
|
+
const mounts = layer.mountPath === undefined ? ['/'] : [].concat(layer.mountPath);
|
|
58
|
+
for (const mount of mounts)
|
|
59
|
+
walk(layer.handle.stack, join(prefix, mount));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
walk(router.stack, '/');
|
|
64
|
+
return found;
|
|
65
|
+
}
|
|
66
|
+
const readYaml = (file) => (0, yaml_1.parse)(fs_1.default.readFileSync(file, 'utf8'));
|
|
67
|
+
/**
|
|
68
|
+
* Every method and path a spec documents. A path item may live in a file of its
|
|
69
|
+
* own (`$ref: 'paths/thing.yml'`), as file-service's source spec does, so a
|
|
70
|
+
* relative `$ref` is followed.
|
|
71
|
+
*/
|
|
72
|
+
function listDocumented(specFile) {
|
|
73
|
+
const spec = readYaml(specFile);
|
|
74
|
+
return Object.entries(spec.paths ?? {}).flatMap(([path, item]) => {
|
|
75
|
+
const ref = item?.$ref;
|
|
76
|
+
const operations = typeof ref === 'string' && !ref.startsWith('#')
|
|
77
|
+
? readYaml(path_1.default.resolve(path_1.default.dirname(specFile), ref))
|
|
78
|
+
: item;
|
|
79
|
+
return Object.keys(operations ?? {})
|
|
80
|
+
.filter((method) => METHODS.includes(method))
|
|
81
|
+
.map((method) => ({ method, path }));
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/** A route's identity regardless of what its parameters are called: `{id}` and `{conversationId}` match. */
|
|
85
|
+
const key = ({ method, path }) => `${method.toUpperCase()} ${path.replace(/\{[^}]+\}/g, '{}')}`;
|
|
86
|
+
const label = ({ method, path }) => `${method.toUpperCase()} ${path}`;
|
|
87
|
+
/**
|
|
88
|
+
* Served by backend-core in every service, for the platform rather than for API
|
|
89
|
+
* callers: Kubernetes-style liveness and Prometheus. Not expected in each spec.
|
|
90
|
+
*/
|
|
91
|
+
exports.PLATFORM_ROUTES = ['GET /health/live', 'GET /metrics'];
|
|
92
|
+
/**
|
|
93
|
+
* What the spec and the app disagree on. `ignore` takes routes that are
|
|
94
|
+
* deliberately left out of the docs (`'GET /docs'`, or a pattern), on top of
|
|
95
|
+
* the platform routes.
|
|
96
|
+
*/
|
|
97
|
+
function openApiDrift(app, specFile, ignore = []) {
|
|
98
|
+
const rules = [...exports.PLATFORM_ROUTES, ...ignore];
|
|
99
|
+
const skipped = (endpoint) => rules.some((rule) => (typeof rule === 'string' ? rule === label(endpoint) : rule.test(label(endpoint))));
|
|
100
|
+
const routes = listRoutes(app).filter((endpoint) => !skipped(endpoint));
|
|
101
|
+
const documented = listDocumented(specFile).filter((endpoint) => !skipped(endpoint));
|
|
102
|
+
const routeKeys = new Set(routes.map(key));
|
|
103
|
+
const documentedKeys = new Set(documented.map(key));
|
|
104
|
+
const unique = (labels) => [...new Set(labels)].sort();
|
|
105
|
+
return {
|
|
106
|
+
undocumented: unique(routes.filter((endpoint) => !documentedKeys.has(key(endpoint))).map(label)),
|
|
107
|
+
stale: unique(documented.filter((endpoint) => !routeKeys.has(key(endpoint))).map(label))
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=openapi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.js","sourceRoot":"","sources":["../../src/testing/openapi.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const session_1 = require("../auth/session");
|
|
4
4
|
const membership_1 = require("../groups/membership");
|
|
5
|
+
const openapi_1 = require("./openapi");
|
|
5
6
|
/**
|
|
6
7
|
* Jest setup for service test suites: they sign session tokens without a
|
|
7
8
|
* session and run with no database or group-service, so every token's session
|
|
@@ -15,4 +16,6 @@ const membership_1 = require("../groups/membership");
|
|
|
15
16
|
// No group-service either: a test token's own `groups` claim is taken as the
|
|
16
17
|
// person's membership. The live lookup is tested in groups/membership.test.ts.
|
|
17
18
|
(0, membership_1.overrideMembershipLookupForTests)(async (user) => user.groups ?? []);
|
|
19
|
+
// Before any app is built, so the OpenAPI drift check can see where routers are mounted.
|
|
20
|
+
(0, openapi_1.recordMountPaths)();
|
|
18
21
|
//# sourceMappingURL=serviceTestSetup.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serviceTestSetup.js","sourceRoot":"","sources":["../../src/testing/serviceTestSetup.ts"],"names":[],"mappings":";;AAAA,6CAA+D;AAC/D,qDAAwE;
|
|
1
|
+
{"version":3,"file":"serviceTestSetup.js","sourceRoot":"","sources":["../../src/testing/serviceTestSetup.ts"],"names":[],"mappings":";;AAAA,6CAA+D;AAC/D,qDAAwE;AACxE,uCAA6C;AAE7C;;;;;;;;GAQG;AACH,IAAA,sCAA4B,EAAC,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC;AAE/C,6EAA6E;AAC7E,+EAA+E;AAC/E,IAAA,6CAAgC,EAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;AAEpE,yFAAyF;AACzF,IAAA,0BAAgB,GAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tumbaland/backend-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.51.0",
|
|
4
4
|
"description": "Core shared functionality for Tumbaland backend services",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist",
|
|
9
|
-
"testing.js"
|
|
9
|
+
"testing.js",
|
|
10
|
+
"testing.d.ts"
|
|
10
11
|
],
|
|
11
12
|
"scripts": {
|
|
12
13
|
"build": "tsc -p tsconfig.build.json",
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
"prom-client": "^15.1.3",
|
|
61
62
|
"uuid": "^14.0.3",
|
|
62
63
|
"winston": "^3.19.0",
|
|
64
|
+
"yaml": "^2.9.1",
|
|
63
65
|
"zod": "^4.6.7"
|
|
64
66
|
},
|
|
65
67
|
"peerDependencies": {
|
package/testing.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './dist/testing/openapi';
|
package/testing.js
CHANGED