Skip to content
Open
14 changes: 14 additions & 0 deletions packages/game-bridge/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const PASSPORT_FUNCTIONS = {
getEmail: 'getEmail',
getPassportId: 'getPassportId',
getLinkedAddresses: 'getLinkedAddresses',
storeTokens: 'storeTokens',
imx: {
getAddress: 'getAddress',
isRegisteredOffchain: 'isRegisteredOffchain',
Expand Down Expand Up @@ -464,6 +465,19 @@ window.callFunction = async (jsonData: string) => {
});
break;
}
case PASSPORT_FUNCTIONS.storeTokens: {
const tokenResponse = JSON.parse(data);
const profile = await getPassportClient().storeTokens(tokenResponse);
identify({ passportId: profile.sub });
trackDuration(moduleName, 'performedStoreTokens', mt(markStart));
callbackToGame({
responseFor: fxName,
requestId,
success: true,
error: null,
});
break;
}
case PASSPORT_FUNCTIONS.getEmail: {
const userProfile = await getPassportClient().getUserInfo();
const success = userProfile?.email !== undefined;
Expand Down
19 changes: 14 additions & 5 deletions packages/passport/sdk/src/Passport.int.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,12 +224,16 @@ describe('Passport', () => {
});

it('registers the user and returns the ether key', async () => {
mockGetUser.mockResolvedValueOnce(null);
mockSigninPopup.mockResolvedValue(mockOidcUser);
mockGetUser.mockResolvedValueOnce(mockOidcUser);
mockSigninSilent.mockResolvedValueOnce(mockOidcUserZkevm);
mockGetUser.mockResolvedValue(mockOidcUserZkevm);
useMswHandlers([
mswHandlers.rpcProvider.success,
mswHandlers.counterfactualAddress.success,
mswHandlers.api.chains.success,
mswHandlers.magicTEE.createWallet.success,
]);

const zkEvmProvider = await getZkEvmProvider();
Expand All @@ -244,13 +248,15 @@ describe('Passport', () => {

describe('when the registration request fails', () => {
it('throws an error', async () => {
mockSigninPopup.mockResolvedValue(mockOidcUser);
mockGetUser.mockResolvedValueOnce(null);
mockSigninPopup.mockResolvedValue(mockOidcUser);
mockGetUser.mockResolvedValueOnce(mockOidcUser);
mockSigninSilent.mockResolvedValue(mockOidcUser);
mockGetUser.mockResolvedValue(mockOidcUser);
useMswHandlers([
mswHandlers.counterfactualAddress.internalServerError,
mswHandlers.api.chains.success,
mswHandlers.magicTEE.createWallet.success,
]);

const zkEvmProvider = await getZkEvmProvider();
Expand All @@ -268,10 +274,11 @@ describe('Passport', () => {
const transferToAddress = '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC';

useMswHandlers([
mswHandlers.counterfactualAddress.success,
mswHandlers.rpcProvider.success,
mswHandlers.relayer.success,
mswHandlers.guardian.evaluateTransaction.success,
mswHandlers.magicTEE.createWallet.success,
mswHandlers.magicTEE.personalSign.success,
]);
mockMagicRequest.mockImplementation(({ method }: RequestArguments) => {
switch (method) {
Expand Down Expand Up @@ -307,7 +314,7 @@ describe('Passport', () => {
});

expect(result).toEqual(transactionHash);
expect(mockGetUser).toHaveBeenCalledTimes(6);
expect(mockGetUser).toHaveBeenCalledTimes(9);
});

it('ethSigner is initialised if user logs in after connectEvm', async () => {
Expand All @@ -318,6 +325,8 @@ describe('Passport', () => {
mswHandlers.rpcProvider.success,
mswHandlers.relayer.success,
mswHandlers.guardian.evaluateTransaction.success,
mswHandlers.magicTEE.createWallet.success,
mswHandlers.magicTEE.personalSign.success,
]);
mockMagicRequest.mockImplementation(({ method }: RequestArguments) => {
switch (method) {
Expand All @@ -335,7 +344,7 @@ describe('Passport', () => {
}
}
});
mockGetUser.mockResolvedValueOnce(Promise.resolve(null));
mockGetUser.mockResolvedValueOnce(null);
mockSigninPopup.mockResolvedValue(mockOidcUserZkevm);
mockSigninSilent.mockResolvedValueOnce(mockOidcUserZkevm);

Expand All @@ -362,7 +371,7 @@ describe('Passport', () => {
// user logs in, ethSigner is initialised
await passport.login();

mockGetUser.mockResolvedValue(Promise.resolve(mockOidcUserZkevm));
mockGetUser.mockResolvedValue(mockOidcUserZkevm);

expect(accounts).toEqual([mockUserZkEvm.zkEvm.ethAddress]);

Expand Down
30 changes: 6 additions & 24 deletions packages/passport/sdk/src/Passport.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { IMXClient } from '@imtbl/x-client';
import { ImxApiClients, imxApiConfig, MultiRollupApiClients } from '@imtbl/generated-clients';
import { trackError, trackFlow } from '@imtbl/metrics';
import AuthManager from './authManager';
import MagicAdapter from './magic/magicAdapter';
import MagicTEESigner from './magic/magicTEESigner';
import { Passport } from './Passport';
import { PassportImxProvider, PassportImxProviderFactory } from './starkEx';
import { OidcConfiguration, UserProfile } from './types';
Expand All @@ -21,7 +21,7 @@ import { ZkEvmProvider } from './zkEvm';
import { PassportError, PassportErrorType } from './errors/passportError';

jest.mock('./authManager');
jest.mock('./magic/magicAdapter');
jest.mock('./magic/magicTEESigner');
jest.mock('./starkEx');
jest.mock('./confirmation');
jest.mock('./zkEvm');
Expand All @@ -44,8 +44,6 @@ describe('Passport', () => {
let loginCallbackMock: jest.Mock;
let logoutMock: jest.Mock;
let removeUserMock: jest.Mock;
let magicLoginMock: jest.Mock;
let magicLogoutMock: jest.Mock;
let getUserMock: jest.Mock;
let requestRefreshTokenMock: jest.Mock;
let getProviderMock: jest.Mock;
Expand All @@ -57,8 +55,6 @@ describe('Passport', () => {
beforeEach(() => {
authLoginMock = jest.fn().mockReturnValue(mockUser);
loginCallbackMock = jest.fn();
magicLoginMock = jest.fn();
magicLogoutMock = jest.fn();
logoutMock = jest.fn();
removeUserMock = jest.fn();
getUserMock = jest.fn();
Expand All @@ -78,9 +74,9 @@ describe('Passport', () => {
requestRefreshTokenAfterRegistration: requestRefreshTokenMock,
forceUserRefresh: forceUserRefreshMock,
});
(MagicAdapter as jest.Mock).mockReturnValue({
login: magicLoginMock,
logout: magicLogoutMock,
(MagicTEESigner as unknown as jest.Mock).mockReturnValue({
getAddress: jest.fn().mockResolvedValue('0x123'),
signMessage: jest.fn().mockResolvedValue('signature'),
});
(PassportImxProviderFactory as jest.Mock).mockReturnValue({
getProvider: getProviderMock,
Expand Down Expand Up @@ -289,26 +285,12 @@ describe('Passport', () => {
await passport.logout();

expect(logoutMock).toBeCalledTimes(1);
expect(magicLogoutMock).toBeCalledTimes(1);
});
});

describe('when the logout mode is redirect', () => {
it('should execute logout without error in the correct order', async () => {
await passport.logout();

const logoutMockOrder = logoutMock.mock.invocationCallOrder[0];
const magicLogoutMockOrder = magicLogoutMock.mock.invocationCallOrder[0];

expect(logoutMock).toBeCalledTimes(1);
expect(magicLogoutMock).toBeCalledTimes(1);
expect(magicLogoutMockOrder).toBeLessThan(logoutMockOrder);
});
});

it('should call track error function if an error occurs', async () => {
const error = new Error('error');
magicLogoutMock.mockRejectedValue(error);
logoutMock.mockRejectedValue(error);

try {
await passport.logout();
Expand Down
57 changes: 34 additions & 23 deletions packages/passport/sdk/src/Passport.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IMXProvider } from '@imtbl/x-provider';
import {
createConfig, ImxApiClients, imxApiConfig, MultiRollupApiClients,
createConfig, ImxApiClients, imxApiConfig, MagicTeeApiClients, MultiRollupApiClients,
} from '@imtbl/generated-clients';
import { IMXClient } from '@imtbl/x-client';
import { Environment } from '@imtbl/config';
Expand All @@ -11,10 +11,11 @@ import {
} from '@imtbl/metrics';
import { isAxiosError } from 'axios';
import AuthManager from './authManager';
import MagicAdapter from './magic/magicAdapter';
import MagicTEESigner from './magic/magicTEESigner';
import { PassportImxProviderFactory } from './starkEx';
import { PassportConfiguration } from './config';
import {
DeviceTokenResponse,
DirectLoginMethod,
isUserImx,
isUserZkEvm,
Expand All @@ -35,7 +36,6 @@ import logger from './utils/logger';
import { announceProvider, passportProviderInfo } from './zkEvm/provider/eip6963';
import { isAPIError, PassportError, PassportErrorType } from './errors/passportError';
import { withMetricsAsync } from './utils/metrics';
import { MagicProviderProxyFactory } from './magic/magicProviderProxyFactory';

const buildImxClientConfig = (passportModuleConfiguration: PassportModuleConfiguration) => {
if (passportModuleConfiguration.overrides) {
Expand All @@ -57,9 +57,14 @@ const buildImxApiClients = (passportModuleConfiguration: PassportModuleConfigura
export const buildPrivateVars = (passportModuleConfiguration: PassportModuleConfiguration) => {
const config = new PassportConfiguration(passportModuleConfiguration);
const authManager = new AuthManager(config);
const magicProviderProxyFactory = new MagicProviderProxyFactory(authManager, config);
const magicAdapter = new MagicAdapter(config, magicProviderProxyFactory);
const confirmationScreen = new ConfirmationScreen(config);
const magicTeeApiClients = new MagicTeeApiClients({
basePath: config.magicTeeBasePath,
timeout: config.magicTeeTimeout,
magicPublishableApiKey: config.magicPublishableApiKey,
magicProviderId: config.magicProviderId,
});
const magicTEESigner = new MagicTEESigner(authManager, magicTeeApiClients);
const multiRollupApiClients = new MultiRollupApiClients(config.multiRollupConfig);
const passportEventEmitter = new TypedEventEmitter<PassportEventMap>();

Expand All @@ -79,7 +84,7 @@ export const buildPrivateVars = (passportModuleConfiguration: PassportModuleConf
const passportImxProviderFactory = new PassportImxProviderFactory({
authManager,
immutableXClient,
magicAdapter,
magicTEESigner,
passportEventEmitter,
imxApiClients,
guardianClient,
Expand All @@ -88,7 +93,7 @@ export const buildPrivateVars = (passportModuleConfiguration: PassportModuleConf
return {
config,
authManager,
magicAdapter,
magicTEESigner,
confirmationScreen,
immutableXClient,
multiRollupApiClients,
Expand All @@ -107,7 +112,7 @@ export class Passport {

private readonly immutableXClient: IMXClient;

private readonly magicAdapter: MagicAdapter;
private readonly magicTEESigner: MagicTEESigner;

private readonly multiRollupApiClients: MultiRollupApiClients;

Expand All @@ -122,7 +127,7 @@ export class Passport {

this.config = privateVars.config;
this.authManager = privateVars.authManager;
this.magicAdapter = privateVars.magicAdapter;
this.magicTEESigner = privateVars.magicTEESigner;
this.confirmationScreen = privateVars.confirmationScreen;
this.immutableXClient = privateVars.immutableXClient;
this.multiRollupApiClients = privateVars.multiRollupApiClients;
Expand Down Expand Up @@ -155,19 +160,27 @@ export class Passport {
* Connects to EVM and optionally announces the provider.
* @param {Object} options - Configuration options
* @param {boolean} options.announceProvider - Whether to announce the provider via EIP-6963 for wallet discovery (defaults to true)
* @returns {Provider} The EVM provider instance
* @returns {Promise<Provider>} The EVM provider instance
*/
public connectEvm(options: {
public async connectEvm(options: {
announceProvider: boolean
} = { announceProvider: true }): Promise<Provider> {
return withMetricsAsync(async () => {
let user: User | null = null;
try {
user = await this.authManager.getUser();
} catch (error) {
// Initialise the zkEvmProvider without a user
}

const provider = new ZkEvmProvider({
passportEventEmitter: this.passportEventEmitter,
authManager: this.authManager,
magicAdapter: this.magicAdapter,
config: this.config,
multiRollupApiClients: this.multiRollupApiClients,
guardianClient: this.guardianClient,
ethSigner: this.magicTEESigner,
user,
});

if (options?.announceProvider) {
Expand Down Expand Up @@ -299,22 +312,21 @@ export class Passport {
}, 'loginWithPKCEFlowCallback');
}

public async storeTokens(tokenResponse: DeviceTokenResponse): Promise<UserProfile> {
return withMetricsAsync(async () => {
const user = await this.authManager.storeTokens(tokenResponse);
this.passportEventEmitter.emit(PassportEvents.LOGGED_IN, user);
return user.profile;
}, 'storeTokens');
}

/**
* Logs out the current user.
* @returns {Promise<void>} A promise that resolves when the logout is complete
*/
public async logout(): Promise<void> {
return withMetricsAsync(async () => {
if (this.config.oidcConfiguration.logoutMode === 'silent') {
await Promise.allSettled([
this.authManager.logout(),
this.magicAdapter.logout(),
]);
} else {
// We need to ensure that the Magic wallet is logged out BEFORE redirecting
await this.magicAdapter.logout();
await this.authManager.logout();
}
await this.authManager.logout();
this.passportEventEmitter.emit(PassportEvents.LOGGED_OUT);
}, 'logout');
}
Expand All @@ -326,7 +338,6 @@ export class Passport {
public async getLogoutUrl(): Promise<string> {
return withMetricsAsync(async () => {
await this.authManager.removeUser();
await this.magicAdapter.logout();
this.passportEventEmitter.emit(PassportEvents.LOGGED_OUT);
return await this.authManager.getLogoutUrl();
}, 'getLogoutUrl');
Expand Down
10 changes: 10 additions & 0 deletions packages/passport/sdk/src/authManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,16 @@ export default class AuthManager {
return response.data;
}

public async storeTokens(tokenResponse: DeviceTokenResponse): Promise<User> {
return withPassportError<User>(async () => {
const oidcUser = AuthManager.mapDeviceTokenResponseToOidcUser(tokenResponse);
const user = AuthManager.mapOidcUserToDomainModel(oidcUser);
await this.userManager.storeUser(oidcUser);

return user;
}, PassportErrorType.AUTHENTICATION_ERROR);
}

public async logout(): Promise<void> {
return withPassportError<void>(async () => {
await this.userManager.revokeTokens(['refresh_token']);
Expand Down
4 changes: 4 additions & 0 deletions packages/passport/sdk/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export class PassportConfiguration {

readonly magicProviderId: string;

readonly magicTeeBasePath: string = 'https://tee.express.magiclabs.com';

readonly magicTeeTimeout: number = 6000;

readonly oidcConfiguration: OidcConfiguration;

readonly baseConfig: ImmutableConfiguration;
Expand Down
4 changes: 1 addition & 3 deletions packages/passport/sdk/src/magic/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
import MagicAdapter from './magicAdapter';

export default { MagicAdapter };
export { default as MagicTEESigner } from './magicTEESigner';
Loading
Loading