Skip to content

feat(passport): ID-3844 Magic TEE Signer #2663

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
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
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
48 changes: 25 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,7 +11,7 @@ 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 {
Expand All @@ -35,7 +35,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 +56,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 +83,7 @@ export const buildPrivateVars = (passportModuleConfiguration: PassportModuleConf
const passportImxProviderFactory = new PassportImxProviderFactory({
authManager,
immutableXClient,
magicAdapter,
magicTEESigner,
passportEventEmitter,
imxApiClients,
guardianClient,
Expand All @@ -88,7 +92,7 @@ export const buildPrivateVars = (passportModuleConfiguration: PassportModuleConf
return {
config,
authManager,
magicAdapter,
magicTEESigner,
confirmationScreen,
immutableXClient,
multiRollupApiClients,
Expand All @@ -107,7 +111,7 @@ export class Passport {

private readonly immutableXClient: IMXClient;

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

private readonly multiRollupApiClients: MultiRollupApiClients;

Expand All @@ -122,7 +126,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 +159,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 @@ -305,16 +317,7 @@ export class Passport {
*/
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 +329,6 @@ export class Passport {
public async getLogoutUrl(): Promise<string | null> {
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
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