Skip to content

Commit

Permalink
⭐️ feat: token (#17)
Browse files Browse the repository at this point in the history
* fix add endpoints to use grpcWrapper

* add login page and auth token

* PR comment

* add wrapper function for token
  • Loading branch information
nandiheath authored Mar 13, 2021
1 parent b5a613a commit 0e344e7
Show file tree
Hide file tree
Showing 18 changed files with 229 additions and 96 deletions.
13 changes: 6 additions & 7 deletions src/components/organisms/sidePanel/CreateEnv.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import SolidIconButtonSeparated from 'components/atoms/SolidIconButtonSeparated'
import { VerticalSpacer } from 'components/atoms/Spacer';
import { useServiceNavigate } from 'components/hooks/PathHook';
import { useAuthState } from 'components/hooks/ReduxStateHook';
import { useGRPCClients } from 'components/templates/GRPCClients';
import { Formik, FormikProps } from 'formik';
import { EnvNameSchema } from 'libraries/helpers/yup';
import React, { useEffect } from 'react';
Expand All @@ -20,6 +19,7 @@ import { IconButton, Typography } from '@material-ui/core';
import CloseIcon from '@material-ui/icons/CloseRounded';
import { createEnvironment } from 'libraries/grpc/environment';
import { bps } from 'theme';
import { useGRPCWrapper } from '../../templates/GRPCWrapper';

const StyledDiv = styled.div`
position: relative;
Expand Down Expand Up @@ -104,9 +104,9 @@ const renderNameEnv = (props: FormikProps<SignUpValues>, next: Function) => {
};

export default () => {
const clients = useGRPCClients();

const dispatch = useDispatch();
const grpcWrapper = useGRPCWrapper();

const { environments } = useAuthState();
const { navigateToServices } = useServiceNavigate();

Expand All @@ -129,10 +129,9 @@ export default () => {
const createEnv = async () => {
props.setSubmitting(true);
try {
const env = await createEnvironment(
clients.kkcClient!,
props.values.envName
);
const env = await grpcWrapper(createEnvironment, {
envName: props.values.envName,
});

// submit the env token
const envId = env.getId();
Expand Down
9 changes: 6 additions & 3 deletions src/components/organisms/sidePanel/EditEnvForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import FormikTextField from 'components/atoms/FormikTextField';
import { FlexEndRow } from 'components/atoms/Row';
import SolidIconButton from 'components/atoms/SolidIconButton';
import { VerticalSpacer } from 'components/atoms/Spacer';
import { useGRPCClients } from 'components/templates/GRPCClients';
import { Formik, FormikProps } from 'formik';
import { updateEnvironment } from 'libraries/grpc/environment';
import { trackError } from 'libraries/helpers';
Expand All @@ -16,6 +15,7 @@ import { Environment } from 'types/environment';
import * as Yup from 'yup';

import FormHelperText from '@material-ui/core/FormHelperText';
import { useGRPCWrapper } from '../../templates/GRPCWrapper';

type Props = {
env: Environment;
Expand All @@ -26,7 +26,7 @@ type EnvValues = {
};

export default ({ env }: Props) => {
const clients = useGRPCClients();
const grpcWrapper = useGRPCWrapper();

const [error, setError] = useState<string | null>(null);
const [updated, setUpdated] = useState(false);
Expand All @@ -43,7 +43,10 @@ export default ({ env }: Props) => {
onSubmit={async (values, actions) => {
setError(null);
try {
await updateEnvironment(clients.kkcClient!, env.envId, values.name);
await grpcWrapper(updateEnvironment, {
envId: env.envId,
envName: values.name,
});
setUpdated(true);
dispatch(updateEnvName(env.envId, values.name));
// restore to save button after 1s
Expand Down
13 changes: 6 additions & 7 deletions src/components/pages/CreateFirstEnv.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import FormikTextField from 'components/atoms/FormikTextField';
import SolidIconButtonSeparated from 'components/atoms/SolidIconButtonSeparated';
import { VerticalSpacer } from 'components/atoms/Spacer';
import { useServiceNavigate } from 'components/hooks/PathHook';
import { useGRPCClients } from 'components/templates/GRPCClients';
import { Formik, FormikProps } from 'formik';
import { EnvNameSchema } from 'libraries/helpers/yup';
import React, { useEffect, useState } from 'react';
Expand All @@ -21,6 +20,7 @@ import { doHidePanel } from 'states/sidePanel/actions';
import { PATH_MAINTENANCE } from 'libraries/constants';
import { push } from 'connected-react-router';
import FullPageLoading from '../molecules/FullPageLoading';
import { useGRPCWrapper } from '../templates/GRPCWrapper';

const StyledDiv = styled.div`
position: relative;
Expand Down Expand Up @@ -62,7 +62,7 @@ type SignUpValues = {
};

export default () => {
const clients = useGRPCClients();
const grpcWrapper = useGRPCWrapper();

const dispatch = useDispatch();
const { navigateToServices } = useServiceNavigate();
Expand All @@ -71,7 +71,7 @@ export default () => {
useEffect(() => {
const initLoad = async () => {
try {
const envs = await getEnvironments(clients.kkcClient!);
const envs = await grpcWrapper(getEnvironments, {});
const envList = envs.getItemsList();
if (envList.length !== 0) {
navigateToServices({ targetEnvId: envList[0].getId() });
Expand Down Expand Up @@ -104,10 +104,9 @@ export default () => {
const createEnv = async () => {
props.setSubmitting(true);
try {
const env = await createEnvironment(
clients.kkcClient!,
props.values.envName
);
const env = await grpcWrapper(createEnvironment, {
envName: props.values.envName,
});

// submit the env token
const envId = env.getId();
Expand Down
100 changes: 100 additions & 0 deletions src/components/pages/LoginPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { useState } from 'react';
import { push } from 'connected-react-router';
import Typography from '@material-ui/core/Typography';
import styled from 'styled-components';
import { VerticalSpacer } from 'components/atoms/Spacer';
import SolidIconButton from 'components/atoms/SolidIconButton';
import { useDispatch } from 'react-redux';
import { Formik, FormikProps } from 'formik';
import { bps } from 'theme';
import FormikTextField from 'components/atoms/FormikTextField';
import DoneButton from '../atoms/DoneButton';
import { updateToken } from '../../states/auth/actions';
import { PATH_APP } from '../../libraries/constants';


const StyledDiv = styled.div`
box-sizing: border-box;
padding-top: 35%;
padding-left: 40px;
padding-right: 40px;
${bps.down('sm')} {
padding: 40% 20px 0 20px;
}
display: flex;
flex-direction: column;
height: 100%;
.retry-button {
width: 140px;
}
`;

interface LoginProps {
secret: string;
}

const LoginPage = () => {
const dispatch = useDispatch();
const [loggedIn, setLoggedIn] = useState(false);
return (
<StyledDiv>
<Typography variant="h1">Provide your credential :) </Typography>
<VerticalSpacer size={32} />
<Typography variant="h4">
Looks like you have enabled authentication on kinto-core.
</Typography>
<VerticalSpacer size={32} />

<Formik<LoginProps>
initialValues={{
secret: '',
}}
onSubmit={(values, { setSubmitting }) => {
setSubmitting(true);
dispatch(updateToken(values.secret));
setTimeout(() => {
setLoggedIn(true);
setSubmitting(false);
dispatch(push(PATH_APP));
}, 800);
}}
>
{({ handleSubmit, isSubmitting }: FormikProps<any>) => {
return (
<form onSubmit={handleSubmit}>
<div>
<FormikTextField
label="secret"
name="secret"
variant="outlined"
placeholder="The KINTO_CORE_SECRET you set on kinto-core"
fullWidth
/>
<VerticalSpacer size={32} />
<div>
{loggedIn ? (
<DoneButton
noPadding
data-cy="login-success-button"
text="Done"
/>
) : (
<SolidIconButton
data-cy="login-button"
text="LOGIN"
size="large"
isLoading={isSubmitting}
type="submit"
/>
)}
</div>
</div>
</form>
);
}}
</Formik>
</StyledDiv>
);
};

export default LoginPage;
22 changes: 16 additions & 6 deletions src/components/templates/GRPCWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ import { isGRPCStreamTimeout, isPermissionDenied } from 'libraries/grpc/errors';
import { trackError } from 'libraries/helpers';
import { useEffect, useRef } from 'react';

import { useDispatch } from 'react-redux';
import { push } from 'connected-react-router';
import { PATH_AUTH } from 'libraries/constants';

import { GRPCClients, useGRPCClients } from './GRPCClients';
import { useAuthState } from '../hooks/ReduxStateHook';

export const useGRPCWrapper = () => {
const clients = useGRPCClients();

const dispatch = useDispatch();
const { token } = useAuthState();
// since passing only the clients won't update the callbacks
// we pass the ref of the clients and listen to the update
const ref = useRef<GRPCClients>();
Expand All @@ -25,12 +31,16 @@ export const useGRPCWrapper = () => {
return <T, P>(grpc: CoreMethod<T, P>, params: P): Promise<T> => {
const { kkcClient } = ref.current!;
return new Promise((resolve, reject) => {
// TODO: remove token
grpc(kkcClient!, '', params)
grpc(kkcClient!, token!, params)
.then((res) => {
resolve(res!);
})
.catch((error) => {
if (
error.message === 'you are not authorized to query this endpoint'
) {
dispatch(push(PATH_AUTH));
}
reject(error);
});
});
Expand All @@ -46,6 +56,7 @@ export const useGRPCStream = <T, P>(
earlyExit?: () => boolean
) => {
const clients = useGRPCClients();
const { token } = useAuthState();
let retryCount = 0;

useEffect(() => {
Expand All @@ -70,8 +81,7 @@ export const useGRPCStream = <T, P>(
}
const pStream = stream;

// TODO: leave token as empty first
stream = ws(clients.kkcClient!, '', params, {
stream = ws(clients.kkcClient!, token!, params, {
onData: (msg) => {
callbacks.onData(msg);
},
Expand All @@ -98,7 +108,7 @@ export const useGRPCStream = <T, P>(
}
};

stream = ws(clients.kkcClient!, '', params, {
stream = ws(clients.kkcClient!, token!, params, {
onData: (message) => {
callbacks.onData(message);
},
Expand Down
6 changes: 3 additions & 3 deletions src/components/templates/app/AuthApp.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { useAppState } from 'components/hooks/ReduxStateHook';
import FullPageLoading from 'components/molecules/FullPageLoading';
import { useGRPCClients } from 'components/templates/GRPCClients';
import AxiosInterceptor from 'libraries/axios';
import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { doInitBackgroundLoad } from 'states/auth/actions';
import { useGRPCWrapper } from '../GRPCWrapper';

/**
* Will check the token from local storage and check if the user is logged in.
* If no, redirect to login page
*/
export default ({ children }: React.PropsWithChildren<{}>) => {
const dispatch = useDispatch();
const { kkcClient } = useGRPCClients();
const grpcWrapper = useGRPCWrapper();
const { isInitialLoading } = useAppState();

useEffect(() => {
// skip loading for maintenance mode
dispatch(doInitBackgroundLoad(kkcClient!));
dispatch(doInitBackgroundLoad(grpcWrapper));
}, []);

return isInitialLoading ? (
Expand Down
3 changes: 2 additions & 1 deletion src/libraries/axios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import axios from 'axios';
import { RootState } from 'states/types';
import { AuthState } from 'states/auth/types';
import { useSelector } from 'react-redux';
import { getAuthorizationHeader } from './helpers';

const AxiosInterceptor = () => {
const { token } = useSelector<RootState, AuthState>(
Expand All @@ -13,7 +14,7 @@ const AxiosInterceptor = () => {
axios.interceptors.request.use((config) => {
if (token) {
// eslint-disable-next-line no-param-reassign
config.headers.Authorization = `Bearer ${token}`;
config.headers.Authorization = getAuthorizationHeader(token);
}
return config;
});
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/constants/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const PATH_AUTH = '/auth';
export const PATH_APP = '/app';

export const PATH_ENV = `${PATH_APP}/environment`;
// export const PATH_SERVICES = `${PATH_APP}/services`;

export const PATH_ACCOUNT = `${PATH_APP}/account`;
export const PATH_BILLING = `${PATH_APP}/billing`;

Expand Down
5 changes: 3 additions & 2 deletions src/libraries/grpc/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import { SyncTimeRequest, SyncTimeResponse } from 'types/proto/coreapi_pb';
import { invokeGRPC, CoreMethod } from './common';

export const getKintoConfig: CoreMethod<KintoConfiguration, {}> = (
client: KintoCoreServiceClient
client: KintoCoreServiceClient,
token
): Promise<KintoConfiguration | null> => {
return invokeGRPC<Empty, KintoConfiguration>(
client.getKintoConfiguration,
'',
token,
new Empty(),
client
);
Expand Down
10 changes: 2 additions & 8 deletions src/libraries/grpc/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ServiceError,
} from 'types/proto/coreapi_pb_service';
import { grpc } from '@improbable-eng/grpc-web';
import { getAuthorizationHeader } from '../helpers';

export interface WatchStream<T, P> {
(
Expand Down Expand Up @@ -41,7 +42,7 @@ export const invokeGRPC = <T, R>(
context: KintoCoreServiceClient
): Promise<R | null> => {
const headers = new grpc.Metadata();
headers.set('Authorization', `Bearer ${token}`);
headers.set('Authorization', getAuthorizationHeader(token));

return new Promise((resolve, reject) => {
call.bind(context)(req, headers, (err, message) => {
Expand All @@ -52,10 +53,3 @@ export const invokeGRPC = <T, R>(
});
});
};

export const getAuthHeader = (token: string): grpc.Metadata => {
const headers = new grpc.Metadata();
headers.set('Authorization', `Bearer ${token}`);

return headers;
};
Loading

0 comments on commit 0e344e7

Please sign in to comment.