Skip to content

Commit

Permalink
Add the 2factor dialog after login (#21)
Browse files Browse the repository at this point in the history
* Update the pipeline set-java module.
* Fix authentication flow in the application and add the 2factor dialog.
  • Loading branch information
gjong authored May 23, 2024
1 parent 552d921 commit 7fe860b
Show file tree
Hide file tree
Showing 13 changed files with 111 additions and 100 deletions.
38 changes: 0 additions & 38 deletions .github/ISSUE_TEMPLATE/bug_report.md

This file was deleted.

20 changes: 0 additions & 20 deletions .github/ISSUE_TEMPLATE/feature_request.md

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Configure Java version
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Configure Java version
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "pledger-ui",
"version": "1.3.0",
"private": true,
"proxy": "http://localhost:8080/",
"proxy": "http://127.0.0.1:8080/",
"homepage": "/ui",
"dependencies": {
"@mdi/js": "^7.4.47",
Expand Down
64 changes: 54 additions & 10 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,21 @@ import { RulesRoutes } from "./rules";

import './assets/css/Main.scss'
import './assets/css/Theme.scss'
import { lazy, Suspense, useState } from "react";
import { lazy, Suspense, useEffect, useState } from "react";
import { ContractRoutes } from "./contract";
import { BudgetRoutes } from "./budget";
import { ProfileRoutes } from "./profile";
import MobileSidebar from "./core/sidebar/mobile-sidebar";
import { BatchRoutes } from "./batch";
import TwoFactorCard from "./security/two-factor.card";
import SecurityRepository from "./core/repositories/security-repository";
import RestAPI from "./core/repositories/rest-api";
import { AxiosError } from "axios";

const LoginCard = lazy(() => import("./security/login-card"));
const RegisterCard = lazy(() => import("./security/RegisterCard"));

const routes = [
<Route path='/' element={<Navigate to='/dashboard'/>} key='index' />
]
routes.push(...AccountRoutes)
const routes = [...AccountRoutes]
routes.push(...CategoryRoutes)
routes.push(...SettingRoutes)
routes.push(...ReportRoutes)
Expand All @@ -38,18 +39,61 @@ routes.push(...ProfileRoutes)
routes.push(...BatchRoutes)

function App() {
const [_, setAuthenticate] = useState(false) //eslint-disable-line
const [isAuthenticated, setAuthenticate] = useState(false) //eslint-disable-line
const [twoFactorNeeded, setTwoFactor] = useState(false) //eslint-disable-line

if (sessionStorage.getItem('token')) {
const authenticated = () => {
RestAPI.profile()
.then(() => {
console.log('Profile loaded')
setAuthenticate(true)
setTwoFactor(false)
})
.catch((ex: AxiosError) => {
if (ex.response?.status === 403) {
setTwoFactor(true)
}
})
}
const logout = () => {
SecurityRepository.logout()
setAuthenticate(false)
}

useEffect(() => {
console.log('App mounted')
if (sessionStorage.getItem('token')) {
authenticated()
}

window.addEventListener('credentials-expired', logout)
}, [])

if (twoFactorNeeded) {
return (
<Suspense>
<BrowserRouter basename='/ui'>
<Notifications.NotificationCenter />
<Routes>
<Route key='two-factor' path="/two-factor" element={<TwoFactorCard callback={ authenticated }/>}/>
<Route key='redirect' path='/*' element={<Navigate to='/two-factor'/>}/>
</Routes>
</BrowserRouter>
</Suspense>
)
}

if (isAuthenticated) {
return (
<Suspense>
<BrowserRouter basename='/ui'>
<Sidebar logoutCallback={() => setAuthenticate(false)}/>
<MobileSidebar logoutCallback={() => setAuthenticate(false)}/>
<Sidebar logoutCallback={ logout }/>
<MobileSidebar logoutCallback={ logout }/>
<main className='Main px-2 md:px-5 h-[100vh] flex flex-col overflow-y-auto'>
<Notifications.NotificationCenter />
<Routes>
{routes}
<Route path='/*' element={<Navigate to='/dashboard'/>}/>
</Routes>
<Suspense>
<Outlet />
Expand All @@ -65,7 +109,7 @@ function App() {
<BrowserRouter basename='/ui'>
<Routes>
<Route path='/' element={<Navigate to='/login'/>}/>
<Route path='/login' element={<LoginCard callback={() => setAuthenticate(true)} />}/>
<Route path='/login' element={<LoginCard callback={ authenticated } />}/>
<Route path='/register' element={<RegisterCard />}/>
<Route path='/*' element={<Navigate to='/login'/>} />
</Routes>
Expand Down
4 changes: 2 additions & 2 deletions src/core/Notification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ const NotificationService = (() => {
}
const handleException = (error: AxiosError) => {
const apiError: ApiError = error.response?.data as ApiError
if (apiError._links.help) {
if (apiError?._links.help) {
push(apiError._links.help[0].href, 'warning')
} else {
console.error('Error intercepted', error)
notifyUser({ type: 'warning', message: (error.response as any).data.message })
}
}

Expand Down
5 changes: 2 additions & 3 deletions src/core/repositories/rest-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ const RestAPI = (() => {
return profile
}

const handle = (response: Promise<AxiosResponse>) => response.then(response => response.data)
const handle = (response: Promise<AxiosResponse>) => response
.then(response => response.data)

const api = {
profile: () => api.get('profile').then(updateProfile),
Expand All @@ -61,8 +62,6 @@ const RestAPI = (() => {
delete: (uri: string, settings = {}): Promise<void> => handle(axiosInstance.delete(uri, settings))
}

if (sessionStorage.getItem('token')) api.profile().finally(() => {})

return api
})()

Expand Down
18 changes: 9 additions & 9 deletions src/core/repositories/security-repository.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ const SecurityRepository = (api => {
sessionStorage.setItem('refresh-token', token.refreshToken);
sessionStorage.setItem('token', token.accessToken);
}),
twoFactor: (code) => api.post('security/2-factor', { verificationCode: code })
.then(serverResponse => {
const token = new TokenResponse(serverResponse)
sessionStorage.setItem('refresh-token', token.refreshToken);
sessionStorage.setItem('token', token.accessToken);
}),
register: (username, password) => api.put(`security/create-account`, { username, password }),
logout: () => {
sessionStorage.removeItem('token');
sessionStorage.removeItem('refresh-token');
}
sessionStorage.removeItem('token');
sessionStorage.removeItem('refresh-token');
}
}
})(RestAPI)

window.addEventListener('credentials-expired', _ => {
console.log('Credentials expired')
SecurityRepository.logout()
window.location.reload()
})

export default SecurityRepository
7 changes: 1 addition & 6 deletions src/core/sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@ type SidebarProps = {
}

const Sidebar = ({ logoutCallback } : SidebarProps) => {
const onLogout = () => {
dispatchEvent(new CustomEvent('credentials-expired'))
logoutCallback()
}

return <>
<div className='h-screen max-w-[218px] flex-col overflow-y-auto
hidden md:flex
Expand Down Expand Up @@ -55,7 +50,7 @@ const Sidebar = ({ logoutCallback } : SidebarProps) => {
</NavLink>

<Buttons.Button icon={ mdiLogoutVariant }
onClick={ onLogout }
onClick={ logoutCallback }
variant='icon'
className='px-2 text-[var(--sidebar-icon-color)]'/>
</footer>
Expand Down
7 changes: 1 addition & 6 deletions src/core/sidebar/mobile-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { NavLink } from "react-router-dom";
import ProfilePicture from "../../profile/profile-picture.component";
import { Buttons } from "../index";
import { mdiCloseBox, mdiLogoutVariant } from "@mdi/js";
import SecurityRepository from "../repositories/security-repository";


type SidebarProps = {
Expand All @@ -17,10 +16,6 @@ type SidebarProps = {

const MobileSidebar = ({ logoutCallback } : SidebarProps) => {
const [isOpen, setIsOpen] = useState(false)
const onLogout = () => {
SecurityRepository.logout()
logoutCallback()
}

useEffect(() => {
const onMenuClick = () => setIsOpen(previous => !previous)
Expand Down Expand Up @@ -61,7 +56,7 @@ const MobileSidebar = ({ logoutCallback } : SidebarProps) => {
className='border-none !text-white' />

<Buttons.Button icon={ mdiLogoutVariant }
onClick={ onLogout }
onClick={ logoutCallback }
variant='icon'
className='px-2 text-[var(--sidebar-icon-color)]'/>
</footer>
Expand Down
3 changes: 0 additions & 3 deletions src/security/login-card.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { mdiAccountPlus, mdiLogin, mdiWeb } from "@mdi/js";

import { Form, Input, SubmitButton } from '../core/form'
Expand All @@ -16,11 +15,9 @@ type LoginCallback = () => void

const LoginCard = ({ callback }: { callback: LoginCallback }) => {
const [failure, setFailure] = useState()
const navigate = useNavigate()

const onSubmit = (entity: LoginForm) => SecurityRepository.authenticate(entity.username, entity.password)
.then(() => callback())
.then(() => navigate('/dashboard'))
.catch(setFailure)

return (
Expand Down
39 changes: 39 additions & 0 deletions src/security/two-factor.card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Form, Input, SubmitButton } from "../core/form";
import { Layout, Message, Notifications } from "../core";
import { mdiCheck } from "@mdi/js";
import SecurityRepository from "../core/repositories/security-repository";
import { AxiosError } from "axios";

const TwoFactorCard = ({ callback }: { callback: () => void }) => {
const onSubmit = (entity: any) => {
SecurityRepository.twoFactor(entity.code)
.then(callback)
.catch((error: AxiosError) => Notifications.Service.exception(error))
}

return <>
<div className='flex justify-center h-[100vh] items-center'>
<Form entity='UserAccount' onSubmit={ onSubmit }>
<Layout.Card title='page.login.verify.title'
buttons={[
<SubmitButton key='verify'
label='page.login.verify.action'
icon={ mdiCheck }/>
]}
className='min-w-[30rem]'>

<Message label='page.login.verify.explain' variant='info' />

<Input.Text id='code'
title='UserAccount.twofactor.secret'
type='text'
pattern="^[0-9]{6}$"
required />

</Layout.Card>
</Form>
</div>
</>
}

export default TwoFactorCard;

0 comments on commit 7fe860b

Please sign in to comment.