feat(auth): refactor auth, add oidc
Some checks are pending
Docker / build-and-push (frontend) (push) Waiting to run
Docker / build-and-push (backend) (push) Waiting to run
Deploy HD2 docs to Netlify / Deploys to netlify (push) Waiting to run
E2E Tests / backend-sqlite (push) Waiting to run
E2E Tests / backend-mariadb (push) Waiting to run
E2E Tests / backend-postgres (push) Waiting to run
E2E Tests / Build test build of frontend (push) Waiting to run
E2E Tests / frontend-cypress (1) (push) Blocked by required conditions
E2E Tests / frontend-cypress (2) (push) Blocked by required conditions
E2E Tests / frontend-cypress (3) (push) Blocked by required conditions
Lint and check format / Lint files and check formatting (push) Waiting to run
REUSE Compliance Check / reuse (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Static Analysis / Njsscan code scanning (push) Waiting to run
Static Analysis / CodeQL analysis (push) Waiting to run
Run tests & build / Test and build with NodeJS 20 (push) Waiting to run

Thanks to all HedgeDoc team members for the time discussing,
helping with weird Nest issues, providing feedback
and suggestions!

Co-authored-by: Philip Molares <philip.molares@udo.edu>
Signed-off-by: Philip Molares <philip.molares@udo.edu>
Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson 2024-03-23 02:10:25 +01:00
parent 1609f3e01f
commit 7f665fae4b
109 changed files with 2927 additions and 1700 deletions

View file

@ -0,0 +1,124 @@
/*
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { Button, Card, Form } from 'react-bootstrap'
import { Trans } from 'react-i18next'
import { useAsync } from 'react-use'
import { cancelPendingUser, confirmPendingUser, getPendingUserInfo } from '../../../api/auth/pending-user'
import { useRouter } from 'next/navigation'
import { useUiNotifications } from '../../notifications/ui-notification-boundary'
import { UsernameLabelField } from '../../common/fields/username-label-field'
import { DisplayNameField } from '../../common/fields/display-name-field'
import { ProfilePictureChoice, ProfilePictureSelectField } from '../../common/fields/profile-picture-select-field'
import { useOnInputChange } from '../../../hooks/common/use-on-input-change'
import { fetchAndSetUser } from '../utils/fetch-and-set-user'
/**
* The card where a new user can enter their user information.
*/
export const NewUserCard: React.FC = () => {
const router = useRouter()
const { showErrorNotification } = useUiNotifications()
const { value, error, loading } = useAsync(getPendingUserInfo, [])
const [username, setUsername] = useState('')
const [displayName, setDisplayName] = useState('')
const [pictureChoice, setPictureChoice] = useState(ProfilePictureChoice.FALLBACK)
const [isUsernameSubmittable, setIsUsernameSubmittable] = useState(false)
const [isDisplayNameSubmittable, setIsDisplayNameSubmittable] = useState(false)
const isSubmittable = useMemo(() => {
return isUsernameSubmittable && isDisplayNameSubmittable
}, [isUsernameSubmittable, isDisplayNameSubmittable])
const onChangeUsername = useOnInputChange(setUsername)
const onChangeDisplayName = useOnInputChange(setDisplayName)
const submitUserdata = useCallback(() => {
confirmPendingUser({
username,
displayName,
profilePicture: pictureChoice === ProfilePictureChoice.PROVIDER ? value?.photoUrl : undefined
})
.then(() => fetchAndSetUser())
.then(() => {
router.push('/')
})
.catch(showErrorNotification('login.welcome.error'))
}, [username, displayName, pictureChoice, router, showErrorNotification, value?.photoUrl])
const cancelUserCreation = useCallback(() => {
cancelPendingUser()
.catch(showErrorNotification('login.welcome.cancelError'))
.finally(() => {
router.push('/login')
})
}, [router, showErrorNotification])
useEffect(() => {
if (error) {
showErrorNotification('login.welcome.error')(error)
router.push('/login')
}
}, [error, router, showErrorNotification])
useEffect(() => {
if (!value) {
return
}
setUsername(value.username ?? '')
setDisplayName(value.displayName ?? '')
if (value.photoUrl) {
setPictureChoice(ProfilePictureChoice.PROVIDER)
}
}, [value])
if (!value && !loading) {
return null
}
return (
<Card>
<Card.Body>
{loading && <p>Loading...</p>}
<Card.Title>
{displayName !== '' ? (
<Trans i18nKey={'login.welcome.title'} values={{ name: displayName }} />
) : (
<Trans i18nKey={'login.welcome.titleFallback'} />
)}
</Card.Title>
<Trans i18nKey={'login.welcome.description'} />
<hr />
<Form onSubmit={submitUserdata} className={'d-flex flex-column gap-3'}>
<DisplayNameField
onChange={onChangeDisplayName}
value={displayName}
onValidityChange={setIsDisplayNameSubmittable}
/>
<UsernameLabelField
onChange={onChangeUsername}
value={username}
onValidityChange={setIsUsernameSubmittable}
/>
<ProfilePictureSelectField
onChange={setPictureChoice}
value={pictureChoice}
pictureUrl={value?.photoUrl}
username={username}
/>
<div className={'d-flex gap-3'}>
<Button variant={'secondary'} type={'button'} className={'w-50'} onClick={cancelUserCreation}>
<Trans i18nKey={'common.cancel'} />
</Button>
<Button variant={'success'} type={'submit'} className={'w-50'} disabled={!isSubmittable}>
<Trans i18nKey={'common.continue'} />
</Button>
</div>
</Form>
</Card.Body>
</Card>
)
}