Skip to content
6 changes: 6 additions & 0 deletions .changeset/violet-badgers-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@clerk/clerk-js': minor
'@clerk/types': minor
---

Adding iframeContext to SignIn and SignUp params when a CHIPS build is running in an iframe context
19 changes: 16 additions & 3 deletions packages/clerk-js/src/core/resources/SignIn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import type {
} from '@clerk/types';

import { debugLogger } from '@/utils/debug';
import { inIframe } from '@/utils/runtime';

import {
generateSignatureWithBase,
Expand Down Expand Up @@ -298,12 +299,18 @@ export class SignIn extends BaseResource implements SignInResource {
const redirectUrl = SignIn.clerk.buildUrlWithAuth(params.redirectUrl);

if (!this.id || !continueSignIn) {
await this.create({
const createParams: SignInCreateParams = {
strategy,
identifier,
redirectUrl,
actionCompleteRedirectUrl,
});
};

if (__BUILD_VARIANT_CHIPS__ && inIframe()) {
createParams.sessionId = BaseResource.clerk.session?.id;
}

await this.create(createParams);
}

if (strategy === 'saml' || strategy === 'enterprise_sso') {
Expand Down Expand Up @@ -627,9 +634,15 @@ class SignInFuture implements SignInFutureResource {

async create(params: SignInFutureCreateParams): Promise<{ error: unknown }> {
return runAsyncResourceTask(this.resource, async () => {
const createParams: SignInFutureCreateParams = { ...params };

if (__BUILD_VARIANT_CHIPS__ && inIframe()) {
createParams.sessionId = BaseResource.clerk.session?.id;
}

await this.resource.__internal_basePost({
path: this.resource.pathRoot,
body: params,
body: createParams,
});
});
}
Expand Down
13 changes: 12 additions & 1 deletion packages/clerk-js/src/core/resources/SignUp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import type {
} from '@clerk/types';

import { debugLogger } from '@/utils/debug';
import { inIframe } from '@/utils/runtime';

import {
generateSignatureWithBase,
Expand Down Expand Up @@ -142,6 +143,10 @@ export class SignUp extends BaseResource implements SignUpResource {

let finalParams = { ...params };

if (__BUILD_VARIANT_CHIPS__ && inIframe()) {
finalParams.sessionId = BaseResource.clerk.session?.id;
}

if (!__BUILD_DISABLE_RHC__ && !this.clientBypass() && !this.shouldBypassCaptchaForAttempt(params)) {
const captchaChallenge = new CaptchaChallenge(SignUp.clerk);
const captchaParams = await captchaChallenge.managedOrInvisible({ action: 'signup' });
Expand Down Expand Up @@ -426,8 +431,14 @@ export class SignUp extends BaseResource implements SignUpResource {
};

update = (params: SignUpUpdateParams): Promise<SignUpResource> => {
const finalParams = { ...params };

if (__BUILD_VARIANT_CHIPS__ && inIframe()) {
finalParams.sessionId = BaseResource.clerk.session?.id;
}

return this._basePatch({
body: normalizeUnsafeMetadata(params),
body: normalizeUnsafeMetadata(finalParams),
});
};

Expand Down
231 changes: 231 additions & 0 deletions packages/clerk-js/src/core/resources/__tests__/SignIn.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

Check failure on line 1 in packages/clerk-js/src/core/resources/__tests__/SignIn.spec.ts

View workflow job for this annotation

GitHub Actions / Static analysis

Run autofix to sort these imports!

import { inIframe } from '../../../utils/runtime';
import { SignIn } from '../SignIn';
import { BaseResource } from '../Base';

vi.mock('../../../utils/runtime', () => ({
inIframe: vi.fn(),
}));

const originalBuildVariant = (globalThis as any).__BUILD_VARIANT_CHIPS__;
(globalThis as any).__BUILD_VARIANT_CHIPS__ = true;

describe('SignIn', () => {
let signIn: SignIn;
let mockCreate: any;
let mockBuildUrlWithAuth: any;

beforeEach(() => {
vi.clearAllMocks();

const mockClerk = {
buildUrlWithAuth: vi.fn((url: string) => `https://clerk.example.com${url}`),
session: {
id: 'test-session-id',
},
};

const mockFapiClient = {
buildEmailAddress: vi.fn(() => '[email protected]'),
};

SignIn.clerk = mockClerk as any;

// Mock BaseResource.clerk for client ID access
BaseResource.clerk = mockClerk as any;

Object.defineProperty(SignIn, 'fapiClient', {
get: () => mockFapiClient,
configurable: true,
});

signIn = new SignIn({
id: 'test-signin-id',
status: 'needs_first_factor',
first_factor_verification: {
status: 'unverified',
external_verification_redirect_url: 'https://oauth.provider.com/auth',
},
} as any);

mockCreate = vi.fn().mockResolvedValue({});
signIn.create = mockCreate;

mockBuildUrlWithAuth = vi.fn((url: string) => `https://clerk.example.com${url}`);
SignIn.clerk.buildUrlWithAuth = mockBuildUrlWithAuth;
});

afterEach(() => {
(globalThis as any).__BUILD_VARIANT_CHIPS__ = originalBuildVariant;
});

describe('authenticateWithRedirectOrPopup', () => {
it('should set sessionId to true when CHIPS build and in iframe', async () => {
vi.mocked(inIframe).mockReturnValue(true);

const params = {
strategy: 'oauth_google' as const,
redirectUrl: '/callback',
identifier: '[email protected]',
};

const mockNavigate = vi.fn();

Object.defineProperty(signIn, 'firstFactorVerification', {
value: {
status: 'unverified',
externalVerificationRedirectURL: 'https://oauth.provider.com/auth',
},
writable: true,
});

await signIn.authenticateWithRedirectOrPopup(params, mockNavigate);

expect(mockCreate).toHaveBeenCalledWith({
strategy: 'oauth_google',
identifier: '[email protected]',
redirectUrl: 'https://clerk.example.com/callback',
actionCompleteRedirectUrl: undefined,
sessionId: 'test-session-id',
});
});

it('should not set sessionId when not in iframe', async () => {
vi.mocked(inIframe).mockReturnValue(false);

const params = {
strategy: 'oauth_google' as const,
redirectUrl: '/callback',
identifier: '[email protected]',
};

const mockNavigate = vi.fn();

Object.defineProperty(signIn, 'firstFactorVerification', {
value: {
status: 'unverified',
externalVerificationRedirectURL: 'https://oauth.provider.com/auth',
},
writable: true,
});

await signIn.authenticateWithRedirectOrPopup(params, mockNavigate);

expect(mockCreate).toHaveBeenCalledWith({
strategy: 'oauth_google',
identifier: '[email protected]',
redirectUrl: 'https://clerk.example.com/callback',
actionCompleteRedirectUrl: undefined,
});
expect(mockCreate).toHaveBeenCalledWith(expect.not.objectContaining({ sessionId: 'test-session-id' }));
});

it('should not set sessionId when not CHIPS build', async () => {
(globalThis as any).__BUILD_VARIANT_CHIPS__ = false;

vi.mocked(inIframe).mockReturnValue(true);

const params = {
strategy: 'oauth_google' as const,
redirectUrl: '/callback',
identifier: '[email protected]',
};

const mockNavigate = vi.fn();

Object.defineProperty(signIn, 'firstFactorVerification', {
value: {
status: 'unverified',
externalVerificationRedirectURL: 'https://oauth.provider.com/auth',
},
writable: true,
});

await signIn.authenticateWithRedirectOrPopup(params, mockNavigate);

expect(mockCreate).toHaveBeenCalledWith(expect.not.objectContaining({ sessionId: 'test-session-id' }));
});

it('should not set sessionId when continueSignIn is true', async () => {
vi.mocked(inIframe).mockReturnValue(true);

const params = {
strategy: 'oauth_google' as const,
redirectUrl: '/callback',
identifier: '[email protected]',
continueSignIn: true,
};

const mockNavigate = vi.fn();

Object.defineProperty(signIn, 'firstFactorVerification', {
value: {
status: 'unverified',
externalVerificationRedirectURL: 'https://oauth.provider.com/auth',
},
writable: true,
});

await signIn.authenticateWithRedirectOrPopup(params, mockNavigate);

expect(mockCreate).not.toHaveBeenCalled();
});
});

describe('SignInFuture.create', () => {
it('should set sessionId to true when CHIPS build and in iframe', async () => {
(globalThis as any).__BUILD_VARIANT_CHIPS__ = true;
vi.mocked(inIframe).mockReturnValue(true);

const params = {
strategy: 'oauth_google' as const,
redirectUrl: '/callback',
identifier: '[email protected]',
};

const signInFuture = signIn.__internal_future;

const mockBasePost = vi.fn().mockResolvedValue({});
signInFuture.resource.__internal_basePost = mockBasePost;

await signInFuture.create(params);

expect(mockBasePost).toHaveBeenCalledWith({
path: '/client/sign_ins',
body: {
strategy: 'oauth_google',
redirectUrl: '/callback',
identifier: '[email protected]',
sessionId: 'test-session-id',
},
});
});

it('should not set sessionId when not in iframe', async () => {
vi.mocked(inIframe).mockReturnValue(false);

const params = {
strategy: 'oauth_google' as const,
redirectUrl: '/callback',
identifier: '[email protected]',
};

const signInFuture = signIn.__internal_future;

const mockBasePost = vi.fn().mockResolvedValue({});
signInFuture.resource.__internal_basePost = mockBasePost;

await signInFuture.create(params);

expect(mockBasePost).toHaveBeenCalledWith({
path: '/client/sign_ins',
body: {
strategy: 'oauth_google',
redirectUrl: '/callback',
identifier: '[email protected]',
},
});
});
});
});
Loading
Loading