@ibgib/web-gib 0.0.59 → 0.0.60
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/AUTO-GENERATED-version.d.mts +1 -1
- package/dist/AUTO-GENERATED-version.mjs +1 -1
- package/dist/identity/custodian-delegate-helper.d.mts.map +1 -1
- package/dist/identity/custodian-delegate-helper.mjs +20 -3
- package/dist/identity/custodian-delegate-helper.mjs.map +1 -1
- package/dist/identity/identity-constants.mjs +2 -2
- package/dist/identity/identity-constants.mjs.map +1 -1
- package/dist/identity/identity-types.d.mts +4 -4
- package/dist/identity/identity-types.d.mts.map +1 -1
- package/dist/ui/component/identity/add-device/add-device.css +93 -17
- package/dist/ui/component/identity/add-device/add-device.d.mts +24 -7
- package/dist/ui/component/identity/add-device/add-device.d.mts.map +1 -1
- package/dist/ui/component/identity/add-device/add-device.html +97 -23
- package/dist/ui/component/identity/add-device/add-device.mjs +490 -186
- package/dist/ui/component/identity/add-device/add-device.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +7 -4
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.css +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.d.mts +8 -8
- package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.html +29 -20
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs +51 -51
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
- package/dist/ui/component/identity/remove-device/remove-device.d.mts +1 -1
- package/dist/ui/component/identity/remove-device/remove-device.d.mts.map +1 -1
- package/dist/ui/component/identity/remove-device/remove-device.html +3 -3
- package/dist/ui/component/identity/remove-device/remove-device.mjs +7 -7
- package/dist/ui/component/identity/remove-device/remove-device.mjs.map +1 -1
- package/dist/util/device-helper.d.mts +30 -0
- package/dist/util/device-helper.d.mts.map +1 -0
- package/dist/util/device-helper.mjs +90 -0
- package/dist/util/device-helper.mjs.map +1 -0
- package/dist/util/device-helper.respec.d.mts +2 -0
- package/dist/util/device-helper.respec.d.mts.map +1 -0
- package/dist/util/device-helper.respec.mjs +83 -0
- package/dist/util/device-helper.respec.mjs.map +1 -0
- package/package.json +3 -3
- package/src/AUTO-GENERATED-version.mts +1 -1
- package/src/identity/custodian-delegate-helper.mts +28 -3
- package/src/identity/identity-constants.mts +2 -2
- package/src/identity/identity-types.mts +4 -4
- package/src/ui/component/identity/add-device/add-device.css +93 -17
- package/src/ui/component/identity/add-device/add-device.html +97 -23
- package/src/ui/component/identity/add-device/add-device.mts +577 -194
- package/src/ui/component/identity/keystone-creator/keystone-creator.mts +7 -4
- package/src/ui/component/identity/keystone-details/keystone-details.css +1 -1
- package/src/ui/component/identity/keystone-details/keystone-details.html +29 -20
- package/src/ui/component/identity/keystone-details/keystone-details.mts +57 -57
- package/src/ui/component/identity/remove-device/remove-device.html +3 -3
- package/src/ui/component/identity/remove-device/remove-device.mts +8 -8
- package/src/util/device-helper.mts +104 -0
- package/src/util/device-helper.respec.mts +90 -0
|
@@ -10,7 +10,7 @@ import { getGibInfo } from "@ibgib/ts-gib/dist/V1/transforms/transform-helper.mj
|
|
|
10
10
|
import { KeystoneService_V1 } from "@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs";
|
|
11
11
|
import { KeystoneProfileBuilder } from "@ibgib/core-gib/dist/keystone/policy/keystone-profile-builder.mjs";
|
|
12
12
|
import { updateSpecialIndex, } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
|
|
13
|
-
import { toDto, getTjpAddr } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
|
|
13
|
+
import { toDto, getTjpAddr, getTimelinesGroupedByTjp } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
|
|
14
14
|
import { FlatIbGibGraph } from "@ibgib/core-gib/dist/common/other/graph-types.mjs";
|
|
15
15
|
import { validateEmail } from "@ibgib/core-gib/dist/keystone/keystone-helpers.mjs";
|
|
16
16
|
import { KEYSTONE_USERNAME_REGEXP, KEYSTONE_DESCRIPTION_REGEXP, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
|
|
@@ -921,9 +921,12 @@ export class KeystoneCreatorComponentInstance
|
|
|
921
921
|
// 0. Persist all frames of primary identity graph into local browser space
|
|
922
922
|
if (primaryGraph) {
|
|
923
923
|
const graphIbGibs = Object.values(primaryGraph) as IbGib_V1[];
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
924
|
+
await metaspace.put({ ibGibs: graphIbGibs, space });
|
|
925
|
+
const timelinesMap = getTimelinesGroupedByTjp({ ibGibs: graphIbGibs });
|
|
926
|
+
for (const tjpKey of Object.keys(timelinesMap)) {
|
|
927
|
+
for (const frame of timelinesMap[tjpKey]) {
|
|
928
|
+
await metaspace.registerNewIbGib({ ibGib: frame, space });
|
|
929
|
+
}
|
|
927
930
|
}
|
|
928
931
|
} else if (primaryKeystone) {
|
|
929
932
|
await metaspace.put({ ibGibs: [primaryKeystone], space });
|
|
@@ -60,7 +60,8 @@
|
|
|
60
60
|
<div id="delegates-card" class="card">
|
|
61
61
|
<h3>Registered Delegates</h3>
|
|
62
62
|
<div id="delegates-container" class="delegates-container">
|
|
63
|
-
<p class="description">Active sub-keystones authorized to perform
|
|
63
|
+
<p class="description">Active sub-keystones authorized to perform operations on behalf of this Domain
|
|
64
|
+
Identity.</p>
|
|
64
65
|
<table class="delegates-table">
|
|
65
66
|
<thead>
|
|
66
67
|
<tr>
|
|
@@ -80,7 +81,8 @@
|
|
|
80
81
|
<!-- SSO Identity Linking Card -->
|
|
81
82
|
<div id="sso-link-card" class="card hidden">
|
|
82
83
|
<h3>Identity Providers</h3>
|
|
83
|
-
<p class="description">Securely link or unlink external OAuth2 providers to delegate sync access to this
|
|
84
|
+
<p class="description">Securely link or unlink external OAuth2 providers to delegate sync access to this
|
|
85
|
+
identity.</p>
|
|
84
86
|
<div class="sso-toggle-group">
|
|
85
87
|
<!-- Google Card Toggle -->
|
|
86
88
|
<div id="btn-link-google" class="sso-toggle-card google" role="button" tabindex="0">
|
|
@@ -88,10 +90,14 @@
|
|
|
88
90
|
<div class="toggle-content">
|
|
89
91
|
<!-- Custom Google SVG Icon -->
|
|
90
92
|
<svg class="sso-icon" viewBox="0 0 24 24">
|
|
91
|
-
<path fill="#4285F4"
|
|
92
|
-
|
|
93
|
-
<path fill="#
|
|
94
|
-
|
|
93
|
+
<path fill="#4285F4"
|
|
94
|
+
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" />
|
|
95
|
+
<path fill="#34A853"
|
|
96
|
+
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
|
|
97
|
+
<path fill="#FBBC05"
|
|
98
|
+
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.06H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.94l2.85-2.22.81-.63z" />
|
|
99
|
+
<path fill="#EA4335"
|
|
100
|
+
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.06l3.66 2.84c.87-2.6 3.3-4.52 6.16-4.52z" />
|
|
95
101
|
</svg>
|
|
96
102
|
<span class="provider-label">Google</span>
|
|
97
103
|
<span class="toggle-state-text">Unlinked</span>
|
|
@@ -105,7 +111,8 @@
|
|
|
105
111
|
<div class="toggle-content">
|
|
106
112
|
<!-- Custom GitHub SVG Icon -->
|
|
107
113
|
<svg class="sso-icon" viewBox="0 0 24 24" fill="currentColor">
|
|
108
|
-
<path
|
|
114
|
+
<path
|
|
115
|
+
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
|
|
109
116
|
</svg>
|
|
110
117
|
<span class="provider-label">GitHub</span>
|
|
111
118
|
<span class="toggle-state-text">Unlinked</span>
|
|
@@ -116,28 +123,30 @@
|
|
|
116
123
|
<div id="sso-status-msg" class="status-msg hidden"></div>
|
|
117
124
|
</div>
|
|
118
125
|
|
|
119
|
-
<!-- Change
|
|
120
|
-
<div id="change-
|
|
121
|
-
<h3>Change
|
|
122
|
-
<p class="description">Rotate your local
|
|
126
|
+
<!-- Change Password Card -->
|
|
127
|
+
<div id="change-password-card" class="card">
|
|
128
|
+
<h3>Change Password</h3>
|
|
129
|
+
<p class="description">Rotate your local password that drives this identity. This will cryptographically
|
|
130
|
+
evolve your primary
|
|
131
|
+
identity and all registered user-owned delegate keystones to the new password.</p>
|
|
123
132
|
<div class="form-row">
|
|
124
133
|
<div class="form-group">
|
|
125
|
-
<label for="input-current-
|
|
126
|
-
<input type="password" id="input-current-
|
|
134
|
+
<label for="input-current-password">Current Password</label>
|
|
135
|
+
<input type="password" id="input-current-password" placeholder="••••••••" class="form-input">
|
|
127
136
|
</div>
|
|
128
137
|
<div class="form-group">
|
|
129
|
-
<label for="input-new-
|
|
130
|
-
<input type="password" id="input-new-
|
|
138
|
+
<label for="input-new-password">New Password</label>
|
|
139
|
+
<input type="password" id="input-new-password" placeholder="••••••••" class="form-input">
|
|
131
140
|
</div>
|
|
132
141
|
<div class="form-group">
|
|
133
|
-
<label for="input-confirm-
|
|
134
|
-
<input type="password" id="input-confirm-
|
|
142
|
+
<label for="input-confirm-password">Confirm New Password</label>
|
|
143
|
+
<input type="password" id="input-confirm-password" placeholder="••••••••" class="form-input">
|
|
135
144
|
</div>
|
|
136
145
|
</div>
|
|
137
146
|
<div class="action-row">
|
|
138
|
-
<button id="btn-change-
|
|
147
|
+
<button id="btn-change-password" class="action-btn">Update Password</button>
|
|
139
148
|
</div>
|
|
140
|
-
<div id="change-
|
|
149
|
+
<div id="change-password-status" class="status-msg hidden"></div>
|
|
141
150
|
</div>
|
|
142
151
|
|
|
143
152
|
<!-- Raw Keystone Timeline Explorer -->
|
|
@@ -151,4 +160,4 @@
|
|
|
151
160
|
</div>
|
|
152
161
|
</div>
|
|
153
162
|
</div>
|
|
154
|
-
</div>
|
|
163
|
+
</div>
|
|
@@ -94,12 +94,12 @@ interface KeystoneDetailsElements extends ElementsBase {
|
|
|
94
94
|
btnLinkGoogleEl: HTMLDivElement;
|
|
95
95
|
btnLinkGithubEl: HTMLDivElement;
|
|
96
96
|
ssoStatusMsgEl: HTMLDivElement;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
inputCurrentPasswordEl: HTMLInputElement;
|
|
98
|
+
inputNewPasswordEl: HTMLInputElement;
|
|
99
|
+
inputConfirmPasswordEl: HTMLInputElement;
|
|
100
|
+
btnChangePasswordEl: HTMLButtonElement;
|
|
101
|
+
changePasswordStatusEl: HTMLDivElement;
|
|
102
|
+
changePasswordCardEl: HTMLDivElement;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
export class KeystoneDetailsComponentInstance
|
|
@@ -212,12 +212,12 @@ export class KeystoneDetailsComponentInstance
|
|
|
212
212
|
this.elements.btnLinkGoogleEl = shadow.getElementById('btn-link-google') as HTMLDivElement;
|
|
213
213
|
this.elements.btnLinkGithubEl = shadow.getElementById('btn-link-github') as HTMLDivElement;
|
|
214
214
|
this.elements.ssoStatusMsgEl = shadow.getElementById('sso-status-msg') as HTMLDivElement;
|
|
215
|
-
this.elements.
|
|
216
|
-
this.elements.
|
|
217
|
-
this.elements.
|
|
218
|
-
this.elements.
|
|
219
|
-
this.elements.
|
|
220
|
-
this.elements.
|
|
215
|
+
this.elements.inputCurrentPasswordEl = shadow.getElementById('input-current-password') as HTMLInputElement;
|
|
216
|
+
this.elements.inputNewPasswordEl = shadow.getElementById('input-new-password') as HTMLInputElement;
|
|
217
|
+
this.elements.inputConfirmPasswordEl = shadow.getElementById('input-confirm-password') as HTMLInputElement;
|
|
218
|
+
this.elements.btnChangePasswordEl = shadow.getElementById('btn-change-password') as HTMLButtonElement;
|
|
219
|
+
this.elements.changePasswordStatusEl = shadow.getElementById('change-password-status') as HTMLDivElement;
|
|
220
|
+
this.elements.changePasswordCardEl = shadow.getElementById('change-password-card') as HTMLDivElement;
|
|
221
221
|
|
|
222
222
|
// ElementBase requires contentEl
|
|
223
223
|
this.elements.contentEl = shadow.getElementById('identity-details-view') as HTMLDivElement;
|
|
@@ -260,7 +260,7 @@ export class KeystoneDetailsComponentInstance
|
|
|
260
260
|
this.elements!.btnUpdateIdentityEl.addEventListener('click', () => this.handleUpdateIdentity());
|
|
261
261
|
this.elements!.btnLinkGoogleEl.addEventListener('click', () => this.handleSSOClick('google'));
|
|
262
262
|
this.elements!.btnLinkGithubEl.addEventListener('click', () => this.handleSSOClick('github'));
|
|
263
|
-
this.elements!.
|
|
263
|
+
this.elements!.btnChangePasswordEl.addEventListener('click', () => this.handleChangePassword());
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
private toggleAccordion() {
|
|
@@ -591,17 +591,17 @@ export class KeystoneDetailsComponentInstance
|
|
|
591
591
|
// Render Challenge Pools
|
|
592
592
|
this.renderChallengePools(keystone.data?.challengePools || []);
|
|
593
593
|
|
|
594
|
-
// Render Delegates, SSO link, and
|
|
594
|
+
// Render Delegates, SSO link, and Password Change cards (only if this is a primary keystone, hide them for delegate/session keystones)
|
|
595
595
|
const details = await this.getKeystoneDetails(this.ibGibAddr);
|
|
596
596
|
if (details.isPrimary) {
|
|
597
597
|
this.elements.delegatesCardEl.classList.remove('hidden');
|
|
598
598
|
await this.renderDelegates(keystone.data?.delegates);
|
|
599
599
|
this.elements.ssoLinkCardEl.classList.remove('hidden');
|
|
600
|
-
this.elements.
|
|
600
|
+
this.elements.changePasswordCardEl.classList.remove('hidden');
|
|
601
601
|
} else {
|
|
602
602
|
this.elements.delegatesCardEl.classList.add('hidden');
|
|
603
603
|
this.elements.ssoLinkCardEl.classList.add('hidden');
|
|
604
|
-
this.elements.
|
|
604
|
+
this.elements.changePasswordCardEl.classList.add('hidden');
|
|
605
605
|
}
|
|
606
606
|
|
|
607
607
|
this.updateActiveButtonState();
|
|
@@ -924,7 +924,7 @@ export class KeystoneDetailsComponentInstance
|
|
|
924
924
|
let masterSecret: string;
|
|
925
925
|
try {
|
|
926
926
|
masterSecret = await promptForSecret({
|
|
927
|
-
msg: `Enter Master
|
|
927
|
+
msg: `Enter Master Password to revoke delegate '${deviceName}':`,
|
|
928
928
|
confirm: false
|
|
929
929
|
});
|
|
930
930
|
} catch (err) {
|
|
@@ -1103,8 +1103,8 @@ export class KeystoneDetailsComponentInstance
|
|
|
1103
1103
|
while (tries < maxTries && !isCorrect) {
|
|
1104
1104
|
try {
|
|
1105
1105
|
const promptMsg = tries === 0
|
|
1106
|
-
? "Link successful! Enter your local Master Secret (
|
|
1107
|
-
: `Incorrect
|
|
1106
|
+
? "Link successful! Enter your local Master Secret (Password) to sign and register the custodian pool"
|
|
1107
|
+
: `Incorrect password. Please try again (${maxTries - tries} tries remaining):`;
|
|
1108
1108
|
masterSecret = await promptForSecret({
|
|
1109
1109
|
msg: promptMsg,
|
|
1110
1110
|
confirm: false
|
|
@@ -1133,7 +1133,7 @@ export class KeystoneDetailsComponentInstance
|
|
|
1133
1133
|
}
|
|
1134
1134
|
|
|
1135
1135
|
if (!isCorrect) {
|
|
1136
|
-
this.setSSOStatus("Link cancelled or failed (incorrect
|
|
1136
|
+
this.setSSOStatus("Link cancelled or failed (incorrect password).", "error");
|
|
1137
1137
|
return;
|
|
1138
1138
|
}
|
|
1139
1139
|
|
|
@@ -1200,7 +1200,7 @@ export class KeystoneDetailsComponentInstance
|
|
|
1200
1200
|
this.setSSOStatus(`Unlinking ${providerId}...`, "info");
|
|
1201
1201
|
|
|
1202
1202
|
try {
|
|
1203
|
-
// 1. Prompt user for
|
|
1203
|
+
// 1. Prompt user for password
|
|
1204
1204
|
const keystoneService = new KeystoneService_V1();
|
|
1205
1205
|
let masterSecret: string | undefined;
|
|
1206
1206
|
let isCorrect = false;
|
|
@@ -1210,8 +1210,8 @@ export class KeystoneDetailsComponentInstance
|
|
|
1210
1210
|
while (tries < maxTries && !isCorrect) {
|
|
1211
1211
|
try {
|
|
1212
1212
|
const promptMsg = tries === 0
|
|
1213
|
-
? `Are you sure you want to unlink your ${providerId} account? If so, enter your local Master Secret (
|
|
1214
|
-
: `Incorrect
|
|
1213
|
+
? `Are you sure you want to unlink your ${providerId} account? If so, enter your local Master Secret (Password) to authorize. Otherwise, click Cancel.`
|
|
1214
|
+
: `Incorrect password. Please try again (${maxTries - tries} tries remaining):`;
|
|
1215
1215
|
masterSecret = await promptForSecret({
|
|
1216
1216
|
msg: promptMsg,
|
|
1217
1217
|
confirm: false
|
|
@@ -1238,7 +1238,7 @@ export class KeystoneDetailsComponentInstance
|
|
|
1238
1238
|
}
|
|
1239
1239
|
|
|
1240
1240
|
if (!isCorrect) {
|
|
1241
|
-
this.setSSOStatus("Unlink cancelled or failed (incorrect
|
|
1241
|
+
this.setSSOStatus("Unlink cancelled or failed (incorrect password).", "error");
|
|
1242
1242
|
return;
|
|
1243
1243
|
}
|
|
1244
1244
|
|
|
@@ -1292,49 +1292,49 @@ export class KeystoneDetailsComponentInstance
|
|
|
1292
1292
|
}
|
|
1293
1293
|
}
|
|
1294
1294
|
|
|
1295
|
-
private
|
|
1296
|
-
const el = this.elements!.
|
|
1295
|
+
private setChangePasswordStatus(msg: string, type: 'info' | 'success' | 'error') {
|
|
1296
|
+
const el = this.elements!.changePasswordStatusEl;
|
|
1297
1297
|
el.textContent = msg;
|
|
1298
1298
|
el.className = `status-msg ${type}`;
|
|
1299
1299
|
el.classList.remove('hidden');
|
|
1300
1300
|
}
|
|
1301
1301
|
|
|
1302
|
-
protected async
|
|
1303
|
-
const lc = `${this.lc}[${this.
|
|
1304
|
-
this.
|
|
1302
|
+
protected async handleChangePassword(): Promise<void> {
|
|
1303
|
+
const lc = `${this.lc}[${this.handleChangePassword.name}]`;
|
|
1304
|
+
this.setChangePasswordStatus("Validating entries...", "info");
|
|
1305
1305
|
|
|
1306
1306
|
try {
|
|
1307
|
-
const
|
|
1308
|
-
const
|
|
1309
|
-
const
|
|
1307
|
+
const currentPassword = this.elements!.inputCurrentPasswordEl.value;
|
|
1308
|
+
const newPassword = this.elements!.inputNewPasswordEl.value;
|
|
1309
|
+
const confirmPassword = this.elements!.inputConfirmPasswordEl.value;
|
|
1310
1310
|
|
|
1311
|
-
if (!
|
|
1312
|
-
this.
|
|
1311
|
+
if (!currentPassword) {
|
|
1312
|
+
this.setChangePasswordStatus("Current password is required.", "error");
|
|
1313
1313
|
return;
|
|
1314
1314
|
}
|
|
1315
|
-
if (!
|
|
1316
|
-
this.
|
|
1315
|
+
if (!newPassword) {
|
|
1316
|
+
this.setChangePasswordStatus("New password is required.", "error");
|
|
1317
1317
|
return;
|
|
1318
1318
|
}
|
|
1319
|
-
if (
|
|
1320
|
-
this.
|
|
1319
|
+
if (newPassword !== confirmPassword) {
|
|
1320
|
+
this.setChangePasswordStatus("New password and confirmation do not match.", "error");
|
|
1321
1321
|
return;
|
|
1322
1322
|
}
|
|
1323
1323
|
|
|
1324
1324
|
const keystoneService = new KeystoneService_V1();
|
|
1325
1325
|
|
|
1326
|
-
// 1. Verify current
|
|
1326
|
+
// 1. Verify current password against parent manage pool
|
|
1327
1327
|
const isMatch = await keystoneService.verifySigningSecret({
|
|
1328
1328
|
keystoneIbGib: this.ibGib!,
|
|
1329
|
-
signingSecret:
|
|
1329
|
+
signingSecret: currentPassword,
|
|
1330
1330
|
poolId: 'manage'
|
|
1331
1331
|
});
|
|
1332
1332
|
if (!isMatch) {
|
|
1333
|
-
this.
|
|
1333
|
+
this.setChangePasswordStatus("Incorrect current password.", "error");
|
|
1334
1334
|
return;
|
|
1335
1335
|
}
|
|
1336
1336
|
|
|
1337
|
-
this.
|
|
1337
|
+
this.setChangePasswordStatus("Evolving primary identity keystone...", "info");
|
|
1338
1338
|
|
|
1339
1339
|
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
1340
1340
|
if (!metaspace) { throw new Error(`(UNEXPECTED) metaspace falsy? (E: 95e3c8374b0f088f339f5c5a8700f826)`); }
|
|
@@ -1346,15 +1346,15 @@ export class KeystoneDetailsComponentInstance
|
|
|
1346
1346
|
const { newKeystone: evolvedParent, newDelegates: evolvedDelegates } =
|
|
1347
1347
|
await keystoneService.changePassword({
|
|
1348
1348
|
keystoneIbGib: this.ibGib!,
|
|
1349
|
-
newMasterSecret:
|
|
1350
|
-
signingSecret:
|
|
1349
|
+
newMasterSecret: newPassword,
|
|
1350
|
+
signingSecret: currentPassword,
|
|
1351
1351
|
signingPoolId: 'manage',
|
|
1352
1352
|
metaspace,
|
|
1353
1353
|
space
|
|
1354
1354
|
});
|
|
1355
1355
|
debugger; // look at evolvedDelegates
|
|
1356
1356
|
|
|
1357
|
-
this.
|
|
1357
|
+
this.setChangePasswordStatus("Resolving and rotating delegate keystones...", "info");
|
|
1358
1358
|
|
|
1359
1359
|
// // 3. Evolve registered delegate keystones (preserving custodian/foreign delegate secrets)
|
|
1360
1360
|
// const delegates = this.ibGib!.data?.delegates || {};
|
|
@@ -1368,7 +1368,7 @@ export class KeystoneDetailsComponentInstance
|
|
|
1368
1368
|
// space,
|
|
1369
1369
|
// });
|
|
1370
1370
|
|
|
1371
|
-
// // Identify user-owned challenge pools in the delegate that match the old
|
|
1371
|
+
// // Identify user-owned challenge pools in the delegate that match the old password
|
|
1372
1372
|
// const poolsToRotate: string[] = [];
|
|
1373
1373
|
// for (const pool of delegateKeystone.data?.challengePools ?? []) {
|
|
1374
1374
|
// if (!pool.isForeign) {
|
|
@@ -1383,7 +1383,7 @@ export class KeystoneDetailsComponentInstance
|
|
|
1383
1383
|
// */
|
|
1384
1384
|
// const isPoolMatch = await keystoneService.verifySigningSecret({
|
|
1385
1385
|
// keystoneIbGib: delegateKeystone,
|
|
1386
|
-
// signingSecret:
|
|
1386
|
+
// signingSecret: currentPassword,
|
|
1387
1387
|
// poolId: pool.id
|
|
1388
1388
|
// });
|
|
1389
1389
|
// if (isPoolMatch) { poolsToRotate.push(pool.id); }
|
|
@@ -1394,8 +1394,8 @@ export class KeystoneDetailsComponentInstance
|
|
|
1394
1394
|
// console.log(`${lc} Rotating pools in delegate ${delegateInfo.delegateTjpAddr}: ${poolsToRotate.join(', ')}`);
|
|
1395
1395
|
// const {newKeystone: evolvedDelegate} = await keystoneService.changePassword({
|
|
1396
1396
|
// keystoneIbGib: delegateKeystone,
|
|
1397
|
-
// newMasterSecret:
|
|
1398
|
-
// signingSecret:
|
|
1397
|
+
// newMasterSecret: newPassword,
|
|
1398
|
+
// signingSecret: currentPassword,
|
|
1399
1399
|
// signingPoolId: 'manage',
|
|
1400
1400
|
// poolIds: poolsToRotate,
|
|
1401
1401
|
// metaspace,
|
|
@@ -1406,7 +1406,7 @@ export class KeystoneDetailsComponentInstance
|
|
|
1406
1406
|
// }
|
|
1407
1407
|
|
|
1408
1408
|
// 4. Sync evolved parent and all evolved delegate keystones atomically
|
|
1409
|
-
this.
|
|
1409
|
+
this.setChangePasswordStatus("Syncing evolved keystones to server...", "info");
|
|
1410
1410
|
const parentTjpAddr = getIbGibAddr({ ibGib: this.ibGib! }); // is this.ibGib the tjp always? or is it the latest in the keystone line? Does it matter?
|
|
1411
1411
|
|
|
1412
1412
|
const syncRes = await getApiBridge().putEvolveKeystone({
|
|
@@ -1419,7 +1419,7 @@ export class KeystoneDetailsComponentInstance
|
|
|
1419
1419
|
}
|
|
1420
1420
|
|
|
1421
1421
|
// 5. Update index locally
|
|
1422
|
-
this.
|
|
1422
|
+
this.setChangePasswordStatus("Updating local indexes...", "info");
|
|
1423
1423
|
const newKeystoneIbGibs = [toDto({ ibGib: evolvedParent })];
|
|
1424
1424
|
if (evolvedDelegates) {
|
|
1425
1425
|
evolvedDelegates.forEach(d => {
|
|
@@ -1440,16 +1440,16 @@ export class KeystoneDetailsComponentInstance
|
|
|
1440
1440
|
});
|
|
1441
1441
|
|
|
1442
1442
|
// Clear inputs
|
|
1443
|
-
this.elements!.
|
|
1444
|
-
this.elements!.
|
|
1445
|
-
this.elements!.
|
|
1443
|
+
this.elements!.inputCurrentPasswordEl.value = "";
|
|
1444
|
+
this.elements!.inputNewPasswordEl.value = "";
|
|
1445
|
+
this.elements!.inputConfirmPasswordEl.value = "";
|
|
1446
1446
|
|
|
1447
|
-
this.
|
|
1447
|
+
this.setChangePasswordStatus("Password updated successfully!", "success");
|
|
1448
1448
|
await this.loadIbGib({ getLatest: true });
|
|
1449
1449
|
|
|
1450
1450
|
} catch (error) {
|
|
1451
|
-
console.error(`${lc}
|
|
1452
|
-
this.
|
|
1451
|
+
console.error(`${lc} Password rotation failed: ${extractErrorMsg(error)}`);
|
|
1452
|
+
this.setChangePasswordStatus(`Update failed: ${extractErrorMsg(error)}`, "error");
|
|
1453
1453
|
}
|
|
1454
1454
|
}
|
|
1455
1455
|
}
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
</div>
|
|
12
12
|
|
|
13
13
|
<div class="input-group">
|
|
14
|
-
<label for="input-
|
|
15
|
-
<input type="password" id="input-
|
|
14
|
+
<label for="input-password">Primary Password</label>
|
|
15
|
+
<input type="password" id="input-password" placeholder="Enter your master password..." class="secret-input">
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
18
|
<div class="action-row">
|
|
@@ -28,4 +28,4 @@
|
|
|
28
28
|
<p class="warning">🔒 The selected device delegate has been unlinked from your primary identity.</p>
|
|
29
29
|
</div>
|
|
30
30
|
</div>
|
|
31
|
-
</div>
|
|
31
|
+
</div>
|
|
@@ -58,7 +58,7 @@ interface RemoveDeviceElements extends ElementsBase {
|
|
|
58
58
|
containerEl: HTMLDivElement;
|
|
59
59
|
titleEl: HTMLHeadingElement;
|
|
60
60
|
selectDelegateEl: HTMLSelectElement;
|
|
61
|
-
|
|
61
|
+
inputPassword: HTMLInputElement;
|
|
62
62
|
btnRemoveDeviceEl: HTMLButtonElement;
|
|
63
63
|
statusArea: HTMLDivElement;
|
|
64
64
|
statusText: HTMLParagraphElement;
|
|
@@ -89,7 +89,7 @@ export class RemoveDeviceComponentInstance
|
|
|
89
89
|
this.elements.containerEl = this.shadowRoot!.getElementById('container') as HTMLDivElement;
|
|
90
90
|
this.elements.titleEl = this.shadowRoot!.getElementById('title') as HTMLHeadingElement;
|
|
91
91
|
this.elements.selectDelegateEl = this.shadowRoot!.getElementById('select-delegate') as HTMLSelectElement;
|
|
92
|
-
this.elements.
|
|
92
|
+
this.elements.inputPassword = this.shadowRoot!.getElementById('input-password') as HTMLInputElement;
|
|
93
93
|
this.elements.btnRemoveDeviceEl = this.shadowRoot!.getElementById('btn-remove-device') as HTMLButtonElement;
|
|
94
94
|
this.elements.statusArea = this.shadowRoot!.getElementById('status-area') as HTMLDivElement;
|
|
95
95
|
this.elements.statusText = this.shadowRoot!.getElementById('status-text') as HTMLParagraphElement;
|
|
@@ -108,8 +108,8 @@ export class RemoveDeviceComponentInstance
|
|
|
108
108
|
|
|
109
109
|
private initHandlers() {
|
|
110
110
|
this.elements!.btnRemoveDeviceEl.addEventListener('click', () => this.handleRemoveDevice());
|
|
111
|
-
this.elements!.
|
|
112
|
-
this.elements!.
|
|
111
|
+
this.elements!.inputPassword.addEventListener('input', () => {
|
|
112
|
+
this.elements!.inputPassword.classList.remove('invalid');
|
|
113
113
|
});
|
|
114
114
|
}
|
|
115
115
|
|
|
@@ -187,11 +187,11 @@ export class RemoveDeviceComponentInstance
|
|
|
187
187
|
return;
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
const masterSecret = this.elements!.
|
|
191
|
-
this.elements!.
|
|
190
|
+
const masterSecret = this.elements!.inputPassword.value.trim();
|
|
191
|
+
this.elements!.inputPassword.value = masterSecret;
|
|
192
192
|
if (!masterSecret) {
|
|
193
|
-
this.elements!.
|
|
194
|
-
this.setStatus("Please enter your master
|
|
193
|
+
this.elements!.inputPassword.classList.add('invalid');
|
|
194
|
+
this.setStatus("Please enter your master password.", "error");
|
|
195
195
|
return;
|
|
196
196
|
}
|
|
197
197
|
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module util/device-helper
|
|
3
|
+
*
|
|
4
|
+
* Pure utility functions for generating clean, friendly, auto-generated device names/tags.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface DeviceTagOptions {
|
|
8
|
+
userAgent?: string;
|
|
9
|
+
userAgentData?: {
|
|
10
|
+
mobile?: boolean;
|
|
11
|
+
platform?: string;
|
|
12
|
+
brands?: Array<{ brand: string; version: string }>;
|
|
13
|
+
};
|
|
14
|
+
maxTouchPoints?: number;
|
|
15
|
+
screenWidth?: number;
|
|
16
|
+
randomSuffix?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generates a friendly, URL-safe, kebab-cased default device tag.
|
|
21
|
+
* (e.g., `chrome-win-a1b2`, `mobile-safari-ios-3c4d`, `tablet-android-e5f6`).
|
|
22
|
+
*
|
|
23
|
+
* Pure function: Uses global browser objects (`navigator`, `window`) only as fallbacks
|
|
24
|
+
* when explicit options are omitted, enabling deterministic unit testing.
|
|
25
|
+
*
|
|
26
|
+
* @param opts Optional mock options for deterministic unit testing.
|
|
27
|
+
*/
|
|
28
|
+
export function generateDefaultDeviceTag(opts?: DeviceTagOptions): string {
|
|
29
|
+
const ua = (opts?.userAgent ?? (typeof navigator !== 'undefined' ? navigator.userAgent : '') ?? '').toLowerCase();
|
|
30
|
+
const uaData = opts?.userAgentData ?? (typeof navigator !== 'undefined' ? (navigator as any).userAgentData : undefined);
|
|
31
|
+
const touchPoints = opts?.maxTouchPoints ?? (typeof navigator !== 'undefined' ? navigator.maxTouchPoints : 0) ?? 0;
|
|
32
|
+
const width = opts?.screenWidth ?? (typeof window !== 'undefined' ? window.innerWidth : 1024) ?? 1024;
|
|
33
|
+
|
|
34
|
+
// 1. Detect Form Factor (Mobile, Tablet, Desktop)
|
|
35
|
+
let formFactor = '';
|
|
36
|
+
const isMobileUaData = uaData?.mobile;
|
|
37
|
+
const isMobileUa = /mobile|iphone|ipod|android.*mobile|windows phone/i.test(ua);
|
|
38
|
+
const isTabletUa = /ipad|tablet|(android(?!.*mobile))/i.test(ua) || (touchPoints > 1 && /macintosh/i.test(ua) && width <= 1366);
|
|
39
|
+
|
|
40
|
+
if (isTabletUa) {
|
|
41
|
+
formFactor = 'tablet';
|
|
42
|
+
} else if (isMobileUaData || isMobileUa) {
|
|
43
|
+
formFactor = 'mobile';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 2. Detect OS / Platform
|
|
47
|
+
let platform = '';
|
|
48
|
+
const uaPlatform = (uaData?.platform || '').toLowerCase();
|
|
49
|
+
|
|
50
|
+
if (uaPlatform.includes('win') || ua.includes('windows')) {
|
|
51
|
+
platform = 'win';
|
|
52
|
+
} else if (uaPlatform.includes('ios') || ua.includes('iphone') || ua.includes('ipad') || ua.includes('ipod')) {
|
|
53
|
+
platform = 'ios';
|
|
54
|
+
} else if (uaPlatform.includes('mac') || ua.includes('macintosh') || ua.includes('mac os')) {
|
|
55
|
+
platform = 'mac';
|
|
56
|
+
} else if (uaPlatform.includes('android') || ua.includes('android')) {
|
|
57
|
+
platform = 'android';
|
|
58
|
+
} else if (uaPlatform.includes('linux') || ua.includes('linux')) {
|
|
59
|
+
platform = 'linux';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 3. Detect Browser
|
|
63
|
+
let browser = '';
|
|
64
|
+
if (ua.includes('edg/') || ua.includes('edge/')) {
|
|
65
|
+
browser = 'edge';
|
|
66
|
+
} else if (ua.includes('chrome/') || ua.includes('crios/')) {
|
|
67
|
+
browser = 'chrome';
|
|
68
|
+
} else if (ua.includes('firefox/') || ua.includes('fxios/')) {
|
|
69
|
+
browser = 'firefox';
|
|
70
|
+
} else if (ua.includes('safari/') && !ua.includes('chrome/') && !ua.includes('crios/')) {
|
|
71
|
+
browser = 'safari';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 4. Generate or use provided 4-character random suffix
|
|
75
|
+
let suffix = opts?.randomSuffix;
|
|
76
|
+
if (!suffix) {
|
|
77
|
+
const chars = '0123456789abcdef';
|
|
78
|
+
suffix = '';
|
|
79
|
+
for (let i = 0; i < 4; i++) {
|
|
80
|
+
suffix += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
suffix = suffix.toLowerCase().replace(/[^a-z0-9]/g, '').slice(0, 8);
|
|
84
|
+
|
|
85
|
+
// 5. Assemble Tokens
|
|
86
|
+
const tokens: string[] = [];
|
|
87
|
+
if (formFactor) {
|
|
88
|
+
tokens.push(formFactor);
|
|
89
|
+
}
|
|
90
|
+
if (browser) {
|
|
91
|
+
tokens.push(browser);
|
|
92
|
+
}
|
|
93
|
+
if (platform) {
|
|
94
|
+
tokens.push(platform);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (tokens.length === 0) {
|
|
98
|
+
tokens.push('device');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
tokens.push(suffix);
|
|
102
|
+
|
|
103
|
+
return tokens.join('-');
|
|
104
|
+
}
|