@ibgib/node-gib 0.0.18 → 0.0.20
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/serve-gib/handlers/api/identity/keystone-custodian-add-master-password.handler.d.mts +39 -0
- package/dist/serve-gib/handlers/api/identity/keystone-custodian-add-master-password.handler.d.mts.map +1 -0
- package/dist/serve-gib/handlers/api/identity/keystone-custodian-add-master-password.handler.mjs +197 -0
- package/dist/serve-gib/handlers/api/identity/keystone-custodian-add-master-password.handler.mjs.map +1 -0
- package/dist/serve-gib/handlers/api/identity/keystone-custodian-genesis.handler.d.mts.map +1 -1
- package/dist/serve-gib/handlers/api/identity/keystone-custodian-genesis.handler.mjs +6 -3
- package/dist/serve-gib/handlers/api/identity/keystone-custodian-genesis.handler.mjs.map +1 -1
- package/dist/serve-gib/handlers/api/identity/keystone-custodian-register-delegate.handler.d.mts.map +1 -1
- package/dist/serve-gib/handlers/api/identity/keystone-custodian-register-delegate.handler.mjs +5 -3
- package/dist/serve-gib/handlers/api/identity/keystone-custodian-register-delegate.handler.mjs.map +1 -1
- package/dist/serve-gib/handlers/api/keystone/sso-login.handler.d.mts.map +1 -1
- package/dist/serve-gib/handlers/api/keystone/sso-login.handler.mjs +11 -5
- package/dist/serve-gib/handlers/api/keystone/sso-login.handler.mjs.map +1 -1
- package/dist/serve-gib/identity-index/identity-index-service.d.mts +5 -1
- package/dist/serve-gib/identity-index/identity-index-service.d.mts.map +1 -1
- package/dist/serve-gib/identity-index/identity-index-service.mjs +10 -1
- package/dist/serve-gib/identity-index/identity-index-service.mjs.map +1 -1
- package/dist/serve-gib/index.d.mts +1 -0
- package/dist/serve-gib/index.d.mts.map +1 -1
- package/dist/serve-gib/index.mjs +3 -0
- package/dist/serve-gib/index.mjs.map +1 -1
- package/dist/serve-gib/path-constants.d.mts +4 -0
- package/dist/serve-gib/path-constants.d.mts.map +1 -1
- package/dist/serve-gib/path-constants.mjs +4 -0
- package/dist/serve-gib/path-constants.mjs.map +1 -1
- package/e2e/identity-custodian-lifecycle.e2e.md +37 -0
- package/e2e/identity-sso-lifecycle.e2e.md +9 -7
- package/package.json +3 -3
- package/src/serve-gib/handlers/api/identity/keystone-custodian-add-master-password.handler.mts +245 -0
- package/src/serve-gib/handlers/api/identity/keystone-custodian-add-master-password.respec.mts +167 -0
- package/src/serve-gib/handlers/api/identity/keystone-custodian-genesis.handler.mts +6 -3
- package/src/serve-gib/handlers/api/identity/keystone-custodian-register-delegate.handler.mts +5 -3
- package/src/serve-gib/handlers/api/identity/keystone-custodian-register-delegate.respec.mts +3 -3
- package/src/serve-gib/handlers/api/keystone/sso-login.handler.mts +12 -6
- package/src/serve-gib/identity-index/identity-index-service.mts +11 -1
- package/src/serve-gib/index.mts +3 -0
- package/src/serve-gib/path-constants.mts +5 -0
- package/data/identity_index/email/hashes/32/13/49/e64f32730f01a61ada211f29eaf0a210fc2386b668ad04d7e8a9b16766.json +0 -4
- package/dist/serve-gib/handlers/api/ibgib/ibgib-handler-types.d.mts +0 -41
- package/dist/serve-gib/handlers/api/ibgib/ibgib-handler-types.d.mts.map +0 -1
- package/dist/serve-gib/handlers/api/ibgib/ibgib-handler-types.mjs +0 -34
- package/dist/serve-gib/handlers/api/ibgib/ibgib-handler-types.mjs.map +0 -1
- package/dist/serve-gib/handlers/api/ibgib/ibgib.handler.d.mts +0 -26
- package/dist/serve-gib/handlers/api/ibgib/ibgib.handler.d.mts.map +0 -1
- package/dist/serve-gib/handlers/api/ibgib/ibgib.handler.mjs +0 -179
- package/dist/serve-gib/handlers/api/ibgib/ibgib.handler.mjs.map +0 -1
- package/src/serve-gib/handlers/api/ibgib/ibgib-handler-types.mts +0 -49
- package/src/serve-gib/handlers/api/ibgib/ibgib.handler.mts +0 -176
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-constants.d.mts","sourceRoot":"","sources":["../../src/serve-gib/path-constants.mts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,eAAO,MAAM,eAAe,OAAO,CAAC;AAEpC;;;GAGG;AACH,eAAO,MAAM,kBAAkB,UAK9B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB;IACzB;;OAEG;IACH,MAAM;IAEN;;OAEG;IACH,WAAW;IAEX;;OAEG;IACH,UAAU;IAEV;;OAEG;IACH,QAAQ;IAER;;OAEG;IACH,aAAa;IAEb;;OAEG;IACH,UAAU;IAEV;;OAEG;IACH,eAAe;IAEf;;;;;;;;OAQG;IACH,gBAAgB;IAEhB;;OAEG;IACH,eAAe;IAEf;;OAEG;IACH,oBAAoB;IAEpB;;;;;;;;;;;OAWG;IACH,aAAa;IACb;;OAEG;IACH,OAAO;IACP;;OAEG;IACH,iBAAiB;IACjB;;OAEG;IACH,kBAAkB;IAClB;;OAEG;IACH,mBAAmB;IACnB;;OAEG;IACH,aAAa;IACb;;OAEG;IACH,UAAU;IACV;;OAEG;IACH,uBAAuB;IAEvB;;OAEG;IACH,YAAY;IACZ,iBAAiB;IAEjB;;OAEG;IACH,0BAA0B;IAE1B;;OAEG;IACH,oCAAoC;
|
|
1
|
+
{"version":3,"file":"path-constants.d.mts","sourceRoot":"","sources":["../../src/serve-gib/path-constants.mts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,eAAO,MAAM,eAAe,OAAO,CAAC;AAEpC;;;GAGG;AACH,eAAO,MAAM,kBAAkB,UAK9B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB;IACzB;;OAEG;IACH,MAAM;IAEN;;OAEG;IACH,WAAW;IAEX;;OAEG;IACH,UAAU;IAEV;;OAEG;IACH,QAAQ;IAER;;OAEG;IACH,aAAa;IAEb;;OAEG;IACH,UAAU;IAEV;;OAEG;IACH,eAAe;IAEf;;;;;;;;OAQG;IACH,gBAAgB;IAEhB;;OAEG;IACH,eAAe;IAEf;;OAEG;IACH,oBAAoB;IAEpB;;;;;;;;;;;OAWG;IACH,aAAa;IACb;;OAEG;IACH,OAAO;IACP;;OAEG;IACH,iBAAiB;IACjB;;OAEG;IACH,kBAAkB;IAClB;;OAEG;IACH,mBAAmB;IACnB;;OAEG;IACH,aAAa;IACb;;OAEG;IACH,UAAU;IACV;;OAEG;IACH,uBAAuB;IAEvB;;OAEG;IACH,YAAY;IACZ,iBAAiB;IAEjB;;OAEG;IACH,0BAA0B;IAE1B;;OAEG;IACH,oCAAoC;IAEpC;;OAEG;IACH,sCAAsC;CACzC,CAAC;AAEF,eAAO,MAAM,uBAAuB,QAA4B,CAAC"}
|
|
@@ -119,6 +119,10 @@ export const API_PATH_REGEXES = {
|
|
|
119
119
|
* /api/identity/custodian/register-delegate
|
|
120
120
|
*/
|
|
121
121
|
KEYSTONE_CUSTODIAN_REGISTER_DELEGATE: /^\/api\/identity\/custodian\/register-delegate\/?$/i,
|
|
122
|
+
/**
|
|
123
|
+
* /api/identity/custodian/add-master-password
|
|
124
|
+
*/
|
|
125
|
+
KEYSTONE_CUSTODIAN_ADD_MASTER_PASSWORD: /^\/api\/identity\/custodian\/add-master-password\/?$/i,
|
|
122
126
|
};
|
|
123
127
|
export const VALID_STATIC_PATH_REGEX = /^\/[a-zA-Z0-9\-_\.\/]*$/;
|
|
124
128
|
//# sourceMappingURL=path-constants.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-constants.mjs","sourceRoot":"","sources":["../../src/serve-gib/path-constants.mts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC;AAEpC;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAC9B,MAAM,EAAO,yBAAyB;IACtC,MAAM,EAAO,uBAAuB;IACpC,IAAI,EAAS,gBAAgB;IAC7B,UAAU,EAAG,wCAAwC;CACxD,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC5B;;OAEG;IACH,MAAM,EAAE,oBAAoB;IAE5B;;OAEG;IACH,WAAW,EAAE,oFAAoF;IAEjG;;OAEG;IACH,UAAU,EAAE,6EAA6E;IAEzF;;OAEG;IACH,QAAQ,EAAE,sBAAsB;IAEhC;;OAEG;IACH,aAAa,EAAE,mDAAmD;IAElE;;OAEG;IACH,UAAU,EAAE,mBAAmB;IAE/B;;OAEG;IACH,eAAe,EAAE,2DAA2D;IAE5E;;;;;;;;OAQG;IACH,gBAAgB,EAAE,+BAA+B;IAEjD;;OAEG;IACH,eAAe,EAAE,8BAA8B;IAE/C;;OAEG;IACH,oBAAoB,EAAE,mCAAmC;IAEzD;;;;;;;;;;;OAWG;IACH,aAAa,EAAE,4BAA4B;IAC3C;;OAEG;IACH,OAAO,EAAE,mDAAmD;IAC5D;;OAEG;IACH,iBAAiB,EAAE,iCAAiC;IACpD;;OAEG;IACH,kBAAkB,EAAE,kCAAkC;IACtD;;OAEG;IACH,mBAAmB,EAAE,mCAAmC;IACxD;;OAEG;IACH,aAAa,EAAE,4BAA4B;IAC3C;;OAEG;IACH,UAAU,EAAE,yBAAyB;IACrC;;OAEG;IACH,uBAAuB,EAAE,sCAAsC;IAE/D;;OAEG;IACH,YAAY,EAAE,2BAA2B;IACzC,iBAAiB,EAAE,gCAAgC;IAEnD;;OAEG;IACH,0BAA0B,EAAE,2CAA2C;IAEvE;;OAEG;IACH,oCAAoC,EAAE,qDAAqD;
|
|
1
|
+
{"version":3,"file":"path-constants.mjs","sourceRoot":"","sources":["../../src/serve-gib/path-constants.mts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC;AAEpC;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAC9B,MAAM,EAAO,yBAAyB;IACtC,MAAM,EAAO,uBAAuB;IACpC,IAAI,EAAS,gBAAgB;IAC7B,UAAU,EAAG,wCAAwC;CACxD,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC5B;;OAEG;IACH,MAAM,EAAE,oBAAoB;IAE5B;;OAEG;IACH,WAAW,EAAE,oFAAoF;IAEjG;;OAEG;IACH,UAAU,EAAE,6EAA6E;IAEzF;;OAEG;IACH,QAAQ,EAAE,sBAAsB;IAEhC;;OAEG;IACH,aAAa,EAAE,mDAAmD;IAElE;;OAEG;IACH,UAAU,EAAE,mBAAmB;IAE/B;;OAEG;IACH,eAAe,EAAE,2DAA2D;IAE5E;;;;;;;;OAQG;IACH,gBAAgB,EAAE,+BAA+B;IAEjD;;OAEG;IACH,eAAe,EAAE,8BAA8B;IAE/C;;OAEG;IACH,oBAAoB,EAAE,mCAAmC;IAEzD;;;;;;;;;;;OAWG;IACH,aAAa,EAAE,4BAA4B;IAC3C;;OAEG;IACH,OAAO,EAAE,mDAAmD;IAC5D;;OAEG;IACH,iBAAiB,EAAE,iCAAiC;IACpD;;OAEG;IACH,kBAAkB,EAAE,kCAAkC;IACtD;;OAEG;IACH,mBAAmB,EAAE,mCAAmC;IACxD;;OAEG;IACH,aAAa,EAAE,4BAA4B;IAC3C;;OAEG;IACH,UAAU,EAAE,yBAAyB;IACrC;;OAEG;IACH,uBAAuB,EAAE,sCAAsC;IAE/D;;OAEG;IACH,YAAY,EAAE,2BAA2B;IACzC,iBAAiB,EAAE,gCAAgC;IAEnD;;OAEG;IACH,0BAA0B,EAAE,2CAA2C;IAEvE;;OAEG;IACH,oCAAoC,EAAE,qDAAqD;IAE3F;;OAEG;IACH,sCAAsC,EAAE,uDAAuD;CAClG,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAG,yBAAyB,CAAC"}
|
|
@@ -91,3 +91,40 @@ Attempt to re-submit a previously consumed `custodianReqs` payload to `POST /api
|
|
|
91
91
|
3. Verify rejection outcome:
|
|
92
92
|
- Server rejects the request with **HTTP 409 Conflict**:
|
|
93
93
|
`"Custodian requirements payload has already been used. Nonce single-turn reservation violation."`
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
### Phase 4: Keystone Details Inspection & Sovereign Elevation
|
|
97
|
+
Inspect the identity in `<ibgib-keystone-details>` to verify that capabilities, device badges, and password options adapt dynamically to the custodial state, and add a sovereign master password.
|
|
98
|
+
|
|
99
|
+
#### Steps
|
|
100
|
+
1. In Browser 1, open `<ibgib-identity-header>` and click the active identity to open `<ibgib-keystone-details>`.
|
|
101
|
+
2. Verify dynamic UI elements:
|
|
102
|
+
- **Password Card**: Displays **"Add Master Password"** (with New Password and Confirm Password inputs; Current Password is hidden).
|
|
103
|
+
- **SSO Status Card**: Displays `"Google (Primary Account) ✅"` or provider primary indicator.
|
|
104
|
+
- **Registered Delegates Table**:
|
|
105
|
+
- Browser 1's delegate displays the green **"This Device"** badge.
|
|
106
|
+
- Browser 2's delegate (from Phase 2) displays the blue **"Remote Device"** badge.
|
|
107
|
+
3. Click **View Details** on Browser 2's remote delegate:
|
|
108
|
+
- Verify modal opens showing remote device metadata without throwing or failing.
|
|
109
|
+
4. In the **Add Master Password** card, enter `<E2E_TEST_NEW_PASSWORD>` in New Password and Confirm Password inputs, and click **Add Master Password**.
|
|
110
|
+
5. Verify outcomes:
|
|
111
|
+
- Client adds a sovereign `manage` pool via `keystoneService.addPools()`, evolves the primary identity, and syncs to server.
|
|
112
|
+
- The password card updates dynamically to **"Change Password"** (now displaying Current Password input).
|
|
113
|
+
- Identity capability transitions from Custodial to **Hybrid**.
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
### Phase 5: Silent Background Sync & Expired Session Renewal ("Sign Back In")
|
|
117
|
+
Verify that daily background sync operations execute silently via stored IndexedDB delegate tokens ($S_D$), and verify that expired sync delegates cleanly pause sync and prompt the user to "Sign Back In".
|
|
118
|
+
|
|
119
|
+
#### Steps
|
|
120
|
+
1. In Browser 1, click **Check for Updates** and **Update Identity**:
|
|
121
|
+
- Verify sync succeeds silently in the background without prompting for master passwords.
|
|
122
|
+
2. Simulate delegate expiration / session expiry:
|
|
123
|
+
- Option A: Clear the stored device delegate secret from IndexedDB (`space_gib_db` -> `space_gib_store` -> delete `ibgib_device_delegate_secret_<primaryTjpAddr>`).
|
|
124
|
+
- Option B: Test with a short TTL development profile (`sync-profile.dev.json` with `defaultTtlSeconds: 10`) and wait for expiration.
|
|
125
|
+
3. Trigger a sync check or page refresh:
|
|
126
|
+
- Verify `KeystoneSyncService` detects the expired / missing delegate token and fires `EVENT_IBGIB_IDENTITY_RENEWAL_REQUIRED`.
|
|
127
|
+
- Verify the UI prompts the user to **"Sign Back In"**.
|
|
128
|
+
4. Re-authenticate via Email OTP (or Google SSO / Master Password):
|
|
129
|
+
- Verify single-turn re-authentication generates and registers a fresh outward sync delegate ($D_{\text{device}}$).
|
|
130
|
+
- Verify the new secret ($S_D$) is stored in IndexedDB and silent background sync resumes seamlessly.
|
|
@@ -99,18 +99,20 @@ Open Identity Manager on `https://local.ibgib.space`, trigger Google OAuth2 link
|
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
### Phase 4: New-Device Google SSO Sign-In (Incognito Mode)
|
|
102
|
-
Open a clean Incognito browser window to `https://local.ibgib.space`, complete Google SSO
|
|
102
|
+
Open a clean Incognito browser window to `https://local.ibgib.space`, navigate to Sign In, click **Continue with Google**, complete Google SSO authentication, and verify that the client automatically creates the device delegate and signs in without prompting for a password.
|
|
103
103
|
|
|
104
104
|
#### Steps
|
|
105
105
|
1. Open a fresh **Incognito Window** (ctrl+shift+N) to `https://local.ibgib.space`.
|
|
106
|
-
2. In `<ibgib-identity-header>`, click **Sign In
|
|
107
|
-
3.
|
|
108
|
-
4.
|
|
106
|
+
2. In `<ibgib-identity-header>`, click **Sign In** (which renders the streamlined `<ibgib-add-device>` component).
|
|
107
|
+
3. Click **Continue with Google** (or enter `<E2E_TEST_GMAIL_ADDRESS_ALICE>` and complete 2FA).
|
|
108
|
+
4. Complete Google OAuth2 popup authentication with `<E2E_TEST_GMAIL_ADDRESS_ALICE>`.
|
|
109
|
+
5. Verify server log processing:
|
|
109
110
|
- `[IdentityIndexService]` resolves `<E2E_TEST_GMAIL_ADDRESS_ALICE>` -> `primaryTjpAddr`.
|
|
110
|
-
-
|
|
111
|
-
|
|
112
|
-
- Browser
|
|
111
|
+
- `[SsoLoginHandler]` loads domain graph, verifies `custodian-manage-google` challenge pool, and returns `custodianReqs`.
|
|
112
|
+
6. Verify client UI result:
|
|
113
|
+
- Browser creates local sync delegate, registers it with the server, and pairs the device seamlessly.
|
|
113
114
|
- Active identity header updates to `alice` with active status badge `🟢`.
|
|
115
|
+
- Zero password prompts are shown.
|
|
114
116
|
|
|
115
117
|
|
|
116
118
|
### Phase 5: Delegate Device Tagging & Revocation
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ibgib/node-gib",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"description": "Node and server-specific ibGib code, i.e., things that don't run in the browser.",
|
|
5
5
|
"funding": {
|
|
6
6
|
"type": "individual",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"license": "ISC",
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@ibgib/helper-gib": "^0.0.37",
|
|
28
|
-
"@ibgib/ts-gib": "^0.5.
|
|
29
|
-
"@ibgib/core-gib": "^0.1.
|
|
28
|
+
"@ibgib/ts-gib": "^0.5.34",
|
|
29
|
+
"@ibgib/core-gib": "^0.1.75",
|
|
30
30
|
"@ibgib/encrypt-gib": "^0.3.1"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
package/src/serve-gib/handlers/api/identity/keystone-custodian-add-master-password.handler.mts
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module serve-gib/handlers/api/identity/keystone-custodian-add-master-password.handler
|
|
3
|
+
*
|
|
4
|
+
* POST /api/identity/custodian/add-master-password
|
|
5
|
+
*
|
|
6
|
+
* Single-turn sovereignty elevation endpoint for purely custodial identities.
|
|
7
|
+
* Authenticates the user via fresh OAuth code or 6-digit email OTP, validates that
|
|
8
|
+
* the provided `newManagePool` config matches the existing custodian pool config,
|
|
9
|
+
* evolves the custodian manage pool into an auxiliary inward custodian pool (e.g. `custodian-manage-google`),
|
|
10
|
+
* installs the user's new sovereign pool as `manage`, and signs the evolution using the server's custodian secret.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { extractErrorMsg, hash } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
14
|
+
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
15
|
+
import { getTjpAddr } from '@ibgib/core-gib/dist/common/other/ibgib-helper.mjs';
|
|
16
|
+
import { KeystoneService_V1 } from '@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs';
|
|
17
|
+
import { KeystoneChallengePool } from '@ibgib/core-gib/dist/keystone/keystone-types.mjs';
|
|
18
|
+
import { KEYSTONE_VERB_MANAGE, POOL_ID_MANAGE } from '@ibgib/core-gib/dist/keystone/keystone-constants.mjs';
|
|
19
|
+
import { CUSTODIAN_POOL_ID_PREFIX } from '@ibgib/core-gib/dist/keystone/delegate/delegate-constants.mjs';
|
|
20
|
+
import { KeystoneClaim } from '@ibgib/core-gib/dist/keystone/keystone-types.mjs';
|
|
21
|
+
import { validateEmail } from '@ibgib/core-gib/dist/keystone/keystone-helpers.mjs';
|
|
22
|
+
|
|
23
|
+
import { GLOBAL_LOG_A_LOT } from '../../../constants.mjs';
|
|
24
|
+
import { ServeGibHandlerBase } from '../../handler-base.mjs';
|
|
25
|
+
import { RequestContext, ResponseResult, ServeGibHttpMethod } from '../../../types.mjs';
|
|
26
|
+
import { API_PATH_REGEXES } from '../../../path-constants.mjs';
|
|
27
|
+
import { bootstrapDomainMetaspace } from '../../../bootstrap-helper.mjs';
|
|
28
|
+
import { getIdentityIndexService } from '../../../identity-index/identity-index-service.mjs';
|
|
29
|
+
import { normalizeEmail } from '../../../identity-index/identity-index-helpers.mjs';
|
|
30
|
+
import { loadSsoServerConfig } from '../../../../identity/sso/sso-config-helper.mjs';
|
|
31
|
+
import { SsoCustodianService } from '../../../../identity/sso/sso-custodian-service.mjs';
|
|
32
|
+
import { SsoProviderId } from '../../../../identity/sso/sso-types.mjs';
|
|
33
|
+
import { getPendingStagingDir, getPendingTjpHash, verifyPendingCode } from '../../../../email/verify/email-verify-helpers.mjs';
|
|
34
|
+
|
|
35
|
+
const logalot = GLOBAL_LOG_A_LOT || true;
|
|
36
|
+
|
|
37
|
+
let custodian: SsoCustodianService | undefined;
|
|
38
|
+
function getCustodian(): SsoCustodianService {
|
|
39
|
+
if (!custodian) {
|
|
40
|
+
const config = loadSsoServerConfig();
|
|
41
|
+
custodian = new SsoCustodianService(config);
|
|
42
|
+
}
|
|
43
|
+
return custodian;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface CustodianAddMasterPasswordRequestBody {
|
|
47
|
+
primaryTjpAddr: string;
|
|
48
|
+
newManagePool: KeystoneChallengePool;
|
|
49
|
+
providerId?: SsoProviderId;
|
|
50
|
+
code?: string;
|
|
51
|
+
redirectUri?: string;
|
|
52
|
+
email?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface CustodianAddMasterPasswordResponseBody {
|
|
56
|
+
success: boolean;
|
|
57
|
+
primaryTjpAddr: string;
|
|
58
|
+
primaryKeystone: any;
|
|
59
|
+
message?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* POST /api/identity/custodian/add-master-password
|
|
64
|
+
*/
|
|
65
|
+
export class KeystoneCustodianAddMasterPasswordHandler extends ServeGibHandlerBase<undefined, undefined> {
|
|
66
|
+
protected override lc: string = `[${KeystoneCustodianAddMasterPasswordHandler.name}]`;
|
|
67
|
+
protected override method: ServeGibHttpMethod = 'POST';
|
|
68
|
+
protected override regex = API_PATH_REGEXES.KEYSTONE_CUSTODIAN_ADD_MASTER_PASSWORD;
|
|
69
|
+
|
|
70
|
+
protected async handleRouteImpl(
|
|
71
|
+
reqCtx: RequestContext<undefined, undefined>
|
|
72
|
+
): Promise<ResponseResult | undefined> {
|
|
73
|
+
const lc = `${this.lc}[${this.handleRouteImpl.name}]`;
|
|
74
|
+
try {
|
|
75
|
+
if (logalot) { console.log(`${lc} starting... (I: 8a1b2c3d4e5f67890a1b2c3d4e5f6790)`); }
|
|
76
|
+
|
|
77
|
+
if (reqCtx.method !== 'POST') {
|
|
78
|
+
return this.error(405, 'Method Not Allowed');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!reqCtx.body) {
|
|
82
|
+
return this.error(400, 'Request body is empty (E: 8a1b2c3d4e5f67890a1b2c3d4e5f6791)');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let body: CustodianAddMasterPasswordRequestBody;
|
|
86
|
+
try {
|
|
87
|
+
body = JSON.parse(reqCtx.body);
|
|
88
|
+
} catch {
|
|
89
|
+
return this.error(400, 'Request body must be valid JSON (E: 8a1b2c3d4e5f67890a1b2c3d4e5f6792)');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const { primaryTjpAddr, newManagePool, providerId, code, redirectUri, email } = body;
|
|
93
|
+
|
|
94
|
+
if (!primaryTjpAddr) {
|
|
95
|
+
return this.error(400, 'primaryTjpAddr is required (E: 8a1b2c3d4e5f67890a1b2c3d4e5f6793)');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!newManagePool || typeof newManagePool !== 'object' || newManagePool.id !== POOL_ID_MANAGE) {
|
|
99
|
+
return this.error(400, 'newManagePool is invalid. Must be an object with id "manage" (E: 8a1b2c3d4e5f67890a1b2c3d4e5f6794)');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!newManagePool.challenges || Object.keys(newManagePool.challenges).length === 0) {
|
|
103
|
+
return this.error(400, 'newManagePool must contain pre-generated public challenges (E: 8a1b2c3d4e5f67890a1b2c3d4e5f6795)');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 1. Authenticate user credentials via OAuth code or Email OTP
|
|
107
|
+
let verifiedEmail: string;
|
|
108
|
+
let effectiveProvider: string = providerId || 'email';
|
|
109
|
+
|
|
110
|
+
if (providerId && code && redirectUri) {
|
|
111
|
+
const service = getCustodian();
|
|
112
|
+
const userInfo = await service.getOAuthUserInfo(providerId, code, redirectUri);
|
|
113
|
+
if (!userInfo || !userInfo.email) {
|
|
114
|
+
return this.error(401, `Failed to authenticate with SSO provider ${providerId} (E: 8a1b2c3d4e5f67890a1b2c3d4e5f6796)`);
|
|
115
|
+
}
|
|
116
|
+
verifiedEmail = normalizeEmail(userInfo.email);
|
|
117
|
+
} else if (email && code) {
|
|
118
|
+
if (!validateEmail(email)) {
|
|
119
|
+
return this.error(400, 'Invalid email address format (E: 8a1b2c3d4e5f67890a1b2c3d4e5f6797)');
|
|
120
|
+
}
|
|
121
|
+
verifiedEmail = normalizeEmail(email);
|
|
122
|
+
|
|
123
|
+
const tjpHash = await getPendingTjpHash(primaryTjpAddr);
|
|
124
|
+
const pendingInfo = reqCtx.dataDir && tjpHash ? await getPendingStagingDir({ dataDir: reqCtx.dataDir, tjpHash }) : undefined;
|
|
125
|
+
|
|
126
|
+
if (!pendingInfo || pendingInfo.isExpired) {
|
|
127
|
+
return this.error(400, 'Verification record missing or expired. Please request a new verification code. (E: 8a1b2c3d4e5f67890a1b2c3d4e5f6798)');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const codeVerifyResult = await verifyPendingCode({
|
|
131
|
+
pendingDir: pendingInfo.pendingDir,
|
|
132
|
+
code,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (!codeVerifyResult.valid) {
|
|
136
|
+
return this.error(401, codeVerifyResult.error || 'Invalid verification code (E: 8a1b2c3d4e5f67890a1b2c3d4e5f6799)');
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
return this.error(400, 'Authentication credentials required (providerId+code+redirectUri or email+code) (E: 8a1b2c3d4e5f67890a1b2c3d4e5f679a)');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 2. Lookup primary identity and confirm email matches
|
|
143
|
+
const indexService = getIdentityIndexService();
|
|
144
|
+
const identityRecord = await indexService.lookupIdentityByEmail({ email: verifiedEmail });
|
|
145
|
+
if (!identityRecord || identityRecord.primaryTjpAddr !== primaryTjpAddr) {
|
|
146
|
+
return this.error(403, `Authenticated email ${verifiedEmail} does not match primary identity ${primaryTjpAddr} (E: 8a1b2c3d4e5f67890a1b2c3d4e5f679b)`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 3. Bootstrap domain metaspace and load latest keystone
|
|
150
|
+
const metaspace = await bootstrapDomainMetaspace(primaryTjpAddr, reqCtx.dataDir);
|
|
151
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
152
|
+
if (!space) {
|
|
153
|
+
return this.error(500, `Local domain space not found for identity ${primaryTjpAddr}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const keystoneService = new KeystoneService_V1();
|
|
157
|
+
const latestKeystone = await keystoneService.getLatestKeystone({
|
|
158
|
+
addr: primaryTjpAddr,
|
|
159
|
+
metaspace,
|
|
160
|
+
space
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (!latestKeystone || !latestKeystone.data) {
|
|
164
|
+
return this.error(404, `Primary identity keystone not found for address ${primaryTjpAddr}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 4. Verify existing custodial manage pool
|
|
168
|
+
const prevPools = latestKeystone.data.challengePools || [];
|
|
169
|
+
const existingManagePool = prevPools.find(p => p.id === POOL_ID_MANAGE);
|
|
170
|
+
if (!existingManagePool) {
|
|
171
|
+
return this.error(400, 'Primary keystone missing active "manage" pool (E: 8a1b2c3d4e5f67890a1b2c3d4e5f679c)');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Validate new pool config matches existing manage pool config structure
|
|
175
|
+
if (existingManagePool.config?.type && newManagePool.config?.type !== existingManagePool.config.type) {
|
|
176
|
+
return this.error(400, `newManagePool type "${newManagePool.config?.type}" does not match required type "${existingManagePool.config.type}" (E: 8a1b2c3d4e5f67890a1b2c3d4e5f679d)`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 5. Build next pool set:
|
|
180
|
+
// Convert existing custodian manage pool to auxiliary custodian pool (e.g. custodian-manage-google)
|
|
181
|
+
// and install user's newManagePool as 'manage'.
|
|
182
|
+
const custodianPoolId = `${CUSTODIAN_POOL_ID_PREFIX}${effectiveProvider}`;
|
|
183
|
+
const auxiliaryCustodianPool: KeystoneChallengePool = {
|
|
184
|
+
...existingManagePool,
|
|
185
|
+
id: custodianPoolId,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const nextPools: KeystoneChallengePool[] = [];
|
|
189
|
+
for (const pool of prevPools) {
|
|
190
|
+
if (pool.id === POOL_ID_MANAGE) {
|
|
191
|
+
nextPools.push(newManagePool);
|
|
192
|
+
nextPools.push(auxiliaryCustodianPool);
|
|
193
|
+
} else if (pool.id === custodianPoolId) {
|
|
194
|
+
// Skip if already present to prevent duplicate
|
|
195
|
+
} else {
|
|
196
|
+
nextPools.push(pool);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 6. Sign evolution turn using server's custodian secret
|
|
201
|
+
const ssoConfig = loadSsoServerConfig();
|
|
202
|
+
const serverSecret = ssoConfig.ssoServerKdfSecret;
|
|
203
|
+
const primarySecret = await hash({ s: `${serverSecret}_custodian_${verifiedEmail}` });
|
|
204
|
+
|
|
205
|
+
const claim: Partial<KeystoneClaim> = {
|
|
206
|
+
verb: KEYSTONE_VERB_MANAGE,
|
|
207
|
+
target: getIbGibAddr({ ibGib: latestKeystone })
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const evolvedKeystone = await keystoneService.sign({
|
|
211
|
+
latestKeystone,
|
|
212
|
+
masterSecret: primarySecret,
|
|
213
|
+
claim,
|
|
214
|
+
poolId: POOL_ID_MANAGE,
|
|
215
|
+
metaspace,
|
|
216
|
+
space,
|
|
217
|
+
dataToPatch: {
|
|
218
|
+
challengePools: nextPools,
|
|
219
|
+
frameDetails: {
|
|
220
|
+
action: 'add_master_password',
|
|
221
|
+
custodyMode: 'hybrid',
|
|
222
|
+
elevatedAt: new Date().toISOString(),
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// 7. Persist evolved keystone in domain space
|
|
228
|
+
await metaspace.put({ ibGibs: [evolvedKeystone], space });
|
|
229
|
+
await metaspace.registerNewIbGib({ ibGib: evolvedKeystone, space });
|
|
230
|
+
|
|
231
|
+
const response: CustodianAddMasterPasswordResponseBody = {
|
|
232
|
+
success: true,
|
|
233
|
+
primaryTjpAddr,
|
|
234
|
+
primaryKeystone: evolvedKeystone,
|
|
235
|
+
message: 'Successfully elevated identity to sovereign master password'
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
return this.ok(response);
|
|
239
|
+
|
|
240
|
+
} catch (error) {
|
|
241
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
242
|
+
return this.error(500, `Internal error adding master password: ${extractErrorMsg(error)}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { existsSync, rmSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
respecfully,
|
|
5
|
+
iReckon,
|
|
6
|
+
ifWe,
|
|
7
|
+
ifWeMight
|
|
8
|
+
} from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
|
|
9
|
+
|
|
10
|
+
import { KeystoneCustodianGenesisHandler } from './keystone-custodian-genesis.handler.mjs';
|
|
11
|
+
import { KeystoneCustodianAddMasterPasswordHandler } from './keystone-custodian-add-master-password.handler.mjs';
|
|
12
|
+
import { KeystoneService_V1 } from '@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs';
|
|
13
|
+
import { POOL_ID_MANAGE } from '@ibgib/core-gib/dist/keystone/keystone-constants.mjs';
|
|
14
|
+
import { CUSTODIAN_POOL_ID_PREFIX } from '@ibgib/core-gib/dist/keystone/delegate/delegate-constants.mjs';
|
|
15
|
+
import { generatePoolChallenges } from '@ibgib/core-gib/dist/keystone/keystone-helpers.mjs';
|
|
16
|
+
import { getPendingStagingDir, getPendingTjpHash, savePendingCodeRecord, savePendingStagingKeystone } from '../../../../email/verify/email-verify-helpers.mjs';
|
|
17
|
+
import { resetIdentityIndexService } from '../../../identity-index/identity-index-service.mjs';
|
|
18
|
+
|
|
19
|
+
const sir = `[${import.meta.url}]`;
|
|
20
|
+
|
|
21
|
+
await respecfully(sir, 'KeystoneCustodianAddMasterPasswordHandler', async () => {
|
|
22
|
+
const testDataDir = './test-data-custodian-add-password';
|
|
23
|
+
|
|
24
|
+
if (existsSync(testDataDir)) {
|
|
25
|
+
rmSync(testDataDir, { recursive: true, force: true });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const origDataDir = process.env.DATA_DIR;
|
|
29
|
+
process.env.DATA_DIR = testDataDir;
|
|
30
|
+
process.env.KEYSTONE_PROFILE_CUSTODIAN_PRIMARY = 'primary-profile.dev';
|
|
31
|
+
process.env.SSO_SERVER_KDF_SECRET = 'test-sso-server-kdf-secret-12345';
|
|
32
|
+
process.env.SESSION_SECRET = 'test-session-secret-12345';
|
|
33
|
+
resetIdentityIndexService();
|
|
34
|
+
|
|
35
|
+
const genesisHandler = new KeystoneCustodianGenesisHandler();
|
|
36
|
+
const addPasswordHandler = new KeystoneCustodianAddMasterPasswordHandler();
|
|
37
|
+
|
|
38
|
+
await ifWe(sir, 'reject request with 400 if required fields are missing', async () => {
|
|
39
|
+
const reqCtx: any = {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
pathname: '/api/identity/custodian/add-master-password',
|
|
42
|
+
url: new URL('http://localhost/api/identity/custodian/add-master-password'),
|
|
43
|
+
body: JSON.stringify({}),
|
|
44
|
+
dataDir: testDataDir
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const res = await addPasswordHandler.handleRoute(reqCtx);
|
|
48
|
+
iReckon(sir, res?.status).isGonnaBe(400);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await ifWe(sir, 'successfully elevate custodial identity to sovereign master password', async () => {
|
|
52
|
+
try {
|
|
53
|
+
const email = 'alice.elevate@example.com';
|
|
54
|
+
const genesisCode = '123456';
|
|
55
|
+
|
|
56
|
+
// 1. Create custodial identity via genesis handler with staged code
|
|
57
|
+
const genesisStaging = await savePendingStagingKeystone({
|
|
58
|
+
primaryTjpAddr: `pending:${email}`,
|
|
59
|
+
emails: [email],
|
|
60
|
+
dataDir: testDataDir,
|
|
61
|
+
});
|
|
62
|
+
await savePendingCodeRecord({
|
|
63
|
+
pendingDir: genesisStaging.pendingDir,
|
|
64
|
+
code: genesisCode,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const genesisReqCtx: any = {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
pathname: '/api/identity/custodian/genesis',
|
|
70
|
+
url: new URL('http://localhost/api/identity/custodian/genesis'),
|
|
71
|
+
body: JSON.stringify({ email, username: 'alice', code: genesisCode }),
|
|
72
|
+
dataDir: testDataDir
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const genesisRes = await genesisHandler.handleRoute(genesisReqCtx);
|
|
76
|
+
if (genesisRes?.status !== 201) {
|
|
77
|
+
console.error(`[DEBUG] genesisRes failed: ${genesisRes?.status}, body:`, JSON.stringify(genesisRes?.body));
|
|
78
|
+
}
|
|
79
|
+
iReckon(sir, genesisRes?.status).isGonnaBe(201);
|
|
80
|
+
|
|
81
|
+
const genesisBody = typeof genesisRes?.body === 'string' ? JSON.parse(genesisRes?.body) : genesisRes?.body;
|
|
82
|
+
const primaryTjpAddr = genesisBody.primaryTjpAddr;
|
|
83
|
+
const initialKeystone = genesisBody.primaryKeystone;
|
|
84
|
+
|
|
85
|
+
iReckon(sir, primaryTjpAddr).not.isGonnaBeUndefined();
|
|
86
|
+
iReckon(sir, initialKeystone).not.isGonnaBeUndefined();
|
|
87
|
+
|
|
88
|
+
const initialManagePool = initialKeystone.data.challengePools.find((p: any) => p.id === POOL_ID_MANAGE);
|
|
89
|
+
iReckon(sir, initialManagePool).not.isGonnaBeUndefined();
|
|
90
|
+
|
|
91
|
+
// 2. Generate new sovereign manage challenge pool locally with user's new password
|
|
92
|
+
const userNewPassword = 'AliceSuperSecretPassphrase2026!';
|
|
93
|
+
const newChallenges = await generatePoolChallenges({
|
|
94
|
+
config: initialManagePool.config,
|
|
95
|
+
masterSecret: userNewPassword,
|
|
96
|
+
});
|
|
97
|
+
const newManagePool = {
|
|
98
|
+
id: POOL_ID_MANAGE,
|
|
99
|
+
config: initialManagePool.config,
|
|
100
|
+
challenges: newChallenges,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Stage verification code for the elevation turn
|
|
104
|
+
const elevateStaging = await savePendingStagingKeystone({
|
|
105
|
+
primaryTjpAddr,
|
|
106
|
+
emails: [email],
|
|
107
|
+
dataDir: testDataDir,
|
|
108
|
+
});
|
|
109
|
+
const elevateCode = '654321';
|
|
110
|
+
await savePendingCodeRecord({
|
|
111
|
+
pendingDir: elevateStaging.pendingDir,
|
|
112
|
+
code: elevateCode,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// 3. Call add-master-password endpoint
|
|
116
|
+
const addPasswordReqCtx: any = {
|
|
117
|
+
method: 'POST',
|
|
118
|
+
pathname: '/api/identity/custodian/add-master-password',
|
|
119
|
+
url: new URL('http://localhost/api/identity/custodian/add-master-password'),
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
primaryTjpAddr,
|
|
122
|
+
email,
|
|
123
|
+
code: elevateCode,
|
|
124
|
+
newManagePool,
|
|
125
|
+
}),
|
|
126
|
+
dataDir: testDataDir
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const addPasswordRes = await addPasswordHandler.handleRoute(addPasswordReqCtx);
|
|
130
|
+
if (addPasswordRes?.status !== 200) {
|
|
131
|
+
console.error(`[DEBUG] addPasswordRes failed with status: ${addPasswordRes?.status}, body:`, addPasswordRes?.body);
|
|
132
|
+
}
|
|
133
|
+
iReckon(sir, addPasswordRes?.status).isGonnaBe(200);
|
|
134
|
+
|
|
135
|
+
const addPasswordBody = typeof addPasswordRes?.body === 'string' ? JSON.parse(addPasswordRes?.body) : addPasswordRes?.body;
|
|
136
|
+
iReckon(sir, addPasswordBody.success).isGonnaBeTrue();
|
|
137
|
+
iReckon(sir, addPasswordBody.primaryTjpAddr).isGonnaBe(primaryTjpAddr);
|
|
138
|
+
|
|
139
|
+
const evolvedKeystone = addPasswordBody.primaryKeystone;
|
|
140
|
+
iReckon(sir, evolvedKeystone).not.isGonnaBeUndefined();
|
|
141
|
+
iReckon(sir, evolvedKeystone.data.n).isGonnaBe(1);
|
|
142
|
+
iReckon(sir, evolvedKeystone.data.frameDetails.custodyMode).isGonnaBe('hybrid');
|
|
143
|
+
|
|
144
|
+
// Check pools: should contain both 'manage' and 'custodian-manage-email'
|
|
145
|
+
const poolIds = evolvedKeystone.data.challengePools.map((p: any) => p.id);
|
|
146
|
+
iReckon(sir, poolIds.includes('manage')).isGonnaBeTrue();
|
|
147
|
+
iReckon(sir, poolIds.includes(`${CUSTODIAN_POOL_ID_PREFIX}email`)).isGonnaBeTrue();
|
|
148
|
+
|
|
149
|
+
// 4. Verify user password solves the evolved manage pool
|
|
150
|
+
const keystoneService = new KeystoneService_V1();
|
|
151
|
+
const isUserPasswordValid = await keystoneService.verifySigningSecret({
|
|
152
|
+
keystoneIbGib: evolvedKeystone,
|
|
153
|
+
signingSecret: userNewPassword,
|
|
154
|
+
poolId: POOL_ID_MANAGE,
|
|
155
|
+
});
|
|
156
|
+
iReckon(sir, isUserPasswordValid).isGonnaBeTrue();
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error(`[DEBUG TEST 2 EXCEPTION]`, error);
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (existsSync(testDataDir)) {
|
|
164
|
+
rmSync(testDataDir, { recursive: true, force: true });
|
|
165
|
+
}
|
|
166
|
+
process.env.DATA_DIR = origDataDir;
|
|
167
|
+
});
|
|
@@ -126,9 +126,12 @@ export class KeystoneCustodianGenesisHandler extends ServeGibHandlerBase<undefin
|
|
|
126
126
|
const metaspace = await bootstrapDomainMetaspace(primaryTjpAddr, reqCtx.dataDir);
|
|
127
127
|
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
128
128
|
if (space) {
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
const keystoneService = new KeystoneService_V1();
|
|
130
|
+
primaryKeystone = await keystoneService.getLatestKeystone({
|
|
131
|
+
addr: primaryTjpAddr,
|
|
132
|
+
metaspace,
|
|
133
|
+
space
|
|
134
|
+
});
|
|
132
135
|
}
|
|
133
136
|
} else {
|
|
134
137
|
// New custodian identity creation
|
package/src/serve-gib/handlers/api/identity/keystone-custodian-register-delegate.handler.mts
CHANGED
|
@@ -140,9 +140,11 @@ export class KeystoneCustodianRegisterDelegateHandler extends ServeGibHandlerBas
|
|
|
140
140
|
return this.error(500, `Could not load domain space for primary identity ${custodianReqs.primaryTjpAddr}`);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
const parentKeystone = await keystoneService.getLatestKeystone({
|
|
144
|
+
addr: custodianReqs.primaryTjpAddr,
|
|
145
|
+
metaspace,
|
|
146
|
+
space
|
|
147
|
+
});
|
|
146
148
|
|
|
147
149
|
if (!parentKeystone) {
|
|
148
150
|
return this.error(404, `Primary identity keystone not found for address ${custodianReqs.primaryTjpAddr}`);
|
|
@@ -10,7 +10,7 @@ import { KeystoneCustodianGenesisHandler } from './keystone-custodian-genesis.ha
|
|
|
10
10
|
import { KeystoneCustodianRegisterDelegateHandler } from './keystone-custodian-register-delegate.handler.mjs';
|
|
11
11
|
import { KeystoneProfileBuilder } from '@ibgib/core-gib/dist/keystone/policy/keystone-profile-builder.mjs';
|
|
12
12
|
import { KeystoneService_V1 } from '@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs';
|
|
13
|
-
import {
|
|
13
|
+
import { deriveDeviceDelegateSecret } from '@ibgib/core-gib/dist/keystone/delegate/delegate-helpers.mjs';
|
|
14
14
|
import { bootstrapDomainMetaspace } from '../../../bootstrap-helper.mjs';
|
|
15
15
|
|
|
16
16
|
const sir = `[${import.meta.url}]`;
|
|
@@ -58,8 +58,8 @@ await respecfully(sir, 'KeystoneCustodianRegisterDelegateHandler', async () => {
|
|
|
58
58
|
iReckon(sir, custodianReqs).not.isGonnaBeUndefined();
|
|
59
59
|
|
|
60
60
|
// Step 2: Build client delegate keystone containing custodianReqs
|
|
61
|
-
const delegateSecret = await
|
|
62
|
-
const delegateBuilder = KeystoneProfileBuilder.buildKeystone('sync')
|
|
61
|
+
const delegateSecret = await deriveDeviceDelegateSecret({ primaryTjpAddr, deviceTag: 'phone' });
|
|
62
|
+
const delegateBuilder = KeystoneProfileBuilder.buildKeystone('sync-profile.dev')
|
|
63
63
|
.withUsername('bob-sync-phone')
|
|
64
64
|
.withDescription('Sync Delegate for bob on phone')
|
|
65
65
|
.withDetails({
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* generates a fresh single-turn `CustodianRequirements` payload, and returns it to the client.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { extractErrorMsg, hash, getUUID } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
11
|
+
import { extractErrorMsg, hash, getUUID, getSaferSubstring } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
12
12
|
import { getTjpAddr } from '@ibgib/core-gib/dist/common/other/ibgib-helper.mjs';
|
|
13
13
|
import { KeystoneProfileBuilder } from '@ibgib/core-gib/dist/keystone/policy/keystone-profile-builder.mjs';
|
|
14
14
|
import { KeystoneService_V1 } from '@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs';
|
|
@@ -103,7 +103,9 @@ export class SsoLoginHandler extends ServeGibHandlerBase<undefined, undefined> {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
const email = userInfo.email.trim().toLowerCase();
|
|
106
|
-
const
|
|
106
|
+
const rawSSOName = userInfo.name || '';
|
|
107
|
+
const username = getSaferSubstring({ text: rawSSOName || email.split('@')[0], length: 62, keepLiterals: ['_', '-'] })
|
|
108
|
+
;
|
|
107
109
|
|
|
108
110
|
// 2. Check if primary identity exists for this email
|
|
109
111
|
const ssoConfig = loadSsoServerConfig();
|
|
@@ -122,10 +124,12 @@ export class SsoLoginHandler extends ServeGibHandlerBase<undefined, undefined> {
|
|
|
122
124
|
primaryTjpAddr = existingLookup.primaryTjpAddr;
|
|
123
125
|
const domainMetaspace = await bootstrapDomainMetaspace(primaryTjpAddr, reqCtx.dataDir);
|
|
124
126
|
const space = await domainMetaspace.getLocalUserSpace({ lock: false });
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
const keystoneService = new KeystoneService_V1();
|
|
128
|
+
primaryKeystone = await keystoneService.getLatestKeystone({
|
|
129
|
+
addr: primaryTjpAddr,
|
|
130
|
+
metaspace: domainMetaspace,
|
|
131
|
+
space: space!
|
|
132
|
+
});
|
|
129
133
|
} else {
|
|
130
134
|
// First-time custodian identity creation
|
|
131
135
|
isNewIdentity = true;
|
|
@@ -145,6 +149,8 @@ export class SsoLoginHandler extends ServeGibHandlerBase<undefined, undefined> {
|
|
|
145
149
|
client: 'space-gib-server',
|
|
146
150
|
role: 'custodian-primary',
|
|
147
151
|
custodianProvider: providerId,
|
|
152
|
+
custodyMode: 'full',
|
|
153
|
+
[`rawSSOName-${providerId}`]: rawSSOName,
|
|
148
154
|
});
|
|
149
155
|
|
|
150
156
|
const configs = await primaryBuilder.compileConfigs();
|