Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Authentication #237

Merged
merged 33 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
25e83bf
added basic login screen and functions
Corepex May 6, 2024
d258466
restructured interfaces
Corepex May 6, 2024
f9d3943
improved router, added redirect function on login, added redirect fun…
Corepex May 8, 2024
d802d03
added `react-redux` and `react-router-dom`
Corepex May 8, 2024
297586c
added translations and aria tags
Corepex May 8, 2024
fcb90c2
merged controller actions
Corepex May 8, 2024
5158ba5
Apply php-cs-fixer changes
Corepex May 8, 2024
65f62b0
added a punch of auth related stuff i cant describe
Corepex May 14, 2024
dc4ec5d
Merge remote-tracking branch 'origin/110-authentication' into 110-aut…
Corepex May 14, 2024
173b5bc
added unified middleware to layouts
Corepex May 14, 2024
026bb78
build files
Corepex May 14, 2024
4179f95
removed cookies from `prepareHeaders`
Corepex May 14, 2024
1106b9f
Merge branch '1.x' into 110-authentication
Corepex May 14, 2024
a52f6f5
moved files due to structure change in 1.x
Corepex May 14, 2024
7b8eae9
added jsdoc, removed logline
Corepex May 14, 2024
e405a6f
added `package-lock.json`
Corepex May 14, 2024
889156c
build files
Corepex May 14, 2024
e9799c5
Apply eslint-fixer changes
Corepex May 14, 2024
5550265
Automatic frontend build
Corepex May 14, 2024
147c6db
Merge branch '1.x' into 110-authentication
Corepex Jun 7, 2024
cf2e1e1
changed authentication to session based auth
Corepex Jun 7, 2024
c74df7d
added translation container
Corepex Jun 7, 2024
3d6e396
build files
Corepex Jun 7, 2024
cf8a25b
implemented `current-user-information` endpoint with beacon hook
Corepex Jun 10, 2024
06d9ab8
build files
Corepex Jun 10, 2024
561667a
added a global app loader to fetch data before router inits
Corepex Jun 10, 2024
ff8c60d
build files
Corepex Jun 10, 2024
ba308cb
moved several files to `modules/app/auth`
Corepex Jun 11, 2024
e4f74e9
moved user api slice to modules folder
Corepex Jun 11, 2024
6f21755
once again changed folder structure
Corepex Jun 11, 2024
4041fd7
improved router and default page
Corepex Jun 11, 2024
b2713d3
improved `AppLoader`
Corepex Jun 11, 2024
1064794
build files, updated path to user.slice.gen
Corepex Jun 11, 2024
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
2 changes: 1 addition & 1 deletion assets/build/api/docs.jsonopenapi.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions assets/build/api/openapi-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ const config: ConfigFile = {
},
'../../js/src/core/modules/asset/properties-api-slice.gen.ts': {
filterEndpoints: [/properties/i]
},
'../../js/src/core/modules/auth/user/user-api-slice.gen.ts': {
filterEndpoints: [/user/i]
}
},
exportName: 'api',
Expand Down
2 changes: 1 addition & 1 deletion assets/dist/core-dll/core-manifest.json

Large diffs are not rendered by default.

28 changes: 26 additions & 2 deletions assets/dist/sdk/manifest.json

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions assets/js/src/core/app/router/router.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Pimcore
*
* This source file is available under two different licenses:
* - Pimcore Open Core License (POCL)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license https://github.com/pimcore/studio-ui-bundle/blob/1.x/LICENSE.md POCL and PCL
*/

import React from 'react'
import { createBrowserRouter } from 'react-router-dom'
import { DefaultPage } from '@Pimcore/modules/app/default-page'
import { LoginPage } from '@Pimcore/modules/auth/login-page'

export const router = createBrowserRouter([
{
path: '/admin/studio',
element: <DefaultPage />
},
{
path: '/admin/studio/login',
element: <LoginPage />
}
])
56 changes: 56 additions & 0 deletions assets/js/src/core/components/login-form/login-form-style.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Pimcore
*
* This source file is available under two different licenses:
* - Pimcore Open Core License (POCL)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license https://github.com/pimcore/studio-ui-bundle/blob/1.x/LICENSE.md POCL and PCL
*/

import { createStyles } from 'antd-style'

export const useStyle = createStyles(({ token, css }) => {
return {
form: css`
form {
display: flex;
flex-direction: column;
gap: 8px;
font-family: Lato, sans-serif;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 22px;

.flex-space {
display: flex;
justify-content: space-between;
align-items: center;
}

.ant-btn-link {
color: ${token.colorPrimary};

&:hover {
color: ${token.colorPrimaryHover};
}
}
}

.login__additional-logins {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;

.ant-btn {
width: 100%;
}
}
`
}
})
110 changes: 110 additions & 0 deletions assets/js/src/core/components/login-form/login-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* Pimcore
*
* This source file is available under two different licenses:
* - Pimcore Open Core License (POCL)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license https://github.com/pimcore/studio-ui-bundle/blob/1.x/LICENSE.md POCL and PCL
*/

import { Button, Checkbox, Input } from 'antd'
import React from 'react'
import { EyeInvisibleOutlined, EyeTwoTone, UserOutlined } from '@ant-design/icons'
import { useStyle } from '@Pimcore/components/login-form/login-form-style'
import { type ILoginRequest, useLoginMutation } from '@Pimcore/modules/auth/auth-api-slice'
import { useDispatch } from 'react-redux'
import { useMessage } from '@Pimcore/components/message/useMessage'
import { useTranslation } from 'react-i18next'
import { setUser } from '@Pimcore/modules/auth/user/user-slice'

export interface IAdditionalLogins {
key: string
name: string
link: string
}

interface ILoginFormProps {
additionalLogins?: IAdditionalLogins[]
}

export const LoginForm = ({ additionalLogins }: ILoginFormProps): React.JSX.Element => {
const dispatch = useDispatch()
const { styles } = useStyle()
const messageApi = useMessage()
const { t } = useTranslation()

const [formState, setFormState] = React.useState<ILoginRequest>({
username: '',
password: ''
})

const [login, { isLoading }] = useLoginMutation()

const handleAuthentication = async (e: React.FormEvent<HTMLFormElement>): Promise<void> => {
try {
e.preventDefault()
const user = await login(formState).unwrap()

dispatch(setUser(user))
} catch (error) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
messageApi.error({
content: error.data.error
})
}
}

return (
<div className={ styles.form }>
<form onSubmit={ handleAuthentication }>
<Input
onChange={ (e) => { setFormState({ ...formState, username: e.target.value }) } }
placeholder="Username"
prefix={ <UserOutlined /> }
/>
<Input.Password
iconRender={ (visible) => (visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />) }
onChange={ (e) => { setFormState({ ...formState, password: e.target.value }) } }
placeholder="Password"
/>
<div className={ 'flex-space' }>
<Checkbox
aria-label={ t('aria.login-form-additional-logins.remember-me-checkbox') }
>
{t('login-form.remember-me')}
</Checkbox>
<Button type={ 'link' }>{t('login-form.forgot-password')}</Button>
</div>

<Button
htmlType="submit"
loading={ isLoading }
type="primary"
>
{t('login-form.login')}
</Button>
</form>

{Array.isArray(additionalLogins) && (
<div className={ 'login__additional-logins' }>
<p>{t('login-form-additional-logins.or')}</p>

{additionalLogins?.map((login) => (
<Button
aria-label={ `${t('aria.login-form-additional-logins.additional-login-provider')} ${login.name}` }
href={ login.link }
key={ login.key }
type={ 'primary' }
>
{login.name}
</Button>
))}
</div>
)}
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,59 @@
*/

import React, { useEffect, useState } from 'react'
import { api } from '@Pimcore/modules/auth/user/user-api-slice.gen'
import { useAppDispatch } from '@Pimcore/app/store'
import { useGetTranslationsMutation } from '@Pimcore/modules/app/translations/translations-api-slice.gen'
import { useTranslation } from 'react-i18next'
import { setUser } from '@Pimcore/modules/auth/user/user-slice'

interface TranslationsLoaderContainerProps {
export interface IAppLoaderProps {
children: React.ReactNode
}

export const TranslationsLoaderContainer = (props: TranslationsLoaderContainerProps): React.JSX.Element => {
const [isLoading, setIsLoading] = useState(true)
export const AppLoader = (props: IAppLoaderProps): React.JSX.Element => {
const dispatch = useAppDispatch()
const [translations] = useGetTranslationsMutation()
const { i18n } = useTranslation()
const [isLoading, setIsLoading] = useState(true)

useEffect(() => {
translations({ translation: { locale: 'en', keys: [] } })
async function initLoadUser (): Promise<any> {
const userFetcher = dispatch(api.endpoints.getStudioApiUserCurrentUserInformation.initiate())

userFetcher
.then(({ data, isSuccess }) => {
if (isSuccess && data !== undefined) {
dispatch(setUser(data))
}
})
.catch(() => {})

return await userFetcher
}

async function loadTranslations (): Promise<any> {
await translations({ translation: { locale: 'en', keys: [] } })
.unwrap()
.then(response => {
i18n.addResourceBundle('en', 'translation', response.keys ?? [], true, true)
setIsLoading(false)
})
.catch((error) => {
console.error('rejected', error)
})
}

useEffect(() => {
Promise.all([
initLoadUser(),
loadTranslations()
]).then(() => {
setIsLoading(false)
}).catch(() => {})
}, [])

if (isLoading) {
return (
<div>Loading...</div>
<div>Loading ...</div>
)
}

Expand Down
13 changes: 6 additions & 7 deletions assets/js/src/core/modules/app/app-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,20 @@

import React, { StrictMode } from 'react'
import { GlobalProvider } from './global-provider'
import { BaseLayoutView } from '@Pimcore/modules/app/base-layout/base-layout-view'
import { App as AntApp } from 'antd'
import { TranslationsLoaderContainer } from '@Pimcore/modules/app/translations/translations-loader-container'
import { Background } from '@Pimcore/components/background/background'
import { RouterProvider } from 'react-router-dom'
import { router } from '@Pimcore/app/router/router'
import { AppLoader } from '@Pimcore/modules/app/app-loader'

export const AppView = (): React.JSX.Element => {
return (
<>
<StrictMode>
<GlobalProvider>
<AntApp>
<Background />
<TranslationsLoaderContainer>
<BaseLayoutView />
</TranslationsLoaderContainer>
<AppLoader>
<RouterProvider router={ router } />
</AppLoader>
</AntApp>
</GlobalProvider>
</StrictMode>
Expand Down
28 changes: 28 additions & 0 deletions assets/js/src/core/modules/app/default-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Pimcore
*
* This source file is available under two different licenses:
* - Pimcore Open Core License (POCL)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license https://github.com/pimcore/studio-ui-bundle/blob/1.x/LICENSE.md POCL and PCL
*/

import React from 'react'
import { Background } from '@Pimcore/components/background/background'
import { BaseLayoutView } from '@Pimcore/modules/app/base-layout/base-layout-view'
import { useMiddleware } from '@Pimcore/modules/auth/hooks/use-middleware'

export const DefaultPage = (): React.JSX.Element => {
useMiddleware()

return (
<>
<Background />
<BaseLayoutView />
</>
)
}
42 changes: 42 additions & 0 deletions assets/js/src/core/modules/auth/auth-api-slice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Pimcore
*
* This source file is available under two different licenses:
* - Pimcore Open Core License (POCL)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license https://github.com/pimcore/studio-ui-bundle/blob/1.x/LICENSE.md POCL and PCL
*/

import { api } from '@Pimcore/app/api/pimcore'

export interface IUser {
username: string
roles: string[]
}

export interface ILoginRequest {
username: string
password: string
}

export const authApi = api
.injectEndpoints({
endpoints: (builder) => ({
login: builder.mutation<IUser, ILoginRequest>({
query: (credentials) => ({
url: 'studio/api/login',
method: 'POST',
body: credentials
})
})
}),
overrideExisting: false
})

export { authApi as api }

export const { useLoginMutation } = authApi
21 changes: 21 additions & 0 deletions assets/js/src/core/modules/auth/hooks/use-is-authenticated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Pimcore
*
* This source file is available under two different licenses:
* - Pimcore Open Core License (POCL)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license https://github.com/pimcore/studio-ui-bundle/blob/1.x/LICENSE.md POCL and PCL
*/

import { useMemo } from 'react'
import { useUser } from '@Pimcore/modules/auth/hooks/use-user'

export const useIsAuthenticated = (): boolean => {
const user = useUser()

return useMemo(() => (user?.username !== null), [user])
}
Loading
Loading