@proteinjs/user-server 1.17.0 → 1.19.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/CHANGELOG.md +22 -0
- package/dist/generated/index.d.ts.map +1 -1
- package/dist/generated/index.js +3 -1
- package/dist/generated/index.js.map +1 -1
- package/dist/src/authorization/UserActivityStamp.d.ts +17 -15
- package/dist/src/authorization/UserActivityStamp.d.ts.map +1 -1
- package/dist/src/authorization/UserActivityStamp.js +21 -19
- package/dist/src/authorization/UserActivityStamp.js.map +1 -1
- package/dist/src/authorization/userCache.d.ts +9 -0
- package/dist/src/authorization/userCache.d.ts.map +1 -1
- package/dist/src/authorization/userCache.js +9 -9
- package/dist/src/authorization/userCache.js.map +1 -1
- package/dist/src/routes/devLogin.d.ts +10 -0
- package/dist/src/routes/devLogin.d.ts.map +1 -1
- package/dist/src/routes/devLogin.js +28 -9
- package/dist/src/routes/devLogin.js.map +1 -1
- package/dist/src/services/Roles.d.ts +20 -1
- package/dist/src/services/Roles.d.ts.map +1 -1
- package/dist/src/services/Roles.js +79 -3
- package/dist/src/services/Roles.js.map +1 -1
- package/dist/src/services/UserPresence.d.ts +16 -0
- package/dist/src/services/UserPresence.d.ts.map +1 -0
- package/dist/src/services/UserPresence.js +75 -0
- package/dist/src/services/UserPresence.js.map +1 -0
- package/dist/test/DevLogin.test.js +13 -41
- package/dist/test/DevLogin.test.js.map +1 -1
- package/dist/test/DevLoginBootstrapAdmin.test.d.ts +2 -0
- package/dist/test/DevLoginBootstrapAdmin.test.d.ts.map +1 -0
- package/dist/test/DevLoginBootstrapAdmin.test.js +384 -0
- package/dist/test/DevLoginBootstrapAdmin.test.js.map +1 -0
- package/dist/test/UserActivityStamp.integration.test.js +57 -90
- package/dist/test/UserActivityStamp.integration.test.js.map +1 -1
- package/dist/test/UserCacheDoesNotStampPresence.integration.test.d.ts +2 -0
- package/dist/test/UserCacheDoesNotStampPresence.integration.test.d.ts.map +1 -0
- package/dist/test/UserCacheDoesNotStampPresence.integration.test.js +117 -0
- package/dist/test/UserCacheDoesNotStampPresence.integration.test.js.map +1 -0
- package/dist/test/devLoginHarness.d.ts +16 -0
- package/dist/test/devLoginHarness.d.ts.map +1 -0
- package/dist/test/devLoginHarness.js +79 -0
- package/dist/test/devLoginHarness.js.map +1 -0
- package/generated/index.ts +3 -1
- package/package.json +3 -3
- package/src/authorization/UserActivityStamp.ts +22 -20
- package/src/authorization/userCache.ts +9 -9
- package/src/routes/devLogin.ts +17 -0
- package/src/services/Roles.ts +49 -4
- package/src/services/UserPresence.ts +26 -0
- package/test/DevLogin.test.ts +5 -36
- package/test/DevLoginBootstrapAdmin.test.ts +175 -0
- package/test/UserActivityStamp.integration.test.ts +38 -50
- package/test/UserCacheDoesNotStampPresence.integration.test.ts +44 -0
- package/test/devLoginHarness.ts +39 -0
|
@@ -46,33 +46,35 @@ var logger_1 = require("@proteinjs/logger");
|
|
|
46
46
|
var user_1 = require("@proteinjs/user");
|
|
47
47
|
/**
|
|
48
48
|
* Writes the LAST-ACTIVITY presence stamp (`user_activity`, one row per user — see
|
|
49
|
-
* UserActivityTable's contract): "this
|
|
49
|
+
* UserActivityTable's contract): "this person gave a page input just now".
|
|
50
50
|
*
|
|
51
|
-
* The
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
51
|
+
* The INPUT keying lives at the call site — the UserPresence service is the only caller, and
|
|
52
|
+
* the page reports through it only from a real pointer / key / touch / wheel event. The
|
|
53
|
+
* per-request session build (userCache.create) does NOT call this: it runs for every request
|
|
54
|
+
* over a session cookie, and that transport is what an idle tab produces all day (polls, socket
|
|
55
|
+
* re-joins on reconnect, the reload a deploy pushes) — presence keyed on it read every user as
|
|
56
|
+
* "active today" (founder finding 2026-09-12). What this class owns is the ACCOUNT predicate:
|
|
57
|
+
* the guest identity and machine accounts (`machine` — the one owner of "is this a machine",
|
|
58
|
+
* founder ruling 2026-09-02) are refused, whatever door they arrive through.
|
|
57
59
|
*
|
|
58
60
|
* Write behavior mirrors DbSessionStore's touch: throttled per user (a presence fact consumed at
|
|
59
|
-
* day grain needs no finer cadence, and
|
|
60
|
-
* fail-open (a lost stamp is a few minutes of staleness, never a failed
|
|
61
|
-
* promise NEVER rejects), and race-tolerant (concurrent first stamps contend on the
|
|
62
|
-
* index; the loser's error is swallowed as debug).
|
|
61
|
+
* day grain needs no finer cadence, and the page already throttles its reports — this is the
|
|
62
|
+
* belt), fail-open (a lost stamp is a few minutes of staleness, never a failed call — the
|
|
63
|
+
* returned promise NEVER rejects), and race-tolerant (concurrent first stamps contend on the
|
|
64
|
+
* scope-unique index; the loser's error is swallowed as debug).
|
|
63
65
|
*/
|
|
64
66
|
var UserActivityStamp = /** @class */ (function () {
|
|
65
67
|
function UserActivityStamp() {
|
|
66
68
|
this.logger = new logger_1.Logger({ name: this.constructor.name });
|
|
67
69
|
}
|
|
68
70
|
/**
|
|
69
|
-
* Record that `user`
|
|
70
|
-
*
|
|
71
|
+
* Record that `user` gave a page human input. Fire-and-forget safe: errors are handled (and
|
|
72
|
+
* logged) here, so callers may `void` the returned promise.
|
|
71
73
|
*/
|
|
72
|
-
UserActivityStamp.prototype.
|
|
74
|
+
UserActivityStamp.prototype.recordHumanInput = function (user) {
|
|
73
75
|
var _this = this;
|
|
74
76
|
var _a;
|
|
75
|
-
if (!user.id || user.
|
|
77
|
+
if (!user.id || user.id === user_1.guestUser.id || user.machine === true) {
|
|
76
78
|
return Promise.resolve();
|
|
77
79
|
}
|
|
78
80
|
var last = (_a = UserActivityStamp.lastStampMs.get(user.id)) !== null && _a !== void 0 ? _a : 0;
|
|
@@ -85,9 +87,9 @@ var UserActivityStamp = /** @class */ (function () {
|
|
|
85
87
|
UserActivityStamp.lastStampMs.clear(); // bounded memory; worst case is one extra stamp per user
|
|
86
88
|
}
|
|
87
89
|
return this.upsert(user.id).catch(function (error) {
|
|
88
|
-
// Contention (a concurrent
|
|
89
|
-
// failures land here alike; both are harmless to the
|
|
90
|
-
//
|
|
90
|
+
// Contention (a concurrent report stamped first, racing the scope-unique index) and real
|
|
91
|
+
// failures land here alike; both are harmless to the caller. Un-throttle so the next
|
|
92
|
+
// report retries instead of waiting out a full interval on a stamp that never landed.
|
|
91
93
|
UserActivityStamp.lastStampMs.delete(user.id);
|
|
92
94
|
_this.logger.error({ message: 'Failed to write user activity stamp', error: error });
|
|
93
95
|
});
|
|
@@ -118,7 +120,7 @@ var UserActivityStamp = /** @class */ (function () {
|
|
|
118
120
|
};
|
|
119
121
|
/** Stamp at most this often per user — same cadence class as DbSessionStore.TOUCH_INTERVAL. */
|
|
120
122
|
UserActivityStamp.STAMP_INTERVAL_MS = 1000 * 60 * 5;
|
|
121
|
-
/** Process-wide:
|
|
123
|
+
/** Process-wide: the throttle outlives any one service instance. */
|
|
122
124
|
UserActivityStamp.lastStampMs = new Map();
|
|
123
125
|
return UserActivityStamp;
|
|
124
126
|
}());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserActivityStamp.js","sourceRoot":"","sources":["../../../src/authorization/UserActivityStamp.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,kDAA4B;AAC5B,oCAA8C;AAC9C,4CAA2C;AAC3C,
|
|
1
|
+
{"version":3,"file":"UserActivityStamp.js","sourceRoot":"","sources":["../../../src/authorization/UserActivityStamp.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,kDAA4B;AAC5B,oCAA8C;AAC9C,4CAA2C;AAC3C,wCAAkF;AAElF;;;;;;;;;;;;;;;;;;GAkBG;AACH;IAAA;QAMU,WAAM,GAAG,IAAI,eAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC;IAqC/D,CAAC;IAnCC;;;OAGG;IACH,4CAAgB,GAAhB,UAAiB,IAAkC;QAAnD,iBAoBC;;QAnBC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,EAAE,KAAK,gBAAS,CAAC,EAAE,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,EAAE;YACjE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;SAC1B;QACD,IAAM,IAAI,GAAG,MAAA,iBAAiB,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,mCAAI,CAAC,CAAC;QAC7D,IAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,GAAG,GAAG,IAAI,GAAG,iBAAiB,CAAC,iBAAiB,EAAE;YACpD,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;SAC1B;QACD,iBAAiB,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QAChD,IAAI,iBAAiB,CAAC,WAAW,CAAC,IAAI,GAAG,KAAK,EAAE;YAC9C,iBAAiB,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC,yDAAyD;SACjG;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,UAAC,KAAK;YACtC,yFAAyF;YACzF,qFAAqF;YACrF,sFAAsF;YACtF,iBAAiB,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC9C,KAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,qCAAqC,EAAE,KAAK,OAAA,EAAE,CAAC,CAAC;QAC/E,CAAC,CAAC,CAAC;IACL,CAAC;IAEa,kCAAM,GAApB,UAAqB,MAAc;;;;;;wBAC3B,EAAE,GAAG,IAAA,kBAAa,GAAE,CAAC;wBACV,qBAAM,EAAE,CAAC,GAAG,CAAC,aAAM,CAAC,YAAY,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAA;;wBAA/D,QAAQ,GAAG,SAAoD;6BACjE,QAAQ,EAAR,wBAAQ;wBACV,qBAAM,EAAE,CAAC,MAAM,CAAC,aAAM,CAAC,YAAY,EAAE,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,YAAY,EAAE,IAAA,gBAAM,GAAE,EAAkB,CAAC,EAAA;;wBAAjG,SAAiG,CAAC;;4BAElG,qBAAM,EAAE,CAAC,MAAM,CAAC,aAAM,CAAC,YAAY,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,IAAA,gBAAM,GAAE,EAAkB,CAAC,EAAA;;wBAA/F,SAA+F,CAAC;;;;;;KAEnG;IAzCD,+FAA+F;IACvE,mCAAiB,GAAG,IAAI,GAAG,EAAE,GAAG,CAAC,AAAhB,CAAiB;IAC1D,oEAAoE;IACrD,6BAAW,GAAG,IAAI,GAAG,EAAkB,AAA5B,CAA6B;IAuCzD,wBAAC;CAAA,AA3CD,IA2CC;AA3CY,8CAAiB"}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { SessionDataCache } from '@proteinjs/server-api';
|
|
2
2
|
import { User } from '@proteinjs/user';
|
|
3
|
+
/**
|
|
4
|
+
* The per-request session-cache build: resolves the session's email to the account row every
|
|
5
|
+
* request that rides a session cookie (wrapRoute), and every socket event (util-server's
|
|
6
|
+
* SocketSessionContext). It resolves IDENTITY only — it does not write the presence stamp
|
|
7
|
+
* (`user_activity`, UserActivityTable's contract): this build runs for an idle tab's polls, a
|
|
8
|
+
* socket's room re-joins on every reconnect, the reload a deploy pushes onto every open tab —
|
|
9
|
+
* transport, not a person. Presence is written from the page's human-input report alone
|
|
10
|
+
* (UserPresence.recordPresence).
|
|
11
|
+
*/
|
|
3
12
|
export declare const userCache: SessionDataCache<User>;
|
|
4
13
|
//# sourceMappingURL=userCache.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"userCache.d.ts","sourceRoot":"","sources":["../../../src/authorization/userCache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGzD,OAAO,EAAE,IAAI,EAA6C,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"userCache.d.ts","sourceRoot":"","sources":["../../../src/authorization/userCache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGzD,OAAO,EAAE,IAAI,EAA6C,MAAM,iBAAiB,CAAC;AAKlF;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,EAAE,gBAAgB,CAAC,IAAI,CA8C5C,CAAC"}
|
|
@@ -45,9 +45,16 @@ var db_1 = require("@proteinjs/db");
|
|
|
45
45
|
var logger_1 = require("@proteinjs/logger");
|
|
46
46
|
var user_1 = require("@proteinjs/user");
|
|
47
47
|
var DefaultAdminCredentials_1 = require("../authentication/DefaultAdminCredentials");
|
|
48
|
-
var UserActivityStamp_1 = require("./UserActivityStamp");
|
|
49
48
|
var logger = new logger_1.Logger({ name: 'userCache' });
|
|
50
|
-
|
|
49
|
+
/**
|
|
50
|
+
* The per-request session-cache build: resolves the session's email to the account row every
|
|
51
|
+
* request that rides a session cookie (wrapRoute), and every socket event (util-server's
|
|
52
|
+
* SocketSessionContext). It resolves IDENTITY only — it does not write the presence stamp
|
|
53
|
+
* (`user_activity`, UserActivityTable's contract): this build runs for an idle tab's polls, a
|
|
54
|
+
* socket's room re-joins on every reconnect, the reload a deploy pushes onto every open tab —
|
|
55
|
+
* transport, not a person. Presence is written from the page's human-input report alone
|
|
56
|
+
* (UserPresence.recordPresence).
|
|
57
|
+
*/
|
|
51
58
|
exports.userCache = {
|
|
52
59
|
key: user_1.USER_SESSION_CACHE_KEY,
|
|
53
60
|
create: function (sessionId, userEmail) { return __awaiter(void 0, void 0, void 0, function () {
|
|
@@ -86,13 +93,6 @@ exports.userCache = {
|
|
|
86
93
|
else if (accountUser) {
|
|
87
94
|
delete accountUser['password'];
|
|
88
95
|
user = accountUser;
|
|
89
|
-
// LAST ACTIVITY (human presence — UserActivityTable's contract): stamped HERE because
|
|
90
|
-
// this cache build runs exactly once per session-cookie request (wrapRoute), i.e. only
|
|
91
|
-
// for interactive transport. Background/seeded contexts (runInUserScope) set session
|
|
92
|
-
// data directly and never pass through, so machinery acting as the user structurally
|
|
93
|
-
// cannot stamp; the stamp itself refuses machine accounts. Fire-and-forget: the
|
|
94
|
-
// returned promise never rejects, and a request never waits on its own stamp.
|
|
95
|
-
void userActivityStamp.recordInteractiveRequest(user);
|
|
96
96
|
}
|
|
97
97
|
else {
|
|
98
98
|
// A session can outlive its account (row deleted, or a dev auto-login for a never-created
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"userCache.js","sourceRoot":"","sources":["../../../src/authorization/userCache.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,kDAA4B;AAE5B,oCAA8C;AAC9C,4CAA2C;AAC3C,wCAAkF;AAClF,qFAAoF;
|
|
1
|
+
{"version":3,"file":"userCache.js","sourceRoot":"","sources":["../../../src/authorization/userCache.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,kDAA4B;AAE5B,oCAA8C;AAC9C,4CAA2C;AAC3C,wCAAkF;AAClF,qFAAoF;AAEpF,IAAM,MAAM,GAAG,IAAI,eAAM,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;AAEjD;;;;;;;;GAQG;AACU,QAAA,SAAS,GAA2B;IAC/C,GAAG,EAAE,6BAAsB;IAC3B,MAAM,EAAE,UAAO,SAAiB,EAAE,SAAiB;;;;;oBAC7C,IAAI,GAAG,gBAAS,CAAC;yBACjB,SAAS,EAAT,wBAAS;oBACL,gBAAgB,GAAG,iDAAuB,CAAC,cAAc,EAAE,CAAC;yBAC9D,CAAA,gBAAgB,IAAI,SAAS,IAAI,gBAAgB,CAAC,QAAQ,CAAA,EAA1D,wBAA0D;oBACtD,SAAS,GAAS;wBACtB,IAAI,EAAE,OAAO;wBACb,KAAK,EAAE,gBAAgB,CAAC,QAAQ;wBAChC,QAAQ,EAAE,gBAAgB,CAAC,QAAQ;wBACnC,aAAa,EAAE,IAAI;wBACnB,KAAK,EAAE,CAAC,OAAO,CAAC;wBAChB,OAAO,EAAE,IAAA,gBAAM,GAAE;wBACjB,OAAO,EAAE,IAAA,gBAAM,GAAE;wBACjB,EAAE,EAAE,OAAO;qBACZ,CAAC;oBACF,IAAI,GAAG,SAAS,CAAC;;wBAEG,qBAAM,IAAA,kBAAa,GAAE,CAAC,GAAG,CAAC,aAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,CAAC,WAAW,EAAE,EAAE,CAAC,EAAA;;oBAAxF,WAAW,GAAG,SAA0E;oBAC9F,IAAI,WAAW,IAAI,WAAW,CAAC,MAAM,KAAK,aAAa,EAAE;wBACvD,sFAAsF;wBACtF,iFAAiF;wBACjF,4EAA4E;wBAC5E,MAAM,CAAC,IAAI,CAAC;4BACV,OAAO,EAAE,wEAAwE;4BACjF,GAAG,EAAE,EAAE,SAAS,WAAA,EAAE,SAAS,WAAA,EAAE;yBAC9B,CAAC,CAAC;qBACJ;yBAAM,IAAI,WAAW,EAAE;wBACtB,OAAQ,WAAmB,CAAC,UAAU,CAAC,CAAC;wBACxC,IAAI,GAAG,WAAW,CAAC;qBACpB;yBAAM;wBACL,0FAA0F;wBAC1F,+EAA+E;wBAC/E,sFAAsF;wBACtF,yDAAyD;wBACzD,MAAM,CAAC,IAAI,CAAC;4BACV,OAAO,EAAE,iFAAiF;4BAC1F,GAAG,EAAE,EAAE,SAAS,WAAA,EAAE,SAAS,WAAA,EAAE;yBAC9B,CAAC,CAAC;qBACJ;;wBAIL,sBAAO,IAAI,EAAC;;;SACb;CACF,CAAC"}
|
|
@@ -23,6 +23,16 @@ import { Route } from '@proteinjs/server-api';
|
|
|
23
23
|
* interactive login works for the same identity. Composes with userCache's missing-account→guest
|
|
24
24
|
* seam: that covers sessions whose account was deleted AFTER minting; this ensures dev-minted
|
|
25
25
|
* sessions reference a real account from the start.
|
|
26
|
+
*
|
|
27
|
+
* First-admin door (`DEV_BOOTSTRAP_ADMIN_EMAIL`): a dev server on a FRESH real database has no
|
|
28
|
+
* privileged account and no sanctioned raw write to make one. Behind the same two gates, when
|
|
29
|
+
* the resolved address equals the variable exactly (case-normalized like every account email),
|
|
30
|
+
* the account this request created or loaded is granted the break-glass `admin` role — once,
|
|
31
|
+
* only while no account carries it, audited like any grant (`Roles.bootstrapAdmin`). Every other
|
|
32
|
+
* call is unchanged; the variable absent = nothing changes; the gates closed = 404 regardless.
|
|
33
|
+
* Test and prod never set it — the omission is the safety, the same idiom as the gates. The
|
|
34
|
+
* outcome is logged as ONE marker line, `Dev bootstrap admin door: <granted|admin-exists>`,
|
|
35
|
+
* which the n3xa compose-estate boot proof reads from the server log to PROVE the grant landed.
|
|
26
36
|
*/
|
|
27
37
|
export declare const devLogin: Route;
|
|
28
38
|
//# sourceMappingURL=devLogin.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devLogin.d.ts","sourceRoot":"","sources":["../../../src/routes/devLogin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"devLogin.d.ts","sourceRoot":"","sources":["../../../src/routes/devLogin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAa9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,QAAQ,EAAE,KAsDtB,CAAC"}
|
|
@@ -40,6 +40,7 @@ exports.devLogin = void 0;
|
|
|
40
40
|
var logger_1 = require("@proteinjs/logger");
|
|
41
41
|
var util_1 = require("@proteinjs/util");
|
|
42
42
|
var establishSession_1 = require("../authentication/establishSession");
|
|
43
|
+
var Roles_1 = require("../services/Roles");
|
|
43
44
|
var Signup_1 = require("../services/Signup");
|
|
44
45
|
var logger = new logger_1.Logger({ name: 'devLogin' });
|
|
45
46
|
/** Lowercased domain of an email address (the whole string when there is no `@`, which can never
|
|
@@ -69,15 +70,25 @@ var emailDomain = function (address) { return address.slice(address.lastIndexOf(
|
|
|
69
70
|
* interactive login works for the same identity. Composes with userCache's missing-account→guest
|
|
70
71
|
* seam: that covers sessions whose account was deleted AFTER minting; this ensures dev-minted
|
|
71
72
|
* sessions reference a real account from the start.
|
|
73
|
+
*
|
|
74
|
+
* First-admin door (`DEV_BOOTSTRAP_ADMIN_EMAIL`): a dev server on a FRESH real database has no
|
|
75
|
+
* privileged account and no sanctioned raw write to make one. Behind the same two gates, when
|
|
76
|
+
* the resolved address equals the variable exactly (case-normalized like every account email),
|
|
77
|
+
* the account this request created or loaded is granted the break-glass `admin` role — once,
|
|
78
|
+
* only while no account carries it, audited like any grant (`Roles.bootstrapAdmin`). Every other
|
|
79
|
+
* call is unchanged; the variable absent = nothing changes; the gates closed = 404 regardless.
|
|
80
|
+
* Test and prod never set it — the omission is the safety, the same idiom as the gates. The
|
|
81
|
+
* outcome is logged as ONE marker line, `Dev bootstrap admin door: <granted|admin-exists>`,
|
|
82
|
+
* which the n3xa compose-estate boot proof reads from the server log to PROVE the grant landed.
|
|
72
83
|
*/
|
|
73
84
|
exports.devLogin = {
|
|
74
85
|
path: '/dev/login',
|
|
75
86
|
method: 'get',
|
|
76
87
|
onRequest: function (request, response) { return __awaiter(void 0, void 0, void 0, function () {
|
|
77
|
-
var envEmail, email, emailParam, requested, creation;
|
|
78
|
-
var _a, _b;
|
|
79
|
-
return __generator(this, function (
|
|
80
|
-
switch (
|
|
88
|
+
var envEmail, email, emailParam, requested, creation, bootstrapEmail, outcome;
|
|
89
|
+
var _a, _b, _c;
|
|
90
|
+
return __generator(this, function (_d) {
|
|
91
|
+
switch (_d.label) {
|
|
81
92
|
case 0:
|
|
82
93
|
envEmail = ((_a = process.env.DEV_AUTO_LOGIN_EMAIL) !== null && _a !== void 0 ? _a : '').trim();
|
|
83
94
|
if (!process.env.DEVELOPMENT || !envEmail) {
|
|
@@ -110,17 +121,25 @@ exports.devLogin = {
|
|
|
110
121
|
invitedBy: null,
|
|
111
122
|
})];
|
|
112
123
|
case 1:
|
|
113
|
-
creation =
|
|
124
|
+
creation = _d.sent();
|
|
114
125
|
if (creation === 'created') {
|
|
115
126
|
logger.info({ message: 'Dev auto-login created missing test account', obj: { email: email } });
|
|
116
127
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return [4 /*yield*/,
|
|
128
|
+
bootstrapEmail = ((_c = process.env.DEV_BOOTSTRAP_ADMIN_EMAIL) !== null && _c !== void 0 ? _c : '').trim().toLowerCase();
|
|
129
|
+
if (!(bootstrapEmail && email === bootstrapEmail)) return [3 /*break*/, 3];
|
|
130
|
+
return [4 /*yield*/, new Roles_1.Roles().bootstrapAdmin(email)];
|
|
120
131
|
case 2:
|
|
132
|
+
outcome = _d.sent();
|
|
133
|
+
logger.info({ message: "Dev bootstrap admin door: ".concat(outcome), obj: { email: email } });
|
|
134
|
+
_d.label = 3;
|
|
135
|
+
case 3:
|
|
136
|
+
// establishSession commits the session row before the redirect — the redirected GET / must
|
|
137
|
+
// never read the store ahead of the write (observed: first /dev/login load landed on /login).
|
|
138
|
+
return [4 /*yield*/, (0, establishSession_1.establishSession)(request, email)];
|
|
139
|
+
case 4:
|
|
121
140
|
// establishSession commits the session row before the redirect — the redirected GET / must
|
|
122
141
|
// never read the store ahead of the write (observed: first /dev/login load landed on /login).
|
|
123
|
-
|
|
142
|
+
_d.sent();
|
|
124
143
|
logger.info({ message: 'Dev auto-login session established', obj: { email: email } });
|
|
125
144
|
response.redirect('/');
|
|
126
145
|
return [2 /*return*/];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devLogin.js","sourceRoot":"","sources":["../../../src/routes/devLogin.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,4CAA2C;AAC3C,wCAA6C;AAC7C,uEAAsE;AACtE,6CAA4C;AAE5C,IAAM,MAAM,GAAG,IAAI,eAAM,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;AAEhD;kFACkF;AAClF,IAAM,WAAW,GAAG,UAAC,OAAe,IAAK,OAAA,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,EAAzD,CAAyD,CAAC;AAEnG
|
|
1
|
+
{"version":3,"file":"devLogin.js","sourceRoot":"","sources":["../../../src/routes/devLogin.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,9 @@ import { Service } from '@proteinjs/service';
|
|
|
6
6
|
* audited: the role update and its `role_grant_event` row (actor, target, role, action; `created`
|
|
7
7
|
* is the timestamp) commit in one transaction, so the trail cannot diverge from the grants.
|
|
8
8
|
*
|
|
9
|
-
* Break-glass roles are never granted
|
|
9
|
+
* Break-glass roles are never granted through the service door — see `changeRole`; revoking one
|
|
10
|
+
* stays allowed. The ONE break-glass grant in code is `bootstrapAdmin`, the dev first-admin door
|
|
11
|
+
* (server-internal; reachable only through `/dev/login`'s gates, never over RPC).
|
|
10
12
|
*
|
|
11
13
|
* Nobody edits their OWN roles: separating 'roles' from 'users' means nothing if the holder can
|
|
12
14
|
* simply grant themselves more, and a self-revoke is the mirror hazard (the last holder locking
|
|
@@ -16,6 +18,23 @@ export declare class Roles implements RolesService {
|
|
|
16
18
|
serviceMetadata: Service['serviceMetadata'];
|
|
17
19
|
grantRole(userId: string, role: string): Promise<void>;
|
|
18
20
|
revokeRole(userId: string, role: string): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* The dev first-admin door's grant — `/dev/login` honoring `DEV_BOOTSTRAP_ADMIN_EMAIL`
|
|
23
|
+
* (routes/devLogin.ts). A fresh dev database has no privileged account to grant from, and the
|
|
24
|
+
* only other path to break-glass is a manual UPDATE in Spanner Studio — right for prod and every
|
|
25
|
+
* clone-backed instance, wrong for a lane's throwaway estate on a real dev database, where raw
|
|
26
|
+
* writes are forbidden. So this grants 'admin' to `email`'s account exactly when NO account
|
|
27
|
+
* carries it yet (the membership test `UserAuth.hasRole` makes, over every row), audited like
|
|
28
|
+
* any grant — actor = the account itself, because the door acts for nobody else. Once an admin
|
|
29
|
+
* exists it never grants again: every later grant rides Admin → Users.
|
|
30
|
+
*
|
|
31
|
+
* Server-internal, like `Signup.createAccount`: absent from `RolesService`, so never
|
|
32
|
+
* RPC-reachable. The caller's two gates (DEVELOPMENT + DEV_AUTO_LOGIN_EMAIL) are the only way
|
|
33
|
+
* in, and test/prod never set the variable.
|
|
34
|
+
*/
|
|
35
|
+
bootstrapAdmin(email: string): Promise<'granted' | 'admin-exists' | 'no-account'>;
|
|
19
36
|
private changeRole;
|
|
37
|
+
/** Whether any account carries the break-glass role — `UserAuth.hasRole`'s membership test, over every row. */
|
|
38
|
+
private adminExists;
|
|
20
39
|
}
|
|
21
40
|
//# sourceMappingURL=Roles.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Roles.d.ts","sourceRoot":"","sources":["../../../src/services/Roles.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAA8D,MAAM,iBAAiB,CAAC;AAE3G,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C
|
|
1
|
+
{"version":3,"file":"Roles.d.ts","sourceRoot":"","sources":["../../../src/services/Roles.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAA8D,MAAM,iBAAiB,CAAC;AAE3G,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C;;;;;;;;;;;;;GAaG;AACH,qBAAa,KAAM,YAAW,YAAY;IACjC,eAAe,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAIhD;IAEI,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7D;;;;;;;;;;;;;OAaG;IACG,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,cAAc,GAAG,YAAY,CAAC;YAqBzE,UAAU;IA8ExB,+GAA+G;YACjG,WAAW;CAK1B"}
|
|
@@ -55,7 +55,9 @@ var logger_1 = require("@proteinjs/logger");
|
|
|
55
55
|
* audited: the role update and its `role_grant_event` row (actor, target, role, action; `created`
|
|
56
56
|
* is the timestamp) commit in one transaction, so the trail cannot diverge from the grants.
|
|
57
57
|
*
|
|
58
|
-
* Break-glass roles are never granted
|
|
58
|
+
* Break-glass roles are never granted through the service door — see `changeRole`; revoking one
|
|
59
|
+
* stays allowed. The ONE break-glass grant in code is `bootstrapAdmin`, the dev first-admin door
|
|
60
|
+
* (server-internal; reachable only through `/dev/login`'s gates, never over RPC).
|
|
59
61
|
*
|
|
60
62
|
* Nobody edits their OWN roles: separating 'roles' from 'users' means nothing if the holder can
|
|
61
63
|
* simply grant themselves more, and a self-revoke is the mirror hazard (the last holder locking
|
|
@@ -93,6 +95,63 @@ var Roles = /** @class */ (function () {
|
|
|
93
95
|
});
|
|
94
96
|
});
|
|
95
97
|
};
|
|
98
|
+
/**
|
|
99
|
+
* The dev first-admin door's grant — `/dev/login` honoring `DEV_BOOTSTRAP_ADMIN_EMAIL`
|
|
100
|
+
* (routes/devLogin.ts). A fresh dev database has no privileged account to grant from, and the
|
|
101
|
+
* only other path to break-glass is a manual UPDATE in Spanner Studio — right for prod and every
|
|
102
|
+
* clone-backed instance, wrong for a lane's throwaway estate on a real dev database, where raw
|
|
103
|
+
* writes are forbidden. So this grants 'admin' to `email`'s account exactly when NO account
|
|
104
|
+
* carries it yet (the membership test `UserAuth.hasRole` makes, over every row), audited like
|
|
105
|
+
* any grant — actor = the account itself, because the door acts for nobody else. Once an admin
|
|
106
|
+
* exists it never grants again: every later grant rides Admin → Users.
|
|
107
|
+
*
|
|
108
|
+
* Server-internal, like `Signup.createAccount`: absent from `RolesService`, so never
|
|
109
|
+
* RPC-reachable. The caller's two gates (DEVELOPMENT + DEV_AUTO_LOGIN_EMAIL) are the only way
|
|
110
|
+
* in, and test/prod never set the variable.
|
|
111
|
+
*/
|
|
112
|
+
Roles.prototype.bootstrapAdmin = function (email) {
|
|
113
|
+
var _a;
|
|
114
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
115
|
+
var logger, db, user, roles;
|
|
116
|
+
var _this = this;
|
|
117
|
+
return __generator(this, function (_b) {
|
|
118
|
+
switch (_b.label) {
|
|
119
|
+
case 0:
|
|
120
|
+
logger = new logger_1.Logger({ name: 'Roles.bootstrapAdmin' });
|
|
121
|
+
db = (0, db_1.getDbAsSystem)();
|
|
122
|
+
return [4 /*yield*/, this.adminExists()];
|
|
123
|
+
case 1:
|
|
124
|
+
if (_b.sent()) {
|
|
125
|
+
return [2 /*return*/, 'admin-exists'];
|
|
126
|
+
}
|
|
127
|
+
return [4 /*yield*/, db.get(user_1.tables.User, { email: email.toLowerCase() })];
|
|
128
|
+
case 2:
|
|
129
|
+
user = _b.sent();
|
|
130
|
+
if (!user) {
|
|
131
|
+
return [2 /*return*/, 'no-account'];
|
|
132
|
+
}
|
|
133
|
+
roles = (_a = user.roles) !== null && _a !== void 0 ? _a : [];
|
|
134
|
+
return [4 /*yield*/, db.runTransaction(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
135
|
+
return __generator(this, function (_a) {
|
|
136
|
+
switch (_a.label) {
|
|
137
|
+
case 0: return [4 /*yield*/, db.update(user_1.tables.User, { id: user.id, roles: __spreadArray(__spreadArray([], roles, true), ['admin'], false) })];
|
|
138
|
+
case 1:
|
|
139
|
+
_a.sent();
|
|
140
|
+
return [4 /*yield*/, db.insert(user_1.tables.RoleGrantEvent, { actor: user.id, target: user.id, role: 'admin', action: 'grant' })];
|
|
141
|
+
case 2:
|
|
142
|
+
_a.sent();
|
|
143
|
+
return [2 /*return*/];
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}); })];
|
|
147
|
+
case 3:
|
|
148
|
+
_b.sent();
|
|
149
|
+
logger.info({ message: 'Break-glass admin granted by the dev first-admin door', obj: { target: user.id, email: email } });
|
|
150
|
+
return [2 /*return*/, 'granted'];
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
};
|
|
96
155
|
Roles.prototype.changeRole = function (userId, role, action) {
|
|
97
156
|
var _a;
|
|
98
157
|
return __awaiter(this, void 0, void 0, function () {
|
|
@@ -108,8 +167,9 @@ var Roles = /** @class */ (function () {
|
|
|
108
167
|
}
|
|
109
168
|
// Break-glass passes every permission check, so no permission-mapped role — including
|
|
110
169
|
// 'roles' — may mint it: the only path to break-glass is a manual UPDATE on the user row
|
|
111
|
-
// in Spanner Studio, by a human, on purpose
|
|
112
|
-
// "held by nobody day-to-day"
|
|
170
|
+
// in Spanner Studio, by a human, on purpose (and, on a DEV server only, the first-admin door
|
|
171
|
+
// `bootstrapAdmin`). Revoke stays open: de-escalation toward "held by nobody day-to-day"
|
|
172
|
+
// should ride the audited path, not require database access.
|
|
113
173
|
if (entry.breakGlass && action === 'grant') {
|
|
114
174
|
throw new Error("'".concat(role, "' is a break-glass role \u2014 this service refuses to grant it. The only path to ") +
|
|
115
175
|
"break-glass is a manual UPDATE on the user row in Spanner Studio. (Revoking it here " +
|
|
@@ -180,6 +240,22 @@ var Roles = /** @class */ (function () {
|
|
|
180
240
|
});
|
|
181
241
|
});
|
|
182
242
|
};
|
|
243
|
+
/** Whether any account carries the break-glass role — `UserAuth.hasRole`'s membership test, over every row. */
|
|
244
|
+
Roles.prototype.adminExists = function () {
|
|
245
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
246
|
+
var qb, users;
|
|
247
|
+
return __generator(this, function (_a) {
|
|
248
|
+
switch (_a.label) {
|
|
249
|
+
case 0:
|
|
250
|
+
qb = new db_1.QueryBuilderFactory().getQueryBuilder(user_1.tables.User).select({ fields: ['id', 'roles'] });
|
|
251
|
+
return [4 /*yield*/, (0, db_1.getDbAsSystem)().query(user_1.tables.User, qb)];
|
|
252
|
+
case 1:
|
|
253
|
+
users = _a.sent();
|
|
254
|
+
return [2 /*return*/, users.some(function (user) { var _a; return ((_a = user.roles) !== null && _a !== void 0 ? _a : []).includes('admin'); })];
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
};
|
|
183
259
|
return Roles;
|
|
184
260
|
}());
|
|
185
261
|
exports.Roles = Roles;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Roles.js","sourceRoot":"","sources":["../../../src/services/Roles.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,
|
|
1
|
+
{"version":3,"file":"Roles.js","sourceRoot":"","sources":["../../../src/services/Roles.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oCAAmE;AACnE,wCAA2G;AAC3G,4CAA2C;AAG3C;;;;;;;;;;;;;GAaG;AACH;IAAA;QACS,oBAAe,GAA+B;YACnD,IAAI,EAAE;gBACJ,UAAU,EAAE,uBAAgB,CAAC,KAAK;aACnC;SACF,CAAC;IAiIJ,CAAC;IA/HO,yBAAS,GAAf,UAAgB,MAAc,EAAE,IAAY;;;;4BAC1C,qBAAM,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,EAAA;;wBAA5C,SAA4C,CAAC;;;;;KAC9C;IAEK,0BAAU,GAAhB,UAAiB,MAAc,EAAE,IAAY;;;;4BAC3C,qBAAM,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAA;;wBAA7C,SAA6C,CAAC;;;;;KAC/C;IAED;;;;;;;;;;;;;OAaG;IACG,8BAAc,GAApB,UAAqB,KAAa;;;;;;;;wBAC1B,MAAM,GAAG,IAAI,eAAM,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,CAAC,CAAC;wBACtD,EAAE,GAAG,IAAA,kBAAa,GAAE,CAAC;wBACvB,qBAAM,IAAI,CAAC,WAAW,EAAE,EAAA;;wBAA5B,IAAI,SAAwB,EAAE;4BAC5B,sBAAO,cAAc,EAAC;yBACvB;wBAEY,qBAAM,EAAE,CAAC,GAAG,CAAC,aAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC,EAAA;;wBAAhE,IAAI,GAAG,SAAyD;wBACtE,IAAI,CAAC,IAAI,EAAE;4BACT,sBAAO,YAAY,EAAC;yBACrB;wBAEK,KAAK,GAAG,MAAA,IAAI,CAAC,KAAK,mCAAI,EAAE,CAAC;wBAC/B,qBAAM,EAAE,CAAC,cAAc,CAAC;;;gDACtB,qBAAM,EAAE,CAAC,MAAM,CAAC,aAAM,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,kCAAM,KAAK,UAAE,OAAO,SAAC,EAAE,CAAC,EAAA;;4CAAzE,SAAyE,CAAC;4CAC1E,qBAAM,EAAE,CAAC,MAAM,CAAC,aAAM,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAA;;4CAA3G,SAA2G,CAAC;;;;iCAC7G,CAAC,EAAA;;wBAHF,SAGE,CAAC;wBACH,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,uDAAuD,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,OAAA,EAAE,EAAE,CAAC,CAAC;wBACnH,sBAAO,SAAS,EAAC;;;;KAClB;IAEa,0BAAU,GAAxB,UAAyB,MAAc,EAAE,IAAY,EAAE,MAA0B;;;;;;;;wBACzE,MAAM,GAAG,IAAI,eAAM,CAAC,EAAE,IAAI,EAAE,gBAAS,MAAM,SAAM,EAAE,CAAC,CAAC;wBACrD,KAAK,GAAG,mBAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;wBAC1C,IAAI,CAAC,KAAK,EAAE;4BACV,MAAM,IAAI,KAAK,CAAC,WAAI,IAAI,4DAAyD,CAAC,CAAC;yBACpF;wBAED,sFAAsF;wBACtF,yFAAyF;wBACzF,6FAA6F;wBAC7F,yFAAyF;wBACzF,6DAA6D;wBAC7D,IAAI,KAAK,CAAC,UAAU,IAAI,MAAM,KAAK,OAAO,EAAE;4BAC1C,MAAM,IAAI,KAAK,CACb,WAAI,IAAI,uFAA+E;gCACrF,sFAAsF;gCACtF,iBAAiB,CACpB,CAAC;yBACH;wBAED,2FAA2F;wBAC3F,6FAA6F;wBAC7F,yFAAyF;wBACzF,2FAA2F;wBAC3F,IAAI,KAAK,CAAC,cAAc,IAAI,MAAM,KAAK,OAAO,IAAI,CAAC,eAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;4BAC5E,MAAM,IAAI,KAAK,CACb,WAAI,IAAI,mFAA2E;gCACjF,mCAAmC,CACtC,CAAC;yBACH;wBAEK,EAAE,GAAG,IAAA,kBAAa,GAAE,CAAC;wBACd,qBAAM,EAAE,CAAC,GAAG,CAAC,aAAM,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAA;;wBAAhD,IAAI,GAAG,SAAyC;wBACtD,IAAI,CAAC,IAAI,EAAE;4BACT,MAAM,IAAI,KAAK,CAAC,gCAAyB,MAAM,CAAE,CAAC,CAAC;yBACpD;wBAED,4FAA4F;wBAC5F,4FAA4F;wBAC5F,iFAAiF;wBACjF,IAAI,IAAI,CAAC,kBAAkB,KAAK,IAAI,EAAE;4BACpC,MAAM,IAAI,KAAK,CACb,WAAI,IAAI,CAAC,KAAK,gFAA6E;gCACzF,6FAA6F,CAChG,CAAC;yBACH;wBAEK,KAAK,GAAG,IAAI,eAAQ,EAAE,CAAC,OAAO,EAAE,CAAC;wBACvC,wFAAwF;wBACxF,0FAA0F;wBAC1F,2EAA2E;wBAC3E,IAAI,MAAM,KAAK,KAAK,CAAC,EAAE,EAAE;4BACvB,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,qCAAqC,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,MAAA,EAAE,MAAM,QAAA,EAAE,EAAE,CAAC,CAAC;4BACxG,MAAM,IAAI,KAAK,CAAC,oBAAa,MAAM,qDAA6C,CAAC,CAAC;yBACnF;wBAEK,KAAK,GAAG,MAAA,IAAI,CAAC,KAAK,mCAAI,EAAE,CAAC;wBAC/B,IAAI,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;4BACrE,mFAAmF;4BACnF,sBAAO;yBACR;wBAEK,QAAQ,GAAG,MAAM,KAAK,OAAO,CAAC,CAAC,iCAAK,KAAK,UAAE,IAAI,UAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,UAAC,QAAQ,IAAK,OAAA,QAAQ,KAAK,IAAI,EAAjB,CAAiB,CAAC,CAAC;wBACvG,qBAAM,EAAE,CAAC,cAAc,CAAC;;;gDACtB,qBAAM,EAAE,CAAC,MAAM,CAAC,aAAM,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAA;;4CAA7D,SAA6D,CAAC;4CAC9D,qBAAM,EAAE,CAAC,MAAM,CAAC,aAAM,CAAC,cAAc,EAAE;oDACrC,KAAK,EAAE,KAAK,CAAC,EAAE;oDACf,MAAM,EAAE,MAAM;oDACd,IAAI,MAAA;oDACJ,MAAM,QAAA;iDACP,CAAC,EAAA;;4CALF,SAKE,CAAC;;;;iCACJ,CAAC,EAAA;;wBARF,SAQE,CAAC;wBACH,MAAM,CAAC,IAAI,CAAC;4BACV,OAAO,EAAE,eAAQ,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAE;4BAC7D,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,MAAA,EAAE;yBAC/C,CAAC,CAAC;;;;;KACJ;IAED,+GAA+G;IACjG,2BAAW,GAAzB;;;;;;wBACQ,EAAE,GAAG,IAAI,wBAAmB,EAAE,CAAC,eAAe,CAAC,aAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;wBACxF,qBAAM,IAAA,kBAAa,GAAE,CAAC,KAAK,CAAC,aAAM,CAAC,IAAI,EAAE,EAAE,CAAC,EAAA;;wBAApD,KAAK,GAAG,SAA4C;wBAC1D,sBAAO,KAAK,CAAC,IAAI,CAAC,UAAC,IAAI,YAAK,OAAA,CAAC,MAAA,IAAI,CAAC,KAAK,mCAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA,EAAA,CAAC,EAAC;;;;KACnE;IACH,YAAC;AAAD,CAAC,AAtID,IAsIC;AAtIY,sBAAK"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Service } from '@proteinjs/service';
|
|
2
|
+
import { UserPresenceService } from '@proteinjs/user';
|
|
3
|
+
/**
|
|
4
|
+
* The server end of the human-input presence door (UserActivityTable's contract): the page
|
|
5
|
+
* reports "a person interacted just now" and this writes the `user_activity` stamp for the
|
|
6
|
+
* CALLING user — the one write path onto presence. Any signed-in user may report their own
|
|
7
|
+
* presence and nobody else's (the user is the session's, never an argument); the guest identity
|
|
8
|
+
* and machine accounts are refused inside the stamp. `doNotAwait`: the page never waits on its
|
|
9
|
+
* own stamp, and a lost stamp is minutes of staleness at day grain, never a failed call.
|
|
10
|
+
*/
|
|
11
|
+
export declare class UserPresence implements UserPresenceService {
|
|
12
|
+
serviceMetadata: Service['serviceMetadata'];
|
|
13
|
+
private stamp;
|
|
14
|
+
recordPresence(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=UserPresence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UserPresence.d.ts","sourceRoot":"","sources":["../../../src/services/UserPresence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAuB,MAAM,iBAAiB,CAAC;AAG3E;;;;;;;GAOG;AACH,qBAAa,YAAa,YAAW,mBAAmB;IAC/C,eAAe,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAKhD;IAEF,OAAO,CAAC,KAAK,CAA2B;IAElC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;CAGtC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
13
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.UserPresence = void 0;
|
|
40
|
+
var user_1 = require("@proteinjs/user");
|
|
41
|
+
var UserActivityStamp_1 = require("../authorization/UserActivityStamp");
|
|
42
|
+
/**
|
|
43
|
+
* The server end of the human-input presence door (UserActivityTable's contract): the page
|
|
44
|
+
* reports "a person interacted just now" and this writes the `user_activity` stamp for the
|
|
45
|
+
* CALLING user — the one write path onto presence. Any signed-in user may report their own
|
|
46
|
+
* presence and nobody else's (the user is the session's, never an argument); the guest identity
|
|
47
|
+
* and machine accounts are refused inside the stamp. `doNotAwait`: the page never waits on its
|
|
48
|
+
* own stamp, and a lost stamp is minutes of staleness at day grain, never a failed call.
|
|
49
|
+
*/
|
|
50
|
+
var UserPresence = /** @class */ (function () {
|
|
51
|
+
function UserPresence() {
|
|
52
|
+
this.serviceMetadata = {
|
|
53
|
+
auth: {
|
|
54
|
+
allUsers: true,
|
|
55
|
+
},
|
|
56
|
+
doNotAwait: true,
|
|
57
|
+
};
|
|
58
|
+
this.stamp = new UserActivityStamp_1.UserActivityStamp();
|
|
59
|
+
}
|
|
60
|
+
UserPresence.prototype.recordPresence = function () {
|
|
61
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
62
|
+
return __generator(this, function (_a) {
|
|
63
|
+
switch (_a.label) {
|
|
64
|
+
case 0: return [4 /*yield*/, this.stamp.recordHumanInput(new user_1.UserRepo().getUser())];
|
|
65
|
+
case 1:
|
|
66
|
+
_a.sent();
|
|
67
|
+
return [2 /*return*/];
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
return UserPresence;
|
|
73
|
+
}());
|
|
74
|
+
exports.UserPresence = UserPresence;
|
|
75
|
+
//# sourceMappingURL=UserPresence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UserPresence.js","sourceRoot":"","sources":["../../../src/services/UserPresence.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,wCAA2E;AAC3E,wEAAuE;AAEvE;;;;;;;GAOG;AACH;IAAA;QACS,oBAAe,GAA+B;YACnD,IAAI,EAAE;gBACJ,QAAQ,EAAE,IAAI;aACf;YACD,UAAU,EAAE,IAAI;SACjB,CAAC;QAEM,UAAK,GAAG,IAAI,qCAAiB,EAAE,CAAC;IAK1C,CAAC;IAHO,qCAAc,GAApB;;;;4BACE,qBAAM,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,IAAI,eAAQ,EAAE,CAAC,OAAO,EAAkC,CAAC,EAAA;;wBAA3F,SAA2F,CAAC;;;;;KAC7F;IACH,mBAAC;AAAD,CAAC,AAbD,IAaC;AAbY,oCAAY"}
|