Nostr: enable Nostr login

This commit is contained in:
Alex Gleason 2023-10-04 18:03:26 -05:00
parent 3cebd961ca
commit 28731f6087
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
4 changed files with 123 additions and 70 deletions

18
src/actions/nostr.ts Normal file
View File

@ -0,0 +1,18 @@
import { nip19 } from 'nostr-tools';
import { getPublicKey } from 'soapbox/features/nostr/sign';
import { type AppDispatch } from 'soapbox/store';
import { verifyCredentials } from './auth';
/** Log in with a Nostr pubkey. */
function nostrLogIn() {
return async (dispatch: AppDispatch) => {
const pubkey = await getPublicKey();
const npub = nip19.npubEncode(pubkey);
return dispatch(verifyCredentials(npub));
};
}
export { nostrLogIn };

View File

@ -245,46 +245,52 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
/> />
</FormGroup> </FormGroup>
<Input {!features.nostrSignup && (
type='email'
name='email'
placeholder={intl.formatMessage(messages.email)}
autoComplete='off'
autoCorrect='off'
autoCapitalize='off'
onChange={onInputChange}
value={params.get('email', '')}
required
/>
<Input
type='password'
name='password'
placeholder={intl.formatMessage(messages.password)}
autoComplete='off'
autoCorrect='off'
autoCapitalize='off'
onChange={onPasswordChange}
value={params.get('password', '')}
required
/>
<FormGroup
errors={passwordMismatch ? [intl.formatMessage(messages.passwordMismatch)] : undefined}
>
<Input <Input
type='password' type='email'
name='password_confirmation' name='email'
placeholder={intl.formatMessage(messages.confirm)} placeholder={intl.formatMessage(messages.email)}
autoComplete='off' autoComplete='off'
autoCorrect='off' autoCorrect='off'
autoCapitalize='off' autoCapitalize='off'
onChange={onPasswordConfirmChange} onChange={onInputChange}
onBlur={onPasswordConfirmBlur} value={params.get('email', '')}
value={passwordConfirmation}
required required
/> />
</FormGroup> )}
{!features.nostrSignup && (
<>
<Input
type='password'
name='password'
placeholder={intl.formatMessage(messages.password)}
autoComplete='off'
autoCorrect='off'
autoCapitalize='off'
onChange={onPasswordChange}
value={params.get('password', '')}
required
/>
<FormGroup
errors={passwordMismatch ? [intl.formatMessage(messages.passwordMismatch)] : undefined}
>
<Input
type='password'
name='password_confirmation'
placeholder={intl.formatMessage(messages.confirm)}
autoComplete='off'
autoCorrect='off'
autoCapitalize='off'
onChange={onPasswordConfirmChange}
onBlur={onPasswordConfirmBlur}
value={passwordConfirmation}
required
/>
</FormGroup>
</>
)}
{birthdayRequired && ( {birthdayRequired && (
<BirthdayInput <BirthdayInput

View File

@ -5,6 +5,7 @@ import { Link, Redirect } from 'react-router-dom';
import { logIn, verifyCredentials } from 'soapbox/actions/auth'; import { logIn, verifyCredentials } from 'soapbox/actions/auth';
import { fetchInstance } from 'soapbox/actions/instance'; import { fetchInstance } from 'soapbox/actions/instance';
import { nostrLogIn } from 'soapbox/actions/nostr';
import { openSidebar } from 'soapbox/actions/sidebar'; import { openSidebar } from 'soapbox/actions/sidebar';
import SiteLogo from 'soapbox/components/site-logo'; import SiteLogo from 'soapbox/components/site-logo';
import { Avatar, Button, Form, HStack, IconButton, Input, Tooltip } from 'soapbox/components/ui'; import { Avatar, Button, Form, HStack, IconButton, Input, Tooltip } from 'soapbox/components/ui';
@ -38,6 +39,12 @@ const Navbar = () => {
const onOpenSidebar = () => dispatch(openSidebar()); const onOpenSidebar = () => dispatch(openSidebar());
const handleNostrLogin = async () => {
setLoading(true);
await dispatch(nostrLogIn()).catch(console.error);
setLoading(false);
};
const handleSubmit: React.FormEventHandler = (event) => { const handleSubmit: React.FormEventHandler = (event) => {
event.preventDefault(); event.preventDefault();
setLoading(true); setLoading(true);
@ -107,50 +114,66 @@ const Navbar = () => {
</div> </div>
) : ( ) : (
<> <>
<Form className='hidden items-center space-x-2 rtl:space-x-reverse lg:flex' onSubmit={handleSubmit}> {features.nostrSignup ? (
<Input <div className='hidden items-center xl:flex'>
required <Button
value={username} theme='primary'
onChange={(event) => setUsername(event.target.value)} onClick={handleNostrLogin}
type='text' disabled={isLoading}
placeholder={intl.formatMessage(features.logInWithUsername ? messages.username : messages.email)} >
className='max-w-[200px]' {intl.formatMessage(messages.login)}
/> </Button>
</div>
) : (
<Form className='hidden items-center space-x-2 rtl:space-x-reverse xl:flex' onSubmit={handleSubmit}>
<Input
required
value={username}
onChange={(event) => setUsername(event.target.value)}
type='text'
placeholder={intl.formatMessage(features.logInWithUsername ? messages.username : messages.email)}
className='max-w-[200px]'
/>
<Input <Input
required required
value={password} value={password}
onChange={(event) => setPassword(event.target.value)} onChange={(event) => setPassword(event.target.value)}
type='password' type='password'
placeholder={intl.formatMessage(messages.password)} placeholder={intl.formatMessage(messages.password)}
className='max-w-[200px]' className='max-w-[200px]'
/> />
<Link to='/reset-password'> <Link to='/reset-password'>
<Tooltip text={intl.formatMessage(messages.forgotPassword)}> <Tooltip text={intl.formatMessage(messages.forgotPassword)}>
<IconButton <IconButton
src={require('@tabler/icons/help.svg')} src={require('@tabler/icons/help.svg')}
className='cursor-pointer bg-transparent text-gray-400 hover:text-gray-700 dark:text-gray-500 dark:hover:text-gray-200' className='cursor-pointer bg-transparent text-gray-400 hover:text-gray-700 dark:text-gray-500 dark:hover:text-gray-200'
iconClassName='h-5 w-5' iconClassName='h-5 w-5'
/> />
</Tooltip> </Tooltip>
</Link> </Link>
<Button
theme='primary'
type='submit'
disabled={isLoading}
>
{intl.formatMessage(messages.login)}
</Button>
</Form>
)}
<div className='space-x-1.5 xl:hidden'>
<Button <Button
theme='primary' theme='tertiary'
type='submit' size='sm'
disabled={isLoading} {...(features.nostrSignup ? { onClick: handleNostrLogin } : { to: '/login' })}
> >
{intl.formatMessage(messages.login)}
</Button>
</Form>
<div className='space-x-1.5 lg:hidden'>
<Button theme='tertiary' to='/login' size='sm'>
<FormattedMessage id='account.login' defaultMessage='Log In' /> <FormattedMessage id='account.login' defaultMessage='Log In' />
</Button> </Button>
{isOpen && ( {(isOpen) && (
<Button theme='primary' to='/signup' size='sm'> <Button theme='primary' to='/signup' size='sm'>
<FormattedMessage id='account.register' defaultMessage='Sign up' /> <FormattedMessage id='account.register' defaultMessage='Sign up' />
</Button> </Button>

View File

@ -685,6 +685,12 @@ const getInstanceFeatures = (instance: Instance) => {
*/ */
nostrSign: v.software === DITTO, nostrSign: v.software === DITTO,
/**
* Whether the backend uses Ditto's Nosteric way of registration.
* @see POST /api/v1/accounts
*/
nostrSignup: v.software === DITTO,
/** /**
* Add private notes to accounts. * Add private notes to accounts.
* @see POST /api/v1/accounts/:id/note * @see POST /api/v1/accounts/:id/note