mirror of
https://github.com/maelgangloff/domain-watchdog.git
synced 2025-12-29 16:15:04 +00:00
feat: allow unauthenticated users to perform domain name lookups
This commit is contained in:
@@ -9,8 +9,8 @@ import WatchlistPage from './pages/tracking/WatchlistPage'
|
||||
import UserPage from './pages/UserPage'
|
||||
import type {PropsWithChildren} from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useState} from 'react'
|
||||
import {getUser} from './utils/api'
|
||||
import LoginPage, {AuthenticatedContext} from './pages/LoginPage'
|
||||
import {getConfiguration, getUser, type InstanceConfig} from './utils/api'
|
||||
import LoginPage from './pages/LoginPage'
|
||||
import ConnectorPage from './pages/tracking/ConnectorPage'
|
||||
import NotFoundPage from './pages/NotFoundPage'
|
||||
import useBreakpoint from './hooks/useBreakpoint'
|
||||
@@ -19,12 +19,14 @@ import {jt, t} from 'ttag'
|
||||
import {MenuOutlined} from '@ant-design/icons'
|
||||
import TrackedDomainPage from './pages/tracking/TrackedDomainPage'
|
||||
import IcannRegistrarPage from "./pages/infrastructure/IcannRegistrarPage"
|
||||
import type {AuthContextType} from "./contexts"
|
||||
import { AuthenticatedContext, ConfigurationContext} from "./contexts"
|
||||
|
||||
const PROJECT_LINK = 'https://github.com/maelgangloff/domain-watchdog'
|
||||
const LICENSE_LINK = 'https://www.gnu.org/licenses/agpl-3.0.txt'
|
||||
|
||||
const ProjectLink = <Typography.Link key="projectLink" target='_blank' href={PROJECT_LINK}>Domain Watchdog</Typography.Link>
|
||||
const LicenseLink = <Typography.Link key="licenceLink" target='_blank' href={LICENSE_LINK}>AGPL-3.0-or-later</Typography.Link>
|
||||
const LicenseLink = <Typography.Link key="licenceLink" target='_blank' rel='license' href={LICENSE_LINK}>AGPL-3.0-or-later</Typography.Link>
|
||||
|
||||
function SiderWrapper(props: PropsWithChildren<{sidebarCollapsed: boolean, setSidebarCollapsed: (collapsed: boolean) => void}>): React.ReactElement {
|
||||
const {sidebarCollapsed, setSidebarCollapsed, children} = props
|
||||
@@ -68,18 +70,21 @@ export default function App(): React.ReactElement {
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
||||
|
||||
const authenticated = useCallback((authenticated: boolean) => {
|
||||
setIsAuthenticated(authenticated)
|
||||
}, [])
|
||||
|
||||
const contextValue = useMemo(() => ({
|
||||
authenticated,
|
||||
setIsAuthenticated
|
||||
}), [authenticated, setIsAuthenticated])
|
||||
const [configuration, setConfiguration] = useState<InstanceConfig | undefined>(undefined)
|
||||
|
||||
const [darkMode, setDarkMode] = useState(false)
|
||||
|
||||
const windowQuery = window.matchMedia('(prefers-color-scheme:dark)')
|
||||
|
||||
|
||||
const authContextValue: AuthContextType = useMemo(() => ({
|
||||
isAuthenticated,
|
||||
setIsAuthenticated
|
||||
}), [isAuthenticated])
|
||||
|
||||
const configContextValue = useMemo(() => ({
|
||||
configuration,
|
||||
}), [configuration])
|
||||
|
||||
const darkModeChange = useCallback((event: MediaQueryListEvent) => {
|
||||
setDarkMode(event.matches)
|
||||
}, [])
|
||||
@@ -93,13 +98,18 @@ export default function App(): React.ReactElement {
|
||||
|
||||
useEffect(() => {
|
||||
setDarkMode(windowQuery.matches)
|
||||
getUser().then(() => {
|
||||
setIsAuthenticated(true)
|
||||
if (location.pathname === '/login') navigate('/home')
|
||||
}).catch(() => {
|
||||
setIsAuthenticated(false)
|
||||
const pathname = location.pathname
|
||||
if (!['/login', '/tos', '/faq', '/privacy'].includes(pathname)) navigate('/home')
|
||||
getConfiguration().then(configuration => {
|
||||
setConfiguration(configuration)
|
||||
|
||||
getUser().then(() => {
|
||||
setIsAuthenticated(true)
|
||||
if (location.pathname === '/login') navigate('/home')
|
||||
}).catch(() => {
|
||||
setIsAuthenticated(false)
|
||||
const pathname = location.pathname
|
||||
if(configuration.publicRdapLookupEnabled) return navigate('/search/domain')
|
||||
if (!['/login', '/tos', '/faq', '/privacy'].includes(pathname)) return navigate('/home')
|
||||
})
|
||||
})
|
||||
}, [])
|
||||
|
||||
@@ -109,10 +119,11 @@ export default function App(): React.ReactElement {
|
||||
algorithm: darkMode ? theme.darkAlgorithm : undefined
|
||||
}}
|
||||
>
|
||||
<AuthenticatedContext.Provider value={contextValue}>
|
||||
<ConfigurationContext.Provider value={configContextValue}>
|
||||
<AuthenticatedContext.Provider value={authContextValue}>
|
||||
<Layout hasSider style={{minHeight: '100vh'}}>
|
||||
<SiderWrapper sidebarCollapsed={sidebarCollapsed} setSidebarCollapsed={setSidebarCollapsed}>
|
||||
<Sider isAuthenticated={isAuthenticated}/>
|
||||
<Sider />
|
||||
</SiderWrapper>
|
||||
<Layout>
|
||||
<Layout.Header style={{padding: 0}}>
|
||||
@@ -129,7 +140,7 @@ export default function App(): React.ReactElement {
|
||||
}}
|
||||
>
|
||||
<Routes>
|
||||
<Route path='/' element={<Navigate to='/home'/>}/>
|
||||
<Route path='/' element={<Navigate to={configuration?.publicRdapLookupEnabled ? '/search/domain' : '/home'}/>}/>
|
||||
<Route path='/home' element={<TextPage resource='home.md'/>}/>
|
||||
|
||||
<Route path='/search/domain' element={<DomainSearchPage/>}/>
|
||||
@@ -158,8 +169,8 @@ export default function App(): React.ReactElement {
|
||||
</Layout.Content>
|
||||
<Layout.Footer style={{textAlign: 'center'}}>
|
||||
<Flex gap='middle' wrap justify='center'>
|
||||
<Link to='/tos'><Button type='text'>{t`TOS`}</Button></Link>
|
||||
<Link to='/privacy'><Button type='text'>{t`Privacy Policy`}</Button></Link>
|
||||
<Link to='/tos' rel='terms-of-service'><Button type='text'>{t`TOS`}</Button></Link>
|
||||
<Link to='/privacy' rel='privacy-policy'><Button type='text'>{t`Privacy Policy`}</Button></Link>
|
||||
<Link to='/faq'><Button type='text'>{t`FAQ`}</Button></Link>
|
||||
<Typography.Link
|
||||
target='_blank'
|
||||
@@ -193,6 +204,7 @@ export default function App(): React.ReactElement {
|
||||
</Layout>
|
||||
</Layout>
|
||||
</AuthenticatedContext.Provider>
|
||||
</ConfigurationContext.Provider>
|
||||
</ConfigProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ import {Button, Flex, Form, Input, message} from 'antd'
|
||||
import {t} from 'ttag'
|
||||
import React, {useContext, useEffect, useState} from 'react'
|
||||
import {getUser, login} from '../utils/api'
|
||||
import {AuthenticatedContext} from '../pages/LoginPage'
|
||||
import {useNavigate} from 'react-router-dom'
|
||||
|
||||
import {showErrorAPI} from '../utils/functions/showErrorAPI'
|
||||
import {AuthenticatedContext} from "../contexts"
|
||||
|
||||
interface FieldType {
|
||||
username: string
|
||||
@@ -66,9 +66,9 @@ export function LoginForm({ssoLogin}: { ssoLogin?: boolean }) {
|
||||
</Form.Item>
|
||||
|
||||
<Flex wrap justify="center" gap="middle">
|
||||
<Button type='primary' htmlType='submit'>
|
||||
{t`Submit`}
|
||||
</Button>
|
||||
<Button type='primary' htmlType='submit'>
|
||||
{t`Submit`}
|
||||
</Button>
|
||||
{ssoLogin &&
|
||||
<Button type='dashed' htmlType='button' href='/login/oauth'>
|
||||
{t`Log in with SSO`}
|
||||
|
||||
@@ -17,10 +17,14 @@ import {
|
||||
UserOutlined
|
||||
} from '@ant-design/icons'
|
||||
import {Menu} from 'antd'
|
||||
import React from 'react'
|
||||
import React, {useContext} from 'react'
|
||||
import {useLocation, useNavigate} from 'react-router-dom'
|
||||
import {AuthenticatedContext, ConfigurationContext} from "../contexts"
|
||||
|
||||
export function Sider() {
|
||||
const {isAuthenticated} = useContext(AuthenticatedContext)
|
||||
const {configuration} = useContext(ConfigurationContext)
|
||||
|
||||
export function Sider({isAuthenticated}: { isAuthenticated: boolean }) {
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
|
||||
@@ -42,7 +46,7 @@ export function Sider({isAuthenticated}: { isAuthenticated: boolean }) {
|
||||
icon: <CompassOutlined/>,
|
||||
label: t`Domain`,
|
||||
title: t`Domain Finder`,
|
||||
disabled: !isAuthenticated,
|
||||
disabled: !configuration?.publicRdapLookupEnabled && !isAuthenticated,
|
||||
onClick: () => navigate('/search/domain')
|
||||
},
|
||||
/*
|
||||
|
||||
24
assets/contexts/index.ts
Normal file
24
assets/contexts/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type React from 'react'
|
||||
import {createContext} from 'react'
|
||||
import type {InstanceConfig} from "../utils/api"
|
||||
|
||||
|
||||
export type ConfigurationContextType = {
|
||||
configuration: InstanceConfig | undefined
|
||||
}
|
||||
|
||||
export const ConfigurationContext = createContext<ConfigurationContextType>({
|
||||
configuration: undefined,
|
||||
})
|
||||
|
||||
|
||||
export type AuthContextType = {
|
||||
isAuthenticated: boolean
|
||||
setIsAuthenticated: React.Dispatch<React.SetStateAction<boolean>>
|
||||
}
|
||||
|
||||
export const AuthenticatedContext = createContext<AuthContextType>({
|
||||
isAuthenticated: false,
|
||||
setIsAuthenticated: () => {
|
||||
},
|
||||
})
|
||||
@@ -1,28 +1,16 @@
|
||||
import React, {createContext, useEffect, useState} from 'react'
|
||||
import React, { useContext, useEffect, useState} from 'react'
|
||||
import {Button, Card} from 'antd'
|
||||
import {t} from 'ttag'
|
||||
import TextPage from './TextPage'
|
||||
import {LoginForm} from '../components/LoginForm'
|
||||
import type { InstanceConfig} from '../utils/api'
|
||||
import {getConfiguration} from '../utils/api'
|
||||
import {RegisterForm} from '../components/RegisterForm'
|
||||
import useBreakpoint from "../hooks/useBreakpoint"
|
||||
|
||||
export const AuthenticatedContext = createContext<
|
||||
{
|
||||
authenticated: (authenticated: boolean) => void
|
||||
setIsAuthenticated: React.Dispatch<React.SetStateAction<boolean>>
|
||||
}
|
||||
>({
|
||||
authenticated: () => {
|
||||
},
|
||||
setIsAuthenticated: () => {
|
||||
}
|
||||
})
|
||||
import {ConfigurationContext} from "../contexts"
|
||||
|
||||
export default function LoginPage() {
|
||||
const [wantRegister, setWantRegister] = useState<boolean>(false)
|
||||
const [configuration, setConfiguration] = useState<InstanceConfig>()
|
||||
const { configuration } = useContext(ConfigurationContext)
|
||||
|
||||
const md = useBreakpoint('md')
|
||||
|
||||
const toggleWantRegister = () => {
|
||||
@@ -30,14 +18,11 @@ export default function LoginPage() {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getConfiguration().then((configuration) => {
|
||||
if(!configuration.registerEnabled && configuration.ssoLogin && configuration.ssoAutoRedirect) {
|
||||
window.location.href = '/login/oauth'
|
||||
return
|
||||
}
|
||||
setConfiguration(configuration)
|
||||
})
|
||||
}, [])
|
||||
if(!configuration?.registerEnabled && configuration?.ssoLogin && configuration?.ssoAutoRedirect) {
|
||||
window.location.href = '/login/oauth'
|
||||
return
|
||||
}
|
||||
}, [configuration])
|
||||
|
||||
const grid = [
|
||||
<Card.Grid key="form" style={{width: md ? '100%' : '50%', textAlign: 'center'}} hoverable={false}>
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import type { FormProps} from 'antd'
|
||||
import {FloatButton} from 'antd'
|
||||
import {Empty, Flex, message, Skeleton} from 'antd'
|
||||
import React, {useContext, useEffect, useState} from 'react'
|
||||
import type {FormProps} from 'antd'
|
||||
import {Empty, Flex, FloatButton, message, Skeleton} from 'antd'
|
||||
import type {Domain, Watchlist} from '../../utils/api'
|
||||
import {addDomainToWatchlist} from '../../utils/api'
|
||||
import {getDomain} from '../../utils/api'
|
||||
import {addDomainToWatchlist, getDomain} from '../../utils/api'
|
||||
import type {AxiosError} from 'axios'
|
||||
import {t} from 'ttag'
|
||||
import type { FieldType} from '../../components/search/DomainSearchBar'
|
||||
import type {FieldType} from '../../components/search/DomainSearchBar'
|
||||
import {DomainSearchBar} from '../../components/search/DomainSearchBar'
|
||||
import {DomainResult} from '../../components/search/DomainResult'
|
||||
import {showErrorAPI} from '../../utils/functions/showErrorAPI'
|
||||
import {useNavigate, useParams} from 'react-router-dom'
|
||||
import {PlusOutlined} from '@ant-design/icons'
|
||||
import WatchlistSelectionModal from '../../components/tracking/watchlist/WatchlistSelectionModal'
|
||||
import {AuthenticatedContext} from "../../contexts"
|
||||
|
||||
export default function DomainSearchPage() {
|
||||
const {query} = useParams()
|
||||
@@ -21,6 +20,8 @@ export default function DomainSearchPage() {
|
||||
const domainLdhName = domain?.ldhName
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [addToWatchlistModal, setAddToWatchlistModal] = useState(false)
|
||||
const {isAuthenticated} = useContext(AuthenticatedContext)
|
||||
|
||||
|
||||
const [messageApi, contextHolder] = message.useMessage()
|
||||
const navigate = useNavigate()
|
||||
@@ -72,7 +73,7 @@ export default function DomainSearchPage() {
|
||||
}
|
||||
</Skeleton>
|
||||
</Flex>
|
||||
{domain
|
||||
{domain && isAuthenticated
|
||||
&& <FloatButton
|
||||
style={{
|
||||
position: 'fixed',
|
||||
@@ -94,7 +95,7 @@ export default function DomainSearchPage() {
|
||||
onClose: () => setAddToWatchlistModal(false),
|
||||
cancelText: t`Cancel`,
|
||||
okText: t`Add`
|
||||
}}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ export interface InstanceConfig {
|
||||
ssoLogin: boolean
|
||||
limtedFeatures: boolean
|
||||
registerEnabled: boolean
|
||||
publicRdapLookupEnabled: boolean
|
||||
}
|
||||
|
||||
export interface Statistics {
|
||||
|
||||
Reference in New Issue
Block a user