Skip to content

Commit ce11468

Browse files
authored
chore: update seedless controller 6.1.0 (MetaMask#21991)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** Update seedless controller to v6.1.0 Update required types checking ( throw on not matching type / undefined ) Remove unused code in basehandler add jsdoc to AuthTokenHandler Jira Link https://consensyssoftware.atlassian.net/browse/SL-253?atlOrigin=eyJpIjoiNzc2ZjgxYTRlZGEzNGFhY2I4ZWZjNDc3ZjExNzg4NzkiLCJwIjoiaiJ9 <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: update seedless controller v6 ## **Related issues** Fixes: MetaMask#21933 ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Upgrades seedless controller to v6.1.0 and updates OAuth services to stricter token typing/validation, new v2 revoke/renew endpoints, and refreshed tests. > > - **OAuth services**: > - **AuthTokenHandler**: > - Implements controller types; validates presence of `id_token`, `access_token`, `metadata_access_token`. > - Adds strict checks for `refresh_token`/`revoke_token` on renew; throws on missing tokens. > - Switches endpoints: `AUTH_SERVER_RENEW_PATH` and `AUTH_SERVER_REVOKE_PATH` to `/api/v2/...`. > - **OAuthService**: > - Enforces non-empty `loginHandler.login()` result. > - Requires `refresh_token` and `revoke_token` before `authenticate`; throws controller errors if missing. > - **Base handler**: > - Removes refresh/revoke helpers; keeps `getAuthTokens` with explicit typing. > - **Interfaces**: > - Adds `AuthRefreshTokenResponse`; documents response types. > - **Tests**: > - Updates and expands unit tests for new validations, error handling, request bodies, and handler behavior across `AuthTokenHandler`, base handler, login handlers, and service. > - **Dependencies**: > - Bumps `@metamask/seedless-onboarding-controller` to `^6.1.0` and related transitive packages. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 5b82940. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 3873936 commit ce11468

10 files changed

Lines changed: 225 additions & 193 deletions

File tree

app/core/OAuthService/AuthTokenHandler.test.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -292,20 +292,18 @@ describe('AuthTokenHandler', () => {
292292

293293
fetchSpy.mockResolvedValueOnce({
294294
ok: true,
295+
statusText: 'OK',
295296
json: jest.fn().mockResolvedValueOnce(mockResponse),
296297
});
297298

298299
// Act
299-
const result = await AuthTokenHandler.renewRefreshToken({
300+
const pendingPromise = AuthTokenHandler.renewRefreshToken({
300301
connection: mockConnection,
301302
revokeToken: mockRevokeToken,
302303
});
303304

304305
// Assert
305-
expect(result).toEqual({
306-
newRefreshToken: undefined,
307-
newRevokeToken: undefined,
308-
});
306+
await expect(pendingPromise).rejects.toThrow();
309307
});
310308
});
311309

@@ -503,6 +501,15 @@ describe('AuthTokenHandler', () => {
503501
refreshToken: 'test-token',
504502
});
505503

504+
const refreshMockResponse = {
505+
refresh_token: 'new-refresh-token',
506+
revoke_token: 'new-revoke-token',
507+
};
508+
fetchSpy.mockResolvedValue({
509+
ok: true,
510+
json: jest.fn().mockResolvedValue(refreshMockResponse),
511+
});
512+
506513
await AuthTokenHandler.renewRefreshToken({
507514
connection: AuthConnection.Apple,
508515
revokeToken: 'test-token',

app/core/OAuthService/AuthTokenHandler.ts

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,31 @@
11
import { Platform } from 'react-native';
2-
import { AuthConnection } from './OAuthInterface';
2+
import { AuthConnection, AuthRefreshTokenResponse } from './OAuthInterface';
33
import { createLoginHandler } from './OAuthLoginHandlers';
4+
import type {
5+
RefreshJWTToken,
6+
RenewRefreshToken,
7+
RevokeRefreshToken,
8+
} from '@metamask/seedless-onboarding-controller/dist/types.d.cts';
49

510
export const AUTH_SERVER_RENEW_PATH = '/api/v2/oauth/renew_refresh_token';
611
export const AUTH_SERVER_REVOKE_PATH = '/api/v2/oauth/revoke';
712
export const AUTH_SERVER_TOKEN_PATH = '/api/v1/oauth/token';
813

9-
class AuthTokenHandler {
14+
interface AuthTokenHandlerInterface {
15+
refreshJWTToken: RefreshJWTToken;
16+
renewRefreshToken: RenewRefreshToken;
17+
revokeRefreshToken: RevokeRefreshToken;
18+
}
19+
20+
class AuthTokenHandler implements AuthTokenHandlerInterface {
21+
/**
22+
* Refresh the JWT Token using the refresh token.
23+
*
24+
* @param params - The params from the login handler
25+
* @param params.connection - The connection type (Google, Apple, etc.)
26+
* @param params.refreshToken - The refresh token from the Web3Auth Authentication Server.
27+
* @returns The id token, access token, and metadata access token.
28+
*/
1029
async refreshJWTToken(params: {
1130
connection: AuthConnection;
1231
refreshToken: string;
@@ -41,7 +60,7 @@ class AuthTokenHandler {
4160
throw new Error('Failed to refresh JWT token');
4261
}
4362

44-
const refreshTokenData = await response.json();
63+
const refreshTokenData: AuthRefreshTokenResponse = await response.json();
4564
const idToken = refreshTokenData.id_token;
4665

4766
if (
@@ -62,6 +81,14 @@ class AuthTokenHandler {
6281
};
6382
}
6483

84+
/**
85+
* Renew the refresh token.
86+
*
87+
* @param params - The params from the login handler
88+
* @param params.connection - The connection type (Google, Apple, etc.)
89+
* @param params.revokeToken - The revoke token from the Web3Auth Authentication Server.
90+
* @returns The new refresh token and revoke token.
91+
*/
6592
async renewRefreshToken(params: {
6693
connection: AuthConnection;
6794
revokeToken: string;
@@ -89,12 +116,28 @@ class AuthTokenHandler {
89116
}
90117

91118
const responseData = await response.json();
119+
120+
const newRefreshToken = responseData.refresh_token;
121+
const newRevokeToken = responseData.revoke_token;
122+
123+
if (!newRefreshToken || !newRevokeToken) {
124+
throw new Error('Failed to renew refresh token - ' + response.statusText);
125+
}
126+
92127
return {
93-
newRefreshToken: responseData.refresh_token,
94-
newRevokeToken: responseData.revoke_token,
128+
newRefreshToken,
129+
newRevokeToken,
95130
};
96131
}
97132

133+
/**
134+
* Revoke the refresh token.
135+
*
136+
* @param params - The params from the login handler
137+
* @param params.connection - The connection type (Google, Apple, etc.)
138+
* @param params.revokeToken - The revoke token from the Web3Auth Authentication Server.
139+
* @returns void
140+
*/
98141
async revokeRefreshToken(params: {
99142
connection: AuthConnection;
100143
revokeToken: string;
@@ -122,7 +165,6 @@ class AuthTokenHandler {
122165
'Failed to revoke refresh token - ' + response.statusText,
123166
);
124167
}
125-
126168
return;
127169
}
128170
}

app/core/OAuthService/OAuthInterface.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ export type AuthRequestParams =
6868
| AuthRequestCodeParams
6969
| AuthRequestIdTokenParams;
7070

71+
// return type for auth request with
72+
// grant type : authorization_code, access_type: offline
7173
export interface AuthResponse {
7274
id_token: string;
7375
access_token: string;
@@ -78,6 +80,17 @@ export interface AuthResponse {
7880
revoke_token?: string;
7981
}
8082

83+
// return type for auth request with
84+
// grant type : refresh_token
85+
// grant type : authorization_code, access_type: online
86+
export interface AuthRefreshTokenResponse {
87+
id_token: string;
88+
access_token: string;
89+
metadata_access_token: string;
90+
indexes: number[];
91+
endpoints: Record<string, string>;
92+
}
93+
8194
export interface LoginHandler {
8295
get authConnection(): AuthConnection;
8396
get scope(): string[];

app/core/OAuthService/OAuthLoginHandlers/baseHandler.test.ts

Lines changed: 17 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,11 @@ describe('BaseLoginHandler', () => {
183183
success: true,
184184
id_token: 'mock-id-token',
185185
refresh_token: 'mock-refresh-token',
186-
indexes: [1, 2, 3],
187-
endpoints: { endpoint1: 'value1' },
188-
message: 'Success',
189-
jwt_tokens: { token1: 'value1' },
186+
revoke_token: 'mock-revoke-token',
187+
access_token: 'mock-access-token',
188+
metadata_access_token: 'mock-metadata-access-token',
189+
token_type: 'Bearer',
190+
expires_in: 3600,
190191
};
191192

192193
(global.fetch as jest.Mock).mockResolvedValueOnce({
@@ -225,44 +226,16 @@ describe('BaseLoginHandler', () => {
225226
);
226227

227228
expect(result).toEqual(mockResponse);
228-
229-
jest
230-
.spyOn(global, 'fetch')
231-
.mockResolvedValueOnce(new Response(JSON.stringify(mockResponse)));
232-
233-
const refreshResult = await mockHandler.refreshAuthToken('refresh-token');
234-
235-
expect(refreshResult).toEqual(mockResponse);
236-
237-
const mockRevokeResponse = {
238-
new_refresh_token: 'refresh-token',
239-
new_revoke_token: 'revoke-token',
240-
};
241-
242-
jest
243-
.spyOn(global, 'fetch')
244-
.mockResolvedValueOnce(
245-
new Response(JSON.stringify(mockRevokeResponse)),
246-
);
247-
248-
const revokeResult =
249-
await mockHandler.revokeRefreshToken('refresh-token');
250-
251-
expect(revokeResult).toEqual({
252-
refresh_token: 'refresh-token',
253-
revoke_token: 'revoke-token',
254-
});
255229
});
256230

257231
it('successfully gets auth tokens with idToken', async () => {
258232
const mockResponse = {
259-
success: true,
260233
id_token: 'mock-id-token',
261234
refresh_token: 'mock-refresh-token',
262-
indexes: [1, 2, 3],
263-
endpoints: { endpoint1: 'value1' },
264-
message: 'Success',
265-
jwt_tokens: { token1: 'value1' },
235+
revoke_token: 'mock-revoke-token',
236+
access_token: 'mock-access-token',
237+
metadata_access_token: 'mock-metadata-access-token',
238+
expires_in: 3600,
266239
};
267240

268241
(global.fetch as jest.Mock).mockResolvedValueOnce({
@@ -361,6 +334,10 @@ describe('BaseLoginHandler', () => {
361334
const mockResponse = {
362335
success: true,
363336
id_token: 'mock-id-token',
337+
refresh_token: 'mock-refresh-token',
338+
revoke_token: 'mock-revoke-token',
339+
access_token: 'mock-access-token',
340+
metadata_access_token: 'mock-metadata-access-token',
364341
message: 'Success',
365342
};
366343

@@ -410,6 +387,10 @@ describe('BaseLoginHandler', () => {
410387
const mockResponse = {
411388
success: true,
412389
id_token: 'mock-id-token',
390+
refresh_token: 'mock-refresh-token',
391+
revoke_token: 'mock-revoke-token',
392+
access_token: 'mock-access-token',
393+
metadata_access_token: 'mock-metadata-access-token',
413394
message: 'Success',
414395
};
415396

app/core/OAuthService/OAuthLoginHandlers/baseHandler.ts

Lines changed: 1 addition & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export async function getAuthTokens(
3838
});
3939

4040
if (res.status === 200 || res.status === 201) {
41-
const data = (await res.json()) satisfies AuthResponse;
41+
const data: AuthResponse = (await res.json()) satisfies AuthResponse;
4242
return data;
4343
}
4444

@@ -140,78 +140,6 @@ export abstract class BaseLoginHandler {
140140
}
141141
}
142142

143-
/**
144-
* Refresh the JWT Token using the refresh token.
145-
*
146-
* @param refreshToken - The refresh token from the Web3Auth Authentication Server.
147-
* @returns The JWT Token from the Web3Auth Authentication Server and new refresh token.
148-
*/
149-
async refreshAuthToken(refreshToken: string): Promise<AuthResponse> {
150-
const { web3AuthNetwork } = this.options;
151-
const requestData = {
152-
client_id: this.options.clientId,
153-
login_provider: this.authConnection,
154-
network: web3AuthNetwork,
155-
refresh_token: refreshToken,
156-
grant_type: 'refresh_token', // specify refresh token flow
157-
};
158-
const res = await this.requestAuthToken(JSON.stringify(requestData));
159-
return res;
160-
}
161-
162-
/**
163-
* Revoke the refresh token.
164-
*
165-
* @param revokeToken - The revoke token from the Web3Auth Authentication Server.
166-
*/
167-
async revokeRefreshToken(revokeToken: string): Promise<{
168-
refresh_token: string;
169-
revoke_token: string;
170-
}> {
171-
const requestData = {
172-
revoke_token: revokeToken,
173-
};
174-
175-
const res = await fetch(
176-
`${this.options.authServerUrl}${this.AUTH_SERVER_REVOKE_PATH}`,
177-
{
178-
method: 'POST',
179-
headers: {
180-
'Content-Type': 'application/json',
181-
},
182-
body: JSON.stringify(requestData),
183-
},
184-
);
185-
186-
const data = await res.json();
187-
return {
188-
refresh_token: data.new_refresh_token,
189-
revoke_token: data.new_revoke_token,
190-
};
191-
}
192-
193-
/**
194-
* Make a request to the Web3Auth Authentication Server to get the JWT Token.
195-
*
196-
* @param requestData - The request data for the Web3Auth Authentication Server.
197-
* @returns The JWT Token from the Web3Auth Authentication Server.
198-
*/
199-
protected async requestAuthToken(requestData: string): Promise<AuthResponse> {
200-
const res = await fetch(
201-
`${this.options.authServerUrl}${this.AUTH_SERVER_TOKEN_PATH}`,
202-
{
203-
method: 'POST',
204-
headers: {
205-
'Content-Type': 'application/json',
206-
},
207-
body: requestData,
208-
},
209-
);
210-
211-
const data = await res.json();
212-
return data;
213-
}
214-
215143
/**
216144
* Generate a nonce value.
217145
*

0 commit comments

Comments
 (0)