Skip to content

Commit

Permalink
Merge pull request #1 from javiruiz27/create-libs
Browse files Browse the repository at this point in the history
feat: verify token
  • Loading branch information
javiruiz27 authored Dec 17, 2024
2 parents 359b3ca + 2d520ec commit 6cb6a22
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 80 deletions.
16 changes: 7 additions & 9 deletions apps/auth/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
import koa from 'koa';
import jwt from "koa-jwt";
import router from "./routes";
import bodyParser from 'koa-bodyparser';
import cors from "koa-cors"
import jwtMiddleware from 'koa-jwt';


const host = process.env.HOST ?? 'localhost';
const port = process.env.PORT ? Number(process.env.PORT) : 8080;
const secretKey = 'your-secret-key';

const app = new koa();
app.use(cors({
origin: '*', // or specify a specific origin, e.g., 'http://example.com'
origin: '*',
credentials: true,
headers: ['Content-Type', 'Authorization'],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
}));
app.use(bodyParser())

app.use(jwt({
secret: 'your-shared-secret',
passthrough: true
app.use(jwtMiddleware({
secret: secretKey,
passthrough: true,
}));


app.use(bodyParser())
app.use(router.routes()).use(router.allowedMethods());

app.listen(port, host, () => {
Expand Down
49 changes: 29 additions & 20 deletions apps/auth/src/routes.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,41 @@
import koaRouter from "koa-router";
import crypto from "crypto";
const jwt = require('koa-jwt');
import koaRouter from 'koa-router';
import crypto from 'crypto';
import jwt from 'jsonwebtoken';

const router = new koaRouter();

router.get("hello", "/", (ctx) => {
ctx.body = "<h1>Hello</h1>";
router.get('hello', '/', (ctx) => {
ctx.body = '<h1>Hello</h1>';
});

router.post('/otp/generate', async (ctx, next) => {
const otp = crypto.randomBytes(2).toString('hex');
const email = ctx.request.body;
//set in context state temporally
ctx.state[email] = otp;
ctx.body = { otp };
console.log("hola", ctx.state[email]);
const otp = crypto.randomBytes(2).toString('hex');
const email = ctx.request.body;

ctx.app.user = { email: email.email, otp };

ctx.body = { otp };

next();
});

router.post('/otp/verify', async (ctx, next) => {
const userOtp = ctx.request.body.otp;
const storedOtp = "927100b8f1ee"; //await getStoredOtp(ctx.state.user.id); // retrieve stored OTP
if (userOtp === storedOtp) {
const token = jwt.sign({ userId: ctx.state.user.id }, 'your-secret-key', { expiresIn: '1h' });
ctx.body = { token };
} else {
ctx.status = 401;
ctx.body = { error: 'Invalid OTP' };
}
const { otp: userOtp, email } = ctx.request.body;
console.log('users', ctx.app.user);

const storedOtp = ctx.app.user?.otp ?? ''; // retrieve stored OTP

if (userOtp === storedOtp) {
const token = jwt.sign({ user: email }, 'your-secret-key', {
expiresIn: '1h',
});
ctx.body = { token };
} else {
ctx.status = 401;
ctx.body = { error: 'Invalid OTP' };
}

next();
});

export default router;
5 changes: 2 additions & 3 deletions apps/ui/src/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { AuthenticationContext } from './context/authentication-context';
import ValidateTokenForm from './components/validate-token-form/validate-token-form';

export function App() {
const { otpToken } = useContext(AuthenticationContext);
console.log("🚀 ~ App ~ otpToken:", otpToken)
const { isWaitingForValidation } = useContext(AuthenticationContext);

const appContainerStyles = {
height: '100vh',
Expand All @@ -16,7 +15,7 @@ export function App() {

return (
<div data-testid="app" style={appContainerStyles}>
{otpToken ? (
{isWaitingForValidation ? (
<div data-testid="validate_token">
<ValidateTokenForm />
</div>
Expand Down
52 changes: 23 additions & 29 deletions apps/ui/src/app/components/otp-input/otp-input.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
import { makeStyles, useTheme } from '@mui/material';
import React from 'react'
import React, { Dispatch, SetStateAction } from 'react';
import OtpInput from 'react-otp-input';

const useStyles = makeStyles(theme => ({
grid: {
backgroundColor: "grey",
height: "50vh",
textAlign: "center"
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main
},
submit: {
margin: theme.spacing(3, 0, 2)
},
paper: {
marginTop: theme.spacing(8),
display: "flex",
flexDirection: "column",
alignItems: "center"
}
}));

const OtpInput = () => {

return (
<div>OtpInput</div>
)
type OtpInputProps = {
userToken: string;
setUserToken: Dispatch<SetStateAction<string>>;
}

export default OtpInput
export const OtpInputComp: React.FC<OtpInputProps> = ({
userToken, setUserToken
}) =>{

return (
<OtpInput
value={userToken}
onChange={setUserToken}
numInputs={4}
renderSeparator={<span>-</span>}
renderInput={(props) => <input {...props} />}
inputStyle={{
height: "3em",
width: "3em"
}}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const RequestTokenForm: React.FC<IRequestTokenForm> = (props) => {
},
});

const { setOtpToken } = useContext(AuthenticationContext);
const { setIsWaitingForValidation, setEmail } = useContext(AuthenticationContext);
const {mutate: fetchNewToken, isSuccess, data} = useRequestToken();

const onSubmit: SubmitHandler<Inputs> = (data, e) => {
Expand All @@ -38,8 +38,11 @@ const RequestTokenForm: React.FC<IRequestTokenForm> = (props) => {
};

useEffect(() => {
setOtpToken(data?.otp ?? null);
}, [isSuccess, setOtpToken, data])
if(data?.otp) {
setIsWaitingForValidation(true);
setEmail(data.emailRequired);
}
}, [isSuccess, data, setIsWaitingForValidation, setEmail])


return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,46 @@
import { Typography } from '@mui/material';
import React from 'react'
import { Button, Typography } from '@mui/material';
import { useContext, useEffect, useState } from 'react';
import { OtpInputComp } from '../otp-input/otp-input';
import useValidateToken from '../../hooks/use-validate-token';
import { AuthenticationContext } from '../../context/authentication-context';
import { Snackbar } from '@mui/material';

const ValidateTokenForm = () => {
const [userToken, setUserToken] = useState<string>("");
const [openErrorMessage, setOpenErrorMessage] = useState<boolean>(false);


const { mutate: fetchValidation, isSuccess, data: jwtToken, error } = useValidateToken();
const {email} = useContext(AuthenticationContext);


const handleValidateToken = () => {
fetchValidation({email: email ?? "", otp: userToken});
}

useEffect(() => {
if(error) {
setOpenErrorMessage(true);
}
}, [error])

return (
<div>
<div style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
gap: "8px"
}}>
<Typography variant="h5">Introduce your token</Typography>
<OtpInputComp userToken={userToken} setUserToken={setUserToken} />
<Button type='submit' onClick={handleValidateToken}>
Validate
</Button>
<Snackbar
open={openErrorMessage}
message={"Error validating your otp token"}
onClose={() => setOpenErrorMessage(false)}
/>
</div>
)
}
Expand Down
15 changes: 10 additions & 5 deletions apps/ui/src/app/context/authentication-context.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { createContext, Dispatch, SetStateAction } from "react";

interface IAuthenticationContext {
otpToken: string | null;
setOtpToken: Dispatch<SetStateAction<string | null>>;

email: string | null;
setEmail: Dispatch<SetStateAction<string | null>>;
isWaitingForValidation: boolean;
setIsWaitingForValidation: Dispatch<SetStateAction<boolean>>;
}

export const authenticationContextInitialValue: IAuthenticationContext = {
otpToken: null,
setOtpToken: () => {
email: null,
setEmail: () => {
/** do nothing */
},
isWaitingForValidation: false,
setIsWaitingForValidation: () => {
/** do nothing */
}
};

export const AuthenticationContext = createContext(authenticationContextInitialValue);
9 changes: 6 additions & 3 deletions apps/ui/src/app/context/authentication-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import { ReactNode, useState } from "react";
import { AuthenticationContext } from "./authentication-context";

export const AuthenticationProvider = ({ children }: {children: ReactNode}) => {
const [otpToken, setOtpToken] = useState<string | null>(null);
const [email, setEmail] = useState<string | null>(null);
const [isWaitingForValidation, setIsWaitingForValidation] = useState(false);

return (
<AuthenticationContext.Provider
value={{
otpToken,
setOtpToken
email,
setEmail,
isWaitingForValidation,
setIsWaitingForValidation
}}
>
{children}
Expand Down
28 changes: 28 additions & 0 deletions apps/ui/src/app/hooks/use-validate-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useMutation } from '@tanstack/react-query';
import { postValidateToken } from '../services';

const VALIDATE_TOKEN_KEY = 'validate_token';

interface IValidateMutation {
email: string;
otp: string;
}

const useValidateToken = () => {
const { data, mutate, error, isSuccess } = useMutation({
mutationKey: [VALIDATE_TOKEN_KEY],
mutationFn: async ({email, otp}: IValidateMutation) => {
const response = await postValidateToken(email, otp);
return response.token;
},
});

return {
data,
mutate,
error,
isSuccess
};
};

export default useValidateToken;
21 changes: 18 additions & 3 deletions apps/ui/src/app/services/otp-authentication.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import axios from 'axios';

const apiUrlGenerateToken = 'http://localhost:8080/otp/generate';
const apiURLValidateToken = 'http://localhost:8080/otp/verify';

export const postRequestToken = (email: string) => {

const body = {
email: email,
}
};

return axios
.post(apiUrlGenerateToken, body)
Expand All @@ -17,4 +17,19 @@ export const postRequestToken = (email: string) => {
.catch((error) => {
console.error(error);
});
};
};

export const postValidateToken = (email: string, otp: string) => {
const body = {
email: email,
otp: otp,
};
return axios
.post(apiURLValidateToken, body)
.then((response) => {
return response.data;
})
.catch((error) => {
console.error(error);
});
};
13 changes: 12 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"koa-router": "13.0.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-hook-form": "^7.54.1"
"react-hook-form": "^7.54.1",
"react-otp-input": "^3.1.1"
},
"devDependencies": {
"@eslint/js": "^9.8.0",
Expand Down

0 comments on commit 6cb6a22

Please sign in to comment.