6 changed files with 224 additions and 14 deletions
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
.codeInputContainer { |
||||
.codeInput { |
||||
font-size: 1.6rem; |
||||
font-family: monospace; |
||||
max-width: 220px; |
||||
margin: auto; |
||||
text-align: center; |
||||
letter-spacing: 6px; |
||||
border-color: var(--theme-color-palette-9); |
||||
color: var(--theme-color-palette-9); |
||||
} |
||||
|
||||
.submitButton { |
||||
max-width: 150px; |
||||
margin: auto; |
||||
margin-top: 10px; |
||||
} |
||||
|
||||
display: flex; |
||||
justify-content: center; |
||||
flex-direction: column; |
||||
} |
@ -1,3 +1,175 @@
@@ -1,3 +1,175 @@
|
||||
import { FC } from 'react'; |
||||
import { Alert, Button, Input, Space, Spin, Collapse } from 'antd'; |
||||
import React, { FC, useState } from 'react'; |
||||
import styles from './FediAuthModal.module.scss'; |
||||
import { validateAccount } from '../../../utils/validators'; |
||||
|
||||
export const FediAuthModal: FC = () => <div>Component goes here</div>; |
||||
const { Panel } = Collapse; |
||||
|
||||
export type FediAuthModalProps = { |
||||
authenticated: boolean; |
||||
displayName: string; |
||||
accessToken: string; |
||||
}; |
||||
|
||||
export const FediAuthModal: FC<FediAuthModalProps> = ({ |
||||
authenticated, |
||||
displayName, |
||||
accessToken, |
||||
}) => { |
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null); |
||||
const [loading, setLoading] = useState(false); |
||||
const [valid, setValid] = useState(false); |
||||
const [account, setAccount] = useState(''); |
||||
const [code, setCode] = useState(''); |
||||
const [verifyingCode, setVerifyingCode] = useState(true); |
||||
|
||||
const message = !authenticated ? ( |
||||
<span> |
||||
Receive a direct message on the Fediverse to link your account to{' '} |
||||
<strong>{displayName}</strong>, or login as a previously linked chat user. |
||||
</span> |
||||
) : ( |
||||
<span> |
||||
<b>You are already authenticated</b>. However, you can add other domains or log in as a |
||||
different user. |
||||
</span> |
||||
); |
||||
|
||||
let errorMessageText = errorMessage; |
||||
if (errorMessageText) { |
||||
if (errorMessageText.includes('url does not support indieauth')) { |
||||
errorMessageText = 'The provided URL is either invalid or does not support IndieAuth.'; |
||||
} |
||||
} |
||||
|
||||
const validate = (acct: string) => { |
||||
setValid(validateAccount(acct)); |
||||
}; |
||||
|
||||
const onInput = (e: React.ChangeEvent<HTMLInputElement>) => { |
||||
setAccount(e.target.value); |
||||
validate(e.target.value); |
||||
}; |
||||
|
||||
const makeRequest = async (url, data) => { |
||||
const rawResponse = await fetch(url, { |
||||
method: 'POST', |
||||
headers: { |
||||
Accept: 'application/json', |
||||
'Content-Type': 'application/json', |
||||
}, |
||||
body: JSON.stringify(data), |
||||
}); |
||||
|
||||
const content = await rawResponse.json(); |
||||
if (content.message) { |
||||
setErrorMessage(content.message); |
||||
setLoading(false); |
||||
} |
||||
}; |
||||
|
||||
const submitCodePressed = async () => { |
||||
setLoading(true); |
||||
const url = `/api/auth/fediverse/verify?accessToken=${accessToken}`; |
||||
const data = { code }; |
||||
|
||||
try { |
||||
await makeRequest(url, data); |
||||
|
||||
// Success. Reload the page.
|
||||
window.location.href = '/'; |
||||
} catch (e) { |
||||
console.error(e); |
||||
setErrorMessage(e); |
||||
} |
||||
setLoading(false); |
||||
}; |
||||
|
||||
const submitAccountPressed = async () => { |
||||
if (!valid) { |
||||
return; |
||||
} |
||||
|
||||
setLoading(true); |
||||
setErrorMessage(null); |
||||
const url = `/api/auth/fediverse?accessToken=${accessToken}`; |
||||
const normalizedAccount = account.replace(/^@+/, ''); |
||||
const data = { account: normalizedAccount }; |
||||
|
||||
try { |
||||
await makeRequest(url, data); |
||||
setVerifyingCode(true); |
||||
} catch (e) { |
||||
console.error(e); |
||||
setErrorMessage(e); |
||||
} |
||||
setLoading(false); |
||||
}; |
||||
|
||||
const inputCodeStep = ( |
||||
<div> |
||||
Paste in the code that was sent to your Fediverse account. If you did not receive a code, make |
||||
sure you can accept direct messages. |
||||
<div className={styles.codeInputContainer}> |
||||
<Input |
||||
value={code} |
||||
onChange={e => setCode(e.target.value)} |
||||
className={styles.codeInput} |
||||
placeholder="123456" |
||||
maxLength={6} |
||||
/> |
||||
<Button |
||||
type="primary" |
||||
onClick={submitCodePressed} |
||||
disabled={code.length < 6} |
||||
className={styles.submitButton} |
||||
> |
||||
Verify Code |
||||
</Button> |
||||
</div> |
||||
</div> |
||||
); |
||||
|
||||
const inputAccountStep = ( |
||||
<> |
||||
<div>Your Fediverse Account</div> |
||||
<Input.Search |
||||
addonBefore="@" |
||||
onInput={onInput} |
||||
value={account} |
||||
placeholder="youraccount@yourserver.com" |
||||
status={!valid && account.length > 0 ? 'error' : undefined} |
||||
onPressEnter={submitAccountPressed} |
||||
enterButton={ |
||||
<Button onClick={submitAccountPressed} disabled={!valid}> |
||||
Authenticate with Fediverse |
||||
</Button> |
||||
} |
||||
/> |
||||
</> |
||||
); |
||||
|
||||
return ( |
||||
<Spin spinning={loading}> |
||||
<Space direction="vertical"> |
||||
{message} |
||||
{errorMessageText && ( |
||||
<Alert message="Error" description={errorMessageText} type="error" showIcon /> |
||||
)} |
||||
{verifyingCode ? inputCodeStep : inputAccountStep} |
||||
<Collapse ghost> |
||||
<Panel |
||||
key="header" |
||||
header="Learn more about using the Fediverse to authenticate with chat." |
||||
> |
||||
<p>xxxxxx</p> |
||||
</Panel> |
||||
</Collapse> |
||||
<div> |
||||
<strong>Note</strong>: This is for authentication purposes only, and no personal |
||||
information will be accessed or stored. |
||||
</div> |
||||
</Space> |
||||
</Spin> |
||||
); |
||||
}; |
||||
|
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export function validateAccount(account) { |
||||
const a = account.replace(/^@+/, ''); |
||||
const regex = |
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; |
||||
return regex.test(String(a).toLowerCase()); |
||||
} |
Loading…
Reference in new issue