8000 fix login from redirect ; added hooks ; APP_URL fns · devel0/example-webapp-with-auth@ab81c92 · GitHub
[go: up one dir, main page]

Skip to content

Commit ab81c92

Browse files
committed
fix login from redirect ; added hooks ; APP_URL fns
1 parent c66e557 commit ab81c92

File tree

13 files changed

+161
-105
lines changed

13 files changed

+161
-105
lines changed

src/frontend/src/axios.manager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ export const ConfigAxios = () => {
3232
store.dispatch(setGeneralNetwork(false))
3333

3434
if (error?.response?.status === HttpStatusCode.Unauthorized) {
35-
if (document.location.pathname !== APP_URL_Login) {
35+
if (document.location.pathname !== APP_URL_Login()) {
3636
localStorage.removeItem(LOCAL_STORAGE_CURRENT_USER_NFO)
37-
document.location = APP_URL_Login
37+
document.location = APP_URL_Login()
3838
}
3939

4040
return

src/frontend/src/components/Layout.tsx

Lines changed: 7 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
11
import { AboutDialog } from '../dialogs/AboutDialog';
2-
import {
3-
APP_URL_Login, APP_URL_Users, DEFAULT_SIZE_0_5_REM, LOCAL_STORAGE_CURRENT_USER_NFO,
4-
LOCAL_STORAGE_REFRESH_TOKEN_EXPIRE, RENEW_REFRESH_TOKEN_BEFORE_EXPIRE_SEC
5-
} from '../constants/general'
2+
import { APP_URL_Users, DEFAULT_SIZE_0_5_REM } from '../constants/general'
63
import { authApi } from '../axios.manager';
7-
import { AxiosError, HttpStatusCode } from 'axios';
84
import { Box, LinearProgress } from '@mui/material'
9-
import { computeIsMobile, handleApiException } from '../utils/utils';
10-
import { CurrentUserNfo } from '../types/CurrentUserNfo';
115
import { GlobalState } from '../redux/states/GlobalState'
12-
import { ReactNode, useEffect, useState } from 'react'
13-
import { setIsMobile, setLoggedOut, setSuccessfulLogin, setUrlWanted } from '../redux/slices/globalSlice'
6+
import { ReactNode, useState } from 'react'
7+
import { setLoggedOut } from '../redux/slices/globalSlice'
148
import { useAppDispatch, useAppSelector } from '../redux/hooks/hooks'
15-
import { useEventListener } from 'usehooks-ts';
169
import { useLocation, useNavigate } from 'react-router-dom'
10+
import { useLoginManager } from '../hooks/useLoginManager';
11+
import { useMobileDetect } from '../hooks/useMobileDetect';
1712
import LogoutIcon from '@mui/icons-material/Logout';
1813
import ResponsiveAppBar, { AppBarItem } from './ResponsiveAppBar'
1914

@@ -28,76 +23,9 @@ const MainLayout = (props: Props) => {
2823
const location = useLocation()
2924
const [aboutDialogOpen, setAboutDialogOpen] = useState(false)
3025

31-
useEffect(() => {
32-
if (global.currentUserInitialized) {
33-
const act = () => {
34-
const q = localStorage.getItem(LOCAL_STORAGE_REFRESH_TOKEN_EXPIRE);
35-
if (q) {
36-
const refreshTokenExpire = new Date(q);
37-
console.log(`refresh token will expire at ${refreshTokenExpire}`)
26+
useLoginManager()
3827

39-
const renewAt = new Date(refreshTokenExpire.getTime() - RENEW_REFRESH_TOKEN_BEFORE_EXPIRE_SEC * 1e3);
40-
const now = new Date()
41-
if (now.getTime() < renewAt.getTime()) {
42-
console.log(` renew at ${renewAt}`)
43-
setTimeout(async () => {
44-
console.log(" renewing refresh token");
45-
try {
46-
const res = await authApi.apiAuthRenewRefreshTokenGet();
47-
if (res.data.refreshTokenNfo?.expiration) {
48-
localStorage.setItem(LOCAL_STORAGE_REFRESH_TOKEN_EXPIRE, res.data.refreshTokenNfo.expiration);
49-
act()
50-
}
51-
} catch (ex_) {
52-
const ex = ex_ as AxiosError
53-
handleApiException(ex, "Renew refresh token")
54-
}
55-
}, renewAt.getTime() - now.getTime());
56-
}
57-
}
58-
}
59-
60-
act()
61-
}
62-
}, [global.currentUserInitialized])
63-
64-
useEffect(() => {
65-
if (location.pathname !== APP_URL_Login && (!global.currentUserInitialized || !global.currentUser)) {
66-
67-
authApi.apiAuthCurrentUserGet()
68-
.then(res => {
69-
if (res.status === HttpStatusCode.Ok) {
70-
const currentUser: CurrentUserNfo = {
71-
userName: res.data.userName!,
72-
email: res.data.email!,
73-
roles: Array.from(res.data.roles ?? []),
74-
permissions: Array.from(res.data.permissions ?? [])
75-
}
76-
77-
dispatch(setSuccessfulLogin(currentUser))
78-
}
79-
else {
80-
dispatch(setUrlWanted(location.pathname))
81-
navigate(APP_URL_Login)
82-
}
83-
})
84-
.catch(_err => {
85-
const err = _err as AxiosError
86-
87-
if (err.response?.status === HttpStatusCode.Unauthorized) {
88-
if (document.location.pathname !== APP_URL_Login) {
89-
dispatch(setUrlWanted(location.pathname))
90-
localStorage.removeItem(LOCAL_STORAGE_CURRENT_USER_NFO)
91-
document.location = APP_URL_Login
92-
}
93-
}
94-
})
95-
}
96-
}, [location.pathname, global.currentUser, global.currentUserInitialized])
97-
98-
useEventListener('resize', () => {
99-
dispatch(setIsMobile(computeIsMobile()))
100-
})
28+
useMobileDetect()
10129

10230
const menuPages: AppBarItem[] = [
10331
{
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
// https://github.com/remix-run/react-router/issues/10637#issuecomment-1802180978
22

3-
import { useAppSelector } from '../redux/hooks/hooks'
4-
import { Navigate } from 'react-router'
3+
import { APP_URL_Login } from '../constants/general';
54
import { GlobalState } from '../redux/states/GlobalState'
5+
import { Navigate } from 'react-router'
66
import { Outlet } from "react-router-dom";
7+
import { useAppSelector } from '../redux/hooks/hooks'
78
import Layout from './Layout';
89

910
const ProtectedRoutes = () => {
1011
const global = useAppSelector<GlobalState>((state) => state.global)
1112

13+
const loginRedirectUrlFrom = () => {
14+
if (location.pathname !== APP_URL_Login())
15+
return encodeURIComponent(location.pathname)
16+
}
17+
1218
return global.currentUser
1319
?
1420
<Layout child={<Outlet />} />
1521
:
16-
<Navigate to={`/app/login/${encodeURIComponent(location.pathname)}`} replace />;
22+
<Navigate to={APP_URL_Login(loginRedirectUrlFrom())} replace />;
1723
};
1824

1925
export default ProtectedRoutes;

src/frontend/src/constants/general.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,28 @@ export const APP_LOGO_TEXT: string | undefined = "WEBAPP";
1313

1414
import { PaletteMode } from "@mui/material";
1515
import { green } from "@mui/material/colors"
16+
import { generateUrl } from "../utils/utils";
1617

17-
export const APP_URL_BASE = "/app";
18+
//------------------------------------------ urls
1819

19-
export const APP_URL_Home = `${APP_URL_BASE}`
20-
export const APP_URL_Login = `${APP_URL_BASE}/login/:from?/:token?`
21-
export const APP_URL_Users = `${APP_URL_BASE}/users`
20+
export const APP_URL_BASE =
21+
"/app";
22+
23+
export const APP_URL_Home =
24+
`${APP_URL_BASE}`
25+
26+
export const APP_URL_Login = (from?: string, token?: string) => generateUrl(
27+
`${APP_URL_BASE}/login/:from/:token`, { from, token })
28+
29+
export const APP_URL_Users =
30+
`${APP_URL_BASE}/users`
31+
32+
//------------------------------------------ urls (end)
2233

2334
export const LOCAL_STORAGE_CURRENT_USER_NFO = "currentUserNfo";
35+
2436
export const LOCAL_STORAGE_THEME = "theme";
37+
2538
export const LOCAL_STORAGE_REFRESH_TOKEN_EXPIRE = "refreshTokenExpire";
2639

2740
/** invoke RenewRefreshToken api before expiration */

src/frontend/src/dialogs/EditUserDialog.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,7 @@ import { from } from "linq-to-typescript";
1616
import { AxiosError } from "axios";
1717

1818
export const NewUserDataSample = () => {
19-
let res: EditUserRequestDto = {
20-
existingUsername: null,
21-
editUsername: null,
22-
editEmail: null,
23-
editPassword: null,
24-
editLockoutEnd: null,
25-
editRoles: null,
26-
editDisabled: null,
27-
}
19+
let res: EditUserRequestDto = { }
2820
return res
2921
}
3022

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import {
2+
APP_URL_Login, LOCAL_STORAGE_CURRENT_USER_NFO, LOCAL_STORAGE_REFRESH_TOKEN_EXPIRE,
3+
RENEW_REFRESH_TOKEN_BEFORE_EXPIRE_SEC
4+
} from "../constants/general"
5+
import { authApi } from "../axios.manager"
6+
import { CurrentUserNfo } from "../types/CurrentUserNfo"
7+
import { GlobalState } from "../redux/states/GlobalState"
8+
import { handleApiException } from "../utils/utils"
9+
import { HttpStatusCode, AxiosError } from "axios"
10+
import { setSuccessfulLogin, setUrlWanted } from "../redux/slices/globalSlice"
11+
import { useAppSelector, useAppDispatch } from "../redux/hooks/hooks"
12+
import { useEffect } from "react"
13+
import { useNavigate } from "react-router-dom"
14+
15+
export const useLoginManager = () => {
16+
const global = useAppSelector<GlobalState>((state) => state.global)
17+
const dispatch = useAppDispatch()
18+
const navigate = useNavigate()
19+
20+
useEffect(() => {
21+
if (global.currentUserInitialized) {
22+
const act = () => {
23+
const q = lo 10000 calStorage.getItem(LOCAL_STORAGE_REFRESH_TOKEN_EXPIRE);
24+
if (q) {
25+
const refreshTokenExpire = new Date(q);
26+
console.log(`refresh token will expire at ${refreshTokenExpire}`)
27+
28+
const renewAt = new Date(refreshTokenExpire.getTime() - RENEW_REFRESH_TOKEN_BEFORE_EXPIRE_SEC * 1e3);
29+
const now = new Date()
30+
if (now.getTime() < renewAt.getTime()) {
31+
console.log(` renew at ${renewAt}`)
32+
setTimeout(async () => {
33+
console.log(" renewing refresh token");
34+
try {
35+
const res = await authApi.apiAuthRenewRefreshTokenGet();
36+
if (res.data.refreshTokenNfo?.expiration) {
37+
localStorage.setItem(LOCAL_STORAGE_REFRESH_TOKEN_EXPIRE, res.data.refreshTokenNfo.expiration);
38+
act()
39+
}
40+
} catch (ex_) {
41+
const ex = ex_ as AxiosError
42+
handleApiException(ex, "Renew refresh token")
43+
}
44+
}, renewAt.getTime() - now.getTime());
45+
}
46+
}
47+
}
48+
49+
act()
50+
}
51+
}, [global.currentUserInitialized])
52+
53+
useEffect(() => {
54+
if (location.pathname !== APP_URL_Login() && (!global.currentUserInitialized || !global.currentUser)) {
55+
56+
authApi.apiAuthCurrentUserGet()
57+
.then(res => {
58+
if (res.status === HttpStatusCode.Ok) {
59+
const currentUser: CurrentUserNfo = {
60+
userName: res.data.userName!,
61+
email: res.data.email!,
62+
roles: Array.from(res.data.roles ?? []),
63+
permissions: Array.from(res.data.permissions ?? [])
64+
}
65+
66+
dispatch(setSuccessfulLogin(currentUser))
67+
}
68+
else {
69+
dispatch(setUrlWanted(location.pathname))
70+
navigate(APP_URL_Login())
71+
}
72+
})
73+
.catch(_err => {
74+
const err = _err as AxiosError
75+
76+
if (err.response?.status === HttpStatusCode.Unauthorized) {
77+
if (document.location.pathname !== APP_URL_Login()) {
78+
dispatch(setUrlWanted(location.pathname))
79+
localStorage.removeItem(LOCAL_STORAGE_CURRENT_USER_NFO)
80+
document.location = APP_URL_Login()
81+
}
82+
}
83+
})
84+
}
85+
}, [location.pathname, global.currentUser, global.currentUserInitialized])
86+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { useEventListener } from "usehooks-ts"
2+
import { useAppSelector, useAppDispatch } from "../redux/hooks/hooks"
3+
import { setIsMobile } from "../redux/slices/globalSlice"
4+
import { GlobalState } from "../re F438 dux/states/GlobalState"
5+
import { computeIsMobile } from "../utils/utils"
6+
7+
export const useMobileDetect = () => {
8+
const global = useAppSelector<GlobalState>((state) => state.global)
9+
const dispatch = useAppDispatch()
10+
11+
useEventListener('resize', () => dispatch(setIsMobile(computeIsMobile())))
12+
}

src/frontend/src/pages/LoginPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export const LoginPage = () => {
4343

4444
useEffect(() => {
4545
if (global.currentUserInitialized && global.currentUser) {
46-
if (global.urlWanted && global.urlWanted !== APP_URL_Login) {
46+
if (global.urlWanted && global.urlWanted !== APP_URL_Login()) {
4747
const urlWanted = global.urlWanted
4848

4949
dispatch(setUrlWanted(undefined))

src/frontend/src/pages/MainPage.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ export const MainPage = () => {
3333
<Button onClick={async () => {
3434
try {
3535
await mainApi.apiMainLongRunningGet()
36+
37+
setSnack({
38+
msg: ['completed'],
39+
type: "success"
40+
})
3641
} catch (_ex) {
3742
const ex = _ex as AxiosError
3843
handleApiException(ex)

src/frontend/src/pages/UsersPage.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,8 @@ export const UsersPage = () => {
8585
if (res.data.length > 0) {
8686
const user = res.data[0]
8787
setUserData({
88-
existingUsername: user.userName!,
89-
editUsername: null,
90-
editEmail: user.email,
91-
editLockoutEnd: null,
92-
editPassword: null,
88+
existingUsername: user.userName!,
89+
editEmail: user.email,
9390
editRoles: user.roles,
9491
editDisabled: user.disabled
9592
})

src/frontend/src/redux/slices/globalSlice.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { CurrentUserNfo } from "../../types/CurrentUserNfo";
22
import { GlobalInitialState } from "../states/GlobalState";
3-
import { LOCAL_STORAGE_THEME } from "../../constants/general";
3+
import { LOCAL_STORAGE_CURRENT_USER_NFO, LOCAL_STORAGE_THEME } from "../../constants/general";
44
import { PaletteMode } from "@mui/material";
55
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
66
import { UserPermission } from "../../../api";
@@ -33,7 +33,9 @@ export const globalSlice = createSlice({
3333
},
3434

3535
setLoggedOut: (state) => {
36+
state.urlWanted = undefined
3637
state.currentUser = undefined
38+
localStorage.removeItem(LOCAL_STORAGE_CURRENT_USER_NFO)
3739
},
3840

3941
setTheme: (state, action: PayloadAction<PaletteMode>) => {

src/frontend/src/router.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const router = createBrowserRouter(
1717

1818
// login
1919
{
20-
path: APP_URL_Login,
20+
path: APP_URL_Login(),
2121
element: <LoginPage />
2222
},
2323

src/frontend/src/utils/utils.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,19 @@ export const computeIsMobile = () => {
111111
const isMobile = w <= 600
112112

113113
return isMobile
114+
}
115+
116+
export const generateUrl = (schema: string, values: { [key: string]: string | undefined }) => {
117+
let res = schema
118+
119+
Object.keys(values).forEach(k => {
120+
const v = values[k]
121+
122+
if (v !== undefined) {
123+
var rgx = new RegExp(`/:${k}[?]*`, "g")
124+
res = res.replace(rgx, `/${v}`)
125+
}
126+
})
127+
128+
return res
114129
}

0 commit comments

Comments
 (0)
0