Skip to content

Commit e375306

Browse files
committed
fix: fixed revokeToken missing in vault
1 parent 87bc2d2 commit e375306

File tree

5 files changed

+93
-148
lines changed

5 files changed

+93
-148
lines changed

packages/seedless-onboarding-controller/src/SeedlessOnboardingController.test.ts

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1385,14 +1385,28 @@ describe('SeedlessOnboardingController', () => {
13851385
withMockAuthenticatedUser: true,
13861386
withMockAuthPubKey: true,
13871387
withoutMockRevokeToken: true,
1388+
vault: 'MOCK_VAULT',
13881389
}),
13891390
},
1390-
async ({ controller, toprfClient }) => {
1391+
async ({ controller, toprfClient, encryptor }) => {
13911392
mockcreateLocalKey(toprfClient, MOCK_PASSWORD);
13921393

13931394
// persist the local enc key
13941395
jest.spyOn(toprfClient, 'persistLocalKey').mockResolvedValueOnce();
13951396

1397+
// since revoke token is not in the state, it should be retrieved from the vault
1398+
// mock the decryptWithDetail to return the mock vault data without revoke token
1399+
jest.spyOn(encryptor, 'decryptWithDetail').mockResolvedValue({
1400+
vault: JSON.stringify({
1401+
toprfEncryptionKey: '',
1402+
toprfPwEncryptionKey: '',
1403+
toprfAuthKeyPair: '',
1404+
accessToken,
1405+
}),
1406+
exportedKeyString: '',
1407+
salt: '',
1408+
});
1409+
13961410
// encrypt and store the secret data
13971411
handleMockSecretDataAdd();
13981412
await expect(
@@ -3849,6 +3863,7 @@ describe('SeedlessOnboardingController', () => {
38493863
initialPwEncKey,
38503864
initialAuthKeyPair,
38513865
OLD_PASSWORD,
3866+
revokeToken,
38523867
);
38533868

38543869
MOCK_VAULT = mockResult.encryptedMockVault;
@@ -5378,10 +5393,10 @@ describe('SeedlessOnboardingController', () => {
53785393
});
53795394
});
53805395

5381-
describe('#getAccessToken', () => {
5396+
describe('#getAccessTokenAndRevokeToken', () => {
53825397
const MOCK_PASSWORD = 'mock-password';
53835398

5384-
it('should retrieve the access token from the vault if it is not available in the state', async () => {
5399+
it('should retrieve the access token and revoke token from the vault if it is not available in the state', async () => {
53855400
const mockToprfEncryptor = createMockToprfEncryptor();
53865401
const MOCK_ENCRYPTION_KEY =
53875402
mockToprfEncryptor.deriveEncKey(MOCK_PASSWORD);
@@ -5456,12 +5471,28 @@ describe('SeedlessOnboardingController', () => {
54565471
state: getMockInitialControllerState({
54575472
withMockAuthenticatedUser: true,
54585473
withoutMockAccessToken: true,
5474+
vault: 'MOCK_VAULT',
54595475
}),
54605476
},
5461-
async ({ controller, toprfClient }) => {
5477+
async ({ controller, toprfClient, encryptor }) => {
54625478
// fetch and decrypt the secret data
54635479
mockRecoverEncKey(toprfClient, MOCK_PASSWORD);
5464-
// mock the incorrect data shape
5480+
5481+
// since access token is not in the state, it should be retrieved from the vault
5482+
// mock the decryptWithDetail to return the mock vault data without access token
5483+
const decryptWithDetailMock = jest
5484+
.spyOn(encryptor, 'decryptWithDetail')
5485+
.mockResolvedValue({
5486+
vault: JSON.stringify({
5487+
toprfEncryptionKey: '',
5488+
toprfPwEncryptionKey: '',
5489+
toprfAuthKeyPair: '',
5490+
revokeToken: 'MOCK_REVOKE_TOKEN',
5491+
}),
5492+
exportedKeyString: '',
5493+
salt: '',
5494+
});
5495+
54655496
jest
54665497
.spyOn(toprfClient, 'fetchAllSecretDataItems')
54675498
.mockResolvedValueOnce([
@@ -5480,6 +5511,10 @@ describe('SeedlessOnboardingController', () => {
54805511
).rejects.toThrow(
54815512
SeedlessOnboardingControllerErrorMessage.InvalidAccessToken,
54825513
);
5514+
expect(decryptWithDetailMock).toHaveBeenCalledWith(
5515+
MOCK_PASSWORD,
5516+
'MOCK_VAULT',
5517+
);
54835518
},
54845519
);
54855520
});

packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts

Lines changed: 36 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -961,35 +961,6 @@ export class SeedlessOnboardingController<EncryptionKey> extends BaseController<
961961
return isAuthenticated;
962962
}
963963

964-
/**
965-
* Get the access token from the state or the vault.
966-
* If the access token is not in the state, it will be retrieved from the vault by decrypting it with the password.
967-
*
968-
* If both the access token and the vault are not available, an error will be thrown.
969-
*
970-
* @param password - The optional password to unlock the vault. If not provided, the access token will be retrieved from the vault.
971-
* @returns The access token.
972-
*/
973-
async #getAccessToken(password: string): Promise<string> {
974-
const { accessToken, vault } = this.state;
975-
if (accessToken) {
976-
// if the access token is in the state, return it
977-
return accessToken;
978-
}
979-
980-
// otherwise, check the vault availability and decrypt the access token from the vault
981-
if (!vault) {
982-
throw new Error(
983-
SeedlessOnboardingControllerErrorMessage.InvalidAccessToken,
984-
);
985-
}
986-
987-
const { vaultData } = await this.#decryptAndParseVaultData({
988-
password,
989-
});
990-
return vaultData.accessToken;
991-
}
992-
993964
#setUnlocked(): void {
994965
this.#isUnlocked = true;
995966
}
@@ -1558,36 +1529,46 @@ export class SeedlessOnboardingController<EncryptionKey> extends BaseController<
15581529
rawToprfPwEncryptionKey: Uint8Array;
15591530
rawToprfAuthKeyPair: KeyPair;
15601531
}): Promise<void> {
1561-
this.#assertIsAuthenticatedUser(this.state);
1532+
try {
1533+
this.#assertIsAuthenticatedUser(this.state);
15621534

1563-
const { revokeToken } = this.state;
1564-
if (!revokeToken) {
1565-
throw new Error(
1566-
SeedlessOnboardingControllerErrorMessage.InvalidRevokeToken,
1567-
);
1568-
}
1569-
const accessToken = await this.#getAccessToken(password);
1570-
1571-
const vaultData: DeserializedVaultData = {
1572-
toprfAuthKeyPair: rawToprfAuthKeyPair,
1573-
toprfEncryptionKey: rawToprfEncryptionKey,
1574-
toprfPwEncryptionKey: rawToprfPwEncryptionKey,
1575-
revokeToken,
1576-
accessToken,
1577-
};
1535+
let { accessToken, revokeToken } = this.state;
1536+
if (!accessToken || !revokeToken) {
1537+
({
1538+
vaultData: { accessToken, revokeToken },
1539+
} = await this.#decryptAndParseVaultData({
1540+
password,
1541+
}));
1542+
}
15781543

1579-
await this.#updateVault({
1580-
password,
1581-
vaultData,
1582-
pwEncKey: rawToprfPwEncryptionKey,
1583-
});
1544+
const vaultData: DeserializedVaultData = {
1545+
toprfAuthKeyPair: rawToprfAuthKeyPair,
1546+
toprfEncryptionKey: rawToprfEncryptionKey,
1547+
toprfPwEncryptionKey: rawToprfPwEncryptionKey,
1548+
revokeToken,
1549+
accessToken,
1550+
};
15841551

1585-
// update the authPubKey in the state
1586-
this.#persistAuthPubKey({
1587-
authPubKey: rawToprfAuthKeyPair.pk,
1588-
});
1552+
await this.#updateVault({
1553+
password,
1554+
vaultData,
1555+
pwEncKey: rawToprfPwEncryptionKey,
1556+
});
15891557

1590-
this.#setUnlocked();
1558+
// update the authPubKey in the state
1559+
this.#persistAuthPubKey({
1560+
authPubKey: rawToprfAuthKeyPair.pk,
1561+
});
1562+
1563+
this.#setUnlocked();
1564+
} catch (error) {
1565+
log(
1566+
'Error creating new vault with auth data',
1567+
error,
1568+
JSON.stringify(this.state),
1569+
);
1570+
throw error;
1571+
}
15911572
}
15921573

15931574
/**
@@ -1860,11 +1841,6 @@ export class SeedlessOnboardingController<EncryptionKey> extends BaseController<
18601841
password,
18611842
encryptionKey: vaultEncryptionKey,
18621843
});
1863-
if (!revokeToken) {
1864-
throw new Error(
1865-
SeedlessOnboardingControllerErrorMessage.InvalidRevokeToken,
1866-
);
1867-
}
18681844

18691845
const { newRevokeToken, newRefreshToken } = await this.#renewRefreshToken(
18701846
{

packages/seedless-onboarding-controller/src/assertions.test.ts

Lines changed: 3 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -81,51 +81,23 @@ describe('assertIsValidVaultData', () => {
8181
}).toThrow(SeedlessOnboardingControllerErrorMessage.InvalidVaultData);
8282
});
8383

84-
it('should throw when revokeToken exists but is not a string or undefined', () => {
85-
const invalidData = {
86-
...createValidVaultData(),
87-
revokeToken: 789,
88-
};
89-
90-
expect(() => {
91-
assertIsValidVaultData(invalidData);
92-
}).toThrow(SeedlessOnboardingControllerErrorMessage.InvalidVaultData);
93-
94-
const invalidData2 = {
95-
...createValidVaultData(),
96-
revokeToken: null,
97-
};
98-
99-
expect(() => {
100-
assertIsValidVaultData(invalidData2);
101-
}).toThrow(SeedlessOnboardingControllerErrorMessage.InvalidVaultData);
102-
103-
const invalidData3 = {
104-
...createValidVaultData(),
105-
revokeToken: {},
106-
};
107-
108-
expect(() => {
109-
assertIsValidVaultData(invalidData3);
110-
}).toThrow(SeedlessOnboardingControllerErrorMessage.InvalidVaultData);
111-
});
112-
11384
it('should throw when accessToken is missing or not a string', () => {
11485
const invalidData = createValidVaultData();
11586
delete (invalidData as Record<string, unknown>).accessToken;
11687

11788
expect(() => {
11889
assertIsValidVaultData(invalidData);
119-
}).toThrow(SeedlessOnboardingControllerErrorMessage.InvalidVaultData);
90+
}).toThrow(SeedlessOnboardingControllerErrorMessage.InvalidAccessToken);
12091

12192
const invalidData2 = {
12293
...createValidVaultData(),
94+
revokeToken: 'MOCK_REVOKE_TOKEN',
12395
accessToken: 999,
12496
};
12597

12698
expect(() => {
12799
assertIsValidVaultData(invalidData2);
128-
}).toThrow(SeedlessOnboardingControllerErrorMessage.InvalidVaultData);
100+
}).toThrow(SeedlessOnboardingControllerErrorMessage.InvalidAccessToken);
129101
});
130102
});
131103

@@ -138,50 +110,6 @@ describe('assertIsValidVaultData', () => {
138110
}).not.toThrow();
139111
});
140112

141-
it('should not throw when revokeToken is undefined', () => {
142-
const validData = {
143-
...createValidVaultData(),
144-
revokeToken: undefined,
145-
};
146-
147-
expect(() => {
148-
assertIsValidVaultData(validData);
149-
}).not.toThrow();
150-
});
151-
152-
it('should not throw when revokeToken is a valid string', () => {
153-
const validData = {
154-
...createValidVaultData(),
155-
revokeToken: 'valid_revoke_token',
156-
};
157-
158-
expect(() => {
159-
assertIsValidVaultData(validData);
160-
}).not.toThrow();
161-
});
162-
163-
it('should not throw when revokeToken property is missing entirely', () => {
164-
const validData = createValidVaultData();
165-
delete (validData as Record<string, unknown>).revokeToken;
166-
167-
expect(() => {
168-
assertIsValidVaultData(validData);
169-
}).not.toThrow();
170-
});
171-
172-
it('should not throw with minimal valid vault data', () => {
173-
const minimalValidData = {
174-
toprfEncryptionKey: 'key1',
175-
toprfPwEncryptionKey: 'key2',
176-
toprfAuthKeyPair: 'keyPair',
177-
accessToken: 'token',
178-
};
179-
180-
expect(() => {
181-
assertIsValidVaultData(minimalValidData);
182-
}).not.toThrow();
183-
});
184-
185113
it('should not throw with extra properties in valid vault data', () => {
186114
const validDataWithExtras = {
187115
...createValidVaultData(),

packages/seedless-onboarding-controller/src/assertions.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,20 @@ export function assertIsValidVaultData(
8989
!('toprfPwEncryptionKey' in value) || // toprfPwEncryptionKey is not defined
9090
typeof value.toprfPwEncryptionKey !== 'string' || // toprfPwEncryptionKey is not a string
9191
!('toprfAuthKeyPair' in value) || // toprfAuthKeyPair is not defined
92-
typeof value.toprfAuthKeyPair !== 'string' || // toprfAuthKeyPair is not a string
93-
// revoke token exists but is not a string and is not undefined
94-
('revokeToken' in value &&
95-
typeof value.revokeToken !== 'string' &&
96-
value.revokeToken !== undefined) ||
97-
!('accessToken' in value) || // accessToken is not defined
98-
typeof value.accessToken !== 'string' // accessToken is not a string
92+
typeof value.toprfAuthKeyPair !== 'string' // toprfAuthKeyPair is not a string
9993
) {
10094
throw new Error(SeedlessOnboardingControllerErrorMessage.InvalidVaultData);
10195
}
96+
97+
if (!('revokeToken' in value) || typeof value.revokeToken !== 'string') {
98+
throw new Error(
99+
SeedlessOnboardingControllerErrorMessage.InvalidRevokeToken,
100+
);
101+
}
102+
103+
if (!('accessToken' in value) || typeof value.accessToken !== 'string') {
104+
throw new Error(
105+
SeedlessOnboardingControllerErrorMessage.InvalidAccessToken,
106+
);
107+
}
102108
}

packages/seedless-onboarding-controller/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ export type VaultData = {
359359
* The revoke token to revoke refresh token and get new refresh token and new revoke token.
360360
* The revoke token may no longer be available after a large number of password changes. In this case, re-authentication is advised.
361361
*/
362-
revokeToken?: string;
362+
revokeToken: string;
363363
/**
364364
* The access token used for pairing with profile sync auth service and to access other services.
365365
*/

0 commit comments

Comments
 (0)