mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-04-01 19:52:50 +00:00
Add register via username and refactor email-login to username-login (#313)
* Added config option to enable/disable the email signup * Added register API call * Added register button and error handling * Show register button only if enabled in config * Renamed login handler, added dir-attribute, removed obsolete css class * Added separate registration page, changed email-login to internal-login As an username is sufficient for registration, this commit changes the email-login into an username-based login. This login method is now called "internal" in the code. This commit also introduces a new registration page instead of using the same form as for login. * Added information texts below form fields * Added error differentiation * Added CHANGELOG entry * Replace "magic string" with Enum representation * Removed password-field to DOM rewrite With the value attribute set, the password would be written to the DOM while typing. That's bad practise as attackers could read that password (e.g. with dirty CSS-hacks). * Fixed backendConfig to config renaming * Fixed links on register page being external links * Refactored error handling to use string-enum that corresponds with i18n keys * Fix chrome warnings regarding autocomplete and duplicated id * Refactor login action buttons to use callbacks and handle promises directly * Remove unnecessary async function * Added promise chaining
This commit is contained in:
parent
4054e130bb
commit
dbce0181a4
34 changed files with 347 additions and 119 deletions
|
@ -38,6 +38,7 @@
|
|||
### Changed
|
||||
|
||||
- The sign-in/sign-up functions are now on a separate page
|
||||
- The email sign-in/registration does not require an email address anymore but uses a username
|
||||
- The history shows both the entries saved in LocalStorage and the entries saved on the server together
|
||||
- The gist and pdf embeddings now use a one-click aproach similar to vimeo and youtube
|
||||
- Use [Twemoji](https://twemoji.twitter.com/) as icon font
|
||||
|
|
|
@ -10,9 +10,10 @@
|
|||
"google": true,
|
||||
"saml": true,
|
||||
"oauth2": true,
|
||||
"email": true,
|
||||
"internal": true,
|
||||
"openid": true
|
||||
},
|
||||
"allowRegister": true,
|
||||
"branding": {
|
||||
"name": "ACME Corp",
|
||||
"logo": "http://localhost:3001/acme.png"
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
"photo": "https://1.gravatar.com/avatar/767fc9c115a1b989744c755db47feb60?s=200&r=pg&d=mp",
|
||||
"name": "Test",
|
||||
"status": "ok",
|
||||
"provider": "email"
|
||||
"provider": "internal"
|
||||
}
|
||||
|
|
|
@ -181,7 +181,9 @@
|
|||
"signInVia": "لِج عبر {{service}}",
|
||||
"signIn": "لِج",
|
||||
"signOut": "خروج",
|
||||
"register": "انشئ حسابا",
|
||||
"register": {
|
||||
"title": "انشئ حسابا"
|
||||
},
|
||||
"auth": {
|
||||
"error": {}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,9 @@
|
|||
"signInVia": "Entrar a través de {{service}}",
|
||||
"signIn": "Entrar",
|
||||
"signOut": "Sortir",
|
||||
"register": "Registrar-se",
|
||||
"register": {
|
||||
"title": "Registrar-se"
|
||||
},
|
||||
"auth": {
|
||||
"error": {}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,9 @@
|
|||
"signInVia": "Přihlásit se přes {{service}}",
|
||||
"signIn": "Přihlásit",
|
||||
"signOut": "Odhlásit",
|
||||
"register": "Registrovat",
|
||||
"register": {
|
||||
"title": "Registrovat"
|
||||
},
|
||||
"auth": {
|
||||
"error": {}
|
||||
}
|
||||
|
|
|
@ -181,15 +181,16 @@
|
|||
"signInVia": "Einloggen über {{service}}",
|
||||
"signIn": "Einloggen",
|
||||
"signOut": "Ausloggen",
|
||||
"register": "Registrieren",
|
||||
"register": {
|
||||
"title": "Registrieren"
|
||||
},
|
||||
"auth": {
|
||||
"email": "E-Mail",
|
||||
"password": "Passwort",
|
||||
"username": "Benutzername",
|
||||
"error": {
|
||||
"openIdLogin": "OpenID nicht korrekt",
|
||||
"emailLogin": "E-Mail oder Passwort nicht korrekt",
|
||||
"ldapLogin": "Benutzername oder Passwort nicht korrekt"
|
||||
"usernamePassword": "Benutzername oder Passwort nicht korrekt"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -284,16 +284,25 @@
|
|||
"signInVia": "Sign in via {{service}}",
|
||||
"signIn": "Sign In",
|
||||
"signOut": "Sign Out",
|
||||
"register": "Register",
|
||||
"auth": {
|
||||
"email": "Email",
|
||||
"password": "Password",
|
||||
"username": "Username",
|
||||
"error": {
|
||||
"openIdLogin": "Invalid OpenID provided",
|
||||
"emailLogin": "Invalid email or password",
|
||||
"ldapLogin": "Invalid username or password"
|
||||
"usernamePassword": "Invalid username or password"
|
||||
}
|
||||
},
|
||||
"register": {
|
||||
"title": "Register",
|
||||
"passwordAgain": "Password (again)",
|
||||
"usernameInfo": "The username is your unique identifier for login.",
|
||||
"passwordInfo": "Choose a unique and secure password. It must contain at least 8 characters.",
|
||||
"infoTermsPrivacy": "With the registration of my user account I agree to the following terms:",
|
||||
"error": {
|
||||
"usernameExisting": "There is already an account with this username.",
|
||||
"other": "There was an error while registering your account. Just try it again."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,9 @@
|
|||
"signInVia": "Ingresar via {{service}}",
|
||||
"signIn": "Ingresar",
|
||||
"signOut": "Salir",
|
||||
"register": "Registrar",
|
||||
"register": {
|
||||
"title": "Registrar"
|
||||
},
|
||||
"auth": {
|
||||
"error": {}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,9 @@
|
|||
"signInVia": "Se connecter depuis {{service}}",
|
||||
"signIn": "Se connecter",
|
||||
"signOut": "Se déconnecter",
|
||||
"register": "S'enregistrer",
|
||||
"register": {
|
||||
"title": "S'enregistrer"
|
||||
},
|
||||
"auth": {
|
||||
"error": {}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,9 @@
|
|||
"signInVia": "Masuk menggunakan {{service}}",
|
||||
"signIn": "Masuk",
|
||||
"signOut": "Keluar",
|
||||
"register": "Daftar",
|
||||
"register": {
|
||||
"title": "Daftar"
|
||||
},
|
||||
"auth": {
|
||||
"error": {}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,9 @@
|
|||
"signInVia": "Entra con {{service}}",
|
||||
"signIn": "Entra",
|
||||
"signOut": "Disconettiti",
|
||||
"register": "Registrati",
|
||||
"register": {
|
||||
"title": "Registrati"
|
||||
},
|
||||
"auth": {
|
||||
"error": {}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,9 @@
|
|||
"signInVia": "{{service}}でサインイン",
|
||||
"signIn": "サインイン",
|
||||
"signOut": "サインアウト",
|
||||
"register": "登録",
|
||||
"register": {
|
||||
"title": "登録"
|
||||
},
|
||||
"auth": {
|
||||
"error": {}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,9 @@
|
|||
"signInVia": "Log in via {{service}}",
|
||||
"signIn": "Inloggen",
|
||||
"signOut": "Uitloggen",
|
||||
"register": "Registreren",
|
||||
"register": {
|
||||
"title": "Registreren"
|
||||
},
|
||||
"auth": {
|
||||
"error": {}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,9 @@
|
|||
"signInVia": "Zaloguj się poprzez {{service}}",
|
||||
"signIn": "Zaloguj się",
|
||||
"signOut": "Wyloguj się",
|
||||
"register": "Zarejestruj",
|
||||
"register": {
|
||||
"title": "Zarejestruj"
|
||||
},
|
||||
"auth": {
|
||||
"error": {}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,9 @@
|
|||
"signInVia": "Entrar via {{service}}",
|
||||
"signIn": "Entrar",
|
||||
"signOut": "Sair",
|
||||
"register": "Register",
|
||||
"register": {
|
||||
"title": "Register"
|
||||
},
|
||||
"auth": {
|
||||
"error": {}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,9 @@
|
|||
"signInVia": "Войти с помощью {{service}}",
|
||||
"signIn": "Войти",
|
||||
"signOut": "Выйти",
|
||||
"register": "Регистрация",
|
||||
"register": {
|
||||
"title": "Регистрация"
|
||||
},
|
||||
"auth": {
|
||||
"error": {}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,9 @@
|
|||
"signInVia": "Prihlásiť sa cez {{service}}",
|
||||
"signIn": "Prihlásiť sa",
|
||||
"signOut": "Odhlásiť sa",
|
||||
"register": "Registrovať",
|
||||
"register": {
|
||||
"title": "Registrovať"
|
||||
},
|
||||
"auth": {
|
||||
"error": {}
|
||||
}
|
||||
|
|
|
@ -180,7 +180,9 @@
|
|||
"signInVia": "Пријави се уз {{service}}",
|
||||
"signIn": "Пријави се",
|
||||
"signOut": "Одјави се",
|
||||
"register": "Региструј се",
|
||||
"register": {
|
||||
"title": "Региструј се"
|
||||
},
|
||||
"auth": {
|
||||
"error": {}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,9 @@
|
|||
"signInVia": "Logga in via {{service}}",
|
||||
"signIn": "Logga in",
|
||||
"signOut": "Logga ut",
|
||||
"register": "Registrera",
|
||||
"register": {
|
||||
"title": "Registrera"
|
||||
},
|
||||
"auth": {
|
||||
"error": {}
|
||||
}
|
||||
|
|
|
@ -180,7 +180,9 @@
|
|||
"signInVia": "Đăng nhấp với {{service}}",
|
||||
"signIn": "Đăng nhập",
|
||||
"signOut": "Đăng xuất",
|
||||
"register": "Đăng ký",
|
||||
"register": {
|
||||
"title": "Đăng ký"
|
||||
},
|
||||
"auth": {
|
||||
"error": {}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,9 @@
|
|||
"signInVia": "通过 {{service}} 登录",
|
||||
"signIn": "登录",
|
||||
"signOut": "登出",
|
||||
"register": "注册",
|
||||
"register": {
|
||||
"title": "注册"
|
||||
},
|
||||
"auth": {
|
||||
"error": {}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,9 @@
|
|||
"signInVia": "透過 {{service}} 登入",
|
||||
"signIn": "登入",
|
||||
"signOut": "登出",
|
||||
"register": "註冊",
|
||||
"register": {
|
||||
"title": "註冊"
|
||||
},
|
||||
"auth": {
|
||||
"error": {}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { RegisterError } from '../components/landing/pages/register/register'
|
||||
import { expectResponseCode, getApiUrl } from '../utils/apiUtils'
|
||||
import { defaultFetchConfig } from './default'
|
||||
|
||||
export const doEmailLogin = async (email: string, password: string): Promise<void> => {
|
||||
const response = await fetch(getApiUrl() + '/auth/email', {
|
||||
export const doInternalLogin = async (username: string, password: string): Promise<void> => {
|
||||
const response = await fetch(getApiUrl() + '/auth/internal', {
|
||||
...defaultFetchConfig,
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
username: username,
|
||||
password: password
|
||||
})
|
||||
})
|
||||
|
@ -14,6 +15,23 @@ export const doEmailLogin = async (email: string, password: string): Promise<voi
|
|||
expectResponseCode(response)
|
||||
}
|
||||
|
||||
export const doInternalRegister = async (username: string, password: string): Promise<void> => {
|
||||
const response = await fetch(getApiUrl() + '/auth/register', {
|
||||
...defaultFetchConfig,
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
username: username,
|
||||
password: password
|
||||
})
|
||||
})
|
||||
|
||||
if (response.status === 409) {
|
||||
throw new Error(RegisterError.USERNAME_EXISTING)
|
||||
}
|
||||
|
||||
expectResponseCode(response)
|
||||
}
|
||||
|
||||
export const doLdapLogin = async (username: string, password: string): Promise<void> => {
|
||||
const response = await fetch(getApiUrl() + '/auth/ldap', {
|
||||
...defaultFetchConfig,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export interface Config {
|
||||
allowAnonymous: boolean,
|
||||
allowRegister: boolean,
|
||||
authProviders: AuthProvidersState,
|
||||
branding: BrandingConfig,
|
||||
banner: BannerConfig,
|
||||
|
@ -36,7 +37,7 @@ export interface AuthProvidersState {
|
|||
google: boolean,
|
||||
saml: boolean,
|
||||
oauth2: boolean,
|
||||
email: boolean,
|
||||
internal: boolean,
|
||||
openid: boolean,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
import React, { FormEvent, useState } from 'react'
|
||||
import { Alert, Button, Card, Form } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { doEmailLogin } from '../../../../../api/auth'
|
||||
import { getAndSetUser } from '../../../../../utils/apiUtils'
|
||||
|
||||
export const ViaEMail: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
const doAsyncLogin = async () => {
|
||||
await doEmailLogin(email, password)
|
||||
await getAndSetUser()
|
||||
}
|
||||
|
||||
const onFormSubmit = (event: FormEvent) => {
|
||||
doAsyncLogin().catch(() => setError(true))
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="bg-dark mb-4">
|
||||
<Card.Body>
|
||||
<Card.Title>
|
||||
<Trans i18nKey="login.signInVia" values={{ service: 'E-Mail' }}/>
|
||||
</Card.Title>
|
||||
<Form onSubmit={onFormSubmit}>
|
||||
<Form.Group controlId="email">
|
||||
<Form.Control
|
||||
isInvalid={error}
|
||||
type="email"
|
||||
size="sm"
|
||||
placeholder={t('login.auth.email')}
|
||||
onChange={(event) => setEmail(event.currentTarget.value)} className="bg-dark text-white"
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group controlId="password">
|
||||
<Form.Control
|
||||
isInvalid={error}
|
||||
type="password"
|
||||
size="sm"
|
||||
placeholder={t('login.auth.password')}
|
||||
onChange={(event) => setPassword(event.currentTarget.value)}
|
||||
className="bg-dark text-white"/>
|
||||
</Form.Group>
|
||||
|
||||
<Alert className="small" show={error} variant="danger">
|
||||
<Trans i18nKey="login.auth.error.emailLogin"/>
|
||||
</Alert>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
|
||||
variant="primary">
|
||||
<Trans i18nKey="login.signIn"/>
|
||||
</Button>
|
||||
</Form>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
)
|
||||
}
|
81
src/components/landing/pages/login/auth/via-internal.tsx
Normal file
81
src/components/landing/pages/login/auth/via-internal.tsx
Normal file
|
@ -0,0 +1,81 @@
|
|||
import React, { FormEvent, useCallback, useState } from 'react'
|
||||
import { Alert, Button, Card, Form } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { doInternalLogin } from '../../../../../api/auth'
|
||||
import { ApplicationState } from '../../../../../redux'
|
||||
import { getAndSetUser } from '../../../../../utils/apiUtils'
|
||||
import { ShowIf } from '../../../../common/show-if/show-if'
|
||||
|
||||
export const ViaInternal: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [error, setError] = useState(false)
|
||||
const allowRegister = useSelector((state: ApplicationState) => state.config.allowRegister)
|
||||
|
||||
const onLoginSubmit = useCallback((event: FormEvent) => {
|
||||
doInternalLogin(username, password)
|
||||
.then(() => getAndSetUser())
|
||||
.catch(() => setError(true))
|
||||
event.preventDefault()
|
||||
}, [username, password])
|
||||
|
||||
return (
|
||||
<Card className="bg-dark mb-4">
|
||||
<Card.Body>
|
||||
<Card.Title>
|
||||
<Trans i18nKey="login.signInVia" values={{ service: t('login.auth.username') }}/>
|
||||
</Card.Title>
|
||||
<Form onSubmit={onLoginSubmit}>
|
||||
<Form.Group controlId="internal-username">
|
||||
<Form.Control
|
||||
isInvalid={error}
|
||||
type="text"
|
||||
size="sm"
|
||||
placeholder={t('login.auth.username')}
|
||||
onChange={(event) => setUsername(event.currentTarget.value)} className="bg-dark text-white"
|
||||
autoComplete='username'
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group controlId="internal-password">
|
||||
<Form.Control
|
||||
isInvalid={error}
|
||||
type="password"
|
||||
size="sm"
|
||||
placeholder={t('login.auth.password')}
|
||||
onChange={(event) => setPassword(event.currentTarget.value)}
|
||||
className="bg-dark text-white"
|
||||
autoComplete='current-password'
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Alert className="small" show={error} variant="danger">
|
||||
<Trans i18nKey="login.auth.error.usernamePassword"/>
|
||||
</Alert>
|
||||
|
||||
<div className='flex flex-row' dir='auto'>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
className='mx-2'>
|
||||
<Trans i18nKey="login.signIn"/>
|
||||
</Button>
|
||||
<ShowIf condition={allowRegister}>
|
||||
<Link to={'/register'}>
|
||||
<Button
|
||||
type='button'
|
||||
variant='secondary'
|
||||
className='mx-2'>
|
||||
<Trans i18nKey='login.register.title'/>
|
||||
</Button>
|
||||
</Link>
|
||||
</ShowIf>
|
||||
</div>
|
||||
</Form>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import React, { FormEvent, useState } from 'react'
|
||||
import React, { FormEvent, useCallback, useState } from 'react'
|
||||
import { Alert, Button, Card, Form } from 'react-bootstrap'
|
||||
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
@ -17,19 +17,12 @@ export const ViaLdap: React.FC = () => {
|
|||
|
||||
const name = ldapCustomName ? `${ldapCustomName} (LDAP)` : 'LDAP'
|
||||
|
||||
const doAsyncLogin = async () => {
|
||||
try {
|
||||
await doLdapLogin(username, password)
|
||||
await getAndSetUser()
|
||||
} catch {
|
||||
setError(true)
|
||||
}
|
||||
}
|
||||
|
||||
const onFormSubmit = (event: FormEvent) => {
|
||||
doAsyncLogin().catch(() => setError(true))
|
||||
const onLoginSubmit = useCallback((event: FormEvent) => {
|
||||
doLdapLogin(username, password)
|
||||
.then(() => getAndSetUser())
|
||||
.catch(() => setError(true))
|
||||
event.preventDefault()
|
||||
}
|
||||
}, [username, password])
|
||||
|
||||
return (
|
||||
<Card className="bg-dark mb-4">
|
||||
|
@ -37,29 +30,32 @@ export const ViaLdap: React.FC = () => {
|
|||
<Card.Title>
|
||||
<Trans i18nKey="login.signInVia" values={{ service: name }}/>
|
||||
</Card.Title>
|
||||
<Form onSubmit={onFormSubmit}>
|
||||
<Form.Group controlId="username">
|
||||
<Form onSubmit={onLoginSubmit}>
|
||||
<Form.Group controlId="ldap-username">
|
||||
<Form.Control
|
||||
isInvalid={error}
|
||||
type="text"
|
||||
size="sm"
|
||||
placeholder={t('login.auth.username')}
|
||||
onChange={(event) => setUsername(event.currentTarget.value)} className="bg-dark text-white"
|
||||
autoComplete='username'
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group controlId="password">
|
||||
<Form.Group controlId="ldap-password">
|
||||
<Form.Control
|
||||
isInvalid={error}
|
||||
type="password"
|
||||
size="sm"
|
||||
placeholder={t('login.auth.password')}
|
||||
onChange={(event) => setPassword(event.currentTarget.value)}
|
||||
className="bg-dark text-white"/>
|
||||
className="bg-dark text-white"
|
||||
autoComplete='current-password'
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Alert className="small" show={error} variant="danger">
|
||||
<Trans i18nKey="login.auth.error.ldapLogin"/>
|
||||
<Trans i18nKey="login.auth.error.usernamePassword"/>
|
||||
</Alert>
|
||||
|
||||
<Button
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useSelector } from 'react-redux'
|
|||
import { Redirect } from 'react-router'
|
||||
import { ApplicationState } from '../../../../redux'
|
||||
import { ShowIf } from '../../../common/show-if/show-if'
|
||||
import { ViaEMail } from './auth/via-email'
|
||||
import { ViaInternal } from './auth/via-internal'
|
||||
import { ViaLdap } from './auth/via-ldap'
|
||||
import { OneClickType, ViaOneClick } from './auth/via-one-click'
|
||||
import { ViaOpenId } from './auth/via-openid'
|
||||
|
@ -40,9 +40,9 @@ export const Login: React.FC = () => {
|
|||
return (
|
||||
<div className="my-3">
|
||||
<Row className="h-100 flex justify-content-center">
|
||||
<ShowIf condition={authProviders.email || authProviders.ldap || authProviders.openid}>
|
||||
<ShowIf condition={authProviders.internal || authProviders.ldap || authProviders.openid}>
|
||||
<Col xs={12} sm={10} lg={4}>
|
||||
<ShowIf condition={authProviders.email}><ViaEMail/></ShowIf>
|
||||
<ShowIf condition={authProviders.internal}><ViaInternal/></ShowIf>
|
||||
<ShowIf condition={authProviders.ldap}><ViaLdap/></ShowIf>
|
||||
<ShowIf condition={authProviders.openid}><ViaOpenId/></ShowIf>
|
||||
</Col>
|
||||
|
|
|
@ -23,7 +23,7 @@ export const Profile: React.FC = () => {
|
|||
<Row className="h-100 flex justify-content-center">
|
||||
<Col lg={6}>
|
||||
<ProfileDisplayName/>
|
||||
<ShowIf condition={user.provider === LoginProvider.EMAIL}>
|
||||
<ShowIf condition={user.provider === LoginProvider.INTERNAL}>
|
||||
<ProfileChangePassword/>
|
||||
</ShowIf>
|
||||
<ProfileAccountManagement/>
|
||||
|
|
141
src/components/landing/pages/register/register.tsx
Normal file
141
src/components/landing/pages/register/register.tsx
Normal file
|
@ -0,0 +1,141 @@
|
|||
import React, { FormEvent, useCallback, useEffect, useState } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Redirect } from 'react-router'
|
||||
import { doInternalRegister } from '../../../../api/auth'
|
||||
import { ApplicationState } from '../../../../redux'
|
||||
import { getAndSetUser } from '../../../../utils/apiUtils'
|
||||
import { Row, Col, Card, Form, Button, Alert } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { TranslatedExternalLink } from '../../../common/links/translated-external-link'
|
||||
import { ShowIf } from '../../../common/show-if/show-if'
|
||||
|
||||
export enum RegisterError {
|
||||
NONE = 'none',
|
||||
USERNAME_EXISTING = 'usernameExisting',
|
||||
OTHER = 'other'
|
||||
}
|
||||
|
||||
export const Register: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const config = useSelector((state: ApplicationState) => state.config)
|
||||
const user = useSelector((state: ApplicationState) => state.user)
|
||||
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [passwordAgain, setPasswordAgain] = useState('')
|
||||
const [error, setError] = useState(RegisterError.NONE)
|
||||
const [ready, setReady] = useState(false)
|
||||
|
||||
const doRegisterSubmit = useCallback((event: FormEvent) => {
|
||||
doInternalRegister(username, password)
|
||||
.then(() => getAndSetUser())
|
||||
.catch((err: Error) => {
|
||||
console.error(err)
|
||||
setError(err.message === RegisterError.USERNAME_EXISTING ? err.message : RegisterError.OTHER)
|
||||
})
|
||||
event.preventDefault()
|
||||
}, [username, password])
|
||||
|
||||
useEffect(() => {
|
||||
setReady(username !== '' && password !== '' && password.length >= 8 && password === passwordAgain)
|
||||
}, [username, password, passwordAgain])
|
||||
|
||||
if (!config.allowRegister) {
|
||||
return (
|
||||
<Redirect to={'/login'}/>
|
||||
)
|
||||
}
|
||||
|
||||
if (user) {
|
||||
return (
|
||||
<Redirect to={'/intro'}/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='my-3'>
|
||||
<h1 className='mb-4'><Trans i18nKey='login.register.title'/></h1>
|
||||
<Row className='h-100 d-flex justify-content-center'>
|
||||
<Col lg={6}>
|
||||
<Card className='bg-dark mb-4 text-start'>
|
||||
<Card.Body>
|
||||
<Form onSubmit={doRegisterSubmit}>
|
||||
<Form.Group controlId='username'>
|
||||
<Form.Label><Trans i18nKey='login.auth.username'/></Form.Label>
|
||||
<Form.Control
|
||||
type='text'
|
||||
size='sm'
|
||||
value={username}
|
||||
isValid={username !== ''}
|
||||
onChange={(event) => setUsername(event.target.value)}
|
||||
placeholder={t('login.auth.username')}
|
||||
className='bg-dark text-white'
|
||||
autoComplete='username'
|
||||
autoFocus={true}
|
||||
required
|
||||
/>
|
||||
<Form.Text><Trans i18nKey='login.register.usernameInfo'/></Form.Text>
|
||||
</Form.Group>
|
||||
<Form.Group controlId='password'>
|
||||
<Form.Label><Trans i18nKey='login.auth.password'/></Form.Label>
|
||||
<Form.Control
|
||||
type='password'
|
||||
size='sm'
|
||||
isValid={password !== '' && password.length >= 8}
|
||||
onChange={(event) => setPassword(event.target.value)}
|
||||
placeholder={t('login.auth.password')}
|
||||
className='bg-dark text-white'
|
||||
minLength={8}
|
||||
autoComplete='new-password'
|
||||
required
|
||||
/>
|
||||
<Form.Text><Trans i18nKey='login.register.passwordInfo'/></Form.Text>
|
||||
</Form.Group>
|
||||
<Form.Group controlId='re-password'>
|
||||
<Form.Label><Trans i18nKey='login.register.passwordAgain'/></Form.Label>
|
||||
<Form.Control
|
||||
type='password'
|
||||
size='sm'
|
||||
isInvalid={passwordAgain !== '' && password !== passwordAgain}
|
||||
isValid={passwordAgain !== '' && password === passwordAgain}
|
||||
onChange={(event) => setPasswordAgain(event.target.value)}
|
||||
placeholder={t('login.register.passwordAgain')}
|
||||
className='bg-dark text-white'
|
||||
autoComplete='new-password'
|
||||
required
|
||||
/>
|
||||
</Form.Group>
|
||||
<ShowIf condition={!!config.specialLinks?.termsOfUse || !!config.specialLinks?.privacy}>
|
||||
<Trans i18nKey='login.register.infoTermsPrivacy'/>
|
||||
<ul>
|
||||
<ShowIf condition={!!config.specialLinks?.termsOfUse}>
|
||||
<li>
|
||||
<TranslatedExternalLink i18nKey='landing.footer.termsOfUse' href={config.specialLinks.termsOfUse}/>
|
||||
</li>
|
||||
</ShowIf>
|
||||
<ShowIf condition={!!config.specialLinks?.privacy}>
|
||||
<li>
|
||||
<TranslatedExternalLink i18nKey='landing.footer.privacy' href={config.specialLinks.privacy}/>
|
||||
</li>
|
||||
</ShowIf>
|
||||
</ul>
|
||||
</ShowIf>
|
||||
<Button
|
||||
variant='primary'
|
||||
type='submit'
|
||||
block={true}
|
||||
disabled={!ready}>
|
||||
<Trans i18nKey='login.register.title'/>
|
||||
</Button>
|
||||
</Form>
|
||||
<br/>
|
||||
<Alert show={error !== RegisterError.NONE} variant='danger'>
|
||||
<Trans i18nKey={`login.register.error.${error}`}/>
|
||||
</Alert>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -10,10 +10,11 @@ import { History } from './components/landing/pages/history/history'
|
|||
import { Intro } from './components/landing/pages/intro/intro'
|
||||
import { Login } from './components/landing/pages/login/login'
|
||||
import { Profile } from './components/landing/pages/profile/profile'
|
||||
import { Register } from './components/landing/pages/register/register'
|
||||
import { Redirector } from './components/redirector/redirector'
|
||||
import './global-style/index.scss'
|
||||
import * as serviceWorker from './service-worker'
|
||||
import { store } from './utils/store'
|
||||
import { Redirector } from './components/redirector/redirector'
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
|
@ -35,6 +36,11 @@ ReactDOM.render(
|
|||
<Login/>
|
||||
</LandingLayout>
|
||||
</Route>
|
||||
<Route path="/register">
|
||||
<LandingLayout>
|
||||
<Register/>
|
||||
</LandingLayout>
|
||||
</Route>
|
||||
<Route path="/profile">
|
||||
<LandingLayout>
|
||||
<Profile/>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { ConfigActions, ConfigActionType, SetConfigAction } from './types'
|
|||
|
||||
export const initialState: Config = {
|
||||
allowAnonymous: true,
|
||||
allowRegister: true,
|
||||
authProviders: {
|
||||
facebook: false,
|
||||
github: false,
|
||||
|
@ -14,7 +15,7 @@ export const initialState: Config = {
|
|||
google: false,
|
||||
saml: false,
|
||||
oauth2: false,
|
||||
email: false,
|
||||
internal: false,
|
||||
openid: false
|
||||
},
|
||||
branding: {
|
||||
|
|
|
@ -31,7 +31,7 @@ export enum LoginProvider {
|
|||
GOOGLE = 'google',
|
||||
SAML = 'saml',
|
||||
OAUTH2 = 'oauth2',
|
||||
EMAIL = 'email',
|
||||
INTERNAL = 'internal',
|
||||
LDAP = 'ldap',
|
||||
OPENID = 'openid'
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue