@atproto/oauth-provider 0.1.0 → 0.1.2-rc.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
}
|