Rename konnectd to IDP

This commit is contained in:
Ilja Neumann
2021-01-29 12:31:16 +01:00
parent a378f259eb
commit 03cadf304c
172 changed files with 314 additions and 403 deletions
+45
View File
@@ -0,0 +1,45 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import Routes from './Routes';
class App extends PureComponent {
render() {
const { classes, hello, pathPrefix } = this.props;
return (
<BrowserRouter basename={pathPrefix}>
<Routes hello={hello}/>
</BrowserRouter>
);
}
reload(event) {
event.preventDefault();
window.location.reload();
}
}
App.propTypes = {
classes: PropTypes.object.isRequired,
hello: PropTypes.object,
updateAvailable: PropTypes.bool.isRequired,
pathPrefix: PropTypes.string.isRequired
};
const mapStateToProps = (state) => {
const { hello, updateAvailable, pathPrefix } = state.common;
return {
hello,
updateAvailable,
pathPrefix
};
};
export default connect(mapStateToProps)(App);
+12
View File
@@ -0,0 +1,12 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './Main';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<Provider store={store}><App/></Provider>, div);
});
+8
View File
@@ -0,0 +1,8 @@
all: images
.PHONY: images
images:
@$(MAKE) -C images
clean:
@$(MAKE) -C images clean
+40
View File
@@ -0,0 +1,40 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Route, Switch } from 'react-router-dom';
import AsyncComponent from 'kpop/es/AsyncComponent';
import PrivateRoute from './components/PrivateRoute';
const AsyncLogin = AsyncComponent(() =>
import(/* webpackChunkName: "containers-login" */ './containers/Login'));
const AsyncWelcome = AsyncComponent(() =>
import(/* webpackChunkName: "containers-welcome" */ './containers/Welcome'));
const AsyncGoodbye = AsyncComponent(() =>
import(/* webpackChunkName: "containers-goodbye" */ './containers/Goodbye'));
const Routes = ({ hello }) => (
<Switch>
<PrivateRoute
path="/welcome"
exact
component={AsyncWelcome}
hello={hello}
/>
<Route
path="/goodbye"
exact
component={AsyncGoodbye}
/>
<Route
path="/"
component={AsyncLogin}
/>
</Switch>
);
Routes.propTypes = {
hello: PropTypes.object
};
export default Routes;
+133
View File
@@ -0,0 +1,133 @@
import axios from 'axios';
import { newHelloRequest } from '../models/hello';
import { withClientRequestState } from '../utils';
import {
ExtendedError,
ERROR_HTTP_UNEXPECTED_RESPONSE_STATUS,
ERROR_HTTP_UNEXPECTED_RESPONSE_STATE
} from '../errors';
import { handleAxiosError } from './utils';
import * as types from './types';
export function receiveError(error) {
return {
type: types.RECEIVE_ERROR,
error
};
}
export function resetHello() {
return {
type: types.RESET_HELLO
};
}
export function receiveHello(hello) {
const { success, username, displayName } = hello;
return {
type: types.RECEIVE_HELLO,
state: success === true,
username,
displayName,
hello
};
}
export function executeHello() {
return function(dispatch, getState) {
dispatch(resetHello());
const { flow, query } = getState().common;
const r = withClientRequestState(newHelloRequest(flow, query));
return axios.post('./identifier/_/hello', r, {
headers: {
'Kopano-Konnect-XSRF': '1'
}
}).then(response => {
switch (response.status) {
case 200:
// success.
return response.data;
case 204:
// not signed-in.
return {
success: false,
state: response.headers['kopano-konnect-state']
};
default:
// error.
throw new ExtendedError(ERROR_HTTP_UNEXPECTED_RESPONSE_STATUS, response);
}
}).then(response => {
if (response.state !== r.state) {
throw new ExtendedError(ERROR_HTTP_UNEXPECTED_RESPONSE_STATE, response);
}
dispatch(receiveHello(response));
return Promise.resolve(response);
}).catch(error => {
error = handleAxiosError(error);
dispatch(receiveError(error));
});
};
}
export function retryHello() {
return function(dispatch) {
dispatch(receiveError(null));
return dispatch(executeHello());
};
}
export function requestLogoff() {
return {
type: types.REQUEST_LOGOFF
};
}
export function receiveLogoff(state) {
return {
type: types.RECEIVE_LOGOFF,
state
};
}
export function executeLogoff() {
return function(dispatch) {
dispatch(resetHello());
dispatch(requestLogoff());
const r = withClientRequestState({});
return axios.post('./identifier/_/logoff', r, {
headers: {
'Kopano-Konnect-XSRF': '1'
}
}).then(response => {
switch (response.status) {
case 200:
// success.
return response.data;
default:
// error.
throw new ExtendedError(ERROR_HTTP_UNEXPECTED_RESPONSE_STATUS, response);
}
}).then(response => {
if (response.state !== r.state) {
throw new ExtendedError(ERROR_HTTP_UNEXPECTED_RESPONSE_STATE, response);
}
dispatch(receiveLogoff(response.success === true));
return Promise.resolve(response);
}).catch(error => {
error = handleAxiosError(error);
dispatch(receiveError(error));
});
};
}
+291
View File
@@ -0,0 +1,291 @@
import axios from 'axios';
import queryString from 'query-string';
import { newHelloRequest } from '../models/hello';
import { withClientRequestState } from '../utils';
import {
ExtendedError,
ERROR_LOGIN_VALIDATE_MISSINGUSERNAME,
ERROR_LOGIN_VALIDATE_MISSINGPASSWORD,
ERROR_LOGIN_FAILED,
ERROR_HTTP_UNEXPECTED_RESPONSE_STATUS,
ERROR_HTTP_UNEXPECTED_RESPONSE_STATE
} from '../errors';
import * as types from './types';
import { receiveHello } from './common';
import { handleAxiosError } from './utils';
// Modes for logon.
export const ModeLogonUsernameEmptyPasswordCookie = '0';
export const ModeLogonUsernamePassword = '1';
export function updateInput(name, value) {
return {
type: types.UPDATE_INPUT,
name,
value
};
}
export function receiveValidateLogon(errors) {
return {
type: types.RECEIVE_VALIDATE_LOGON,
errors
};
}
export function requestLogon(username, password) {
return {
type: types.REQUEST_LOGON,
username,
password
};
}
export function receiveLogon(logon) {
const { success, errors } = logon;
return {
type: types.RECEIVE_LOGON,
success,
errors
};
}
export function requestConsent(allow=false) {
return {
type: allow ? types.REQUEST_CONSENT_ALLOW : types.REQUEST_CONSENT_CANCEL
};
}
export function receiveConsent(logon) {
const { success, errors } = logon;
return {
type: types.RECEIVE_CONSENT,
success,
errors
};
}
export function executeLogon(username, password, mode=ModeLogonUsernamePassword) {
return function(dispatch, getState) {
dispatch(requestLogon(username, password));
dispatch(receiveHello({
username
})); // Reset any hello state on logon.
const { flow, query } = getState().common;
// Prepare params based on mode.
const params = [];
switch (mode) {
case ModeLogonUsernamePassword:
// Username with password.
params.push(username, password, mode);
break;
case ModeLogonUsernameEmptyPasswordCookie:
// Username with empty password - this only works when the user is already signed in.
params.push(username, '', mode);
break;
}
const r = withClientRequestState({
params: params,
hello: newHelloRequest(flow, query)
});
return axios.post('./identifier/_/logon', r, {
headers: {
'Kopano-Konnect-XSRF': '1'
}
}).then(response => {
switch (response.status) {
case 200:
// success.
return response.data;
case 204:
// login failed.
return {
success: false,
state: response.headers['kopano-konnect-state'],
errors: {
http: new Error(ERROR_LOGIN_FAILED)
}
};
default:
// error.
throw new ExtendedError(ERROR_HTTP_UNEXPECTED_RESPONSE_STATUS, response);
}
}).then(response => {
if (response.state !== r.state) {
throw new ExtendedError(ERROR_HTTP_UNEXPECTED_RESPONSE_STATE, response);
}
let { hello } = response;
if (!hello) {
hello = {
success: response.success,
username
};
}
dispatch(receiveHello(hello));
dispatch(receiveLogon(response));
return Promise.resolve(response);
}).catch(error => {
error = handleAxiosError(error);
const errors = {
http: error
};
dispatch(receiveValidateLogon(errors));
return {
success: false,
errors: errors
};
});
};
}
export function executeConsent(allow=false, scope='') {
return function(dispatch, getState) {
dispatch(requestConsent(allow));
const { query } = getState().common;
const r = withClientRequestState({
allow,
scope,
client_id: query.client_id || '', // eslint-disable-line camelcase
redirect_uri: query.redirect_uri || '', // eslint-disable-line camelcase
ref: query.state || '',
flow_nonce: query.nonce || '' // eslint-disable-line camelcase
});
return axios.post('./identifier/_/consent', r, {
headers: {
'Kopano-Konnect-XSRF': '1'
}
}).then(response => {
switch (response.status) {
case 200:
// success.
return response.data;
case 204:
// cancel reply.
return {
success: true,
state: response.headers['kopano-konnect-state']
};
default:
// error.
throw new ExtendedError(ERROR_HTTP_UNEXPECTED_RESPONSE_STATUS, response);
}
}).then(response => {
if (response.state !== r.state) {
throw new ExtendedError(ERROR_HTTP_UNEXPECTED_RESPONSE_STATE, response);
}
dispatch(receiveConsent(response));
return Promise.resolve(response);
}).catch(error => {
error = handleAxiosError(error);
const errors = {
http: error
};
dispatch(receiveValidateLogon(errors));
return {
success: false,
errors: errors
};
});
};
}
export function validateUsernamePassword(username, password, isSignedIn) {
return function(dispatch) {
return new Promise((resolve, reject) => {
const errors = {};
if (!username) {
errors.username = new Error(ERROR_LOGIN_VALIDATE_MISSINGUSERNAME);
}
if (!password && !isSignedIn) {
errors.password = new Error(ERROR_LOGIN_VALIDATE_MISSINGPASSWORD);
}
dispatch(receiveValidateLogon(errors));
if (Object.keys(errors).length === 0) {
resolve(errors);
} else {
reject(errors);
}
});
};
}
export function executeLogonIfFormValid(username, password, isSignedIn) {
return (dispatch) => {
return dispatch(
validateUsernamePassword(username, password, isSignedIn)
).then(() => {
const mode = isSignedIn ? ModeLogonUsernameEmptyPasswordCookie : ModeLogonUsernamePassword;
return dispatch(executeLogon(username, password, mode));
}).catch((errors) => {
return {
success: false,
errors: errors
};
});
};
}
export function advanceLogonFlow(success, history, done=false, extraQuery={}) {
return (dispatch, getState) => {
if (!success) {
return;
}
const { flow, query, hello } = getState().common;
const q = Object.assign({}, query, extraQuery);
switch (flow) {
case 'oauth':
case 'consent':
case 'oidc':
if (hello.details.flow !== flow) {
// Ignore requested flow if hello flow does not match.
break;
}
if (!done && hello.details.next === 'consent') {
history.replace(`/consent${history.location.search}${history.location.hash}`);
return;
}
if (hello.details.continue_uri) {
q.prompt = 'none';
window.location.replace(hello.details.continue_uri + '?' + queryString.stringify(q));
return;
}
break;
default:
// Legacy stupid modes.
if (q.continue && q.continue.indexOf(document.location.origin) === 0) {
window.location.replace(q.continue);
return;
}
}
// Default action.
let target = '/welcome';
if (history.action === 'REPLACE') {
target = target + history.location.search + history.location.hash;
}
dispatch(receiveValidateLogon({})); // XXX(longsleep): hack to reset loading and errors.
history.push(target);
};
}
+25
View File
@@ -0,0 +1,25 @@
export const RECEIVE_ERROR = 'RECEIVE_ERROR';
export const RESET_HELLO = 'RESET_HELLO';
export const EXECUTE_HELLO = 'EXECUTE_HELLO';
export const RECEIVE_HELLO = 'RECEIVE_HELLO';
export const RECEIVE_VALIDATE_LOGON = 'RECEIVE_VALIDATE_LOGON';
export const REQUEST_LOGON = 'REQUEST_LOGON';
export const EXECUTE_LOGON = 'EXECUTE_LOGON';
export const RECEIVE_LOGON = 'RECEIVE_LOGON';
export const UPDATE_INPUT = 'UPDATE_INPUT';
export const REQUEST_CONSENT_ALLOW = 'REQUEST_CONSENT_ALLOW';
export const REQUEST_CONSENT_CANCEL = 'REQUEST_CONSENT_CANCEL';
export const EXECUTE_CONSENT = 'EXECUTE_CONSENT';
export const RECEIVE_CONSENT = 'RECEIVE_CONSENT';
export const REQUEST_LOGOFF = 'REQUEST_LOGOFF';
export const EXECUTE_LOGOFF = 'EXECUTE_LOGOFF';
export const RECEIVE_LOGOFF = 'RECEIVE_LOGOFF';
export const SERVICE_WORKER_NEW_CONTENT = 'SERVICE_WORKER_NEW_CONTENT';
export const SERVICE_WORKER_READY = 'SERVICE_WORKER_READY';
export const SERVICE_WORKER_ERROR = 'SERVICE_WORKER_ERROR';
export const SERVICE_WORKER_OFFLINE = 'SERVICE_WORKER_OFFLINE';
+18
View File
@@ -0,0 +1,18 @@
import {
ExtendedError,
ERROR_HTTP_NETWORK_ERROR,
ERROR_HTTP_UNEXPECTED_RESPONSE_STATUS
} from '../errors';
export function handleAxiosError(error) {
if (error.request) {
// Axios errors.
if (error.response) {
error = new ExtendedError(ERROR_HTTP_UNEXPECTED_RESPONSE_STATUS, error.response);
} else {
error = new ExtendedError(ERROR_HTTP_NETWORK_ERROR);
}
}
return error;
}
+113
View File
@@ -0,0 +1,113 @@
/* additional css on top of kpop */
html {
font-family: 'Open Sans', sans-serif;
}
strong {
font-weight: 600;
}
.oc-login-bg {
background-image: url(./images/background.jpg);
background-size: cover;
background-repeat: no-repeat;
background-position: center;
z-index: 0;
}
#loader {
/* NOTE(longsleep): White here needed because of the background image */
color: white;
text-shadow: #000 0 0 1px;
}
.oc-logo {
position: absolute;
top: -130px;
left: 50%;
height: 80px;
transform: translateX(-50%);
}
.oc-progress {
/* Needs to be important to overwrite material-ui */
background-color: rgba(78, 133, 200, 0.8) !important;
height: 4px;
width: 100px;
}
.oc-progress > div {
/* Needs to be important to overwrite material-ui */
background-color: #4e85c8 !important;
}
.oc-input {
background-color: #042047;
border: 1px solid rgba(78, 133, 200, 0.8);
border-radius: 3px;
color: rgba(255, 255, 255, 0.8);
height: 40px;
width: 300px;
padding: 16px;
box-sizing: border-box;
font-size: 1rem;
}
.oc-input:focus {
outline: none;
}
.oc-input::placeholder {
color: rgba(78, 133, 200, 0.8);
}
.oc-input + .oc-input {
margin-top: 15px;
}
.oc-button {
/* Needs to be important to overwrite material-ui */
font-size: 1.0625rem !important;
}
.oc-button-primary {
/* Needs to be important to overwrite material-ui */
background-color: #4e85c8 !important;
}
.oc-button-primary:hover,
.oc-button-primary:focus {
/* Needs to be important to overwrite material-ui */
background-color: #306db5 !important;
}
.oc-checkbox-dark svg {
/* Needs to be important to overwrite material-ui */
fill: white !important;
}
.oc-footer-message {
color: white;
padding: 10px;
font-size: 0.8rem;
}
@media only screen and (max-width: 768px) {
.oc-logo {
height: 60px;
top: -90px;
}
}
/* Helpers */
.oc-mt-l {
margin-top: 30px !important;
}
.oc-mb-m {
margin-bottom: 20px !important;
}
.oc-light {
color: #fff !important;
}
+34
View File
@@ -0,0 +1,34 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Loadable from 'react-loadable';
import { Provider } from 'react-redux';
import { MuiThemeProvider } from '@material-ui/core/styles';
import { defaultTheme as theme } from 'kpop/es/theme';
import { IntlProvider } from 'react-intl';
import Loading from 'kpop/es/Loading';
import { unregister } from 'kpop/es/serviceWorker';
import store from './store';
import translations from './locales';
// NOTE(longsleep): Load async with loader, this enables code splitting via Webpack.
const LoadableApp = Loadable({
loader: () => import(/* webpackChunkName: "identifier-main" */ './Main'),
loading: Loading,
timeout: 20000
});
ReactDOM.render(
<Provider store={store}>
<MuiThemeProvider theme={theme}>
<IntlProvider messages={translations} locale="en" defaultLocale="en">
<LoadableApp />
</IntlProvider>
</MuiThemeProvider>
</Provider>,
document.getElementById('root')
);
unregister();
@@ -0,0 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
const ClientDisplayName = ({ client, ...rest }) => (
<span {...rest}>{client.display_name ? client.display_name : client.id}</span>
);
ClientDisplayName.propTypes = {
client: PropTypes.object.isRequired
};
export default ClientDisplayName;
+63
View File
@@ -0,0 +1,63 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import LinearProgress from '@material-ui/core/LinearProgress';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import renderIf from 'render-if';
import { retryHello } from '../actions/common';
import { ErrorMessage } from '../errors';
function Loading({ error, dispatch }) {
const retry = (event) => {
event.preventDefault();
dispatch(retryHello());
}
return (
<Grid item align="center">
{renderIf(error === null)(() => (
<LinearProgress className="oc-progress" />
))}
{renderIf(error !== null)(() => (
<div>
<Typography className="oc-light" variant="h5" gutterBottom align="center">
<FormattedMessage id="konnect.loading.error.headline" defaultMessage="Failed to connect to server" />
</Typography>
<Typography align="center" color="error">
<ErrorMessage error={error} />
</Typography>
<Button
autoFocus
color="primary"
variant="contained"
className="oc-button-primary oc-mt-l"
onClick={(event) => retry(event)}
>
<FormattedMessage id="konnect.login.retryButton.label" defaultMessage="Retry" />
</Button>
</div>
))}
</Grid>
);
}
Loading.propTypes = {
error: PropTypes.object,
dispatch: PropTypes.func.isRequired
};
const mapStateToProps = (state) => {
const { error } = state.common;
return {
error
};
};
export default connect(mapStateToProps)(Loading);
+22
View File
@@ -0,0 +1,22 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Route } from 'react-router-dom';
import RedirectWithQuery from './RedirectWithQuery';
const PrivateRoute = ({ component: Target, hello, ...rest }) => (
<Route {...rest} render={props => (
hello ? (
<Target {...props}/>
) : (
<RedirectWithQuery target='/identifier' />
)
)}/>
);
PrivateRoute.propTypes = {
component: PropTypes.func.isRequired,
hello: PropTypes.object
};
export default PrivateRoute;
@@ -0,0 +1,23 @@
import React from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router';
import { Redirect } from 'react-router-dom';
const RedirectWithQuery = ({target, location, ...rest}) => {
const to = {
pathname: target,
search: location.search,
hash: location.hash
};
return (
<Redirect to={to} {...rest}></Redirect>
);
};
RedirectWithQuery.propTypes = {
target: PropTypes.string.isRequired,
location: PropTypes.object.isRequired
};
export default withRouter(RedirectWithQuery);
+95
View File
@@ -0,0 +1,95 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { FormattedMessage } from 'react-intl';
import { withStyles } from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
import DialogContent from '@material-ui/core/DialogContent';
import Loading from './Loading';
const styles = theme => ({
root: {
display: 'flex',
flex: 1
},
content: {
position: 'relative',
width: '100%'
},
actions: {
marginTop: -40,
justifyContent: 'flex-start',
paddingLeft: theme.spacing(3),
paddingRight: theme.spacing(3)
},
wrapper: {
width: '100%',
maxWidth: 300,
display: 'flex',
flex: 1,
alignItems: 'center'
}
});
const footerProductName = name => <strong>{name}</strong>;
const ResponsiveScreen = (props) => {
const {
classes,
withoutLogo,
withoutPadding,
loading,
children,
className,
DialogProps,
PaperProps,
...other
} = props;
const logo = withoutLogo ? null :
<img src={process.env.PUBLIC_URL + '/static/logo.svg'} className="oc-logo" alt="ownCloud"/>;
const content = loading ? <Loading/> : (withoutPadding ? children : <DialogContent>{children}</DialogContent>);
return (
<Grid container justify="center" alignItems="center" direction="column" spacing={0}
className={classNames(classes.root, className)} {...other}>
<div className={classes.wrapper}>
<div className={classes.content}>
{logo}
{content}
</div>
</div>
<footer className="oc-footer-message">
<FormattedMessage
id="konnect.footer.slogan"
defaultMessage="<name>ownCloud</name> - a safe home for all your data"
values={{
name: chunks => footerProductName(chunks)
}}
/>
</footer>
</Grid>
);
};
ResponsiveScreen.defaultProps = {
withoutLogo: false,
withoutPadding: false,
loading: false
};
ResponsiveScreen.propTypes = {
classes: PropTypes.object.isRequired,
withoutLogo: PropTypes.bool,
withoutPadding: PropTypes.bool,
loading: PropTypes.bool,
children: PropTypes.node.isRequired,
className: PropTypes.string,
PaperProps: PropTypes.object,
DialogProps: PropTypes.object
};
export default withStyles(styles)(ResponsiveScreen);
+102
View File
@@ -0,0 +1,102 @@
import React from 'react';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import { withStyles } from '@material-ui/core/styles';
import PropTypes from 'prop-types';
import Checkbox from '@material-ui/core/Checkbox';
import { injectIntl, useIntl, defineMessages, FormattedMessage } from 'react-intl';
const styles = () => ({
row: {
paddingTop: 0,
paddingBottom: 0
}
});
const scopeIDTranslations = defineMessages({
'scope_alias_basic': {
id: 'konnect.scopeDescription.aliasBasic',
defaultMessage: 'Access your basic account information'
},
'scope_offline_access': {
id: 'konnect.scopeDescription.offlineAccess',
defaultMessage: 'Keep the allowed access persistently and forever'
}
});
const ScopesList = ({scopes, meta, classes, ...rest}) => {
const { mapping, definitions } = meta;
const intl = useIntl()
const rows = [];
const known = {};
// TODO(longsleep): Sort scopes according to priority.
for (let scope in scopes) {
if (!scopes[scope]) {
continue;
}
let id = mapping[scope];
if (id) {
if (known[id]) {
continue;
}
known[id] = true;
} else {
id = scope;
}
let definition = definitions[id];
let label ;
if (definition) {
if (definition.id) {
const translation = scopeIDTranslations[definition.id];
if (translation) {
label = intl.formatMessage(translation);
}
}
if (!label) {
label = definition.description;
}
}
if (!label) {
label = <FormattedMessage
id="konnect.scopeDescription.scope"
defaultMessage="Scope: {scope}"
values={{scope}}
/>;
}
rows.push(
<ListItem
disableGutters
dense
key={id}
className={classes.row}
><Checkbox
checked
disableRipple
disabled
className="oc-checkbox-dark"
/>
<ListItemText primary={label} className="oc-light" />
</ListItem>
);
}
return (
<List {...rest}>
{rows}
</List>
);
};
ScopesList.propTypes = {
classes: PropTypes.object.isRequired,
scopes: PropTypes.object.isRequired,
meta: PropTypes.object.isRequired
};
export default withStyles(styles)(injectIntl(ScopesList));
+16
View File
@@ -0,0 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
const TextInput = (props) => {
const intl = useIntl();
return <input className="oc-input" {...props} placeholder={props.placeholder ? intl.formatMessage(props.placeholder) : null} />;
};
TextInput.propTypes = {
placeholder: PropTypes.object,
}
export default TextInput;
@@ -0,0 +1,125 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import renderIf from 'render-if';
import { FormattedMessage } from 'react-intl';
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import DialogActions from '@material-ui/core/DialogActions';
import ResponsiveScreen from '../../components/ResponsiveScreen';
import { executeHello, executeLogoff } from '../../actions/common';
const styles = theme => ({
subHeader: {
marginBottom: theme.spacing(5)
},
wrapper: {
marginTop: theme.spacing(5),
position: 'relative',
display: 'inline-block'
}
});
class Goodbyescreen extends React.PureComponent {
componentDidMount() {
this.props.dispatch(executeHello());
}
render() {
const { classes, hello } = this.props;
const loading = hello === null;
return (
<ResponsiveScreen loading={loading}>
{renderIf(hello !== null && !hello.state)(() => (
<div>
<Typography variant="h5" component="h3">
<FormattedMessage id="konnect.goodbye.headline" defaultMessage="Goodbye"></FormattedMessage>
</Typography>
<Typography variant="subtitle1" className={classes.subHeader}>
<FormattedMessage id="konnect.goodbye.subHeader"
defaultMessage="you have been signed out from your Kopano account">
</FormattedMessage>
</Typography>
<Typography gutterBottom>
<FormattedMessage id="konnect.goodbye.message.close"
defaultMessage="You can close this window now.">
</FormattedMessage>
</Typography>
</div>
))}
{renderIf(hello !== null && hello.state === true)(() => (
<div>
<Typography variant="h5" component="h3">
<FormattedMessage
id="konnect.goodbye.confirm.headline"
defaultMessage="Hello {displayName}"
values={{displayName: hello.displayName}}>
</FormattedMessage>
</Typography>
<Typography variant="subtitle1" className={classes.subHeader}>
<FormattedMessage id="konnect.goodbye.confirm.subHeader"
defaultMessage="please confirm sign out">
</FormattedMessage>
</Typography>
<Typography gutterBottom>
<FormattedMessage id="konnect.goodbye.message.confirm"
defaultMessage="Press the button below, to sign out from your Kopano account now.">
</FormattedMessage>
</Typography>
<DialogActions>
<div className={classes.wrapper}>
<Button
color="secondary"
className={classes.button}
onClick={(event) => this.logoff(event)}
>
<FormattedMessage id="konnect.goodbye.signoutButton.label"
defaultMessage="Sign out"></FormattedMessage>
</Button>
</div>
</DialogActions>
</div>
))}
</ResponsiveScreen>
);
}
logoff(event) {
event.preventDefault();
this.props.dispatch(executeLogoff()).then((response) => {
const { history } = this.props;
if (response.success) {
this.props.dispatch(executeHello());
history.push('/goodbye');
}
});
}
}
Goodbyescreen.propTypes = {
classes: PropTypes.object.isRequired,
hello: PropTypes.object,
dispatch: PropTypes.func.isRequired,
history: PropTypes.object.isRequired
};
const mapStateToProps = (state) => {
const { hello } = state.common;
return {
hello
};
};
export default connect(mapStateToProps)(withStyles(styles)(Goodbyescreen));
+1
View File
@@ -0,0 +1 @@
export { default } from './Goodbyescreen';
@@ -0,0 +1,144 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import { withStyles } from '@material-ui/core/styles';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import Avatar from '@material-ui/core/Avatar';
import Typography from '@material-ui/core/Typography';
import { executeLogonIfFormValid, advanceLogonFlow } from '../../actions/login';
import { ErrorMessage } from '../../errors';
const styles = theme => ({
subHeader: {
marginBottom: theme.spacing(2)
},
message: {
marginTop: theme.spacing(2)
},
accountList: {
marginLeft: theme.spacing(-3),
marginRight: theme.spacing(-3)
},
accountListItem: {
paddingLeft: theme.spacing(3),
paddingRight: theme.spacing(3)
}
});
function Chooseaccount({ loading, errors, classes, hello, history, dispatch }) {
useEffect(() => {
if ((!hello || !hello.state) && history.action !== 'PUSH') {
history.replace(`/identifier${history.location.search}${history.location.hash}`);
}
});
let errorMessage = null;
if (errors.http) {
errorMessage = <Typography color="error" className={classes.message}>
<ErrorMessage error={errors.http}></ErrorMessage>
</Typography>;
}
let username = '';
if (hello && hello.state) {
username = hello.username;
}
const logon = (event) => {
event.preventDefault();
dispatch(executeLogonIfFormValid(hello.username, '', true)).then((response) => {
if (response.success) {
dispatch(advanceLogonFlow(response.success, history));
}
});
}
const logoff = (event) => {
event.preventDefault();
history.push(`/identifier${history.location.search}${history.location.hash}`);
}
return (
<div>
<Typography variant="h5" component="h3" className="oc-light">
<FormattedMessage id="konnect.chooseaccount.headline" defaultMessage="Choose an account"></FormattedMessage>
</Typography>
<Typography variant="subtitle1" className={classes.subHeader + " oc-light"}>
<FormattedMessage id="konnect.chooseaccount.subHeader" defaultMessage="to sign in to Kopano">
</FormattedMessage>
</Typography>
<form action="" onSubmit={(event) => logon(event)}>
<List disablePadding className={classes.accountList}>
<ListItem
button
disableGutters
className={classes.accountListItem}
disabled={!!loading}
onClick={(event) => logon(event)}
><ListItemAvatar><Avatar>{username.substr(0, 1)}</Avatar></ListItemAvatar>
<ListItemText className="oc-light" primary={username} />
</ListItem>
<ListItem
button
disableGutters
className={classes.accountListItem}
disabled={!!loading}
onClick={(event) => logoff(event)}
>
<ListItemAvatar>
<Avatar>
<FormattedMessage id="konnect.chooseaccount.useOther.persona.label" defaultMessage="?">
</FormattedMessage>
</Avatar>
</ListItemAvatar>
<ListItemText
className="oc-light"
primary={
<FormattedMessage
id="konnect.chooseaccount.useOther.label"
defaultMessage="Use another account">
</FormattedMessage>
}
/>
</ListItem>
</List>
{errorMessage}
</form>
</div>
);
}
Chooseaccount.propTypes = {
classes: PropTypes.object.isRequired,
loading: PropTypes.string.isRequired,
errors: PropTypes.object.isRequired,
hello: PropTypes.object,
dispatch: PropTypes.func.isRequired,
history: PropTypes.object.isRequired
};
const mapStateToProps = (state) => {
const { loading, errors } = state.login;
const { hello } = state.common;
return {
loading,
errors,
hello
};
};
export default connect(mapStateToProps)(withStyles(styles)(Chooseaccount));
+187
View File
@@ -0,0 +1,187 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import renderIf from 'render-if';
import { FormattedMessage } from 'react-intl';
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import Tooltip from '@material-ui/core/Tooltip';
import CircularProgress from '@material-ui/core/CircularProgress';
import green from '@material-ui/core/colors/green';
import Typography from '@material-ui/core/Typography';
import DialogActions from '@material-ui/core/DialogActions';
import { executeConsent, advanceLogonFlow, receiveValidateLogon } from '../../actions/login';
import { ErrorMessage } from '../../errors';
import { REQUEST_CONSENT_ALLOW } from '../../actions/types';
import ClientDisplayName from '../../components/ClientDisplayName';
import ScopesList from '../../components/ScopesList';
const styles = theme => ({
buttonProgress: {
color: green[500],
position: 'absolute',
top: '50%',
left: '50%',
marginTop: -12,
marginLeft: -12
},
scopesList: {
marginBottom: theme.spacing(2)
},
wrapper: {
marginTop: theme.spacing(2),
position: 'relative',
display: 'inline-block'
},
message: {
marginTop: theme.spacing(2),
marginBottom: theme.spacing(2)
}
});
class Consent extends React.PureComponent {
componentDidMount() {
const { dispatch, hello, history, client } = this.props;
if ((!hello || !hello.state || !client) && history.action !== 'PUSH') {
history.replace(`/identifier${history.location.search}${history.location.hash}`);
}
dispatch(receiveValidateLogon({})); // XXX(longsleep): hack to reset loading and errors.
}
action = (allow=false, scopes={}) => (event) => {
event.preventDefault();
if (allow === undefined) {
return;
}
// Convert all scopes which are true to a scope value.
const scope = Object.keys(scopes).filter(scope => {
return !!scopes[scope];
}).join(' ');
const { dispatch, history } = this.props;
dispatch(executeConsent(allow, scope)).then((response) => {
if (response.success) {
dispatch(advanceLogonFlow(response.success, history, true, {konnect: response.state}));
}
});
}
render() {
const { classes, loading, hello, errors, client } = this.props;
const scopes = hello.details.scopes || {};
const meta = hello.details.meta || {};
return (
<div>
<Typography variant="h5" component="h3" className="oc-light">
<FormattedMessage
id="konnect.consent.headline"
defaultMessage="Hi {displayName}"
values={{displayName: hello.displayName}}
/>
</Typography>
<Typography variant="subtitle1" className="oc-light oc-mb-m">
{hello.username}
</Typography>
<Typography variant="subtitle1" gutterBottom className="oc-light">
<FormattedMessage
id="konnect.consent.message"
defaultMessage="{clientDisplayName} wants to"
values={{clientDisplayName:
<Tooltip
placement="bottom"
title={<FormattedMessage
id="konnect.consent.tooltip.client"
defaultMessage='Clicking "Allow" will redirect you to: {redirectURI}'
values={{
redirectURI: client.redirect_uri
}}
></FormattedMessage>}
>
<em><ClientDisplayName client={client}/></em>
</Tooltip>
}}
></FormattedMessage>
</Typography>
<ScopesList dense disablePadding className={classes.scopesList} scopes={scopes} meta={meta.scopes}></ScopesList>
<Typography className="oc-light">
<FormattedMessage
id="konnect.consent.consequence"
defaultMessage="By clicking Allow, you allow this app to use your information.">
</FormattedMessage>
</Typography>
<form action="" onSubmit={this.action(undefined, scopes)}>
<DialogActions>
<div className={classes.wrapper}>
<Button
color="secondary"
className={classes.button}
disabled={!!loading}
onClick={this.action(false, scopes)}
>
<FormattedMessage id="konnect.consent.cancelButton.label" defaultMessage="Cancel"></FormattedMessage>
</Button>
{(loading && loading !== REQUEST_CONSENT_ALLOW) &&
<CircularProgress size={24} className={classes.buttonProgress} />}
</div>
<div className={classes.wrapper}>
<Button
type="submit"
color="primary"
variant="contained"
className="oc-button-primary"
disabled={!!loading}
onClick={this.action(true, scopes)}
>
<FormattedMessage id="konnect.consent.allowButton.label" defaultMessage="Allow"></FormattedMessage>
</Button>
{loading === REQUEST_CONSENT_ALLOW && <CircularProgress size={24} className={classes.buttonProgress} />}
</div>
</DialogActions>
{renderIf(errors.http)(() => (
<Typography variant="subtitle2" color="error" className={classes.message}>
<ErrorMessage error={errors.http}></ErrorMessage>
</Typography>
))}
</form>
</div>
);
}
}
Consent.propTypes = {
classes: PropTypes.object.isRequired,
loading: PropTypes.string.isRequired,
errors: PropTypes.object.isRequired,
hello: PropTypes.object,
client: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
history: PropTypes.object.isRequired
};
const mapStateToProps = (state) => {
const { hello } = state.common;
const { loading, errors } = state.login;
return {
loading: loading,
errors,
hello,
client: hello.details.client || {}
};
};
export default connect(mapStateToProps)(withStyles(styles)(Consent));
+148
View File
@@ -0,0 +1,148 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import CircularProgress from '@material-ui/core/CircularProgress';
import green from '@material-ui/core/colors/green';
import Typography from '@material-ui/core/Typography';
import TextInput from '../../components/TextInput'
import { updateInput, executeLogonIfFormValid, advanceLogonFlow } from '../../actions/login';
import { ErrorMessage } from '../../errors';
const styles = theme => ({
buttonProgress: {
color: green[500],
position: 'absolute',
top: '50%',
left: '50%',
marginTop: -12,
marginLeft: -12
},
subHeader: {
marginBottom: theme.spacing(3)
},
wrapper: {
position: 'relative',
width: '100%',
textAlign: 'center'
},
message: {
marginTop: 5,
marginBottom: 5
}
});
class Login extends React.PureComponent {
state = {};
componentDidMount() {
const { hello, query, dispatch, history } = this.props;
if (hello && hello.state && history.action !== 'PUSH') {
if (!query.prompt || query.prompt.indexOf('select_account') == -1) {
dispatch(advanceLogonFlow(true, history));
return;
}
history.replace(`/chooseaccount${history.location.search}${history.location.hash}`);
return;
}
}
render() {
const { loading, errors, classes, username } = this.props;
const hasError = errors.http || errors.username || errors.password;
const errorMessage = errors.http
? <ErrorMessage error={errors.http}></ErrorMessage>
: (errors.username
? <ErrorMessage error={errors.username}></ErrorMessage>
: <ErrorMessage error={errors.password}></ErrorMessage>);
return (
<form action="" onSubmit={(event) => this.logon(event)}>
<TextInput
autoFocus
autoCapitalize="off"
spellCheck="false"
value={username}
onChange={this.handleChange('username')}
autoComplete="kopano-account username"
placeholder={({ id: "konnect.login.usernameField.label", defaultMessage: "Username" })}
/>
<TextInput
type="password"
margin="normal"
onChange={this.handleChange('password')}
autoComplete="kopano-account current-password"
placeholder={({ id: "konnect.login.usernameField.label", defaultMessage: "Password" })}
/>
{hasError && <Typography variant="subtitle2" color="error" className={classes.message}>{errorMessage}</Typography>}
<div className={classes.wrapper}>
<Button
type="submit"
color="primary"
variant="contained"
className="oc-button-primary oc-mt-l"
disabled={!!loading}
onClick={(event) => this.logon(event)}
>
<FormattedMessage id="konnect.login.nextButton.label" defaultMessage="Log in"></FormattedMessage>
</Button>
{loading && <CircularProgress size={24} className={classes.buttonProgress} />}
</div>
</form>
);
}
handleChange(name) {
return event => {
this.props.dispatch(updateInput(name, event.target.value));
};
}
logon(event) {
event.preventDefault();
const { username, password, dispatch, history } = this.props;
dispatch(executeLogonIfFormValid(username, password, false)).then((response) => {
if (response.success) {
dispatch(advanceLogonFlow(response.success, history));
}
});
}
}
Login.propTypes = {
classes: PropTypes.object.isRequired,
loading: PropTypes.string.isRequired,
username: PropTypes.string.isRequired,
password: PropTypes.string.isRequired,
errors: PropTypes.object.isRequired,
hello: PropTypes.object,
query: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
history: PropTypes.object.isRequired
};
const mapStateToProps = (state) => {
const { loading, username, password, errors} = state.login;
const { hello, query } = state.common;
return {
loading,
username,
password,
errors,
hello,
query
};
};
export default connect(mapStateToProps)(withStyles(styles)(Login));
@@ -0,0 +1,58 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Route, Switch } from 'react-router-dom';
import { withStyles } from '@material-ui/core/styles';
import ResponsiveScreen from '../../components/ResponsiveScreen';
import RedirectWithQuery from '../../components/RedirectWithQuery';
import { executeHello } from '../../actions/common';
import Login from './Login';
import Chooseaccount from './Chooseaccount';
import Consent from './Consent';
const styles = () => ({
});
class Loginscreen extends React.PureComponent {
componentDidMount() {
this.props.dispatch(executeHello());
}
render() {
const { hello } = this.props;
const loading = hello === null;
return (
<ResponsiveScreen loading={loading} withoutPadding={true} >
<Switch>
<Route path="/identifier" exact component={Login}></Route>
<Route path="/chooseaccount" exact component={Chooseaccount}></Route>
<Route path="/consent" exact component={Consent}></Route>
<RedirectWithQuery target="/identifier"/>
</Switch>
</ResponsiveScreen>
);
}
}
Loginscreen.propTypes = {
classes: PropTypes.object.isRequired,
hello: PropTypes.object,
dispatch: PropTypes.func.isRequired
};
const mapStateToProps = (state) => {
const { hello } = state.common;
return {
hello
};
};
export default connect(mapStateToProps)(withStyles(styles)(Loginscreen));
+1
View File
@@ -0,0 +1 @@
export { default } from './Loginscreen';
@@ -0,0 +1,92 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import DialogActions from '@material-ui/core/DialogActions';
import ResponsiveScreen from '../../components/ResponsiveScreen';
import { executeLogoff } from '../../actions/common';
const styles = theme => ({
button: {
margin: theme.spacing(1),
minWidth: 100
},
subHeader: {
marginBottom: theme.spacing(5)
}
});
class Welcomescreen extends React.PureComponent {
render() {
const { classes, hello } = this.props;
const loading = hello === null;
return (
<ResponsiveScreen loading={loading}>
<Typography variant="h5" component="h3" className="oc-light">
<FormattedMessage
id="konnect.welcome.headline"
defaultMessage="Welcome {displayName}"
values={{displayName: hello.displayName}}>
</FormattedMessage>
</Typography>
<Typography variant="subtitle1" className={classes.subHeader + " oc-light"}>
{hello.username}
</Typography>
<Typography gutterBottom className="oc-light">
<FormattedMessage id="konnect.welcome.message"
defaultMessage="You are signed in - awesome!"></FormattedMessage>
</Typography>
<DialogActions>
<Button
color="secondary"
className={classes.button}
variant="contained"
onClick={(event) => this.logoff(event)}
>
<FormattedMessage id="konnect.welcome.signoutButton.label" defaultMessage="Sign out"></FormattedMessage>
</Button>
</DialogActions>
</ResponsiveScreen>
);
}
logoff(event) {
event.preventDefault();
this.props.dispatch(executeLogoff()).then((response) => {
const { history } = this.props;
if (response.success) {
history.push('/identifier');
}
});
}
}
Welcomescreen.propTypes = {
classes: PropTypes.object.isRequired,
hello: PropTypes.object,
dispatch: PropTypes.func.isRequired,
history: PropTypes.object.isRequired
};
const mapStateToProps = (state) => {
const { hello } = state.common;
return {
hello
};
};
export default connect(mapStateToProps)(withStyles(styles)(Welcomescreen));
+1
View File
@@ -0,0 +1 @@
export { default } from './Welcomescreen';
+68
View File
@@ -0,0 +1,68 @@
import { injectIntl, defineMessages } from 'react-intl';
export const ERROR_LOGIN_VALIDATE_MISSINGUSERNAME = 'konnect.error.login.validate.missingUsername';
export const ERROR_LOGIN_VALIDATE_MISSINGPASSWORD = 'konnect.error.login.validate.missingPassword';
export const ERROR_LOGIN_FAILED = 'konnect.error.login.failed';
export const ERROR_HTTP_NETWORK_ERROR = 'konnet.error.http.networkError';
export const ERROR_HTTP_UNEXPECTED_RESPONSE_STATUS = 'konnect.error.http.unexpectedResponseStatus';
export const ERROR_HTTP_UNEXPECTED_RESPONSE_STATE = 'konnect.error.http.unexpectedResponseState';
// Translatable error messages.
const translations = defineMessages({
[ERROR_LOGIN_VALIDATE_MISSINGUSERNAME]: {
id: ERROR_LOGIN_VALIDATE_MISSINGUSERNAME,
defaultMessage: 'Enter an username'
},
[ERROR_LOGIN_VALIDATE_MISSINGPASSWORD]: {
id: ERROR_LOGIN_VALIDATE_MISSINGPASSWORD,
defaultMessage: 'Enter a password'
},
[ERROR_LOGIN_FAILED]: {
id: ERROR_LOGIN_FAILED,
defaultMessage: 'Logon failed. Please verify your credentials and try again.'
},
[ERROR_HTTP_NETWORK_ERROR]: {
id: ERROR_HTTP_NETWORK_ERROR,
defaultMessage: 'Network error. Please check your connection and try again.'
},
[ERROR_HTTP_UNEXPECTED_RESPONSE_STATUS]: {
id: ERROR_HTTP_UNEXPECTED_RESPONSE_STATUS,
defaultMessage: 'Unexpected HTTP response: {status}. Please check your connection and try again.'
},
[ERROR_HTTP_UNEXPECTED_RESPONSE_STATE]: {
id: ERROR_HTTP_UNEXPECTED_RESPONSE_STATE,
defaultMessage: 'Unexpected response state: {state}'
}
});
// Error with values.
export class ExtendedError extends Error {
values = undefined;
constructor(message, values) {
super(message);
if (Error.captureStackTrace !== undefined) {
Error.captureStackTrace(this, ExtendedError);
}
this.values = values;
}
}
// Component to translate error text with values.
function ErrorMessageComponent(props) {
const { error, intl } = props;
if (!error) {
return null;
}
const id = error.id ? error.id : error.message;
const messageDescriptor = Object.assign({}, {
id,
defaultMessage: error.id ? error.message : undefined
}, translations[id]);
return intl.formatMessage(messageDescriptor, error.values);
}
export const ErrorMessage = injectIntl(ErrorMessageComponent);
Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

+13
View File
@@ -0,0 +1,13 @@
import 'kpop/static/css/base.css';
import 'kpop/static/css/scrollbar.css';
import './app.css';
import * as kpop from 'kpop/es/version';
import * as version from './version';
console.info(`Kopano Identifier build version: ${version.build}`); // eslint-disable-line no-console
console.info(`Kopano Kpop build version: ${kpop.build}`); // eslint-disable-line no-console
// NOTE(longsleep): Load async, this enables code splitting via Webpack.
import(/* webpackChunkName: "identifier-app" */ './app');
+39
View File
@@ -0,0 +1,39 @@
{
"konnect.loading.error.headline": "Verbindung zum Server fehlgeschlagen",
"konnect.login.retryButton.label": "Wiederholen",
"konnect.scopeDescription.aliasBasic": "Zugriff auf Ihre grundlegenden Benutzerinformationen",
"konnect.scopeDescription.offlineAccess": "Dauerhaften Zugriff (läuft nicht ab)",
"konnect.scopeDescription.scope": "Geltungsbereich: {scope}",
"konnect.goodbye.headline": "Bis bald",
"konnect.goodbye.subHeader": "sie sind jetzt von Ihrem Kopano Konto abgemeldet",
"konnect.goodbye.message.close": "Sie können dieses Fenster jetzt schließen.",
"konnect.goodbye.confirm.headline": "Hallo {displayName}",
"konnect.goodbye.confirm.subHeader": "Bitte bestätigen Sie, dass Sie sich abmelden möchten",
"konnect.goodbye.message.confirm": "Klicken Sie auf die Schaltfläche unten um sich aus Ihrem Kopano Konto abzumelden.",
"konnect.goodbye.signoutButton.label": "Abmelden",
"konnect.welcome.signoutButton.label": "Abmelden",
"konnect.chooseaccount.headline": "Konto auswählen",
"konnect.chooseaccount.subHeader": "um sich bei Kopano anzumelden",
"konnect.chooseaccount.useOther.persona.label": "?",
"konnect.chooseaccount.useOther.label": "Anderes Konto",
"konnect.consent.headline": "Hi {displayName}",
"konnect.consent.message": "{clientDisplayName} möchte",
"konnect.consent.tooltip.client": "Wenn Sie \"Einverstanden\" klicken werden Sie zu {redirectURI} weitergeleitet",
"konnect.consent.question": "{clientDisplayName} den Zugriff gestatten?",
"konnect.consent.consequence": "Wenn Sie Einverstanden klicken, erhält die App Zugriff auf Ihre Informationen.",
"konnect.consent.cancelButton.label": "Abbrechen",
"konnect.consent.allowButton.label": "Einverstanden",
"konnect.login.headline": "Anmelden",
"konnect.login.subHeader": "mit Ihrem Kopano Konto",
"konnect.login.usernameField.label": "Benutzername",
"konnect.login.passwordField.label": "Passwort",
"konnect.login.nextButton.label": "Weiter",
"konnect.welcome.headline": "Willkommen {displayName}",
"konnect.welcome.message": "Sie sind angemeldet - super!",
"konnect.error.login.validate.missingUsername": "Geben Sie einen Benutzername ein",
"konnect.error.login.validate.missingPassword": "Geben Sie ein Passwort ein",
"konnect.error.login.failed": "Anmeldung fehlgeschlagen. Bitte überprüfen Sie Ihre Eingabe und versuchen Sie es noch einmal.",
"konnet.error.http.networkError": "Netzwerkfehler. Bitte prüfen Sie Ihre Verbindung und versuchen Sie es noch einmal.",
"konnect.error.http.unexpectedResponseStatus": "Unerwartete HTTP-Antwort: {status}. Bitte prüfen Sie Ihre Verbindung und versuchen Sie es noch einmal.",
"konnect.error.http.unexpectedResponseState": "Unerwarteter Antwort-Status: {state}"
}
+39
View File
@@ -0,0 +1,39 @@
{
"konnect.loading.error.headline": "",
"konnect.login.retryButton.label": "",
"konnect.scopeDescription.aliasBasic": "",
"konnect.scopeDescription.offlineAccess": "",
"konnect.scopeDescription.scope": "",
"konnect.goodbye.headline": "",
"konnect.goodbye.subHeader": "",
"konnect.goodbye.message.close": "",
"konnect.goodbye.confirm.headline": "",
"konnect.goodbye.confirm.subHeader": "",
"konnect.goodbye.message.confirm": "",
"konnect.goodbye.signoutButton.label": "",
"konnect.welcome.signoutButton.label": "",
"konnect.chooseaccount.headline": "",
"konnect.chooseaccount.subHeader": "",
"konnect.chooseaccount.useOther.persona.label": "",
"konnect.chooseaccount.useOther.label": "",
"konnect.consent.headline": "",
"konnect.consent.message": "",
"konnect.consent.tooltip.client": "",
"konnect.consent.question": "",
"konnect.consent.consequence": "",
"konnect.consent.cancelButton.label": "",
"konnect.consent.allowButton.label": "",
"konnect.login.headline": "",
"konnect.login.subHeader": "",
"konnect.login.usernameField.label": "",
"konnect.login.passwordField.label": "",
"konnect.login.nextButton.label": "",
"konnect.welcome.headline": "",
"konnect.welcome.message": "",
"konnect.error.login.validate.missingUsername": "",
"konnect.error.login.validate.missingPassword": "",
"konnect.error.login.failed": "",
"konnet.error.http.networkError": "",
"konnect.error.http.unexpectedResponseStatus": "",
"konnect.error.http.unexpectedResponseState": ""
}
+39
View File
@@ -0,0 +1,39 @@
{
"konnect.loading.error.headline": "La connexion au serveur a échoué",
"konnect.login.retryButton.label": "Réessayer",
"konnect.scopeDescription.aliasBasic": "Consulter les informations de base de votre compte",
"konnect.scopeDescription.offlineAccess": "Conserver les autorisations d'accès à l'avenir",
"konnect.scopeDescription.scope": "Portée : {scope}",
"konnect.goodbye.headline": "Au revoir",
"konnect.goodbye.subHeader": "vous avez été déconnecté de Kopano",
"konnect.goodbye.message.close": "Vous pouvez fermer cette fenêtre à présent.",
"konnect.goodbye.confirm.headline": "Bonjour {displayName}",
"konnect.goodbye.confirm.subHeader": "Confirmer votre déconnexion",
"konnect.goodbye.message.confirm": "Cliquer le bouton ci-dessous, pour quitter Kopano.",
"konnect.goodbye.signoutButton.label": "Quitter",
"konnect.welcome.signoutButton.label": "Quitter",
"konnect.chooseaccount.headline": "Choisir un compte",
"konnect.chooseaccount.subHeader": "pour vous authentifier dans Kopano",
"konnect.chooseaccount.useOther.persona.label": "?",
"konnect.chooseaccount.useOther.label": "Utiliser un autre compte",
"konnect.consent.headline": "Bonjour {displayName}",
"konnect.consent.message": "{clientDisplayName} souhaite",
"konnect.consent.tooltip.client": "En cliquant \"Autoriser\" vous serez redirigé vers : {redirectURI}",
"konnect.consent.question": "Autoriser {clientDisplayName} à faire cela ?",
"konnect.consent.consequence": "En cliquant, vous autoriser l'app à accéder à vos informations.",
"konnect.consent.cancelButton.label": "Annuler",
"konnect.consent.allowButton.label": "Autoriser",
"konnect.login.headline": "Identification",
"konnect.login.subHeader": "avec vos identifiants Kopano",
"konnect.login.usernameField.label": "Utilisateur",
"konnect.login.passwordField.label": "Mot de passe",
"konnect.login.nextButton.label": "Suivant",
"konnect.welcome.headline": "Bienvenue {displayName}",
"konnect.welcome.message": "Magnifique - Vous êtes connecté !",
"konnect.error.login.validate.missingUsername": "Saisir un identifiant",
"konnect.error.login.validate.missingPassword": "Saisir un mot de passe",
"konnect.error.login.failed": "Echec de connexion. Vérifier vos identifiants et essayer à nouveau.",
"konnet.error.http.networkError": "Erreur réseau. Vérifier votre connexion, et réessayer.",
"konnect.error.http.unexpectedResponseStatus": "Erreur HTTP inattendue : {status}. Vérifier votre connexion et réessayer.",
"konnect.error.http.unexpectedResponseState": "Erreur d'état inattendue : {state}"
}
+39
View File
@@ -0,0 +1,39 @@
{
"konnect.loading.error.headline": "सर्वर से कनेक्ट करने में विफल",
"konnect.login.retryButton.label": "पुन: प्रयास करें",
"konnect.scopeDescription.aliasBasic": "अपनी मूल खाता जानकारी देखे",
"konnect.scopeDescription.offlineAccess": "अनुमत पहुंच को लगातार और हमेशा बनाए रखें",
"konnect.scopeDescription.scope": "क्षेत्र: {scope}",
"konnect.goodbye.headline": "अलविदा",
"konnect.goodbye.subHeader": "आपको अपने Kopano खाते से साइन आउट कर दिया गया है",
"konnect.goodbye.message.close": "अब आप इस विंडो को बंद कर सकते हैं.",
"konnect.goodbye.confirm.headline": "नमस्ते {displayName}",
"konnect.goodbye.confirm.subHeader": "कृपया साइन आउट की पुष्टि करें",
"konnect.goodbye.message.confirm": "अपने Kopano खाते से साइन आउट करने के लिए नीचे दिए गए बटन को दबाएं.",
"konnect.goodbye.signoutButton.label": "साइन आउट",
"konnect.welcome.signoutButton.label": "साइन आउट",
"konnect.chooseaccount.headline": "खाता चुनें",
"konnect.chooseaccount.subHeader": "Kopano में साइन इन करने के लिए",
"konnect.chooseaccount.useOther.persona.label": "?",
"konnect.chooseaccount.useOther.label": "दूसरे खाते का उपयोग करें",
"konnect.consent.headline": "नमस्ते {displayName}",
"konnect.consent.message": "{clientDisplayName} चाहते है की",
"konnect.consent.tooltip.client": "\"अनुमति\" पर क्लिक करने से आपको {redirecturI} पे पुनर्निर्देशित किया जायेगा",
"konnect.consent.question": "क्या {clientDisplayName} को ये करने की अनुमति देना चाहते है?",
"konnect.consent.consequence": "अनुमति पर क्लिक करके, आप इस एप्लिकेशन को आपकी जानकारी का उपयोग करने की अनुमति देते हैं.",
"konnect.consent.cancelButton.label": "रद्द करें",
"konnect.consent.allowButton.label": "अनुमति दीजिये",
"konnect.login.headline": "साइन इन",
"konnect.login.subHeader": "अपने Kopano खाते के साथ",
"konnect.login.usernameField.label": "उपयोगकर्ता नाम",
"konnect.login.passwordField.label": "पासवर्ड",
"konnect.login.nextButton.label": "अगला",
"konnect.welcome.headline": "स्वागत हे {displayName}",
"konnect.welcome.message": "आप साइंड इन हैं - अद्भुत!",
"konnect.error.login.validate.missingUsername": "उपयोगकर्ता नाम डालिये",
"konnect.error.login.validate.missingPassword": "पासवर्ड डालिए",
"konnect.error.login.failed": "लोगऑन नाकाम रहा. कृपया अपने क्रेडेंशियल्स जांचे और पुनः प्रयास करें.",
"konnet.error.http.networkError": "नेटवर्क त्रुटि। कृपया अपने संपर्क की जांच करे और फिर से प्रयास करें.",
"konnect.error.http.unexpectedResponseStatus": "अनपेक्षित HTTP प्रतिक्रिया: {status}. कृपया अपने संपर्क की जांच करे और फिर से प्रयास करें.",
"konnect.error.http.unexpectedResponseState": "अनपेक्षित प्रतिक्रिया अवस्था: {state}"
}
+39
View File
@@ -0,0 +1,39 @@
{
"konnect.loading.error.headline": "",
"konnect.login.retryButton.label": "",
"konnect.scopeDescription.aliasBasic": "",
"konnect.scopeDescription.offlineAccess": "",
"konnect.scopeDescription.scope": "",
"konnect.goodbye.headline": "",
"konnect.goodbye.subHeader": "",
"konnect.goodbye.message.close": "",
"konnect.goodbye.confirm.headline": "",
"konnect.goodbye.confirm.subHeader": "",
"konnect.goodbye.message.confirm": "",
"konnect.goodbye.signoutButton.label": "",
"konnect.welcome.signoutButton.label": "",
"konnect.chooseaccount.headline": "",
"konnect.chooseaccount.subHeader": "",
"konnect.chooseaccount.useOther.persona.label": "",
"konnect.chooseaccount.useOther.label": "",
"konnect.consent.headline": "",
"konnect.consent.message": "",
"konnect.consent.tooltip.client": "",
"konnect.consent.question": "",
"konnect.consent.consequence": "",
"konnect.consent.cancelButton.label": "",
"konnect.consent.allowButton.label": "",
"konnect.login.headline": "",
"konnect.login.subHeader": "",
"konnect.login.usernameField.label": "",
"konnect.login.passwordField.label": "",
"konnect.login.nextButton.label": "",
"konnect.welcome.headline": "",
"konnect.welcome.message": "",
"konnect.error.login.validate.missingUsername": "",
"konnect.error.login.validate.missingPassword": "",
"konnect.error.login.failed": "",
"konnet.error.http.networkError": "",
"konnect.error.http.unexpectedResponseStatus": "",
"konnect.error.http.unexpectedResponseState": ""
}
+39
View File
@@ -0,0 +1,39 @@
{
"konnect.loading.error.headline": "",
"konnect.login.retryButton.label": "",
"konnect.scopeDescription.aliasBasic": "",
"konnect.scopeDescription.offlineAccess": "",
"konnect.scopeDescription.scope": "",
"konnect.goodbye.headline": "",
"konnect.goodbye.subHeader": "",
"konnect.goodbye.message.close": "",
"konnect.goodbye.confirm.headline": "",
"konnect.goodbye.confirm.subHeader": "",
"konnect.goodbye.message.confirm": "",
"konnect.goodbye.signoutButton.label": "",
"konnect.welcome.signoutButton.label": "",
"konnect.chooseaccount.headline": "",
"konnect.chooseaccount.subHeader": "",
"konnect.chooseaccount.useOther.persona.label": "",
"konnect.chooseaccount.useOther.label": "",
"konnect.consent.headline": "",
"konnect.consent.message": "",
"konnect.consent.tooltip.client": "",
"konnect.consent.question": "",
"konnect.consent.consequence": "",
"konnect.consent.cancelButton.label": "",
"konnect.consent.allowButton.label": "",
"konnect.login.headline": "",
"konnect.login.subHeader": "",
"konnect.login.usernameField.label": "",
"konnect.login.passwordField.label": "",
"konnect.login.nextButton.label": "",
"konnect.welcome.headline": "",
"konnect.welcome.message": "",
"konnect.error.login.validate.missingUsername": "",
"konnect.error.login.validate.missingPassword": "",
"konnect.error.login.failed": "",
"konnet.error.http.networkError": "",
"konnect.error.http.unexpectedResponseStatus": "",
"konnect.error.http.unexpectedResponseState": ""
}
+50
View File
@@ -0,0 +1,50 @@
// NOTE(longsleep): This loads all translation files to be included in the
// app bundle. They are not that large.
// Please keep imports and exports alphabetically sorted.
import de from './de.json';
import fr from './fr.json';
import hi from './hi.json';
import is from './is.json';
import nb from './nb.json';
import nl from './nl.json';
import ptPT from './pt_PT.json';
import ru from './ru.json';
function enableLocales(locales, enabled) {
if (process.env.NODE_ENV !== 'production') { // eslint-disable-line no-undef
return locales;
}
return enabled.reduce(function(value, locale) {
value[locale] = locales[locale];
return value;
}, {});
}
// Locales must follow BCP 47 format (https://tools.ietf.org/html/rfc5646).
const locales = enableLocales({
de,
'en-GB': {},
'en-US': {},
fr,
hi,
is,
nb,
nl,
'pt-PT': ptPT,
ru
}, [
// List of enabled languages in production builds.
'de',
'en-GB',
'en-US',
'fr',
'hi',
'is',
'nb',
'nl',
'pt-PT',
'ru'
]);
export default locales;
+39
View File
@@ -0,0 +1,39 @@
{
"konnect.loading.error.headline": "Mistókst að tengjast netþjóni",
"konnect.login.retryButton.label": "Reyna aftur",
"konnect.scopeDescription.aliasBasic": "Komast í grunnupplýsingar um þig",
"konnect.scopeDescription.offlineAccess": "Viðhalda heimildunum alltaf og að eilífu (mundu mig)",
"konnect.scopeDescription.scope": "Gildissvið: {scope}",
"konnect.goodbye.headline": "Bless",
"konnect.goodbye.subHeader": "útskráning Kopano-aðgangsins þíns tókst",
"konnect.goodbye.message.close": "Þú getur lokað þessum glugga núna.",
"konnect.goodbye.confirm.headline": "Halló {displayName}",
"konnect.goodbye.confirm.subHeader": "vinsamlegast staðfestu útskráningu",
"konnect.goodbye.message.confirm": "Smelltu á takkann fyrir neðan til að skrá þig út af Kopano aðganginum núna.",
"konnect.goodbye.signoutButton.label": "Útskrá",
"konnect.welcome.signoutButton.label": "Útskrá",
"konnect.chooseaccount.headline": "Veldu aðgang",
"konnect.chooseaccount.subHeader": "til að skrá þig inn í Kopano",
"konnect.chooseaccount.useOther.persona.label": "?",
"konnect.chooseaccount.useOther.label": "Nota annan aðgang",
"konnect.consent.headline": "Hæ {displayName}",
"konnect.consent.message": "{clientDisplayName} vill",
"konnect.consent.tooltip.client": "Þegar þú smellir á \"Leyfa\" áframsendist þú á: {redirectURI}",
"konnect.consent.question": "Leyfa {clientDisplayName} að gera þetta?",
"konnect.consent.consequence": "Með því að smella á \"Leyfa\", leyfir þú þessu forriti að nota upplýsingarnar um þig.",
"konnect.consent.cancelButton.label": "Hætta við",
"konnect.consent.allowButton.label": "Leyfa",
"konnect.login.headline": "Innskrá",
"konnect.login.subHeader": "með Kopano-aðganginum þínum",
"konnect.login.usernameField.label": "Notandanafn",
"konnect.login.passwordField.label": "Lykilorð",
"konnect.login.nextButton.label": "Næsta",
"konnect.welcome.headline": "Halló {displayName}",
"konnect.welcome.message": "Innskráningin tókst - frábært!",
"konnect.error.login.validate.missingUsername": "Sláðu inn notandanafn",
"konnect.error.login.validate.missingPassword": "Sláðu inn lykilorð",
"konnect.error.login.failed": "Innskráning mistókst. Vinsamlegast staðfestu notandaupplýsingarnar og reyndu aftur.",
"konnet.error.http.networkError": "Netvilla. Vinsamlegast athugaðu tenginguna þína og reyndu aftur.",
"konnect.error.http.unexpectedResponseStatus": "Ófyrirsjáanlegt HTTP svar: {status}. Vinsamlegast athugaðu tenginguna þína og reyndu aftur.",
"konnect.error.http.unexpectedResponseState": "Ófyrirsjáanleg svarstaða: {state}"
}
+39
View File
@@ -0,0 +1,39 @@
{
"konnect.loading.error.headline": "",
"konnect.login.retryButton.label": "",
"konnect.scopeDescription.aliasBasic": "",
"konnect.scopeDescription.offlineAccess": "",
"konnect.scopeDescription.scope": "",
"konnect.goodbye.headline": "",
"konnect.goodbye.subHeader": "",
"konnect.goodbye.message.close": "",
"konnect.goodbye.confirm.headline": "",
"konnect.goodbye.confirm.subHeader": "",
"konnect.goodbye.message.confirm": "",
"konnect.goodbye.signoutButton.label": "",
"konnect.welcome.signoutButton.label": "",
"konnect.chooseaccount.headline": "",
"konnect.chooseaccount.subHeader": "",
"konnect.chooseaccount.useOther.persona.label": "",
"konnect.chooseaccount.useOther.label": "",
"konnect.consent.headline": "",
"konnect.consent.message": "",
"konnect.consent.tooltip.client": "",
"konnect.consent.question": "",
"konnect.consent.consequence": "",
"konnect.consent.cancelButton.label": "",
"konnect.consent.allowButton.label": "",
"konnect.login.headline": "",
"konnect.login.subHeader": "",
"konnect.login.usernameField.label": "",
"konnect.login.passwordField.label": "",
"konnect.login.nextButton.label": "",
"konnect.welcome.headline": "",
"konnect.welcome.message": "",
"konnect.error.login.validate.missingUsername": "",
"konnect.error.login.validate.missingPassword": "",
"konnect.error.login.failed": "",
"konnet.error.http.networkError": "",
"konnect.error.http.unexpectedResponseStatus": "",
"konnect.error.http.unexpectedResponseState": ""
}
+39
View File
@@ -0,0 +1,39 @@
{
"konnect.loading.error.headline": "",
"konnect.login.retryButton.label": "",
"konnect.scopeDescription.aliasBasic": "",
"konnect.scopeDescription.offlineAccess": "",
"konnect.scopeDescription.scope": "",
"konnect.goodbye.headline": "",
"konnect.goodbye.subHeader": "",
"konnect.goodbye.message.close": "",
"konnect.goodbye.confirm.headline": "",
"konnect.goodbye.confirm.subHeader": "",
"konnect.goodbye.message.confirm": "",
"konnect.goodbye.signoutButton.label": "",
"konnect.welcome.signoutButton.label": "",
"konnect.chooseaccount.headline": "",
"konnect.chooseaccount.subHeader": "",
"konnect.chooseaccount.useOther.persona.label": "",
"konnect.chooseaccount.useOther.label": "",
"konnect.consent.headline": "",
"konnect.consent.message": "",
"konnect.consent.tooltip.client": "",
"konnect.consent.question": "",
"konnect.consent.consequence": "",
"konnect.consent.cancelButton.label": "",
"konnect.consent.allowButton.label": "",
"konnect.login.headline": "",
"konnect.login.subHeader": "",
"konnect.login.usernameField.label": "",
"konnect.login.passwordField.label": "",
"konnect.login.nextButton.label": "",
"konnect.welcome.headline": "",
"konnect.welcome.message": "",
"konnect.error.login.validate.missingUsername": "",
"konnect.error.login.validate.missingPassword": "",
"konnect.error.login.failed": "",
"konnet.error.http.networkError": "",
"konnect.error.http.unexpectedResponseStatus": "",
"konnect.error.http.unexpectedResponseState": ""
}
+39
View File
@@ -0,0 +1,39 @@
{
"konnect.loading.error.headline": "Klarte ikke å forbinde med server",
"konnect.login.retryButton.label": "Prøv igjen",
"konnect.scopeDescription.aliasBasic": "Tilgang til informasjon om basiskonto",
"konnect.scopeDescription.offlineAccess": "Behold den tillate tilgangen for alltid",
"konnect.scopeDescription.scope": "Omfang: {scope}",
"konnect.goodbye.headline": "Farvel",
"konnect.goodbye.subHeader": "du har blitt logget ut fra din Kopano konto",
"konnect.goodbye.message.close": "Du kan lukke dette vinduet nå.",
"konnect.goodbye.confirm.headline": "Hallo {displayName}",
"konnect.goodbye.confirm.subHeader": "vennligst bekreft utlogging",
"konnect.goodbye.message.confirm": "Trykk på knappen under, for å logge ut av din Kopano konto.",
"konnect.goodbye.signoutButton.label": "Logg ut",
"konnect.welcome.signoutButton.label": "Logg ut",
"konnect.chooseaccount.headline": "Velg en konto",
"konnect.chooseaccount.subHeader": "for å logge inn i Kopano",
"konnect.chooseaccount.useOther.persona.label": "?",
"konnect.chooseaccount.useOther.label": "Bruk en annen konto",
"konnect.consent.headline": "Hei {displayName}",
"konnect.consent.message": "{clientDisplayName} ønsker",
"konnect.consent.tooltip.client": "Ved å klikke på \"Tillatt\" så vil du bli ledet til: {redirectURI}",
"konnect.consent.question": "Tillatt {clientDisplayName} å gjøre dette?",
"konnect.consent.consequence": "Ved å klikke på Aksepter, så tillater du at appen bruker din informasjon.",
"konnect.consent.cancelButton.label": "Avbryt",
"konnect.consent.allowButton.label": "Tillat",
"konnect.login.headline": "Logg inn",
"konnect.login.subHeader": "med din Kopano konto",
"konnect.login.usernameField.label": "Brukernavn",
"konnect.login.passwordField.label": "Passord",
"konnect.login.nextButton.label": "Neste",
"konnect.welcome.headline": "Velkommen {displayName}",
"konnect.welcome.message": "Du er logget på!",
"konnect.error.login.validate.missingUsername": "Skriv inn et brukernavn",
"konnect.error.login.validate.missingPassword": "Skriv inn et passord",
"konnect.error.login.failed": "Logg inn feilet. Vennligst sjekk brukernavn/passord, og forsøk igjen.",
"konnet.error.http.networkError": "Nettverksfeil. Sjekk din forbindelse, og forsøk igjen.",
"konnect.error.http.unexpectedResponseStatus": "Uventet HTTP respons: {status}. Sjekk tilkoblingen din og prøv igjen.",
"konnect.error.http.unexpectedResponseState": "Uventet svar-status: {state}"
}
+39
View File
@@ -0,0 +1,39 @@
{
"konnect.loading.error.headline": "Kon niet met server verbinden",
"konnect.login.retryButton.label": "Opnieuw",
"konnect.scopeDescription.aliasBasic": "Basis accountgegevens weergeven",
"konnect.scopeDescription.offlineAccess": "De toestemming voor altijd onthouden",
"konnect.scopeDescription.scope": "Scope: {scope}",
"konnect.goodbye.headline": "Tot ziens",
"konnect.goodbye.subHeader": "je bent afgemeld van je Kopano account",
"konnect.goodbye.message.close": "Dit venster kan nu worden gesloten.",
"konnect.goodbye.confirm.headline": "Hallo {displayName}",
"konnect.goodbye.confirm.subHeader": "bevestig afmelden",
"konnect.goodbye.message.confirm": "Klik op onderstaande knop om af te melden van je Kopano account.",
"konnect.goodbye.signoutButton.label": "Afmelden",
"konnect.welcome.signoutButton.label": "Afmelden",
"konnect.chooseaccount.headline": "Account kiezen",
"konnect.chooseaccount.subHeader": "om aan te melden bij Kopano",
"konnect.chooseaccount.useOther.persona.label": "?",
"konnect.chooseaccount.useOther.label": "Gebruik een ander account",
"konnect.consent.headline": "Hoi {displayName}",
"konnect.consent.message": "{clientDisplayName} wil",
"konnect.consent.tooltip.client": "Door op \"Toestaan\" te klikken word je doorverwezen naar: {redirectURI}",
"konnect.consent.question": "{clientDisplayName} toestaan dit te doen?",
"konnect.consent.consequence": "Door op Toestaan te klikken, krijgt deze app toestemming je informatie te gebruiken.",
"konnect.consent.cancelButton.label": "Annuleren",
"konnect.consent.allowButton.label": "Toestaan",
"konnect.login.headline": "Aanmelden",
"konnect.login.subHeader": "met je Kopano account",
"konnect.login.usernameField.label": "Gebruikersnaam",
"konnect.login.passwordField.label": "Wachtwoord",
"konnect.login.nextButton.label": "Volgende",
"konnect.welcome.headline": "Welkom {displayName}",
"konnect.welcome.message": "Je bent aangemeld - fantastisch!",
"konnect.error.login.validate.missingUsername": "Voer een gebruikersnaam in",
"konnect.error.login.validate.missingPassword": "Voer een wachtwoord in",
"konnect.error.login.failed": "Inloggen mislukt. Controleer logingegevens en probeer opnieuw.",
"konnet.error.http.networkError": "Netwerk probleem. Controleer je verbinding en probeer opnieuw.",
"konnect.error.http.unexpectedResponseStatus": "Onverwachte HTTP respons: {status}. Controleer je verbinding en probeer opnieuw.",
"konnect.error.http.unexpectedResponseState": "Onverwachte respons status: {state}"
}
+39
View File
@@ -0,0 +1,39 @@
{
"konnect.loading.error.headline": "",
"konnect.login.retryButton.label": "",
"konnect.scopeDescription.aliasBasic": "",
"konnect.scopeDescription.offlineAccess": "",
"konnect.scopeDescription.scope": "",
"konnect.goodbye.headline": "",
"konnect.goodbye.subHeader": "",
"konnect.goodbye.message.close": "",
"konnect.goodbye.confirm.headline": "",
"konnect.goodbye.confirm.subHeader": "",
"konnect.goodbye.message.confirm": "",
"konnect.goodbye.signoutButton.label": "",
"konnect.welcome.signoutButton.label": "",
"konnect.chooseaccount.headline": "",
"konnect.chooseaccount.subHeader": "",
"konnect.chooseaccount.useOther.persona.label": "",
"konnect.chooseaccount.useOther.label": "",
"konnect.consent.headline": "",
"konnect.consent.message": "",
"konnect.consent.tooltip.client": "",
"konnect.consent.question": "",
"konnect.consent.consequence": "",
"konnect.consent.cancelButton.label": "",
"konnect.consent.allowButton.label": "",
"konnect.login.headline": "",
"konnect.login.subHeader": "",
"konnect.login.usernameField.label": "",
"konnect.login.passwordField.label": "",
"konnect.login.nextButton.label": "",
"konnect.welcome.headline": "",
"konnect.welcome.message": "",
"konnect.error.login.validate.missingUsername": "",
"konnect.error.login.validate.missingPassword": "",
"konnect.error.login.failed": "",
"konnet.error.http.networkError": "",
"konnect.error.http.unexpectedResponseStatus": "",
"konnect.error.http.unexpectedResponseState": ""
}
+39
View File
@@ -0,0 +1,39 @@
{
"konnect.loading.error.headline": "Falhou a ligar ao servidor",
"konnect.login.retryButton.label": "Tentar novamente",
"konnect.scopeDescription.aliasBasic": "Aceder à sua informação básica da conta",
"konnect.scopeDescription.offlineAccess": "Manter o acesso permitido de forma persistente e para sempre",
"konnect.scopeDescription.scope": "Contexto: {scope}",
"konnect.goodbye.headline": "Adeus",
"konnect.goodbye.subHeader": "a sessão da sua conta Kopano foi terminada",
"konnect.goodbye.message.close": "Pode fechar esta janela agora.",
"konnect.goodbye.confirm.headline": "Olá {displayName}",
"konnect.goodbye.confirm.subHeader": "por favor confirme o fim de sessão",
"konnect.goodbye.message.confirm": "Carregue no botão abaixo para terminar sessão na sua conta Kopano agora.",
"konnect.goodbye.signoutButton.label": "Terminar sessão",
"konnect.welcome.signoutButton.label": "Terminar sessão",
"konnect.chooseaccount.headline": "Escolher uma conta",
"konnect.chooseaccount.subHeader": "para iniciar sessão no Kopano",
"konnect.chooseaccount.useOther.persona.label": "?",
"konnect.chooseaccount.useOther.label": "Use outra conta",
"konnect.consent.headline": "Olá {displayName}",
"konnect.consent.message": "{clientDisplayName} quer",
"konnect.consent.tooltip.client": "Ao carregar em \"Permitir\" será redirecionado para: {redirectURI}",
"konnect.consent.question": "Permitir que {clientDisplayName} faça isto?",
"konnect.consent.consequence": "Ao carregar em Permitir, está a permitir que esta app use os seus dados.",
"konnect.consent.cancelButton.label": "Cancelar",
"konnect.consent.allowButton.label": "Permitir",
"konnect.login.headline": "Iniciar sessão",
"konnect.login.subHeader": "com a sua conta Kopano",
"konnect.login.usernameField.label": "Utilizador",
"konnect.login.passwordField.label": "Palavra-passe",
"konnect.login.nextButton.label": "Seguinte",
"konnect.welcome.headline": "Bem vindo {displayName}",
"konnect.welcome.message": "Iniciou sessão - fantástico!",
"konnect.error.login.validate.missingUsername": "Insira um utilizador",
"konnect.error.login.validate.missingPassword": "Insira uma palavra-passe",
"konnect.error.login.failed": "Falhou início de sessão. Por favor verifique as suas credenciais e tente novamente.",
"konnet.error.http.networkError": "Erro de rede. Por favor verifique as ligações e tente novamente.",
"konnect.error.http.unexpectedResponseStatus": "Resposta HTTP inesperada: {status}. Por favor verifique a sua ligação e tente novamente.",
"konnect.error.http.unexpectedResponseState": "Resposta de estado inesperada: {state}"
}
+39
View File
@@ -0,0 +1,39 @@
{
"konnect.loading.error.headline": "Не удалось подключиться к серверу",
"konnect.login.retryButton.label": "Повторить",
"konnect.scopeDescription.aliasBasic": "Доступ к вашей основной учетной записи",
"konnect.scopeDescription.offlineAccess": "Сохраните разрешение на доступ как постоянное",
"konnect.scopeDescription.scope": "Охват: {scope}",
"konnect.goodbye.headline": "До свидания",
"konnect.goodbye.subHeader": "вы вышли из вашей учетной записи Kopano",
"konnect.goodbye.message.close": "Теперь вы можете закрыть окно.",
"konnect.goodbye.confirm.headline": "Здравствуйте, {displayName}",
"konnect.goodbye.confirm.subHeader": "пожалуйста, подтвердите выход",
"konnect.goodbye.message.confirm": "Нажмите кнопку ниже, чтобы выйти из вашей учетной записи Kopano.",
"konnect.goodbye.signoutButton.label": "Выход",
"konnect.welcome.signoutButton.label": "Выход",
"konnect.chooseaccount.headline": "Выберите учётную запись",
"konnect.chooseaccount.subHeader": "для входа в Kopano",
"konnect.chooseaccount.useOther.persona.label": "?",
"konnect.chooseaccount.useOther.label": "Другой пользователь",
"konnect.consent.headline": "Привет, {displayName}",
"konnect.consent.message": "{clientDisplayName} хочет",
"konnect.consent.tooltip.client": "После нажатия \"Разрешить\" вы будете перенаправлена на: {redirectURI}",
"konnect.consent.question": "Разрешить {clientDisplayName} сделать это?",
"konnect.consent.consequence": "Нажимая Разрешить, ты даёте разрешение этому приложению использовать вашу информацию.",
"konnect.consent.cancelButton.label": "Отмена",
"konnect.consent.allowButton.label": "Разрешить",
"konnect.login.headline": "Вход",
"konnect.login.subHeader": "с вашей учётной записью Kopano",
"konnect.login.usernameField.label": "Имя пользователя",
"konnect.login.passwordField.label": "Пароль",
"konnect.login.nextButton.label": "Далее",
"konnect.welcome.headline": "Добро пожаловать, {displayName}",
"konnect.welcome.message": "Вы вошли - круто!",
"konnect.error.login.validate.missingUsername": "Введите имя пользователя",
"konnect.error.login.validate.missingPassword": "Введите пароль",
"konnect.error.login.failed": "Не удалось войти. Пожалуйста, проверьте ваши учетные данные и попробуйте снова.",
"konnet.error.http.networkError": "Сетевая ошибка. Пожалуйста, проверьте ваше соединение и попробуйте снова.",
"konnect.error.http.unexpectedResponseStatus": "Неожиданный ответ HTTP: {status}. Проверьте подключение и повторите попытку.",
"konnect.error.http.unexpectedResponseState": "Неожиданный ответ: {state}"
}
+39
View File
@@ -0,0 +1,39 @@
{
"konnect.loading.error.headline": "",
"konnect.login.retryButton.label": "",
"konnect.scopeDescription.aliasBasic": "",
"konnect.scopeDescription.offlineAccess": "",
"konnect.scopeDescription.scope": "",
"konnect.goodbye.headline": "",
"konnect.goodbye.subHeader": "",
"konnect.goodbye.message.close": "",
"konnect.goodbye.confirm.headline": "",
"konnect.goodbye.confirm.subHeader": "",
"konnect.goodbye.message.confirm": "",
"konnect.goodbye.signoutButton.label": "",
"konnect.welcome.signoutButton.label": "",
"konnect.chooseaccount.headline": "",
"konnect.chooseaccount.subHeader": "",
"konnect.chooseaccount.useOther.persona.label": "",
"konnect.chooseaccount.useOther.label": "",
"konnect.consent.headline": "",
"konnect.consent.message": "",
"konnect.consent.tooltip.client": "",
"konnect.consent.question": "",
"konnect.consent.consequence": "",
"konnect.consent.cancelButton.label": "",
"konnect.consent.allowButton.label": "",
"konnect.login.headline": "",
"konnect.login.subHeader": "",
"konnect.login.usernameField.label": "",
"konnect.login.passwordField.label": "",
"konnect.login.nextButton.label": "",
"konnect.welcome.headline": "",
"konnect.welcome.message": "",
"konnect.error.login.validate.missingUsername": "",
"konnect.error.login.validate.missingPassword": "",
"konnect.error.login.failed": "",
"konnet.error.http.networkError": "",
"konnect.error.http.unexpectedResponseStatus": "",
"konnect.error.http.unexpectedResponseState": ""
}
+38
View File
@@ -0,0 +1,38 @@
export function newHelloRequest(flow, query) {
const r = {};
if (query.prompt) {
// TODO(longsleep): Validate prompt values?
r.prompt = query.prompt;
}
let selectedFlow = flow;
switch (flow) {
case 'oauth':
case 'consent':
case 'oidc':
r.scope = query.scope || '';
r.client_id = query.client_id || ''; // eslint-disable-line camelcase
r.redirect_uri = query.redirect_uri || ''; // eslint-disable-line camelcase
if (query.id_token_hint) {
r.id_token_hint = query.id_token_hint; // eslint-disable-line camelcase
}
if (query.max_age) {
r.max_age = query.max_age; // eslint-disable-line camelcase
}
if (query.claims_scope) {
// Add additional scopes from claims request if given.
r.scope += ' ' + query.claims_scope;
}
break;
default:
selectedFlow = null;
}
if (selectedFlow) {
r.flow = selectedFlow;
}
return r;
}
+64
View File
@@ -0,0 +1,64 @@
import {
RECEIVE_ERROR,
RESET_HELLO,
RECEIVE_HELLO,
SERVICE_WORKER_NEW_CONTENT
} from '../actions/types';
import queryString from 'query-string';
const query = queryString.parse(document.location.search);
const flow = query.flow || '';
delete query.flow;
const defaultPathPrefix = (() => {
const root = document.getElementById('root');
let pathPrefix = root ? root.getAttribute('data-path-prefix') : null;
if (!pathPrefix || pathPrefix === '__PATH_PREFIX__') {
// Not replaced, probably we are running in debug mode or whatever. Use sane default.
pathPrefix = '/signin/v1';
}
return pathPrefix;
})();
const defaultState = {
hello: null,
error: null,
flow: flow,
query: query,
updateAvailable: false,
pathPrefix: defaultPathPrefix
};
function commonReducer(state = defaultState, action) {
switch (action.type) {
case RECEIVE_ERROR:
return Object.assign({}, state, {
error: action.error
});
case RESET_HELLO:
return Object.assign({}, state, {
hello: null
});
case RECEIVE_HELLO:
return Object.assign({}, state, {
hello: {
state: action.state,
username: action.username,
displayName: action.displayName,
details: action.hello
}
});
case SERVICE_WORKER_NEW_CONTENT:
return Object.assign({}, state, {
updateAvailable: true
});
default:
return state;
}
}
export default commonReducer;
+11
View File
@@ -0,0 +1,11 @@
import { combineReducers } from 'redux';
import commonReducer from './common';
import loginReducer from './login';
const rootReducer = combineReducers({
common: commonReducer,
login: loginReducer
});
export default rootReducer;
+60
View File
@@ -0,0 +1,60 @@
import {
RECEIVE_VALIDATE_LOGON,
REQUEST_LOGON,
RECEIVE_LOGON,
RECEIVE_LOGOFF,
REQUEST_CONSENT_ALLOW,
REQUEST_CONSENT_CANCEL,
RECEIVE_CONSENT,
UPDATE_INPUT
} from '../actions/types';
function loginReducer(state = {
loading: '',
username: '',
password: '',
errors: {}
}, action) {
switch (action.type) {
case RECEIVE_VALIDATE_LOGON:
return Object.assign({}, state, {
errors: action.errors,
loading: ''
});
case REQUEST_CONSENT_ALLOW:
case REQUEST_CONSENT_CANCEL:
case REQUEST_LOGON:
return Object.assign({}, state, {
loading: action.type,
errors: {}
});
case RECEIVE_CONSENT:
case RECEIVE_LOGON:
if (!action.success) {
return Object.assign({}, state, {
errors: action.errors ? action.errors : {},
loading: ''
});
}
return state;
case RECEIVE_LOGOFF:
return Object.assign({}, state, {
username: '',
password: ''
});
case UPDATE_INPUT:
delete state.errors[action.name];
return Object.assign({}, state, {
[action.name]: action.value
});
default:
return state;
}
}
export default loginReducer;
+24
View File
@@ -0,0 +1,24 @@
import { createStore, applyMiddleware, compose } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { createLogger } from 'redux-logger';
import rootReducer from './reducers';
const middlewares = [
thunkMiddleware
];
if (process.env.NODE_ENV === 'development') { // eslint-disable-line no-undef
middlewares.push(createLogger()); // must be last middleware in the chain.
}
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(
...middlewares,
))
);
export default store;
+87
View File
@@ -0,0 +1,87 @@
export function withClientRequestState(obj) {
// Generate a 16 byte random token
const values = new Uint8Array(16);
crypto.getRandomValues(values);
// Convert the 16 byte to a hex string and assign to the state attribute
obj.state = Array.prototype.map.call(values, x => x.toString(16)).join('');
return obj;
}
export function dirname(s) {
return s.replace(/\\/g,'/').replace(/\/[^/]*$/, '');
}
export function propertyFromStylesheet(selector, attribute, asURL=false) {
let value;
let sheetHref;
Array.prototype.some.call(document.styleSheets, function(sheet) {
try {
return Array.prototype.some.call(sheet.cssRules, function(rule) {
sheetHref = sheet.href;
if (selector === rule.selectorText) {
return Array.prototype.some.call(rule.style, function(style) {
if (attribute === style) {
value = rule.style.getPropertyValue(attribute);
return true;
}
return false;
});
}
return false;
});
} catch(e) {
// Ignore sheels which caused errors. This for example can happen if an
// extension injected styles from an other origin.
return false;
}
});
if (value && asURL) {
// This removes url() shit if there.
value = value.match(/(?:\(['|"]?)(.*?)(?:['|"]?\))/)[1];
if (!value) {
return null;
}
if (sheetHref) {
// URLs in CSS are relative to the CSS - so lets add stuff.
const baseHref = dirname(sheetHref);
value = baseHref + '/' + value;
}
}
return value;
}
export function enhanceBodyBackground() {
const bg = propertyFromStylesheet('#bg-enhanced.enhanced', 'background-image', true);
const overlay = propertyFromStylesheet('#bg-enhanced.enhanced::after', 'background-image', true);
const promises = [];
if (bg) {
promises.push(new Promise(resolve => {
const img = new Image();
img.onload = () => {
resolve();
};
// Set image source to whatever the url from css holds.
img.src = bg;
}));
}
if (overlay) {
promises.push(new Promise(resolve => {
const img = new Image();
img.onload = () => {
resolve();
};
// Set image source to whatever the url from css holds.
img.src = overlay;
}));
}
Promise.all(promises).then(() => {
window.document.getElementById('bg-enhanced').className += ' enhanced';
});
}
+7
View File
@@ -0,0 +1,7 @@
/*global process: true*/
const build = process.env.REACT_APP_KOPANO_BUILD || '0.0.0-no-proper-build';
export {
build
};