mirror of
https://github.com/plankanban/planka.git
synced 2025-12-18 01:11:13 +03:00
feat: Add legal requirements (#1306)
This commit is contained in:
18
.github/workflows/build-and-test.yml
vendored
18
.github/workflows/build-and-test.yml
vendored
@@ -13,9 +13,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
POSTGRES_DB: planka
|
||||
POSTGRES_USER: user
|
||||
POSTGRES_PASSWORD: password
|
||||
POSTGRES_USERNAME: planka
|
||||
POSTGRES_PASSWORD: planka
|
||||
POSTGRES_DATABASE: planka
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -30,9 +30,9 @@ jobs:
|
||||
- name: Set up PostgreSQL
|
||||
uses: ikalnytskyi/action-setup-postgres@v5
|
||||
with:
|
||||
database: ${{ env.POSTGRES_DB }}
|
||||
username: ${{ env.POSTGRES_USER }}
|
||||
username: ${{ env.POSTGRES_USERNAME }}
|
||||
password: ${{ env.POSTGRES_PASSWORD }}
|
||||
database: ${{ env.POSTGRES_DATABASE }}
|
||||
|
||||
- name: Cache Node.js modules
|
||||
uses: actions/cache@v3
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
client/tests/setup-symlinks.sh
|
||||
cd server
|
||||
cp .env.sample .env
|
||||
sed -i "s|^DATABASE_URL=.*|DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost/${POSTGRES_DB}|" .env
|
||||
sed -i "s|^DATABASE_URL=.*|DATABASE_URL=postgresql://${POSTGRES_USERNAME}:${POSTGRES_PASSWORD}@localhost/${POSTGRES_DATABASE}|" .env
|
||||
npm run db:init
|
||||
npm start --prod &
|
||||
|
||||
@@ -67,6 +67,12 @@ jobs:
|
||||
sudo apt-get install wait-for-it -y
|
||||
wait-for-it -h localhost -p 1337 -t 10
|
||||
|
||||
- name: Seed database with terms signature
|
||||
run: |
|
||||
TERMS_SIGNATURE=$(sha256sum terms/en-US/extended.md | awk '{print $1}')
|
||||
PGPASSWORD=$POSTGRES_PASSWORD psql -h localhost -U $POSTGRES_USERNAME -d $POSTGRES_DATABASE -c "UPDATE user_account SET terms_signature = '$TERMS_SIGNATURE';"
|
||||
working-directory: ./server
|
||||
|
||||
- name: Run UI tests
|
||||
run: |
|
||||
npx playwright install chromium
|
||||
|
||||
@@ -91,8 +91,8 @@ const logout = () => ({
|
||||
payload: {},
|
||||
});
|
||||
|
||||
logout.invalidateAccessToken = () => ({
|
||||
type: ActionTypes.LOGOUT__ACCESS_TOKEN_INVALIDATE,
|
||||
logout.revokeAccessToken = () => ({
|
||||
type: ActionTypes.LOGOUT__ACCESS_TOKEN_REVOKE,
|
||||
payload: {},
|
||||
});
|
||||
|
||||
|
||||
@@ -26,10 +26,11 @@ authenticate.success = (accessToken) => ({
|
||||
},
|
||||
});
|
||||
|
||||
authenticate.failure = (error) => ({
|
||||
authenticate.failure = (error, terms) => ({
|
||||
type: ActionTypes.AUTHENTICATE__FAILURE,
|
||||
payload: {
|
||||
error,
|
||||
terms,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -45,10 +46,11 @@ authenticateWithOidc.success = (accessToken) => ({
|
||||
},
|
||||
});
|
||||
|
||||
authenticateWithOidc.failure = (error) => ({
|
||||
authenticateWithOidc.failure = (error, terms) => ({
|
||||
type: ActionTypes.WITH_OIDC_AUTHENTICATE__FAILURE,
|
||||
payload: {
|
||||
error,
|
||||
terms,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -57,9 +59,71 @@ const clearAuthenticateError = () => ({
|
||||
payload: {},
|
||||
});
|
||||
|
||||
const acceptTerms = (signature) => ({
|
||||
type: ActionTypes.TERMS_ACCEPT,
|
||||
payload: {
|
||||
signature,
|
||||
},
|
||||
});
|
||||
|
||||
acceptTerms.success = (accessToken) => ({
|
||||
type: ActionTypes.TERMS_ACCEPT__SUCCESS,
|
||||
payload: {
|
||||
accessToken,
|
||||
},
|
||||
});
|
||||
|
||||
acceptTerms.failure = (error) => ({
|
||||
type: ActionTypes.TERMS_ACCEPT__FAILURE,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const cancelTerms = () => ({
|
||||
type: ActionTypes.TERMS_CANCEL,
|
||||
payload: {},
|
||||
});
|
||||
|
||||
cancelTerms.success = () => ({
|
||||
type: ActionTypes.TERMS_CANCEL__SUCCESS,
|
||||
payload: {},
|
||||
});
|
||||
|
||||
cancelTerms.failure = (error) => ({
|
||||
type: ActionTypes.TERMS_CANCEL__FAILURE,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const updateTermsLanguage = (value) => ({
|
||||
type: ActionTypes.TERMS_LANGUAGE_UPDATE,
|
||||
payload: {
|
||||
value,
|
||||
},
|
||||
});
|
||||
|
||||
updateTermsLanguage.success = (terms) => ({
|
||||
type: ActionTypes.TERMS_LANGUAGE_UPDATE__SUCCESS,
|
||||
payload: {
|
||||
terms,
|
||||
},
|
||||
});
|
||||
|
||||
updateTermsLanguage.failure = (error) => ({
|
||||
type: ActionTypes.TERMS_LANGUAGE_UPDATE__FAILURE,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
initializeLogin,
|
||||
authenticate,
|
||||
authenticateWithOidc,
|
||||
clearAuthenticateError,
|
||||
acceptTerms,
|
||||
cancelTerms,
|
||||
updateTermsLanguage,
|
||||
};
|
||||
|
||||
@@ -13,10 +13,18 @@ const createAccessToken = (data, headers) =>
|
||||
const exchangeForAccessTokenWithOidc = (data, headers) =>
|
||||
http.post('/access-tokens/exchange-with-oidc?withHttpOnlyToken=true', data, headers);
|
||||
|
||||
// TODO: rename?
|
||||
const acceptTerms = (data, headers) => http.post('/access-tokens/accept-terms', data, headers);
|
||||
|
||||
const revokePendingToken = (data, headers) =>
|
||||
http.post('/access-tokens/revoke-pending-token', data, headers);
|
||||
|
||||
const deleteCurrentAccessToken = (headers) => http.delete('/access-tokens/me', undefined, headers);
|
||||
|
||||
export default {
|
||||
createAccessToken,
|
||||
exchangeForAccessTokenWithOidc,
|
||||
acceptTerms,
|
||||
revokePendingToken,
|
||||
deleteCurrentAccessToken,
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import http from './http';
|
||||
import socket from './socket';
|
||||
import config from './config';
|
||||
import terms from './terms';
|
||||
import accessTokens from './access-tokens';
|
||||
import webhooks from './webhooks';
|
||||
import users from './users';
|
||||
@@ -35,6 +36,7 @@ export { http, socket };
|
||||
|
||||
export default {
|
||||
...config,
|
||||
...terms,
|
||||
...accessTokens,
|
||||
...webhooks,
|
||||
...users,
|
||||
|
||||
15
client/src/api/terms.js
Normal file
15
client/src/api/terms.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import http from './http';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const getTerms = (type, language, headers) =>
|
||||
http.get(`/terms/${type}${language ? `?language=${language}` : ''}`, undefined, headers);
|
||||
|
||||
export default {
|
||||
getTerms,
|
||||
};
|
||||
@@ -16,6 +16,8 @@ import selectors from '../../../selectors';
|
||||
import entryActions from '../../../entry-actions';
|
||||
import { useForm, useNestedRef } from '../../../hooks';
|
||||
import { isUsername } from '../../../utils/validator';
|
||||
import AccessTokenSteps from '../../../constants/AccessTokenSteps';
|
||||
import TermsModal from './TermsModal';
|
||||
|
||||
import styles from './Content.module.scss';
|
||||
|
||||
@@ -45,6 +47,11 @@ const createMessage = (error) => {
|
||||
type: 'error',
|
||||
content: 'common.useSingleSignOn',
|
||||
};
|
||||
case 'Admin login required to initialize instance':
|
||||
return {
|
||||
type: 'error',
|
||||
content: 'common.adminLoginRequiredToInitializeInstance',
|
||||
};
|
||||
case 'Email already in use':
|
||||
return {
|
||||
type: 'error',
|
||||
@@ -86,6 +93,7 @@ const Content = React.memo(() => {
|
||||
isSubmitting,
|
||||
isSubmittingWithOidc,
|
||||
error,
|
||||
step,
|
||||
} = useSelector(selectors.selectAuthenticateForm);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
@@ -265,6 +273,7 @@ const Content = React.memo(() => {
|
||||
<div className={styles.coverOverlay} />
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
{step === AccessTokenSteps.ACCEPT_TERMS && <TermsModal />}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
93
client/src/components/common/Login/TermsModal.jsx
Normal file
93
client/src/components/common/Login/TermsModal.jsx
Normal file
@@ -0,0 +1,93 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Checkbox, Dropdown, Modal } from 'semantic-ui-react';
|
||||
|
||||
import selectors from '../../../selectors';
|
||||
import entryActions from '../../../entry-actions';
|
||||
import { localeByLanguage } from '../../../locales';
|
||||
import TERMS_LANGUAGES from '../../../constants/TermsLanguages';
|
||||
import Markdown from '../Markdown';
|
||||
|
||||
import styles from './TermsModal.module.scss';
|
||||
|
||||
const LOCALES = TERMS_LANGUAGES.map((language) => localeByLanguage[language]);
|
||||
|
||||
const TermsModal = React.memo(() => {
|
||||
const {
|
||||
termsForm: { payload: terms, isSubmitting, isCancelling, isLanguageUpdating },
|
||||
} = useSelector(selectors.selectAuthenticateForm);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const [t] = useTranslation();
|
||||
const [isTermsAccepted, setIsTermsAccepted] = useState(false);
|
||||
|
||||
const handleContinueClick = useCallback(() => {
|
||||
dispatch(entryActions.acceptTerms(terms.signature));
|
||||
}, [terms.signature, dispatch]);
|
||||
|
||||
const handleCancelClick = useCallback(() => {
|
||||
dispatch(entryActions.cancelTerms());
|
||||
}, [dispatch]);
|
||||
|
||||
const handleLanguageChange = useCallback(
|
||||
(_, { value }) => {
|
||||
dispatch(entryActions.updateTermsLanguage(value));
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
const handleToggleAcceptClick = useCallback((_, { checked }) => {
|
||||
setIsTermsAccepted(checked);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Modal open centered={false}>
|
||||
<Modal.Content>
|
||||
<Dropdown
|
||||
fluid
|
||||
selection
|
||||
options={LOCALES.map((locale) => ({
|
||||
value: locale.language,
|
||||
flag: locale.country,
|
||||
text: locale.name,
|
||||
}))}
|
||||
value={terms.language}
|
||||
loading={isLanguageUpdating}
|
||||
disabled={isLanguageUpdating}
|
||||
className={styles.language}
|
||||
onChange={handleLanguageChange}
|
||||
/>
|
||||
<Markdown>{terms.content}</Markdown>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button
|
||||
content={t('action.cancelAndClose')}
|
||||
floated="left"
|
||||
loading={isCancelling}
|
||||
disabled={isSubmitting || isCancelling}
|
||||
className={styles.cancelButton}
|
||||
onClick={handleCancelClick}
|
||||
/>
|
||||
<Checkbox
|
||||
label={t('common.iHaveReadAndAgreeToTheseTerms')}
|
||||
onChange={handleToggleAcceptClick}
|
||||
/>
|
||||
<Button
|
||||
positive
|
||||
content={t('action.continue')}
|
||||
loading={isSubmitting}
|
||||
disabled={!isTermsAccepted || isSubmitting || isCancelling}
|
||||
onClick={handleContinueClick}
|
||||
/>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
||||
export default TermsModal;
|
||||
14
client/src/components/common/Login/TermsModal.module.scss
Normal file
14
client/src/components/common/Login/TermsModal.module.scss
Normal file
@@ -0,0 +1,14 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
:global(#app) {
|
||||
.cancelButton {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.language {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Divider, Dropdown, Header, Tab } from 'semantic-ui-react';
|
||||
|
||||
import selectors from '../../../../selectors';
|
||||
import entryActions from '../../../../entry-actions';
|
||||
import { usePopupInClosableContext } from '../../../../hooks';
|
||||
import locales from '../../../../locales';
|
||||
import EditAvatarStep from './EditAvatarStep';
|
||||
@@ -17,9 +19,6 @@ import EditUserEmailStep from '../../EditUserEmailStep';
|
||||
import EditUserPasswordStep from '../../EditUserPasswordStep';
|
||||
import UserAvatar from '../../UserAvatar';
|
||||
|
||||
import selectors from '../../../../selectors';
|
||||
import entryActions from '../../../../entry-actions';
|
||||
|
||||
import styles from './AccountPane.module.scss';
|
||||
|
||||
const AccountPane = React.memo(() => {
|
||||
|
||||
49
client/src/components/users/UserSettingsModal/TermsPane.jsx
Normal file
49
client/src/components/users/UserSettingsModal/TermsPane.jsx
Normal file
@@ -0,0 +1,49 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Loader, Tab } from 'semantic-ui-react';
|
||||
|
||||
import selectors from '../../../selectors';
|
||||
import api from '../../../api';
|
||||
import Markdown from '../../common/Markdown';
|
||||
|
||||
import styles from './TermsPane.module.scss';
|
||||
|
||||
const TermsPane = React.memo(() => {
|
||||
const type = useSelector((state) => selectors.selectCurrentUser(state).termsType);
|
||||
|
||||
const { i18n } = useTranslation();
|
||||
const [content, setContent] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchTerms() {
|
||||
let terms;
|
||||
try {
|
||||
({ item: terms } = await api.getTerms(type, i18n.resolvedLanguage));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
setContent(terms.content);
|
||||
}
|
||||
|
||||
fetchTerms();
|
||||
}, [type, i18n.resolvedLanguage]);
|
||||
|
||||
return (
|
||||
<Tab.Pane attached={false} className={styles.wrapper}>
|
||||
{content ? (
|
||||
<Markdown>{content}</Markdown>
|
||||
) : (
|
||||
<Loader active inverted inline="centered" size="small" />
|
||||
)}
|
||||
</Tab.Pane>
|
||||
);
|
||||
});
|
||||
|
||||
export default TermsPane;
|
||||
@@ -0,0 +1,11 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
:global(#app) {
|
||||
.wrapper {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import { useClosableModal } from '../../../hooks';
|
||||
import AccountPane from './AccountPane';
|
||||
import PreferencesPane from './PreferencesPane';
|
||||
import NotificationsPane from './NotificationsPane';
|
||||
import TermsPane from './TermsPane';
|
||||
import AboutPane from './AboutPane';
|
||||
|
||||
const UserSettingsModal = React.memo(() => {
|
||||
@@ -44,6 +45,12 @@ const UserSettingsModal = React.memo(() => {
|
||||
}),
|
||||
render: () => <NotificationsPane />,
|
||||
},
|
||||
{
|
||||
menuItem: t('common.terms', {
|
||||
context: 'title',
|
||||
}),
|
||||
render: () => <TermsPane />,
|
||||
},
|
||||
{
|
||||
menuItem: t('common.aboutPlanka', {
|
||||
context: 'title',
|
||||
|
||||
8
client/src/constants/AccessTokenSteps.js
Normal file
8
client/src/constants/AccessTokenSteps.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
export default {
|
||||
ACCEPT_TERMS: 'accept-terms',
|
||||
};
|
||||
@@ -26,6 +26,15 @@ export default {
|
||||
WITH_OIDC_AUTHENTICATE__SUCCESS: 'WITH_OIDC_AUTHENTICATE__SUCCESS',
|
||||
WITH_OIDC_AUTHENTICATE__FAILURE: 'WITH_OIDC_AUTHENTICATE__FAILURE',
|
||||
AUTHENTICATE_ERROR_CLEAR: 'AUTHENTICATE_ERROR_CLEAR',
|
||||
TERMS_ACCEPT: 'TERMS_ACCEPT',
|
||||
TERMS_ACCEPT__SUCCESS: 'TERMS_ACCEPT__SUCCESS',
|
||||
TERMS_ACCEPT__FAILURE: 'TERMS_ACCEPT__FAILURE',
|
||||
TERMS_CANCEL: 'TERMS_CANCEL',
|
||||
TERMS_CANCEL__SUCCESS: 'TERMS_CANCEL__SUCCESS',
|
||||
TERMS_CANCEL__FAILURE: 'TERMS_CANCEL__FAILURE',
|
||||
TERMS_LANGUAGE_UPDATE: 'TERMS_LANGUAGE_UPDATE',
|
||||
TERMS_LANGUAGE_UPDATE__SUCCESS: 'TERMS_LANGUAGE_UPDATE__SUCCESS',
|
||||
TERMS_LANGUAGE_UPDATE__FAILURE: 'TERMS_LANGUAGE_UPDATE__FAILURE',
|
||||
|
||||
/* Core */
|
||||
|
||||
@@ -35,7 +44,7 @@ export default {
|
||||
EDIT_MODE_TOGGLE: 'EDIT_MODE_TOGGLE',
|
||||
HOME_VIEW_UPDATE: 'HOME_VIEW_UPDATE',
|
||||
LOGOUT: 'LOGOUT',
|
||||
LOGOUT__ACCESS_TOKEN_INVALIDATE: 'LOGOUT__ACCESS_TOKEN_INVALIDATE',
|
||||
LOGOUT__ACCESS_TOKEN_REVOKE: 'LOGOUT__ACCESS_TOKEN_REVOKE',
|
||||
|
||||
/* Modals */
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ export default {
|
||||
AUTHENTICATE: `${PREFIX}/AUTHENTICATE`,
|
||||
WITH_OIDC_AUTHENTICATE: `${PREFIX}/WITH_OIDC_AUTHENTICATE`,
|
||||
AUTHENTICATE_ERROR_CLEAR: `${PREFIX}/AUTHENTICATE_ERROR_CLEAR`,
|
||||
TERMS_ACCEPT: `${PREFIX}/TERMS_ACCEPT`,
|
||||
TERMS_CANCEL: `${PREFIX}/TERMS_CANCEL`,
|
||||
TERMS_LANGUAGE_UPDATE: `${PREFIX}/TERMS_LANGUAGE_UPDATE`,
|
||||
|
||||
/* Core */
|
||||
|
||||
|
||||
6
client/src/constants/TermsLanguages.js
Normal file
6
client/src/constants/TermsLanguages.js
Normal file
@@ -0,0 +1,6 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
export default ['de-DE', 'en-US'];
|
||||
@@ -26,10 +26,10 @@ const updateHomeView = (value) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const logout = (invalidateAccessToken = true) => ({
|
||||
const logout = (revokeAccessToken = true) => ({
|
||||
type: EntryActionTypes.LOGOUT,
|
||||
payload: {
|
||||
invalidateAccessToken,
|
||||
revokeAccessToken,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -22,8 +22,30 @@ const clearAuthenticateError = () => ({
|
||||
payload: {},
|
||||
});
|
||||
|
||||
const acceptTerms = (signature) => ({
|
||||
type: EntryActionTypes.TERMS_ACCEPT,
|
||||
payload: {
|
||||
signature,
|
||||
},
|
||||
});
|
||||
|
||||
const cancelTerms = () => ({
|
||||
type: EntryActionTypes.TERMS_CANCEL,
|
||||
payload: {},
|
||||
});
|
||||
|
||||
const updateTermsLanguage = (value) => ({
|
||||
type: EntryActionTypes.TERMS_LANGUAGE_UPDATE,
|
||||
payload: {
|
||||
value,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
authenticate,
|
||||
authenticateWithOidc,
|
||||
clearAuthenticateError,
|
||||
acceptTerms,
|
||||
cancelTerms,
|
||||
updateTermsLanguage,
|
||||
};
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
taskListActions_title: null,
|
||||
taskList_title: null,
|
||||
team: null,
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: 'لا يوجد معاينة متاحة لهذا المرفق.',
|
||||
time: 'الوقت',
|
||||
title: 'العنوان',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: null,
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'البريد الإلكتروني مستخدم بالفعل',
|
||||
emailOrUsername: 'البريد الإلكتروني أو اسم المستخدم',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: 'بيانات الاعتماد غير صالحة',
|
||||
invalidEmailOrUsername: 'البريد الإلكتروني أو اسم المستخدم غير صالح',
|
||||
invalidPassword: 'كلمة المرور غير صالحة',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'تسجيل الدخول',
|
||||
logInWithSso: 'تسجيل الدخول باستخدام SSO',
|
||||
},
|
||||
|
||||
@@ -274,6 +274,7 @@ export default {
|
||||
taskListActions_title: null,
|
||||
taskList_title: null,
|
||||
team: null,
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: 'Няма наличен преглед за този прикачен файл.',
|
||||
time: 'Време',
|
||||
title: 'Заглавие',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: null,
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'Имейлът вече се използва',
|
||||
emailOrUsername: 'Имейл или потребителско име',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: null,
|
||||
invalidEmailOrUsername: 'Невалиден имейл или потребителско име',
|
||||
invalidPassword: 'Невалидна парола',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Вход',
|
||||
logInWithSso: 'Вход чрез SSO',
|
||||
},
|
||||
|
||||
@@ -284,6 +284,7 @@ export default {
|
||||
taskListActions_title: 'Akce seznamu úkolů',
|
||||
taskList_title: 'Seznam úkolů',
|
||||
team: 'Tým',
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: 'Pro tuto přílohu není k dispozici žádný náhled.',
|
||||
time: 'Čas',
|
||||
title: 'Titulek',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: 'Dosažený limit aktivních uživatelů',
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'E-mail se již používá',
|
||||
emailOrUsername: 'E-mail nebo uživatelské jméno',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: 'Neplatné přihlašovací údaje',
|
||||
invalidEmailOrUsername: 'Nesprávný e-mail nebo uživatelské jméno',
|
||||
invalidPassword: 'Nesprávné heslo',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Přihlásit se',
|
||||
logInWithSso: null,
|
||||
},
|
||||
|
||||
@@ -290,6 +290,7 @@ export default {
|
||||
taskListActions_title: 'Opgaveliste handlinger',
|
||||
taskList_title: 'Opgaveliste',
|
||||
team: 'Team',
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment:
|
||||
'Der er ingen forhåndsvisning tilgængelig for denne vedhæftning.',
|
||||
time: 'Tid',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: 'Grænsen for aktive brugere er nået',
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'E-mail allerede i brug',
|
||||
emailOrUsername: 'E-mail eller brugernavn',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: 'Forkerte loginoplysninger',
|
||||
invalidEmailOrUsername: 'Ugyldig e-mail eller brugernavn',
|
||||
invalidPassword: 'Ugyldig adgangskode',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Log på',
|
||||
logInWithSso: 'Log på med SSO',
|
||||
},
|
||||
|
||||
@@ -299,6 +299,7 @@ export default {
|
||||
taskListActions_title: 'Aufgaben-Aktionen',
|
||||
taskList_title: 'Aufgaben',
|
||||
team: 'Team',
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: 'Für diesen Anhang ist keine Vorschau verfügbar.',
|
||||
time: 'Zeit',
|
||||
title: 'Titel',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: 'Maximale Anzahl aktiver Benutzer erreicht',
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'E-mail Adresse wird bereits benutzt',
|
||||
emailOrUsername: 'E-Mail-Adresse oder Benutzername',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: 'Ungültige Anmeldeinformationen',
|
||||
invalidEmailOrUsername: 'Ungültige E-Mail-Adresse oder Benutzername',
|
||||
invalidPassword: 'Ungültiges Passwort',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Einloggen',
|
||||
logInWithSso: 'Einloggen mit SSO',
|
||||
},
|
||||
|
||||
@@ -300,6 +300,7 @@ export default {
|
||||
taskListActions_title: 'Ενέργειες λίστας εργασιών',
|
||||
taskList_title: 'Λίστα εργασιών',
|
||||
team: 'Ομάδα',
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment:
|
||||
'Δεν υπάρχει διαθέσιμη προεπισκόπηση για αυτό το συνημμένο.',
|
||||
time: 'Ώρα',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: 'Έχει επιτευχθεί το όριο ενεργών χρηστών',
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'Το e-mail χρησιμοποιείται ήδη',
|
||||
emailOrUsername: 'E-mail ή όνομα χρήστη',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: 'Μη έγκυρα στοιχεία σύνδεσης',
|
||||
invalidEmailOrUsername: 'Μη έγκυρο e-mail ή όνομα χρήστη',
|
||||
invalidPassword: 'Μη έγκυρος κωδικός',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Σύνδεση',
|
||||
logInWithSso: 'Σύνδεση με SSO',
|
||||
},
|
||||
|
||||
@@ -290,6 +290,7 @@ export default {
|
||||
taskListActions_title: 'Task List Actions',
|
||||
taskList_title: 'Task List',
|
||||
team: 'Team',
|
||||
terms: 'Terms',
|
||||
thereIsNoPreviewAvailableForThisAttachment:
|
||||
'There is no preview available for this attachment.',
|
||||
time: 'Time',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: 'Active users limit reached',
|
||||
adminLoginRequiredToInitializeInstance: 'Admin login required to initialize instance',
|
||||
emailAlreadyInUse: 'E-mail already in use',
|
||||
emailOrUsername: 'E-mail or username',
|
||||
iHaveReadAndAgreeToTheseTerms: 'I have read and agree to these Terms',
|
||||
invalidCredentials: 'Invalid credentials',
|
||||
invalidEmailOrUsername: 'Invalid e-mail or username',
|
||||
invalidPassword: 'Invalid password',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: 'Cancel and close',
|
||||
continue: 'Continue',
|
||||
logIn: 'Log in',
|
||||
logInWithSso: 'Log in with SSO',
|
||||
},
|
||||
|
||||
@@ -285,6 +285,7 @@ export default {
|
||||
taskListActions_title: 'Task List Actions',
|
||||
taskList_title: 'Task List',
|
||||
team: 'Team',
|
||||
terms: 'Terms',
|
||||
thereIsNoPreviewAvailableForThisAttachment:
|
||||
'There is no preview available for this attachment.',
|
||||
time: 'Time',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: 'Active users limit reached',
|
||||
adminLoginRequiredToInitializeInstance: 'Admin login required to initialize instance',
|
||||
emailAlreadyInUse: 'E-mail already in use',
|
||||
emailOrUsername: 'E-mail or username',
|
||||
iHaveReadAndAgreeToTheseTerms: 'I have read and agree to these Terms',
|
||||
invalidCredentials: 'Invalid credentials',
|
||||
invalidEmailOrUsername: 'Invalid e-mail or username',
|
||||
invalidPassword: 'Invalid password',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: 'Cancel and close',
|
||||
continue: 'Continue',
|
||||
logIn: 'Log in',
|
||||
logInWithSso: 'Log in with SSO',
|
||||
},
|
||||
|
||||
@@ -290,6 +290,7 @@ export default {
|
||||
taskListActions_title: 'Acciones de la lista de tareas',
|
||||
taskList_title: 'Lista de tareas',
|
||||
team: 'Equipo',
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment:
|
||||
'No hay vista previa disponible para este adjunto.',
|
||||
time: 'Tiempo',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: null,
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'El correo ya está en uso',
|
||||
emailOrUsername: 'Correo o nombre de usuario',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: null,
|
||||
invalidEmailOrUsername: 'Correo o nombre de usuario incorrecto',
|
||||
invalidPassword: 'Contraseña incorrecta',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Iniciar sesión',
|
||||
logInWithSso: null,
|
||||
},
|
||||
|
||||
@@ -289,6 +289,7 @@ export default {
|
||||
taskListActions_title: 'Ülesannete nimekiri tegevused',
|
||||
taskList_title: 'Ülesanne nimekiri',
|
||||
team: 'Töögrupp',
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: 'Selle manusi eelvaadet pole saadaval.',
|
||||
time: 'Aeg',
|
||||
title: 'Pealkiri',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: 'Aktiivsete kasutajate limiit on täis',
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'E-post on juba kasutusel',
|
||||
emailOrUsername: 'E-post või kasutajanimi',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: 'Vale kasutajanimi või parool',
|
||||
invalidEmailOrUsername: 'Vale e-post või kasutajanimi',
|
||||
invalidPassword: 'Vale parool',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Logi sisse',
|
||||
logInWithSso: 'Logi sisse SSO-ga',
|
||||
},
|
||||
|
||||
@@ -271,6 +271,7 @@ export default {
|
||||
taskListActions_title: null,
|
||||
taskList_title: null,
|
||||
team: null,
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: 'پیش نمایشی برای این پیوست موجود نیست.',
|
||||
time: 'زمان',
|
||||
title: 'عنوان',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: null,
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'ایمیل قبلا استفاده شده است',
|
||||
emailOrUsername: 'ایمیل یا نام کاربری',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: null,
|
||||
invalidEmailOrUsername: 'ایمیل یا نام کاربری نامعتبر است',
|
||||
invalidPassword: 'رمز عبور نامعتبر است',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'ورود',
|
||||
logInWithSso: 'ورود با SSO',
|
||||
},
|
||||
|
||||
@@ -285,6 +285,7 @@ export default {
|
||||
taskListActions_title: 'Tehtävälistan toiminnot',
|
||||
taskList_title: 'Tehtävälista',
|
||||
team: 'Tiimi',
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: 'Tälle liitteelle ei ole esikatselua saatavilla.',
|
||||
time: 'Aika',
|
||||
title: 'Otsikko',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: 'Aktiivisten käyttäjien raja saavutettu',
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'Sähköposti on jo käytössä',
|
||||
emailOrUsername: 'Sähköposti tai käyttäjänimi',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: 'Virheelliset tunnistetiedot',
|
||||
invalidEmailOrUsername: 'Virheellinen sähköposti tai käyttäjänimi',
|
||||
invalidPassword: 'Virheellinen salasana',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Kirjaudu sisään',
|
||||
logInWithSso: 'Kirjaudu SSO:lla',
|
||||
},
|
||||
|
||||
@@ -293,6 +293,7 @@ export default {
|
||||
taskListActions_title: 'Actions de la liste de tâches',
|
||||
taskList_title: 'Liste de tâches',
|
||||
team: "Mes projets d'équipe",
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment:
|
||||
"Il n'y a pas d'aperçu disponible pour cette pièce jointe.",
|
||||
time: 'Temps',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: 'La limite d’utilisateurs actifs a été atteinte',
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'E-mail déjà utilisé',
|
||||
emailOrUsername: "E-mail ou nom d'utilisateur",
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: 'Identifiants invalides',
|
||||
invalidEmailOrUsername: "E-mail ou nom d'utilisateur invalide",
|
||||
invalidPassword: 'Mot de passe invalide',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Se connecter',
|
||||
logInWithSso: "Se connecter avec l'authentification unique",
|
||||
},
|
||||
|
||||
@@ -283,6 +283,7 @@ export default {
|
||||
taskListActions_title: 'Feladatlista műveletek',
|
||||
taskList_title: 'Feladatlista',
|
||||
team: 'Csapat',
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: 'Nincs elérhető előnézet ehhez a melléklethez.',
|
||||
time: 'Idő',
|
||||
title: 'Cím',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: null,
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'Az e-mail cím már használatban van',
|
||||
emailOrUsername: 'E-mail vagy felhasználó',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: null,
|
||||
invalidEmailOrUsername: 'Érvénytelen e-mail vagy felhasználó',
|
||||
invalidPassword: 'Érvénytelen jelszó',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Belépés',
|
||||
logInWithSso: 'Belépés SSO-val',
|
||||
},
|
||||
|
||||
@@ -273,6 +273,7 @@ export default {
|
||||
taskListActions_title: null,
|
||||
taskList_title: null,
|
||||
team: null,
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment:
|
||||
'Tidak ada pratinjau yang tersedia untuk lampiran ini.',
|
||||
time: 'Waktu',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: null,
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'E-mail telah digunakan',
|
||||
emailOrUsername: 'E-mail atau username',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: null,
|
||||
invalidEmailOrUsername: 'E-mail atau username salah',
|
||||
invalidPassword: 'Kata sandi salah',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Masuk',
|
||||
logInWithSso: 'Masuk dengan SSO',
|
||||
},
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import keyBy from 'lodash/keyBy';
|
||||
|
||||
import arYE from './ar-YE';
|
||||
import bgBG from './bg-BG';
|
||||
import csCZ from './cs-CZ';
|
||||
@@ -84,3 +86,5 @@ export const embeddedLocales = locales.reduce(
|
||||
}),
|
||||
{},
|
||||
);
|
||||
|
||||
export const localeByLanguage = keyBy(locales, 'language');
|
||||
|
||||
@@ -291,6 +291,7 @@ export default {
|
||||
taskListActions_title: 'Azioni lista di task',
|
||||
taskList_title: 'Lista di task',
|
||||
team: 'Team',
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment:
|
||||
'Non è disponibile alcuna anteprima per questo allegato.',
|
||||
time: 'Tempo',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: 'Limite utenti attivi raggiunto',
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'E-mail già in uso',
|
||||
emailOrUsername: 'E-mail o username',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: 'Credenziali non valide',
|
||||
invalidEmailOrUsername: 'E-mail o username non valido',
|
||||
invalidPassword: 'Password non valida',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Accedi',
|
||||
logInWithSso: null,
|
||||
},
|
||||
|
||||
@@ -273,6 +273,7 @@ export default {
|
||||
taskListActions_title: null,
|
||||
taskList_title: null,
|
||||
team: null,
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: 'この添付ファイルにはプレビューがありません。',
|
||||
time: '時間',
|
||||
title: 'タイトル',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: null,
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'Eメールは既に使われています',
|
||||
emailOrUsername: 'Eメールまたはユーザー名',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: null,
|
||||
invalidEmailOrUsername: 'Eメールまたはユーザー名が無効',
|
||||
invalidPassword: 'パスワードが無効',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'ログイン',
|
||||
logInWithSso: 'SSOでログイン',
|
||||
},
|
||||
|
||||
@@ -271,6 +271,7 @@ export default {
|
||||
taskListActions_title: null,
|
||||
taskList_title: null,
|
||||
team: null,
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment:
|
||||
'이 첨부 파일에 대한 미리보기를 사용할 수 없습니다.',
|
||||
time: '시간',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: null,
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: '이미 사용 중인 이메일',
|
||||
emailOrUsername: '이메일 또는 사용자 이름',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: '잘못된 자격 증명',
|
||||
invalidEmailOrUsername: '잘못된 이메일 또는 사용자 이름',
|
||||
invalidPassword: '잘못된 비밀번호',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: '로그인',
|
||||
logInWithSso: 'SSO로 로그인',
|
||||
},
|
||||
|
||||
@@ -273,6 +273,7 @@ export default {
|
||||
taskListActions_title: null,
|
||||
taskList_title: null,
|
||||
team: null,
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment:
|
||||
'Er is geen voorbeeld beschikbaar voor deze bijlage.',
|
||||
time: 'Tijd',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: null,
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'E-mail is al in gebruik',
|
||||
emailOrUsername: 'E-mail of gebruikersnaam',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: null,
|
||||
invalidEmailOrUsername: 'Ongeldig e-mailadres of gebruikersnaam',
|
||||
invalidPassword: 'Ongeldig wachtwoord',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Inloggen',
|
||||
logInWithSso: 'Inloggen met SSO',
|
||||
},
|
||||
|
||||
@@ -281,6 +281,7 @@ export default {
|
||||
taskListActions_title: 'Akcje Listy Zadań',
|
||||
taskList_title: 'Lista Zadań',
|
||||
team: 'Zespół',
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: 'Brak podglądu dostępnego dla tego załącznika.',
|
||||
time: 'Czas',
|
||||
title: 'Tytuł',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: 'Osiągnięto limit aktywnych użytkowników',
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'E-mail jest już używany',
|
||||
emailOrUsername: 'E-mail lub nazwa użytkownika',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: 'Błędne dane logowania',
|
||||
invalidEmailOrUsername: 'Błędny e-mail lub nazwa użytkownika',
|
||||
invalidPassword: 'Błędne hasło',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Zaloguj',
|
||||
logInWithSso: 'Zaloguj z SSO',
|
||||
},
|
||||
|
||||
@@ -292,6 +292,7 @@ export default {
|
||||
taskListActions_title: 'Ações da Lista de Tarefas',
|
||||
taskList_title: 'Lista de Tarefas',
|
||||
team: 'Equipe',
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment:
|
||||
'Não há pré-visualização disponível para este anexo.',
|
||||
time: 'Tempo',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: null,
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'E-mail já está em uso',
|
||||
emailOrUsername: 'E-mail ou nome de usuário',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: null,
|
||||
invalidEmailOrUsername: 'E-mail ou nome de usuário inválido',
|
||||
invalidPassword: 'Senha inválida',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Entrar',
|
||||
logInWithSso: 'Entrar com SSO',
|
||||
},
|
||||
|
||||
@@ -274,6 +274,7 @@ export default {
|
||||
taskListActions_title: null,
|
||||
taskList_title: null,
|
||||
team: null,
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment:
|
||||
'Não há pré-visualização disponível para este anexo.',
|
||||
time: 'Tempo',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: null,
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'E-mail já está em uso',
|
||||
emailOrUsername: 'E-mail ou nome de utilizador',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: null,
|
||||
invalidEmailOrUsername: 'E-mail ou nome de utilizador inválido',
|
||||
invalidPassword: 'Palavra-passe inválida',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Iniciar sessão',
|
||||
logInWithSso: 'Iniciar sessão com SSO',
|
||||
},
|
||||
|
||||
@@ -273,6 +273,7 @@ export default {
|
||||
taskListActions_title: null,
|
||||
taskList_title: null,
|
||||
team: null,
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment:
|
||||
'Nu există nicio previzualizare disponibilă pentru acest atașament.',
|
||||
time: 'Timp',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: null,
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'E-mail deja utilizat',
|
||||
emailOrUsername: 'E-mail sau nume de utilizator',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: null,
|
||||
invalidEmailOrUsername: 'E-mail sau nume de utilizator introduse greșit',
|
||||
invalidPassword: 'Parola greșita',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Autentificarea',
|
||||
logInWithSso: 'Autentificarea cu SSO',
|
||||
},
|
||||
|
||||
@@ -288,6 +288,7 @@ export default {
|
||||
taskListActions_title: 'Действия с списком задач',
|
||||
taskList_title: 'Список задач',
|
||||
team: 'Команда',
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: 'Предпросмотр для этого вложения недоступен.',
|
||||
time: 'Время',
|
||||
title: 'Название',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: 'Достигнут лимит активных пользователей',
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'E-mail уже занят',
|
||||
emailOrUsername: 'E-mail или имя пользователя',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: 'Недействительные учетные данные',
|
||||
invalidEmailOrUsername: 'Неверный e-mail или имя пользователя',
|
||||
invalidPassword: 'Неверный пароль',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Войти',
|
||||
logInWithSso: 'Войти с помощью единого входа',
|
||||
},
|
||||
|
||||
@@ -273,6 +273,7 @@ export default {
|
||||
taskListActions_title: null,
|
||||
taskList_title: null,
|
||||
team: null,
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: null,
|
||||
time: 'Čas',
|
||||
title: 'Názov',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: null,
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'E-mail je už použitý',
|
||||
emailOrUsername: 'E-mail alebo používateľské meno',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: null,
|
||||
invalidEmailOrUsername: 'Nesprávny e-mail alebo používateľské meno',
|
||||
invalidPassword: 'Nesprávne heslo',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Prihlásiť sa',
|
||||
logInWithSso: null,
|
||||
},
|
||||
|
||||
@@ -273,6 +273,7 @@ export default {
|
||||
taskListActions_title: null,
|
||||
taskList_title: null,
|
||||
team: null,
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: 'Нема прегледа доступног за овај прилог.',
|
||||
time: 'Време',
|
||||
title: 'Наслов',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: null,
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'Е-пошта је већ у употреби',
|
||||
emailOrUsername: 'Е-пошта или корисничко име',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: 'Неисправни акредитиви',
|
||||
invalidEmailOrUsername: 'Неисправна е-пошта или корисничко име',
|
||||
invalidPassword: 'Неисправна лозинка',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Пријава',
|
||||
logInWithSso: 'Пријава са УП',
|
||||
},
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
taskListActions_title: null,
|
||||
taskList_title: null,
|
||||
team: null,
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: 'Nema pregleda dostupnog za ovaj prilog.',
|
||||
time: 'Vreme',
|
||||
title: 'Naslov',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: null,
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'E-pošta je već u upotrebi',
|
||||
emailOrUsername: 'E-pošta ili korisničko ime',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: 'Neispravni akreditivi',
|
||||
invalidEmailOrUsername: 'Neispravna e-pošta ili korisničko ime',
|
||||
invalidPassword: 'Neispravna lozinka',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Prijava',
|
||||
logInWithSso: 'Prijava sa UP',
|
||||
},
|
||||
|
||||
@@ -272,6 +272,7 @@ export default {
|
||||
taskListActions_title: null,
|
||||
taskList_title: null,
|
||||
team: null,
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: null,
|
||||
time: 'Tid',
|
||||
title: 'Titel',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: null,
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'E-mail används redan',
|
||||
emailOrUsername: 'E-mail eller användarnamn',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: null,
|
||||
invalidEmailOrUsername: 'Ogiltig e-mail eller användarnamn',
|
||||
invalidPassword: 'Ogiltigt lösenord',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Logga in',
|
||||
logInWithSso: null,
|
||||
},
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
taskListActions_title: null,
|
||||
taskList_title: null,
|
||||
team: null,
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: 'Bu ek için önizleme mevcut değil.',
|
||||
time: 'zaman',
|
||||
title: 'başlık',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: null,
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'E-posta adresi zaten kullanımda',
|
||||
emailOrUsername: 'E-posta adresi veya Kullanıcı adı',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: null,
|
||||
invalidEmailOrUsername: 'Geçersiz e-posta adresi veya kullanıcı adı',
|
||||
invalidPassword: 'Hatalı Şifre',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Giriş Yap',
|
||||
logInWithSso: null,
|
||||
},
|
||||
|
||||
@@ -287,6 +287,7 @@ export default {
|
||||
taskListActions_title: 'Дії для списку завдань',
|
||||
taskList_title: 'Список завдань',
|
||||
team: 'Команда',
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: 'Для цього вкладення немає доступного перегляду.',
|
||||
time: 'Час',
|
||||
title: 'Назва',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: 'Досягнуто ліміту активних користувачів',
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'Електронна пошта вже використовується',
|
||||
emailOrUsername: "Електронна пошта або ім'я користувача",
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: 'Неправильні облікові дані',
|
||||
invalidEmailOrUsername: "Неправильна електронна пошта або ім'я користувача",
|
||||
invalidPassword: 'Неправильний пароль',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Увійти',
|
||||
logInWithSso: 'Увійти за допомогою SSO',
|
||||
},
|
||||
|
||||
@@ -269,6 +269,7 @@ export default {
|
||||
taskListActions_title: null,
|
||||
taskList_title: null,
|
||||
team: null,
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: null,
|
||||
time: 'Vaqt',
|
||||
title: 'Sarlavha',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: null,
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: 'E-mail allaqachon mavjud',
|
||||
emailOrUsername: 'E-mail yoki foydalanuvchi nomi',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: null,
|
||||
invalidEmailOrUsername: "Noto'g'ri e-mail yoki foydalanuvchi nomi",
|
||||
invalidPassword: "Noto'g'ri parol",
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: 'Kirish',
|
||||
logInWithSso: null,
|
||||
},
|
||||
|
||||
@@ -271,6 +271,7 @@ export default {
|
||||
taskListActions_title: '任务列表操作',
|
||||
taskList_title: '任务列表',
|
||||
team: '团队',
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: '此附件无法预览',
|
||||
time: '时间',
|
||||
title: '标题',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: '活跃用户数已达上限',
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: '邮箱已使用',
|
||||
emailOrUsername: '邮箱或用户名',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: '无效凭证',
|
||||
invalidEmailOrUsername: '无效的邮箱或用户名',
|
||||
invalidPassword: '密码错误',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: '登录',
|
||||
logInWithSso: '使用SSO登录',
|
||||
},
|
||||
|
||||
@@ -267,6 +267,7 @@ export default {
|
||||
taskListActions_title: null,
|
||||
taskList_title: null,
|
||||
team: null,
|
||||
terms: null,
|
||||
thereIsNoPreviewAvailableForThisAttachment: '此附件無法預覽',
|
||||
time: '時間',
|
||||
title: '標題',
|
||||
|
||||
@@ -2,8 +2,10 @@ export default {
|
||||
translation: {
|
||||
common: {
|
||||
activeUsersLimitReached: null,
|
||||
adminLoginRequiredToInitializeInstance: null,
|
||||
emailAlreadyInUse: '郵箱已被使用',
|
||||
emailOrUsername: '郵箱或使用者名稱',
|
||||
iHaveReadAndAgreeToTheseTerms: null,
|
||||
invalidCredentials: null,
|
||||
invalidEmailOrUsername: '無效的郵箱或使用者名稱',
|
||||
invalidPassword: '密碼錯誤',
|
||||
@@ -20,6 +22,8 @@ export default {
|
||||
},
|
||||
|
||||
action: {
|
||||
cancelAndClose: null,
|
||||
continue: null,
|
||||
logIn: '登入',
|
||||
logInWithSso: '使用SSO登入',
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@ export default (state = initialState, { type, payload }) => {
|
||||
switch (type) {
|
||||
case ActionTypes.AUTHENTICATE__SUCCESS:
|
||||
case ActionTypes.WITH_OIDC_AUTHENTICATE__SUCCESS:
|
||||
case ActionTypes.TERMS_ACCEPT__SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
accessToken: payload.accessToken,
|
||||
|
||||
@@ -26,6 +26,7 @@ export default (state = initialState, { type, payload }) => {
|
||||
};
|
||||
case ActionTypes.AUTHENTICATE__SUCCESS:
|
||||
case ActionTypes.WITH_OIDC_AUTHENTICATE__SUCCESS:
|
||||
case ActionTypes.TERMS_ACCEPT__SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
isInitializing: true,
|
||||
|
||||
@@ -92,7 +92,7 @@ export default (state = initialState, { type, payload }) => {
|
||||
...state,
|
||||
homeView: payload.value,
|
||||
};
|
||||
case ActionTypes.LOGOUT__ACCESS_TOKEN_INVALIDATE:
|
||||
case ActionTypes.LOGOUT__ACCESS_TOKEN_REVOKE:
|
||||
return {
|
||||
...state,
|
||||
isLogouting: true,
|
||||
|
||||
@@ -16,6 +16,14 @@ const initialState = {
|
||||
isSubmitting: false,
|
||||
isSubmittingWithOidc: false,
|
||||
error: null,
|
||||
pendingToken: null,
|
||||
step: null,
|
||||
termsForm: {
|
||||
payload: null,
|
||||
isSubmitting: false,
|
||||
isCancelling: false,
|
||||
isLanguageUpdating: false,
|
||||
},
|
||||
};
|
||||
|
||||
// eslint-disable-next-line default-param-last
|
||||
@@ -41,14 +49,43 @@ export default (state = initialState, { type, payload }) => {
|
||||
};
|
||||
case ActionTypes.AUTHENTICATE__SUCCESS:
|
||||
case ActionTypes.WITH_OIDC_AUTHENTICATE__SUCCESS:
|
||||
case ActionTypes.TERMS_ACCEPT__SUCCESS:
|
||||
case ActionTypes.TERMS_CANCEL__SUCCESS:
|
||||
case ActionTypes.TERMS_CANCEL__FAILURE:
|
||||
return initialState;
|
||||
case ActionTypes.AUTHENTICATE__FAILURE:
|
||||
if (payload.terms) {
|
||||
return {
|
||||
...state,
|
||||
data: initialState.data,
|
||||
pendingToken: payload.error.pendingToken,
|
||||
step: payload.error.step,
|
||||
termsForm: {
|
||||
...state.termsForm,
|
||||
payload: payload.terms,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
isSubmitting: false,
|
||||
error: payload.error,
|
||||
};
|
||||
case ActionTypes.WITH_OIDC_AUTHENTICATE__FAILURE:
|
||||
if (payload.terms) {
|
||||
return {
|
||||
...state,
|
||||
data: initialState.data,
|
||||
pendingToken: payload.error.pendingToken,
|
||||
step: payload.error.step,
|
||||
termsForm: {
|
||||
...state.termsForm,
|
||||
payload: payload.terms,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
isSubmittingWithOidc: false,
|
||||
@@ -59,6 +96,53 @@ export default (state = initialState, { type, payload }) => {
|
||||
...state,
|
||||
error: null,
|
||||
};
|
||||
case ActionTypes.TERMS_ACCEPT:
|
||||
return {
|
||||
...state,
|
||||
termsForm: {
|
||||
...state.termsForm,
|
||||
isSubmitting: true,
|
||||
},
|
||||
};
|
||||
case ActionTypes.TERMS_ACCEPT__FAILURE:
|
||||
return {
|
||||
...initialState,
|
||||
error: payload.error,
|
||||
};
|
||||
case ActionTypes.TERMS_CANCEL:
|
||||
return {
|
||||
...state,
|
||||
pendingToken: null,
|
||||
termsForm: {
|
||||
...state.termsForm,
|
||||
isCancelling: true,
|
||||
},
|
||||
};
|
||||
case ActionTypes.TERMS_LANGUAGE_UPDATE:
|
||||
return {
|
||||
...state,
|
||||
termsForm: {
|
||||
...state.termsForm,
|
||||
isLanguageUpdating: true,
|
||||
},
|
||||
};
|
||||
case ActionTypes.TERMS_LANGUAGE_UPDATE__SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
termsForm: {
|
||||
...state.termsForm,
|
||||
payload: payload.terms,
|
||||
isLanguageUpdating: false,
|
||||
},
|
||||
};
|
||||
case ActionTypes.TERMS_LANGUAGE_UPDATE__FAILURE:
|
||||
return {
|
||||
...state,
|
||||
termsForm: {
|
||||
...state.termsForm,
|
||||
isLanguageUpdating: false,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -119,11 +119,11 @@ export function* updateHomeView(value) {
|
||||
}
|
||||
}
|
||||
|
||||
export function* logout(invalidateAccessToken) {
|
||||
export function* logout(revokeAccessToken) {
|
||||
yield call(removeAccessToken);
|
||||
|
||||
if (invalidateAccessToken) {
|
||||
yield put(actions.logout.invalidateAccessToken());
|
||||
if (revokeAccessToken) {
|
||||
yield put(actions.logout.revokeAccessToken());
|
||||
|
||||
try {
|
||||
yield call(request, api.deleteCurrentAccessToken);
|
||||
|
||||
@@ -19,8 +19,8 @@ export default function* coreWatchers() {
|
||||
takeEvery(EntryActionTypes.HOME_VIEW_UPDATE, ({ payload: { value } }) =>
|
||||
services.updateHomeView(value),
|
||||
),
|
||||
takeEvery(EntryActionTypes.LOGOUT, ({ payload: { invalidateAccessToken } }) =>
|
||||
services.logout(invalidateAccessToken),
|
||||
takeEvery(EntryActionTypes.LOGOUT, ({ payload: { revokeAccessToken } }) =>
|
||||
services.logout(revokeAccessToken),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,12 @@ export default function* loginSaga() {
|
||||
const watcherTasks = yield all(watchers.map((watcher) => fork(watcher)));
|
||||
|
||||
yield fork(services.initializeLogin);
|
||||
yield take([ActionTypes.AUTHENTICATE__SUCCESS, ActionTypes.WITH_OIDC_AUTHENTICATE__SUCCESS]);
|
||||
|
||||
yield take([
|
||||
ActionTypes.AUTHENTICATE__SUCCESS,
|
||||
ActionTypes.WITH_OIDC_AUTHENTICATE__SUCCESS,
|
||||
ActionTypes.TERMS_ACCEPT__SUCCESS,
|
||||
]);
|
||||
|
||||
yield cancel(watcherTasks);
|
||||
yield call(services.goToRoot);
|
||||
|
||||
@@ -10,8 +10,10 @@ import { replace } from '../../../lib/redux-router';
|
||||
import selectors from '../../../selectors';
|
||||
import actions from '../../../actions';
|
||||
import api from '../../../api';
|
||||
import i18n from '../../../i18n';
|
||||
import { setAccessToken } from '../../../utils/access-token-storage';
|
||||
import Paths from '../../../constants/Paths';
|
||||
import AccessTokenSteps from '../../../constants/AccessTokenSteps';
|
||||
|
||||
export function* initializeLogin() {
|
||||
const { item: config } = yield call(api.getConfig); // TODO: handle error
|
||||
@@ -26,7 +28,12 @@ export function* authenticate(data) {
|
||||
try {
|
||||
({ item: accessToken } = yield call(api.createAccessToken, data));
|
||||
} catch (error) {
|
||||
yield put(actions.authenticate.failure(error));
|
||||
let terms;
|
||||
if (error.step === AccessTokenSteps.ACCEPT_TERMS) {
|
||||
({ item: terms } = yield call(api.getTerms, error.termsType, i18n.resolvedLanguage));
|
||||
}
|
||||
|
||||
yield put(actions.authenticate.failure(error, terms));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -106,7 +113,12 @@ export function* authenticateWithOidcCallback() {
|
||||
nonce,
|
||||
}));
|
||||
} catch (error) {
|
||||
yield put(actions.authenticateWithOidc.failure(error));
|
||||
let terms;
|
||||
if (error.step === AccessTokenSteps.ACCEPT_TERMS) {
|
||||
({ item: terms } = yield call(api.getTerms, error.termsType, i18n.resolvedLanguage));
|
||||
}
|
||||
|
||||
yield put(actions.authenticateWithOidc.failure(error, terms));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -118,10 +130,70 @@ export function* clearAuthenticateError() {
|
||||
yield put(actions.clearAuthenticateError());
|
||||
}
|
||||
|
||||
export function* acceptTerms(signature) {
|
||||
yield put(actions.acceptTerms(signature));
|
||||
|
||||
const { pendingToken } = yield select(selectors.selectAuthenticateForm);
|
||||
|
||||
let accessToken;
|
||||
try {
|
||||
({ item: accessToken } = yield call(api.acceptTerms, {
|
||||
pendingToken,
|
||||
signature,
|
||||
}));
|
||||
} catch (error) {
|
||||
yield put(actions.acceptTerms.failure(error));
|
||||
return;
|
||||
}
|
||||
|
||||
yield call(setAccessToken, accessToken);
|
||||
yield put(actions.acceptTerms.success(accessToken));
|
||||
}
|
||||
|
||||
export function* cancelTerms() {
|
||||
const { pendingToken } = yield select(selectors.selectAuthenticateForm);
|
||||
|
||||
yield put(actions.cancelTerms());
|
||||
|
||||
try {
|
||||
yield call(api.revokePendingToken, {
|
||||
pendingToken,
|
||||
});
|
||||
} catch (error) {
|
||||
yield put(actions.cancelTerms.failure(error));
|
||||
return;
|
||||
}
|
||||
|
||||
yield put(actions.cancelTerms.success(pendingToken));
|
||||
}
|
||||
|
||||
export function* updateTermsLanguage(value) {
|
||||
yield put(actions.updateTermsLanguage(value));
|
||||
|
||||
const {
|
||||
termsForm: {
|
||||
payload: { type },
|
||||
},
|
||||
} = yield select(selectors.selectAuthenticateForm);
|
||||
|
||||
let terms;
|
||||
try {
|
||||
({ item: terms } = yield call(api.getTerms, type, value));
|
||||
} catch (error) {
|
||||
yield put(actions.updateTermsLanguage.failure(error));
|
||||
return;
|
||||
}
|
||||
|
||||
yield put(actions.updateTermsLanguage.success(terms));
|
||||
}
|
||||
|
||||
export default {
|
||||
initializeLogin,
|
||||
authenticate,
|
||||
authenticateWithOidc,
|
||||
authenticateWithOidcCallback,
|
||||
clearAuthenticateError,
|
||||
acceptTerms,
|
||||
cancelTerms,
|
||||
updateTermsLanguage,
|
||||
};
|
||||
|
||||
@@ -15,5 +15,12 @@ export default function* loginWatchers() {
|
||||
),
|
||||
takeEvery(EntryActionTypes.WITH_OIDC_AUTHENTICATE, () => services.authenticateWithOidc()),
|
||||
takeEvery(EntryActionTypes.AUTHENTICATE_ERROR_CLEAR, () => services.clearAuthenticateError()),
|
||||
takeEvery(EntryActionTypes.TERMS_ACCEPT, ({ payload: { signature } }) =>
|
||||
services.acceptTerms(signature),
|
||||
),
|
||||
takeEvery(EntryActionTypes.TERMS_CANCEL, () => services.cancelTerms()),
|
||||
takeEvery(EntryActionTypes.TERMS_LANGUAGE_UPDATE, ({ payload: { value } }) =>
|
||||
services.updateTermsLanguage(value),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
133
server/api/controllers/access-tokens/accept-terms.js
Normal file
133
server/api/controllers/access-tokens/accept-terms.js
Normal file
@@ -0,0 +1,133 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const { getRemoteAddress } = require('../../../utils/remote-address');
|
||||
|
||||
const { AccessTokenSteps } = require('../../../constants');
|
||||
|
||||
const Errors = {
|
||||
INVALID_PENDING_TOKEN: {
|
||||
invalidPendingToken: 'Invalid pending token',
|
||||
},
|
||||
INVALID_SIGNATURE: {
|
||||
invalidSignature: 'Invalid signature',
|
||||
},
|
||||
ADMIN_LOGIN_REQUIRED_TO_INITIALIZE_INSTANCE: {
|
||||
adminLoginRequiredToInitializeInstance: 'Admin login required to initialize instance',
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
pendingToken: {
|
||||
type: 'string',
|
||||
maxLength: 1024,
|
||||
required: true,
|
||||
},
|
||||
signature: {
|
||||
type: 'string',
|
||||
minLength: 64,
|
||||
maxLength: 64,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
exits: {
|
||||
invalidPendingToken: {
|
||||
responseType: 'unauthorized',
|
||||
},
|
||||
invalidSignature: {
|
||||
responseType: 'forbidden',
|
||||
},
|
||||
adminLoginRequiredToInitializeInstance: {
|
||||
responseType: 'forbidden',
|
||||
},
|
||||
},
|
||||
|
||||
async fn(inputs) {
|
||||
const remoteAddress = getRemoteAddress(this.req);
|
||||
const { httpOnlyToken } = this.req.cookies;
|
||||
|
||||
try {
|
||||
payload = sails.helpers.utils.verifyJwtToken(inputs.pendingToken);
|
||||
} catch (error) {
|
||||
if (error.raw.name === 'TokenExpiredError') {
|
||||
throw Errors.INVALID_PENDING_TOKEN;
|
||||
}
|
||||
|
||||
sails.log.warn(`Invalid pending token! (IP: ${remoteAddress})`);
|
||||
throw Errors.INVALID_PENDING_TOKEN;
|
||||
}
|
||||
|
||||
if (payload.subject !== AccessTokenSteps.ACCEPT_TERMS) {
|
||||
throw Errors.INVALID_PENDING_TOKEN;
|
||||
}
|
||||
|
||||
let session = await Session.qm.getOneUndeletedByPendingToken(inputs.pendingToken);
|
||||
|
||||
if (!session) {
|
||||
sails.log.warn(`Invalid pending token! (IP: ${remoteAddress})`);
|
||||
throw Errors.INVALID_PENDING_TOKEN;
|
||||
}
|
||||
|
||||
if (session.httpOnlyToken && httpOnlyToken !== session.httpOnlyToken) {
|
||||
throw Errors.INVALID_PENDING_TOKEN;
|
||||
}
|
||||
|
||||
let user = await User.qm.getOneById(session.userId, {
|
||||
withDeactivated: false,
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw Errors.INVALID_PENDING_TOKEN; // TODO: introduce separate error?
|
||||
}
|
||||
|
||||
if (!user.termsSignature) {
|
||||
const termsSignature = sails.hooks.terms.getSignatureByUserRole(user.role);
|
||||
|
||||
if (inputs.signature !== termsSignature) {
|
||||
throw Errors.INVALID_SIGNATURE;
|
||||
}
|
||||
|
||||
user = await User.qm.updateOne(user.id, {
|
||||
termsSignature,
|
||||
termsAcceptedAt: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
const config = await Config.qm.getOneMain();
|
||||
|
||||
if (!config.isInitialized) {
|
||||
if (user.role === User.Roles.ADMIN) {
|
||||
await Config.qm.updateOneMain({
|
||||
isInitialized: true,
|
||||
});
|
||||
} else {
|
||||
throw Errors.ADMIN_LOGIN_REQUIRED_TO_INITIALIZE_INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
const { token: accessToken, payload: accessTokenPayload } = sails.helpers.utils.createJwtToken(
|
||||
user.id,
|
||||
);
|
||||
|
||||
session = await Session.qm.updateOne(session.id, {
|
||||
accessToken,
|
||||
pendingToken: null,
|
||||
});
|
||||
|
||||
if (session.httpOnlyToken && !this.req.isSocket) {
|
||||
sails.helpers.utils.setHttpOnlyTokenCookie(
|
||||
session.httpOnlyToken,
|
||||
accessTokenPayload,
|
||||
this.res,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
item: accessToken,
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
const bcrypt = require('bcrypt');
|
||||
const { v4: uuid } = require('uuid');
|
||||
|
||||
const { isEmailOrUsername } = require('../../../utils/validators');
|
||||
const { getRemoteAddress } = require('../../../utils/remote-address');
|
||||
@@ -22,6 +21,9 @@ const Errors = {
|
||||
USE_SINGLE_SIGN_ON: {
|
||||
useSingleSignOn: 'Use single sign-on',
|
||||
},
|
||||
TERMS_ACCEPTANCE_REQUIRED: {
|
||||
termsAcceptanceRequired: 'Terms acceptance required',
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
@@ -39,7 +41,6 @@ module.exports = {
|
||||
},
|
||||
withHttpOnlyToken: {
|
||||
type: 'boolean',
|
||||
defaultsTo: false,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -56,6 +57,12 @@ module.exports = {
|
||||
useSingleSignOn: {
|
||||
responseType: 'forbidden',
|
||||
},
|
||||
termsAcceptanceRequired: {
|
||||
responseType: 'forbidden',
|
||||
},
|
||||
adminLoginRequiredToInitializeInstance: {
|
||||
responseType: 'forbidden',
|
||||
},
|
||||
},
|
||||
|
||||
async fn(inputs) {
|
||||
@@ -90,26 +97,19 @@ module.exports = {
|
||||
: Errors.INVALID_CREDENTIALS;
|
||||
}
|
||||
|
||||
const { token: accessToken, payload: accessTokenPayload } = sails.helpers.utils.createJwtToken(
|
||||
user.id,
|
||||
);
|
||||
|
||||
const httpOnlyToken = inputs.withHttpOnlyToken ? uuid() : null;
|
||||
|
||||
await Session.qm.createOne({
|
||||
accessToken,
|
||||
httpOnlyToken,
|
||||
return sails.helpers.accessTokens.handleSteps
|
||||
.with({
|
||||
user,
|
||||
remoteAddress,
|
||||
userId: user.id,
|
||||
userAgent: this.req.headers['user-agent'],
|
||||
});
|
||||
|
||||
if (httpOnlyToken && !this.req.isSocket) {
|
||||
sails.helpers.utils.setHttpOnlyTokenCookie(httpOnlyToken, accessTokenPayload, this.res);
|
||||
}
|
||||
|
||||
return {
|
||||
item: accessToken,
|
||||
};
|
||||
request: this.req,
|
||||
response: this.res,
|
||||
withHttpOnlyToken: inputs.withHttpOnlyToken,
|
||||
})
|
||||
.intercept('adminLoginRequiredToInitializeInstance', (error) => ({
|
||||
adminLoginRequiredToInitializeInstance: error.raw,
|
||||
}))
|
||||
.intercept('termsAcceptanceRequired', (error) => ({
|
||||
termsAcceptanceRequired: error.raw,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const { v4: uuid } = require('uuid');
|
||||
|
||||
const { getRemoteAddress } = require('../../../utils/remote-address');
|
||||
|
||||
const Errors = {
|
||||
@@ -17,6 +15,9 @@ const Errors = {
|
||||
INVALID_USERINFO_CONFIGURATION: {
|
||||
invalidUserinfoConfiguration: 'Invalid userinfo configuration',
|
||||
},
|
||||
TERMS_ACCEPTANCE_REQUIRED: {
|
||||
termsAcceptanceRequired: 'Terms acceptance required',
|
||||
},
|
||||
EMAIL_ALREADY_IN_USE: {
|
||||
emailAlreadyInUse: 'Email already in use',
|
||||
},
|
||||
@@ -45,7 +46,6 @@ module.exports = {
|
||||
},
|
||||
withHttpOnlyToken: {
|
||||
type: 'boolean',
|
||||
defaultsTo: false,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -59,6 +59,12 @@ module.exports = {
|
||||
invalidUserinfoConfiguration: {
|
||||
responseType: 'unauthorized',
|
||||
},
|
||||
termsAcceptanceRequired: {
|
||||
responseType: 'forbidden',
|
||||
},
|
||||
adminLoginRequiredToInitializeInstance: {
|
||||
responseType: 'forbidden',
|
||||
},
|
||||
emailAlreadyInUse: {
|
||||
responseType: 'conflict',
|
||||
},
|
||||
@@ -89,26 +95,19 @@ module.exports = {
|
||||
.intercept('activeLimitReached', () => Errors.ACTIVE_USERS_LIMIT_REACHED)
|
||||
.intercept('missingValues', () => Errors.MISSING_VALUES);
|
||||
|
||||
const { token: accessToken, payload: accessTokenPayload } = sails.helpers.utils.createJwtToken(
|
||||
user.id,
|
||||
);
|
||||
|
||||
const httpOnlyToken = inputs.withHttpOnlyToken ? uuid() : null;
|
||||
|
||||
await Session.qm.createOne({
|
||||
accessToken,
|
||||
httpOnlyToken,
|
||||
return sails.helpers.accessTokens.handleSteps
|
||||
.with({
|
||||
user,
|
||||
remoteAddress,
|
||||
userId: user.id,
|
||||
userAgent: this.req.headers['user-agent'],
|
||||
});
|
||||
|
||||
if (httpOnlyToken && !this.req.isSocket) {
|
||||
sails.helpers.utils.setHttpOnlyTokenCookie(httpOnlyToken, accessTokenPayload, this.res);
|
||||
}
|
||||
|
||||
return {
|
||||
item: accessToken,
|
||||
};
|
||||
request: this.req,
|
||||
response: this.res,
|
||||
withHttpOnlyToken: inputs.withHttpOnlyToken,
|
||||
})
|
||||
.intercept('adminLoginRequiredToInitializeInstance', (error) => ({
|
||||
adminLoginRequiredToInitializeInstance: error.raw,
|
||||
}))
|
||||
.intercept('termsAcceptanceRequired', (error) => ({
|
||||
termsAcceptanceRequired: error.raw,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
49
server/api/controllers/access-tokens/revoke-pending-token.js
Normal file
49
server/api/controllers/access-tokens/revoke-pending-token.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const Errors = {
|
||||
PENDING_TOKEN_NOT_FOUND: {
|
||||
pendingTokenNotFound: 'Pending token not found',
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
pendingToken: {
|
||||
type: 'string',
|
||||
maxLength: 1024,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
exits: {
|
||||
pendingTokenNotFound: {
|
||||
responseType: 'notFound',
|
||||
},
|
||||
},
|
||||
|
||||
async fn(inputs) {
|
||||
const { httpOnlyToken } = this.req.cookies;
|
||||
let session = await Session.qm.getOneUndeletedByPendingToken(inputs.pendingToken);
|
||||
|
||||
if (!session) {
|
||||
throw Errors.PENDING_TOKEN_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (session.httpOnlyToken && httpOnlyToken !== session.httpOnlyToken) {
|
||||
throw Errors.PENDING_TOKEN_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
session = await Session.qm.deleteOneById(session.id);
|
||||
|
||||
if (session.httpOnlyToken && !this.req.isSocket) {
|
||||
sails.helpers.utils.clearHttpOnlyTokenCookie(this.res);
|
||||
}
|
||||
|
||||
return {
|
||||
item: null,
|
||||
};
|
||||
},
|
||||
};
|
||||
26
server/api/controllers/terms/show.js
Normal file
26
server/api/controllers/terms/show.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
type: {
|
||||
type: 'string',
|
||||
isIn: Object.values(sails.hooks.terms.Types),
|
||||
required: true,
|
||||
},
|
||||
language: {
|
||||
type: 'string',
|
||||
isIn: User.LANGUAGES,
|
||||
},
|
||||
},
|
||||
|
||||
async fn(inputs) {
|
||||
const terms = await sails.hooks.terms.getPayload(inputs.type, inputs.language);
|
||||
|
||||
return {
|
||||
item: terms,
|
||||
};
|
||||
},
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user