Skip to content

Commit

Permalink
Merge pull request #577 from knowit/feature/9-opprette-ad-palogging
Browse files Browse the repository at this point in the history
[9] Opprette AD-pålogging for Folk.knowit.no
  • Loading branch information
joacimds committed May 28, 2024
2 parents db3bcc0 + e9ffbff commit 6b55f7d
Show file tree
Hide file tree
Showing 14 changed files with 2,524 additions and 277 deletions.
101 changes: 4 additions & 97 deletions apps/server/routers/authRouter.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { Issuer } from 'openid-client'
import express, { Request, Response, Router } from 'express'
import { Issuer, TokenSet } from 'openid-client'
import { URL } from 'url'
import reporting from '../reporting'
const router: Router = express.Router()

const router: Router = express.Router()
const authEndpoint = process.env.OAUTH_URL
const clientId = process.env.CLIENT_ID
const clientSecret = process.env.CLIENT_SECRET

const dpIssuer = new Issuer({
issuer: `${authEndpoint}/oauth2/`,
Expand All @@ -19,69 +17,10 @@ const dpIssuer = new Issuer({
const getClient = (applicationUrl = '') =>
new dpIssuer.Client({
client_id: clientId,
client_secret: clientSecret,
redirect_uris: [`${applicationUrl}/auth/callback`],
redirect_uris: [`${applicationUrl}`],
response_types: ['code'],
})

const getOrigin = (url: string) => {
const parsed = new URL(url)
return `${parsed.protocol}//${parsed.host}`
}

const getPath = (url: string) => new URL(url).pathname

router.get('/login', function (req: Request, res: Response) {
const { referer } = req.headers
const authorizationUrl = getClient(getOrigin(referer)).authorizationUrl({
scope: 'email openid profile',
})

res.cookie('authReferer', referer, {
path: '/',
httpOnly: true,
secure: true,
sameSite: 'lax',
})
res.redirect(302, authorizationUrl)
})

router.get('/logout', async function (req: Request, res: Response) {
const { referer } = req.headers
const logoutUri = `${getOrigin(referer)}/`
const logoutUrl = getClient().endSessionUrl({
client_id: clientId,
logout_uri: logoutUri,
})
res.clearCookie('refreshToken')
res.redirect(logoutUrl)
})

router.get('/callback', async function (req: Request, res: Response) {
const { authReferer: referer }: { authReferer: string } = req.cookies

const origin = getOrigin(referer)

const tokens = await getClient(origin).oauthCallback(
`${origin}/auth/callback`,
req.query
)
const cookieSettings = {
sameSite: true,
secure: true,
path: '/',
}

res.cookie('refreshToken', tokens.refresh_token, {
httpOnly: true,
...cookieSettings,
})
res.clearCookie('authReferer')
const redirectURL = new URL(referer)
redirectURL.searchParams.append('login', 'true')
res.redirect(302, redirectURL.toString())
})

router.get('/userInfo', async function (req: Request, res: Response) {
const accessToken: string = req.headers.authorization
.split(/bearer/i)
Expand All @@ -98,41 +37,9 @@ router.get('/userInfo', async function (req: Request, res: Response) {
)

res.send({
name: [userInfo.given_name || '', userInfo.family_name || ''].join(' '),
name: userInfo.name,
email: userInfo.email,
picture: userInfo.picture,
})
})

router.post('/refresh', async function (req: Request, res: Response) {
const refreshToken = req.cookies['refreshToken'] || null

if (!refreshToken) {
return (res.statusCode = 403)
}

getClient()
.grant({
grant_type: 'refresh_token',
refresh_token: refreshToken,
})
.then((tokens: TokenSet) =>
res.send({
accessToken: tokens.access_token,
expiresAt: tokens.expires_at,
expiration: tokens.expires_in,
sameSite: true,
secure: true,
})
)
.catch((err) =>
res.send(
reporting({
message: 'Auth failed grant on /refresh',
data: err,
})
)
)
})

export default router
2 changes: 2 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"lint:fix": "run -T eslint --fix ./src/**/*.{ts,tsx}"
},
"dependencies": {
"@aws-amplify/ui-react": "^6.1.9",
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@jonkoops/matomo-tracker": "^0.7.0",
Expand All @@ -50,6 +51,7 @@
"@nivo/radar": "^0.83.0",
"@nivo/sunburst": "^0.83.0",
"@types/d3": "^7.4.0",
"aws-amplify": "^6.3.1",
"axios": "^1.6.0",
"browserslist": "^4.21.4",
"d3": "^7.8.2",
Expand Down
79 changes: 47 additions & 32 deletions apps/web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { useEffect, useState } from 'react'
import { Theme, styled } from '@mui/material/styles'
import { updateTheme } from './theme'
import { useUserInfo } from './hooks/useUserInfo'
import { fetchAuthSession } from 'aws-amplify/auth'
import { CssBaseline, useMediaQuery } from '@mui/material'
import {
Theme,
ThemeProvider,
StyledEngineProvider,
styled,
} from '@mui/material/styles'
import Header from './components/header/Header'
import Content from './components/Content'
import Footer from './components/Footer'
import { useUserInfo } from './context/UserInfoContext'
import { CssBaseline, useMediaQuery } from '@mui/material'
import { ThemeProvider } from '@mui/material/styles'
import { StyledEngineProvider } from '@mui/material/styles'
import { updateTheme } from './theme'

declare module '@mui/styles/defaultTheme' {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
Expand All @@ -22,6 +26,7 @@ const AppContainer = styled('div')(() => ({
display: 'flex',
flexDirection: 'column',
}))

const AppContentContainer = styled('div')(({ theme }) => ({
padding: '30px',
backgroundColor: theme.palette.background.paper,
Expand All @@ -31,14 +36,29 @@ const AppContentContainer = styled('div')(({ theme }) => ({
justifyContent: 'space-between',
flexGrow: 1,
}))

const AppMainContent = styled('main')(() => ({ width: '100%', height: '100%' }))

export default function App() {
const [activeTheme, setActiveTheme] = useState<Theme>(updateTheme('light'))
const [darkMode, setDarkMode] = useState(false)
const [activeTheme, setActiveTheme] = useState<Theme | undefined>()
const url = new URL(window.location.href)
const { user } = useUserInfo()
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)')

useEffect(() => {
const initApp = async () => {
try {
await fetchAuthSession()
} catch (e) {
if (e !== 'No current user') {
console.warn(e)
}
}
}

initApp()
}, [])

useEffect(() => {
if (localStorage.getItem('darkMode') === null) {
setActiveTheme(updateTheme(prefersDarkMode ? 'dark' : 'light'))
Expand All @@ -53,16 +73,7 @@ export default function App() {
}
}, [prefersDarkMode])

if (url.searchParams.has('login')) {
localStorage.setItem('login', 'true')
url.searchParams.delete('login')
history.replaceState(null, '', url)
}

const { user } = useUserInfo()
if (user === undefined) return null

const handleModeChange = () => {
function handleModeChange() {
localStorage.setItem('darkMode', (!darkMode).toString())
setDarkMode(!darkMode)
setActiveTheme(
Expand All @@ -73,19 +84,23 @@ export default function App() {
}

return (
<StyledEngineProvider injectFirst>
<ThemeProvider theme={activeTheme}>
<CssBaseline />
<AppContainer>
<Header darkMode={darkMode} onChangeMode={handleModeChange} />
<AppContentContainer>
<AppMainContent>
<Content />
</AppMainContent>
<Footer />
</AppContentContainer>
</AppContainer>
</ThemeProvider>
</StyledEngineProvider>
<>
{user !== undefined && (
<StyledEngineProvider injectFirst>
<ThemeProvider theme={activeTheme}>
<CssBaseline />
<AppContainer>
<Header darkMode={darkMode} onChangeMode={handleModeChange} />
<AppContentContainer>
<AppMainContent>
<Content />
</AppMainContent>
<Footer />
</AppContentContainer>
</AppContainer>
</ThemeProvider>
</StyledEngineProvider>
)}
</>
)
}
23 changes: 4 additions & 19 deletions apps/web/src/api/client.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import axios from 'axios'
import { renewAuth } from './auth/authClient'
import {
getAccessToken,
getAccessTokenExpiresAt,
isAccessTokenValid,
} from './auth/authHelpers'
import { ApiError } from './errorHandling'
import { fetchAuthSession } from 'aws-amplify/auth'
import axios from 'axios'

const BASE_URL = '/'

Expand All @@ -26,23 +21,13 @@ interface GetOptions {
* @returns the data at the endpoint
*/
const getAt = async <T>(endpoint: string, options?: GetOptions) => {
const expiresAt = getAccessTokenExpiresAt()
// Attempt to renew if a user is present and has a valid token/it is to be forced.
if (options?.forceAuth || !isAccessTokenValid(expiresAt)) {
try {
await renewAuth()
} catch (error) {
return Promise.reject(error)
}
}

const accessToken = getAccessToken()
const session = await fetchAuthSession({ forceRefresh: true })

try {
const res = await instance.get<T>(endpoint, {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
Authorization: `Bearer ${session?.tokens?.accessToken.toString()}`,
},
params: { ...options?.params },
})
Expand Down
41 changes: 21 additions & 20 deletions apps/web/src/components/Content.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { Navigate, Route, Routes } from 'react-router-dom'
import { isLoggedIn } from '../api/auth/authHelpers'
import { useUserInfo } from '../context/UserInfoContext'
import {
CompetencePage,
CustomerPage,
Expand All @@ -12,28 +10,31 @@ import {
DebugPage,
OrganizationStructurePage,
} from '../pages'
import { useUserInfo } from '../hooks/useUserInfo'
import LoginPage from '../pages/login/LoginPage'

export default function Content() {
const { user } = useUserInfo()

if (!isLoggedIn(user)) {
return <LoginPage />
}
const { isAuthenticated } = useUserInfo()

return (
<Routes>
<Route path="/" element={<Navigate replace to="/ansatte" />} />
<Route path="/ansatte" element={<EmployeePage />} />
<Route path="/ansatt/:id" element={<EmployeeProfilePage />} />
<Route path="/kunder" element={<CustomerPage />} />
<Route path="/kunder/:id" element={<CustomerSitePage />} />
<Route path="/kompetanse" element={<CompetencePage />} />
<Route path="/organisasjon" element={<OrganizationStructurePage />} />
<Route path="/arbeidsmiljo" element={<UnderConstructionPage />} />
<Route path="/rekruttering" element={<UnderConstructionPage />} />
<Route path="/debug" element={<DebugPage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
<>
{isAuthenticated ? (
<Routes>
<Route path="/" element={<Navigate replace to="/ansatte" />} />
<Route path="/ansatte" element={<EmployeePage />} />
<Route path="/ansatt/:id" element={<EmployeeProfilePage />} />
<Route path="/kunder" element={<CustomerPage />} />
<Route path="/kunder/:id" element={<CustomerSitePage />} />
<Route path="/kompetanse" element={<CompetencePage />} />
<Route path="/organisasjon" element={<OrganizationStructurePage />} />
<Route path="/arbeidsmiljo" element={<UnderConstructionPage />} />
<Route path="/rekruttering" element={<UnderConstructionPage />} />
<Route path="/debug" element={<DebugPage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
) : (
<LoginPage />
)}
</>
)
}
Loading

0 comments on commit 6b55f7d

Please sign in to comment.