@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
|
@@ -6,36 +6,41 @@ import {
|
|
|
6
6
|
useState,
|
|
7
7
|
} from 'react'
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { Button } from './button'
|
|
10
|
+
import { FormCard, FormCardProps } from './form-card'
|
|
11
|
+
import { Override } from '../lib/util'
|
|
12
|
+
import { Fieldset } from './fieldset'
|
|
11
13
|
|
|
12
14
|
export type SignUpAccountFormOutput = {
|
|
13
15
|
username: string
|
|
14
16
|
password: string
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
export type SignUpAccountFormProps =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
19
|
+
export type SignUpAccountFormProps = Override<
|
|
20
|
+
FormCardProps,
|
|
21
|
+
{
|
|
22
|
+
onSubmit: (credentials: SignUpAccountFormOutput) => void | PromiseLike<void>
|
|
23
|
+
submitLabel?: ReactNode
|
|
24
|
+
submitAria?: string
|
|
25
|
+
|
|
26
|
+
onCancel?: () => void
|
|
27
|
+
cancelLabel?: ReactNode
|
|
28
|
+
cancelAria?: string
|
|
29
|
+
|
|
30
|
+
username?: string
|
|
31
|
+
usernamePlaceholder?: string
|
|
32
|
+
usernameLabel?: string
|
|
33
|
+
usernameAria?: string
|
|
34
|
+
usernamePattern?: string
|
|
35
|
+
usernameTitle?: string
|
|
36
|
+
|
|
37
|
+
passwordPlaceholder?: string
|
|
38
|
+
passwordLabel?: string
|
|
39
|
+
passwordAria?: string
|
|
40
|
+
passwordPattern?: string
|
|
41
|
+
passwordTitle?: string
|
|
42
|
+
}
|
|
43
|
+
>
|
|
39
44
|
|
|
40
45
|
export function SignUpAccountForm({
|
|
41
46
|
onSubmit,
|
|
@@ -59,9 +64,8 @@ export function SignUpAccountForm({
|
|
|
59
64
|
passwordPattern,
|
|
60
65
|
passwordTitle,
|
|
61
66
|
|
|
62
|
-
className,
|
|
63
67
|
children,
|
|
64
|
-
...
|
|
68
|
+
...props
|
|
65
69
|
}: SignUpAccountFormProps &
|
|
66
70
|
Omit<FormHTMLAttributes<HTMLFormElement>, keyof SignUpAccountFormProps>) {
|
|
67
71
|
const [loading, setLoading] = useState(false)
|
|
@@ -98,104 +102,84 @@ export function SignUpAccountForm({
|
|
|
98
102
|
)
|
|
99
103
|
|
|
100
104
|
return (
|
|
101
|
-
<
|
|
102
|
-
{
|
|
103
|
-
|
|
105
|
+
<FormCard
|
|
106
|
+
append={children}
|
|
107
|
+
error={errorMessage}
|
|
104
108
|
onSubmit={doSubmit}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
<span className="w-6 ml-1 text-center text-base">@</span>
|
|
116
|
-
<input
|
|
117
|
-
name="username"
|
|
118
|
-
type="text"
|
|
119
|
-
onChange={() => setErrorMessage(null)}
|
|
120
|
-
className="relative m-1 block w-[1px] min-w-0 flex-auto leading-[1.6] bg-transparent bg-clip-padding text-base text-inherit outline-none dark:placeholder:text-neutral-100 disabled:text-gray-500"
|
|
121
|
-
placeholder={usernamePlaceholder}
|
|
122
|
-
aria-label={usernameAria}
|
|
123
|
-
autoCapitalize="none"
|
|
124
|
-
autoCorrect="off"
|
|
125
|
-
autoComplete="username"
|
|
126
|
-
spellCheck="false"
|
|
127
|
-
dir="auto"
|
|
128
|
-
enterKeyHint="next"
|
|
129
|
-
required
|
|
130
|
-
defaultValue={defaultUsername}
|
|
131
|
-
pattern={usernamePattern}
|
|
132
|
-
title={usernameTitle}
|
|
133
|
-
/>
|
|
134
|
-
</div>
|
|
135
|
-
|
|
136
|
-
<label className="text-sm font-medium" htmlFor="password">
|
|
137
|
-
{passwordLabel}
|
|
138
|
-
</label>
|
|
139
|
-
|
|
140
|
-
<div
|
|
141
|
-
id="password"
|
|
142
|
-
className="mb-4 relative flex flex-wrap items-center justify-stretch rounded-md border border-solid border-slate-200 dark:border-slate-700 text-neutral-700 dark:text-neutral-100"
|
|
143
|
-
>
|
|
144
|
-
<span className="w-6 ml-1 text-center text-2xl font-light -mb-2">
|
|
145
|
-
*
|
|
146
|
-
</span>
|
|
147
|
-
<input
|
|
148
|
-
name="password"
|
|
149
|
-
type="password"
|
|
150
|
-
onChange={() => setErrorMessage(null)}
|
|
151
|
-
className="relative m-1 block w-[1px] min-w-0 flex-auto leading-[1.6] bg-transparent bg-clip-padding text-base text-inherit outline-none dark:placeholder:text-neutral-100"
|
|
152
|
-
placeholder={passwordPlaceholder}
|
|
153
|
-
aria-label={passwordAria}
|
|
154
|
-
autoCapitalize="none"
|
|
155
|
-
autoCorrect="off"
|
|
156
|
-
autoComplete="new-password"
|
|
157
|
-
dir="auto"
|
|
158
|
-
enterKeyHint="done"
|
|
159
|
-
spellCheck="false"
|
|
160
|
-
required
|
|
161
|
-
pattern={passwordPattern}
|
|
162
|
-
title={passwordTitle}
|
|
163
|
-
/>
|
|
164
|
-
</div>
|
|
165
|
-
</fieldset>
|
|
166
|
-
|
|
167
|
-
{children && <div className="mt-4">{children}</div>}
|
|
168
|
-
|
|
169
|
-
{errorMessage && <ErrorCard className="mt-2" message={errorMessage} />}
|
|
170
|
-
|
|
171
|
-
<div className="flex-auto"></div>
|
|
172
|
-
|
|
173
|
-
<div className="p-4 flex flex-wrap items-center justify-start">
|
|
174
|
-
<button
|
|
175
|
-
className="py-2 bg-transparent text-primary rounded-md font-semibold order-last"
|
|
109
|
+
cancel={
|
|
110
|
+
onCancel && (
|
|
111
|
+
<Button aria-label={cancelAria} onClick={onCancel}>
|
|
112
|
+
{cancelLabel}
|
|
113
|
+
</Button>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
actions={
|
|
117
|
+
<Button
|
|
118
|
+
color="brand"
|
|
176
119
|
type="submit"
|
|
177
|
-
role="Button"
|
|
178
120
|
aria-label={submitAria}
|
|
179
121
|
disabled={loading}
|
|
180
122
|
>
|
|
181
123
|
{submitLabel}
|
|
182
|
-
</
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
124
|
+
</Button>
|
|
125
|
+
}
|
|
126
|
+
{...props}
|
|
127
|
+
>
|
|
128
|
+
<Fieldset disabled={loading}>
|
|
129
|
+
<label className="text-sm font-medium block">
|
|
130
|
+
<p>{usernameLabel}</p>
|
|
131
|
+
|
|
132
|
+
<div className="relative flex flex-wrap items-center justify-stretch rounded-md border border-solid border-slate-200 dark:border-slate-700 text-neutral-700 dark:text-neutral-100">
|
|
133
|
+
<span className="w-6 ml-1 text-center text-base">@</span>
|
|
134
|
+
<input
|
|
135
|
+
name="username"
|
|
136
|
+
type="text"
|
|
137
|
+
onChange={() => setErrorMessage(null)}
|
|
138
|
+
className="relative m-1 block w-[1px] min-w-0 flex-auto leading-[1.6] bg-transparent bg-clip-padding text-base text-inherit outline-none dark:placeholder:text-neutral-100 disabled:text-gray-500"
|
|
139
|
+
placeholder={usernamePlaceholder}
|
|
140
|
+
aria-label={usernameAria}
|
|
141
|
+
autoCapitalize="none"
|
|
142
|
+
autoCorrect="off"
|
|
143
|
+
autoComplete="username"
|
|
144
|
+
spellCheck="false"
|
|
145
|
+
dir="auto"
|
|
146
|
+
enterKeyHint="next"
|
|
147
|
+
required
|
|
148
|
+
defaultValue={defaultUsername}
|
|
149
|
+
pattern={usernamePattern}
|
|
150
|
+
title={usernameTitle}
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
</label>
|
|
195
154
|
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
155
|
+
<label className="text-sm font-medium block">
|
|
156
|
+
<p>{passwordLabel}</p>
|
|
157
|
+
|
|
158
|
+
<div className="relative flex flex-wrap items-center justify-stretch rounded-md border border-solid border-slate-200 dark:border-slate-700 text-neutral-700 dark:text-neutral-100">
|
|
159
|
+
<span className="w-6 ml-1 text-center text-2xl font-light -mb-2">
|
|
160
|
+
*
|
|
161
|
+
</span>
|
|
162
|
+
<input
|
|
163
|
+
name="password"
|
|
164
|
+
type="password"
|
|
165
|
+
onChange={() => setErrorMessage(null)}
|
|
166
|
+
className="relative m-1 block w-[1px] min-w-0 flex-auto leading-[1.6] bg-transparent bg-clip-padding text-base text-inherit outline-none dark:placeholder:text-neutral-100"
|
|
167
|
+
placeholder={passwordPlaceholder}
|
|
168
|
+
aria-label={passwordAria}
|
|
169
|
+
autoCapitalize="none"
|
|
170
|
+
autoCorrect="off"
|
|
171
|
+
autoComplete="new-password"
|
|
172
|
+
dir="auto"
|
|
173
|
+
enterKeyHint="done"
|
|
174
|
+
spellCheck="false"
|
|
175
|
+
required
|
|
176
|
+
pattern={passwordPattern}
|
|
177
|
+
title={passwordTitle}
|
|
178
|
+
/>
|
|
179
|
+
</div>
|
|
180
|
+
</label>
|
|
181
|
+
</Fieldset>
|
|
182
|
+
</FormCard>
|
|
199
183
|
)
|
|
200
184
|
}
|
|
201
185
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FetchResponseError, Json } from '@atproto-labs/fetch'
|
|
2
2
|
|
|
3
3
|
import { Account, Session } from '../backend-data'
|
|
4
4
|
|
|
@@ -15,7 +15,7 @@ export class Api {
|
|
|
15
15
|
password: string
|
|
16
16
|
remember?: boolean
|
|
17
17
|
}): Promise<Session> {
|
|
18
|
-
const
|
|
18
|
+
const response = await fetch('/oauth/authorize/sign-in', {
|
|
19
19
|
method: 'POST',
|
|
20
20
|
headers: { 'Content-Type': 'application/json' },
|
|
21
21
|
mode: 'same-origin',
|
|
@@ -26,20 +26,40 @@ export class Api {
|
|
|
26
26
|
credentials,
|
|
27
27
|
}),
|
|
28
28
|
})
|
|
29
|
-
.then(fetchOkProcessor())
|
|
30
|
-
.then(
|
|
31
|
-
fetchJsonProcessor<{
|
|
32
|
-
account: Account
|
|
33
|
-
consentRequired: boolean
|
|
34
|
-
}>(),
|
|
35
|
-
)
|
|
36
29
|
|
|
37
|
-
|
|
38
|
-
account: json.account,
|
|
30
|
+
const json: Json = await response.json()
|
|
39
31
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
32
|
+
if (response.ok) {
|
|
33
|
+
const data = json as {
|
|
34
|
+
account: Account
|
|
35
|
+
consentRequired: boolean
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
account: data.account,
|
|
40
|
+
|
|
41
|
+
selected: true,
|
|
42
|
+
loginRequired: false,
|
|
43
|
+
consentRequired: this.newSessionsRequireConsent || data.consentRequired,
|
|
44
|
+
}
|
|
45
|
+
} else if (
|
|
46
|
+
response.status === 400 &&
|
|
47
|
+
json?.['error'] === 'invalid_request' &&
|
|
48
|
+
json?.['error_description'] === 'Invalid credentials'
|
|
49
|
+
) {
|
|
50
|
+
throw new InvalidCredentialsError()
|
|
51
|
+
} else if (
|
|
52
|
+
response.status === 401 &&
|
|
53
|
+
json?.['error'] === 'second_authentication_factor_required'
|
|
54
|
+
) {
|
|
55
|
+
const data = json as {
|
|
56
|
+
type: 'emailOtp'
|
|
57
|
+
hint: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
throw new SecondAuthenticationFactorRequiredError(data.type, data.hint)
|
|
61
|
+
} else {
|
|
62
|
+
throw new FetchResponseError(response)
|
|
43
63
|
}
|
|
44
64
|
}
|
|
45
65
|
|
|
@@ -62,3 +82,18 @@ export class Api {
|
|
|
62
82
|
return url
|
|
63
83
|
}
|
|
64
84
|
}
|
|
85
|
+
|
|
86
|
+
export class InvalidCredentialsError extends Error {
|
|
87
|
+
constructor() {
|
|
88
|
+
super('Invalid credentials')
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export class SecondAuthenticationFactorRequiredError extends Error {
|
|
93
|
+
constructor(
|
|
94
|
+
public type: 'emailOtp',
|
|
95
|
+
public hint: string,
|
|
96
|
+
) {
|
|
97
|
+
super(`${type} authentication factor required (hint: ${hint})`)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
export function clsx(
|
|
1
|
+
export function clsx(
|
|
2
|
+
a?: string,
|
|
3
|
+
...args: readonly (string | undefined)[]
|
|
4
|
+
): string | undefined {
|
|
5
|
+
if (args.length === 0) return a
|
|
6
|
+
const b = clsx(...args)
|
|
2
7
|
if (a && b) return `${a} ${b}`
|
|
3
8
|
return a || b
|
|
4
9
|
}
|
package/src/assets/app/main.css
CHANGED
|
@@ -31,13 +31,14 @@ export function AcceptView({
|
|
|
31
31
|
subtitle={
|
|
32
32
|
<>
|
|
33
33
|
Grant access to your{' '}
|
|
34
|
-
<b
|
|
35
|
-
|
|
34
|
+
<b className="text-black dark:text-white">
|
|
35
|
+
{account.preferred_username || account.email || account.sub}
|
|
36
|
+
</b>{' '}
|
|
37
|
+
account
|
|
36
38
|
</>
|
|
37
39
|
}
|
|
38
40
|
>
|
|
39
41
|
<AcceptForm
|
|
40
|
-
className="max-w-lg w-full"
|
|
41
42
|
clientId={clientId}
|
|
42
43
|
clientMetadata={clientMetadata}
|
|
43
44
|
clientTrusted={clientTrusted}
|
|
@@ -85,10 +85,14 @@ export function AuthorizeView({
|
|
|
85
85
|
clientTrusted={authorizeData.clientTrusted}
|
|
86
86
|
onAccept={() => doAccept(session.account)}
|
|
87
87
|
onReject={doReject}
|
|
88
|
-
onBack={
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
onBack={
|
|
89
|
+
forceSignIn
|
|
90
|
+
? undefined
|
|
91
|
+
: () => {
|
|
92
|
+
setSession(null)
|
|
93
|
+
setView(sessions.length ? 'sign-in' : 'welcome')
|
|
94
|
+
}
|
|
95
|
+
}
|
|
92
96
|
/>
|
|
93
97
|
)
|
|
94
98
|
}
|
|
@@ -1,27 +1,36 @@
|
|
|
1
1
|
import { CustomizationData, ErrorData } from '../backend-data'
|
|
2
|
-
import {
|
|
3
|
-
import { LayoutWelcome } from '../components/layout-welcome'
|
|
2
|
+
import { InfoCard } from '../components/info-card'
|
|
3
|
+
import { LayoutWelcome, LayoutWelcomeProps } from '../components/layout-welcome'
|
|
4
|
+
import { Override } from '../lib/util'
|
|
4
5
|
|
|
5
|
-
export type ErrorViewProps =
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
export type ErrorViewProps = Override<
|
|
7
|
+
Omit<LayoutWelcomeProps, keyof CustomizationData>,
|
|
8
|
+
{
|
|
9
|
+
customizationData?: CustomizationData
|
|
10
|
+
errorData?: ErrorData
|
|
11
|
+
}
|
|
12
|
+
>
|
|
9
13
|
|
|
10
|
-
export function ErrorView({
|
|
14
|
+
export function ErrorView({
|
|
15
|
+
errorData,
|
|
16
|
+
customizationData,
|
|
17
|
+
...props
|
|
18
|
+
}: ErrorViewProps) {
|
|
11
19
|
return (
|
|
12
|
-
<LayoutWelcome {...customizationData}>
|
|
13
|
-
<
|
|
20
|
+
<LayoutWelcome {...customizationData} {...props}>
|
|
21
|
+
<InfoCard role="alert">{getUserFriendlyMessage(errorData)}</InfoCard>
|
|
14
22
|
</LayoutWelcome>
|
|
15
23
|
)
|
|
16
24
|
}
|
|
17
25
|
|
|
18
26
|
function getUserFriendlyMessage(errorData?: ErrorData) {
|
|
19
27
|
const desc = errorData?.error_description
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
if (
|
|
29
|
+
desc === 'This request has expired' ||
|
|
30
|
+
desc?.startsWith('Unknown request_uri') // Request was removed from database
|
|
31
|
+
) {
|
|
32
|
+
return 'This sign-in session has expired'
|
|
33
|
+
} else {
|
|
34
|
+
return desc || 'An unknown error occurred'
|
|
26
35
|
}
|
|
27
36
|
}
|
|
@@ -44,11 +44,12 @@ export function SignInView({
|
|
|
44
44
|
subtitle="Confirm your password to continue"
|
|
45
45
|
>
|
|
46
46
|
<SignInForm
|
|
47
|
-
className="max-w-lg w-full"
|
|
48
47
|
onSubmit={onSignIn}
|
|
49
48
|
onCancel={clearSession}
|
|
50
49
|
cancelAria="Back" // to account picker
|
|
51
|
-
usernameDefault={
|
|
50
|
+
usernameDefault={
|
|
51
|
+
session.account.preferred_username || session.account.sub
|
|
52
|
+
}
|
|
52
53
|
usernameReadonly={true}
|
|
53
54
|
rememberDefault={true}
|
|
54
55
|
/>
|
|
@@ -60,7 +61,6 @@ export function SignInView({
|
|
|
60
61
|
return (
|
|
61
62
|
<LayoutTitlePage title="Sign in" subtitle="Enter your password">
|
|
62
63
|
<SignInForm
|
|
63
|
-
className="max-w-lg w-full"
|
|
64
64
|
onSubmit={onSignIn}
|
|
65
65
|
onCancel={onBack}
|
|
66
66
|
cancelAria="Back"
|
|
@@ -77,12 +77,7 @@ export function SignInView({
|
|
|
77
77
|
title="Sign in"
|
|
78
78
|
subtitle="Enter your username and password"
|
|
79
79
|
>
|
|
80
|
-
<SignInForm
|
|
81
|
-
className="max-w-lg w-full"
|
|
82
|
-
onSubmit={onSignIn}
|
|
83
|
-
onCancel={onBack}
|
|
84
|
-
cancelAria="Back"
|
|
85
|
-
/>
|
|
80
|
+
<SignInForm onSubmit={onSignIn} onCancel={onBack} cancelAria="Back" />
|
|
86
81
|
</LayoutTitlePage>
|
|
87
82
|
)
|
|
88
83
|
}
|
|
@@ -94,7 +89,6 @@ export function SignInView({
|
|
|
94
89
|
subtitle="Enter your username and password"
|
|
95
90
|
>
|
|
96
91
|
<SignInForm
|
|
97
|
-
className="max-w-lg w-full"
|
|
98
92
|
onSubmit={onSignIn}
|
|
99
93
|
onCancel={() => setShowSignInForm(false)}
|
|
100
94
|
cancelAria="Back" // to account picker
|
|
@@ -104,12 +98,8 @@ export function SignInView({
|
|
|
104
98
|
}
|
|
105
99
|
|
|
106
100
|
return (
|
|
107
|
-
<LayoutTitlePage
|
|
108
|
-
title="Sign in as..."
|
|
109
|
-
subtitle="Select an account to continue."
|
|
110
|
-
>
|
|
101
|
+
<LayoutTitlePage title="Sign in" subtitle="Select from an existing account">
|
|
111
102
|
<AccountPicker
|
|
112
|
-
className="max-w-lg w-full"
|
|
113
103
|
accounts={accounts}
|
|
114
104
|
onAccount={(a) => setSession(a.sub)}
|
|
115
105
|
onOther={() => setShowSignInForm(true)}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
SignUpAccountFormOutput,
|
|
9
9
|
} from '../components/sign-up-account-form'
|
|
10
10
|
import { SignUpDisclaimer } from '../components/sign-up-disclaimer'
|
|
11
|
+
import { Button } from '../components/button'
|
|
11
12
|
|
|
12
13
|
export type SignUpViewProps = {
|
|
13
14
|
stepName?: (step: number, total: number) => ReactNode
|
|
@@ -57,7 +58,7 @@ export function SignUpView({
|
|
|
57
58
|
title="Create Account"
|
|
58
59
|
subtitle="We're so excited to have you join us!"
|
|
59
60
|
>
|
|
60
|
-
<div className="
|
|
61
|
+
<div className="flex flex-col">
|
|
61
62
|
<p className="mt-4 text-slate-400 dark:text-slate-600">
|
|
62
63
|
{stepName(step, stepCount)}
|
|
63
64
|
</p>
|
|
@@ -76,15 +77,7 @@ export function SignUpView({
|
|
|
76
77
|
</SignUpAccountForm>
|
|
77
78
|
)}
|
|
78
79
|
|
|
79
|
-
{step === 2 && (
|
|
80
|
-
<button
|
|
81
|
-
type="button"
|
|
82
|
-
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-slate-700 bg-slate-100 hover:bg-slate-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-500"
|
|
83
|
-
onClick={() => setStep(1)}
|
|
84
|
-
>
|
|
85
|
-
Back
|
|
86
|
-
</button>
|
|
87
|
-
)}
|
|
80
|
+
{step === 2 && <Button onClick={() => setStep(1)}>Back</Button>}
|
|
88
81
|
|
|
89
82
|
<HelpCard className="mb-4" links={links} />
|
|
90
83
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { Button } from '../components/button'
|
|
1
2
|
import { LayoutWelcome, LayoutWelcomeProps } from '../components/layout-welcome'
|
|
2
|
-
import { clsx } from '../lib/clsx'
|
|
3
3
|
|
|
4
4
|
export type WelcomeViewParams = LayoutWelcomeProps & {
|
|
5
5
|
onSignIn?: () => void
|
|
@@ -25,36 +25,29 @@ export function WelcomeView({
|
|
|
25
25
|
return (
|
|
26
26
|
<LayoutWelcome {...props}>
|
|
27
27
|
{onSignUp && (
|
|
28
|
-
<
|
|
29
|
-
className={
|
|
30
|
-
|
|
31
|
-
onSignIn ? 'bg-primary' : 'bg-slate-400',
|
|
32
|
-
)}
|
|
28
|
+
<Button
|
|
29
|
+
className={'m-1 w-60 max-w-full'}
|
|
30
|
+
color={onSignIn ? 'brand' : undefined}
|
|
33
31
|
onClick={onSignUp}
|
|
34
32
|
>
|
|
35
33
|
{signUpLabel}
|
|
36
|
-
</
|
|
34
|
+
</Button>
|
|
37
35
|
)}
|
|
38
36
|
|
|
39
37
|
{onSignIn && (
|
|
40
|
-
<
|
|
41
|
-
className={
|
|
42
|
-
|
|
43
|
-
onSignUp ? 'bg-slate-100 dark:bg-slate-700' : 'bg-primary',
|
|
44
|
-
)}
|
|
38
|
+
<Button
|
|
39
|
+
className={'m-1 w-60 max-w-full'}
|
|
40
|
+
color={onSignUp ? undefined : 'brand'}
|
|
45
41
|
onClick={onSignIn}
|
|
46
42
|
>
|
|
47
43
|
{signInLabel}
|
|
48
|
-
</
|
|
44
|
+
</Button>
|
|
49
45
|
)}
|
|
50
46
|
|
|
51
47
|
{onCancel && (
|
|
52
|
-
<
|
|
53
|
-
className="m-1 w-60 max-w-full bg-transparent text-primary py-2 px-4 rounded-full truncate font-light"
|
|
54
|
-
onClick={onCancel}
|
|
55
|
-
>
|
|
48
|
+
<Button className="m-1 w-60 max-w-full" onClick={onCancel}>
|
|
56
49
|
{cancelLabel}
|
|
57
|
-
</
|
|
50
|
+
</Button>
|
|
58
51
|
)}
|
|
59
52
|
</LayoutWelcome>
|
|
60
53
|
)
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { CLIENT_ASSERTION_TYPE_JWT_BEARER } from '@atproto/oauth-types'
|
|
2
|
-
import { KeyLike, calculateJwkThumbprint, exportJWK } from 'jose'
|
|
3
|
-
import { JOSEError } from 'jose/errors'
|
|
2
|
+
import { KeyLike, calculateJwkThumbprint, errors, exportJWK } from 'jose'
|
|
4
3
|
|
|
5
4
|
import { InvalidClientError } from '../errors/invalid-client-error.js'
|
|
6
5
|
|
|
6
|
+
const { JOSEError } = errors
|
|
7
|
+
|
|
7
8
|
export type ClientAuth =
|
|
8
9
|
| { method: 'none' }
|
|
9
10
|
| {
|
|
@@ -39,7 +39,8 @@ import { Client } from './client.js'
|
|
|
39
39
|
|
|
40
40
|
const fetchMetadataHandler = pipe(
|
|
41
41
|
fetchOkProcessor(),
|
|
42
|
-
|
|
42
|
+
// https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html#section-4.1
|
|
43
|
+
fetchJsonProcessor('application/json', true),
|
|
43
44
|
fetchJsonZodProcessor(oauthClientMetadataSchema),
|
|
44
45
|
)
|
|
45
46
|
|