mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-20 02:05:21 -04: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
|
### Changed
|
||||||
|
|
||||||
- The sign-in/sign-up functions are now on a separate page
|
- 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 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
|
- 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
|
- Use [Twemoji](https://twemoji.twitter.com/) as icon font
|
||||||
|
|
|
@ -10,9 +10,10 @@
|
||||||
"google": true,
|
"google": true,
|
||||||
"saml": true,
|
"saml": true,
|
||||||
"oauth2": true,
|
"oauth2": true,
|
||||||
"email": true,
|
"internal": true,
|
||||||
"openid": true
|
"openid": true
|
||||||
},
|
},
|
||||||
|
"allowRegister": true,
|
||||||
"branding": {
|
"branding": {
|
||||||
"name": "ACME Corp",
|
"name": "ACME Corp",
|
||||||
"logo": "http://localhost:3001/acme.png"
|
"logo": "http://localhost:3001/acme.png"
|
||||||
|
|
|
@ -3,5 +3,5 @@
|
||||||
"photo": "https://1.gravatar.com/avatar/767fc9c115a1b989744c755db47feb60?s=200&r=pg&d=mp",
|
"photo": "https://1.gravatar.com/avatar/767fc9c115a1b989744c755db47feb60?s=200&r=pg&d=mp",
|
||||||
"name": "Test",
|
"name": "Test",
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"provider": "email"
|
"provider": "internal"
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,9 @@
|
||||||
"signInVia": "لِج عبر {{service}}",
|
"signInVia": "لِج عبر {{service}}",
|
||||||
"signIn": "لِج",
|
"signIn": "لِج",
|
||||||
"signOut": "خروج",
|
"signOut": "خروج",
|
||||||
"register": "انشئ حسابا",
|
"register": {
|
||||||
|
"title": "انشئ حسابا"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,9 @@
|
||||||
"signInVia": "Entrar a través de {{service}}",
|
"signInVia": "Entrar a través de {{service}}",
|
||||||
"signIn": "Entrar",
|
"signIn": "Entrar",
|
||||||
"signOut": "Sortir",
|
"signOut": "Sortir",
|
||||||
"register": "Registrar-se",
|
"register": {
|
||||||
|
"title": "Registrar-se"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,9 @@
|
||||||
"signInVia": "Přihlásit se přes {{service}}",
|
"signInVia": "Přihlásit se přes {{service}}",
|
||||||
"signIn": "Přihlásit",
|
"signIn": "Přihlásit",
|
||||||
"signOut": "Odhlásit",
|
"signOut": "Odhlásit",
|
||||||
"register": "Registrovat",
|
"register": {
|
||||||
|
"title": "Registrovat"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,15 +181,16 @@
|
||||||
"signInVia": "Einloggen über {{service}}",
|
"signInVia": "Einloggen über {{service}}",
|
||||||
"signIn": "Einloggen",
|
"signIn": "Einloggen",
|
||||||
"signOut": "Ausloggen",
|
"signOut": "Ausloggen",
|
||||||
"register": "Registrieren",
|
"register": {
|
||||||
|
"title": "Registrieren"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"email": "E-Mail",
|
"email": "E-Mail",
|
||||||
"password": "Passwort",
|
"password": "Passwort",
|
||||||
"username": "Benutzername",
|
"username": "Benutzername",
|
||||||
"error": {
|
"error": {
|
||||||
"openIdLogin": "OpenID nicht korrekt",
|
"openIdLogin": "OpenID nicht korrekt",
|
||||||
"emailLogin": "E-Mail oder Passwort nicht korrekt",
|
"usernamePassword": "Benutzername oder Passwort nicht korrekt"
|
||||||
"ldapLogin": "Benutzername oder Passwort nicht korrekt"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -284,16 +284,25 @@
|
||||||
"signInVia": "Sign in via {{service}}",
|
"signInVia": "Sign in via {{service}}",
|
||||||
"signIn": "Sign In",
|
"signIn": "Sign In",
|
||||||
"signOut": "Sign Out",
|
"signOut": "Sign Out",
|
||||||
"register": "Register",
|
|
||||||
"auth": {
|
"auth": {
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"error": {
|
"error": {
|
||||||
"openIdLogin": "Invalid OpenID provided",
|
"openIdLogin": "Invalid OpenID provided",
|
||||||
"emailLogin": "Invalid email or password",
|
"usernamePassword": "Invalid username or password"
|
||||||
"ldapLogin": "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}}",
|
"signInVia": "Ingresar via {{service}}",
|
||||||
"signIn": "Ingresar",
|
"signIn": "Ingresar",
|
||||||
"signOut": "Salir",
|
"signOut": "Salir",
|
||||||
"register": "Registrar",
|
"register": {
|
||||||
|
"title": "Registrar"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,9 @@
|
||||||
"signInVia": "Se connecter depuis {{service}}",
|
"signInVia": "Se connecter depuis {{service}}",
|
||||||
"signIn": "Se connecter",
|
"signIn": "Se connecter",
|
||||||
"signOut": "Se déconnecter",
|
"signOut": "Se déconnecter",
|
||||||
"register": "S'enregistrer",
|
"register": {
|
||||||
|
"title": "S'enregistrer"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,9 @@
|
||||||
"signInVia": "Masuk menggunakan {{service}}",
|
"signInVia": "Masuk menggunakan {{service}}",
|
||||||
"signIn": "Masuk",
|
"signIn": "Masuk",
|
||||||
"signOut": "Keluar",
|
"signOut": "Keluar",
|
||||||
"register": "Daftar",
|
"register": {
|
||||||
|
"title": "Daftar"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,9 @@
|
||||||
"signInVia": "Entra con {{service}}",
|
"signInVia": "Entra con {{service}}",
|
||||||
"signIn": "Entra",
|
"signIn": "Entra",
|
||||||
"signOut": "Disconettiti",
|
"signOut": "Disconettiti",
|
||||||
"register": "Registrati",
|
"register": {
|
||||||
|
"title": "Registrati"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,9 @@
|
||||||
"signInVia": "{{service}}でサインイン",
|
"signInVia": "{{service}}でサインイン",
|
||||||
"signIn": "サインイン",
|
"signIn": "サインイン",
|
||||||
"signOut": "サインアウト",
|
"signOut": "サインアウト",
|
||||||
"register": "登録",
|
"register": {
|
||||||
|
"title": "登録"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,9 @@
|
||||||
"signInVia": "Log in via {{service}}",
|
"signInVia": "Log in via {{service}}",
|
||||||
"signIn": "Inloggen",
|
"signIn": "Inloggen",
|
||||||
"signOut": "Uitloggen",
|
"signOut": "Uitloggen",
|
||||||
"register": "Registreren",
|
"register": {
|
||||||
|
"title": "Registreren"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,9 @@
|
||||||
"signInVia": "Zaloguj się poprzez {{service}}",
|
"signInVia": "Zaloguj się poprzez {{service}}",
|
||||||
"signIn": "Zaloguj się",
|
"signIn": "Zaloguj się",
|
||||||
"signOut": "Wyloguj się",
|
"signOut": "Wyloguj się",
|
||||||
"register": "Zarejestruj",
|
"register": {
|
||||||
|
"title": "Zarejestruj"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,9 @@
|
||||||
"signInVia": "Entrar via {{service}}",
|
"signInVia": "Entrar via {{service}}",
|
||||||
"signIn": "Entrar",
|
"signIn": "Entrar",
|
||||||
"signOut": "Sair",
|
"signOut": "Sair",
|
||||||
"register": "Register",
|
"register": {
|
||||||
|
"title": "Register"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,9 @@
|
||||||
"signInVia": "Войти с помощью {{service}}",
|
"signInVia": "Войти с помощью {{service}}",
|
||||||
"signIn": "Войти",
|
"signIn": "Войти",
|
||||||
"signOut": "Выйти",
|
"signOut": "Выйти",
|
||||||
"register": "Регистрация",
|
"register": {
|
||||||
|
"title": "Регистрация"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,9 @@
|
||||||
"signInVia": "Prihlásiť sa cez {{service}}",
|
"signInVia": "Prihlásiť sa cez {{service}}",
|
||||||
"signIn": "Prihlásiť sa",
|
"signIn": "Prihlásiť sa",
|
||||||
"signOut": "Odhlásiť sa",
|
"signOut": "Odhlásiť sa",
|
||||||
"register": "Registrovať",
|
"register": {
|
||||||
|
"title": "Registrovať"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,7 +180,9 @@
|
||||||
"signInVia": "Пријави се уз {{service}}",
|
"signInVia": "Пријави се уз {{service}}",
|
||||||
"signIn": "Пријави се",
|
"signIn": "Пријави се",
|
||||||
"signOut": "Одјави се",
|
"signOut": "Одјави се",
|
||||||
"register": "Региструј се",
|
"register": {
|
||||||
|
"title": "Региструј се"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,9 @@
|
||||||
"signInVia": "Logga in via {{service}}",
|
"signInVia": "Logga in via {{service}}",
|
||||||
"signIn": "Logga in",
|
"signIn": "Logga in",
|
||||||
"signOut": "Logga ut",
|
"signOut": "Logga ut",
|
||||||
"register": "Registrera",
|
"register": {
|
||||||
|
"title": "Registrera"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,7 +180,9 @@
|
||||||
"signInVia": "Đăng nhấp với {{service}}",
|
"signInVia": "Đăng nhấp với {{service}}",
|
||||||
"signIn": "Đăng nhập",
|
"signIn": "Đăng nhập",
|
||||||
"signOut": "Đăng xuất",
|
"signOut": "Đăng xuất",
|
||||||
"register": "Đăng ký",
|
"register": {
|
||||||
|
"title": "Đăng ký"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,9 @@
|
||||||
"signInVia": "通过 {{service}} 登录",
|
"signInVia": "通过 {{service}} 登录",
|
||||||
"signIn": "登录",
|
"signIn": "登录",
|
||||||
"signOut": "登出",
|
"signOut": "登出",
|
||||||
"register": "注册",
|
"register": {
|
||||||
|
"title": "注册"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,9 @@
|
||||||
"signInVia": "透過 {{service}} 登入",
|
"signInVia": "透過 {{service}} 登入",
|
||||||
"signIn": "登入",
|
"signIn": "登入",
|
||||||
"signOut": "登出",
|
"signOut": "登出",
|
||||||
"register": "註冊",
|
"register": {
|
||||||
|
"title": "註冊"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
|
import { RegisterError } from '../components/landing/pages/register/register'
|
||||||
import { expectResponseCode, getApiUrl } from '../utils/apiUtils'
|
import { expectResponseCode, getApiUrl } from '../utils/apiUtils'
|
||||||
import { defaultFetchConfig } from './default'
|
import { defaultFetchConfig } from './default'
|
||||||
|
|
||||||
export const doEmailLogin = async (email: string, password: string): Promise<void> => {
|
export const doInternalLogin = async (username: string, password: string): Promise<void> => {
|
||||||
const response = await fetch(getApiUrl() + '/auth/email', {
|
const response = await fetch(getApiUrl() + '/auth/internal', {
|
||||||
...defaultFetchConfig,
|
...defaultFetchConfig,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
email: email,
|
username: username,
|
||||||
password: password
|
password: password
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -14,6 +15,23 @@ export const doEmailLogin = async (email: string, password: string): Promise<voi
|
||||||
expectResponseCode(response)
|
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> => {
|
export const doLdapLogin = async (username: string, password: string): Promise<void> => {
|
||||||
const response = await fetch(getApiUrl() + '/auth/ldap', {
|
const response = await fetch(getApiUrl() + '/auth/ldap', {
|
||||||
...defaultFetchConfig,
|
...defaultFetchConfig,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export interface Config {
|
export interface Config {
|
||||||
allowAnonymous: boolean,
|
allowAnonymous: boolean,
|
||||||
|
allowRegister: boolean,
|
||||||
authProviders: AuthProvidersState,
|
authProviders: AuthProvidersState,
|
||||||
branding: BrandingConfig,
|
branding: BrandingConfig,
|
||||||
banner: BannerConfig,
|
banner: BannerConfig,
|
||||||
|
@ -36,7 +37,7 @@ export interface AuthProvidersState {
|
||||||
google: boolean,
|
google: boolean,
|
||||||
saml: boolean,
|
saml: boolean,
|
||||||
oauth2: boolean,
|
oauth2: boolean,
|
||||||
email: boolean,
|
internal: boolean,
|
||||||
openid: 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 { Alert, Button, Card, Form } from 'react-bootstrap'
|
||||||
|
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
@ -17,19 +17,12 @@ export const ViaLdap: React.FC = () => {
|
||||||
|
|
||||||
const name = ldapCustomName ? `${ldapCustomName} (LDAP)` : 'LDAP'
|
const name = ldapCustomName ? `${ldapCustomName} (LDAP)` : 'LDAP'
|
||||||
|
|
||||||
const doAsyncLogin = async () => {
|
const onLoginSubmit = useCallback((event: FormEvent) => {
|
||||||
try {
|
doLdapLogin(username, password)
|
||||||
await doLdapLogin(username, password)
|
.then(() => getAndSetUser())
|
||||||
await getAndSetUser()
|
.catch(() => setError(true))
|
||||||
} catch {
|
|
||||||
setError(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onFormSubmit = (event: FormEvent) => {
|
|
||||||
doAsyncLogin().catch(() => setError(true))
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}
|
}, [username, password])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="bg-dark mb-4">
|
<Card className="bg-dark mb-4">
|
||||||
|
@ -37,29 +30,32 @@ export const ViaLdap: React.FC = () => {
|
||||||
<Card.Title>
|
<Card.Title>
|
||||||
<Trans i18nKey="login.signInVia" values={{ service: name }}/>
|
<Trans i18nKey="login.signInVia" values={{ service: name }}/>
|
||||||
</Card.Title>
|
</Card.Title>
|
||||||
<Form onSubmit={onFormSubmit}>
|
<Form onSubmit={onLoginSubmit}>
|
||||||
<Form.Group controlId="username">
|
<Form.Group controlId="ldap-username">
|
||||||
<Form.Control
|
<Form.Control
|
||||||
isInvalid={error}
|
isInvalid={error}
|
||||||
type="text"
|
type="text"
|
||||||
size="sm"
|
size="sm"
|
||||||
placeholder={t('login.auth.username')}
|
placeholder={t('login.auth.username')}
|
||||||
onChange={(event) => setUsername(event.currentTarget.value)} className="bg-dark text-white"
|
onChange={(event) => setUsername(event.currentTarget.value)} className="bg-dark text-white"
|
||||||
|
autoComplete='username'
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
|
|
||||||
<Form.Group controlId="password">
|
<Form.Group controlId="ldap-password">
|
||||||
<Form.Control
|
<Form.Control
|
||||||
isInvalid={error}
|
isInvalid={error}
|
||||||
type="password"
|
type="password"
|
||||||
size="sm"
|
size="sm"
|
||||||
placeholder={t('login.auth.password')}
|
placeholder={t('login.auth.password')}
|
||||||
onChange={(event) => setPassword(event.currentTarget.value)}
|
onChange={(event) => setPassword(event.currentTarget.value)}
|
||||||
className="bg-dark text-white"/>
|
className="bg-dark text-white"
|
||||||
|
autoComplete='current-password'
|
||||||
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
|
|
||||||
<Alert className="small" show={error} variant="danger">
|
<Alert className="small" show={error} variant="danger">
|
||||||
<Trans i18nKey="login.auth.error.ldapLogin"/>
|
<Trans i18nKey="login.auth.error.usernamePassword"/>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { useSelector } from 'react-redux'
|
||||||
import { Redirect } from 'react-router'
|
import { Redirect } from 'react-router'
|
||||||
import { ApplicationState } from '../../../../redux'
|
import { ApplicationState } from '../../../../redux'
|
||||||
import { ShowIf } from '../../../common/show-if/show-if'
|
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 { ViaLdap } from './auth/via-ldap'
|
||||||
import { OneClickType, ViaOneClick } from './auth/via-one-click'
|
import { OneClickType, ViaOneClick } from './auth/via-one-click'
|
||||||
import { ViaOpenId } from './auth/via-openid'
|
import { ViaOpenId } from './auth/via-openid'
|
||||||
|
@ -40,9 +40,9 @@ export const Login: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="my-3">
|
<div className="my-3">
|
||||||
<Row className="h-100 flex justify-content-center">
|
<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}>
|
<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.ldap}><ViaLdap/></ShowIf>
|
||||||
<ShowIf condition={authProviders.openid}><ViaOpenId/></ShowIf>
|
<ShowIf condition={authProviders.openid}><ViaOpenId/></ShowIf>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
@ -23,7 +23,7 @@ export const Profile: React.FC = () => {
|
||||||
<Row className="h-100 flex justify-content-center">
|
<Row className="h-100 flex justify-content-center">
|
||||||
<Col lg={6}>
|
<Col lg={6}>
|
||||||
<ProfileDisplayName/>
|
<ProfileDisplayName/>
|
||||||
<ShowIf condition={user.provider === LoginProvider.EMAIL}>
|
<ShowIf condition={user.provider === LoginProvider.INTERNAL}>
|
||||||
<ProfileChangePassword/>
|
<ProfileChangePassword/>
|
||||||
</ShowIf>
|
</ShowIf>
|
||||||
<ProfileAccountManagement/>
|
<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 { Intro } from './components/landing/pages/intro/intro'
|
||||||
import { Login } from './components/landing/pages/login/login'
|
import { Login } from './components/landing/pages/login/login'
|
||||||
import { Profile } from './components/landing/pages/profile/profile'
|
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 './global-style/index.scss'
|
||||||
import * as serviceWorker from './service-worker'
|
import * as serviceWorker from './service-worker'
|
||||||
import { store } from './utils/store'
|
import { store } from './utils/store'
|
||||||
import { Redirector } from './components/redirector/redirector'
|
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
@ -35,6 +36,11 @@ ReactDOM.render(
|
||||||
<Login/>
|
<Login/>
|
||||||
</LandingLayout>
|
</LandingLayout>
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path="/register">
|
||||||
|
<LandingLayout>
|
||||||
|
<Register/>
|
||||||
|
</LandingLayout>
|
||||||
|
</Route>
|
||||||
<Route path="/profile">
|
<Route path="/profile">
|
||||||
<LandingLayout>
|
<LandingLayout>
|
||||||
<Profile/>
|
<Profile/>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { ConfigActions, ConfigActionType, SetConfigAction } from './types'
|
||||||
|
|
||||||
export const initialState: Config = {
|
export const initialState: Config = {
|
||||||
allowAnonymous: true,
|
allowAnonymous: true,
|
||||||
|
allowRegister: true,
|
||||||
authProviders: {
|
authProviders: {
|
||||||
facebook: false,
|
facebook: false,
|
||||||
github: false,
|
github: false,
|
||||||
|
@ -14,7 +15,7 @@ export const initialState: Config = {
|
||||||
google: false,
|
google: false,
|
||||||
saml: false,
|
saml: false,
|
||||||
oauth2: false,
|
oauth2: false,
|
||||||
email: false,
|
internal: false,
|
||||||
openid: false
|
openid: false
|
||||||
},
|
},
|
||||||
branding: {
|
branding: {
|
||||||
|
|
|
@ -31,7 +31,7 @@ export enum LoginProvider {
|
||||||
GOOGLE = 'google',
|
GOOGLE = 'google',
|
||||||
SAML = 'saml',
|
SAML = 'saml',
|
||||||
OAUTH2 = 'oauth2',
|
OAUTH2 = 'oauth2',
|
||||||
EMAIL = 'email',
|
INTERNAL = 'internal',
|
||||||
LDAP = 'ldap',
|
LDAP = 'ldap',
|
||||||
OPENID = 'openid'
|
OPENID = 'openid'
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue