Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions app/core/OAuthService/AuthTokenHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,20 +292,18 @@ describe('AuthTokenHandler', () => {

fetchSpy.mockResolvedValueOnce({
ok: true,
statusText: 'OK',
json: jest.fn().mockResolvedValueOnce(mockResponse),
});

// Act
const result = await AuthTokenHandler.renewRefreshToken({
const pendingPromise = AuthTokenHandler.renewRefreshToken({
connection: mockConnection,
revokeToken: mockRevokeToken,
});

// Assert
expect(result).toEqual({
newRefreshToken: undefined,
newRevokeToken: undefined,
});
await expect(pendingPromise).rejects.toThrow();
});
});

Expand Down Expand Up @@ -503,6 +501,15 @@ describe('AuthTokenHandler', () => {
refreshToken: 'test-token',
});

const refreshMockResponse = {
refresh_token: 'new-refresh-token',
revoke_token: 'new-revoke-token',
};
fetchSpy.mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue(refreshMockResponse),
});

await AuthTokenHandler.renewRefreshToken({
connection: AuthConnection.Apple,
revokeToken: 'test-token',
Expand Down
54 changes: 48 additions & 6 deletions app/core/OAuthService/AuthTokenHandler.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
import { Platform } from 'react-native';
import { AuthConnection } from './OAuthInterface';
import { AuthConnection, AuthRefreshTokenResponse } from './OAuthInterface';
import { createLoginHandler } from './OAuthLoginHandlers';
import type {
RefreshJWTToken,
RenewRefreshToken,
RevokeRefreshToken,
} from '@metamask/seedless-onboarding-controller/dist/types.d.cts';

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

class AuthTokenHandler {
interface AuthTokenHandlerInterface {
refreshJWTToken: RefreshJWTToken;
renewRefreshToken: RenewRefreshToken;
revokeRefreshToken: RevokeRefreshToken;
}

class AuthTokenHandler implements AuthTokenHandlerInterface {
/**
* Refresh the JWT Token using the refresh token.
*
* @param params - The params from the login handler
* @param params.connection - The connection type (Google, Apple, etc.)
* @param params.refreshToken - The refresh token from the Web3Auth Authentication Server.
* @returns The id token, access token, and metadata access token.
*/
async refreshJWTToken(params: {
connection: AuthConnection;
refreshToken: string;
Expand Down Expand Up @@ -41,7 +60,7 @@ class AuthTokenHandler {
throw new Error('Failed to refresh JWT token');
}

const refreshTokenData = await response.json();
const refreshTokenData: AuthRefreshTokenResponse = await response.json();
const idToken = refreshTokenData.id_token;

if (
Expand All @@ -62,6 +81,14 @@ class AuthTokenHandler {
};
}

/**
* Renew the refresh token.
*
* @param params - The params from the login handler
* @param params.connection - The connection type (Google, Apple, etc.)
* @param params.revokeToken - The revoke token from the Web3Auth Authentication Server.
* @returns The new refresh token and revoke token.
*/
async renewRefreshToken(params: {
connection: AuthConnection;
revokeToken: string;
Expand Down Expand Up @@ -89,12 +116,28 @@ class AuthTokenHandler {
}

const responseData = await response.json();

const newRefreshToken = responseData.refresh_token;
const newRevokeToken = responseData.revoke_token;

if (!newRefreshToken || !newRevokeToken) {
throw new Error('Failed to renew refresh token - ' + response.statusText);
}

return {
newRefreshToken: responseData.refresh_token,
newRevokeToken: responseData.revoke_token,
newRefreshToken,
newRevokeToken,
};
}

/**
* Revoke the refresh token.
*
* @param params - The params from the login handler
* @param params.connection - The connection type (Google, Apple, etc.)
* @param params.revokeToken - The revoke token from the Web3Auth Authentication Server.
* @returns void
*/
async revokeRefreshToken(params: {
connection: AuthConnection;
revokeToken: string;
Expand Down Expand Up @@ -122,7 +165,6 @@ class AuthTokenHandler {
'Failed to revoke refresh token - ' + response.statusText,
);
}

return;
}
}
Expand Down
13 changes: 13 additions & 0 deletions app/core/OAuthService/OAuthInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ export type AuthRequestParams =
| AuthRequestCodeParams
| AuthRequestIdTokenParams;

// return type for auth request with
// grant type : authorization_code, access_type: offline
export interface AuthResponse {
id_token: string;
access_token: string;
Expand All @@ -78,6 +80,17 @@ export interface AuthResponse {
revoke_token?: string;
}

// return type for auth request with
// grant type : refresh_token
// grant type : authorization_code, access_type: online
export interface AuthRefreshTokenResponse {
id_token: string;
access_token: string;
metadata_access_token: string;
indexes: number[];
endpoints: Record<string, string>;
}

export interface LoginHandler {
get authConnection(): AuthConnection;
get scope(): string[];
Expand Down
53 changes: 17 additions & 36 deletions app/core/OAuthService/OAuthLoginHandlers/baseHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,11 @@ describe('BaseLoginHandler', () => {
success: true,
id_token: 'mock-id-token',
refresh_token: 'mock-refresh-token',
indexes: [1, 2, 3],
endpoints: { endpoint1: 'value1' },
message: 'Success',
jwt_tokens: { token1: 'value1' },
revoke_token: 'mock-revoke-token',
access_token: 'mock-access-token',
metadata_access_token: 'mock-metadata-access-token',
token_type: 'Bearer',
expires_in: 3600,
};

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

expect(result).toEqual(mockResponse);

jest
.spyOn(global, 'fetch')
.mockResolvedValueOnce(new Response(JSON.stringify(mockResponse)));

const refreshResult = await mockHandler.refreshAuthToken('refresh-token');

expect(refreshResult).toEqual(mockResponse);

const mockRevokeResponse = {
new_refresh_token: 'refresh-token',
new_revoke_token: 'revoke-token',
};

jest
.spyOn(global, 'fetch')
.mockResolvedValueOnce(
new Response(JSON.stringify(mockRevokeResponse)),
);

const revokeResult =
await mockHandler.revokeRefreshToken('refresh-token');

expect(revokeResult).toEqual({
refresh_token: 'refresh-token',
revoke_token: 'revoke-token',
});
});

it('successfully gets auth tokens with idToken', async () => {
const mockResponse = {
success: true,
id_token: 'mock-id-token',
refresh_token: 'mock-refresh-token',
indexes: [1, 2, 3],
endpoints: { endpoint1: 'value1' },
message: 'Success',
jwt_tokens: { token1: 'value1' },
revoke_token: 'mock-revoke-token',
access_token: 'mock-access-token',
metadata_access_token: 'mock-metadata-access-token',
expires_in: 3600,
};

(global.fetch as jest.Mock).mockResolvedValueOnce({
Expand Down Expand Up @@ -361,6 +334,10 @@ describe('BaseLoginHandler', () => {
const mockResponse = {
success: true,
id_token: 'mock-id-token',
refresh_token: 'mock-refresh-token',
revoke_token: 'mock-revoke-token',
access_token: 'mock-access-token',
metadata_access_token: 'mock-metadata-access-token',
message: 'Success',
};

Expand Down Expand Up @@ -410,6 +387,10 @@ describe('BaseLoginHandler', () => {
const mockResponse = {
success: true,
id_token: 'mock-id-token',
refresh_token: 'mock-refresh-token',
revoke_token: 'mock-revoke-token',
access_token: 'mock-access-token',
metadata_access_token: 'mock-metadata-access-token',
message: 'Success',
};

Expand Down
74 changes: 1 addition & 73 deletions app/core/OAuthService/OAuthLoginHandlers/baseHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export async function getAuthTokens(
});

if (res.status === 200 || res.status === 201) {
const data = (await res.json()) satisfies AuthResponse;
const data: AuthResponse = (await res.json()) satisfies AuthResponse;
return data;
}

Expand Down Expand Up @@ -140,78 +140,6 @@ export abstract class BaseLoginHandler {
}
}

/**
* Refresh the JWT Token using the refresh token.
*
* @param refreshToken - The refresh token from the Web3Auth Authentication Server.
* @returns The JWT Token from the Web3Auth Authentication Server and new refresh token.
*/
async refreshAuthToken(refreshToken: string): Promise<AuthResponse> {
const { web3AuthNetwork } = this.options;
const requestData = {
client_id: this.options.clientId,
login_provider: this.authConnection,
network: web3AuthNetwork,
refresh_token: refreshToken,
grant_type: 'refresh_token', // specify refresh token flow
};
const res = await this.requestAuthToken(JSON.stringify(requestData));
return res;
}

/**
* Revoke the refresh token.
*
* @param revokeToken - The revoke token from the Web3Auth Authentication Server.
*/
async revokeRefreshToken(revokeToken: string): Promise<{
refresh_token: string;
revoke_token: string;
}> {
const requestData = {
revoke_token: revokeToken,
};

const res = await fetch(
`${this.options.authServerUrl}${this.AUTH_SERVER_REVOKE_PATH}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestData),
},
);

const data = await res.json();
return {
refresh_token: data.new_refresh_token,
revoke_token: data.new_revoke_token,
};
}

/**
* Make a request to the Web3Auth Authentication Server to get the JWT Token.
*
* @param requestData - The request data for the Web3Auth Authentication Server.
* @returns The JWT Token from the Web3Auth Authentication Server.
*/
protected async requestAuthToken(requestData: string): Promise<AuthResponse> {
const res = await fetch(
`${this.options.authServerUrl}${this.AUTH_SERVER_TOKEN_PATH}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: requestData,
},
);

const data = await res.json();
return data;
}

/**
* Generate a nonce value.
*
Expand Down
Loading
Loading