Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/app check #107

Merged
merged 3 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion app-dev/party-game/app/actions/create-game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ import {Game, GameSettings, Question, QuestionSchema, gameStates} from '@/app/ty
import {QueryDocumentSnapshot, Timestamp} from 'firebase-admin/firestore';
import {GameSettingsSchema} from '@/app/types';
import {getAuth} from 'firebase-admin/auth';
import {getAppCheck} from 'firebase-admin/app-check';

export async function createGameAction({gameSettings, token}: {gameSettings: GameSettings, token: string}): Promise<{gameId: string}> {
export async function createGameAction({gameSettings, token, appCheckToken}: {gameSettings: GameSettings, token: string, appCheckToken: string}): Promise<{gameId: string}> {
await getAppCheck().verifyToken(appCheckToken);
const authUser = await getAuth(app).verifyIdToken(token);

// Parse request (throw an error if not correct)
Expand Down
5 changes: 3 additions & 2 deletions app-dev/party-game/app/actions/delete-game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@

import {app, gamesRef} from '@/app/lib/firebase-server-initialization';
import {GameIdSchema} from '@/app/types';
import {getAppCheck} from 'firebase-admin/app-check';
import {getAuth} from 'firebase-admin/auth';

export async function deleteGameAction({gameId, token}: {gameId: string, token: string}) {
// Authenticate user
export async function deleteGameAction({gameId, token, appCheckToken}: {gameId: string, token: string, appCheckToken: string}) {
await getAppCheck().verifyToken(appCheckToken);
const authUser = await getAuth(app).verifyIdToken(token);

// Parse request (throw an error if not correct)
Expand Down
5 changes: 3 additions & 2 deletions app-dev/party-game/app/actions/exit-game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ import {app, gamesRef} from '@/app/lib/firebase-server-initialization';
import {FieldValue} from 'firebase-admin/firestore';
import {GameIdSchema} from '@/app/types';
import {getAuth} from 'firebase-admin/auth';
import {getAppCheck} from 'firebase-admin/app-check';

export async function exitGameAction({gameId, token}: {gameId: string, token: string}) {
// Authenticate user
export async function exitGameAction({gameId, token, appCheckToken}: {gameId: string, token: string, appCheckToken: string}) {
await getAppCheck().verifyToken(appCheckToken);
const authUser = await getAuth(app).verifyIdToken(token);

// Parse request (throw an error if not correct)
Expand Down
5 changes: 3 additions & 2 deletions app-dev/party-game/app/actions/join-game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@
import {app, gamesRef} from '@/app/lib/firebase-server-initialization';
import {generateName} from '@/app/lib/name-generator';
import {GameIdSchema} from '@/app/types';
import {getAppCheck} from 'firebase-admin/app-check';
import {getAuth} from 'firebase-admin/auth';

export async function joinGameAction({gameId, token}: {gameId: string, token: string}) {
// Authenticate user
export async function joinGameAction({gameId, token, appCheckToken}: {gameId: string, token: string, appCheckToken: string}) {
await getAppCheck().verifyToken(appCheckToken);
const authUser = await getAuth(app).verifyIdToken(token);

// Parse request (throw an error if not correct)
Expand Down
9 changes: 7 additions & 2 deletions app-dev/party-game/app/actions/nudge-game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@

'use server';

import {gamesRef} from '@/app/lib/firebase-server-initialization';
import {app, gamesRef} from '@/app/lib/firebase-server-initialization';
import {GameIdSchema, gameStates} from '@/app/types';
import {getAppCheck} from 'firebase-admin/app-check';
import {getAuth} from 'firebase-admin/auth';
import {Timestamp} from 'firebase-admin/firestore';

export async function nudgeGame({gameId}: {gameId: string}) {
export async function nudgeGameAction({gameId, token, appCheckToken}: {gameId: string, token: string, appCheckToken: string}) {
await getAppCheck().verifyToken(appCheckToken);
await getAuth(app).verifyIdToken(token);

// Validate request
// Will throw an error if not a string
GameIdSchema.parse(gameId);
Expand Down
5 changes: 3 additions & 2 deletions app-dev/party-game/app/actions/start-game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ import {app, gamesRef} from '@/app/lib/firebase-server-initialization';
import {GameIdSchema, gameStates} from '@/app/types';
import {FieldValue} from 'firebase-admin/firestore';
import {getAuth} from 'firebase-admin/auth';
import {getAppCheck} from 'firebase-admin/app-check';

export async function startGameAction({gameId, token}: {gameId: string, token: string}) {
// Authenticate user
export async function startGameAction({gameId, token, appCheckToken}: {gameId: string, token: string, appCheckToken: string}) {
await getAppCheck().verifyToken(appCheckToken);
const authUser = await getAuth(app).verifyIdToken(token);

// Parse request (throw an error if not correct)
Expand Down
5 changes: 3 additions & 2 deletions app-dev/party-game/app/actions/update-answer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@

import {app, gamesRef} from '@/app/lib/firebase-server-initialization';
import {GameIdSchema, gameStates} from '@/app/types';
import {getAppCheck} from 'firebase-admin/app-check';
import {getAuth} from 'firebase-admin/auth';
import {z} from 'zod';

export async function updateAnswerAction({gameId, answerSelection, token}: {gameId: string, answerSelection: boolean[], token: string}) {
// Authenticate user
export async function updateAnswerAction({gameId, answerSelection, token, appCheckToken}: {gameId: string, answerSelection: boolean[], token: string, appCheckToken: string}) {
await getAppCheck().verifyToken(appCheckToken);
const authUser = await getAuth(app).verifyIdToken(token);

// Parse request (throw an error if not correct)
Expand Down
7 changes: 6 additions & 1 deletion app-dev/party-game/app/components/border-countdown-timer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {Game, gameStates} from '@/app/types';
import {DocumentReference, Timestamp} from 'firebase/firestore';
import {useEffect, useState} from 'react';
import useFirebaseAuthentication from '@/app/hooks/use-firebase-authentication';
import {nudgeGame} from '@/app/actions/nudge-game';
import {nudgeGameAction} from '@/app/actions/nudge-game';
import {addTokens} from '@/app/lib/request-formatter';

export default function BorderCountdownTimer({game, children, gameRef}: { game: Game, children: React.ReactNode, gameRef: DocumentReference }) {
const [timeLeft, setTimeLeft] = useState<number>(game.timePerQuestion);
Expand All @@ -31,6 +32,10 @@ export default function BorderCountdownTimer({game, children, gameRef}: { game:
const isShowingCorrectAnswers = game.state === gameStates.SHOWING_CORRECT_ANSWERS;
const timeToCountDown = isShowingCorrectAnswers ? game.timePerAnswer : game.timePerQuestion;

const nudgeGame = async ({gameId}: {gameId: string}) => {
nudgeGameAction(await addTokens({gameId}));
};

useEffect(() => {
// all times are in seconds unless noted as `InMillis`
const timeElapsedInMillis = Timestamp.now().toMillis() - game.startTime.seconds * 1000;
Expand Down
13 changes: 8 additions & 5 deletions app-dev/party-game/app/components/create-game-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,15 @@

'use client';

import useFirebaseAuthentication from '@/app/hooks/use-firebase-authentication';
import {useRouter} from 'next/navigation';
import {useEffect, useState} from 'react';
import BigColorBorderButton from './big-color-border-button';
import {TimePerAnswerSchema, TimePerQuestionSchema} from '@/app/types';
import {createGameAction} from '../actions/create-game';
import {createGameAction} from '@/app/actions/create-game';
import {z} from 'zod';
import {addTokens} from '@/app/lib/request-formatter';

export default function CreateGameForm() {
const authUser = useFirebaseAuthentication();
const defaultTimePerQuestion = 60;
const defaultTimePerAnswer = 20;
const [timePerQuestionInputValue, setTimePerQuestionInputValue] = useState<string>(defaultTimePerQuestion.toString());
Expand All @@ -38,15 +37,19 @@ export default function CreateGameForm() {
const router = useRouter();
const onCreateGameSubmit = async (event: React.FormEvent) => {
event.preventDefault();
const token = await authUser.getIdToken();
try {
const response = await createGameAction({gameSettings: {timePerQuestion, timePerAnswer}, token});
const gameSettings = {timePerQuestion, timePerAnswer};
const response = await createGameAction(await addTokens({gameSettings}));
router.push(`/game/${response.gameId}`);
} catch (error) {
setSubmissionErrorMessage('There was an error handling the request.');
}
};

useEffect(() => {
setSubmissionErrorMessage('');
}, [timePerQuestion, timePerAnswer]);

useEffect(() => {
try {
TimePerQuestionSchema.parse(timePerQuestion);
Expand Down
9 changes: 3 additions & 6 deletions app-dev/party-game/app/components/delete-game-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,12 @@

'use client';

import useFirebaseAuthentication from '@/app/hooks/use-firebase-authentication';
import {deleteGameAction} from '../actions/delete-game';
import {deleteGameAction} from '@/app/actions/delete-game';
import {addTokens} from '../lib/request-formatter';

export default function DeleteGameButton({gameId}: { gameId: string }) {
const authUser = useFirebaseAuthentication();

const onDeleteGameClick = async () => {
const token = await authUser.getIdToken();
await deleteGameAction({gameId, token});
await deleteGameAction(await addTokens({gameId}));
};

return (
Expand Down
8 changes: 3 additions & 5 deletions app-dev/party-game/app/components/exit-game-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,15 @@

'use client';

import useFirebaseAuthentication from '@/app/hooks/use-firebase-authentication';
import {useRouter} from 'next/navigation';
import {exitGameAction} from '../actions/exit-game';
import {exitGameAction} from '@/app/actions/exit-game';
import {addTokens} from '../lib/request-formatter';

export default function ExitGameButton({gameId}: { gameId: string }) {
const authUser = useFirebaseAuthentication();
const router = useRouter();

const onExitGameClick = async () => {
const token = await authUser.getIdToken();
await exitGameAction({gameId, token});
await exitGameAction(await addTokens({gameId}));
router.push('/');
};

Expand Down
2 changes: 1 addition & 1 deletion app-dev/party-game/app/components/game-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
'use client';

import Link from 'next/link';
import useActiveGameList from '../hooks/use-active-game-list';
import useActiveGameList from '@/app/hooks/use-active-game-list';

export default function GameList() {
const {activeGameList} = useActiveGameList();
Expand Down
2 changes: 1 addition & 1 deletion app-dev/party-game/app/components/lobby.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import StartGameButton from '@/app/components/start-game-button';
import DeleteGameButton from '@/app/components/delete-game-button';
import PlayerList from './player-list';
import {Game} from '@/app/types';
import useFirebaseAuthentication from '../hooks/use-firebase-authentication';
import useFirebaseAuthentication from '@/app/hooks/use-firebase-authentication';
import ShareLinkPanel from './share-link-panel';
import {useState} from 'react';

Expand Down
19 changes: 12 additions & 7 deletions app-dev/party-game/app/components/question-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ import useFirebaseAuthentication from '@/app/hooks/use-firebase-authentication';
import QRCode from 'react-qr-code';
import {useEffect, useState} from 'react';
import Scoreboard from './scoreboard';
import useScoreboard from '../hooks/use-scoreboard';
import {updateAnswerAction} from '../actions/update-answer';
import useScoreboard from '@/app/hooks/use-scoreboard';
import {updateAnswerAction} from '@/app/actions/update-answer';
import {addTokens} from '../lib/request-formatter';

export default function QuestionPanel({game, gameRef, currentQuestion}: { game: Game, gameRef: DocumentReference, currentQuestion: Question }) {
const authUser = useFirebaseAuthentication();
Expand Down Expand Up @@ -53,11 +54,15 @@ export default function QuestionPanel({game, gameRef, currentQuestion}: { game:
const onAnswerClick = async (answerIndex: number) => {
if (game.state === gameStates.AWAITING_PLAYER_ANSWERS && !isGameLeader) {
// If the user is only supposed to pick one answer, clear the other answers first
const startingAnswerSelection = isSingleAnswer ? emptyAnswerSelection : answerSelection;
const newAnswerSelection: boolean[] = startingAnswerSelection.with(answerIndex, !answerSelection[answerIndex]);

const token = await authUser.getIdToken();
await updateAnswerAction({gameId, answerSelection: newAnswerSelection, token});
const newAnswerSelection: boolean[] = answerSelection.map((currentValue, index) => {
// update the selection to true
if (index === answerIndex) return true;
// update other selections to false if there is only one correct answer
if (isSingleAnswer) return false;
// otherwise, don't change it
return currentValue;
});
await updateAnswerAction(await addTokens({gameId, answerSelection: newAnswerSelection}));
}
};

Expand Down
2 changes: 1 addition & 1 deletion app-dev/party-game/app/components/scoreboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
'use client';

import '@/app/components/player-list.css';
import useScoreboard from '../hooks/use-scoreboard';
import useScoreboard from '@/app/hooks/use-scoreboard';

export default function Scoreboard() {
const {currentPlayer, playerScores} = useScoreboard();
Expand Down
6 changes: 2 additions & 4 deletions app-dev/party-game/app/components/start-game-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,14 @@

'use client';

import useFirebaseAuthentication from '@/app/hooks/use-firebase-authentication';
import './big-color-border-button.css';
import BigColorBorderButton from '@/app/components/big-color-border-button';
import {startGameAction} from '@/app/actions/start-game';
import {addTokens} from '@/app/lib/request-formatter';

export default function StartGameButton({gameId}: {gameId: string}) {
const authUser = useFirebaseAuthentication();
const onStartGameClick = async () => {
const token = await authUser.getIdToken();
await startGameAction({gameId, token});
await startGameAction(await addTokens({gameId}));
};

return (
Expand Down
7 changes: 3 additions & 4 deletions app-dev/party-game/app/hooks/use-game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {Game, GameSchema, emptyGame, gameStates} from '@/app/types';
import {doc, onSnapshot} from 'firebase/firestore';
import {usePathname} from 'next/navigation';
import useFirebaseAuthentication from './use-firebase-authentication';
import {joinGameAction} from '../actions/join-game';
import {joinGameAction} from '@/app/actions/join-game';
import {addTokens} from '../lib/request-formatter';

const useGame = () => {
const pathname = usePathname();
Expand All @@ -31,8 +32,7 @@ const useGame = () => {

useEffect(() => {
const joinGame = async () => {
const token = await authUser.getIdToken();
joinGameAction({gameId, token});
joinGameAction(await addTokens({gameId}));
};
if (game.leader.uid && authUser.uid && game.leader.uid !== authUser.uid) {
joinGame();
Expand All @@ -48,7 +48,6 @@ const useGame = () => {
const game = GameSchema.parse(doc.data());
setGame(game);
} catch (error) {
console.log(error);
setErrorMessage(`Game ${gameId} was not found.`);
}
});
Expand Down
13 changes: 13 additions & 0 deletions app-dev/party-game/app/lib/firebase-client-initialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,25 @@
* limitations under the License.
*/

'use client';

import {initializeApp} from 'firebase/app';
import {getFirestore} from 'firebase/firestore';
import {getAuth} from 'firebase/auth';
import {firebaseConfig} from '@/app/lib/firebase-config';
import {AppCheck, initializeAppCheck, ReCaptchaEnterpriseProvider} from 'firebase/app-check';

// Initialize Firebase
export const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
export const auth = getAuth(app);
export let appCheck: AppCheck;

if (typeof window !== 'undefined') {
// Create a ReCaptchaEnterpriseProvider instance using your reCAPTCHA Enterprise
// site key and pass it to initializeAppCheck().
appCheck = initializeAppCheck(app, {
provider: new ReCaptchaEnterpriseProvider('6Lc3JP8nAAAAAPrX4s-HwUT8L-k_aMtbKGhJEq_0'),
isTokenAutoRefreshEnabled: true, // Set to true to allow auto-refresh.
});
}
1 change: 1 addition & 0 deletions app-dev/party-game/app/lib/firebase-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@
export const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY || 'AIzaSyBr0i2bC9kdsdRVh-9pQ5yFOjxpweiTJrQ',
projectId: process.env.NEXT_PUBLIC_GOOGLE_CLOUD_PROJECT || 'cloud-quiz-next',
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID || '1:406096902405:web:7311c44c3657568af1df6c',
};
25 changes: 25 additions & 0 deletions app-dev/party-game/app/lib/request-formatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {appCheck, auth} from '@/app/lib/firebase-client-initialization';
import {getToken} from 'firebase/app-check';

export async function addTokens(requestBody: any) {
const appCheckTokenResponse = await getToken(appCheck, false);
const appCheckToken = appCheckTokenResponse.token;
const token = await auth.currentUser?.getIdToken();
return {...requestBody, token, appCheckToken};
}
Loading
Loading