Skip to content

Commit

Permalink
feat: migrate to Keycloak
Browse files Browse the repository at this point in the history
  • Loading branch information
Reddine authored and AOelen committed Nov 20, 2024
1 parent 687d881 commit b2045a8
Show file tree
Hide file tree
Showing 40 changed files with 349 additions and 1,311 deletions.
8 changes: 4 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:18-alpine AS base
FROM node:20-alpine AS base

# Install dependencies only when needed
FROM base AS deps
Expand All @@ -22,17 +22,17 @@ COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED 1
ENV NEXT_TELEMETRY_DISABLED=1

RUN npm run build

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production
ENV NODE_ENV=production
# Uncomment the following line in case you want to disable telemetry during runtime.
ENV NEXT_TELEMETRY_DISABLED 1
ENV NEXT_TELEMETRY_DISABLED=1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
Expand Down
71 changes: 35 additions & 36 deletions README.md

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion default.env
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ NEXT_PUBLIC_GROBID_URL=https://orkg.org/grobid/
NEXT_PUBLIC_CMS_URL=https://orkg.org/strapi/api/
NEXT_PUBLIC_OPEN_CITATIONS_URL=https://opencitations.net/

# Keycloak
NEXT_PUBLIC_KEYCLOAK_URL=http://localhost:8888/
NEXT_PUBLIC_KEYCLOAK_REALM=orkg
NEXT_PUBLIC_KEYCLOAK_CLIENT_ID=orkg-frontend

NEXT_PUBLIC_ORCID_API_URL=https://pub.orcid.org/v2.0/
NEXT_PUBLIC_SEMANTIC_SCHOLAR_URL=https://api.semanticscholar.org/
NEXT_PUBLIC_ALTMETRIC_URL=https://api.altmetric.com/v1/
NEXT_PUBLIC_AUTHENTICATION_CLIENT_ID=orkg-client
NEXT_PUBLIC_CHATWOOT_WEBSITE_TOKEN=
NEXT_PUBLIC_GEONAMES_API_URL=https://secure.geonames.org/
NEXT_PUBLIC_GEONAMES_API_USERNAME=reddine
Expand Down
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"intro.js-react": "^0.7.1",
"isomorphic-dompurify": "^1.10.0",
"jspdf": "^2.5.1",
"keycloak-js": "^26.0.5",
"leaflet": "^1.9.4",
"linkify-react": "^4.1.3",
"linkifyjs": "^4.1.3",
Expand Down
8 changes: 8 additions & 0 deletions public/silent-check-sso.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!DOCTYPE html>
<html>
<body>
<script>
parent.postMessage(location.href, location.origin);
</script>
</body>
</html>
35 changes: 17 additions & 18 deletions src/app/addOrganization/[type]/page.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
'use client';

import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import useParams from 'components/useParams/useParams';
import { Container, Button, Form, FormGroup, Input, Label, InputGroup } from 'reactstrap';
import { toast } from 'react-toastify';
import { createOrganization } from 'services/backend/organizations';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUser } from '@fortawesome/free-solid-svg-icons';
import { openAuthDialog } from 'slices/authSlice';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import ButtonWithLoading from 'components/ButtonWithLoading/ButtonWithLoading';
import TitleBar from 'components/TitleBar/TitleBar';
import useParams from 'components/useParams/useParams';
import Tooltip from 'components/Utils/Tooltip';
import { MAX_LENGTH_INPUT } from 'constants/misc';
import { ORGANIZATIONS_TYPES } from 'constants/organizationsTypes';
import REGEX from 'constants/regex';
import ROUTES from 'constants/routes';
import { reverse } from 'named-urls';
import { getPublicUrl } from 'utils';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { toast } from 'react-toastify';
import { Button, Container, Form, FormGroup, Input, InputGroup, Label } from 'reactstrap';
import { createOrganization } from 'services/backend/organizations';
import { login } from 'services/keycloak';
import slugify from 'slugify';
import ROUTES from 'constants/routes';
import Tooltip from 'components/Utils/Tooltip';
import TitleBar from 'components/TitleBar/TitleBar';
import { ORGANIZATIONS_TYPES } from 'constants/organizationsTypes';
import { useSelector, useDispatch } from 'react-redux';
import ButtonWithLoading from 'components/ButtonWithLoading/ButtonWithLoading';
import { MAX_LENGTH_INPUT } from 'constants/misc';
import { getPublicUrl } from 'utils';

const AddOrganization = () => {
const params = useParams();
Expand All @@ -31,7 +31,6 @@ const AddOrganization = () => {
const organizationType = ORGANIZATIONS_TYPES.find((t) => t.label === params.type);
const publicOrganizationRoute = `${getPublicUrl()}${reverse(ROUTES.ORGANIZATION, { type: organizationType?.label, id: ' ' })}`;
const user = useSelector((state) => state.auth.user);
const dispatch = useDispatch();
const router = useRouter();

useEffect(() => {
Expand Down Expand Up @@ -173,7 +172,7 @@ const AddOrganization = () => {
</Form>
)}
{(!user || !user.isCurationAllowed) && (
<Button color="link" className="p-0 mb-2 mt-2 clearfix" onClick={() => dispatch(openAuthDialog({ action: 'signin' }))}>
<Button color="link" className="p-0 mb-2 mt-2 clearfix" onClick={() => login()}>
<FontAwesomeIcon className="me-1" icon={faUser} /> Sign in to create organization
</Button>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
'use client';

import Link from 'next/link';
import TitleBar from 'components/TitleBar/TitleBar';
import useParams from 'components/useParams/useParams';
import DraftComparisons from 'components/UserSettings/DraftComparisons/DraftComparisons';
import DraftLists from 'components/UserSettings/DraftLists/DraftLists';
import DraftReviews from 'components/UserSettings/DraftReviews/DraftReviews';
import ROUTES from 'constants/routes';
import { reverse } from 'named-urls';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import useParams from 'components/useParams/useParams';
import { Container, Row } from 'reactstrap';
import styled from 'styled-components';
import GeneralSettings from 'components/UserSettings/GeneralSettings';
import Password from 'components/UserSettings/Password';
import requireAuthentication from 'requireAuthentication';
import styled from 'styled-components';

const StyledSettingsMenu = styled.div`
padding: 0;
Expand Down Expand Up @@ -42,42 +40,27 @@ const StyledSettingsMenu = styled.div`
`;

const TABS = {
GENERAL: 'general',
PASSWORD: 'password',
DRAFT_COMPARISONS: 'draft-comparisons',
DRAFT_REVIEWS: 'draft-reviews',
DRAFT_LISTS: 'draft-lists',
};

const UserSettings = () => {
const [activeTab, setActiveTab] = useState('general');
const [activeTab, setActiveTab] = useState('draft-comparisons');
const { tab } = useParams();

useEffect(() => {
setActiveTab(tab || 'general');
setActiveTab(tab || 'draft-comparisons');
}, [tab]);

return (
<>
<TitleBar>My account</TitleBar>
<TitleBar>My drafts</TitleBar>
<Container className="p-0">
<Row>
<div className="col-md-3 mb-sm-2 justify-content-center">
<Container className="box rounded p-3">
<StyledSettingsMenu>
<Link
href={reverse(ROUTES.USER_SETTINGS, { tab: TABS.GENERAL })}
className={activeTab === TABS.GENERAL ? 'active' : ''}
>
General settings
</Link>
<Link
href={reverse(ROUTES.USER_SETTINGS, { tab: TABS.PASSWORD })}
className={activeTab === TABS.PASSWORD ? 'active' : ''}
>
Password
</Link>
<hr />
<Link
href={reverse(ROUTES.USER_SETTINGS, { tab: TABS.DRAFT_COMPARISONS })}
className={activeTab === TABS.DRAFT_COMPARISONS ? 'active' : ''}
Expand All @@ -100,17 +83,6 @@ const UserSettings = () => {
</Container>
</div>
<div className="col-md-9 justify-content-center">
{activeTab === TABS.GENERAL && (
<div className="box rounded pt-4 pb-3 px-4">
<GeneralSettings />
</div>
)}
{activeTab === TABS.PASSWORD && (
<div className="box rounded pt-4 pb-3 px-4">
<Password />
</div>
)}

{activeTab === TABS.DRAFT_COMPARISONS && <DraftComparisons />}

{activeTab === TABS.DRAFT_REVIEWS && <DraftReviews />}
Expand Down
3 changes: 2 additions & 1 deletion src/app/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const RootLayout = ({ children }) => (
https://cdnjs.cloudflare.com
;
frame-src 'self'
localhost:*
https://orkg.org
https://*.orkg.org
https://av.tib.eu
Expand All @@ -66,7 +67,7 @@ const RootLayout = ({ children }) => (
https://www.youtube.com
https://time.graphics
https://app.chatwoot.com
https://support.tib.eu
https://support.tib.eu
;
connect-src 'self'
blob:
Expand Down
4 changes: 2 additions & 2 deletions src/app/organizations/[id]/addEvent/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { toast } from 'react-toastify';
import { createConference } from 'services/backend/conferences-series';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUser } from '@fortawesome/free-solid-svg-icons';
import { openAuthDialog } from 'slices/authSlice';
import REGEX from 'constants/regex';
import { reverse } from 'named-urls';
import { getPublicUrl } from 'utils';
Expand All @@ -20,6 +19,7 @@ import { useSelector, useDispatch } from 'react-redux';
import { CONFERENCE_REVIEW_TYPE } from 'constants/organizationsTypes';
import ButtonWithLoading from 'components/ButtonWithLoading/ButtonWithLoading';
import { MAX_LENGTH_INPUT } from 'constants/misc';
import { login } from 'services/keycloak';

const AddConference = () => {
const params = useParams();
Expand Down Expand Up @@ -191,7 +191,7 @@ const AddConference = () => {
</Form>
)}
{(!user || !user.isCurationAllowed) && (
<Button color="link" className="p-0 mb-2 mt-2 clearfix" onClick={() => dispatch(openAuthDialog({ action: 'signin' }))}>
<Button color="link" className="p-0 mb-2 mt-2 clearfix" onClick={() => login()}>
<FontAwesomeIcon className="me-1" icon={faUser} /> Sign in to create conference event
</Button>
)}
Expand Down
9 changes: 4 additions & 5 deletions src/app/organizations/[id]/addObservatory/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import { faUser } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import Autocomplete from 'components/Autocomplete/Autocomplete';
import { MAX_DESCRIPTION_LENGTH } from 'components/Observatory/EditObservatory';
import Tooltip from 'components/Utils/Tooltip';
import { CLASSES, ENTITIES } from 'constants/graphSettings';
import REGEX from 'constants/regex';
Expand All @@ -19,10 +18,10 @@ import { toast } from 'react-toastify';
import { Button, Container, FormGroup, Input, InputGroup, Label } from 'reactstrap';
import { createObservatory } from 'services/backend/observatories';
import { getOrganization } from 'services/backend/organizations';
import { openAuthDialog } from 'slices/authSlice';
import slugify from 'slugify';
import { getPublicUrl } from 'utils';
import { MAX_LENGTH_INPUT } from 'constants/misc';
import { login } from 'services/keycloak';

const AddObservatory = () => {
const params = useParams();
Expand Down Expand Up @@ -184,10 +183,10 @@ const AddObservatory = () => {
value={description}
id="ObservatoryDescription"
disabled={loading}
maxLength={MAX_DESCRIPTION_LENGTH}
maxLength={MAX_LENGTH_INPUT}
/>
<div className="text-muted text-end">
{description?.length}/{MAX_DESCRIPTION_LENGTH}
{description?.length}/{MAX_LENGTH_INPUT}
</div>
</FormGroup>
<Button color="primary" onClick={createNewObservatory} className="mt-4 mb-2" isLoading={loading}>
Expand All @@ -196,7 +195,7 @@ const AddObservatory = () => {
</div>
)}
{(!user || !user.isCurationAllowed) && (
<Button color="link" className="p-0 mb-2 mt-2 clearfix" onClick={() => dispatch(openAuthDialog({ action: 'signin' }))}>
<Button color="link" className="p-0 mb-2 mt-2 clearfix" onClick={() => login()}>
<FontAwesomeIcon className="me-1" icon={faUser} /> Sign in to create an observatory
</Button>
)}
Expand Down
1 change: 1 addition & 0 deletions src/app/u/[userId]/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ const UserProfile = (props) => {
document.title = `${userData.display_name} - ORKG`;
})
.catch((e) => {
console.error(e);
document.title = 'User profile - ORKG';
setNotFound(true);
});
Expand Down
27 changes: 27 additions & 0 deletions src/components/Authentication/AuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { initKeycloak, keycloak } from 'services/keycloak';
import { firstLoad, updateAuth } from 'slices/authSlice';
import { AppDispatch } from 'slices/types';

const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const dispatch = useDispatch<AppDispatch>();

useEffect(() => {
if (typeof window !== 'undefined') {
initKeycloak()
.then((authenticated: boolean) => {
dispatch(updateAuth({ authenticated }));
if (keycloak && keycloak.token) {
dispatch(firstLoad());
}
dispatch(updateAuth({ initialized: true }));
})
.catch((err: Error) => console.error('Failed to initialize Keycloak', err));
}
}, [dispatch]);

return children;
};

export default AuthProvider;
Loading

0 comments on commit b2045a8

Please sign in to comment.