diff --git a/.changeset/wise-hornets-sneeze.md b/.changeset/wise-hornets-sneeze.md new file mode 100644 index 00000000000..ad34fd75d71 --- /dev/null +++ b/.changeset/wise-hornets-sneeze.md @@ -0,0 +1,6 @@ +--- +'@clerk/clerk-js': minor +'@clerk/types': minor +--- + +[Experimental] Signals reset password flow diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index ab2cdca30fb..da405fd6a95 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -1,7 +1,7 @@ { "files": [ { "path": "./dist/clerk.js", "maxSize": "622KB" }, - { "path": "./dist/clerk.browser.js", "maxSize": "75KB" }, + { "path": "./dist/clerk.browser.js", "maxSize": "76KB" }, { "path": "./dist/clerk.legacy.browser.js", "maxSize": "117KB" }, { "path": "./dist/clerk.headless*.js", "maxSize": "58KB" }, { "path": "./dist/ui-common*.js", "maxSize": "113KB" }, diff --git a/packages/clerk-js/src/core/resources/SignIn.ts b/packages/clerk-js/src/core/resources/SignIn.ts index e02ae12614a..3c7623a66c5 100644 --- a/packages/clerk-js/src/core/resources/SignIn.ts +++ b/packages/clerk-js/src/core/resources/SignIn.ts @@ -497,12 +497,82 @@ class SignInFuture implements SignInFutureResource { verifyCode: this.verifyEmailCode.bind(this), }; + resetPasswordEmailCode = { + sendCode: this.sendResetPasswordEmailCode.bind(this), + verifyCode: this.verifyResetPasswordEmailCode.bind(this), + submitPassword: this.submitResetPassword.bind(this), + }; + constructor(readonly resource: SignIn) {} get status() { return this.resource.status; } + async sendResetPasswordEmailCode(): Promise<{ error: unknown }> { + eventBus.emit('resource:error', { resource: this.resource, error: null }); + try { + if (!this.resource.id) { + throw new Error('Cannot reset password without a sign in.'); + } + + const resetPasswordEmailCodeFactor = this.resource.supportedFirstFactors?.find( + f => f.strategy === 'reset_password_email_code', + ); + + if (!resetPasswordEmailCodeFactor) { + throw new Error('Reset password email code factor not found'); + } + + const { emailAddressId } = resetPasswordEmailCodeFactor; + await this.resource.__internal_basePost({ + body: { emailAddressId, strategy: 'reset_password_email_code' }, + action: 'prepare_first_factor', + }); + } catch (err: unknown) { + eventBus.emit('resource:error', { resource: this.resource, error: err }); + return { error: err }; + } + + return { error: null }; + } + + async verifyResetPasswordEmailCode({ code }: { code: string }): Promise<{ error: unknown }> { + eventBus.emit('resource:error', { resource: this.resource, error: null }); + try { + await this.resource.__internal_basePost({ + body: { code, strategy: 'reset_password_email_code' }, + action: 'attempt_first_factor', + }); + } catch (err: unknown) { + eventBus.emit('resource:error', { resource: this.resource, error: err }); + return { error: err }; + } + + return { error: null }; + } + + async submitResetPassword({ + password, + signOutOfOtherSessions = true, + }: { + password: string; + signOutOfOtherSessions?: boolean; + }): Promise<{ error: unknown }> { + eventBus.emit('resource:error', { resource: this.resource, error: null }); + try { + await this.resource.__internal_basePost({ + body: { password, signOutOfOtherSessions }, + action: 'reset_password', + }); + } catch (err: unknown) { + eventBus.emit('resource:error', { resource: this.resource, error: err }); + return { error: err }; + } + + return { error: null }; + } + async create(params: { identifier?: string; strategy?: OAuthStrategy | 'saml' | 'enterprise_sso'; diff --git a/packages/types/src/signIn.ts b/packages/types/src/signIn.ts index 43d7a5fc5e4..5948c880da0 100644 --- a/packages/types/src/signIn.ts +++ b/packages/types/src/signIn.ts @@ -133,6 +133,11 @@ export interface SignInFutureResource { sendCode: (params: { email: string }) => Promise<{ error: unknown }>; verifyCode: (params: { code: string }) => Promise<{ error: unknown }>; }; + resetPasswordEmailCode: { + sendCode: () => Promise<{ error: unknown }>; + verifyCode: (params: { code: string }) => Promise<{ error: unknown }>; + submitPassword: (params: { password: string; signOutOfOtherSessions?: boolean }) => Promise<{ error: unknown }>; + }; sso: (params: { flow?: 'auto' | 'modal'; strategy: OAuthStrategy | 'saml' | 'enterprise_sso';