diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..aa639fc1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# IDE +.idea/ + +# Gradle +.gradle/ +build/ + +# Compiled +bin/ diff --git a/rafts/frontend/messages/en.json b/rafts/frontend/messages/en.json index 380f6919..7788cce3 100644 --- a/rafts/frontend/messages/en.json +++ b/rafts/frontend/messages/en.json @@ -257,7 +257,8 @@ "password": "Password", "email": "User Email", "signing_in": "Signing in", - "forgot_password": "Forgot password?" + "forgot_password": "Forgot password?", + "create_account": "Create an account" }, "profile": { "name": "Name", diff --git a/rafts/frontend/messages/fr.json b/rafts/frontend/messages/fr.json index 18db6700..c65c879a 100644 --- a/rafts/frontend/messages/fr.json +++ b/rafts/frontend/messages/fr.json @@ -266,7 +266,8 @@ "password": "Mot de passe", "email": "Courriel de l'utilisateur", "signing_in": "Connexion en cours...", - "forgot_password": "Mot de passe oublié?" + "forgot_password": "Mot de passe oublié?", + "create_account": "Créer un compte" }, "profile": { "name": "Nom", diff --git a/rafts/frontend/src/actions/assignReviewer.ts b/rafts/frontend/src/actions/assignReviewer.ts index 825f2d83..680e87e6 100644 --- a/rafts/frontend/src/actions/assignReviewer.ts +++ b/rafts/frontend/src/actions/assignReviewer.ts @@ -212,7 +212,7 @@ export const claimForReview = async ( return { [SUCCESS]: true, - data: `RAFT claimed for review by "${reviewerName}"`, + data: `RAFTS claimed for review by "${reviewerName}"`, } } diff --git a/rafts/frontend/src/actions/createDOIForDraft.ts b/rafts/frontend/src/actions/createDOIForDraft.ts index e95bf4f6..98b21713 100644 --- a/rafts/frontend/src/actions/createDOIForDraft.ts +++ b/rafts/frontend/src/actions/createDOIForDraft.ts @@ -121,13 +121,13 @@ function buildMinimalDataCiteMetadata(title: string, creatorName: string): Recor ], }, titles: { - $: [{ title: { $: title || 'Untitled RAFT Draft' } }], + $: [{ title: { $: title || 'Untitled RAFTS Draft' } }], }, publisher: { $: 'NRC CADC' }, publicationYear: { $: publicationYear }, resourceType: { '@resourceTypeGeneral': 'Dataset', - $: 'RAFT Announcement', + $: 'RAFTS Announcement', }, }, } @@ -144,7 +144,7 @@ function buildMinimalDataCiteMetadata(title: string, creatorName: string): Recor * @returns DOI identifier and URL on success */ export async function createDOIForDraft( - title: string = 'Untitled RAFT Draft', + title: string = 'Untitled RAFTS Draft', ): Promise> { const session = await auth() const accessToken = session?.accessToken diff --git a/rafts/frontend/src/actions/deleteRaft.ts b/rafts/frontend/src/actions/deleteRaft.ts index c850e73d..67d70a0c 100644 --- a/rafts/frontend/src/actions/deleteRaft.ts +++ b/rafts/frontend/src/actions/deleteRaft.ts @@ -128,7 +128,7 @@ export const deleteRaft = async (doiSuffix: string): Promise { if (mockRaft) { return { success: true, data: mockRaft } } else { - return { success: false, error: 'RAFT not found' } + return { success: false, error: 'RAFTS not found' } } } diff --git a/rafts/frontend/src/actions/getRaftById.ts b/rafts/frontend/src/actions/getRaftById.ts index 60d27e89..ce1828cc 100644 --- a/rafts/frontend/src/actions/getRaftById.ts +++ b/rafts/frontend/src/actions/getRaftById.ts @@ -81,7 +81,7 @@ export const getRaftById = async (id: string) => { if (mockRaft) { return { success: true, data: mockRaft } } else { - return { success: false, error: 'RAFT not found' } + return { success: false, error: 'RAFTS not found' } } } diff --git a/rafts/frontend/src/actions/getRaftReview.ts b/rafts/frontend/src/actions/getRaftReview.ts index e951abcb..aabb8c00 100644 --- a/rafts/frontend/src/actions/getRaftReview.ts +++ b/rafts/frontend/src/actions/getRaftReview.ts @@ -79,7 +79,7 @@ export const getRaftReview = async (raftId: string) => { const mockRaft = getMockRaftById(raftId) if (!mockRaft) { - return { success: false, error: 'RAFT not found' } + return { success: false, error: 'RAFTS not found' } } // Create a mock review based on the RAFT status diff --git a/rafts/frontend/src/actions/publishRaftDoi.ts b/rafts/frontend/src/actions/publishRaftDoi.ts index 8b8a57cb..8437f213 100644 --- a/rafts/frontend/src/actions/publishRaftDoi.ts +++ b/rafts/frontend/src/actions/publishRaftDoi.ts @@ -144,7 +144,7 @@ export const publishRAFTDOI = async ( return { [SUCCESS]: true, - [MESSAGE]: 'RAFT status changed to Published.', + [MESSAGE]: 'RAFTS status changed to Published.', } } catch (error) { console.error('[publishRAFTDOI] Error:', error) diff --git a/rafts/frontend/src/actions/submitDOI.ts b/rafts/frontend/src/actions/submitDOI.ts index 68dcb858..027609cf 100644 --- a/rafts/frontend/src/actions/submitDOI.ts +++ b/rafts/frontend/src/actions/submitDOI.ts @@ -146,7 +146,7 @@ export const submitDOI = async (formData: TRaftContext): Promise { const session = await auth() const defaultReturnUrl = '/' - if (session) { + if (session && !isStaleSession(session)) { redirect((resolvedSearchParams.returnUrl as string) || defaultReturnUrl) } diff --git a/rafts/frontend/src/app/[locale]/page.tsx b/rafts/frontend/src/app/[locale]/page.tsx index 759f3a76..5db60ea3 100644 --- a/rafts/frontend/src/app/[locale]/page.tsx +++ b/rafts/frontend/src/app/[locale]/page.tsx @@ -67,11 +67,12 @@ import LandingChoice from '@/components/LandingPage/LandingChoice' import { auth } from '@/auth/cadc-auth/credentials' +import { isStaleSession } from '@/auth/cadc-auth/isStaleSession' const HomePage = async () => { const session = await auth() - return + return } export default HomePage diff --git a/rafts/frontend/src/app/[locale]/public-view/doi/page.tsx b/rafts/frontend/src/app/[locale]/public-view/doi/page.tsx index 990b47ec..c09d634b 100644 --- a/rafts/frontend/src/app/[locale]/public-view/doi/page.tsx +++ b/rafts/frontend/src/app/[locale]/public-view/doi/page.tsx @@ -85,7 +85,7 @@ export default function View() { setDoiData(data) } else { console.error('Error fetching DOI data:', error) - setError('Failed to load RAFT data. Please try again later.') + setError('Failed to load RAFTS data. Please try again later.') } setIsLoading(false) } @@ -101,7 +101,7 @@ export default function View() { // Data fetched successfully } else { console.error('Error fetching DOI data:', error) - setError('Failed to load RAFT data. Please try again later.') + setError('Failed to load RAFTS data. Please try again later.') } setIsLoading(false) } @@ -112,12 +112,12 @@ export default function View() { return (
-

Your submissions (RAFTs)

+

Your submissions (RAFTSs)

{isLoading ? (
- Loading RAFT data... + Loading RAFTS data...
) : error ? (
{error}
@@ -126,7 +126,7 @@ export default function View() { )}
-
CADC RAFT Publication System
+
CADC RAFTS Publication System
) diff --git a/rafts/frontend/src/app/[locale]/public-view/rafts/[id]/page.tsx b/rafts/frontend/src/app/[locale]/public-view/rafts/[id]/page.tsx index 29ba89fe..1e8879ad 100644 --- a/rafts/frontend/src/app/[locale]/public-view/rafts/[id]/page.tsx +++ b/rafts/frontend/src/app/[locale]/public-view/rafts/[id]/page.tsx @@ -80,12 +80,12 @@ export async function generateMetadata(props: { if (!success || !data) { return { - title: 'RAFT Not Found', + title: 'RAFTS Not Found', } } return { - title: `RAFT - ${data?.generalInfo?.title || 'View RAFT'}`, + title: `RAFTS - ${data?.generalInfo?.title || 'View RAFTS'}`, description: data.observationInfo?.abstract?.substring(0, 160) || 'Research Announcement For The Solar System', diff --git a/rafts/frontend/src/app/[locale]/public-view/rafts/page.tsx b/rafts/frontend/src/app/[locale]/public-view/rafts/page.tsx index bc189cf6..0b7ac4b8 100644 --- a/rafts/frontend/src/app/[locale]/public-view/rafts/page.tsx +++ b/rafts/frontend/src/app/[locale]/public-view/rafts/page.tsx @@ -85,7 +85,7 @@ export default function View() { setRaftData(data.data) } else { console.error('Error fetching DOI data:', error) - setError('Failed to load RAFT data. Please try again later.') + setError('Failed to load RAFTS data. Please try again later.') } setIsLoading(false) } @@ -96,12 +96,12 @@ export default function View() { return (
-

All Published (RAFTs)

+

All Published (RAFTSs)

{isLoading ? (
- Loading RAFTs... + Loading RAFTSs...
) : error ? (
{error}
@@ -110,7 +110,7 @@ export default function View() { )}
-
CADC RAFT Publication System
+
CADC RAFTS Publication System
) diff --git a/rafts/frontend/src/app/[locale]/review/rafts/[id]/page.tsx b/rafts/frontend/src/app/[locale]/review/rafts/[id]/page.tsx index c3dde0bf..707f4021 100644 --- a/rafts/frontend/src/app/[locale]/review/rafts/[id]/page.tsx +++ b/rafts/frontend/src/app/[locale]/review/rafts/[id]/page.tsx @@ -83,10 +83,10 @@ export async function generateMetadata(props: { } return { - title: `Review RAFT - ${data?.generalInfo?.title || 'Review RAFT'}`, + title: `Review RAFTS - ${data?.generalInfo?.title || 'Review RAFTS'}`, description: data.observationInfo?.abstract?.substring(0, 160) || - 'Research Announcement For The Solar System', + 'Research Announcements For The Solar System', } } diff --git a/rafts/frontend/src/app/[locale]/review/rafts/page.tsx b/rafts/frontend/src/app/[locale]/review/rafts/page.tsx index 0f447586..baa0ad75 100644 --- a/rafts/frontend/src/app/[locale]/review/rafts/page.tsx +++ b/rafts/frontend/src/app/[locale]/review/rafts/page.tsx @@ -120,10 +120,10 @@ export default function ReviewRafts() {
- Review RAFT Submissions + Review RAFTS Submissions - Manage and review RAFT submissions based on their current status. + Manage and review RAFTS submissions based on their current status.
-
CADC RAFT Publication System
+
CADC RAFTS Publication System
) diff --git a/rafts/frontend/src/app/[locale]/view/rafts/[id]/page.tsx b/rafts/frontend/src/app/[locale]/view/rafts/[id]/page.tsx index 3b3278b8..9836f61f 100644 --- a/rafts/frontend/src/app/[locale]/view/rafts/[id]/page.tsx +++ b/rafts/frontend/src/app/[locale]/view/rafts/[id]/page.tsx @@ -78,12 +78,12 @@ export async function generateMetadata(props: { if (!success || !data) { return { - title: 'RAFT Not Found', + title: 'RAFTS Not Found', } } return { - title: `RAFT - ${data?.generalInfo?.title || 'View RAFT'}`, + title: `RAFTS - ${data?.generalInfo?.title || 'View RAFTS'}`, description: data.observationInfo?.abstract?.substring(0, 160) || 'Research Announcement For The Solar System', diff --git a/rafts/frontend/src/app/[locale]/view/rafts/page.tsx b/rafts/frontend/src/app/[locale]/view/rafts/page.tsx index 886847d5..c7cb8b71 100644 --- a/rafts/frontend/src/app/[locale]/view/rafts/page.tsx +++ b/rafts/frontend/src/app/[locale]/view/rafts/page.tsx @@ -91,7 +91,7 @@ export default function View() { await signOut() } console.error('Error fetching DOI data:', error) - setError('Failed to load RAFT data. Please try again later.') + setError('Failed to load RAFTS data. Please try again later.') } setIsLoading(false) }, []) @@ -109,7 +109,7 @@ export default function View() {
-

Your submissions (RAFTs)

+

Your submissions (RAFTSs)

) diff --git a/rafts/frontend/src/app/api/attachments/[doiId]/[filename]/route.ts b/rafts/frontend/src/app/api/attachments/[doiId]/[filename]/route.ts index b1ec4427..38c95f9e 100644 --- a/rafts/frontend/src/app/api/attachments/[doiId]/[filename]/route.ts +++ b/rafts/frontend/src/app/api/attachments/[doiId]/[filename]/route.ts @@ -75,6 +75,7 @@ import { NextRequest, NextResponse } from 'next/server' import { auth } from '@/auth/cadc-auth/credentials' import { downloadAttachment } from '@/services/attachmentService' +import { sanitizeFilename } from '@/types/attachments' export async function GET( request: NextRequest, @@ -121,7 +122,7 @@ export async function GET( return new NextResponse(arrayBuffer, { headers: { 'Content-Type': result.mimeType || 'application/octet-stream', - 'Content-Disposition': `inline; filename="${decodedFilename}"`, + 'Content-Disposition': `inline; filename="${sanitizeFilename(decodedFilename)}"`, 'Cache-Control': 'private, max-age=3600', // Cache for 1 hour }, }) diff --git a/rafts/frontend/src/app/api/set-cookie/route.ts b/rafts/frontend/src/app/api/set-cookie/route.ts deleted file mode 100644 index b67f3a4d..00000000 --- a/rafts/frontend/src/app/api/set-cookie/route.ts +++ /dev/null @@ -1,127 +0,0 @@ -/* - ************************************************************************ - ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* - ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** - * - * (c) 2026. (c) 2026. - * Government of Canada Gouvernement du Canada - * National Research Council Conseil national de recherches - * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 - * All rights reserved Tous droits réservés - * - * NRC disclaims any warranties, Le CNRC dénie toute garantie - * expressed, implied, or énoncée, implicite ou légale, - * statutory, of any kind with de quelque nature que ce - * respect to the software, soit, concernant le logiciel, - * including without limitation y compris sans restriction - * any warranty of merchantability toute garantie de valeur - * or fitness for a particular marchande ou de pertinence - * purpose. NRC shall not be pour un usage particulier. - * liable in any event for any Le CNRC ne pourra en aucun cas - * damages, whether direct or être tenu responsable de tout - * indirect, special or general, dommage, direct ou indirect, - * consequential or incidental, particulier ou général, - * arising from the use of the accessoire ou fortuit, résultant - * software. Neither the name de l'utilisation du logiciel. Ni - * of the National Research le nom du Conseil National de - * Council of Canada nor the Recherches du Canada ni les noms - * names of its contributors may de ses participants ne peuvent - * be used to endorse or promote être utilisés pour approuver ou - * products derived from this promouvoir les produits dérivés - * software without specific prior de ce logiciel sans autorisation - * written permission. préalable et particulière - * par écrit. - * - * This file is part of the Ce fichier fait partie du projet - * OpenCADC project. OpenCADC. - * - * OpenCADC is free software: OpenCADC est un logiciel libre ; - * you can redistribute it and/or vous pouvez le redistribuer ou le - * modify it under the terms of modifier suivant les termes de - * the GNU Affero General Public la "GNU Affero General Public - * License as published by the License" telle que publiée - * Free Software Foundation, par la Free Software Foundation - * either version 3 of the : soit la version 3 de cette - * License, or (at your option) licence, soit (à votre gré) - * any later version. toute version ultérieure. - * - * OpenCADC is distributed in the OpenCADC est distribué - * hope that it will be useful, dans l'espoir qu'il vous - * but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE - * without even the implied GARANTIE : sans même la garantie - * warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ - * or FITNESS FOR A PARTICULAR ni d'ADÉQUATION À UN OBJECTIF - * PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence - * General Public License for Générale Publique GNU Affero - * more details. pour plus de détails. - * - * You should have received Vous devriez avoir reçu une - * a copy of the GNU Affero copie de la Licence Générale - * General Public License along Publique GNU Affero avec - * with OpenCADC. If not, see OpenCADC ; si ce n'est - * . pas le cas, consultez : - * . - * - ************************************************************************ - */ - -// src/app/api/setup-cookies/route.ts -import { NextResponse } from 'next/server' -import { CADC_COOKIE_DOMAIN_URL, CANFAR_COOKIE_DOMAIN_URL } from '@/auth/cadc-auth/constants' - -export async function GET(req: Request) { - const { searchParams } = new URL(req.url) - const token = searchParams.get('token') - - if (!token) { - return NextResponse.json({ error: 'No token provided' }, { status: 400 }) - } - - // Forward the requests and capture responses - const canfarRes = await fetch(`${CANFAR_COOKIE_DOMAIN_URL}${token}`, { - credentials: 'include', - redirect: 'manual', - }) - const cadcRes = await fetch(`${CADC_COOKIE_DOMAIN_URL}${token}`, { - credentials: 'include', - redirect: 'manual', - }) - - // Extract cookie headers - const canfarCookies = canfarRes.headers.getSetCookie() - const cadcCookies = cadcRes.headers.getSetCookie() - - // Create response with combined cookies - const response = NextResponse.json({ success: true }) - - const extractCookieValue = (cookieHeader: string) => { - const matches = cookieHeader.match(/CADC_SSO="([^"]+)"/) - return matches ? matches[1] : null - } - - /*const extractCookieAttribute = (cookieHeader: string, attribute: string): string | undefined => { - const regex = new RegExp(`${attribute}=([^;]+)`, 'i') - const matches = cookieHeader.match(regex) - return matches ? matches[1] : undefined - }*/ - - // Forward all cookies to the client - for (const cookieHeader of [...canfarCookies, ...cadcCookies]) { - const cookieValue = extractCookieValue(cookieHeader) - if (cookieValue) { - // Create a new cookie with the same value but set the domain to your app's domain - - response.cookies.set({ - name: 'CADC_SSO', - value: cookieValue, - httpOnly: true, - secure: true, - sameSite: 'none', - path: '/', - maxAge: 169344, - }) - } - } - - return response -} diff --git a/rafts/frontend/src/auth/cadc-auth/checkIsAuthenticated.ts b/rafts/frontend/src/auth/cadc-auth/checkIsAuthenticated.ts deleted file mode 100644 index 4bd08bf0..00000000 --- a/rafts/frontend/src/auth/cadc-auth/checkIsAuthenticated.ts +++ /dev/null @@ -1,123 +0,0 @@ -/* - ************************************************************************ - ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* - ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** - * - * (c) 2026. (c) 2026. - * Government of Canada Gouvernement du Canada - * National Research Council Conseil national de recherches - * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 - * All rights reserved Tous droits réservés - * - * NRC disclaims any warranties, Le CNRC dénie toute garantie - * expressed, implied, or énoncée, implicite ou légale, - * statutory, of any kind with de quelque nature que ce - * respect to the software, soit, concernant le logiciel, - * including without limitation y compris sans restriction - * any warranty of merchantability toute garantie de valeur - * or fitness for a particular marchande ou de pertinence - * purpose. NRC shall not be pour un usage particulier. - * liable in any event for any Le CNRC ne pourra en aucun cas - * damages, whether direct or être tenu responsable de tout - * indirect, special or general, dommage, direct ou indirect, - * consequential or incidental, particulier ou général, - * arising from the use of the accessoire ou fortuit, résultant - * software. Neither the name de l'utilisation du logiciel. Ni - * of the National Research le nom du Conseil National de - * Council of Canada nor the Recherches du Canada ni les noms - * names of its contributors may de ses participants ne peuvent - * be used to endorse or promote être utilisés pour approuver ou - * products derived from this promouvoir les produits dérivés - * software without specific prior de ce logiciel sans autorisation - * written permission. préalable et particulière - * par écrit. - * - * This file is part of the Ce fichier fait partie du projet - * OpenCADC project. OpenCADC. - * - * OpenCADC is free software: OpenCADC est un logiciel libre ; - * you can redistribute it and/or vous pouvez le redistribuer ou le - * modify it under the terms of modifier suivant les termes de - * the GNU Affero General Public la "GNU Affero General Public - * License as published by the License" telle que publiée - * Free Software Foundation, par la Free Software Foundation - * either version 3 of the : soit la version 3 de cette - * License, or (at your option) licence, soit (à votre gré) - * any later version. toute version ultérieure. - * - * OpenCADC is distributed in the OpenCADC est distribué - * hope that it will be useful, dans l'espoir qu'il vous - * but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE - * without even the implied GARANTIE : sans même la garantie - * warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ - * or FITNESS FOR A PARTICULAR ni d'ADÉQUATION À UN OBJECTIF - * PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence - * General Public License for Générale Publique GNU Affero - * more details. pour plus de détails. - * - * You should have received Vous devriez avoir reçu une - * a copy of the GNU Affero copie de la Licence Générale - * General Public License along Publique GNU Affero avec - * with OpenCADC. If not, see OpenCADC ; si ce n'est - * . pas le cas, consultez : - * . - * - ************************************************************************ - */ - -'use server' - -import { CANFAR_USER_URL } from '@/auth/cadc-auth/constants' - -export async function checkIsAuthenticated(req: Request): Promise<{ - isAuthenticated: boolean - username?: string -}> { - try { - // Extract cookies from the request - const cookieHeader = req.headers.get('cookie') || '' - const cookies = parseCookies(cookieHeader) - - // Look for CADC_SSO cookie - const cadcCookie = cookies['CADC_SSO'] - - if (!cadcCookie) { - return { isAuthenticated: false } - } - - // Validate the cookie by making a request to CADC whoami service - const response = await fetch(CANFAR_USER_URL, { - headers: { - Cookie: `CADC_SSO="${cadcCookie}"`, - }, - }) - - if (!response.ok) { - return { isAuthenticated: false } - } - // Parse the user info - const userInfo = await response.json() - - return { - isAuthenticated: true, - username: userInfo.username, - } - } catch (error) { - console.error('Error checking CADC auth:', error) - return { isAuthenticated: false } - } -} - -// Helper function to parse cookies -function parseCookies(cookieHeader: string): Record { - const cookies: Record = {} - - if (!cookieHeader) return cookies - - cookieHeader.split(';').forEach((cookie) => { - const [name, value] = cookie.trim().split('=') - cookies[name] = value - }) - - return cookies -} diff --git a/rafts/frontend/src/auth/cadc-auth/credentials.ts b/rafts/frontend/src/auth/cadc-auth/credentials.ts index 560f5243..6f706b1e 100644 --- a/rafts/frontend/src/auth/cadc-auth/credentials.ts +++ b/rafts/frontend/src/auth/cadc-auth/credentials.ts @@ -104,6 +104,7 @@ export const { */ // Step 3: Fetch user information const user: User | null = await fetchUserInfo(token) + if (!user) return null // Step 4: Fetch user groups/roles const { role: userRole, groups: userGroups } = await fetchUserGroups(token) @@ -130,7 +131,11 @@ export const { token.role = user.role token.groups = user.groups token.affiliation = user.affiliation - token.name = user.firstName + ' ' + user.lastName + const firstName = user.firstName?.trim() + const lastName = user.lastName?.trim() + token.name = firstName || lastName + ? [firstName, lastName].filter(Boolean).join(' ') + : user.id || '' } return token }, diff --git a/rafts/frontend/src/auth/cadc-auth/isStaleSession.ts b/rafts/frontend/src/auth/cadc-auth/isStaleSession.ts new file mode 100644 index 00000000..ac8953c6 --- /dev/null +++ b/rafts/frontend/src/auth/cadc-auth/isStaleSession.ts @@ -0,0 +1,4 @@ +import { Session } from 'next-auth' + +export const isStaleSession = (session: Session | null): boolean => + !!session && (!session.user?.name || session.user.name.includes('undefined')) diff --git a/rafts/frontend/src/auth/config/authorization.ts b/rafts/frontend/src/auth/config/authorization.ts index f88a48b1..a568070d 100644 --- a/rafts/frontend/src/auth/config/authorization.ts +++ b/rafts/frontend/src/auth/config/authorization.ts @@ -91,14 +91,14 @@ export const routes: Record = { path: '/', roles: ['contributor', 'reviewer', 'admin'], title: 'Dashboard', - description: 'Overview of RAFT activities', + description: 'Overview of RAFTS activities', }, // RAFT Creation createRaft: { path: '/form/create', roles: ['contributor', 'reviewer', 'admin'], - title: 'Create RAFT', + title: 'Create RAFTS', description: 'Submit a new research announcement', }, @@ -106,7 +106,7 @@ export const routes: Record = { viewRafts: { path: '/view', roles: ['contributor', 'reviewer', 'admin'], - title: 'View RAFTs', + title: 'View RAFTSs', description: 'Browse published announcements', }, @@ -114,38 +114,38 @@ export const routes: Record = { review: { path: '/review', roles: ['reviewer', 'admin'], - title: 'Review RAFTs', - description: 'Review and moderate submitted RAFTs', + title: 'Review RAFTSs', + description: 'Review and moderate submitted RAFTSs', children: { pending: { path: '/review/pending', roles: ['reviewer', 'admin'], title: 'Pending Review', - description: 'RAFTs awaiting initial review', + description: 'RAFTSs awaiting initial review', }, inProgress: { path: '/review/in-progress', roles: ['reviewer', 'admin'], title: 'In Progress', - description: 'RAFTs currently being reviewed', + description: 'RAFTSs currently being reviewed', }, approved: { path: '/review/approved', roles: ['reviewer', 'admin'], title: 'Approved', - description: 'RAFTs that have been approved', + description: 'RAFTSs that have been approved', }, rejected: { path: '/review/rejected', roles: ['reviewer', 'admin'], title: 'Rejected', - description: 'RAFTs that have been rejected', + description: 'RAFTSs that have been rejected', }, raftDetail: { path: '/review/rafts/:id', roles: ['reviewer', 'admin'], - title: 'RAFT Details', - description: 'Detailed view of a RAFT submission', + title: 'RAFTS Details', + description: 'Detailed view of a RAFTS submission', }, }, }, diff --git a/rafts/frontend/src/components/DOIRaftTable/ActionMenu.tsx b/rafts/frontend/src/components/DOIRaftTable/ActionMenu.tsx index e3586266..76d3775b 100644 --- a/rafts/frontend/src/components/DOIRaftTable/ActionMenu.tsx +++ b/rafts/frontend/src/components/DOIRaftTable/ActionMenu.tsx @@ -107,7 +107,7 @@ export default function ActionMenu({ rowData, onStatusChange }: ActionMenuProps) setIsSubmitting(false) return } - onStatusChange?.('RAFT status changed to Draft.', 'success') + onStatusChange?.('RAFTS status changed to Draft.', 'success') } catch (error) { console.error('[ActionMenu] Error updating status:', error) onStatusChange?.('An error occurred', 'error') @@ -132,7 +132,7 @@ export default function ActionMenu({ rowData, onStatusChange }: ActionMenuProps) try { const result = await submitForReview(raftId, rowData.dataDirectory) if (result.success) { - onStatusChange?.('RAFT status changed to Review Ready.', 'success') + onStatusChange?.('RAFTS status changed to Review Ready.', 'success') } else { console.error('[ActionMenu] Failed to submit for review:', result.message) onStatusChange?.(result.message || 'Failed to submit for review', 'error') @@ -153,13 +153,13 @@ export default function ActionMenu({ rowData, onStatusChange }: ActionMenuProps) return ( - + - + diff --git a/rafts/frontend/src/components/DOIRaftTable/RaftTable.tsx b/rafts/frontend/src/components/DOIRaftTable/RaftTable.tsx index cfb55b68..1bc4a9d3 100644 --- a/rafts/frontend/src/components/DOIRaftTable/RaftTable.tsx +++ b/rafts/frontend/src/components/DOIRaftTable/RaftTable.tsx @@ -219,7 +219,7 @@ export default function RaftTable({ data, onRefresh, isLoading = false }: RaftTa setGlobalFilter(e.target.value)} InputProps={{ @@ -235,7 +235,7 @@ export default function RaftTable({ data, onRefresh, isLoading = false }: RaftTa - +
{table.getHeaderGroups().map((headerGroup) => ( diff --git a/rafts/frontend/src/components/Layout/AppBar.tsx b/rafts/frontend/src/components/Layout/AppBar.tsx index 6dc075de..a5306730 100644 --- a/rafts/frontend/src/components/Layout/AppBar.tsx +++ b/rafts/frontend/src/components/Layout/AppBar.tsx @@ -151,7 +151,7 @@ const AppBar = ({ session }: AppBarProps) => { sx={{ display: { xs: 'none', md: 'flex' } }} > - Research Announcements For The Solar System (RAFTs) + Research Announcements For The Solar System (RAFTS) diff --git a/rafts/frontend/src/components/Layout/AppLayout.tsx b/rafts/frontend/src/components/Layout/AppLayout.tsx index bc6a59a9..2ef20dd1 100644 --- a/rafts/frontend/src/components/Layout/AppLayout.tsx +++ b/rafts/frontend/src/components/Layout/AppLayout.tsx @@ -69,6 +69,7 @@ import { ReactNode } from 'react' import AppBar from '@/components/Layout/AppBar' import { auth } from '@/auth/cadc-auth/credentials' import { VersionInfo } from '@/components/VersionInfo' +import { isStaleSession } from '@/auth/cadc-auth/isStaleSession' interface AppLayoutProps { children: ReactNode @@ -76,10 +77,11 @@ interface AppLayoutProps { const AppLayout = async ({ children }: AppLayoutProps) => { const session = await auth() + const validSession = isStaleSession(session) ? null : session return (
- +
{children}
Footer
diff --git a/rafts/frontend/src/components/Layout/LoginFormLayout.tsx b/rafts/frontend/src/components/Layout/LoginFormLayout.tsx index 8d4d92fb..e7a16ca9 100644 --- a/rafts/frontend/src/components/Layout/LoginFormLayout.tsx +++ b/rafts/frontend/src/components/Layout/LoginFormLayout.tsx @@ -79,7 +79,7 @@ const LoginFormLayout = ({ children }: LoginFormLayoutProps) => { - Research Announcements For The Solar System (RAFTs) + Research Announcements For The Solar System (RAFTS) } /> diff --git a/rafts/frontend/src/components/RaftDetail/RaftDetail.tsx b/rafts/frontend/src/components/RaftDetail/RaftDetail.tsx index 8f8f4042..8e96451f 100644 --- a/rafts/frontend/src/components/RaftDetail/RaftDetail.tsx +++ b/rafts/frontend/src/components/RaftDetail/RaftDetail.tsx @@ -162,7 +162,7 @@ export default function RaftDetail({ raftData }: RaftDetailProps) { } setSnackbar({ open: true, - message: 'RAFT status changed to Draft.', + message: 'RAFTS status changed to Draft.', severity: 'success', }) } catch (error) { @@ -189,7 +189,7 @@ export default function RaftDetail({ raftData }: RaftDetailProps) { if (!raftData.id) { setSnackbar({ open: true, - message: 'No RAFT ID available', + message: 'No RAFTS ID available', severity: 'error', }) setDeleteDialogOpen(false) @@ -202,7 +202,7 @@ export default function RaftDetail({ raftData }: RaftDetailProps) { if (result.success) { setSnackbar({ open: true, - message: 'RAFT deleted successfully', + message: 'RAFTS deleted successfully', severity: 'success', }) setDeleteDialogOpen(false) @@ -212,7 +212,7 @@ export default function RaftDetail({ raftData }: RaftDetailProps) { } else { setSnackbar({ open: true, - message: result.message || 'Failed to delete RAFT', + message: result.message || 'Failed to delete RAFTS', severity: 'error', }) setDeleteDialogOpen(false) @@ -221,7 +221,7 @@ export default function RaftDetail({ raftData }: RaftDetailProps) { console.error('[RaftDetail] Error deleting RAFT:', error) setSnackbar({ open: true, - message: 'An error occurred while deleting RAFT', + message: 'An error occurred while deleting RAFTS', severity: 'error', }) setDeleteDialogOpen(false) @@ -247,7 +247,7 @@ export default function RaftDetail({ raftData }: RaftDetailProps) { if (result.success) { setSnackbar({ open: true, - message: 'RAFT status changed to Review Ready.', + message: 'RAFTS status changed to Review Ready.', severity: 'success', }) // Refresh the page to reflect the new status @@ -292,7 +292,7 @@ export default function RaftDetail({ raftData }: RaftDetailProps) { if (result.success) { setSnackbar({ open: true, - message: 'RAFT status changed to Draft.', + message: 'RAFTS status changed to Draft.', severity: 'success', }) router.refresh() diff --git a/rafts/frontend/src/components/RaftDetail/components/DeleteConfirmationDialog.tsx b/rafts/frontend/src/components/RaftDetail/components/DeleteConfirmationDialog.tsx index 9dc6f4e0..3431881f 100644 --- a/rafts/frontend/src/components/RaftDetail/components/DeleteConfirmationDialog.tsx +++ b/rafts/frontend/src/components/RaftDetail/components/DeleteConfirmationDialog.tsx @@ -97,10 +97,10 @@ export default function DeleteConfirmationDialog({ aria-labelledby="delete-dialog-title" aria-describedby="delete-dialog-description" > - Delete RAFT + Delete RAFTS - Are you sure you want to delete this RAFT? This action cannot be undone. + Are you sure you want to delete this RAFTS? This action cannot be undone. diff --git a/rafts/frontend/src/components/RaftDetail/components/RaftBreadcrumbs.tsx b/rafts/frontend/src/components/RaftDetail/components/RaftBreadcrumbs.tsx index 64425131..a6571a20 100644 --- a/rafts/frontend/src/components/RaftDetail/components/RaftBreadcrumbs.tsx +++ b/rafts/frontend/src/components/RaftDetail/components/RaftBreadcrumbs.tsx @@ -91,13 +91,13 @@ export default function RaftBreadcrumbs({ title, basePath }: RaftBreadcrumbsProp - RAFTs + RAFTSs - {title || 'RAFT Details'} + {title || 'RAFTS Details'} diff --git a/rafts/frontend/src/components/RaftDetail/components/RaftHeader.tsx b/rafts/frontend/src/components/RaftDetail/components/RaftHeader.tsx index 3821efc1..16a27994 100644 --- a/rafts/frontend/src/components/RaftDetail/components/RaftHeader.tsx +++ b/rafts/frontend/src/components/RaftDetail/components/RaftHeader.tsx @@ -173,7 +173,7 @@ export default function RaftHeader({ )} - {title || 'Untitled RAFT'} + {title || 'Untitled RAFTS'} @@ -272,7 +272,7 @@ export default function RaftHeader({ )} - + )} diff --git a/rafts/frontend/src/components/RaftTable/PublishedRaftTable.tsx b/rafts/frontend/src/components/RaftTable/PublishedRaftTable.tsx index fd03a94d..06397fdd 100644 --- a/rafts/frontend/src/components/RaftTable/PublishedRaftTable.tsx +++ b/rafts/frontend/src/components/RaftTable/PublishedRaftTable.tsx @@ -169,7 +169,7 @@ export default function RaftTable({ data, isLoading = false }: RaftTableProps) { setGlobalFilter(e.target.value)} slotProps={{ @@ -187,7 +187,7 @@ export default function RaftTable({ data, isLoading = false }: RaftTableProps) { -
+
{table.getHeaderGroups().map((headerGroup) => ( @@ -235,8 +235,8 @@ export default function RaftTable({ data, isLoading = false }: RaftTableProps) { diff --git a/rafts/frontend/src/components/RaftTable/RaftTable.tsx b/rafts/frontend/src/components/RaftTable/RaftTable.tsx index 53ef0729..beec807a 100644 --- a/rafts/frontend/src/components/RaftTable/RaftTable.tsx +++ b/rafts/frontend/src/components/RaftTable/RaftTable.tsx @@ -171,7 +171,7 @@ export default function RaftTable({ data, isLoading = false }: RaftTableProps) { setGlobalFilter(e.target.value)} slotProps={{ @@ -189,7 +189,7 @@ export default function RaftTable({ data, isLoading = false }: RaftTableProps) { -
+
{table.getHeaderGroups().map((headerGroup) => ( @@ -240,8 +240,8 @@ export default function RaftTable({ data, isLoading = false }: RaftTableProps) { diff --git a/rafts/frontend/src/components/RaftTable/ReviewRaftTable.tsx b/rafts/frontend/src/components/RaftTable/ReviewRaftTable.tsx index bef9c033..d7401149 100644 --- a/rafts/frontend/src/components/RaftTable/ReviewRaftTable.tsx +++ b/rafts/frontend/src/components/RaftTable/ReviewRaftTable.tsx @@ -184,7 +184,7 @@ export default function RaftTable({ setGlobalFilter(e.target.value)} InputProps={{ @@ -200,7 +200,7 @@ export default function RaftTable({ -
+
{table.getHeaderGroups().map((headerGroup) => ( diff --git a/rafts/frontend/src/components/User/LoginForm.tsx b/rafts/frontend/src/components/User/LoginForm.tsx index 5d6c90e9..54d2dc78 100644 --- a/rafts/frontend/src/components/User/LoginForm.tsx +++ b/rafts/frontend/src/components/User/LoginForm.tsx @@ -74,7 +74,6 @@ import VisibilityIcon from '@mui/icons-material/Visibility' import VisibilityOffIcon from '@mui/icons-material/VisibilityOff' import UserIcon from '@mui/icons-material/VerifiedUser' import { useState } from 'react' -import Link from 'next/link' import { AuthState, LoginFormValues } from '@/actions/auth' import Turnstile from './Turnstile' @@ -205,12 +204,24 @@ const LoginForm = ({ authAction, returnUrl }: LoginFormProps) => { }} disabled={isSubmitting} /> - - {t('forgot_password')} - + {turnstileSiteKey && (
diff --git a/rafts/frontend/src/components/User/management/ManageUsers.tsx b/rafts/frontend/src/components/User/management/ManageUsers.tsx index ca0872a4..86189406 100644 --- a/rafts/frontend/src/components/User/management/ManageUsers.tsx +++ b/rafts/frontend/src/components/User/management/ManageUsers.tsx @@ -163,7 +163,7 @@ export default function ManageUsers() { User Management - Manage users, roles, and permissions for the RAFT system. + Manage users, roles, and permissions for the RAFTS system. @@ -210,7 +210,7 @@ export default function ManageUsers() {
-
CADC RAFT Publication System
+
CADC RAFTS Publication System
) diff --git a/rafts/frontend/src/middleware.ts b/rafts/frontend/src/middleware.ts index 2f9097b6..f851f369 100644 --- a/rafts/frontend/src/middleware.ts +++ b/rafts/frontend/src/middleware.ts @@ -114,11 +114,10 @@ const middleware = async (request: NextRequest) => { const userRole = session?.user?.role // Skip locale handling for API routes + // Note: Next.js automatically strips basePath in middleware when configured in next.config.ts const pathname = request.nextUrl.pathname - const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '' - const pathWithoutBase = pathname.replace(basePath, '') - if (pathWithoutBase.startsWith('/api/')) { + if (pathname.startsWith('/api/')) { // For API routes, just return without locale handling const response = NextResponse.next() response.headers.set('Cache-Control', 'no-store, max-age=0') @@ -138,12 +137,11 @@ const middleware = async (request: NextRequest) => { return response } - // If not authenticated (no session), redirect to login-required page - if (!session) { + // If not authenticated or session is stale (missing user name), redirect to login + const isStaleSession = session && (!session.user?.name || session.user.name.includes('undefined')) + if (!session || isStaleSession) { const loginRequiredUrl = new URL('/login-required', request.url) - // Preserve the current path as returnUrl - strip basePath to avoid double prepending - const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '' - const returnPath = request.nextUrl.pathname.replace(basePath, '') || '/' + const returnPath = request.nextUrl.pathname || '/' loginRequiredUrl.searchParams.set('returnUrl', returnPath) return NextResponse.redirect(loginRequiredUrl) }