@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.
Files changed (52) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/generated/index.d.ts.map +1 -1
  3. package/dist/generated/index.js +3 -1
  4. package/dist/generated/index.js.map +1 -1
  5. package/dist/src/authorization/UserActivityStamp.d.ts +17 -15
  6. package/dist/src/authorization/UserActivityStamp.d.ts.map +1 -1
  7. package/dist/src/authorization/UserActivityStamp.js +21 -19
  8. package/dist/src/authorization/UserActivityStamp.js.map +1 -1
  9. package/dist/src/authorization/userCache.d.ts +9 -0
  10. package/dist/src/authorization/userCache.d.ts.map +1 -1
  11. package/dist/src/authorization/userCache.js +9 -9
  12. package/dist/src/authorization/userCache.js.map +1 -1
  13. package/dist/src/routes/devLogin.d.ts +10 -0
  14. package/dist/src/routes/devLogin.d.ts.map +1 -1
  15. package/dist/src/routes/devLogin.js +28 -9
  16. package/dist/src/routes/devLogin.js.map +1 -1
  17. package/dist/src/services/Roles.d.ts +20 -1
  18. package/dist/src/services/Roles.d.ts.map +1 -1
  19. package/dist/src/services/Roles.js +79 -3
  20. package/dist/src/services/Roles.js.map +1 -1
  21. package/dist/src/services/UserPresence.d.ts +16 -0
  22. package/dist/src/services/UserPresence.d.ts.map +1 -0
  23. package/dist/src/services/UserPresence.js +75 -0
  24. package/dist/src/services/UserPresence.js.map +1 -0
  25. package/dist/test/DevLogin.test.js +13 -41
  26. package/dist/test/DevLogin.test.js.map +1 -1
  27. package/dist/test/DevLoginBootstrapAdmin.test.d.ts +2 -0
  28. package/dist/test/DevLoginBootstrapAdmin.test.d.ts.map +1 -0
  29. package/dist/test/DevLoginBootstrapAdmin.test.js +384 -0
  30. package/dist/test/DevLoginBootstrapAdmin.test.js.map +1 -0
  31. package/dist/test/UserActivityStamp.integration.test.js +57 -90
  32. package/dist/test/UserActivityStamp.integration.test.js.map +1 -1
  33. package/dist/test/UserCacheDoesNotStampPresence.integration.test.d.ts +2 -0
  34. package/dist/test/UserCacheDoesNotStampPresence.integration.test.d.ts.map +1 -0
  35. package/dist/test/UserCacheDoesNotStampPresence.integration.test.js +117 -0
  36. package/dist/test/UserCacheDoesNotStampPresence.integration.test.js.map +1 -0
  37. package/dist/test/devLoginHarness.d.ts +16 -0
  38. package/dist/test/devLoginHarness.d.ts.map +1 -0
  39. package/dist/test/devLoginHarness.js +79 -0
  40. package/dist/test/devLoginHarness.js.map +1 -0
  41. package/generated/index.ts +3 -1
  42. package/package.json +3 -3
  43. package/src/authorization/UserActivityStamp.ts +22 -20
  44. package/src/authorization/userCache.ts +9 -9
  45. package/src/routes/devLogin.ts +17 -0
  46. package/src/services/Roles.ts +49 -4
  47. package/src/services/UserPresence.ts +26 -0
  48. package/test/DevLogin.test.ts +5 -36
  49. package/test/DevLoginBootstrapAdmin.test.ts +175 -0
  50. package/test/UserActivityStamp.integration.test.ts +38 -50
  51. package/test/UserCacheDoesNotStampPresence.integration.test.ts +44 -0
  52. 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 human made an interactive request just now".
49
+ * UserActivityTable's contract): "this person gave a page input just now".
50
50
  *
51
- * The TRANSPORT keying lives at the call site — userCache.create runs once per session-cookie
52
- * request (wrapRoute's session-cache build) and only there, so seeded/background contexts
53
- * (runInUserScope sets session data directly) structurally never reach this class. What this
54
- * class owns is the ACCOUNT predicate: machine accounts (`isLoadedFromSource`) are refused —
55
- * their requests do arrive over real sessions (e.g. the error bridge's polling), but a machine
56
- * logging in is not a person being present.
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 an unthrottled stamp would put a write on EVERY request),
60
- * fail-open (a lost stamp is a few minutes of staleness, never a failed request — the returned
61
- * promise NEVER rejects), and race-tolerant (concurrent first stamps contend on the scope-unique
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` is present on an interactive request. Fire-and-forget safe: errors are
70
- * handled (and logged) here, so callers may `void` the returned promise.
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.recordInteractiveRequest = function (user) {
74
+ UserActivityStamp.prototype.recordHumanInput = function (user) {
73
75
  var _this = this;
74
76
  var _a;
75
- if (!user.id || user.isLoadedFromSource === true) {
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 request stamped first, racing the scope-unique index) and real
89
- // failures land here alike; both are harmless to the request. Un-throttle so the next
90
- // request retries instead of waiting out a full interval on a stamp that never landed.
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: userCache is a plain object, so throttle state can't live per-instance. */
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,wCAAuE;AAEvE;;;;;;;;;;;;;;;;GAgBG;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,oDAAwB,GAAxB,UAAyB,IAAU;QAAnC,iBAoBC;;QAnBC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,kBAAkB,KAAK,IAAI,EAAE;YAChD,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,0FAA0F;YAC1F,sFAAsF;YACtF,uFAAuF;YACvF,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,4FAA4F;IAC7E,6BAAW,GAAG,IAAI,GAAG,EAAkB,AAA5B,CAA6B;IAuCzD,wBAAC;CAAA,AA3CD,IA2CC;AA3CY,8CAAiB"}
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;AAOlF,eAAO,MAAM,SAAS,EAAE,gBAAgB,CAAC,IAAI,CAqD5C,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
- var userActivityStamp = new UserActivityStamp_1.UserActivityStamp();
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;AACpF,yDAAwD;AAExD,IAAM,MAAM,GAAG,IAAI,eAAM,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;AACjD,IAAM,iBAAiB,GAAG,IAAI,qCAAiB,EAAE,CAAC;AAErC,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;wBACnB,sFAAsF;wBACtF,uFAAuF;wBACvF,qFAAqF;wBACrF,qFAAqF;wBACrF,gFAAgF;wBAChF,8EAA8E;wBAC9E,KAAK,iBAAiB,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC;qBACvD;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"}
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;AAY9C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,QAAQ,EAAE,KAgDtB,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 (_c) {
80
- switch (_c.label) {
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 = _c.sent();
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
- // establishSession commits the session row before the redirect — the redirected GET / must
118
- // never read the store ahead of the write (observed: first /dev/login load landed on /login).
119
- return [4 /*yield*/, (0, establishSession_1.establishSession)(request, email)];
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
- _c.sent();
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;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACU,QAAA,QAAQ,GAAU;IAC7B,IAAI,EAAE,YAAY;IAClB,MAAM,EAAE,KAAK;IACb,SAAS,EAAE,UAAO,OAAY,EAAE,QAAQ;;;;;;oBAChC,QAAQ,GAAG,CAAC,MAAA,OAAO,CAAC,GAAG,CAAC,oBAAoB,mCAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;oBACjE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,CAAC,QAAQ,EAAE;wBACzC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;wBAC5B,sBAAO;qBACR;oBAEG,KAAK,GAAG,QAAQ,CAAC;oBACf,UAAU,GAAG,MAAA,OAAO,CAAC,KAAK,0CAAE,KAAK,CAAC;oBACxC,IAAI,UAAU,KAAK,SAAS,EAAE;wBACtB,SAAS,GAAG,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;wBAC1E,IAAI,WAAW,CAAC,SAAS,CAAC,KAAK,WAAW,CAAC,QAAQ,CAAC,EAAE;4BACpD,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,mDAA4C,WAAW,CAAC,QAAQ,CAAC,YAAS,CAAC,CAAC;4BACtG,sBAAO;yBACR;wBACD,IAAI,CAAC,iBAAU,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE;4BAC/B,QAAQ;iCACL,MAAM,CAAC,GAAG,CAAC;iCACX,IAAI,CACH,wBAAgB,SAAS,4EAAiE;gCACxF,uEAAgE,WAAW,CAAC,QAAQ,CAAC,MAAG,CAC3F,CAAC;4BACJ,sBAAO;yBACR;wBACD,KAAK,GAAG,SAAS,CAAC;qBACnB;oBACD,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;oBAEX,qBAAM,IAAI,eAAM,EAAE,CAAC,aAAa,CAAC;4BAChD,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;4BACxC,KAAK,OAAA;4BACL,QAAQ,EAAE,MAAM;4BAChB,aAAa,EAAE,KAAK;4BACpB,SAAS,EAAE,IAAI;yBAChB,CAAC,EAAA;;oBANI,QAAQ,GAAG,SAMf;oBACF,IAAI,QAAQ,KAAK,SAAS,EAAE;wBAC1B,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,6CAA6C,EAAE,GAAG,EAAE,EAAE,KAAK,OAAA,EAAE,EAAE,CAAC,CAAC;qBACzF;oBAED,2FAA2F;oBAC3F,8FAA8F;oBAC9F,qBAAM,IAAA,mCAAgB,EAAC,OAAO,EAAE,KAAK,CAAC,EAAA;;oBAFtC,2FAA2F;oBAC3F,8FAA8F;oBAC9F,SAAsC,CAAC;oBACvC,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,oCAAoC,EAAE,GAAG,EAAE,EAAE,KAAK,OAAA,EAAE,EAAE,CAAC,CAAC;oBAC/E,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;;;;SACxB;CACF,CAAC"}
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 here — see `changeRole`; revoking one stays allowed.
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;;;;;;;;;;;GAWG;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;YAI/C,UAAU;CA4EzB"}
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 here — see `changeRole`; revoking one stays allowed.
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. Revoke stays open: de-escalation toward
112
- // "held by nobody day-to-day" should ride the audited path, not require database access.
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":"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"}
1
+ {"version":3,"file":"Roles.js","sourceRoot":"","sources":["../../../src/services/Roles.ts"],"names":[],"mappings":"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"}
@@ -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"}