diff --git a/packages/oauth-provider/src/assets/app/backend-data.ts b/packages/oauth-provider/src/assets/app/backend-data.ts index 710ac1396ed..f98e7829f37 100644 --- a/packages/oauth-provider/src/assets/app/backend-data.ts +++ b/packages/oauth-provider/src/assets/app/backend-data.ts @@ -1,13 +1,29 @@ import type { ClientMetadata, Session } from './types' +export type FieldDefinition = { + label?: string + placeholder?: string + pattern?: string + title?: string +} + +export type LinkDefinition = { + title: string + href: string + rel?: string +} + export type CustomizationData = { name?: string logo?: string - links?: Array<{ - name: string - href: string - rel?: string - }> + links?: LinkDefinition[] + signIn?: { + fields?: { + username?: FieldDefinition + password?: FieldDefinition + remember?: FieldDefinition + } + } } export type ErrorData = { diff --git a/packages/oauth-provider/src/assets/app/components/error-card.tsx b/packages/oauth-provider/src/assets/app/components/error-card.tsx index 75eba692c35..3a41112717b 100644 --- a/packages/oauth-provider/src/assets/app/components/error-card.tsx +++ b/packages/oauth-provider/src/assets/app/components/error-card.tsx @@ -13,7 +13,7 @@ export function ErrorCard({ className, ...attrs }: Partial & - Omit, keyof ErrorCardProps>) { + Omit, keyof ErrorCardProps | 'children'>) { return (
, keyof PageLayoutProps> ->) { +}: LayoutTitlePageProps & + Omit, keyof LayoutTitlePageProps>) { return (
diff --git a/packages/oauth-provider/src/assets/app/components/welcome-layout.tsx b/packages/oauth-provider/src/assets/app/components/layout-welcome.tsx similarity index 78% rename from packages/oauth-provider/src/assets/app/components/welcome-layout.tsx rename to packages/oauth-provider/src/assets/app/components/layout-welcome.tsx index 275c7de641d..e3c02bba5b2 100644 --- a/packages/oauth-provider/src/assets/app/components/welcome-layout.tsx +++ b/packages/oauth-provider/src/assets/app/components/layout-welcome.tsx @@ -1,25 +1,25 @@ import { PropsWithChildren } from 'react' -export type WelcomeLayoutProps = { +export type LayoutWelcomeProps = { name?: string logo?: string links?: Array<{ - name: string + title: string href: string rel?: string }> logoAlt?: string } -export function WelcomeLayout({ +export function LayoutWelcome({ name, logo, logoAlt = name || 'Logo', links, children, -}: PropsWithChildren) { +}: PropsWithChildren) { return ( -
+
{logo && ( {links != null && links.length > 0 && ( -
@@ -162,10 +171,7 @@ export function SignInForm({ { - setHasPassword(!!e.target.value) - setErrorMessage(null) - }} + onChange={() => setErrorMessage(null)} onFocus={() => setFocused(true)} onBlur={() => setTimeout(setFocused, 100, false)} 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" @@ -173,11 +179,13 @@ export function SignInForm({ aria-label={passwordAria} autoCapitalize="none" autoCorrect="off" - autoComplete="password" + autoComplete="current-password" dir="auto" enterKeyHint="done" spellCheck="false" required + pattern={passwordPattern} + title={passwordTitle} />
@@ -210,46 +218,48 @@ export function SignInForm({ )} -
- -
- - setErrorMessage(null)} - /> - - - -
+ {rememberVisible && ( + <> +
+ +
+ + setErrorMessage(null)} + /> + + + +
+ + )} - {errorMessage && } + {errorMessage && }
-
- {canSubmit && ( - - )} +
+ {onCancel && ( )} - + ) } diff --git a/packages/oauth-provider/src/output/customization.ts b/packages/oauth-provider/src/output/customization.ts index 610a708b38f..8265019779d 100644 --- a/packages/oauth-provider/src/output/customization.ts +++ b/packages/oauth-provider/src/output/customization.ts @@ -4,26 +4,43 @@ type ColorName = typeof colorNames[number] const isColorName = (name: string): name is ColorName => (colorNames as readonly string[]).includes(name) +export type FieldDefinition = { + label?: string + placeholder?: string + pattern?: string + title?: string +} + export type Customization = { name?: string logo?: string colors?: { [_ in ColorName]?: string } links?: Array<{ - name: string + title: string href: string rel?: string }> + + signIn?: { + fields?: { + username?: FieldDefinition + password?: FieldDefinition + remember?: FieldDefinition + } + } } export function buildCustomizationData({ name, logo, links, + signIn, }: Customization = {}) { return { name, logo, links, + signIn, } } diff --git a/packages/pds/example.env b/packages/pds/example.env index 37e17331f29..4a833a09769 100644 --- a/packages/pds/example.env +++ b/packages/pds/example.env @@ -31,6 +31,8 @@ PDS_OAUTH_PROVIDER_PRIMARY_COLOR="#7507e3" PDS_OAUTH_PROVIDER_ERROR_COLOR= PDS_OAUTH_PROVIDER_HOME_LINK= PDS_OAUTH_PROVIDER_TOS_LINK= +PDS_OAUTH_PROVIDER_POLICY_LINK= +PDS_OAUTH_PROVIDER_SUPPORT_LINK= # Debugging NODE_TLS_REJECT_UNAUTHORIZED=1 diff --git a/packages/pds/src/config/config.ts b/packages/pds/src/config/config.ts index 60ee0d0039d..c80f6fd7536 100644 --- a/packages/pds/src/config/config.ts +++ b/packages/pds/src/config/config.ts @@ -1,6 +1,7 @@ import path from 'node:path' import assert from 'node:assert' import { DAY, HOUR, SECOND } from '@atproto/common' +import { Customization } from '@atproto/oauth-provider' import { ServerEnvironment } from './env' // off-config but still from env: @@ -251,19 +252,39 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => { }, links: [ { - name: 'Home', + title: 'Home', href: env.oauthProviderHomeLink, - rel: 'home', + rel: 'bookmark', }, { - name: 'Terms of Service', + title: 'Terms of Service', href: env.oauthProviderTosLink, rel: 'terms-of-service', }, + { + title: 'Privacy Policy', + href: env.oauthProviderPrivacyPolicyLink, + rel: 'privacy-policy', + }, + { + title: 'Support', + href: env.oauthProviderSupportLink, + rel: 'help', + }, ].filter( (f): f is typeof f & { href: NonNullable } => f.href != null, ), + signIn: { + fields: { + username: { + label: 'Email address or handle', + pattern: '.{3,}', + title: 'Must be at least 3 characters long', + }, + password: {}, + }, + }, }, }, } @@ -377,19 +398,7 @@ export type OAuthConfig = { | false | { disableSsrf: boolean - customization: { - name: string - logo?: string - colors?: { - primary?: string - error?: string - } - links?: Array<{ - name: string - href: string - rel?: string - }> - } + customization: Customization } } diff --git a/packages/pds/src/config/env.ts b/packages/pds/src/config/env.ts index 3aaf50cf3e0..c86e14b20d3 100644 --- a/packages/pds/src/config/env.ts +++ b/packages/pds/src/config/env.ts @@ -114,6 +114,8 @@ export const readEnv = (): ServerEnvironment => { oauthProviderErrorColor: envStr('PDS_OAUTH_PROVIDER_ERROR_COLOR'), oauthProviderHomeLink: envStr('PDS_OAUTH_PROVIDER_HOME_LINK'), oauthProviderTosLink: envStr('PDS_OAUTH_PROVIDER_TOS_LINK'), + oauthProviderPrivacyPolicyLink: envStr('PDS_OAUTH_PROVIDER_POLICY_LINK'), + oauthProviderSupportLink: envStr('PDS_OAUTH_PROVIDER_SUPPORT_LINK'), } } @@ -225,4 +227,6 @@ export type ServerEnvironment = { oauthProviderErrorColor?: string oauthProviderHomeLink?: string oauthProviderTosLink?: string + oauthProviderPrivacyPolicyLink?: string + oauthProviderSupportLink?: string }