Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
ee7a2ab
step1-split wallet and auth
shineli1984 Nov 18, 2025
f164052
add lock file
shineli1984 Nov 18, 2025
f065702
make sure the code are copied
shineli1984 Nov 24, 2025
6958697
syncpack
shineli1984 Nov 24, 2025
fac8e9d
pnpm lock file
shineli1984 Nov 24, 2025
88a8058
node types
shineli1984 Nov 24, 2025
1b2aa6d
remove as any
shineli1984 Nov 24, 2025
9e5e0a5
wallet build
shineli1984 Nov 24, 2025
3bc1ba8
sample app eslint fix
shineli1984 Nov 24, 2025
798597f
add missing dependency
shineli1984 Nov 24, 2025
6b87732
remove unnecessary files
shineli1984 Nov 24, 2025
28eacbf
sample app lint
shineli1984 Nov 24, 2025
15bfd09
fix chainID
shineli1984 Nov 24, 2025
fb595ff
fix type error
shineli1984 Nov 24, 2025
9a19c4e
fix provider type
shineli1984 Nov 24, 2025
b101708
fix type
shineli1984 Nov 24, 2025
bcfb6a7
fix config
shineli1984 Nov 24, 2025
6780bd7
use authconfig insted
shineli1984 Nov 24, 2025
91461e6
Merge branch 'main' into v3alpha
shineli1984 Nov 24, 2025
73516a8
fix deployment settings
shineli1984 Nov 24, 2025
dc62012
fix publish settings
shineli1984 Nov 24, 2025
56b73dc
type module
shineli1984 Nov 25, 2025
8aeeeba
align package.json
shineli1984 Nov 25, 2025
648174a
make passport use auth and wallet
shineli1984 Nov 26, 2025
a5f2f97
fix build
shineli1984 Nov 26, 2025
bccc29a
session activity
shineli1984 Nov 26, 2025
48ae93f
remove anomynous id, move tracking
shineli1984 Nov 26, 2025
969c7b2
remove anomynous id
shineli1984 Nov 26, 2025
eb12c6f
fix session activity
shineli1984 Nov 27, 2025
f3324f5
simplify interface
shineli1984 Nov 27, 2025
dbdcf54
move imx stuff back to passport
shineli1984 Nov 27, 2025
a07d6a2
lock file
shineli1984 Nov 27, 2025
508a9e8
refactor
shineli1984 Nov 28, 2025
75b8c8a
default auth
shineli1984 Dec 1, 2025
5b06bd9
lint
shineli1984 Dec 1, 2025
f112217
sdk version
shineli1984 Dec 1, 2025
f6017c1
conflicts
shineli1984 Dec 1, 2025
e10715d
syncpack
shineli1984 Dec 1, 2025
b21f021
lock file
shineli1984 Dec 1, 2025
3453f0f
fix sample app
shineli1984 Dec 2, 2025
d71d3d0
error type fix
shineli1984 Dec 2, 2025
3df2460
add comments
shineli1984 Dec 2, 2025
4b17b25
fix build
shineli1984 Dec 2, 2025
d8f2850
add tests
shineli1984 Dec 2, 2025
a214737
fix test config
shineli1984 Dec 3, 2025
137050d
syncpack
shineli1984 Dec 3, 2025
5256255
login event
shineli1984 Dec 3, 2025
583b130
Merge branch 'main' into v3alpha
shineli1984 Dec 3, 2025
ca49c4e
conflicts
shineli1984 Dec 5, 2025
1798bc8
bug
shineli1984 Dec 5, 2025
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
38 changes: 38 additions & 0 deletions packages/auth/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@imtbl/auth",
"version": "0.0.0",
"description": "Authentication SDK for Immutable",
"main": "dist/node/index.js",
"module": "dist/node/index.mjs",
"types": "dist/types/index.d.ts",
"exports": {
".": {
"types": "./dist/types/index.d.ts",
"import": {
"browser": "./dist/browser/index.mjs",
"default": "./dist/node/index.mjs"
},
"require": "./dist/node/index.js"
}
},
"scripts": {
"build": "pnpm transpile && pnpm typegen",
"transpile": "tsup src/index.ts --config ../../tsup.config.js",
"typegen": "tsc --customConditions default --emitDeclarationOnly --outDir dist/types"
},
"dependencies": {
"@imtbl/config": "workspace:*",
"@imtbl/metrics": "workspace:*",
"axios": "^1.6.2",
"jwt-decode": "^3.1.2",
"localforage": "^1.10.0",
"oidc-client-ts": "^2.4.0",
"uuid": "^9.0.1"
},
"devDependencies": {
"@imtbl/toolkit": "workspace:*",
"tsup": "^8.3.0",
"typescript": "^5.6.2"
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import localForage from 'localforage';
import DeviceCredentialsManager from './storage/device_credentials_manager';
import logger from './utils/logger';
import { isAccessTokenExpiredOrExpiring } from './utils/token';
import { PassportError, PassportErrorType, withPassportError } from './errors/passportError';
import { AuthError, AuthErrorType, withAuthError } from './errors';
import {
DirectLoginOptions,
PassportMetadata,
Expand All @@ -27,7 +27,7 @@ import {
UserImx,
isUserImx,
} from './types';
import { PassportConfiguration } from './config';
import { IAuthConfiguration } from './config';
import ConfirmationOverlay from './overlay/confirmationOverlay';
import { LocalForageAsyncStorage } from './storage/LocalForageAsyncStorage';
import { EmbeddedLoginPrompt } from './confirmation';
Expand All @@ -48,12 +48,12 @@ const getLogoutEndpointPath = (crossSdkBridgeEnabled: boolean): string => (
crossSdkBridgeEnabled ? crossSdkBridgeLogoutEndpoint : logoutEndpoint
);

const getAuthConfiguration = (config: PassportConfiguration): UserManagerSettings => {
const getAuthConfiguration = (config: IAuthConfiguration): UserManagerSettings => {
const { authenticationDomain, oidcConfiguration } = config;

let store;
if (config.crossSdkBridgeEnabled) {
store = new LocalForageAsyncStorage('ImmutableSDKPassport', localForage.INDEXEDDB);
store = new LocalForageAsyncStorage('ImmutableSDKAuth', localForage.INDEXEDDB);
} else if (typeof window !== 'undefined') {
store = window.localStorage;
} else {
Expand Down Expand Up @@ -110,7 +110,7 @@ export default class AuthManager {

private deviceCredentialsManager: DeviceCredentialsManager;

private readonly config: PassportConfiguration;
private readonly config: IAuthConfiguration;

private readonly embeddedLoginPrompt: EmbeddedLoginPrompt;

Expand All @@ -121,7 +121,7 @@ export default class AuthManager {
*/
private refreshingPromise: Promise<User | null> | null = null;

constructor(config: PassportConfiguration, embeddedLoginPrompt: EmbeddedLoginPrompt) {
constructor(config: IAuthConfiguration, embeddedLoginPrompt: EmbeddedLoginPrompt) {
this.config = config;
this.userManager = new UserManager(getAuthConfiguration(config));
this.deviceCredentialsManager = new DeviceCredentialsManager();
Expand Down Expand Up @@ -221,13 +221,13 @@ export default class AuthManager {

public async loginWithRedirect(anonymousId?: string, directLoginOptions?: DirectLoginOptions): Promise<void> {
await this.userManager.clearStaleState();
return withPassportError<void>(async () => {
return withAuthError<void>(async () => {
const extraQueryParams = this.buildExtraQueryParams(anonymousId, directLoginOptions);

await this.userManager.signinRedirect({
extraQueryParams,
});
}, PassportErrorType.AUTHENTICATION_ERROR);
}, AuthErrorType.AUTHENTICATION_ERROR);
}

/**
Expand All @@ -239,19 +239,21 @@ export default class AuthManager {
* @param directLoginOptions.email Required when directLoginMethod is 'email'
*/
public async login(anonymousId?: string, directLoginOptions?: DirectLoginOptions): Promise<User> {
return withPassportError<User>(async () => {
return withAuthError<User>(async () => {
// If directLoginOptions are provided, then the consumer has rendered their own initial login screen.
// If not, display the embedded login prompt and pass the returned direct login options and imPassportTraceId to the login popup.
let directLoginOptionsToUse: DirectLoginOptions | undefined;
let imPassportTraceId: string | undefined;
if (directLoginOptions) {
directLoginOptionsToUse = directLoginOptions;
} else if (!this.config.popupOverlayOptions.disableHeadlessLoginPromptOverlay) {
} else if (!this.config.popupOverlayOptions?.disableHeadlessLoginPromptOverlay) {
const {
imPassportTraceId: embeddedLoginPromptImPassportTraceId,
...embeddedLoginPromptDirectLoginOptions
} = await this.embeddedLoginPrompt.displayEmbeddedLoginPrompt(anonymousId);
directLoginOptionsToUse = embeddedLoginPromptDirectLoginOptions;
} = this.embeddedLoginPrompt
? await this.embeddedLoginPrompt.displayEmbeddedLoginPrompt(anonymousId)
: { imPassportTraceId: undefined };
directLoginOptionsToUse = (embeddedLoginPromptDirectLoginOptions as any).directLoginMethod ? embeddedLoginPromptDirectLoginOptions as DirectLoginOptions : undefined;
imPassportTraceId = embeddedLoginPromptImPassportTraceId;
}

Expand Down Expand Up @@ -314,7 +316,7 @@ export default class AuthManager {

// Popup was blocked; append the blocked popup overlay to allow the user to try again.
let popupHasBeenOpened: boolean = false;
const overlay = new ConfirmationOverlay(this.config.popupOverlayOptions, true);
const overlay = new ConfirmationOverlay(this.config.popupOverlayOptions || {}, true);
overlay.append(
async () => {
try {
Expand Down Expand Up @@ -346,7 +348,7 @@ export default class AuthManager {
);
});
});
}, PassportErrorType.AUTHENTICATION_ERROR);
}, AuthErrorType.AUTHENTICATION_ERROR);
}

public async getUserOrLogin(): Promise<User> {
Expand Down Expand Up @@ -378,7 +380,7 @@ export default class AuthManager {
}

public async loginCallback(): Promise<undefined | User> {
return withPassportError<undefined | User>(async () => {
return withAuthError<undefined | User>(async () => {
// ID-3950: https://github.com/authts/oidc-client-ts/issues/2043
// When using `signinPopup` to initiate a login, call the `signinPopupCallback` method and
// set the `keepOpen` flag to `true`, as the `login` method is now responsible for closing the popup.
Expand All @@ -393,7 +395,7 @@ export default class AuthManager {
}

return AuthManager.mapOidcUserToDomainModel(oidcUser);
}, PassportErrorType.AUTHENTICATION_ERROR);
}, AuthErrorType.AUTHENTICATION_ERROR);
}

public async getPKCEAuthorizationUrl(
Expand Down Expand Up @@ -448,7 +450,7 @@ export default class AuthManager {
}

public async loginWithPKCEFlowCallback(authorizationCode: string, state: string): Promise<User> {
return withPassportError<User>(async () => {
return withAuthError<User>(async () => {
const pkceData = this.deviceCredentialsManager.getPKCEData();
if (!pkceData) {
throw new Error('No code verifier or state for PKCE');
Expand All @@ -464,7 +466,7 @@ export default class AuthManager {
await this.userManager.storeUser(oidcUser);

return user;
}, PassportErrorType.AUTHENTICATION_ERROR);
}, AuthErrorType.AUTHENTICATION_ERROR);
}

private async getPKCEToken(authorizationCode: string, codeVerifier: string): Promise<DeviceTokenResponse> {
Expand All @@ -484,25 +486,25 @@ export default class AuthManager {
}

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

return user;
}, PassportErrorType.AUTHENTICATION_ERROR);
}, AuthErrorType.AUTHENTICATION_ERROR);
}

public async logout(): Promise<void> {
return withPassportError<void>(async () => {
return withAuthError<void>(async () => {
await this.userManager.revokeTokens(['refresh_token']);

if (this.logoutMode === 'silent') {
await this.userManager.signoutSilent();
} else {
await this.userManager.signoutRedirect();
}
}, PassportErrorType.LOGOUT_ERROR);
}, AuthErrorType.LOGOUT_ERROR);
}

public async logoutSilentCallback(url: string): Promise<void> {
Expand Down Expand Up @@ -554,16 +556,16 @@ export default class AuthManager {
}
resolve(null);
} catch (err) {
let passportErrorType = PassportErrorType.AUTHENTICATION_ERROR;
let passportErrorType = AuthErrorType.AUTHENTICATION_ERROR;
let errorMessage = 'Failed to refresh token';
let removeUser = true;

if (err instanceof ErrorTimeout) {
passportErrorType = PassportErrorType.SILENT_LOGIN_ERROR;
passportErrorType = AuthErrorType.SILENT_LOGIN_ERROR;
errorMessage = `${errorMessage}: ${err.message}`;
removeUser = false;
} else if (err instanceof ErrorResponse) {
passportErrorType = PassportErrorType.NOT_LOGGED_IN_ERROR;
passportErrorType = AuthErrorType.NOT_LOGGED_IN_ERROR;
errorMessage = `${errorMessage}: ${err.message || err.error_description}`;
} else if (err instanceof Error) {
errorMessage = `${errorMessage}: ${err.message}`;
Expand All @@ -581,7 +583,7 @@ export default class AuthManager {
}
}

reject(new PassportError(errorMessage, passportErrorType));
reject(new AuthError(errorMessage, passportErrorType));
} finally {
this.refreshingPromise = null; // Reset the promise after completion
}
Expand Down
67 changes: 67 additions & 0 deletions packages/auth/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
OidcConfiguration,
AuthModuleConfiguration,
PopupOverlayOptions,
} from './types';
import { AuthError, AuthErrorType } from './errors';

const validateConfiguration = <T>(
configuration: T,
requiredKeys: Array<keyof T>,
prefix?: string,
) => {
const missingKeys = requiredKeys
.map((key) => !configuration[key] && key)
.filter((n) => n)
.join(', ');
if (missingKeys !== '') {
const errorMessage = prefix
? `${prefix} - ${missingKeys} cannot be null`
: `${missingKeys} cannot be null`;
throw new AuthError(
errorMessage,
AuthErrorType.INVALID_CONFIGURATION,
);
}
};

/**
* Interface that any configuration must implement to work with AuthManager
*/
export interface IAuthConfiguration {
readonly authenticationDomain: string;
readonly passportDomain: string;
readonly oidcConfiguration: OidcConfiguration;
readonly crossSdkBridgeEnabled: boolean;
readonly popupOverlayOptions?: PopupOverlayOptions;
}

export class AuthConfiguration implements IAuthConfiguration {
readonly authenticationDomain: string;
readonly passportDomain: string;
readonly oidcConfiguration: OidcConfiguration;
readonly crossSdkBridgeEnabled: boolean;
readonly popupOverlayOptions?: PopupOverlayOptions;

constructor({
authenticationDomain,
passportDomain,
crossSdkBridgeEnabled,
popupOverlayOptions,
...oidcConfiguration
}: AuthModuleConfiguration) {
validateConfiguration(oidcConfiguration, [
'clientId',
'redirectUri',
]);

this.oidcConfiguration = oidcConfiguration;
this.crossSdkBridgeEnabled = crossSdkBridgeEnabled || false;
this.popupOverlayOptions = popupOverlayOptions;

// Default to production auth domain if not provided
this.authenticationDomain = authenticationDomain || 'https://auth.immutable.com';
this.passportDomain = passportDomain || 'https://passport.immutable.com';
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
ConfirmationSendMessage,
} from './types';
import { openPopupCenter } from './popup';
import { PassportConfiguration } from '../config';
import { IAuthConfiguration } from '../config';
import ConfirmationOverlay from '../overlay/confirmationOverlay';

const CONFIRMATION_WINDOW_TITLE = 'Confirm this transaction';
Expand All @@ -24,7 +24,7 @@ type MessageHandler = (arg0: MessageEvent) => void;
type MessageType = 'erc191' | 'eip712';

export default class ConfirmationScreen {
private config: PassportConfiguration;
private config: IAuthConfiguration;

private confirmationWindow: Window | undefined;

Expand All @@ -36,7 +36,7 @@ export default class ConfirmationScreen {

private timer: NodeJS.Timeout | undefined;

constructor(config: PassportConfiguration) {
constructor(config: IAuthConfiguration) {
this.config = config;
this.overlayClosed = false;
}
Expand Down Expand Up @@ -194,12 +194,12 @@ export default class ConfirmationScreen {
width: popupOptions?.width || CONFIRMATION_WINDOW_WIDTH,
height: popupOptions?.height || CONFIRMATION_WINDOW_HEIGHT,
});
this.overlay = new ConfirmationOverlay(this.config.popupOverlayOptions);
this.overlay = new ConfirmationOverlay(this.config.popupOverlayOptions || {});
} catch (error) {
// If an error is thrown here then the popup is blocked
const errorMessage = error instanceof Error ? error.message : String(error);
trackError('passport', 'confirmationPopupDenied', new Error(errorMessage));
this.overlay = new ConfirmationOverlay(this.config.popupOverlayOptions, true);
this.overlay = new ConfirmationOverlay(this.config.popupOverlayOptions || {}, true);
}

this.overlay.append(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
EmbeddedLoginPromptResult,
EmbeddedLoginPromptReceiveMessage,
} from './types';
import { PassportConfiguration } from '../config';
import { IAuthConfiguration } from '../config';
import EmbeddedLoginPromptOverlay from '../overlay/embeddedLoginPromptOverlay';

const LOGIN_PROMPT_WINDOW_HEIGHT = 660;
Expand All @@ -14,9 +14,9 @@ const LOGIN_PROMPT_KEYFRAME_STYLES_ID = 'passport-embedded-login-keyframes';
const LOGIN_PROMPT_IFRAME_ID = 'passport-embedded-login-iframe';

export default class EmbeddedLoginPrompt {
private config: PassportConfiguration;
private config: IAuthConfiguration;

constructor(config: PassportConfiguration) {
constructor(config: IAuthConfiguration) {
this.config = config;
}

Expand Down
Loading
Loading