Skip to content

Commit

Permalink
Merge branch 'main' into fix/astro-e2e
Browse files Browse the repository at this point in the history
  • Loading branch information
jacekradko authored Nov 20, 2024
2 parents 59ad410 + 6b09617 commit 5d04db7
Show file tree
Hide file tree
Showing 14 changed files with 189 additions and 107 deletions.
5 changes: 5 additions & 0 deletions .changeset/silent-bears-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/shared': minor
---

Change `useReverification` to handle error in a callback, but still allow an error to be thrown via options.
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,7 @@ export const AddAuthenticatorApp = withCardStateProvider((props: AddAuthenticato
const { title, onSuccess, onReset } = props;
const { user } = useUser();
const card = useCardState();
const [createTOTP] = useReverification(() => {
if (!user) {
return Promise.resolve(undefined);
}
return user.createTOTP();
});
const [createTOTP] = useReverification(() => user?.createTOTP());
const { close } = useActionContext();
const [totp, setTOTP] = React.useState<TOTPResource | undefined>(undefined);
const [displayFormat, setDisplayFormat] = React.useState<DisplayFormat>('qr');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,13 @@ const ConnectMenuButton = (props: { strategy: OAuthStrategy }) => {
const isModal = mode === 'modal';

const [createExternalAccount] = useReverification(() => {
if (!user) {
return Promise.resolve(undefined);
}

const socialProvider = strategy.replace('oauth_', '') as OAuthProvider;
const redirectUrl = isModal
? appendModalState({ url: window.location.href, componentName, socialProvider: socialProvider })
: window.location.href;
const additionalScopes = additionalOAuthScopes ? additionalOAuthScopes[socialProvider] : [];

return user.createExternalAccount({
return user?.createExternalAccount({
strategy,
redirectUrl,
additionalScopes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,13 @@ const ConnectedAccount = ({ account }: { account: ExternalAccountResource }) =>
})
: window.location.href;

const [createExternalAccount] = useReverification(() => {
if (!user) {
return Promise.resolve(undefined);
}

return user.createExternalAccount({
const [createExternalAccount] = useReverification(() =>
user?.createExternalAccount({
strategy: account.verification!.strategy as OAuthStrategy,
redirectUrl,
additionalScopes,
});
});
}),
);

if (!user) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,7 @@ export const DeleteUserForm = withCardStateProvider((props: DeleteUserFormProps)
const { t } = useLocalizations();
const { otherSessions } = useMultipleSessions({ user });
const { setActive } = useClerk();
const [deleteUserWithReverification] = useReverification(() => {
if (!user) {
return Promise.resolve(undefined);
}

return user.delete();
});
const [deleteUserWithReverification] = useReverification(() => user?.delete());

const confirmationField = useFormControl('deleteConfirmation', '', {
type: 'text',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,7 @@ export const EmailForm = withCardStateProvider((props: EmailFormProps) => {
const environment = useEnvironment();
const preferEmailLinks = emailLinksEnabledForInstance(environment);

const [createEmailAddress] = useReverification(() => {
if (!user) {
return Promise.resolve(undefined);
}
return user.createEmailAddress({ email: emailField.value });
});
const [createEmailAddress] = useReverification(() => user?.createEmailAddress({ email: emailField.value }));

const emailAddressRef = React.useRef<EmailAddressResource | undefined>(user?.emailAddresses.find(a => a.id === id));
const wizard = useWizard({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,7 @@ export const MfaBackupCodeCreateForm = withCardStateProvider((props: MfaBackupCo
const { onSuccess } = props;
const { user } = useUser();
const card = useCardState();
const [createBackupCode] = useReverification(() => {
if (!user) {
return Promise.resolve(undefined);
}
return user.createBackupCode();
});
const [createBackupCode] = useReverification(() => user?.createBackupCode());
const [backupCode, setBackupCode] = React.useState<BackupCodeResource | undefined>(undefined);

React.useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,7 @@ const AddPasskeyButton = () => {
const card = useCardState();
const { isSatellite } = useClerk();
const { user } = useUser();
const [createPasskey] = useReverification(() => {
if (!user) {
return Promise.resolve(undefined);
}
return user.createPasskey();
});
const [createPasskey] = useReverification(() => user?.createPasskey());

const handleCreatePasskey = async () => {
if (!user) {
Expand Down
25 changes: 11 additions & 14 deletions packages/clerk-js/src/ui/components/UserProfile/PasswordForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { __experimental_useReverification as useReverification, useSession, useUser } from '@clerk/shared/react';
import type { UserResource } from '@clerk/types';
import { useRef } from 'react';

import { useEnvironment } from '../../contexts';
Expand Down Expand Up @@ -36,19 +37,9 @@ type PasswordFormProps = FormProps;
export const PasswordForm = withCardStateProvider((props: PasswordFormProps) => {
const { onSuccess, onReset } = props;
const { user } = useUser();
const [updatePasswordWithReverification] = useReverification(() => {
if (!user) {
return Promise.resolve(undefined);
}

const opts = {
newPassword: passwordField.value,
signOutOfOtherSessions: sessionsField.checked,
currentPassword: user.passwordEnabled ? currentPasswordField.value : undefined,
} satisfies Parameters<typeof user.updatePassword>[0];

return user.updatePassword(opts);
});
const [updatePasswordWithReverification] = useReverification(
(user: UserResource, opts: Parameters<UserResource['updatePassword']>) => user.updatePassword(...opts),
);

if (!user) {
return null;
Expand Down Expand Up @@ -124,7 +115,13 @@ export const PasswordForm = withCardStateProvider((props: PasswordFormProps) =>
text: generateSuccessPageText(user.passwordEnabled, !!sessionsField.checked),
};

await updatePasswordWithReverification();
const opts = {
newPassword: passwordField.value,
signOutOfOtherSessions: sessionsField.checked,
currentPassword: user.passwordEnabled ? currentPasswordField.value : undefined,
} satisfies Parameters<typeof user.updatePassword>[0];

await updatePasswordWithReverification(user, [opts]);
onSuccess();
} catch (e) {
handleError(e, [currentPasswordField, passwordField, confirmField], card.setError);
Expand Down
13 changes: 5 additions & 8 deletions packages/clerk-js/src/ui/components/UserProfile/PhoneForm.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { __experimental_useReverification as useReverification, useUser } from '@clerk/shared/react';
import type { PhoneNumberResource } from '@clerk/types';
import type { PhoneNumberResource, UserResource } from '@clerk/types';
import React from 'react';

import { useWizard, Wizard } from '../../common';
Expand Down Expand Up @@ -49,12 +49,9 @@ export const AddPhone = (props: AddPhoneProps) => {
const { title, onSuccess, onReset, onUseExistingNumberClick, resourceRef } = props;
const card = useCardState();
const { user } = useUser();
const [createPhoneNumber] = useReverification(() => {
if (!user) {
return Promise.resolve(undefined);
}
return user.createPhoneNumber({ phoneNumber: phoneField.value });
});
const [createPhoneNumber] = useReverification(
(user: UserResource, opt: Parameters<UserResource['createPhoneNumber']>[0]) => user.createPhoneNumber(opt),
);

const phoneField = useFormControl('phoneNumber', '', {
type: 'tel',
Expand All @@ -70,7 +67,7 @@ export const AddPhone = (props: AddPhoneProps) => {
if (!user) {
return;
}
return createPhoneNumber()
return createPhoneNumber(user, { phoneNumber: phoneField.value })
.then(res => {
resourceRef.current = res;
onSuccess();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,7 @@ export const UsernameForm = withCardStateProvider((props: UsernameFormProps) =>
const { onSuccess, onReset } = props;
const { user } = useUser();

const [updateUsername] = useReverification(() => {
if (!user) {
return Promise.resolve(undefined);
}
return user.update({ username: usernameField.value });
});
const [updateUsername] = useReverification(() => user?.update({ username: usernameField.value }));

const { userSettings } = useEnvironment();
const card = useCardState();
Expand Down
12 changes: 7 additions & 5 deletions packages/shared/src/authorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,11 @@ const validateReverificationConfig = (config: __experimental_ReverificationConfi
return config;
};

if (typeof config === 'string' && isValidVerificationType(config)) {
return convertConfigToObject.bind(null, config);
}
const isValidStringValue = typeof config === 'string' && isValidVerificationType(config);
const isValidObjectValue =
typeof config === 'object' && isValidLevel(config.level) && isValidMaxAge(config.afterMinutes);

if (typeof config === 'object' && isValidLevel(config.level) && isValidMaxAge(config.afterMinutes)) {
if (isValidStringValue || isValidObjectValue) {
return convertConfigToObject.bind(null, config);
}

Expand Down Expand Up @@ -145,7 +145,7 @@ const checkStepUpAuthorization: CheckStepUpAuthorization = (params, { __experime
* The returned function authorizes if both checks pass, or if at least one passes
* when the other is indeterminate. Fails if userId is missing.
*/
export const createCheckAuthorization = (options: AuthorizationOptions): CheckAuthorizationWithCustomPermissions => {
const createCheckAuthorization = (options: AuthorizationOptions): CheckAuthorizationWithCustomPermissions => {
return (params): boolean => {
if (!options.userId) {
return false;
Expand All @@ -161,3 +161,5 @@ export const createCheckAuthorization = (options: AuthorizationOptions): CheckAu
return [orgAuthorization, stepUpAuthorization].every(a => a === true);
};
};

export { createCheckAuthorization, validateReverificationConfig };
61 changes: 61 additions & 0 deletions packages/shared/src/react/__tests__/useReverification.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { expectTypeOf } from 'expect-type';

import { __experimental_reverificationError } from '../../authorization-errors';
import type { __experimental_useReverification as useReverification } from '../hooks/useReverification';

type ExcludeClerkError<T> = T extends { clerk_error: any } ? never : T;

const fetcher = async (key: string, options: { id: string }) => {
return {
key,
options,
};
};

const fetcherWithHelper = async (key: string, options: { id: string }) => {
if (key == 'a') {
return __experimental_reverificationError();
}

return {
key,
options,
};
};

type Fetcher = typeof fetcherWithHelper;

describe('useReverification type tests', () => {
it('allow pass through types', () => {
type UseReverificationWithFetcher = typeof useReverification<typeof fetcher, object>;
type VerifiedFetcher = ReturnType<UseReverificationWithFetcher>[0];
expectTypeOf(fetcher).toEqualTypeOf<VerifiedFetcher>();
});

it('returned callback with clerk error excluded and possible null in case of cancelled flow', () => {
type UseReverificationWithFetcherHelper = typeof useReverification<typeof fetcherWithHelper, object>;
type VerifiedFetcherHelper = ReturnType<UseReverificationWithFetcherHelper>[0];

expectTypeOf(fetcherWithHelper).not.toEqualTypeOf<VerifiedFetcherHelper>();

expectTypeOf<Parameters<Fetcher>>().toEqualTypeOf<Parameters<VerifiedFetcherHelper>>();
expectTypeOf<ReturnType<Fetcher>>().not.toEqualTypeOf<ReturnType<VerifiedFetcherHelper>>();
expectTypeOf<ExcludeClerkError<Awaited<ReturnType<Fetcher>>> | null>().toEqualTypeOf<
Awaited<ReturnType<VerifiedFetcherHelper>>
>();
});

it('returned callback with clerk error excluded but without null since we throw', () => {
type UseReverificationWithFetcherHelperThrow = typeof useReverification<
typeof fetcherWithHelper,
{
throwOnCancel: true;
}
>;
type VerifiedFetcherHelperThrow = ReturnType<UseReverificationWithFetcherHelperThrow>[0];
expectTypeOf(fetcherWithHelper).not.toEqualTypeOf<VerifiedFetcherHelperThrow>();
expectTypeOf<ExcludeClerkError<Awaited<ReturnType<Fetcher>>>>().toEqualTypeOf<
Awaited<ReturnType<VerifiedFetcherHelperThrow>>
>();
});
});
Loading

0 comments on commit 5d04db7

Please sign in to comment.