Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions ui/app/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ import { useEffect, useMemo, useRef, useState } from 'react'

import { siteConfig } from '@/config/site'

import { useAuth } from '@/context/AuthContext'

type FormTabKey =
| 'thing'
| 'location'
Expand Down Expand Up @@ -56,7 +54,6 @@ export default function Home({
networks: any[]
selectedNetwork?: string
}) {
const { token } = useAuth()
const [localThings, setLocalThings] = useState<any[]>(things)

useEffect(() => {
Expand Down Expand Up @@ -102,11 +99,6 @@ export default function Home({
phenomenonTime?: string,
range?: { start?: string | null; end?: string | null }
) => {
if (siteConfig.authorizationEnabled && !token) {
setObsError('Missing token')
return
}

let startIso = range?.start ?? null
let endIso = range?.end ?? null

Expand All @@ -132,7 +124,7 @@ export default function Home({
setObsError(null)
try {
const { observationData } = await getObservationsByDatastream(
token ?? undefined,
undefined,
datastreamId,
startIso ?? undefined,
endIso ?? undefined
Expand Down
16 changes: 4 additions & 12 deletions ui/components/layout/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,8 @@ import { siteConfig } from '@/config/site'

import { useAuth } from '@/context/AuthContext'

import { getTokenUsername } from '@/lib/auth'

export default function Navbar() {
const { token, setToken } = useAuth()
const { authenticated, username, setSessionState } = useAuth()
const { t } = useTranslation()
const router = useRouter()

Expand Down Expand Up @@ -73,12 +71,6 @@ export default function Navbar() {
setSelectedLang(langCode)
}

const username = useMemo(() => {
if (!siteConfig.authorizationEnabled) return null
if (!token) return null
return getTokenUsername(token)
}, [token])

const initials = useMemo(() => {
if (!username) return ''
const parts = username.trim().split(/\s+/)
Expand All @@ -89,9 +81,9 @@ export default function Navbar() {

const handleLogout = async () => {
try {
if (token) await logout(token)
await logout()
} finally {
setToken(null)
setSessionState(false, null)
router.push(siteConfig.authorizationEnabled ? '/login' : '/')
}
}
Expand Down Expand Up @@ -148,7 +140,7 @@ export default function Navbar() {
<GithubIcon />
</Link>

{username && (
{authenticated && username && (
<div className="flex items-center gap-1">
<span className="hidden sm:inline">
{t('login.cheer')} <b>{username}</b>
Expand Down
109 changes: 42 additions & 67 deletions ui/context/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,98 +13,73 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { refresh } from '@/services/auth'
import { deleteCookie, setCookie } from 'cookies-next'
import { getSession } from '@/services/auth'
import React, { createContext, useContext, useEffect, useState } from 'react'

import { siteConfig } from '@/config/site'
import { decodeTokenPayload } from '@/lib/auth'

type AuthContextType = {
token: string | null
setToken: (token: string | null) => void
authenticated: boolean
username: string | null
setSessionState: (authenticated: boolean, username?: string | null) => void
refreshSession: () => Promise<void>
loading: boolean
}

const AuthContext = createContext<AuthContextType>({
token: null,
setToken: () => {},
authenticated: false,
username: null,
setSessionState: () => {},
refreshSession: async () => {},
loading: true,
})

//create the auth provider component
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [token, setTokenState] = useState<string | null>(null)
const [loading, setLoading] = useState(false)

//check token expiration and refresh if necessary
useEffect(() => {
if (!siteConfig.authorizationEnabled) return
if (!token) return
const payload = decodeTokenPayload(token)
if (!payload?.exp) return

//set now to current time in seconds
const now = Math.floor(Date.now() / 1000)

const timeLeft = payload.exp - now

//if the token is about to expire in less than 2 minutes, refresh it
if (timeLeft < 120) {
refresh(token).then((newToken) => {
//if the refresh was successful, set the new token
if (newToken) setToken(newToken)
//if the refresh failed, clear the token
else setToken(null)
})
}
}, [token])
const [authenticated, setAuthenticated] = useState(false)
const [username, setUsername] = useState<string | null>(null)
const [loading, setLoading] = useState(true)

const setSessionState = (
isAuthenticated: boolean,
nextUsername: string | null = null
) => {
setAuthenticated(isAuthenticated)
setUsername(nextUsername)
}

//initialize token from local storage
useEffect(() => {
const refreshSession = async () => {
if (!siteConfig.authorizationEnabled) {
setSessionState(true, null)
setLoading(false)
return
}

if (typeof window !== 'undefined') {
//take the token from local storage if it exists
const storedToken = localStorage.getItem('token')
if (storedToken) setTokenState(storedToken)
setLoading(true)
try {
const session = await getSession()
setSessionState(!!session?.authenticated, session?.username ?? null)
} catch {
setSessionState(false, null)
} finally {
setLoading(false)
}
}, [])

//set token in state and local storage
const setToken = (newToken: string | null) => {
setTokenState(newToken)

if (!siteConfig.authorizationEnabled) {
return
}

if (newToken) {
localStorage.setItem('token', newToken)
const payload = decodeTokenPayload(newToken)
const now = Math.floor(Date.now() / 1000)
const maxAge =
typeof payload?.exp === 'number' ? Math.max(payload.exp - now, 0) : undefined

setCookie('token', newToken, {
httpOnly: false,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
...(typeof maxAge === 'number' ? { maxAge } : {}),
path: '/',
})
} else {
localStorage.removeItem('token')
deleteCookie('token')
}
}

useEffect(() => {
refreshSession()
}, [])

return (
<AuthContext.Provider value={{ token, setToken, loading }}>
<AuthContext.Provider
value={{
authenticated,
username,
setSessionState,
refreshSession,
loading,
}}
>
{children}
</AuthContext.Provider>
)
Expand Down
22 changes: 3 additions & 19 deletions ui/features/auth/components/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import {
ModalFooter,
ModalHeader,
} from '@heroui/modal'
import { setCookie } from 'cookies-next'
import 'flag-icons/css/flag-icons.min.css'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
Expand All @@ -44,15 +43,14 @@ import { LogoIstSOS, LogoOSGeo } from '@/components/icons'
import { siteConfig } from '@/config/site'

import { useAuth } from '@/context/AuthContext'
import { decodeTokenPayload } from '@/lib/auth'

type LoginModalProps = {
open: boolean
onClose: () => void
}

export default function Login({ open, onClose }: LoginModalProps) {
const { setToken } = useAuth()
const { refreshSession } = useAuth()
const { t } = useTranslation()
const router = useRouter()

Expand Down Expand Up @@ -101,22 +99,8 @@ export default function Login({ open, onClose }: LoginModalProps) {
try {
const result = await login(username, password)

if (result?.access_token) {
setToken(result.access_token)
const payload = decodeTokenPayload(result.access_token)
const now = Math.floor(Date.now() / 1000)
const maxAge =
typeof payload?.exp === 'number'
? Math.max(payload.exp - now, 0)
: 60 * 60 * 24

setCookie('token', result.access_token, {
httpOnly: false,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge,
path: '/',
})
if (result?.success) {
await refreshSession()

onClose()
router.push('/')
Expand Down
22 changes: 5 additions & 17 deletions ui/features/forms/components/FormModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ import {
ThingIcon,
} from '@/components/icons'

import { useAuth } from '@/context/AuthContext'
import { siteConfig } from '@/config/site'

import { EntityFields, ExistingEntitySelect } from './wizard/fields'
Expand Down Expand Up @@ -112,7 +111,6 @@ export default function FormModal({
}: FormProps) {
const { t } = useTranslation()
const router = useRouter()
const { token } = useAuth()

const entityLabels = useMemo<Record<EntityKey, string>>(
() => ({
Expand Down Expand Up @@ -215,11 +213,6 @@ export default function FormModal({

setSubmitError(null)

if (siteConfig.authorizationEnabled && !token) {
setSubmitError('Missing token')
return
}

if (requiresCommitMessage && !commitMessage.trim()) {
setSubmitError(t('commit.message_required'))
return
Expand All @@ -232,40 +225,35 @@ export default function FormModal({

if (singleEntity === 'thing') {
result = await createThing(
normalizeEntityPayload('thing', singleDraft.thing) as CreateThingPayload,
token ?? undefined
normalizeEntityPayload('thing', singleDraft.thing) as CreateThingPayload
)
} else if (singleEntity === 'location') {
result = await createLocation(
normalizeEntityPayload(
'location',
singleDraft.location
) as CreateLocationPayload,
token ?? undefined
) as CreateLocationPayload
)
} else if (singleEntity === 'sensor') {
result = await createSensor(
normalizeEntityPayload(
'sensor',
singleDraft.sensor
) as CreateSensorPayload,
token ?? undefined
) as CreateSensorPayload
)
} else if (singleEntity === 'datastream') {
result = await createDatastream(
normalizeEntityPayload(
'datastream',
singleDraft.datastream
) as CreateDatastreamPayload,
token ?? undefined
) as CreateDatastreamPayload
)
} else {
result = await createObservedProperty(
normalizeEntityPayload(
'observedProperty',
singleDraft.observedProperty
) as CreateObservedPropertyPayload,
token ?? undefined
) as CreateObservedPropertyPayload
)
}

Expand Down
Loading