diff --git a/package.json b/package.json index 3ebd2e1..129a2db 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "homepage": ".", + "proxy": "https://ccv-pubs.web.app/", "scripts": { "start": "react-scripts start", "build": "react-scripts build", diff --git a/src/components/ContentPage.jsx b/src/components/ContentPage.jsx index 2e1af69..02a1939 100755 --- a/src/components/ContentPage.jsx +++ b/src/components/ContentPage.jsx @@ -14,7 +14,7 @@ export function ContentPage() { return (
- {user ? ( + {user?.ccv ? (
diff --git a/src/utils/firebase.ts b/src/utils/firebase.ts index f510031..21274c5 100644 --- a/src/utils/firebase.ts +++ b/src/utils/firebase.ts @@ -4,6 +4,7 @@ import { initializeApp } from 'firebase/app'; import { getFirestore, doc, + getDoc, setDoc, collection, onSnapshot, @@ -19,7 +20,8 @@ import { signOut, } from 'firebase/auth'; -import { setPublications, setUser } from '../store/slice/appState'; +import { setPublications, setUser as setUserState } from '../store/slice/appState'; +import { User } from '../../types'; const firebaseConfig = { apiKey: 'AIzaSyBlu1GzA5jvM6mh6taIcjtNgcSEVxlxa1Q', @@ -35,15 +37,50 @@ const app = initializeApp(firebaseConfig); const db = getFirestore(app); const auth = getAuth(); const provider = new GoogleAuthProvider(); +provider.addScope('https://www.googleapis.com/auth/cloud-identity.groups.readonly'); provider.setCustomParameters({ hd: 'brown.edu', }); -const collectionName = 'publications'; +const publicationsCollection = 'publications'; +const usersCollection = 'users'; +const ccvStaffGoogleGroupId = '0184mhaj2thv9r6'; export const handleLogin = async () => { try { - await signInWithPopup(auth, provider); + const result = await signInWithPopup(auth, provider); + const credential = GoogleAuthProvider.credentialFromResult(result); + const token = credential.accessToken; + const { displayName, email } = result.user; + + // Fetch members from the CCV group + // If the user is in the group, we'll get back members. Otherwise, we'll get a permission error + // The access token is in scope here and I didn't want to save it anywhere + const response = await fetch( + `https://cloudidentity.googleapis.com/v1/groups/${ccvStaffGoogleGroupId}/memberships`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + + let ccv = false; + if (response.ok) { + const { memberships } = (await response.json()) ?? []; + + // Double check that the user is a member in the ccv group + ccv = memberships.some((m) => m.preferredMemberKey.id.toLowerCase() === email.toLowerCase()); + } + + const userDoc = await getUser(email); + if (!userDoc || userDoc.ccv !== ccv || userDoc.displayName !== displayName) { + await updateUser({ + displayName, + email, + ccv, + }); + } } catch (error) { console.log(error); } @@ -64,24 +101,22 @@ export const useAuthStateChanged = () => { const dispatch = useDispatch(); useEffect(() => { - const unsubscribe = onAuthStateChanged(auth, (user) => { + const unsubscribeAuth = onAuthStateChanged(auth, async (user) => { if (user) { - const { displayName, email, uid } = user; - - dispatch( - setUser({ - displayName, - email, - uid, - }) - ); + const { email } = user; + const unsubscribeUser = onSnapshot(doc(db, usersCollection, email), (doc) => { + if (doc.exists) { + dispatch(setUserState(doc.data())); + } + }); + return () => unsubscribeUser(); } else { - dispatch(setUser(null)); + dispatch(setUserState(null)); } }); return () => { - unsubscribe(); + unsubscribeAuth(); }; }, [dispatch]); }; @@ -95,7 +130,7 @@ export const usePublicationsCollection = () => { useEffect(() => { const unsubscribe = onSnapshot( query( - collection(db, collectionName), + collection(db, publicationsCollection), orderBy('updatedAt', 'desc'), limit(100) // TODO: TEMPORARY. Limiting right now. Set up pagination? ), @@ -116,10 +151,28 @@ export const usePublicationsCollection = () => { export const addPublication = async (publication) => { const docId = publication.doi.toLowerCase().replace(/\//g, '_'); - const docRef = doc(db, collectionName, docId); + const docRef = doc(db, publicationsCollection, docId); await setDoc(docRef, { ...publication, updatedAt: Date.now(), }); }; + +const updateUser = async ({ displayName, email, ccv }) => { + const docRef = doc(db, usersCollection, email); + + await setDoc(docRef, { + displayName, + email, + ccv, + updatedAt: Date.now(), + }); +}; + +const getUser = async (email): Promise => { + const docRef = doc(db, usersCollection, email); + const docSnap = await getDoc(docRef); + + return docSnap.data() as User; +}; diff --git a/types/index.d.ts b/types/index.d.ts index b4f79f8..15c8ef4 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -8,3 +8,10 @@ export interface Publication { year: number; abstract: string; } + +export interface User { + displayName: string; + email: string; + ccv: boolean; + updatedAt: number; +}