Skip to content
This repository was archived by the owner on Mar 19, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
58 changes: 3 additions & 55 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions package/package-lock.json

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

2 changes: 1 addition & 1 deletion package/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@userfront/toolkit",
"version": "1.0.6-alpha.0",
"version": "1.0.6-alpha.1",
"description": "Bindings and components for authentication with Userfront with React, Vue, other frameworks, and plain JS + HTML",
"type": "module",
"directories": {
Expand Down
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 @@ -206,7 +207,14 @@ const componentForStep = (state) => {
Component: Placeholder,
};
}

case "disabled":
return {
title: strings.general.disabled,
Component: GeneralErrorMessage,
props: {
message: "Please contact an administrator for assistance",
},
};
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 @@ -526,6 +531,11 @@ const universalMachineConfig: AuthMachineConfig = {
target: "handleLoginWithLink",
cond: "hasLinkQueryParams",
},
// Handle no first factors
{
target: "noFirstFactors",
cond: "hasNoFirstFactors",
},
// If there are multiple first factors, then show the factor selection view
{
actions: "enableBack",
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