Skip to content

Commit

Permalink
feat: support identifier first auth (#187)
Browse files Browse the repository at this point in the history
  • Loading branch information
aeneasr authored Jun 25, 2024
1 parent a93569f commit e198fe5
Show file tree
Hide file tree
Showing 21 changed files with 387 additions and 228 deletions.
4 changes: 2 additions & 2 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"identities.messages.1010005": "Verify",
"identities.messages.1010006": "Authentication code",
"identities.messages.1010007": "Backup recovery code",
"identities.messages.1010008": "Use security key",
"identities.messages.1010008": "Sign in with hardware key",
"identities.messages.1010009": "Use Authenticator",
"identities.messages.1010010": "Use backup recovery code",
"identities.messages.1010011": "Continue with security key",
Expand Down Expand Up @@ -131,7 +131,7 @@
"identities.messages.4070006": "The verification code is invalid or has already been used. Please try again.",
"identities.messages.5000001": "{reason}",
"login.forgot-password": "Forgot password?",
"login.logged-in-as-label": "You're logged in as:",
"login.logged-in-as-label": "You are using:",
"login.logout-button": "Logout",
"login.logout-label": "Something's not working?",
"login.registration-button": "Sign up",
Expand Down
68 changes: 36 additions & 32 deletions src/react-components/ory/helpers/user-auth-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,9 @@ export const UserAuthForm = ({
formFilterOverride,
className,
...props
}: UserAuthFormProps): JSX.Element => (
<form
className={cn(className, formStyle)}
action={flow.ui.action}
method={flow.ui.method}
onKeyDown={(e) => {
if (e.key === "Enter" && !submitOnEnter) {
e.stopPropagation()
e.preventDefault()
}
}}
{...(onSubmit && {
onSubmit: (event: React.FormEvent<HTMLFormElement>) => {
}: UserAuthFormProps): JSX.Element => {
const submitHandler = onSubmit
? (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()

const form = event.currentTarget
Expand All @@ -108,23 +98,37 @@ export const UserAuthForm = ({
}

onSubmit({ body, event })
},
})}
{...props}
>
<>
{/*always add csrf token and other hidden fields to form*/}
<FilterFlowNodes
filter={
formFilterOverride ?? {
nodes: flow.ui.nodes,
groups: "default", // we only want to map hidden default fields here
attributes: "hidden",
}
}
: undefined

return (
<form
className={cn(className, formStyle)}
action={flow.ui.action}
method={flow.ui.method}
onKeyDown={(e) => {
if (e.key === "Enter" && !submitOnEnter) {
e.stopPropagation()
e.preventDefault()
}
includeCSRF={true}
/>
{children}
</>
</form>
)
}}
onSubmit={submitHandler}
{...props}
>
<>
{/*always add csrf token and other hidden fields to form*/}
<FilterFlowNodes
filter={
formFilterOverride ?? {
nodes: flow.ui.nodes,
groups: ["default"], // we only want to map hidden default fields here
attributes: "hidden",
}
}
includeCSRF={true}
/>
{children}
</>
</form>
)
}
2 changes: 2 additions & 0 deletions src/react-components/ory/helpers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ export const hasGroup = (group: string) => (nodes: UiNode[]) =>

export const hasOidc = hasGroup("oidc")
export const hasPassword = hasGroup("password")
export const hasDefault = hasGroup("default")
export const hasProfile = hasGroup("profile")
export const hasWebauthn = hasGroup("webauthn")
export const hasPasskey = hasGroup("passkey")
export const hasIdentifierFirst = hasGroup("identifier_first")
export const hasLookupSecret = hasGroup("lookup_secret")
export const hasTotp = hasGroup("totp")
export const hasCode = hasGroup("code")
Expand Down
7 changes: 4 additions & 3 deletions src/react-components/ory/sections/auth-code-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const AuthCodeSection = ({
<FilterFlowNodes
filter={{
nodes: nodes,
groups: ["code"],
groups: ["code", "identifier_first"],
// we don't want to map the default group twice
// the form already maps hidden fields under the default group
// we are only interested in hidden fields that are under the code group
Expand All @@ -36,9 +36,9 @@ export const AuthCodeSection = ({
<FilterFlowNodes
filter={{
nodes: nodes,
groups: "code",
groups: ["code"],
withoutDefaultAttributes: true,
excludeAttributes: ["hidden", "button", "submit"], // the form will take care of default (csrf) hidden fields
excludeAttributeTypes: ["hidden", "button", "submit"], // the form will take care of default (csrf) hidden fields
}}
/>
{/* include hidden here because we want to have resend support */}
Expand All @@ -49,6 +49,7 @@ export const AuthCodeSection = ({
groups: "code",
withoutDefaultAttributes: true,
attributes: ["button", "submit"],
excludeAttributeTypes: ["hidden"],
}}
/>
</div>
Expand Down
32 changes: 21 additions & 11 deletions src/react-components/ory/sections/link-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,32 @@ export interface LinkSectionProps {
* - https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation
*/
export const LinkSection = ({ nodes }: LinkSectionProps): JSX.Element => (
<div className={gridStyle({ gap: 32 })}>
<div className={gridStyle({ gap: 16 })}>
<>
<FilterFlowNodes
filter={{
nodes: nodes,
groups: ["link", "code", "identifier_first"],
attributes: ["hidden"],
}}
/>
<div className={gridStyle({ gap: 32 })}>
<div className={gridStyle({ gap: 16 })}>
<FilterFlowNodes
filter={{
nodes: nodes,
groups: ["link", "code"],
excludeAttributeTypes: ["submit", "hidden"],
}}
/>
</div>
<FilterFlowNodes
filter={{
nodes: nodes,
groups: ["link", "code"],
excludeAttributes: "submit",
attributes: "submit",
excludeAttributeTypes: ["hidden"],
}}
/>
</div>
<FilterFlowNodes
filter={{
nodes: nodes,
groups: ["link", "code"],
attributes: "submit",
}}
/>
</div>
</>
)
2 changes: 1 addition & 1 deletion src/react-components/ory/sections/logged-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const LoggedInInfo = ({ flow }: IdentifierInfoProps) => {
<div className={identifierStyle}>
<FormattedMessage
id="login.logged-in-as-label"
defaultMessage="You're logged in as:"
defaultMessage="You are using:"
/>
<div className={identifierNameStyle}>{identifier.value}</div>
</div>
Expand Down
83 changes: 59 additions & 24 deletions src/react-components/ory/sections/login-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,81 @@ import { FormattedMessage } from "react-intl"
import { gridStyle } from "../../../theme"
import { ButtonLink, CustomHref } from "../../button-link"
import { FilterFlowNodes } from "../helpers/filter-flow-nodes"
import { hasPassword } from "../helpers/utils"
import { hasPassword, hasIdentifierFirst } from "../helpers/utils"
import { SelfServiceFlow } from "../helpers/types"

export interface LoginSectionProps {
nodes: UiNode[]
forgotPasswordURL?: CustomHref | string
}

export const IdentifierFirstLoginSection = (
flow: SelfServiceFlow,
): JSX.Element | null => {
const nodes = flow.ui.nodes
return hasIdentifierFirst(nodes) ? (
<div className={gridStyle({ gap: 32 })}>
<FilterFlowNodes
filter={{
nodes: nodes,
groups: ["default", "identifier_first"],
excludeAttributeTypes: ["submit", "hidden"],
}}
/>
<FilterFlowNodes
filter={{
nodes: nodes,
groups: ["identifier_first"],
attributes: "submit",
}}
/>
</div>
) : null
}

export const LoginSection = ({
nodes,
forgotPasswordURL,
}: LoginSectionProps): JSX.Element | null => {
return hasPassword(nodes) ? (
<div className={gridStyle({ gap: 32 })}>
<div className={gridStyle({ gap: 16 })}>
<>
<FilterFlowNodes
filter={{
nodes: nodes,
groups: ["link", "code", "identifier_first"],
attributes: ["hidden"],
}}
/>
<div className={gridStyle({ gap: 32 })}>
<div className={gridStyle({ gap: 16 })}>
<FilterFlowNodes
filter={{
nodes: nodes,
groups: ["default", "password"],
excludeAttributeTypes: ["submit", "hidden"],
}}
/>
{forgotPasswordURL && (
<ButtonLink
data-testid="forgot-password-link"
href={forgotPasswordURL}
>
<FormattedMessage
id="login.forgot-password"
defaultMessage="Forgot password?"
/>
</ButtonLink>
)}
</div>
<FilterFlowNodes
filter={{
nodes: nodes,
groups: ["default", "password"],
excludeAttributes: ["submit", "hidden"],
groups: ["password"],
attributes: "submit",
excludeAttributeTypes: ["hidden"],
}}
/>
{forgotPasswordURL && (
<ButtonLink
data-testid="forgot-password-link"
href={forgotPasswordURL}
>
<FormattedMessage
id="login.forgot-password"
defaultMessage="Forgot password?"
/>
</ButtonLink>
)}
</div>
<FilterFlowNodes
filter={{
nodes: nodes,
groups: ["password"],
attributes: "submit",
}}
/>
</div>
</>
) : null
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const LookupSecretSettingsSection = ({
return hasLookupSecret(flow.ui.nodes) ? (
<div className={gridStyle({ gap: 32 })}>
<FilterFlowNodes
filter={{ ...filter, excludeAttributes: "submit,button" }}
filter={{ ...filter, excludeAttributeTypes: "submit,button" }}
/>
<FilterFlowNodes
filter={{ ...filter, attributes: "submit,button" }}
Expand Down
5 changes: 3 additions & 2 deletions src/react-components/ory/sections/oidc-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const OIDCSection = (flow: SelfServiceFlow): JSX.Element | null => {
nodes: flow.ui.nodes,
groups: "oidc",
withoutDefaultGroup: true,
excludeAttributes: "submit",
excludeAttributeTypes: "submit",
}).length > 0

return hasOidc(flow.ui.nodes) ? (
Expand All @@ -24,7 +24,7 @@ export const OIDCSection = (flow: SelfServiceFlow): JSX.Element | null => {
nodes: flow.ui.nodes,
groups: "oidc",
withoutDefaultGroup: true,
excludeAttributes: "submit",
excludeAttributeTypes: ["submit"],
}}
/>
</div>
Expand All @@ -35,6 +35,7 @@ export const OIDCSection = (flow: SelfServiceFlow): JSX.Element | null => {
nodes: flow.ui.nodes,
groups: "oidc",
attributes: "submit",
withoutDefaultGroup: true,
}}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@ export const PasskeySettingsSection = ({
return hasPasskey(flow.ui.nodes) ? (
<div className={gridStyle({ gap: 32 })}>
<FilterFlowNodes
filter={{ ...filter, excludeAttributes: "onclick,button" }}
filter={{ ...filter, excludeAttributeTypes: "onclick,button" }}
/>

<FilterFlowNodes
filter={{ ...filter, attributes: "onclick,button" }}
buttonOverrideProps={{ fullWidth: false }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const PasswordSettingsSection = ({
return hasPassword(flow.ui.nodes) ? (
<div className={gridStyle({ gap: 32 })}>
<FilterFlowNodes
filter={{ ...filter, excludeAttributes: "submit,button" }}
filter={{ ...filter, excludeAttributeTypes: "submit,button" }}
/>
<FilterFlowNodes
filter={{ ...filter, attributes: "submit,button" }}
Expand Down
Loading

0 comments on commit e198fe5

Please sign in to comment.