@atproto/oauth-provider 0.1.0 → 0.1.2-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/account/account-manager.d.ts +2 -2
- package/dist/account/account-manager.d.ts.map +1 -1
- package/dist/account/account-manager.js.map +1 -1
- package/dist/account/account-store.d.ts +19 -6
- package/dist/account/account-store.d.ts.map +1 -1
- package/dist/account/account-store.js +16 -1
- package/dist/account/account-store.js.map +1 -1
- package/dist/assets/app/bundle-manifest.json +3 -3
- package/dist/assets/app/main.css +1 -1
- package/dist/assets/app/main.js +3 -3
- package/dist/assets/app/main.js.map +1 -1
- package/dist/client/client-auth.d.ts.map +1 -1
- package/dist/client/client-auth.js +2 -2
- package/dist/client/client-auth.js.map +1 -1
- package/dist/client/client-manager.d.ts.map +1 -1
- package/dist/client/client-manager.js +3 -1
- package/dist/client/client-manager.js.map +1 -1
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +3 -3
- package/dist/client/client.js.map +1 -1
- package/dist/dpop/dpop-manager.d.ts.map +1 -1
- package/dist/dpop/dpop-manager.js +3 -3
- package/dist/dpop/dpop-manager.js.map +1 -1
- package/dist/errors/invalid-token-error.d.ts.map +1 -1
- package/dist/errors/invalid-token-error.js +3 -2
- package/dist/errors/invalid-token-error.js.map +1 -1
- package/dist/errors/second-authentication-factor-required-error.d.ts +13 -0
- package/dist/errors/second-authentication-factor-required-error.d.ts.map +1 -0
- package/dist/errors/second-authentication-factor-required-error.js +23 -0
- package/dist/errors/second-authentication-factor-required-error.js.map +1 -0
- package/dist/lib/util/authorization-header.js +1 -1
- package/dist/lib/util/authorization-header.js.map +1 -1
- package/dist/metadata/build-metadata.d.ts.map +1 -1
- package/dist/metadata/build-metadata.js +2 -0
- package/dist/metadata/build-metadata.js.map +1 -1
- package/dist/oauth-errors.d.ts +1 -0
- package/dist/oauth-errors.d.ts.map +1 -1
- package/dist/oauth-errors.js +3 -1
- package/dist/oauth-errors.js.map +1 -1
- package/dist/oauth-provider.d.ts +101 -4
- package/dist/oauth-provider.d.ts.map +1 -1
- package/dist/oauth-provider.js +98 -110
- package/dist/oauth-provider.js.map +1 -1
- package/dist/output/build-authorize-data.d.ts +40 -0
- package/dist/output/build-authorize-data.d.ts.map +1 -0
- package/dist/output/build-authorize-data.js +22 -0
- package/dist/output/build-authorize-data.js.map +1 -0
- package/dist/output/build-error-payload.d.ts.map +1 -1
- package/dist/output/build-error-payload.js +4 -3
- package/dist/output/build-error-payload.js.map +1 -1
- package/dist/output/customization.d.ts +2 -12
- package/dist/output/customization.d.ts.map +1 -1
- package/dist/output/customization.js +59 -32
- package/dist/output/customization.js.map +1 -1
- package/dist/output/output-manager.d.ts +16 -0
- package/dist/output/output-manager.d.ts.map +1 -0
- package/dist/output/output-manager.js +69 -0
- package/dist/output/output-manager.js.map +1 -0
- package/dist/output/send-web-page.d.ts +1 -1
- package/dist/output/send-web-page.d.ts.map +1 -1
- package/dist/output/send-web-page.js +3 -2
- package/dist/output/send-web-page.js.map +1 -1
- package/package.json +7 -7
- package/src/account/account-manager.ts +2 -2
- package/src/account/account-store.ts +12 -6
- package/src/assets/app/components/accept-form.tsx +86 -83
- package/src/assets/app/components/account-picker.tsx +98 -79
- package/src/assets/app/components/button.tsx +34 -0
- package/src/assets/app/components/client-identifier.tsx +12 -13
- package/src/assets/app/components/fieldset.tsx +26 -0
- package/src/assets/app/components/form-card.tsx +47 -0
- package/src/assets/app/components/help-card.tsx +1 -1
- package/src/assets/app/components/icons/alert-icon.tsx +5 -0
- package/src/assets/app/components/icons/at-symbol-icon.tsx +5 -0
- package/src/assets/app/components/icons/caret-right-icon.tsx +5 -0
- package/src/assets/app/components/icons/lock-icon.tsx +5 -0
- package/src/assets/app/components/icons/token-icon.tsx +5 -0
- package/src/assets/app/components/icons/util.tsx +17 -0
- package/src/assets/app/components/info-card.tsx +45 -0
- package/src/assets/app/components/input-checkbox.tsx +47 -0
- package/src/assets/app/components/input-container.tsx +37 -0
- package/src/assets/app/components/input-layout.tsx +47 -0
- package/src/assets/app/components/input-text.tsx +69 -0
- package/src/assets/app/components/layout-title-page.tsx +33 -16
- package/src/assets/app/components/layout-welcome.tsx +30 -14
- package/src/assets/app/components/sign-in-form.tsx +214 -196
- package/src/assets/app/components/sign-up-account-form.tsx +101 -117
- package/src/assets/app/components/sign-up-disclaimer.tsx +1 -1
- package/src/assets/app/hooks/use-api.ts +2 -0
- package/src/assets/app/lib/api.ts +49 -14
- package/src/assets/app/lib/clsx.ts +6 -1
- package/src/assets/app/lib/util.ts +3 -0
- package/src/assets/app/main.css +2 -1
- package/src/assets/app/views/accept-view.tsx +4 -3
- package/src/assets/app/views/authorize-view.tsx +8 -4
- package/src/assets/app/views/error-view.tsx +24 -15
- package/src/assets/app/views/sign-in-view.tsx +5 -15
- package/src/assets/app/views/sign-up-view.tsx +3 -10
- package/src/assets/app/views/welcome-view.tsx +11 -18
- package/src/client/client-auth.ts +3 -2
- package/src/client/client-manager.ts +2 -1
- package/src/client/client.ts +3 -1
- package/src/dpop/dpop-manager.ts +3 -2
- package/src/errors/invalid-token-error.ts +3 -1
- package/src/errors/second-authentication-factor-required-error.ts +25 -0
- package/src/lib/util/authorization-header.ts +1 -1
- package/src/metadata/build-metadata.ts +3 -0
- package/src/oauth-errors.ts +1 -0
- package/src/oauth-provider.ts +110 -99
- package/src/output/{send-authorize-page.ts → build-authorize-data.ts} +3 -43
- package/src/output/build-error-payload.ts +3 -1
- package/src/output/customization.ts +67 -45
- package/src/output/output-manager.ts +87 -0
- package/src/output/send-web-page.ts +4 -3
- package/tailwind.config.js +14 -1
- package/src/assets/app/components/error-card.tsx +0 -41
- package/src/output/send-error-page.ts +0 -41
|
@@ -1,43 +1,60 @@
|
|
|
1
1
|
import { HTMLAttributes, ReactNode } from 'react'
|
|
2
2
|
import { clsx } from '../lib/clsx'
|
|
3
|
+
import { Override } from '../lib/util'
|
|
3
4
|
|
|
4
|
-
export type LayoutTitlePageProps =
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export type LayoutTitlePageProps = Override<
|
|
6
|
+
HTMLAttributes<HTMLDivElement>,
|
|
7
|
+
{
|
|
8
|
+
title?: ReactNode
|
|
9
|
+
subtitle?: ReactNode
|
|
10
|
+
}
|
|
11
|
+
>
|
|
8
12
|
|
|
9
13
|
export function LayoutTitlePage({
|
|
10
14
|
children,
|
|
11
15
|
title,
|
|
12
16
|
subtitle,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
className,
|
|
18
|
+
...props
|
|
19
|
+
}: LayoutTitlePageProps) {
|
|
16
20
|
return (
|
|
17
21
|
<div
|
|
18
|
-
{...
|
|
22
|
+
{...props}
|
|
19
23
|
className={clsx(
|
|
20
|
-
|
|
21
|
-
'flex
|
|
24
|
+
className,
|
|
25
|
+
'flex flex-col items-center',
|
|
26
|
+
'md:flex md:flex-row md:justify-stretch md:items-center',
|
|
27
|
+
'min-h-screen min-w-screen',
|
|
28
|
+
'bg-white text-slate-900',
|
|
29
|
+
'dark:bg-slate-900 dark:text-slate-100',
|
|
22
30
|
)}
|
|
23
31
|
>
|
|
24
|
-
<div
|
|
32
|
+
<div
|
|
33
|
+
className={clsx(
|
|
34
|
+
'px-6 pt-4',
|
|
35
|
+
'md:max-w-lg',
|
|
36
|
+
'md:grid md:content-center md:justify-items-end',
|
|
37
|
+
'md:self-stretch',
|
|
38
|
+
'md:w-1/2 md:max-w-fix md:p-4',
|
|
39
|
+
'md:text-right',
|
|
40
|
+
'md:dark:border-r md:dark:border-slate-700',
|
|
41
|
+
'md:bg-slate-100 md:dark:bg-slate-800',
|
|
42
|
+
)}
|
|
43
|
+
>
|
|
25
44
|
{title && (
|
|
26
|
-
<h1 className="text-
|
|
45
|
+
<h1 className="text-xl md:text-2xl lg:text-5xl md:mt-4 mb-4 font-semibold text-brand">
|
|
27
46
|
{title}
|
|
28
47
|
</h1>
|
|
29
48
|
)}
|
|
30
49
|
|
|
31
50
|
{subtitle && (
|
|
32
|
-
<p className="max-w-xs text-slate-500 dark:text-slate-500">
|
|
51
|
+
<p className="hidden md:block max-w-xs text-slate-500 dark:text-slate-500">
|
|
33
52
|
{subtitle}
|
|
34
53
|
</p>
|
|
35
54
|
)}
|
|
36
55
|
</div>
|
|
37
56
|
|
|
38
|
-
<div className="
|
|
39
|
-
{children}
|
|
40
|
-
</div>
|
|
57
|
+
<div className="w-full px-6 md:max-w-3xl md:px-12">{children}</div>
|
|
41
58
|
</div>
|
|
42
59
|
)
|
|
43
60
|
}
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HTMLAttributes } from 'react'
|
|
2
|
+
import { Override } from '../lib/util'
|
|
3
|
+
import { clsx } from '../lib/clsx'
|
|
2
4
|
|
|
3
|
-
export type LayoutWelcomeProps =
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
5
|
+
export type LayoutWelcomeProps = Override<
|
|
6
|
+
HTMLAttributes<HTMLDivElement>,
|
|
7
|
+
{
|
|
8
|
+
name?: string
|
|
9
|
+
logo?: string
|
|
10
|
+
links?: Array<{
|
|
11
|
+
title: string
|
|
12
|
+
href: string
|
|
13
|
+
rel?: string
|
|
14
|
+
}>
|
|
15
|
+
logoAlt?: string
|
|
16
|
+
}
|
|
17
|
+
>
|
|
13
18
|
|
|
14
19
|
export function LayoutWelcome({
|
|
15
20
|
name,
|
|
@@ -17,9 +22,20 @@ export function LayoutWelcome({
|
|
|
17
22
|
logoAlt = name || 'Logo',
|
|
18
23
|
links,
|
|
19
24
|
children,
|
|
20
|
-
|
|
25
|
+
className,
|
|
26
|
+
...props
|
|
27
|
+
}: LayoutWelcomeProps) {
|
|
21
28
|
return (
|
|
22
|
-
<div
|
|
29
|
+
<div
|
|
30
|
+
className={clsx(
|
|
31
|
+
'min-h-screen w-full',
|
|
32
|
+
'flex items-center justify-center flex-col',
|
|
33
|
+
'bg-white text-slate-900',
|
|
34
|
+
'dark:bg-slate-900 dark:text-slate-100',
|
|
35
|
+
className,
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
23
39
|
<div className="w-full max-w-screen-sm overflow-hidden flex-grow flex flex-col items-center justify-center">
|
|
24
40
|
{logo && (
|
|
25
41
|
<img
|
|
@@ -46,7 +62,7 @@ export function LayoutWelcome({
|
|
|
46
62
|
href={link.href}
|
|
47
63
|
rel={link.rel}
|
|
48
64
|
target="_blank"
|
|
49
|
-
className="m-2 md:m-4 text-xs md:text-sm text-
|
|
65
|
+
className="m-2 md:m-4 text-xs md:text-sm text-brand hover:underline"
|
|
50
66
|
>
|
|
51
67
|
{link.title}
|
|
52
68
|
</a>
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
FormHTMLAttributes,
|
|
3
|
-
ReactNode,
|
|
4
|
-
SyntheticEvent,
|
|
5
|
-
useCallback,
|
|
6
|
-
useState,
|
|
7
|
-
} from 'react'
|
|
1
|
+
import { ReactNode, SyntheticEvent, useCallback, useState } from 'react'
|
|
8
2
|
|
|
3
|
+
import {
|
|
4
|
+
InvalidCredentialsError,
|
|
5
|
+
SecondAuthenticationFactorRequiredError,
|
|
6
|
+
} from '../lib/api'
|
|
9
7
|
import { clsx } from '../lib/clsx'
|
|
10
|
-
import {
|
|
8
|
+
import { Override } from '../lib/util'
|
|
9
|
+
import { Button } from './button'
|
|
10
|
+
import { FormCard, FormCardProps } from './form-card'
|
|
11
|
+
import { AtSymbolIcon } from './icons/at-symbol-icon'
|
|
12
|
+
import { LockIcon } from './icons/lock-icon'
|
|
13
|
+
import { InfoCard } from './info-card'
|
|
14
|
+
import { InputCheckbox } from './input-checkbox'
|
|
15
|
+
import { InputText } from './input-text'
|
|
16
|
+
import { TokenIcon } from './icons/token-icon'
|
|
17
|
+
import { Fieldset } from './fieldset'
|
|
11
18
|
|
|
12
19
|
export type SignInFormOutput = {
|
|
13
20
|
username: string
|
|
@@ -15,41 +22,51 @@ export type SignInFormOutput = {
|
|
|
15
22
|
remember?: boolean
|
|
16
23
|
}
|
|
17
24
|
|
|
18
|
-
export type SignInFormProps =
|
|
19
|
-
|
|
25
|
+
export type SignInFormProps = Override<
|
|
26
|
+
FormCardProps,
|
|
27
|
+
{
|
|
28
|
+
onSubmit: (credentials: SignInFormOutput) => void | PromiseLike<void>
|
|
29
|
+
submitLabel?: ReactNode
|
|
30
|
+
submitAria?: string
|
|
20
31
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
32
|
+
onCancel?: () => void
|
|
33
|
+
cancelLabel?: ReactNode
|
|
34
|
+
cancelAria?: string
|
|
24
35
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
accountSection?: ReactNode
|
|
37
|
+
sessionSection?: ReactNode
|
|
38
|
+
secondFactorSection?: ReactNode
|
|
28
39
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
usernameDefault?: string
|
|
41
|
+
usernameReadonly?: boolean
|
|
42
|
+
usernameLabel?: string
|
|
43
|
+
usernamePlaceholder?: string
|
|
44
|
+
usernameAria?: string
|
|
45
|
+
usernamePattern?: string
|
|
46
|
+
usernameFormat?: string
|
|
36
47
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
passwordLabel?: string
|
|
49
|
+
passwordPlaceholder?: string
|
|
50
|
+
passwordWarning?: ReactNode
|
|
51
|
+
passwordAria?: string
|
|
52
|
+
passwordPattern?: string
|
|
53
|
+
passwordFormat?: string
|
|
43
54
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
secondFactorLabel?: string
|
|
56
|
+
secondFactorPlaceholder?: string
|
|
57
|
+
secondFactorAria?: string
|
|
58
|
+
secondFactorPattern?: string
|
|
59
|
+
secondFactorFormat?: string
|
|
60
|
+
secondFactorHint?: string
|
|
49
61
|
|
|
50
|
-
|
|
51
|
-
|
|
62
|
+
rememberVisible?: boolean
|
|
63
|
+
rememberDefault?: boolean
|
|
64
|
+
rememberLabel?: string
|
|
65
|
+
rememberAria?: string
|
|
66
|
+
}
|
|
67
|
+
>
|
|
52
68
|
|
|
69
|
+
export function SignInForm({
|
|
53
70
|
onSubmit,
|
|
54
71
|
submitAria = 'Next',
|
|
55
72
|
submitLabel = submitAria,
|
|
@@ -58,45 +75,63 @@ export function SignInForm({
|
|
|
58
75
|
cancelAria = 'Cancel',
|
|
59
76
|
cancelLabel = cancelAria,
|
|
60
77
|
|
|
78
|
+
accountSection = 'Account',
|
|
79
|
+
sessionSection = 'Session',
|
|
80
|
+
secondFactorSection = '2FA Confirmation',
|
|
81
|
+
|
|
61
82
|
usernameDefault = '',
|
|
62
83
|
usernameReadonly = false,
|
|
63
|
-
usernameLabel = '
|
|
84
|
+
usernameLabel = 'Username or email address',
|
|
64
85
|
usernameAria = usernameLabel,
|
|
65
86
|
usernamePlaceholder = usernameLabel,
|
|
66
|
-
usernamePattern,
|
|
67
|
-
|
|
87
|
+
usernamePattern = undefined,
|
|
88
|
+
usernameFormat = 'valid email address or username',
|
|
68
89
|
|
|
69
90
|
passwordLabel = 'Password',
|
|
70
91
|
passwordAria = passwordLabel,
|
|
71
92
|
passwordPlaceholder = passwordLabel,
|
|
72
|
-
passwordPattern,
|
|
73
|
-
|
|
93
|
+
passwordPattern = undefined,
|
|
94
|
+
passwordFormat = 'non empty string',
|
|
74
95
|
passwordWarning = (
|
|
75
96
|
<>
|
|
76
|
-
<p className="font-bold">Warning</p>
|
|
77
|
-
<p
|
|
97
|
+
<p className="font-bold text-brand leading-8">Warning</p>
|
|
98
|
+
<p>
|
|
78
99
|
Please verify the domain name of the website before entering your
|
|
79
100
|
password. Never enter your password on a domain you do not trust.
|
|
80
101
|
</p>
|
|
81
102
|
</>
|
|
82
103
|
),
|
|
83
104
|
|
|
105
|
+
secondFactorLabel = 'Confirmation code',
|
|
106
|
+
secondFactorAria = secondFactorLabel,
|
|
107
|
+
secondFactorPlaceholder = secondFactorLabel,
|
|
108
|
+
secondFactorPattern = '^[A-Z0-9]{5}-[A-Z0-9]{5}$',
|
|
109
|
+
secondFactorFormat = 'XXXXX-XXXXX',
|
|
110
|
+
secondFactorHint = 'Check your $1 email for a login code and enter it here.',
|
|
111
|
+
|
|
84
112
|
rememberVisible = true,
|
|
85
113
|
rememberDefault = false,
|
|
86
114
|
rememberLabel = 'Remember this account on this device',
|
|
87
115
|
rememberAria = rememberLabel,
|
|
88
116
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}: SignInFormProps &
|
|
92
|
-
Omit<
|
|
93
|
-
FormHTMLAttributes<HTMLFormElement>,
|
|
94
|
-
keyof SignInFormProps | 'children'
|
|
95
|
-
>) {
|
|
117
|
+
...props
|
|
118
|
+
}: SignInFormProps) {
|
|
96
119
|
const [focused, setFocused] = useState(false)
|
|
97
120
|
const [loading, setLoading] = useState(false)
|
|
121
|
+
const [secondFactor, setSecondFactor] = useState<null | {
|
|
122
|
+
type: 'emailOtp'
|
|
123
|
+
hint: string
|
|
124
|
+
}>(null)
|
|
125
|
+
|
|
98
126
|
const [errorMessage, setErrorMessage] = useState<string | null>(null)
|
|
99
127
|
|
|
128
|
+
const resetState = useCallback(() => {
|
|
129
|
+
setSecondFactor(null)
|
|
130
|
+
setErrorMessage(null)
|
|
131
|
+
}, [])
|
|
132
|
+
|
|
133
|
+
const passwordReadonly = secondFactor != null
|
|
134
|
+
|
|
100
135
|
const doSubmit = useCallback(
|
|
101
136
|
async (
|
|
102
137
|
event: SyntheticEvent<
|
|
@@ -104,187 +139,170 @@ export function SignInForm({
|
|
|
104
139
|
username: HTMLInputElement
|
|
105
140
|
password: HTMLInputElement
|
|
106
141
|
remember?: HTMLInputElement
|
|
142
|
+
secondFactor?: HTMLInputElement
|
|
107
143
|
},
|
|
108
144
|
SubmitEvent
|
|
109
145
|
>,
|
|
110
146
|
) => {
|
|
111
147
|
event.preventDefault()
|
|
112
148
|
|
|
113
|
-
const credentials = {
|
|
149
|
+
const credentials: SignInFormOutput = {
|
|
114
150
|
username: event.currentTarget.username.value,
|
|
115
151
|
password: event.currentTarget.password.value,
|
|
116
152
|
remember: event.currentTarget.remember?.checked,
|
|
117
153
|
}
|
|
118
154
|
|
|
155
|
+
if (secondFactor) {
|
|
156
|
+
const element = event.currentTarget.secondFactor
|
|
157
|
+
if (!element) throw new Error('Second factor input not found')
|
|
158
|
+
credentials[secondFactor.type] = element.value
|
|
159
|
+
}
|
|
160
|
+
|
|
119
161
|
setLoading(true)
|
|
120
162
|
setErrorMessage(null)
|
|
121
163
|
try {
|
|
122
164
|
await onSubmit(credentials)
|
|
123
165
|
} catch (err) {
|
|
124
|
-
|
|
166
|
+
if (err instanceof SecondAuthenticationFactorRequiredError) {
|
|
167
|
+
setSecondFactor({
|
|
168
|
+
type: err.type,
|
|
169
|
+
hint: err.hint,
|
|
170
|
+
})
|
|
171
|
+
} else {
|
|
172
|
+
setErrorMessage(parseErrorMessage(err))
|
|
173
|
+
}
|
|
125
174
|
} finally {
|
|
126
175
|
setLoading(false)
|
|
127
176
|
}
|
|
128
177
|
},
|
|
129
|
-
[
|
|
178
|
+
[secondFactor, onSubmit],
|
|
130
179
|
)
|
|
131
180
|
|
|
132
181
|
return (
|
|
133
|
-
<
|
|
134
|
-
{...attrs}
|
|
135
|
-
className={clsx('flex flex-col', className)}
|
|
182
|
+
<FormCard
|
|
136
183
|
onSubmit={doSubmit}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
onChange={() => setErrorMessage(null)}
|
|
149
|
-
className="relative m-0 block w-[1px] min-w-0 flex-auto px-3 py-[0.25rem] leading-[1.6] bg-transparent bg-clip-padding text-base text-inherit outline-none dark:placeholder:text-neutral-100 disabled:text-gray-500"
|
|
150
|
-
placeholder={usernamePlaceholder}
|
|
151
|
-
aria-label={usernameAria}
|
|
152
|
-
autoCapitalize="none"
|
|
153
|
-
autoCorrect="off"
|
|
154
|
-
autoComplete="username"
|
|
155
|
-
spellCheck="false"
|
|
156
|
-
dir="auto"
|
|
157
|
-
enterKeyHint="next"
|
|
158
|
-
required
|
|
159
|
-
defaultValue={usernameDefault}
|
|
160
|
-
readOnly={usernameReadonly}
|
|
161
|
-
disabled={usernameReadonly}
|
|
162
|
-
pattern={usernamePattern}
|
|
163
|
-
title={usernameTitle}
|
|
164
|
-
/>
|
|
165
|
-
</div>
|
|
166
|
-
|
|
167
|
-
<hr className="border-slate-200 dark:border-slate-700" />
|
|
168
|
-
|
|
169
|
-
<div className="relative p-1 flex flex-wrap items-center justify-stretch">
|
|
170
|
-
<span className="w-8 text-center text-2xl leading-[1.6]">*</span>
|
|
171
|
-
<input
|
|
172
|
-
name="password"
|
|
173
|
-
type="password"
|
|
174
|
-
onChange={() => setErrorMessage(null)}
|
|
175
|
-
onFocus={() => setFocused(true)}
|
|
176
|
-
onBlur={() => setTimeout(setFocused, 100, false)}
|
|
177
|
-
className="relative m-0 block w-[1px] min-w-0 flex-auto px-3 py-[0.25rem] leading-[1.6] bg-transparent bg-clip-padding text-base text-inherit outline-none dark:placeholder:text-neutral-100"
|
|
178
|
-
placeholder={passwordPlaceholder}
|
|
179
|
-
aria-label={passwordAria}
|
|
180
|
-
autoCapitalize="none"
|
|
181
|
-
autoCorrect="off"
|
|
182
|
-
autoComplete="current-password"
|
|
183
|
-
dir="auto"
|
|
184
|
-
enterKeyHint="done"
|
|
185
|
-
spellCheck="false"
|
|
186
|
-
required
|
|
187
|
-
pattern={passwordPattern}
|
|
188
|
-
title={passwordTitle}
|
|
189
|
-
/>
|
|
190
|
-
</div>
|
|
191
|
-
|
|
192
|
-
{passwordWarning && (
|
|
193
|
-
<>
|
|
194
|
-
<hr
|
|
195
|
-
className="border-slate-200 dark:border-slate-700 transition-all"
|
|
196
|
-
style={{ borderTopWidth: focused ? '1px' : '0px' }}
|
|
197
|
-
/>
|
|
198
|
-
<div
|
|
199
|
-
className="bg-slate-100 dark:bg-slate-800 overflow-hidden transition-all"
|
|
200
|
-
style={{
|
|
201
|
-
display: 'grid',
|
|
202
|
-
gridTemplateRows: focused ? '1fr' : '0fr',
|
|
203
|
-
}}
|
|
204
|
-
>
|
|
205
|
-
<div className="flex items-center justify-start overflow-hidden">
|
|
206
|
-
<div className="py-1 px-2">
|
|
207
|
-
<svg
|
|
208
|
-
className="fill-current h-4 w-4 text-error"
|
|
209
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
210
|
-
viewBox="0 0 20 20"
|
|
211
|
-
>
|
|
212
|
-
<path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm12.73-1.41A8 8 0 1 0 4.34 4.34a8 8 0 0 0 11.32 11.32zM9 11V9h2v6H9v-4zm0-6h2v2H9V5z" />
|
|
213
|
-
</svg>
|
|
214
|
-
</div>
|
|
215
|
-
<div className="py-2 px-4">{passwordWarning}</div>
|
|
216
|
-
</div>
|
|
217
|
-
</div>
|
|
218
|
-
</>
|
|
219
|
-
)}
|
|
220
|
-
|
|
221
|
-
{rememberVisible && (
|
|
222
|
-
<>
|
|
223
|
-
<hr className="border-slate-200 dark:border-slate-700" />
|
|
224
|
-
|
|
225
|
-
<div className="relative p-1 flex flex-wrap items-center justify-stretch">
|
|
226
|
-
<span className="w-8 flex items-center justify-center">
|
|
227
|
-
<input
|
|
228
|
-
className="text-primary"
|
|
229
|
-
id="remember"
|
|
230
|
-
name="remember"
|
|
231
|
-
type="checkbox"
|
|
232
|
-
defaultChecked={rememberDefault}
|
|
233
|
-
aria-label={rememberAria}
|
|
234
|
-
onChange={() => setErrorMessage(null)}
|
|
235
|
-
/>
|
|
236
|
-
</span>
|
|
237
|
-
|
|
238
|
-
<label
|
|
239
|
-
htmlFor="remember"
|
|
240
|
-
className="relative m-0 block w-[1px] min-w-0 flex-auto px-3 py-[0.25rem] leading-[1.6]"
|
|
241
|
-
>
|
|
242
|
-
{rememberLabel}
|
|
243
|
-
</label>
|
|
244
|
-
</div>
|
|
245
|
-
</>
|
|
246
|
-
)}
|
|
247
|
-
</fieldset>
|
|
248
|
-
|
|
249
|
-
{errorMessage && <ErrorCard className="mt-4" message={errorMessage} />}
|
|
250
|
-
|
|
251
|
-
<div className="flex-auto" />
|
|
252
|
-
|
|
253
|
-
<div className="p-4 flex flex-wrap items-center justify-start">
|
|
254
|
-
<button
|
|
255
|
-
className="py-2 bg-transparent text-primary rounded-md font-semibold order-last"
|
|
184
|
+
error={errorMessage}
|
|
185
|
+
cancel={
|
|
186
|
+
onCancel && (
|
|
187
|
+
<Button aria-label={cancelAria} onClick={onCancel}>
|
|
188
|
+
{cancelLabel}
|
|
189
|
+
</Button>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
actions={
|
|
193
|
+
<Button
|
|
194
|
+
color="brand"
|
|
256
195
|
type="submit"
|
|
257
|
-
role="Button"
|
|
258
196
|
aria-label={submitAria}
|
|
259
|
-
|
|
197
|
+
loading={loading}
|
|
260
198
|
>
|
|
261
199
|
{submitLabel}
|
|
262
|
-
</
|
|
200
|
+
</Button>
|
|
201
|
+
}
|
|
202
|
+
{...props}
|
|
203
|
+
>
|
|
204
|
+
<Fieldset title={accountSection} disabled={loading}>
|
|
205
|
+
<InputText
|
|
206
|
+
icon={<AtSymbolIcon className="w-5" />}
|
|
207
|
+
name="username"
|
|
208
|
+
type="text"
|
|
209
|
+
onChange={resetState}
|
|
210
|
+
placeholder={usernamePlaceholder}
|
|
211
|
+
aria-label={usernameAria}
|
|
212
|
+
autoCapitalize="none"
|
|
213
|
+
autoCorrect="off"
|
|
214
|
+
autoComplete="username"
|
|
215
|
+
spellCheck="false"
|
|
216
|
+
dir="auto"
|
|
217
|
+
enterKeyHint="next"
|
|
218
|
+
required
|
|
219
|
+
defaultValue={usernameDefault}
|
|
220
|
+
readOnly={usernameReadonly}
|
|
221
|
+
disabled={usernameReadonly}
|
|
222
|
+
pattern={usernamePattern}
|
|
223
|
+
title={usernameFormat}
|
|
224
|
+
/>
|
|
225
|
+
|
|
226
|
+
<InputText
|
|
227
|
+
icon={<LockIcon className="w-5" />}
|
|
228
|
+
name="password"
|
|
229
|
+
type="password"
|
|
230
|
+
onChange={resetState}
|
|
231
|
+
onFocus={() => setFocused(true)}
|
|
232
|
+
onBlur={() => setTimeout(setFocused, 100, false)}
|
|
233
|
+
placeholder={passwordPlaceholder}
|
|
234
|
+
aria-label={passwordAria}
|
|
235
|
+
autoCapitalize="none"
|
|
236
|
+
autoCorrect="off"
|
|
237
|
+
autoComplete="current-password"
|
|
238
|
+
dir="auto"
|
|
239
|
+
enterKeyHint="done"
|
|
240
|
+
spellCheck="false"
|
|
241
|
+
required
|
|
242
|
+
readOnly={passwordReadonly}
|
|
243
|
+
disabled={passwordReadonly}
|
|
244
|
+
pattern={passwordPattern}
|
|
245
|
+
title={passwordFormat}
|
|
246
|
+
/>
|
|
263
247
|
|
|
264
|
-
{
|
|
265
|
-
<
|
|
266
|
-
className=
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
onClick={onCancel}
|
|
248
|
+
{passwordWarning && (
|
|
249
|
+
<div
|
|
250
|
+
className={clsx(
|
|
251
|
+
'transition-all delay-300 duration-300 overflow-hidden',
|
|
252
|
+
focused ? 'max-h-80' : 'max-h-0 -z-10 !mt-0',
|
|
253
|
+
)}
|
|
271
254
|
>
|
|
272
|
-
{
|
|
273
|
-
</
|
|
255
|
+
<InfoCard role="status">{passwordWarning}</InfoCard>
|
|
256
|
+
</div>
|
|
274
257
|
)}
|
|
258
|
+
</Fieldset>
|
|
275
259
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
260
|
+
{rememberVisible && (
|
|
261
|
+
<Fieldset key="remember" title={sessionSection} disabled={loading}>
|
|
262
|
+
<InputCheckbox
|
|
263
|
+
name="remember"
|
|
264
|
+
defaultChecked={rememberDefault}
|
|
265
|
+
aria-label={rememberAria}
|
|
266
|
+
>
|
|
267
|
+
{rememberLabel}
|
|
268
|
+
</InputCheckbox>
|
|
269
|
+
</Fieldset>
|
|
270
|
+
)}
|
|
271
|
+
|
|
272
|
+
{secondFactor && (
|
|
273
|
+
<Fieldset key="2fa" title={secondFactorSection} disabled={loading}>
|
|
274
|
+
<div>
|
|
275
|
+
<InputText
|
|
276
|
+
icon={<TokenIcon className="w-5" />}
|
|
277
|
+
name="secondFactor"
|
|
278
|
+
type="text"
|
|
279
|
+
placeholder={secondFactorPlaceholder}
|
|
280
|
+
aria-label={secondFactorAria}
|
|
281
|
+
autoCapitalize="none"
|
|
282
|
+
autoCorrect="off"
|
|
283
|
+
autoComplete="off"
|
|
284
|
+
spellCheck="false"
|
|
285
|
+
dir="auto"
|
|
286
|
+
enterKeyHint="done"
|
|
287
|
+
required
|
|
288
|
+
pattern={secondFactorPattern}
|
|
289
|
+
title={secondFactorFormat}
|
|
290
|
+
autoFocus={true}
|
|
291
|
+
/>
|
|
292
|
+
<p className="text-slate-600 dark:text-slate-400 text-sm">
|
|
293
|
+
{secondFactorHint.replaceAll('$1', secondFactor.hint)}
|
|
294
|
+
</p>
|
|
295
|
+
</div>
|
|
296
|
+
</Fieldset>
|
|
297
|
+
)}
|
|
298
|
+
</FormCard>
|
|
279
299
|
)
|
|
280
300
|
}
|
|
281
301
|
|
|
282
302
|
function parseErrorMessage(err: unknown): string {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
case 'Invalid credentials':
|
|
286
|
-
return 'Invalid username or password'
|
|
287
|
-
default:
|
|
288
|
-
return 'An unknown error occurred'
|
|
303
|
+
if (err instanceof InvalidCredentialsError) {
|
|
304
|
+
return 'Invalid username or password'
|
|
289
305
|
}
|
|
306
|
+
|
|
307
|
+
return 'An unknown error occurred'
|
|
290
308
|
}
|