diff --git a/api.md b/api.md index 80529c53a4a829c2faa6b2e0325cb15b200ae88b..c394bf7132416b6ebe736849139cc485f5380c3a 100644 --- a/api.md +++ b/api.md @@ -247,7 +247,7 @@ Client.constructor #### Defined in -dist/hb-auth.d.ts:213 +dist/hb-auth.d.ts:234 ## Properties @@ -261,7 +261,7 @@ Client.#private #### Defined in -dist/hb-auth.d.ts:90 +dist/hb-auth.d.ts:101 ___ @@ -275,7 +275,7 @@ Client.clientOptions #### Defined in -dist/hb-auth.d.ts:212 +dist/hb-auth.d.ts:233 ## Methods @@ -301,7 +301,7 @@ Client.authenticate #### Defined in -dist/hb-auth.d.ts:216 +dist/hb-auth.d.ts:237 ___ @@ -319,7 +319,7 @@ Client.authorize #### Defined in -dist/hb-auth.d.ts:214 +dist/hb-auth.d.ts:235 ___ @@ -348,7 +348,7 @@ Client.getAuthByUser #### Defined in -dist/hb-auth.d.ts:139 +dist/hb-auth.d.ts:150 ___ @@ -371,13 +371,13 @@ Client.getAuths #### Defined in -dist/hb-auth.d.ts:132 +dist/hb-auth.d.ts:143 ___ ### getUserSettings -â–¸ **getUserSettings**(`username`): `Promise`\<`undefined` \| `UserSettings`\> +â–¸ **getUserSettings**(`username`): `Promise`\<``null`` \| `UserSettings`\> #### Parameters @@ -387,7 +387,7 @@ ___ #### Returns -`Promise`\<`undefined` \| `UserSettings`\> +`Promise`\<``null`` \| `UserSettings`\> #### Inherited from @@ -395,7 +395,7 @@ Client.getUserSettings #### Defined in -dist/hb-auth.d.ts:140 +dist/hb-auth.d.ts:151 ___ @@ -428,7 +428,7 @@ Client.importKey #### Defined in -dist/hb-auth.d.ts:182 +dist/hb-auth.d.ts:199 ___ @@ -451,7 +451,7 @@ Client.initialize #### Defined in -dist/hb-auth.d.ts:120 +dist/hb-auth.d.ts:131 ___ @@ -474,13 +474,19 @@ Client.lock #### Defined in -dist/hb-auth.d.ts:165 +dist/hb-auth.d.ts:182 ___ ### logout -â–¸ **logout**(): `Promise`\<`void`\> +â–¸ **logout**(`username`): `Promise`\<`void`\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `username` | `string` | #### Returns @@ -501,7 +507,29 @@ Client.logout #### Defined in -dist/hb-auth.d.ts:187 +dist/hb-auth.d.ts:204 + +___ + +### logoutAll + +â–¸ **logoutAll**(): `Promise`\<`void`\> + +#### Returns + +`Promise`\<`void`\> + +**`Description`** + +Method that ends all user sessions. + +#### Inherited from + +Client.logoutAll + +#### Defined in + +dist/hb-auth.d.ts:208 ___ @@ -529,7 +557,7 @@ Client.register #### Defined in -dist/hb-auth.d.ts:215 +dist/hb-auth.d.ts:236 ___ @@ -557,7 +585,38 @@ Client.setSessionEndCallback #### Defined in -dist/hb-auth.d.ts:126 +dist/hb-auth.d.ts:137 + +___ + +### setUserSettings + +â–¸ **setUserSettings**(`username`, `settings`, `keyType`): `Promise`\<`void`\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `username` | `string` | +| `settings` | `Object` | +| `settings.authorizedAccounts?` | `Object` | +| `settings.authorizedAccounts.active` | `undefined` \| `string` | +| `settings.authorizedAccounts.owner` | `undefined` \| `string` | +| `settings.authorizedAccounts.posting` | `undefined` \| `string` | +| `settings.strict` | `boolean` | +| `keyType` | ``"active"`` \| ``"posting"`` \| ``"owner"`` | + +#### Returns + +`Promise`\<`void`\> + +#### Inherited from + +Client.setUserSettings + +#### Defined in + +dist/hb-auth.d.ts:152 ___ @@ -589,7 +648,7 @@ Client.sign #### Defined in -dist/hb-auth.d.ts:195 +dist/hb-auth.d.ts:216 ___ @@ -622,7 +681,7 @@ Client.singleSign #### Defined in -dist/hb-auth.d.ts:204 +dist/hb-auth.d.ts:225 ___ @@ -653,7 +712,7 @@ Client.unlock #### Defined in -dist/hb-auth.d.ts:173 +dist/hb-auth.d.ts:190 <a name="classesonlineclientmd"></a> @@ -693,7 +752,7 @@ Client.constructor #### Defined in -dist/hb-auth.d.ts:224 +dist/hb-auth.d.ts:245 ## Properties @@ -707,7 +766,7 @@ Client.#private #### Defined in -dist/hb-auth.d.ts:90 +dist/hb-auth.d.ts:101 ___ @@ -721,7 +780,7 @@ Client.clientOptions #### Defined in -dist/hb-auth.d.ts:223 +dist/hb-auth.d.ts:244 ___ @@ -731,7 +790,7 @@ ___ #### Defined in -dist/hb-auth.d.ts:226 +dist/hb-auth.d.ts:247 ## Methods @@ -757,7 +816,7 @@ Client.authenticate #### Defined in -dist/hb-auth.d.ts:227 +dist/hb-auth.d.ts:248 ___ @@ -784,7 +843,7 @@ Client.authorize #### Defined in -dist/hb-auth.d.ts:225 +dist/hb-auth.d.ts:246 ___ @@ -813,7 +872,7 @@ Client.getAuthByUser #### Defined in -dist/hb-auth.d.ts:139 +dist/hb-auth.d.ts:150 ___ @@ -836,13 +895,13 @@ Client.getAuths #### Defined in -dist/hb-auth.d.ts:132 +dist/hb-auth.d.ts:143 ___ ### getUserSettings -â–¸ **getUserSettings**(`username`): `Promise`\<`undefined` \| `UserSettings`\> +â–¸ **getUserSettings**(`username`): `Promise`\<``null`` \| `UserSettings`\> #### Parameters @@ -852,7 +911,7 @@ ___ #### Returns -`Promise`\<`undefined` \| `UserSettings`\> +`Promise`\<``null`` \| `UserSettings`\> #### Inherited from @@ -860,7 +919,7 @@ Client.getUserSettings #### Defined in -dist/hb-auth.d.ts:140 +dist/hb-auth.d.ts:151 ___ @@ -893,7 +952,7 @@ Client.importKey #### Defined in -dist/hb-auth.d.ts:182 +dist/hb-auth.d.ts:199 ___ @@ -916,7 +975,7 @@ Client.initialize #### Defined in -dist/hb-auth.d.ts:120 +dist/hb-auth.d.ts:131 ___ @@ -939,13 +998,19 @@ Client.lock #### Defined in -dist/hb-auth.d.ts:165 +dist/hb-auth.d.ts:182 ___ ### logout -â–¸ **logout**(): `Promise`\<`void`\> +â–¸ **logout**(`username`): `Promise`\<`void`\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `username` | `string` | #### Returns @@ -966,7 +1031,29 @@ Client.logout #### Defined in -dist/hb-auth.d.ts:187 +dist/hb-auth.d.ts:204 + +___ + +### logoutAll + +â–¸ **logoutAll**(): `Promise`\<`void`\> + +#### Returns + +`Promise`\<`void`\> + +**`Description`** + +Method that ends all user sessions. + +#### Inherited from + +Client.logoutAll + +#### Defined in + +dist/hb-auth.d.ts:208 ___ @@ -994,7 +1081,7 @@ Client.register #### Defined in -dist/hb-auth.d.ts:228 +dist/hb-auth.d.ts:249 ___ @@ -1022,7 +1109,38 @@ Client.setSessionEndCallback #### Defined in -dist/hb-auth.d.ts:126 +dist/hb-auth.d.ts:137 + +___ + +### setUserSettings + +â–¸ **setUserSettings**(`username`, `settings`, `keyType`): `Promise`\<`void`\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `username` | `string` | +| `settings` | `Object` | +| `settings.authorizedAccounts?` | `Object` | +| `settings.authorizedAccounts.active` | `undefined` \| `string` | +| `settings.authorizedAccounts.owner` | `undefined` \| `string` | +| `settings.authorizedAccounts.posting` | `undefined` \| `string` | +| `settings.strict` | `boolean` | +| `keyType` | ``"active"`` \| ``"posting"`` \| ``"owner"`` | + +#### Returns + +`Promise`\<`void`\> + +#### Inherited from + +Client.setUserSettings + +#### Defined in + +dist/hb-auth.d.ts:152 ___ @@ -1054,7 +1172,7 @@ Client.sign #### Defined in -dist/hb-auth.d.ts:195 +dist/hb-auth.d.ts:216 ___ @@ -1087,7 +1205,7 @@ Client.singleSign #### Defined in -dist/hb-auth.d.ts:204 +dist/hb-auth.d.ts:225 ___ @@ -1118,7 +1236,7 @@ Client.unlock #### Defined in -dist/hb-auth.d.ts:173 +dist/hb-auth.d.ts:190 <a name="interfacesauthstatusmd"></a> @@ -1139,7 +1257,7 @@ An error in case of unsuccessful authorization #### Defined in -dist/hb-auth.d.ts:58 +dist/hb-auth.d.ts:69 ___ @@ -1153,7 +1271,7 @@ Value that describes auth status #### Defined in -dist/hb-auth.d.ts:53 +dist/hb-auth.d.ts:64 <a name="interfacesauthusermd"></a> @@ -1168,7 +1286,7 @@ dist/hb-auth.d.ts:53 #### Defined in -dist/hb-auth.d.ts:22 +dist/hb-auth.d.ts:27 ___ @@ -1178,7 +1296,7 @@ ___ #### Defined in -dist/hb-auth.d.ts:23 +dist/hb-auth.d.ts:28 ___ @@ -1188,7 +1306,7 @@ ___ #### Defined in -dist/hb-auth.d.ts:24 +dist/hb-auth.d.ts:29 ___ @@ -1198,7 +1316,7 @@ ___ #### Defined in -dist/hb-auth.d.ts:21 +dist/hb-auth.d.ts:26 ___ @@ -1208,7 +1326,7 @@ ___ #### Defined in -dist/hb-auth.d.ts:20 +dist/hb-auth.d.ts:25 <a name="interfacesclientoptionsmd"></a> @@ -1231,7 +1349,7 @@ Blockchain ID used for calculating digest #### Defined in -dist/hb-auth.d.ts:66 +dist/hb-auth.d.ts:77 ___ @@ -1249,7 +1367,7 @@ Blockchain Node address for online account verification #### Defined in -dist/hb-auth.d.ts:72 +dist/hb-auth.d.ts:83 ___ @@ -1267,7 +1385,7 @@ Session timeout (in seconds) for Wallet, after that session will be destroyed an #### Defined in -dist/hb-auth.d.ts:84 +dist/hb-auth.d.ts:95 ___ @@ -1285,4 +1403,4 @@ Url for worker script path provided by hb-auth library #### Defined in -dist/hb-auth.d.ts:78 +dist/hb-auth.d.ts:89 diff --git a/example/app.js b/example/app.js index f4be443eaa74faedc79d4bce86cd113d71e690f3..6667e114609ef652c703c150780892a582227039 100644 --- a/example/app.js +++ b/example/app.js @@ -78,8 +78,9 @@ client.initialize().then(async (authClient) => { data[key] = val; } + const isStrict = data.strict === 'on' authClient - .register(data.username, data.password, data.key, data.type) + .register(data.username, data.password, data.key, data.type, isStrict) .then((status) => { if (status.ok) { updateStatus(); diff --git a/example/index.html b/example/index.html index 6edeb0c46f710531817b27dc74ed4acfb6fb16fe..a747da3903ae09d4c14d0eb107f7a558cec63add 100644 --- a/example/index.html +++ b/example/index.html @@ -115,6 +115,13 @@ </div> </div> + <div class="field"> + <label class="label">Strict Mode</label> + <div class="control"> + <input name="strict" type="checkbox" /> + </div> + </div> + <button class="button is-primary" style="float: right;" type="submit">Authorize</button> </form> </div> diff --git a/package.json b/package.json index 9c24224ebfd2784c1fd73ea7a85f9dbf837d6765..b31765b098566032688fa0ab174852763ddcd72c 100644 --- a/package.json +++ b/package.json @@ -70,8 +70,8 @@ "typescript": "^5.4.5" }, "dependencies": { - "@hiveio/beekeeper": "1.27.6-rc3-stable.240916231926", - "@hiveio/wax": "1.27.6-rc5-stable.241029104701", + "@hiveio/beekeeper": "1.27.6-rc4", + "@hiveio/wax": "1.27.6-rc6-stable.241217155323", "comlink": "^4.4.1", "idb": "^7.1.1" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5ea89475ad5ba74725dffdabf5b648b6ab07aa61..d8f82cf7801c71c32eabb60bd9f33f97add67e2a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,11 +9,11 @@ importers: .: dependencies: '@hiveio/beekeeper': - specifier: 1.27.6-rc3-stable.240916231926 - version: 1.27.6-rc3-stable.240916231926 + specifier: 1.27.6-rc4 + version: 1.27.6-rc4 '@hiveio/wax': - specifier: 1.27.6-rc5-stable.241029104701 - version: 1.27.6-rc5-stable.241029104701 + specifier: 1.27.6-rc6-stable.241217155323 + version: 1.27.6-rc6-stable.241217155323 comlink: specifier: ^4.4.1 version: 4.4.1 @@ -870,16 +870,12 @@ packages: resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@hiveio/beekeeper@1.27.6-rc3-stable.240916231926': - resolution: {integrity: sha1-GhsyYwb56QgPSawZNrgz+24gzRA=, tarball: https://gitlab.syncad.com/api/v4/projects/198/packages/npm/@hiveio/beekeeper/-/@hiveio/beekeeper-1.27.6-rc3-stable.240916231926.tgz} - engines: {node: '>= 18'} - '@hiveio/beekeeper@1.27.6-rc4': resolution: {integrity: sha1-mj3k3qAgTdIrc6IxzC5VOprhWZs=, tarball: https://gitlab.syncad.com/api/v4/projects/198/packages/npm/@hiveio/beekeeper/-/@hiveio/beekeeper-1.27.6-rc4.tgz} engines: {node: '>= 18'} - '@hiveio/wax@1.27.6-rc5-stable.241029104701': - resolution: {integrity: sha1-Jz6tVQXhyZxf8Gu665dyDTw2wH0=, tarball: https://gitlab.syncad.com/api/v4/projects/419/packages/npm/@hiveio/wax/-/@hiveio/wax-1.27.6-rc5-stable.241029104701.tgz} + '@hiveio/wax@1.27.6-rc6-stable.241217155323': + resolution: {integrity: sha1-z/qDiiz20fYCzIroqSd2A4p7OuY=, tarball: https://gitlab.syncad.com/api/v4/projects/419/packages/npm/@hiveio/wax/-/@hiveio/wax-1.27.6-rc6-stable.241217155323.tgz} engines: {node: '>= 18'} '@humanwhocodes/config-array@0.11.14': @@ -3669,11 +3665,9 @@ snapshots: '@eslint/js@8.57.0': {} - '@hiveio/beekeeper@1.27.6-rc3-stable.240916231926': {} - '@hiveio/beekeeper@1.27.6-rc4': {} - '@hiveio/wax@1.27.6-rc5-stable.241029104701': + '@hiveio/wax@1.27.6-rc6-stable.241217155323': dependencies: '@hiveio/beekeeper': 1.27.6-rc4 class-transformer: 0.5.1 diff --git a/src/__tests__/offline.ts b/src/__tests__/offline.ts index c0af5155c7268b8192276bd87b9996358590f1fd..dcf21c5f1e2ede6622471ab0e8612b14225fd22f 100644 --- a/src/__tests__/offline.ts +++ b/src/__tests__/offline.ts @@ -162,7 +162,7 @@ test.describe("HB Auth Offline Client base tests", () => { test("Should logout user on logout() call", async () => { const authorized = await page.evaluate(async ({ username }) => { - await authInstance.logout(); + await authInstance.logout(username); return (await authInstance.getAuthByUser(username))?.authorized; }, user); @@ -185,25 +185,9 @@ test.describe("HB Auth Offline Client base tests", () => { expect(authorized).toBeTruthy(); }); - test("Should return error if user tries to login while already logged in", async () => { - const error = await page.evaluate(async ({ username, password, keys }) => { - try { - await authInstance.authenticate( - username, - password, - keys[0].type as KeyAuthorityType, - ); - } catch (error) { - return error.message; - } - }, user); - - expect(error).toBe("User is already logged in"); - }); - test("Should return error if user tries to login with bad authority type", async () => { const error = await page.evaluate(async ({ username, password }) => { - await authInstance.logout(); + await authInstance.logout(username); try { await authInstance.authenticate(username, password, "active"); @@ -218,7 +202,7 @@ test.describe("HB Auth Offline Client base tests", () => { test("Should throw if invalid password given", async () => { const error = await page.evaluate(async ({ username, keys }) => { try { - await authInstance.logout(); + await authInstance.logout(username); await authInstance.authenticate( username, "abc", @@ -297,7 +281,7 @@ test.describe("HB Auth Offline Client base tests", () => { test("Should user login with different authority types", async () => { const authorizedKeyType1 = await page.evaluate( async ({ username, password, keys }) => { - await authInstance.logout(); + await authInstance.logout(username); await authInstance.authenticate( username, password, @@ -312,7 +296,7 @@ test.describe("HB Auth Offline Client base tests", () => { const authorizedKeyType2 = await page.evaluate( async ({ username, password, keys }) => { - await authInstance.logout(); + await authInstance.logout(username); await authInstance.authenticate( username, password, @@ -345,12 +329,13 @@ test.describe("HB Auth Offline Client base tests", () => { test("Should user sign tx and get signed tx back with selected key type", async () => { const signed1 = await page.evaluate( async ({ username, password, keys, txs }) => { - await authInstance.logout(); + await authInstance.logout(username); await authInstance.authenticate( username, password, keys[0].type as KeyAuthorityType, ); + const signed = await authInstance.sign( username, txs[0].digest, @@ -404,7 +389,7 @@ test.describe("HB Auth Offline Client base tests", () => { } catch (error) { return error.message; } finally { - await instance.logout(); + await instance.logout(username); } }, user, @@ -453,7 +438,7 @@ test.describe("HB Auth Offline Client base tests", () => { test("Should user able to lock/unlock wallet during user's session time", async () => { const locked = await page.evaluate(async ({ username, password, keys }) => { - await authInstance.logout(); + await authInstance.logout(username); await authInstance.authenticate( username, password, @@ -477,7 +462,7 @@ test.describe("HB Auth Offline Client base tests", () => { test("Should user get error when trying to lock wallet if not authenticated", async () => { const errorWhileLocking = await page.evaluate(async () => { - await authInstance.logout(); + await authInstance.logoutAll(); try { await authInstance.lock(); } catch (error) { diff --git a/src/__tests__/online.ts b/src/__tests__/online.ts index 37c6fa1b4abb70c19835649ecd85027fc9ca5c1d..3e26169041bccd1951dd28848c90c6398197009b 100644 --- a/src/__tests__/online.ts +++ b/src/__tests__/online.ts @@ -15,6 +15,7 @@ let browser!: ChromiumBrowser; const user = { username: process.env.CI_TEST_USER as string, + authorityUsername: process.env.CI_TEST_AUTHORITY_USER as string, password: "banana", keys: [ { @@ -189,7 +190,7 @@ test.describe("HB Auth Online Client base tests", () => { test("Should logout user on logout() call", async () => { const authorized = await page.evaluate(async ({ username }) => { - await authInstance.logout(); + await authInstance.logout(username); return (await authInstance.getAuthByUser(username))?.authorized; }, user); @@ -199,11 +200,8 @@ test.describe("HB Auth Online Client base tests", () => { test("Should user login with username and password", async () => { const authorized = await page.evaluate( async ({ username, password, keys }) => { - const authUser = await authInstance.getAuthByUser(username); + await authInstance.logout(username); - if (authUser?.authorized) { - await authInstance.logout(); - } await authInstance.authenticate( username, password, @@ -217,25 +215,9 @@ test.describe("HB Auth Online Client base tests", () => { expect(authorized).toBeTruthy(); }); - test("Should return error if user tries to login while already logged in", async () => { - const error = await page.evaluate(async ({ username, password, keys }) => { - try { - await authInstance.authenticate( - username, - password, - keys[0].type as KeyAuthorityType, - ); - } catch (error) { - return error.message; - } - }, user); - - expect(error).toBe("User is already logged in"); - }); - test("Should return error if user tries to login with bad authority type", async () => { const error = await page.evaluate(async ({ username, password }) => { - await authInstance.logout(); + await authInstance.logout(username); try { await authInstance.authenticate(username, password, "active"); @@ -250,7 +232,7 @@ test.describe("HB Auth Online Client base tests", () => { test("Should throw if invalid password given", async () => { const error = await page.evaluate(async ({ username, keys }) => { try { - await authInstance.logout(); + await authInstance.logout(username); await authInstance.authenticate( username, "abc", @@ -329,7 +311,7 @@ test.describe("HB Auth Online Client base tests", () => { test("Should user login with different authority types", async () => { const authorizedKeyType1 = await page.evaluate( async ({ username, password, keys }) => { - await authInstance.logout(); + await authInstance.logout(username); await authInstance.authenticate( username, password, @@ -344,7 +326,7 @@ test.describe("HB Auth Online Client base tests", () => { const authorizedKeyType2 = await page.evaluate( async ({ username, password, keys }) => { - await authInstance.logout(); + await authInstance.logout(username); await authInstance.authenticate( username, password, @@ -380,7 +362,7 @@ test.describe("HB Auth Online Client base tests", () => { test("Should user sign tx and get signed tx back with selected key type", async () => { const signed1 = await page.evaluate( async ({ username, password, keys, txs }) => { - await authInstance.logout(); + await authInstance.logout(username); await authInstance.authenticate( username, password, @@ -439,7 +421,7 @@ test.describe("HB Auth Online Client base tests", () => { } catch (error) { return error.message; } finally { - await instance.logout(); + await instance.logout(username); } }, user, @@ -575,7 +557,7 @@ test.describe("HB Auth Online Client base tests", () => { test("Should user able to lock/unlock wallet during user's session time", async () => { const locked = await page.evaluate(async ({ username, password, keys }) => { - await authInstance.logout(); + await authInstance.logout(username); await authInstance.authenticate( username, password, @@ -599,7 +581,7 @@ test.describe("HB Auth Online Client base tests", () => { test("Should user get error when trying to lock wallet if not authenticated", async () => { const errorWhileLocking = await page.evaluate(async () => { - await authInstance.logout(); + await authInstance.logoutAll(); try { await authInstance.lock(); } catch (error) { @@ -617,7 +599,7 @@ test.describe("HB Auth Online Client base tests", () => { const newPage = await newContext.newPage(); await navigate(newPage); - const signed = await newPage.evaluate( + const authorityUsername = await newPage.evaluate( async ({ username, password, keys, txs }) => { try { const instance = new AuthOnlineClient({ @@ -629,14 +611,16 @@ test.describe("HB Auth Online Client base tests", () => { password, keys[2].private, keys[2].type as KeyAuthorityType, - false // strict mode off + false, // strict mode off ); - const signed = await instance.sign( + await instance.sign( username, txs[2].digest, keys[2].type as KeyAuthorityType, ); - return signed; + return ( + await instance.getUserSettings(username) + )?.authorizedAccounts?.[keys[2].type as KeyAuthorityType]; } catch (error) { return error.message; } @@ -644,7 +628,7 @@ test.describe("HB Auth Online Client base tests", () => { user, ); - expect(signed).toBe(user.txs[2].signed); + expect(authorityUsername).toBe(user.authorityUsername); }); test("Should allow singleSign without any prior registration", async ({ @@ -705,43 +689,40 @@ test.describe("HB Auth Online Client base tests", () => { expect(signed).toBe(user.txs[1].signed); }); - test("Should maintain strict mode setting between sessions", async () => { + test("Should allow different users to authenticate simultaneously", async () => { const newContext = await browser.newContext(); const newPage = await newContext.newPage(); await navigate(newPage); - - // First register with user's own key in strict mode - await newPage.evaluate( - async ({ username, password, keys }) => { + + // First user authentication + await newPage.evaluate(async ({ username, password, keys }) => { + const instance = new AuthOnlineClient({ + workerUrl: "/dist/worker.js", + }); + await instance.initialize(); + await instance.register( + username, + password, + keys[0].private, + keys[0].type as KeyAuthorityType, + ); + }, user); + + // Second user authentication + const secondUserAuth = await newPage.evaluate( + async ({ authorityUsername, password, keys }) => { const instance = new AuthOnlineClient({ workerUrl: "/dist/worker.js", }); await instance.initialize(); - await instance.register( - username, - password, - keys[0].private, - keys[0].type as KeyAuthorityType, - true // strict mode - ); - }, - user, - ); - - // Try to register with another authority's key - should fail in strict mode - const error = await newPage.evaluate( - async ({ username, password, keys }) => { try { - const instance = new AuthOnlineClient({ - workerUrl: "/dist/worker.js", - }); - await instance.initialize(); await instance.register( - username, + authorityUsername, password, keys[2].private, keys[2].type as KeyAuthorityType, ); + return true; } catch (error) { return error.message; } @@ -749,74 +730,111 @@ test.describe("HB Auth Online Client base tests", () => { user, ); - expect(error).toBe("Invalid credentials"); + expect(secondUserAuth).toBe(true); + }); + + test("Should allow same user to authenticate with different key types", async () => { + const newContext = await browser.newContext(); + const newPage = await newContext.newPage(); + await navigate(newPage); - // Create new instance to verify strict mode persists - const settings = await newPage.evaluate(async ({ username }) => { + // First register and authenticate with posting key + await newPage.evaluate(async ({ username, password, keys }) => { const instance = new AuthOnlineClient({ workerUrl: "/dist/worker.js", }); await instance.initialize(); - return await instance.getUserSettings(username); + await instance.register( + username, + password, + keys[0].private, + keys[0].type as KeyAuthorityType, + ); + await instance.authenticate( + username, + password, + keys[0].type as KeyAuthorityType, + ); }, user); - expect(settings?.strict).toBe(true); + // Then register and authenticate with active key + const activeAuth = await newPage.evaluate( + async ({ username, password, keys }) => { + const instance = new AuthOnlineClient({ + workerUrl: "/dist/worker.js", + }); + await instance.initialize(); + try { + // First register the active key + await instance.register( + username, + password, + keys[1].private, + keys[1].type as KeyAuthorityType, + ); + // Then authenticate with it + await instance.authenticate( + username, + password, + keys[1].type as KeyAuthorityType, + ); + const authUser = await instance.getAuthByUser(username); + return authUser?.loggedInKeyType; + } catch (error) { + return error.message; + } + }, + user, + ); + + expect(activeAuth).toBe("active"); }); - test("Should maintain non-strict mode setting between sessions", async () => { + test("Should logout specific user while keeping others authenticated", async () => { const newContext = await browser.newContext(); const newPage = await newContext.newPage(); await navigate(newPage); - - // First register with authority's key in non-strict mode - await newPage.evaluate( - async ({ username, password, keys }) => { + + // Setup two authenticated users with state verification + const userStates = await newPage.evaluate( + async ({ authorityUsername, username, password, keys }) => { const instance = new AuthOnlineClient({ workerUrl: "/dist/worker.js", }); await instance.initialize(); + + // Register and authenticate first user + await instance.register(username, password, keys[0].private, "posting"); + await instance.authenticate(username, password, "posting"); + + // Register and authenticate second user await instance.register( - username, + authorityUsername, password, keys[2].private, keys[2].type as KeyAuthorityType, - false // non-strict mode ); - await instance.logout(); - }, - user, - ); - - // Try to login again with the same authority key - const authorized = await newPage.evaluate( - async ({ username, password, keys }) => { - const instance = new AuthOnlineClient({ - workerUrl: "/dist/worker.js", - }); - await instance.initialize(); await instance.authenticate( - username, + authorityUsername, password, keys[2].type as KeyAuthorityType, ); - const authUser = await instance.getAuthByUser(username); - return authUser?.authorized; + const user2State = await instance.getAuthByUser(authorityUsername); + + await instance.logout(username); + + const user1State = await instance.getAuthByUser(username); + + return { + user1: user1State, + user2: user2State, + }; }, user, ); - expect(authorized).toBeTruthy(); - - // Verify the settings are still non-strict - const settings = await newPage.evaluate(async ({ username }) => { - const instance = new AuthOnlineClient({ - workerUrl: "/dist/worker.js", - }); - await instance.initialize(); - return await instance.getUserSettings(username); - }, user); - - expect(settings?.strict).toBe(false); + expect(userStates.user1).toBeNull(); + expect(userStates.user2?.authorized).toBe(true); }); test.afterAll(async () => { diff --git a/src/client.ts b/src/client.ts index 2af6ac9dcde222becc8c4b40ef8fc91365f78173..4bafdc36c8f84a4d733b54922b0681a4829ef607 100644 --- a/src/client.ts +++ b/src/client.ts @@ -126,16 +126,15 @@ abstract class Client { } private async getWorkerEndpoint(): Promise<Endpoint> { - // TODO: detect missing worker file and throw - - return new Promise((resolve, reject) => { + return new Promise((resolve) => { let worker: SharedWorker | Worker; + if (isSupportSharedWorker) { - worker = new SharedWorker(this.options.workerUrl); - return resolve(worker.port); + worker = new SharedWorker(this.options.workerUrl, { type: "module" }); + resolve(worker.port); } else { - worker = new Worker(this.options.workerUrl); - return resolve(worker); + worker = new Worker(this.options.workerUrl, { type: "module" }); + resolve(worker); } }); } @@ -194,12 +193,21 @@ abstract class Client { return await this.#auth.getAuthByUser(username); } - public async getUserSettings( - username: string, - ): Promise<UserSettings | undefined> { + public async getUserSettings(username: string): Promise<UserSettings | null> { return await this.#auth.getUserSettings(username); } + public async setUserSettings( + username: string, + settings: { + strict: boolean; + authorizedAccounts?: { [K in KeyAuthorityType]?: string }; + }, + keyType: KeyAuthorityType, + ): Promise<void> { + return await this.#auth.setUserSettings(username, settings, keyType); + } + /** @hidden */ private async getVerificationTx( username: string, @@ -282,10 +290,10 @@ abstract class Client { ); if (authenticated) { - await this.#auth.onAuthComplete(false); + await this.#auth.onAuthComplete(username, false); return Promise.resolve({ ok: true }); } else { - await this.#auth.onAuthComplete(true); + await this.#auth.onAuthComplete(username, true); return Promise.reject(new AuthorizationError("Invalid credentials")); } } @@ -305,70 +313,9 @@ abstract class Client { ): Promise<AuthStatus> { try { const userSettings = await this.getUserSettings(username); - const isStrict = userSettings?.strict ?? true; - - if (!offline) { - // Get the account's authorities from the blockchain - const accounts = await this.hiveChain.api.database_api.find_accounts({ - accounts: [username], - }); - - // Create a verification transaction to get the public key - const txBuilder = await this.getVerificationTx( - username, - keyType, - offline, - ); - const signature = await this.#auth.authenticate( - username, - password, - keyType, - txBuilder.sigDigest, - ); - - txBuilder.sign(signature); - const publicKey = txBuilder.signatureKeys[0]; - - if (isStrict) { - // In strict mode, only check against key_auths - const account_key = accounts.accounts[0][keyType].key_auths[0][0]; - if (publicKey && !publicKey.endsWith(account_key)) { - await this.#auth.logout(); - return Promise.reject( - new AuthorizationError("Invalid credentials"), - ); - } - } else { - // When not in strict mode, check both key_auths and account_auths - const account = accounts.accounts[0]; - const key_auth_match = account[keyType].key_auths.some((keyAuths) => - publicKey.endsWith(keyAuths[0]), - ); + const isStrict = userSettings?.strict[keyType] ?? true; - if (!key_auth_match) { - // If no direct key match, check if the key belongs to an authorized account - const key_references = - await this.hiveChain.api.account_by_key_api.get_key_references({ - keys: [publicKey], - }); - - const key_owner = key_references.accounts[0]?.[0]; - if ( - !key_owner || - !account[keyType].account_auths.some( - (accountAuths) => accountAuths[0] === key_owner, - ) - ) { - await this.#auth.logout(); - return Promise.reject( - new AuthorizationError("Invalid credentials"), - ); - } - } - } - } - - // Continue with normal authentication + // Create a verification transaction const txBuilder = await this.getVerificationTx( username, keyType, @@ -390,10 +337,10 @@ abstract class Client { ); if (authenticated) { - await this.#auth.onAuthComplete(false); + await this.#auth.onAuthComplete(username, false); return Promise.resolve({ ok: true }); } else { - await this.#auth.logout(); + await this.#auth.logout(username); return Promise.reject(new AuthorizationError("Invalid credentials")); } } catch (err) { @@ -440,8 +387,15 @@ abstract class Client { * @description Method that ends existing user session. This is different than locking user. * When this is called any callback set via @see {Client.setSessionCallback} will fire. */ - public async logout(): Promise<void> { - await this.#auth.logout(); + public async logout(username: string): Promise<void> { + await this.#auth.logout(username); + } + + /** + * @description Method that ends all user sessions. + */ + public async logoutAll(): Promise<void> { + await this.#auth.logoutAll(); } /** @@ -546,16 +500,72 @@ class OnlineClient extends Client { ): Promise<boolean> { const verificationResult = await this.verify(txBuilder.toApiJson()); - if (isStrict && verificationResult) { - const accounts = await this.hiveChain.api.database_api.find_accounts({ - accounts: [username], - }); - const account_key = accounts.accounts[0][keyType].key_auths[0][0]; - - return account_key.endsWith(txBuilder.signatureKeys[0]); + if (!verificationResult) { + return false; } - return verificationResult; + const accounts = await this.hiveChain.api.database_api.find_accounts({ + accounts: [username], + }); + + const account = accounts.accounts[0]; + const publicKey = txBuilder.signatureKeys[0]; + + if (isStrict) { + // In strict mode, only check against key_auths + const account_key = account[keyType].key_auths[0][0]; + return publicKey.endsWith(account_key); + } else { + // When not in strict mode, check both key_auths and account_auths + const key_auth_match = account[keyType].key_auths.some((keyAuths) => + publicKey.endsWith(keyAuths[0]), + ); + + if (!key_auth_match) { + // If no direct key match, check if the key belongs to an authorized account + const key_references = + await this.hiveChain.api.account_by_key_api.get_key_references({ + keys: [publicKey], + }); + + const key_owner = key_references.accounts[0]?.[0]; + + const result = + !!key_owner && + account[keyType].account_auths.some( + (accountAuths) => accountAuths[0] === key_owner, + ); + + if (result && key_owner) { + // Save the authorized account to user settings + const currentSettings = (await this.getUserSettings(username)) ?? { + strict: {}, + alias: username, + authorizedAccounts: {}, + }; + + const authorizedAccounts = currentSettings.authorizedAccounts ?? {}; + + if (authorizedAccounts[keyType] !== key_owner) { + await this.setUserSettings( + username, + { + strict: currentSettings.strict[keyType] ?? true, + authorizedAccounts: { + ...authorizedAccounts, + [keyType]: key_owner, + }, + }, + keyType, + ); + } + } + + return result; + } + + return true; + } } private async verify(trx: ApiTransaction): Promise<boolean> { diff --git a/src/worker.ts b/src/worker.ts index dc7de533b2a91f782db7d9a6e51afd8264fd0f33..39a468118836aa2aad752d0ba53ca76b111f2130 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -19,8 +19,13 @@ const noop = async (): Promise<void> => {}; export type KeyAuthorityType = (typeof KEY_TYPES)[number]; export interface UserSettings { - strict: boolean; + strict: { + [K in KeyAuthorityType]?: boolean; + }; alias: string; + authorizedAccounts?: { + [K in KeyAuthorityType]?: string; + }; } export interface AuthUser { @@ -31,25 +36,19 @@ export interface AuthUser { registeredKeyTypes: KeyAuthorityType[]; } -class AuthWorker { +export type LoggedInUsers = Record<string, AuthUser>; + +export class AuthWorker { public readonly Ready: Promise<AuthWorker>; private api!: IBeekeeperInstance; private session!: IBeekeeperSession; private readonly storage = `/storage_root_${IDB_VERSION}`; private readonly aliasStorage = `/aliases_${IDB_VERSION}`; private sessionEndCallback = noop; - private _loggedInUser: AuthUser | undefined; private _generator!: AsyncGenerator<string, string>; - private _interval!: ReturnType<typeof setInterval>; + private _intervals: Record<string, ReturnType<typeof setInterval>> = {}; private readonly settingsStorage = `/settings_${IDB_VERSION}`; - - public get loggedInUser(): AuthUser | undefined { - return this._loggedInUser; - } - - public set loggedInUser(user: AuthUser | undefined) { - this._loggedInUser = user; - } + #loggedInUsers: LoggedInUsers = {}; constructor(private readonly sessionTimeout: number) { this.Ready = new Promise((resolve, reject) => { @@ -68,6 +67,14 @@ class AuthWorker { unlockTimeout: this.sessionTimeout, }); this.session = this.api.createSession(self.crypto.randomUUID()); + + // Initialize intervals for any existing logged in users + const wallets = await this.getWallets(); + for (const wallet of wallets) { + if (wallet.unlocked) { + this.startSessionInterval(wallet.name); + } + } } public setSessionEndCallback(callback: () => Promise<void> = noop): void { @@ -79,34 +86,58 @@ class AuthWorker { return new Date(now).getTime() < new Date(timeout_time).getTime(); } - private startSessionInterval(): void { - this._interval = setInterval(async () => { + private startSessionInterval(username: string): void { + // Clear any existing interval for this user + if (this._intervals[username]) { + this.clearSessionInterval(username); + } + + this._intervals[username] = setInterval(async () => { if (!this.isValidSession()) { - await this.lock(); - } else { - // still valid auth session + const wallet = await this.getWallet(username); + wallet?.unlocked?.lock(); + + // Update user state + if (this.#loggedInUsers[username]) { + this.#loggedInUsers[username].unlocked = false; + } + + // Clear interval after locking + this.clearSessionInterval(username); } }, SESSION_HEALTH_CHECK); } - private clearSessionInterval(): void { - clearInterval(this._interval); + private clearSessionInterval(username: string): void { + if (this._intervals[username]) { + clearInterval(this._intervals[username]); + this._intervals = Object.fromEntries( + Object.entries(this._intervals).filter(([key]) => key !== username), + ); + } + } + + // Add method to clear all intervals + private clearAllSessionIntervals(): void { + Object.keys(this._intervals).forEach((username) => { + this.clearSessionInterval(username); + }); } - public async onAuthComplete(failed?: boolean): Promise<void> { + public async onAuthComplete( + username: string, + failed?: boolean, + ): Promise<void> { if (failed) { await this._generator.throw( new AuthorizationError("Invalid credentials"), ); } else { - if (this.loggedInUser) { - this.loggedInUser = { - ...this.loggedInUser, - authorized: true, - }; + if (this.#loggedInUsers[username]) { + this.#loggedInUsers[username].authorized = true; } - this.startSessionInterval(); + this.startSessionInterval(username); await this._generator?.next(); } } @@ -197,8 +228,9 @@ class AuthWorker { await this.importKey(wallet, wifKey, keyType); } - if (!this.loggedInUser) { - this.loggedInUser = { + // Initialize or update loggedInUsers state + if (!this.#loggedInUsers[username]) { + this.#loggedInUsers[username] = { username, authorized: true, unlocked: true, @@ -207,7 +239,7 @@ class AuthWorker { }; } - await this.setUserSettings(username, { strict }); + await this.setUserSettings(username, { strict }, keyType); return "success"; } @@ -217,47 +249,54 @@ class AuthWorker { keyType: KeyAuthorityType, digest: string, ): Promise<string> { - if (!username || !password || !keyType) { - throw new AuthorizationError("Empty field"); + const wallet = await this.getWallet(username); + if (!wallet) { + throw new AuthorizationError("Invalid credentials"); } - this.checkKeyType(keyType); + const currentUserState = await this.getAuthByUser(username); + if (currentUserState?.authorized) { + // First ensure any existing session is cleaned up + await this.logout(username); + } try { - const authUser = await this.getAuthByUser(username); + const unlocked = wallet.unlock(password); + const keys = unlocked.getPublicKeys(); + const alias = await this.getAlias(`${username}@${keyType}`); - // Check if user is already logged in and authorized - if (authUser?.authorized && this.isValidSession()) { - throw new AuthorizationError("User is already logged in"); + if (!alias) { + unlocked.lock(); + throw new AuthorizationError("Not authorized, missing authority"); + } + + const foundKey = keys.find((key) => key === alias.pubKey); + if (!foundKey) { + unlocked.lock(); + throw new AuthorizationError("Not authorized, missing authority"); } - const w = await this.getWallet(username); + // Get all registered key types for this user + const registeredKeyTypes = await this.getRegisteredKeyTypes(username); - if (w && w.name === username) { - await this.unlock(username, password); + // Update or create user session + this.#loggedInUsers[username] = { + username, + authorized: true, + unlocked: true, + loggedInKeyType: keyType, + registeredKeyTypes, + }; - this._loggedInUser = { - username, - unlocked: true, - authorized: false, - loggedInKeyType: keyType, - registeredKeyTypes: await this.getRegisteredKeyTypes(username), - }; + // Start session interval for this user + this.startSessionInterval(username); - return await this.sign(username, digest, keyType); - } else { - throw new AuthorizationError("User not found"); - } + return this.sign(username, digest, keyType); } catch (error) { if (error instanceof AuthorizationError) { throw error; - } else { - if (String(error).toLowerCase().includes("invalid password")) { - throw new AuthorizationError("Invalid credentials xxx"); - } else { - throw new InternalError(error); - } } + throw new AuthorizationError("Invalid credentials"); } } @@ -313,28 +352,15 @@ class AuthWorker { } public async getAuthByUser(username: string): Promise<AuthUser | null> { - try { - const wallet = await this.getWallet(username); + const user = this.#loggedInUsers[username]; - if (!wallet) return null; - - const isCurrentUser = this.loggedInUser?.username === username; - - return { - authorized: - isCurrentUser && - !!this.loggedInUser?.authorized && - !!wallet?.unlocked, - unlocked: !!wallet?.unlocked, - username: wallet?.name ?? username, - loggedInKeyType: isCurrentUser - ? this.loggedInUser?.loggedInKeyType - : undefined, - registeredKeyTypes: await this.getRegisteredKeyTypes(username), - }; - } catch (error) { - throw new InternalError(error); + if (!user) { + return null; } + + // Update registered key types for logged in user + user.registeredKeyTypes = await this.getRegisteredKeyTypes(username); + return user; } public async getAuths(): Promise<AuthUser[]> { @@ -386,64 +412,80 @@ class AuthWorker { digest: string, keyType: KeyAuthorityType, ): Promise<string> { - try { - const wallet = await this.getWallet(username); - if (!wallet?.unlocked) throw new AuthorizationError("Not authorized"); - const keys = wallet.unlocked.getPublicKeys(); - const alias = await this.getAlias(`${username}@${keyType}`); - const foundKey = keys.find((key) => key === alias?.pubKey); + const wallet = await this.getWallet(username); + if (!wallet?.unlocked) throw new AuthorizationError("Not authorized"); - if (!foundKey) { - wallet.unlocked?.lock(); - throw new AuthorizationError("Not authorized, missing authority"); - } + const keys = wallet.unlocked.getPublicKeys(); + const alias = await this.getAlias(`${username}@${keyType}`); + const foundKey = keys.find((key) => key === alias?.pubKey); - const signed = wallet.unlocked.signDigest(foundKey, digest); + if (!foundKey) { + wallet.unlocked?.lock(); + throw new AuthorizationError("Not authorized, missing authority"); + } - if (!this.loggedInUser) { - this.loggedInUser = { - username, - unlocked: true, - authorized: true, - loggedInKeyType: keyType, - registeredKeyTypes: await this.getRegisteredKeyTypes(username), - }; - } + // Get key-specific strict mode setting + const settings = await this.getUserSettings(username); + const isStrictMode = settings?.strict[keyType] ?? true; - return signed; - } catch (error) { - if (error instanceof AuthorizationError) { - throw error; - } else { - throw new InternalError(error); - } + // In strict mode, verify the user is authorized with the correct key type + const userSession = this.#loggedInUsers[username]; + if (isStrictMode && !userSession?.authorized) { + throw new AuthorizationError("Not authorized"); } + + return wallet.unlocked.signDigest(foundKey, digest); } - public async logout(): Promise<void> { + public async logout(username: string): Promise<void> { + // Logout specific user + const wallet = await this.getWallet(username); + wallet?.unlocked?.lock(); + + // Remove from logged in users + this.#loggedInUsers = Object.fromEntries( + Object.entries(this.#loggedInUsers).filter(([key]) => key !== username), + ); + + // Clear interval for this specific user + this.clearSessionInterval(username); + await this.sessionEndCallback(); - this.clearSessionInterval(); - // Clear the session - if (this.loggedInUser) { - const wallet = await this.getWallet(this.loggedInUser.username); - wallet?.unlocked?.lock(); + } + + public async logoutAll(): Promise<void> { + // Lock all wallets + const wallets = await this.getWallets(); + for (const wallet of wallets) { + wallet.unlocked?.lock(); } - // Clear the logged in user state completely - this._loggedInUser = undefined; + + // Clear all logged in users + this.#loggedInUsers = {}; + + // Clear all session intervals + this.clearAllSessionIntervals(); + + await this.sessionEndCallback(); } public async lock(): Promise<void> { try { - if (!this.isValidSession() || !this.loggedInUser) { + // Get all unlocked users + const unlockedUsers = Object.values(this.#loggedInUsers).filter( + (user) => user.unlocked, + ); + if (unlockedUsers.length === 0) { throw new AuthorizationError( "There is no existing user session or session already expired", ); - } else { - const wallet = await this.getWallet(this.loggedInUser.username); + } + + // Lock all unlocked users + for (const user of unlockedUsers) { + const wallet = await this.getWallet(user.username); wallet?.unlocked?.lock(); - if (this.loggedInUser) { - this.loggedInUser.unlocked = false; - } + this.#loggedInUsers[user.username].unlocked = false; } } catch (error) { if (error instanceof AuthorizationError) { @@ -458,8 +500,8 @@ class AuthWorker { try { const wallet = await this.getWallet(username); - if (!this.isValidSession()) { - throw new InternalError( + if (!this.#loggedInUsers[username]?.authorized) { + throw new AuthorizationError( "There is no existing user session or session already expired", ); } @@ -471,6 +513,8 @@ class AuthWorker { // Add check for already unlocked wallet if (!wallet.unlocked) { wallet.unlock(password); + // Update user session state + this.#loggedInUsers[username].unlocked = true; } } catch (error) { if (error instanceof AuthorizationError) { @@ -492,8 +536,10 @@ class AuthWorker { try { await this.api.delete(); await this.removeAlias(`${username}@${keyType}`); - this.loggedInUser = undefined; - this.clearSessionInterval(); + this.#loggedInUsers = Object.fromEntries( + Object.entries(this.#loggedInUsers).filter(([key]) => key !== username), + ); + this.clearSessionInterval(username); } catch (error) { throw new InternalError(error); } @@ -570,44 +616,74 @@ class AuthWorker { public async getUserSettings( username: string, - ): Promise<UserSettings | undefined> { - const db = await openDB(this.settingsStorage, 1, { - upgrade(db) { - if (!db.objectStoreNames.contains("settings")) { - const store = db.createObjectStore("settings", { keyPath: "alias" }); - store.createIndex("alias", "alias", { unique: true }); - } - }, - }); + keyType?: KeyAuthorityType, + ): Promise<UserSettings | null> { + try { + const db = await openDB(this.settingsStorage, 1, { + upgrade(db) { + db.createObjectStore("settings"); + }, + }); + + const settings = (await db.get("settings", username)) as UserSettings; + + if (keyType) { + return settings + ? { + strict: { [keyType]: settings.strict[keyType] }, + alias: settings.alias, + } + : null; + } - return await db.get("settings", username); + return settings || null; + } catch (error) { + throw new InternalError(error); + } } public async setUserSettings( username: string, - settings: Partial<UserSettings>, + settings: { + strict: boolean; + authorizedAccounts?: { + [K in KeyAuthorityType]?: string; + }; + }, + keyType: KeyAuthorityType, ): Promise<void> { - const db = await openDB(this.settingsStorage, 1, { - upgrade(db) { - if (!db.objectStoreNames.contains("settings")) { - const store = db.createObjectStore("settings", { keyPath: "alias" }); - store.createIndex("alias", "alias", { unique: true }); - } - }, - }); - - const tx = db.transaction(["settings"], "readwrite"); - const store = tx.objectStore("settings"); + try { + const db = await openDB(this.settingsStorage, 1, { + upgrade(db) { + db.createObjectStore("settings"); + }, + }); + + const existingSettings = ((await db.get( + "settings", + username, + )) as UserSettings) || { + strict: {}, + alias: username, + authorizedAccounts: {}, + }; - const existing = await store.get(username); - await store.put({ - ...existing, - alias: username, - ...settings, - }); + const updatedSettings = { + ...existingSettings, + strict: { + ...existingSettings.strict, + [keyType]: settings.strict, + }, + authorizedAccounts: { + ...existingSettings.authorizedAccounts, + ...settings.authorizedAccounts, + }, + }; - await tx.done; - db.close(); + await db.put("settings", updatedSettings, username); + } catch (error) { + throw new InternalError(error); + } } } @@ -640,8 +716,11 @@ class Auth { ).registerUser(username, password, digest, wifKey, keyType, strict); } - public async onAuthComplete(failed: boolean): Promise<void> { - await (await this.getWorker()).onAuthComplete(failed); + public async onAuthComplete( + username: string, + failed: boolean, + ): Promise<void> { + await (await this.getWorker()).onAuthComplete(username, failed); } public async unregister( @@ -680,8 +759,13 @@ class Auth { ).authenticate(username, password, keyType, digest); } - public async logout(): Promise<void> { - await (await this.getWorker()).logout(); + public async logout(username: string): Promise<void> { + await (await this.getWorker()).logout(username); + Auth.#worker = undefined; + } + + public async logoutAll(): Promise<void> { + await (await this.getWorker()).logoutAll(); Auth.#worker = undefined; } @@ -720,15 +804,22 @@ class Auth { public async getUserSettings( username: string, - ): Promise<UserSettings | undefined> { - return await (await this.getWorker()).getUserSettings(username); + keyType?: KeyAuthorityType, + ): Promise<UserSettings | null> { + return await (await this.getWorker()).getUserSettings(username, keyType); } public async setUserSettings( username: string, - settings: Partial<UserSettings>, + settings: { + strict: boolean; + authorizedAccounts?: { + [K in KeyAuthorityType]?: string; + }; + }, + keyType: KeyAuthorityType, ): Promise<void> { - await (await this.getWorker()).setUserSettings(username, settings); + await (await this.getWorker()).setUserSettings(username, settings, keyType); } }