From dbce0181a4d827ba466c1f4f250d43893c5a3967 Mon Sep 17 00:00:00 2001 From: Erik Michelson Date: Tue, 4 Aug 2020 23:13:12 +0200 Subject: [PATCH] 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 --- CHANGELOG.md | 1 + public/api/v2/config | 3 +- public/api/v2/me | 2 +- public/locales/ar.json | 4 +- public/locales/ca.json | 4 +- public/locales/cs.json | 4 +- public/locales/de.json | 7 +- public/locales/en.json | 15 +- public/locales/es.json | 4 +- public/locales/fr.json | 4 +- public/locales/id.json | 4 +- public/locales/it.json | 4 +- public/locales/ja.json | 4 +- public/locales/nl.json | 4 +- public/locales/pl.json | 4 +- public/locales/pt.json | 4 +- public/locales/ru.json | 4 +- public/locales/sk.json | 4 +- public/locales/sr.json | 4 +- public/locales/sv.json | 4 +- public/locales/vi.json | 4 +- public/locales/zh-CN.json | 4 +- public/locales/zh-TW.json | 4 +- src/api/auth.ts | 24 ++- src/api/config/types.ts | 3 +- .../landing/pages/login/auth/via-email.tsx | 64 -------- .../landing/pages/login/auth/via-internal.tsx | 81 ++++++++++ .../landing/pages/login/auth/via-ldap.tsx | 32 ++-- src/components/landing/pages/login/login.tsx | 6 +- .../landing/pages/profile/profile.tsx | 2 +- .../landing/pages/register/register.tsx | 141 ++++++++++++++++++ src/index.tsx | 8 +- src/redux/config/reducers.ts | 3 +- src/redux/user/types.ts | 2 +- 34 files changed, 347 insertions(+), 119 deletions(-) delete mode 100644 src/components/landing/pages/login/auth/via-email.tsx create mode 100644 src/components/landing/pages/login/auth/via-internal.tsx create mode 100644 src/components/landing/pages/register/register.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index a3b81911e..7643c5ad1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/public/api/v2/config b/public/api/v2/config index dcc4a4568..6a3dd78be 100644 --- a/public/api/v2/config +++ b/public/api/v2/config @@ -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" diff --git a/public/api/v2/me b/public/api/v2/me index 68a396bf5..fcf166f76 100644 --- a/public/api/v2/me +++ b/public/api/v2/me @@ -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" } diff --git a/public/locales/ar.json b/public/locales/ar.json index e41711755..8ba4b9d56 100644 --- a/public/locales/ar.json +++ b/public/locales/ar.json @@ -181,7 +181,9 @@ "signInVia": "لِج عبر {{service}}", "signIn": "لِج", "signOut": "خروج", - "register": "انشئ حسابا", + "register": { + "title": "انشئ حسابا" + }, "auth": { "error": {} } diff --git a/public/locales/ca.json b/public/locales/ca.json index 13d82b901..26a1f7c26 100644 --- a/public/locales/ca.json +++ b/public/locales/ca.json @@ -181,7 +181,9 @@ "signInVia": "Entrar a través de {{service}}", "signIn": "Entrar", "signOut": "Sortir", - "register": "Registrar-se", + "register": { + "title": "Registrar-se" + }, "auth": { "error": {} } diff --git a/public/locales/cs.json b/public/locales/cs.json index ee17680ad..1ade5352c 100644 --- a/public/locales/cs.json +++ b/public/locales/cs.json @@ -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": {} } diff --git a/public/locales/de.json b/public/locales/de.json index cf8d6e25e..24f4ab941 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -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" } } } diff --git a/public/locales/en.json b/public/locales/en.json index 6c36a3940..c651a7282 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -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." + } } } } diff --git a/public/locales/es.json b/public/locales/es.json index 0a2d0b28f..f8471d994 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -181,7 +181,9 @@ "signInVia": "Ingresar via {{service}}", "signIn": "Ingresar", "signOut": "Salir", - "register": "Registrar", + "register": { + "title": "Registrar" + }, "auth": { "error": {} } diff --git a/public/locales/fr.json b/public/locales/fr.json index 108cef49b..65e9946b5 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -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": {} } diff --git a/public/locales/id.json b/public/locales/id.json index 9d5798edf..2b7216e8f 100644 --- a/public/locales/id.json +++ b/public/locales/id.json @@ -181,7 +181,9 @@ "signInVia": "Masuk menggunakan {{service}}", "signIn": "Masuk", "signOut": "Keluar", - "register": "Daftar", + "register": { + "title": "Daftar" + }, "auth": { "error": {} } diff --git a/public/locales/it.json b/public/locales/it.json index fa0d39673..d100e2260 100644 --- a/public/locales/it.json +++ b/public/locales/it.json @@ -181,7 +181,9 @@ "signInVia": "Entra con {{service}}", "signIn": "Entra", "signOut": "Disconettiti", - "register": "Registrati", + "register": { + "title": "Registrati" + }, "auth": { "error": {} } diff --git a/public/locales/ja.json b/public/locales/ja.json index 8f61f65db..17c366e19 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -181,7 +181,9 @@ "signInVia": "{{service}}でサインイン", "signIn": "サインイン", "signOut": "サインアウト", - "register": "登録", + "register": { + "title": "登録" + }, "auth": { "error": {} } diff --git a/public/locales/nl.json b/public/locales/nl.json index 81df5bde7..9bb75da98 100644 --- a/public/locales/nl.json +++ b/public/locales/nl.json @@ -181,7 +181,9 @@ "signInVia": "Log in via {{service}}", "signIn": "Inloggen", "signOut": "Uitloggen", - "register": "Registreren", + "register": { + "title": "Registreren" + }, "auth": { "error": {} } diff --git a/public/locales/pl.json b/public/locales/pl.json index c0a71b085..c4457b991 100644 --- a/public/locales/pl.json +++ b/public/locales/pl.json @@ -181,7 +181,9 @@ "signInVia": "Zaloguj się poprzez {{service}}", "signIn": "Zaloguj się", "signOut": "Wyloguj się", - "register": "Zarejestruj", + "register": { + "title": "Zarejestruj" + }, "auth": { "error": {} } diff --git a/public/locales/pt.json b/public/locales/pt.json index 83fbfc002..f6592fa73 100644 --- a/public/locales/pt.json +++ b/public/locales/pt.json @@ -181,7 +181,9 @@ "signInVia": "Entrar via {{service}}", "signIn": "Entrar", "signOut": "Sair", - "register": "Register", + "register": { + "title": "Register" + }, "auth": { "error": {} } diff --git a/public/locales/ru.json b/public/locales/ru.json index 6556ba6c9..4e6bd86ed 100644 --- a/public/locales/ru.json +++ b/public/locales/ru.json @@ -181,7 +181,9 @@ "signInVia": "Войти с помощью {{service}}", "signIn": "Войти", "signOut": "Выйти", - "register": "Регистрация", + "register": { + "title": "Регистрация" + }, "auth": { "error": {} } diff --git a/public/locales/sk.json b/public/locales/sk.json index f2f257f26..12b7a43a1 100644 --- a/public/locales/sk.json +++ b/public/locales/sk.json @@ -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": {} } diff --git a/public/locales/sr.json b/public/locales/sr.json index 50db9a862..814b8e98c 100644 --- a/public/locales/sr.json +++ b/public/locales/sr.json @@ -180,7 +180,9 @@ "signInVia": "Пријави се уз {{service}}", "signIn": "Пријави се", "signOut": "Одјави се", - "register": "Региструј се", + "register": { + "title": "Региструј се" + }, "auth": { "error": {} } diff --git a/public/locales/sv.json b/public/locales/sv.json index e839033e8..64e4db2f8 100644 --- a/public/locales/sv.json +++ b/public/locales/sv.json @@ -181,7 +181,9 @@ "signInVia": "Logga in via {{service}}", "signIn": "Logga in", "signOut": "Logga ut", - "register": "Registrera", + "register": { + "title": "Registrera" + }, "auth": { "error": {} } diff --git a/public/locales/vi.json b/public/locales/vi.json index ae5632c2d..91dba4ea0 100644 --- a/public/locales/vi.json +++ b/public/locales/vi.json @@ -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": {} } diff --git a/public/locales/zh-CN.json b/public/locales/zh-CN.json index e1f60ff80..142f24b25 100644 --- a/public/locales/zh-CN.json +++ b/public/locales/zh-CN.json @@ -181,7 +181,9 @@ "signInVia": "通过 {{service}} 登录", "signIn": "登录", "signOut": "登出", - "register": "注册", + "register": { + "title": "注册" + }, "auth": { "error": {} } diff --git a/public/locales/zh-TW.json b/public/locales/zh-TW.json index c7b2fb2f1..f28dbc33f 100644 --- a/public/locales/zh-TW.json +++ b/public/locales/zh-TW.json @@ -181,7 +181,9 @@ "signInVia": "透過 {{service}} 登入", "signIn": "登入", "signOut": "登出", - "register": "註冊", + "register": { + "title": "註冊" + }, "auth": { "error": {} } diff --git a/src/api/auth.ts b/src/api/auth.ts index 538ab8c9c..f57547533 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -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 => { - const response = await fetch(getApiUrl() + '/auth/email', { +export const doInternalLogin = async (username: string, password: string): Promise => { + 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 => { + 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 => { const response = await fetch(getApiUrl() + '/auth/ldap', { ...defaultFetchConfig, diff --git a/src/api/config/types.ts b/src/api/config/types.ts index 75a088b5a..4728a0e6d 100644 --- a/src/api/config/types.ts +++ b/src/api/config/types.ts @@ -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, } diff --git a/src/components/landing/pages/login/auth/via-email.tsx b/src/components/landing/pages/login/auth/via-email.tsx deleted file mode 100644 index bd13449ec..000000000 --- a/src/components/landing/pages/login/auth/via-email.tsx +++ /dev/null @@ -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 ( - - - - - -
- - setEmail(event.currentTarget.value)} className="bg-dark text-white" - /> - - - - setPassword(event.currentTarget.value)} - className="bg-dark text-white"/> - - - - - - - -
-
-
- ) -} diff --git a/src/components/landing/pages/login/auth/via-internal.tsx b/src/components/landing/pages/login/auth/via-internal.tsx new file mode 100644 index 000000000..04b6b82c5 --- /dev/null +++ b/src/components/landing/pages/login/auth/via-internal.tsx @@ -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 ( + + + + + +
+ + setUsername(event.currentTarget.value)} className="bg-dark text-white" + autoComplete='username' + /> + + + + setPassword(event.currentTarget.value)} + className="bg-dark text-white" + autoComplete='current-password' + /> + + + + + + +
+ + + + + + +
+
+
+
+ ) +} diff --git a/src/components/landing/pages/login/auth/via-ldap.tsx b/src/components/landing/pages/login/auth/via-ldap.tsx index 1ea19f4c4..f69677933 100644 --- a/src/components/landing/pages/login/auth/via-ldap.tsx +++ b/src/components/landing/pages/login/auth/via-ldap.tsx @@ -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 ( @@ -37,29 +30,32 @@ export const ViaLdap: React.FC = () => { -
- + + setUsername(event.currentTarget.value)} className="bg-dark text-white" + autoComplete='username' /> - + setPassword(event.currentTarget.value)} - className="bg-dark text-white"/> + className="bg-dark text-white" + autoComplete='current-password' + /> - + + +
+ + + + +
+ + + + ) +} diff --git a/src/index.tsx b/src/index.tsx index dc0901e9c..557257100 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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( @@ -35,6 +36,11 @@ ReactDOM.render( + + + + + diff --git a/src/redux/config/reducers.ts b/src/redux/config/reducers.ts index d99d5210d..c70238986 100644 --- a/src/redux/config/reducers.ts +++ b/src/redux/config/reducers.ts @@ -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: { diff --git a/src/redux/user/types.ts b/src/redux/user/types.ts index db338b52f..3a2a5111d 100644 --- a/src/redux/user/types.ts +++ b/src/redux/user/types.ts @@ -31,7 +31,7 @@ export enum LoginProvider { GOOGLE = 'google', SAML = 'saml', OAUTH2 = 'oauth2', - EMAIL = 'email', + INTERNAL = 'internal', LDAP = 'ldap', OPENID = 'openid' }