Skip to content

Commit

Permalink
remove v9 firebase and rely on service account to perform operations
Browse files Browse the repository at this point in the history
  • Loading branch information
choden-dev committed Mar 10, 2024
1 parent fc46030 commit 3b25317
Show file tree
Hide file tree
Showing 20 changed files with 143 additions and 80 deletions.
4 changes: 1 addition & 3 deletions common/__generated__/swagger.json

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

3 changes: 3 additions & 0 deletions server/firebase.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"firestore": {
"rules": "firestore.rules"
},
"emulators": {
"auth": {
"port": 9099
Expand Down
8 changes: 8 additions & 0 deletions server/firestore.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false
}
}
}
4 changes: 2 additions & 2 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.18.2",
"firebase": "^10.8.1",
"firebase-admin": "^12.0.0",
"helmet": "^7.1.0",
"supertest": "^6.3.4",
Expand Down Expand Up @@ -39,6 +38,7 @@
"tsoa": "./node_modules/.bin/tsoa",
"nodemon": "../node_modules/.bin/nodemon",
"build": "tsc -p ./tsconfig.prod.json && tsc-alias",
"serve": "node --es-module-specifier-resolution=node dist/server/src/index.js"
"serve": "node --es-module-specifier-resolution=node dist/server/src/index.js",
"token": "ts-node ./tooling/login-prod.ts"
}
}
11 changes: 6 additions & 5 deletions server/src/business-layer/security/Authentication.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as express from "express"
import auth from "./FirebaseAuth"
import { auth } from "./Firebase"
import FireBaseError from "data-layer/utils/FirebaseError"

export function expressAuthentication(
request: express.Request,
Expand All @@ -14,24 +15,24 @@ export function expressAuthentication(
reject(new Error("No token provided"))
}

const token = authHeader.split(" ")[1] // Gets part after Bearer
const token = authHeader.split(" ")[2] // Gets part after Bearer

auth
.verifyIdToken(token)
.then((decodedToken) => {
const { uid } = decodedToken
auth.getUser(uid).then((user) => {
for (const scope of scopes!) {
if (!user.customClaims![scope]) {
reject(new Error("No scope"))
if (!user.customClaims[scope]) {
throw new Error("No scope")
}
}
resolve(user)
})
})
.catch((reason) => {
console.error(reason)
reject(new Error("Invalid token"))
throw new FireBaseError("Authentication Error", 401, reason)
})
})
}
Expand Down
17 changes: 17 additions & 0 deletions server/src/business-layer/security/Firebase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {
EMULATOR_AUTH_PORT,
EMULATOR_FIRESTORE_PORT,
EMULATOR_HOST
} from "data-layer/adapters/EmulatorConfig"
import * as _admin from "firebase-admin"

if (process.env.DEV || process.env.JEST_WORKER_ID !== undefined) {
process.env.FIRESTORE_EMULATOR_HOST = `${EMULATOR_HOST}:${EMULATOR_FIRESTORE_PORT}`
process.env.FIREBASE_AUTH_EMULATOR_HOST = `${EMULATOR_HOST}:${EMULATOR_AUTH_PORT}`
}

const firebase = _admin.initializeApp()

export const admin = _admin

export const auth = firebase.auth()
5 changes: 0 additions & 5 deletions server/src/business-layer/security/FirebaseAuth.ts

This file was deleted.

1 change: 1 addition & 0 deletions server/src/data-layer/adapters/EmulatorConfig.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const EMULATOR_PROJECT_ID = "uasc-ceebc"
export const EMULATOR_HOST = "localhost"
export const EMULATOR_FIRESTORE_PORT = 8080
export const EMULATOR_AUTH_PORT = 9090
52 changes: 21 additions & 31 deletions server/src/data-layer/adapters/FirestoreCollections.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,27 @@
// credit https://plainenglish.io/blog/using-firestore-with-typescript-in-the-v9-sdk-cf36851bb099
import "dotenv/config"
import { UserAdditionalInfo } from "data-layer/models/firebase"
import {
getFirestore,
CollectionReference,
collection,
DocumentData,
connectFirestoreEmulator
} from "firebase/firestore"
import { USERS_COLLECTION } from "./CollectionNames"
import { initializeApp } from "firebase/app"
import { firebaseConfig } from "./FirestoreConfig"
import {
EMULATOR_FIRESTORE_PORT,
EMULATOR_HOST,
EMULATOR_PROJECT_ID
} from "./EmulatorConfig"
import { admin } from "business-layer/security/Firebase"

if (process.env.DEV || process.env.JEST_WORKER_ID !== undefined) {
initializeApp({ projectId: EMULATOR_PROJECT_ID })
} else {
initializeApp(firebaseConfig)
}
const converter = <T>() => ({
toFirestore: (data: any) => data,
fromFirestore: (doc: any) => doc.data() as T
})

export const firestore = getFirestore()
export const firestore = Object.assign(
() => {
return admin.firestore()
},
{
doc: <T>(path: string) => {
return admin.firestore().doc(path).withConverter<T>(converter<T>())
},
collection: <T>(path: string) => {
return admin.firestore().collection(path).withConverter<T>(converter<T>())
}
}
)

if (process.env.DEV || process.env.JEST_WORKER_ID !== undefined) {
connectFirestoreEmulator(firestore, EMULATOR_HOST, EMULATOR_FIRESTORE_PORT)
}

const createCollection = <T = DocumentData>(collectionName: string) => {
return collection(firestore, collectionName) as CollectionReference<T>
}

export const UsersCollection =
createCollection<UserAdditionalInfo>(USERS_COLLECTION)
export const db = {
users: firestore.collection<UserAdditionalInfo>("users")
} as const
2 changes: 1 addition & 1 deletion server/src/data-layer/adapters/FirestoreUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Timestamp } from "firebase/firestore"
import { Timestamp } from "firebase-admin/firestore"

export const firestoreTimestampToDate = (firestoreDate: Timestamp) => {
return new Date(firestoreDate.seconds * 1000) // date takes ms
Expand Down
2 changes: 1 addition & 1 deletion server/src/data-layer/models/firebase.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Timestamp } from "firebase/firestore"
import { Timestamp } from "firebase-admin/firestore"

export interface UserAdditionalInfo {
date_of_birth: Timestamp // Assuming this is a timestamp
Expand Down
32 changes: 7 additions & 25 deletions server/src/data-layer/services/UserService.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,40 @@
import { UsersCollection } from "data-layer/adapters/FirestoreCollections"
import { db } from "data-layer/adapters/FirestoreCollections"
import { UserAdditionalInfo } from "data-layer/models/firebase"
import {
deleteDoc,
doc,
getDoc,
getDocs,
query,
setDoc,
updateDoc,
where
} from "firebase/firestore"

export default class UserService {
// Create
public async addUser(uid: string, additionalInfo: UserAdditionalInfo) {
await setDoc(doc(UsersCollection, uid), additionalInfo)
await db.users.doc(uid).set(additionalInfo)
}

// Read
public async getUsers() {
const res = await getDocs(UsersCollection)
const res = await db.users.get()
const users = res.docs.map((user) => {
return user.data()
})
return users
}

public async getUser(uid: string) {
const userDoc = await getDoc(doc(UsersCollection, uid))
const userDoc = await db.users.doc(uid).get()
return userDoc.data()
}

public async getFilteredUsers(filters: Partial<UserAdditionalInfo>) {
let q = query(UsersCollection)
for (const filter of Object.keys(filters)) {
const field = filter as keyof UserAdditionalInfo
q = query(q, where(filter, "==", filters[field]))
}
const filteredUsers = await getDocs(q)
return filteredUsers
// TODO
}

// Update
public async editUser(
uid: string,
updatedFields: Partial<UserAdditionalInfo>
) {
const userRef = doc(UsersCollection, uid)
await updateDoc(userRef, updatedFields)
await db.users.doc(uid).set(updatedFields, { merge: true })
}

// Delete
public async deleteUser(uid: string) {
const userRef = await doc(UsersCollection, uid)
await deleteDoc(userRef)
await db.users.doc(uid).delete()
}
}
8 changes: 8 additions & 0 deletions server/src/data-layer/utils/FirebaseError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default class FireBaseError extends Error {
protected statusCode: number
constructor(name: string, statusCode: number, message?: string) {
super(message)
this.name = name
this.statusCode = statusCode
}
}
6 changes: 4 additions & 2 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { RegisterRoutes } from "middleware/__generated__/routes"
import helmet from "helmet"

let spec: swaggerUi.JsonObject | undefined
let generatedHtml: string | undefined
const importSwaggerJson = async () => {
if (!process.env.DEV) {
spec = await import("../../common/__generated__/swagger.json")
Expand All @@ -28,9 +29,10 @@ app.use("/api-docs", swaggerUi.serve, async (_req: Request, res: Response) => {
} else {
// Prod
if (!spec) {
importSwaggerJson()
await importSwaggerJson()
generatedHtml = swaggerUi.generateHTML(spec)
}
return res.send(swaggerUi.setup(spec))
return res.send(generatedHtml)
}
})
app.get("/", (req: Request, res: Response) => {
Expand Down
2 changes: 1 addition & 1 deletion server/src/middleware/__generated__/routes.ts

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

2 changes: 1 addition & 1 deletion server/src/service-layer/controllers/UserController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {

@Route("users")
export class UsersController extends Controller {
@Security("jwt", ["user"])
@Security("jwt")
@Get()
public async getUser(): Promise<UserAdditionalInfo[]> {
return new UserService().getUsers()
Expand Down
57 changes: 57 additions & 0 deletions server/tooling/login-prod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import admin from "firebase-admin"
import dotenv from "dotenv"
dotenv.config()

/**
* Credit John Chen
*
* How to use:
* ```
* ts-node ./test/scripts/loginScript <USER_UID>
*
* ts-node ./test/scripts/loginScript mdLy2GYwTMZovNtnkj121dWU2YP2
* ```
*/

admin.initializeApp({
credential: admin.credential.applicationDefault()
})

const createIdToken = async (uid: string) => {
try {
const customToken = await admin.auth().createCustomToken(uid)

const res = await fetch(
`https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key=${process.env.API_KEY}`,
{
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
token: customToken,
returnSecureToken: true
})
}
)

const data = (await res.json()) as any
console.log("\nAuthorization Header:")
console.log("Bearer " + data.idToken)

return data.idToken
} catch (e) {
console.log(e)
}
}

const args = process.argv.slice(2)

if (args.length === 0) {
console.log("Login with User ID:", process.env.USER_ID)
createIdToken(process.env.USER_ID)
} else {
console.log("Login with User ID:", args[0])
createIdToken(args[0])
}
3 changes: 2 additions & 1 deletion server/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
"resolveJsonModule": true
},
"include": [
"tooling/**/*",
"src/**/*",
"src/business-layer/security/FirebaseAuth.ts",
"src/business-layer/security/Firebase.ts",
"src/data-layer/adapters/EmulatorConfig.ts"
],
"exclude": ["node_modules", "dist"]
Expand Down
2 changes: 1 addition & 1 deletion server/tsconfig.prod.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
},
"extends": "./tsconfig.json",
"include": ["src/middleware/__generated__/routes.ts"],
"exclude": ["**/*.test.ts", "**/*.mock.ts", "src/test-config"]
"exclude": ["**/*.test.ts", "**/*.mock.ts", "src/test-config", "tooling/**/*"]
}
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7530,7 +7530,7 @@ firebase-tools@^12.4.7:
winston-transport "^4.4.0"
ws "^7.2.3"

firebase@^10.3.1, firebase@^10.8.1:
firebase@^10.3.1:
version "10.8.1"
resolved "https://registry.yarnpkg.com/firebase/-/firebase-10.8.1.tgz#d7eee67129a35fcfabda0c125e6b94abb9c420fb"
integrity sha512-4B2jzhU/aumfKL446MG41/T5+t+9d9urf5XGrjC0HRQUm4Ya/amV48HBchnje69ExaJP5f2WxO9OX3wh9ee4wA==
Expand Down

0 comments on commit 3b25317

Please sign in to comment.