Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • andretol/greg
1 result
Show changes
Commits on Source (9)
Showing
with 148 additions and 17 deletions
......@@ -36,6 +36,7 @@
"libphonenumber-js": "^1.9.35",
"lodash": "^4.17.21",
"react": "^17.0.2",
"react-cookie": "^4.1.1",
"react-dom": "^17.0.2",
"react-helmet": "^6.1.0",
"react-hook-form": "^7.17.5",
......@@ -4222,6 +4223,11 @@
"@babel/types": "^7.3.0"
}
},
"node_modules/@types/cookie": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz",
"integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow=="
},
"node_modules/@types/eslint": {
"version": "7.28.2",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.2.tgz",
......@@ -4258,6 +4264,15 @@
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz",
"integrity": "sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ=="
},
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"dependencies": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"node_modules/@types/html-minifier-terser": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz",
......@@ -18211,6 +18226,19 @@
"node": ">=10"
}
},
"node_modules/react-cookie": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.1.1.tgz",
"integrity": "sha512-ffn7Y7G4bXiFbnE+dKhHhbP+b8I34mH9jqnm8Llhj89zF4nPxPutxHT1suUqMeCEhLDBI7InYwf1tpaSoK5w8A==",
"dependencies": {
"@types/hoist-non-react-statics": "^3.0.1",
"hoist-non-react-statics": "^3.0.0",
"universal-cookie": "^4.0.0"
},
"peerDependencies": {
"react": ">= 16.3.0"
}
},
"node_modules/react-dev-utils": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz",
......@@ -21857,6 +21885,15 @@
"node": ">=4"
}
},
"node_modules/universal-cookie": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz",
"integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==",
"dependencies": {
"@types/cookie": "^0.3.3",
"cookie": "^0.4.0"
}
},
"node_modules/universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
......@@ -26973,6 +27010,11 @@
"@babel/types": "^7.3.0"
}
},
"@types/cookie": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz",
"integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow=="
},
"@types/eslint": {
"version": "7.28.2",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.2.tgz",
......@@ -27009,6 +27051,15 @@
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz",
"integrity": "sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ=="
},
"@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"requires": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"@types/html-minifier-terser": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz",
......@@ -37821,6 +37872,16 @@
"whatwg-fetch": "^3.4.1"
}
},
"react-cookie": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.1.1.tgz",
"integrity": "sha512-ffn7Y7G4bXiFbnE+dKhHhbP+b8I34mH9jqnm8Llhj89zF4nPxPutxHT1suUqMeCEhLDBI7InYwf1tpaSoK5w8A==",
"requires": {
"@types/hoist-non-react-statics": "^3.0.1",
"hoist-non-react-statics": "^3.0.0",
"universal-cookie": "^4.0.0"
}
},
"react-dev-utils": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz",
......@@ -40693,6 +40754,15 @@
"crypto-random-string": "^1.0.0"
}
},
"universal-cookie": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz",
"integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==",
"requires": {
"@types/cookie": "^0.3.3",
"cookie": "^0.4.0"
}
},
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
......@@ -31,6 +31,7 @@
"libphonenumber-js": "^1.9.35",
"lodash": "^4.17.21",
"react": "^17.0.2",
"react-cookie": "^4.1.1",
"react-dom": "^17.0.2",
"react-helmet": "^6.1.0",
"react-hook-form": "^7.17.5",
......
......@@ -12,6 +12,7 @@ function noop() {}
export const UserContext = createContext<IUserContext>({
user: {
auth: false,
auth_type: '',
fetched: false,
first_name: '',
last_name: '',
......
......@@ -64,6 +64,7 @@ export type FetchedRole = {
export interface User {
auth: boolean
auth_type: string
fetched: boolean
person_id: string
sponsor_id: string
......
......@@ -10,6 +10,7 @@ function UserProvider(props: UserProviderProps) {
const { children } = props
const [user, setUser] = useState({
auth: false,
auth_type: '',
fetched: false,
first_name: '',
last_name: '',
......@@ -31,6 +32,7 @@ function UserProvider(props: UserProviderProps) {
if (response.ok) {
setUser({
auth: true,
auth_type: data.auth_type,
fetched: true,
first_name: data.first_name,
last_name: data.last_name,
......@@ -46,6 +48,7 @@ function UserProvider(props: UserProviderProps) {
} else {
setUser({
auth: false,
auth_type: '',
fetched: true,
first_name: '',
last_name: '',
......@@ -62,6 +65,7 @@ function UserProvider(props: UserProviderProps) {
} catch (error) {
setUser({
auth: false,
auth_type: '',
fetched: true,
first_name: '',
last_name: '',
......@@ -92,6 +96,7 @@ function UserProvider(props: UserProviderProps) {
const clearUserInfo = () => {
setUser({
auth: false,
auth_type: '',
fetched: false,
first_name: '',
last_name: '',
......
......@@ -48,6 +48,9 @@ const Header = () => {
const { user } = useUserContext()
const { t } = useTranslation('common')
const logoutLink =
user.auth_type === 'oidc' ? '/oidc/logout' : '/invite/logout'
return (
<StyledHeader>
<MainContainer>
......@@ -61,7 +64,7 @@ const Header = () => {
sx={{
color: 'white',
}}
href="/oidc/logout"
href={logoutLink}
>
{t('button.logout')}
<LogoutIcon
......
......@@ -13,6 +13,7 @@ import Register from 'routes/sponsor/register'
import FrontPage from 'routes/frontpage'
import Invite from 'routes/invite'
import InviteLink from 'routes/invitelink'
import LogoutInviteSession from 'routes/invitelink/logout'
import Footer from 'routes/components/footer'
import Header from 'routes/components/header'
import NotFound from 'routes/components/notFound'
......@@ -77,6 +78,7 @@ export default function App() {
<Register />
</ProtectedRoute>
<Route path="/invitelink/" component={InviteLink} />
<Route path="/invite/logout" component={LogoutInviteSession} />
<Route path="/invite/" component={Invite} />
<Route path="/guestregister" component={GuestRegister} />
<Route>
......
import { useEffect, useState } from 'react'
import { Redirect } from 'react-router-dom'
import { useCookies } from 'react-cookie'
import { Box, CircularProgress } from '@mui/material'
import { useUserContext } from 'contexts'
export default function LogoutInviteSession() {
// Fetch backend endpoint to preserve invite_id in backend session then redirect
const [, , removeCookie] = useCookies(['sessionid'])
const [loggedOut, setLoggedOut] = useState(false)
const { fetchUserInfo } = useUserContext()
useEffect(() => {
fetch('/api/ui/v1/invitecheck/', { method: 'DELETE' })
.then(() => removeCookie('sessionid'))
.then(() => fetchUserInfo())
.then(() => setLoggedOut(true))
}, [])
if (loggedOut) {
return <Redirect to="/" />
}
return (
<Box sx={{ margin: 'auto' }}>
<CircularProgress />
</Box>
)
}
......@@ -2,8 +2,8 @@ export type RegisterFormData = {
first_name?: string
last_name?: string
role_type?: string
role_start?: Date
role_end?: Date
role_start: Date
role_end: Date
contact_person_unit?: string
comment?: string
ou_id?: number
......
......@@ -46,6 +46,7 @@ const StepPersonForm = forwardRef(
)
const roleTypes = useRoleTypes()
const { displayContactAtUnit, displayComment } = useContext(FeatureContext)
const today: Date = new Date()
const roleTypeSort = () => (a: RoleTypeData, b: RoleTypeData) => {
if (i18n.language === 'en') {
......@@ -204,6 +205,7 @@ const StepPersonForm = forwardRef(
name="role_start"
control={control}
rules={{ validate: validateStartDateBeforeEndDate }}
defaultValue={today}
render={({ field }) => (
<DatePicker
mask="____-__-__"
......@@ -225,6 +227,7 @@ const StepPersonForm = forwardRef(
<Controller
name="role_end"
control={control}
defaultValue={today}
render={({ field }) => (
<DatePicker
mask="____-__-__"
......
......@@ -33,8 +33,9 @@ export default function StepRegistration() {
first_name: undefined,
last_name: undefined,
role_type: undefined,
role_start: undefined,
role_end: undefined,
// Having role_start and role_end to be nullable caused problems when specifying a default value, so instead having them as non-null and use today as the default date here
role_start: new Date(),
role_end: new Date(),
comment: undefined,
ou_id: undefined,
email: undefined,
......
import logging
from enum import Enum
from typing import Optional, List
import structlog
from django.core import exceptions
from django.db import transaction
......@@ -24,7 +24,7 @@ from gregui.api.serializers.invitation import InviteGuestSerializer
from gregui.mailutils import send_invite_mail
from gregui.models import GregUserProfile
logger = logging.getLogger(__name__)
logger = structlog.getLogger(__name__)
class InvitationView(CreateAPIView, DestroyAPIView):
......@@ -91,9 +91,7 @@ class InvitationView(CreateAPIView, DestroyAPIView):
# not be verified, but including that check just in case here.
# If this is the case then there is an unexpected situation, the cancel option
# should only apply to guests that have not completed the registration
logger.warning(
f"Attempting to delete invitation for already registered guest with person ID {person_id}"
)
logger.warning("try_delete_registered_invite", person_id=person_id)
return Response(status=status.HTTP_400_BAD_REQUEST)
# Delete the person. The delete will cascade and all roles, identities and invitations will be removed.
......@@ -109,7 +107,7 @@ class CheckInvitationView(APIView):
permission_classes = [AllowAny]
throttle_classes = [AnonRateThrottle]
def post(self, request, *args, **kwargs):
def post(self, request, *args, **kwargs) -> Response:
"""
Endpoint for verifying and setting invite_id in session.
......@@ -132,6 +130,16 @@ class CheckInvitationView(APIView):
request.session["invite_id"] = invite_id
return Response(status=status.HTTP_200_OK)
def delete(self, request, *args, **kwargs) -> Response:
if "invite_id" in request.session:
logger.info(
"invitation_session_deleted", invite_id=request.session["invite_id"]
)
del request.session["invite_id"]
return Response(status.HTTP_200_OK)
return Response(status=status.HTTP_403_FORBIDDEN)
class SessionType(Enum):
INVITE = "invite"
......@@ -322,9 +330,7 @@ class ResendInvitationView(UpdateModelMixin, APIView):
if non_expired_links.count() > 0:
if non_expired_links.count() > 1:
# Do not expect this to happen
logger.warning(
f"Person with ID {person_id} has multiple invitation links"
)
logger.warning("found_multiple_invitation_links", person_id=person_id)
# Just resend all and do not create a new one
for link in non_expired_links:
......@@ -339,9 +345,7 @@ class ResendInvitationView(UpdateModelMixin, APIView):
# Do not expected that a person has several open invitations, it could happen
# if he has been invited by different sponsor at the same time, but that
# could be an indication that there has been a mixup
logger.warning(
f"Multiple invitations exist for person with ID {person_id}"
)
logger.warning("found_multiple_invitations", person_id=person_id)
for invitation in invitations_to_resend:
invitation_link = InvitationLink.objects.create(
......
......@@ -34,6 +34,10 @@ class UserInfoView(APIView):
user = request.user
invite_id = request.session.get("invite_id")
auth_type = "invite"
if "oidc_states" in request.session.keys():
auth_type = "oidc"
person = None
sponsor = None
content = {
......@@ -41,6 +45,7 @@ class UserInfoView(APIView):
"sponsor_id": None,
"person_id": None,
"roles": [],
"auth_type": auth_type,
}
# Fetch sponsor and/or person object from profile of authenticated user
......
......@@ -19,6 +19,7 @@ def test_userinfo_invited_get(client, invitation_link):
response = client.get(reverse("api-userinfo"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == {
"auth_type": "invite",
"feide_id": None,
"sponsor_id": None,
"person_id": 1,
......@@ -59,6 +60,7 @@ def test_userinfo_sponsor_get(client, log_in, user_sponsor):
response = client.get(reverse("api-userinfo"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == {
"auth_type": "oidc",
"feide_id": "",
"person_id": None,
"roles": [],
......@@ -73,6 +75,7 @@ def test_userinfo_guest_get(client, log_in, user_person):
response = client.get(reverse("api-userinfo"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == {
"auth_type": "oidc",
"feide_id": "",
"sponsor_id": None,
"person_id": 1,
......
......@@ -471,6 +471,7 @@ def log_in(client) -> Callable[[UserModel], APIClient]:
# It seems like the session was not updated automatically this way
session = client.session
session["oidc_id_token_payload"] = {"iat": time.time()}
session["oidc_states"] = {}
session.save()
return client
......