Skip to content

Commit

Permalink
fix(DEV-878): handle disabled state for no first factors
Browse files Browse the repository at this point in the history
  • Loading branch information
isanchez-userfront committed Jan 10, 2024
1 parent b92235a commit 8620922
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 113 deletions.
56 changes: 2 additions & 54 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 11 additions & 3 deletions package/src/forms/UniversalForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,11 @@ const strings = {
},
},
general: {
disabled: "Authentication is disabled",
redirecting: "Redirecting...",
unhandledError: "Oops, something went wrong",
verified: "Verified",
welcome: "Welcome",
unhandledError: "Oops, something went wrong",
},
};

Expand Down Expand Up @@ -186,6 +187,14 @@ const componentForStep = (state) => {
case "beginFlow":
case "showPreviewAndFetchFlow":
case "showPlaceholderAndFetchFlow":
case "disabled":
return {
title: strings.general.disabled,
Component: GeneralErrorMessage,
props: {
message: "Please contact an administrator for assistance",
},
};
case "initPasswordReset":
if (canShowFlow) {
return {
Expand All @@ -206,7 +215,6 @@ const componentForStep = (state) => {
Component: Placeholder,
};
}

case "selectFirstFactor.showForm":
return {
title: strings[type].title,
Expand Down Expand Up @@ -491,7 +499,7 @@ const componentForStep = (state) => {
case "missingFlowInDevModeError":
case "missingFlowInLocalModeError":
case "missingFlowFromServerError":
case "UnhandledError":
case "unhandledError":
return {
title: strings.general.unhandledError,
Component: GeneralErrorMessage,
Expand Down
26 changes: 13 additions & 13 deletions package/src/models/config/guards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ import { getUserfrontPropertySync } from "../../services/userfront";

// GUARDS / PREDICATES

const ssoStrategies = [
"apple",
"azure",
"google",
"github",
"twitter",
"facebook",
"linkedin",
"okta",
];

// Is this factor an SSO provider?
export const isSsoProvider = (factor: Factor) => {
return (
factor.channel === "email" &&
(factor.strategy === "apple" ||
factor.strategy === "azure" ||
factor.strategy === "google" ||
factor.strategy === "github" ||
factor.strategy === "twitter" ||
factor.strategy === "facebook" ||
factor.strategy === "linkedin" ||
factor.strategy === "okta")
);
};
export const isSsoProvider = (factor: Factor) =>
factor.channel === "email" && ssoStrategies.includes(factor.strategy);

// Is a second factor required to complete the signup or login?
export const secondFactorRequired = (
Expand Down
104 changes: 65 additions & 39 deletions package/src/models/forms/universal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,22 @@ import emailCodeConfig from "../views/emailCode";
import emailLinkConfig from "../views/emailLink";
import setNewPasswordConfig from "../views/setNewPassword";
import {
isSsoProvider,
hasLinkQueryParams,
hasNoActiveFactor,
isLocalMode,
isLocalModeWithoutFlow,
isLoggedIn,
isLoggedInOrHasLinkCredentials,
isMissingFlow,
isMissingFlowFromServer,
isLocalModeWithoutFlow,
isMissingTenantId,
isPasswordReset,
isSecondFactor,
isSetup,
isSsoProvider,
passwordsMatch,
hasLinkQueryParams,
secondFactorRequired,
secondFactorRequiredFromView,
isLoggedIn,
isSecondFactor,
isPasswordReset,
isLoggedInOrHasLinkCredentials,
isSetup,
} from "../config/guards";
import {
setActiveFactor,
Expand Down Expand Up @@ -92,7 +92,12 @@ export const defaultOptions = {
// Predicates for first factors:
// Does the flow have multiple first factors?
hasMultipleFirstFactors: (context: AuthContext<any>, event: any) => {
return (context.config.flow?.firstFactors?.length ?? 0) > 1;
const firstFactors = context.config.flow?.firstFactors ?? [];
return firstFactors.length > 1;
},
hasNoFirstFactors: (context: AuthContext<any>) => {
const firstFactors = context.config.flow?.firstFactors ?? [];
return firstFactors.length === 0;
},
// Is the flow only one factor? One predicate per factor type.
// If there's only one allowed factor, allows us to skip the select screen
Expand Down Expand Up @@ -186,54 +191,54 @@ export const defaultOptions = {
return isSsoProvider(event.factor);
},

hasLinkQueryParams,
hasNoActiveFactor,
isLocalMode,
isLocalModeWithoutFlow,
isLoggedIn,
isLoggedInOrHasLinkCredentials,
isMissingFlow,
isMissingFlowFromServer,
isLocalModeWithoutFlow,
isMissingTenantId,
isPasswordReset,
isSecondFactor,
isSetup,
passwordsMatch,
hasLinkQueryParams,
secondFactorRequired,
secondFactorRequiredFromView,
isLoggedIn,
isSecondFactor,
isPasswordReset,
isLoggedInOrHasLinkCredentials,
isSetup,
},
actions: {
setActiveFactor,
clearError,
clearResentMessage,
disableBack,
enableBack,
markAsSecondFactor,
markQueryParamsInvalid,
readQueryParams,
redirectIfLoggedIn,
redirectOnLoad,
resumeIfNeeded,
setFlowFromUserfrontApi,
setActiveFactor,
setAllowedSecondFactors,
setAllowedSecondFactorsFromView,
setCode,
setEmail,
setErrorForPasswordMismatch,
setErrorFromApiError,
setFirstFactorAction,
setFlowFromUserfrontApi,
setPassword,
setPasswordForReset,
setPhoneNumber,
setQrCode,
setResentMessage,
setSecondFactorAction,
setShowEmailOrUsernameIfFirstFactor,
setTenantIdIfPresent,
setTotpCode,
setupView,
setUseBackupCode,
setQrCode,
redirectIfLoggedIn,
redirectOnLoad,
setCode,
setErrorFromApiError,
clearError,
setErrorForPasswordMismatch,
setShowEmailOrUsernameIfFirstFactor,
markAsSecondFactor,
setAllowedSecondFactors,
setAllowedSecondFactorsFromView,
storeFactorResponse,
disableBack,
enableBack,
setupView,
readQueryParams,
markQueryParamsInvalid,
setResentMessage,
clearResentMessage,
setFirstFactorAction,
setSecondFactorAction,
setPasswordForReset,
},
};

Expand Down Expand Up @@ -520,6 +525,11 @@ const universalMachineConfig: AuthMachineConfig = {
"setupView",
],
always: [
// Handle no first factors
{
target: "noFirstFactors",
cond: "hasNoFirstFactors",
},
// If we're returning from a passwordless/email link or SSO first factor, attempt to use
// the query params to proceed.
{
Expand Down Expand Up @@ -584,6 +594,22 @@ const universalMachineConfig: AuthMachineConfig = {
],
},

// Show the disabled view if there are no first factors
noFirstFactors: {
id: "noFirstFactors",
always: [
{
target: "disabled",
},
],
},

// Show the disabled error view
disabled: {
id: "disabled",
type: "final",
},

// Show the first factor selection view, non-compact
// Parallel states for the Password view and the rest of the form
selectFirstFactor: selectFactorConfig,
Expand Down
9 changes: 6 additions & 3 deletions package/src/views/GeneralErrorMessage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ import ErrorMessage from "../components/ErrorMessage";
* @param {object} props
* @param {object} error - a Userfront error to display
*/
const GeneralErrorMessage = ({ error }) => {
const GeneralErrorMessage = ({
error,
message = "An unhandled error occurred. Please try again later.",
}) => {
return (
<div>
<p>An unhandled error occurred. Please try again later.</p>
<ErrorMessage error={error} />
{message && <p>{message}</p>}
{error && <ErrorMessage error={error} />}
</div>
);
};
Expand Down
2 changes: 1 addition & 1 deletion site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"dependencies": {
"@xstate/inspect": "^0.7.0",
"@xstate/react": "^3.0.1",
"@userfront/toolkit": "^1.0.0-alpha.17",
"@userfront/toolkit": "file:../package",
"lodash.get": "^4.4.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down

0 comments on commit 8620922

Please sign in to comment.