feat: register an account

This commit is contained in:
Maël Gangloff 2024-08-05 03:11:03 +02:00
parent b569083db6
commit 322ae444b2
No known key found for this signature in database
GPG Key ID: 11FDC81C24A7F629
9 changed files with 255 additions and 108 deletions

View File

@ -0,0 +1,91 @@
import {Alert, Button, Form, Input, Space} from "antd";
import {t} from "ttag";
import React, {useContext, useEffect, useState} from "react";
import {getUser, login} from "../utils/api";
import {AuthenticatedContext} from "../pages/LoginPage";
import {useNavigate} from "react-router-dom";
type FieldType = {
username: string;
password: string;
}
export function LoginForm({ssoLogin}: { ssoLogin?: boolean }) {
const [error, setError] = useState<string>()
const navigate = useNavigate()
const {setIsAuthenticated} = useContext(AuthenticatedContext)
useEffect(() => {
getUser().then(() => {
setIsAuthenticated(true)
navigate('/home')
})
}, [])
const onFinish = (data: FieldType) => {
login(data.username, data.password).then(() => {
setIsAuthenticated(true)
navigate('/home')
}).catch((e) => {
setIsAuthenticated(false)
if (e.response.data.message !== undefined) {
setError(e.response.data.message)
} else {
setError(e.response.data['hydra:description'])
}
})
}
return <>
{error &&
<Alert
type='error'
message={t`Error`}
banner={true}
role='role'
description={error}
style={{marginBottom: '1em'}}
/>}
<Form
name="basic"
labelCol={{span: 8}}
wrapperCol={{span: 16}}
style={{maxWidth: 600}}
onFinish={onFinish}
autoComplete="off"
>
<Form.Item
label={t`E-mail`}
name="username"
rules={[{required: true, message: t`Required`}]}
>
<Input autoFocus/>
</Form.Item>
<Form.Item<FieldType>
label={t`Password`}
name="password"
rules={[{required: true, message: t`Required`}]}
>
<Input.Password/>
</Form.Item>
<Space>
<Form.Item wrapperCol={{offset: 8, span: 16}}>
<Button type="primary" htmlType="submit">
{t`Submit`}
</Button>
</Form.Item>
{ssoLogin && <Form.Item wrapperCol={{offset: 8, span: 16}}>
<Button type="dashed" htmlType="button" href="/login/oauth">
{t`Log in with SSO`}
</Button>
</Form.Item>}
</Space>
</Form>
</>
}

View File

@ -0,0 +1,70 @@
import {Alert, Button, Form, Input} from "antd";
import {t} from "ttag";
import React, {useState} from "react";
import {register} from "../utils/api";
import {useNavigate} from "react-router-dom";
type FieldType = {
username: string;
password: string;
}
export function RegisterForm() {
const [error, setError] = useState<string>()
const navigate = useNavigate()
const onFinish = (data: FieldType) => {
register(data.username, data.password).then(() => {
navigate('/home')
}).catch((e) => {
if (e.response.data.message !== undefined) {
setError(e.response.data.message)
} else {
setError(e.response.data['hydra:description'])
}
})
}
return <>
{error &&
<Alert
type='error'
message={t`Error`}
banner={true}
role='role'
description={error}
style={{marginBottom: '1em'}}
/>}
<Form
name="basic"
labelCol={{span: 8}}
wrapperCol={{span: 16}}
style={{maxWidth: 600}}
onFinish={onFinish}
autoComplete="off"
>
<Form.Item
label={t`E-mail`}
name="username"
rules={[{required: true, message: t`Required`}]}
>
<Input autoFocus/>
</Form.Item>
<Form.Item<FieldType>
label={t`Password`}
name="password"
rules={[{required: true, message: t`Required`}]}
>
<Input.Password/>
</Form.Item>
<Form.Item wrapperCol={{offset: 8, span: 16}}>
<Button block type="primary" htmlType="submit">
{t`Register`}
</Button>
</Form.Item>
</Form>
</>
}

View File

@ -1,14 +1,11 @@
import React, {createContext, useContext, useEffect, useState} from "react"; import React, {createContext, useEffect, useState} from "react";
import {Alert, Button, Card, Form, Input} from "antd"; import {Button, Card} from "antd";
import {getConfiguration, getUser, InstanceConfig, login} from "../utils/api";
import {useNavigate} from "react-router-dom";
import {t} from 'ttag' import {t} from 'ttag'
import TextPage from "./TextPage"; import TextPage from "./TextPage";
import {LoginForm} from "../components/LoginForm";
import {getConfiguration, InstanceConfig} from "../utils/api";
import {RegisterForm} from "../components/RegisterForm";
type FieldType = {
username: string;
password: string;
}
const gridStyle: React.CSSProperties = { const gridStyle: React.CSSProperties = {
width: '50%', width: '50%',
@ -19,79 +16,27 @@ export const AuthenticatedContext = createContext<any>(null)
export default function LoginPage() { export default function LoginPage() {
const [error, setError] = useState<string>() const [wantRegister, setWantRegister] = useState<boolean>(false)
const [configuration, setConfiguration] = useState<InstanceConfig>() const [configuration, setConfiguration] = useState<InstanceConfig>()
const navigate = useNavigate()
const {setIsAuthenticated} = useContext(AuthenticatedContext)
const onFinish = (data: FieldType) => { const toggleWantRegister = () => {
login(data.username, data.password).then(() => { setWantRegister(!wantRegister)
setIsAuthenticated(true)
navigate('/home')
}).catch((e) => {
setIsAuthenticated(false)
if (e.response.data.message !== undefined) {
setError(e.response.data.message)
} else {
setError(e.response.data['hydra:description'])
}
})
} }
useEffect(() => { useEffect(() => {
getUser().then(() => {
setIsAuthenticated(true)
navigate('/home')
})
getConfiguration().then(setConfiguration) getConfiguration().then(setConfiguration)
}, []) }, [])
return <Card title={t`Log in`} style={{width: '100%'}}> return <Card title={wantRegister ? t`Register` : t`Log in`} style={{width: '100%'}}>
<Card.Grid style={gridStyle} hoverable={false}> <Card.Grid style={gridStyle} hoverable={false}>
{error && {wantRegister ? <RegisterForm/> : <LoginForm ssoLogin={configuration?.ssoLogin}/>}
<Alert {
type='error' configuration?.registerEnabled &&
message={t`Error`} <Button type='link'
banner={true} block
role='role' style={{marginTop: '1em'}}
description={error} onClick={toggleWantRegister}>{wantRegister ? 'Login' : 'Create an account'}</Button>
style={{marginBottom: '1em'}} }
/>}
<Form
name="basic"
labelCol={{span: 8}}
wrapperCol={{span: 16}}
style={{maxWidth: 600}}
onFinish={onFinish}
autoComplete="off"
>
<Form.Item
label={t`Username`}
name="username"
rules={[{required: true, message: t`Required`}]}
>
<Input autoFocus/>
</Form.Item>
<Form.Item<FieldType>
label={t`Password`}
name="password"
rules={[{required: true, message: t`Required`}]}
>
<Input.Password/>
</Form.Item>
<Form.Item wrapperCol={{offset: 8, span: 16}}>
<Button block type="primary" htmlType="submit">
{t`Submit`}
</Button>
</Form.Item>
{configuration?.ssoLogin && <Form.Item wrapperCol={{offset: 8, span: 16}}>
<Button type="dashed" htmlType="button" href="/login/oauth" block>
{t`Log in with SSO`}
</Button>
</Form.Item>}
</Form>
</Card.Grid> </Card.Grid>
<Card.Grid style={gridStyle} hoverable={false}> <Card.Grid style={gridStyle} hoverable={false}>
<TextPage resource='ads.md'/> <TextPage resource='ads.md'/>

View File

@ -74,6 +74,7 @@ export interface Watchlist {
export interface InstanceConfig { export interface InstanceConfig {
ssoLogin: boolean ssoLogin: boolean
limtedFeatures: boolean limtedFeatures: boolean
registerEnabled: boolean
} }
export async function request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig): Promise<R> { export async function request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig): Promise<R> {

View File

@ -10,6 +10,16 @@ export async function login(email: string, password: string): Promise<boolean> {
return response.status === 200 return response.status === 200
} }
export async function register(email: string, password: string): Promise<boolean> {
const response = await request({
method: 'POST',
url: 'register',
data: {email, password}
})
return response.status === 201
}
export async function getUser(): Promise<User> { export async function getUser(): Promise<User> {
const response = await request<User>({ const response = await request<User>({
url: 'me' url: 'me'

View File

@ -13,7 +13,8 @@ class InstanceController extends AbstractController
$instance $instance
->setLimitedFeatures($this->getParameter('limited_features') ?? false) ->setLimitedFeatures($this->getParameter('limited_features') ?? false)
->setOauthEnabled($this->getParameter('oauth_enabled')); ->setOauthEnabled($this->getParameter('oauth_enabled') ?? false)
->setRegisterEnabled($this->getParameter('registration_enabled') ?? false);
return $instance; return $instance;
} }

View File

@ -14,6 +14,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface; use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Address;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
@ -31,6 +32,7 @@ class RegistrationController extends AbstractController
private readonly EntityManagerInterface $em, private readonly EntityManagerInterface $em,
private readonly SerializerInterface $serializer, private readonly SerializerInterface $serializer,
private readonly LoggerInterface $logger, private readonly LoggerInterface $logger,
private readonly KernelInterface $kernel
) { ) {
} }
@ -54,7 +56,7 @@ class RegistrationController extends AbstractController
$limiter = $this->userRegisterLimiter->create($request->getClientIp()); $limiter = $this->userRegisterLimiter->create($request->getClientIp());
if (false === $limiter->consume()->isAccepted()) { if (false === $this->kernel->isDebug() && false === $limiter->consume()->isAccepted()) {
$this->logger->warning('IP address {ip} was rate limited by the Registration API.', [ $this->logger->warning('IP address {ip} was rate limited by the Registration API.', [
'ip' => $request->getClientIp(), 'ip' => $request->getClientIp(),
]); ]);
@ -90,7 +92,7 @@ class RegistrationController extends AbstractController
->htmlTemplate('emails/success/confirmation_email.html.twig') ->htmlTemplate('emails/success/confirmation_email.html.twig')
); );
return $this->redirectToRoute('index'); return new Response(null, 201);
} }
#[Route('/verify/email', name: 'app_verify_email')] #[Route('/verify/email', name: 'app_verify_email')]

View File

@ -20,6 +20,8 @@ class Instance
{ {
private ?bool $oauthEnabled = null; private ?bool $oauthEnabled = null;
private ?bool $registerEnabled = null;
private ?bool $limitedFeatures = null; private ?bool $limitedFeatures = null;
public function isSsoLogin(): ?bool public function isSsoLogin(): ?bool
@ -45,4 +47,16 @@ class Instance
return $this; return $this;
} }
public function getRegisterEnabled(): ?bool
{
return $this->registerEnabled;
}
public function setRegisterEnabled(?bool $registerEnabled): static
{
$this->registerEnabled = $registerEnabled;
return $this;
}
} }

View File

@ -3,6 +3,46 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Plural-Forms: nplurals=2; plural=(n!=1);\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n"
#: assets/components/LoginForm.tsx:47
#: assets/components/RegisterForm.tsx:33
msgid "Error"
msgstr ""
#: assets/components/LoginForm.tsx:62
#: assets/components/RegisterForm.tsx:48
msgid "E-mail"
msgstr ""
#: assets/components/LoginForm.tsx:64
#: assets/components/LoginForm.tsx:72
#: assets/components/RegisterForm.tsx:50
#: assets/components/RegisterForm.tsx:58
#: assets/components/search/DomainSearchBar.tsx:23
#: assets/components/tracking/ConnectorForm.tsx:40
#: assets/components/tracking/ConnectorForm.tsx:66
#: assets/components/tracking/ConnectorForm.tsx:74
#: assets/components/tracking/ConnectorForm.tsx:81
#: assets/components/tracking/ConnectorForm.tsx:89
#: assets/components/tracking/ConnectorForm.tsx:97
#: assets/components/tracking/ConnectorForm.tsx:110
#: assets/components/tracking/ConnectorForm.tsx:119
#: assets/components/tracking/WatchlistForm.tsx:103
msgid "Required"
msgstr ""
#: assets/components/LoginForm.tsx:70
#: assets/components/RegisterForm.tsx:56
msgid "Password"
msgstr ""
#: assets/components/LoginForm.tsx:80
msgid "Submit"
msgstr ""
#: assets/components/LoginForm.tsx:85
msgid "Log in with SSO"
msgstr ""
#: assets/components/search/EventTimeline.tsx:25 #: assets/components/search/EventTimeline.tsx:25
msgid "Registration" msgid "Registration"
msgstr "" msgstr ""
@ -47,21 +87,6 @@ msgstr ""
msgid "ENUM validation expiration" msgid "ENUM validation expiration"
msgstr "" msgstr ""
#: assets/components/search/DomainSearchBar.tsx:23
#: assets/components/tracking/ConnectorForm.tsx:40
#: assets/components/tracking/ConnectorForm.tsx:66
#: assets/components/tracking/ConnectorForm.tsx:74
#: assets/components/tracking/ConnectorForm.tsx:81
#: assets/components/tracking/ConnectorForm.tsx:89
#: assets/components/tracking/ConnectorForm.tsx:97
#: assets/components/tracking/ConnectorForm.tsx:110
#: assets/components/tracking/ConnectorForm.tsx:119
#: assets/components/tracking/WatchlistForm.tsx:103
#: assets/pages/LoginPage.tsx:71
#: assets/pages/LoginPage.tsx:79
msgid "Required"
msgstr ""
#: assets/components/search/DomainSearchBar.tsx:26 #: assets/components/search/DomainSearchBar.tsx:26
#: assets/components/tracking/WatchlistForm.tsx:106 #: assets/components/tracking/WatchlistForm.tsx:106
msgid "This domain name does not appear to be valid" msgid "This domain name does not appear to be valid"
@ -326,10 +351,15 @@ msgid "Log out"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:120 #: assets/components/Sider.tsx:120
#: assets/pages/LoginPage.tsx:49 #: assets/pages/LoginPage.tsx:30
msgid "Log in" msgid "Log in"
msgstr "" msgstr ""
#: assets/components/RegisterForm.tsx:65
#: assets/pages/LoginPage.tsx:30
msgid "Register"
msgstr ""
#: assets/pages/search/DomainSearchPage.tsx:20 #: assets/pages/search/DomainSearchPage.tsx:20
msgid "Found !" msgid "Found !"
msgstr "" msgstr ""
@ -434,7 +464,6 @@ msgid ""
"their country of origin." "their country of origin."
msgstr "" msgstr ""
#: assets/pages/LoginPage.tsx:69
#: assets/pages/watchdog/UserPage.tsx:18 #: assets/pages/watchdog/UserPage.tsx:18
msgid "Username" msgid "Username"
msgstr "" msgstr ""
@ -463,22 +492,6 @@ msgstr ""
msgid "Sorry, the page you visited does not exist." msgid "Sorry, the page you visited does not exist."
msgstr "" msgstr ""
#: assets/pages/LoginPage.tsx:54
msgid "Error"
msgstr ""
#: assets/pages/LoginPage.tsx:77
msgid "Password"
msgstr ""
#: assets/pages/LoginPage.tsx:86
msgid "Submit"
msgstr ""
#: assets/pages/LoginPage.tsx:91
msgid "Log in with SSO"
msgstr ""
#: assets/utils/providers/index.tsx:11 #: assets/utils/providers/index.tsx:11
msgid "" msgid ""
"Retrieve a set of tokens from your customer account on the Provider's " "Retrieve a set of tokens from your customer account on the Provider's "