From e157f500bc27883a823ae8ec0bf65343f9d95ceb Mon Sep 17 00:00:00 2001 From: MartinBraquet Date: Sun, 21 Sep 2025 16:00:29 +0200 Subject: [PATCH] Fix dev firebase admin key --- .env.example | 4 + .gitignore | 1 + backend/api/src/helpers/endpoint.ts | 165 ++++++++++-------- backend/shared/src/firebase-utils.ts | 9 +- backend/shared/src/init-admin.ts | 9 +- scripts/post_install.sh | 6 +- .../googleApplicationCredentials-dev.json.enc | Bin 0 -> 2384 bytes 7 files changed, 112 insertions(+), 82 deletions(-) create mode 100644 secrets/googleApplicationCredentials-dev.json.enc diff --git a/.env.example b/.env.example index 2f99e828..7af330d6 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,9 @@ # You already have access to basic local functionality (UI, authentication, database read access). +# openssl enc -aes-256-cbc -salt -pbkdf2 -iter 100000 -in backend/shared/src/googleApplicationCredentials-dev.json -out secrets/googleApplicationCredentials-dev.json.enc +GOOGLE_CREDENTIALS_ENC_PWD=nP7s3274uzOG4c2t + + # Optional variables for full local functionality # For the location / distance filtering features. diff --git a/.gitignore b/.gitignore index cf43b8bb..41696098 100644 --- a/.gitignore +++ b/.gitignore @@ -80,3 +80,4 @@ email-preview *.tar.gz *.rar /favicon_color.ico +/backend/shared/src/googleApplicationCredentials-dev.json diff --git a/backend/api/src/helpers/endpoint.ts b/backend/api/src/helpers/endpoint.ts index b34cfaf0..7c1e0bfe 100644 --- a/backend/api/src/helpers/endpoint.ts +++ b/backend/api/src/helpers/endpoint.ts @@ -1,35 +1,29 @@ import * as admin from 'firebase-admin' -import { z } from 'zod' -import { Request, Response, NextFunction } from 'express' +import {z} from 'zod' +import {NextFunction, Request, Response} from 'express' -import { PrivateUser } from 'common/user' -import { APIError } from 'common/api/utils' -export { APIError } from 'common/api/utils' -import { - API, - APIPath, - APIResponseOptionalContinue, - APISchema, - ValidatedAPIParams, -} from 'common/api/schema' -import { log } from 'shared/utils' -import { getPrivateUserByKey } from 'shared/utils' +import {PrivateUser} from 'common/user' +import {APIError} from 'common/api/utils' +import {API, APIPath, APIResponseOptionalContinue, APISchema, ValidatedAPIParams,} from 'common/api/schema' +import {getPrivateUserByKey, log} from 'shared/utils' -export type Json = Record | Json[] -export type JsonHandler = ( - req: Request, - res: Response -) => Promise -export type AuthedHandler = ( - req: Request, - user: AuthedUser, - res: Response -) => Promise -export type MaybeAuthedHandler = ( - req: Request, - user: AuthedUser | undefined, - res: Response -) => Promise +export {APIError} from 'common/api/utils' + +// export type Json = Record | Json[] +// export type JsonHandler = ( +// req: Request, +// res: Response +// ) => Promise +// export type AuthedHandler = ( +// req: Request, +// user: AuthedUser, +// res: Response +// ) => Promise +// export type MaybeAuthedHandler = ( +// req: Request, +// user: AuthedUser | undefined, +// res: Response +// ) => Promise export type AuthedUser = { uid: string @@ -39,6 +33,29 @@ type JwtCredentials = { kind: 'jwt'; data: admin.auth.DecodedIdToken } type KeyCredentials = { kind: 'key'; data: string } type Credentials = JwtCredentials | KeyCredentials +// export async function verifyIdToken(payload: string): Promise { +// TODO: make local dev work without firebase admin SDK setup. +// if (IS_LOCAL) { +// // Skip real verification locally (to avoid needing to set up admin service account). +// return { +// aud: "", +// auth_time: 0, +// email_verified: false, +// exp: 0, +// firebase: {identities: {}, sign_in_provider: ""}, +// iat: 0, +// iss: "", +// phone_number: "", +// picture: "", +// sub: "", +// uid: 'dev-user', +// user_id: 'dev-user', +// email: 'dev-user@example.com' +// }; +// } +// return await admin.auth().verifyIdToken(payload); +// } + export const parseCredentials = async (req: Request): Promise => { const auth = admin.auth() const authHeader = req.get('Authorization') @@ -57,14 +74,14 @@ export const parseCredentials = async (req: Request): Promise => { throw new APIError(401, 'Firebase JWT payload undefined.') } try { - return { kind: 'jwt', data: await auth.verifyIdToken(payload) } + return {kind: 'jwt', data: await auth.verifyIdToken(payload)} } catch (err) { // This is somewhat suspicious, so get it into the firebase console console.error('Error verifying Firebase JWT: ', err, scheme, payload) throw new APIError(500, 'Error validating token.') } case 'Key': - return { kind: 'key', data: payload } + return {kind: 'key', data: payload} default: throw new APIError(401, 'Invalid auth scheme; must be "Key" or "Bearer".') } @@ -76,7 +93,7 @@ export const lookupUser = async (creds: Credentials): Promise => { if (typeof creds.data.user_id !== 'string') { throw new APIError(401, 'JWT must contain user ID.') } - return { uid: creds.data.user_id, creds } + return {uid: creds.data.user_id, creds} } case 'key': { const key = creds.data @@ -84,7 +101,7 @@ export const lookupUser = async (creds: Credentials): Promise => { if (!privateUser) { throw new APIError(401, `No private user exists with API key ${key}.`) } - return { uid: privateUser.id, creds: { privateUser, ...creds } } + return {uid: privateUser.id, creds: {privateUser, ...creds}} } default: throw new APIError(401, 'Invalid credential type.') @@ -109,45 +126,45 @@ export const validate = (schema: T, val: unknown) => { } } -export const jsonEndpoint = (fn: JsonHandler) => { - return async (req: Request, res: Response, next: NextFunction) => { - try { - res.status(200).json(await fn(req, res)) - } catch (e) { - next(e) - } - } -} - -export const authEndpoint = (fn: AuthedHandler) => { - return async (req: Request, res: Response, next: NextFunction) => { - try { - const authedUser = await lookupUser(await parseCredentials(req)) - res.status(200).json(await fn(req, authedUser, res)) - } catch (e) { - next(e) - } - } -} - -export const MaybeAuthedEndpoint = ( - fn: MaybeAuthedHandler -) => { - return async (req: Request, res: Response, next: NextFunction) => { - let authUser: AuthedUser | undefined = undefined - try { - authUser = await lookupUser(await parseCredentials(req)) - } catch { - // it's treated as an anon request - } - - try { - res.status(200).json(await fn(req, authUser, res)) - } catch (e) { - next(e) - } - } -} +// export const jsonEndpoint = (fn: JsonHandler) => { +// return async (req: Request, res: Response, next: NextFunction) => { +// try { +// res.status(200).json(await fn(req, res)) +// } catch (e) { +// next(e) +// } +// } +// } +// +// export const authEndpoint = (fn: AuthedHandler) => { +// return async (req: Request, res: Response, next: NextFunction) => { +// try { +// const authedUser = await lookupUser(await parseCredentials(req)) +// res.status(200).json(await fn(req, authedUser, res)) +// } catch (e) { +// next(e) +// } +// } +// } +// +// export const MaybeAuthedEndpoint = ( +// fn: MaybeAuthedHandler +// ) => { +// return async (req: Request, res: Response, next: NextFunction) => { +// let authUser: AuthedUser | undefined = undefined +// try { +// authUser = await lookupUser(await parseCredentials(req)) +// } catch { +// // it's treated as an anon request +// } +// +// try { +// res.status(200).json(await fn(req, authUser, res)) +// } catch (e) { +// next(e) +// } +// } +// } export type APIHandler = ( props: ValidatedAPIParams, @@ -161,7 +178,7 @@ export const typedEndpoint = ( name: N, handler: APIHandler ) => { - const { props: propSchema, authed: authRequired, method } = API[name] + const {props: propSchema, authed: authRequired, method} = API[name] return async (req: Request, res: Response, next: NextFunction) => { let authUser: AuthedUser | undefined = undefined @@ -195,7 +212,7 @@ export const typedEndpoint = ( // Convert bigint to number, b/c JSON doesn't support bigint. const convertedResult = deepConvertBigIntToNumber(result) - res.status(200).json(convertedResult ?? { success: true }) + res.status(200).json(convertedResult ?? {success: true}) } if (hasContinue) { diff --git a/backend/shared/src/firebase-utils.ts b/backend/shared/src/firebase-utils.ts index 588996c8..b48666ea 100644 --- a/backend/shared/src/firebase-utils.ts +++ b/backend/shared/src/firebase-utils.ts @@ -3,11 +3,12 @@ import {ENV_CONFIG} from "common/envs/constants"; export const getServiceAccountCredentials = () => { let keyPath = ENV_CONFIG.googleApplicationCredentials - // console.log('Using GOOGLE_APPLICATION_CREDENTIALS:', keyPath) + console.log('Using GOOGLE_APPLICATION_CREDENTIALS:', keyPath) if (!keyPath) { - throw new Error( - `Please set the GOOGLE_APPLICATION_CREDENTIALS environment variable to contain the path to your key file.` - ) + // throw new Error( + // `Please set the GOOGLE_APPLICATION_CREDENTIALS environment variable to contain the path to your key file.` + // ) + return {} } if (!keyPath.startsWith('/')) { diff --git a/backend/shared/src/init-admin.ts b/backend/shared/src/init-admin.ts index 08031c68..61ee9bde 100644 --- a/backend/shared/src/init-admin.ts +++ b/backend/shared/src/init-admin.ts @@ -9,9 +9,12 @@ export const initAdmin = () => { if (IS_LOCAL) { try { const serviceAccount = getServiceAccountCredentials() - console.log( - `Initializing connection to ${serviceAccount.project_id} Firebase...` - ) + // console.log(serviceAccount) + if (!serviceAccount.project_id) { + console.log(`GOOGLE_APPLICATION_CREDENTIALS not set, skipping admin firebase init.`) + return + } + console.log(`Initializing connection to ${serviceAccount.project_id} Firebase...`) return admin.initializeApp({ projectId: serviceAccount.project_id, credential: admin.credential.cert(serviceAccount), diff --git a/scripts/post_install.sh b/scripts/post_install.sh index de8dc46a..f94edb4c 100755 --- a/scripts/post_install.sh +++ b/scripts/post_install.sh @@ -7,4 +7,8 @@ cd "$(dirname "$0")"/.. if [ ! -f .env ]; then cp .env.example .env echo ".env file created from .env.example" -fi \ No newline at end of file +fi + +source .env + +openssl enc -d -aes-256-cbc -pbkdf2 -iter 100000 -in secrets/googleApplicationCredentials-dev.json.enc -out backend/shared/src/googleApplicationCredentials-dev.json -pass pass:$GOOGLE_CREDENTIALS_ENC_PWD \ No newline at end of file diff --git a/secrets/googleApplicationCredentials-dev.json.enc b/secrets/googleApplicationCredentials-dev.json.enc new file mode 100644 index 0000000000000000000000000000000000000000..3656e54482f92c21fec1786dcab6f05e6da514f3 GIT binary patch literal 2384 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