diff --git a/_emulator/extensions/firestore-auth-claims.env.local b/_emulator/extensions/firestore-auth-claims.env.local new file mode 100644 index 00000000..ca23245e --- /dev/null +++ b/_emulator/extensions/firestore-auth-claims.env.local @@ -0,0 +1 @@ +LOCATION=europe-west2 \ No newline at end of file diff --git a/_emulator/firebase.json b/_emulator/firebase.json index 0f5728b6..ea8d7362 100644 --- a/_emulator/firebase.json +++ b/_emulator/firebase.json @@ -1,7 +1,6 @@ { "extensions": { - "firestore-record-user-acknowledgements": "../firestore-record-user-acknowledgements", - "firestore-bundle-server": "../firestore-bundle-server" + "firestore-auth-claims": "../firestore-auth-claims" }, "storage": { "rules": "storage.rules" diff --git a/_emulator/functions/.gitignore b/_emulator/functions/.gitignore new file mode 100644 index 00000000..65b4c06e --- /dev/null +++ b/_emulator/functions/.gitignore @@ -0,0 +1,9 @@ +# Compiled JavaScript files +lib/**/*.js +lib/**/*.js.map + +# TypeScript v1 declaration files +typings/ + +# Node.js dependency directory +node_modules/ diff --git a/_emulator/functions/package.json b/_emulator/functions/package.json new file mode 100644 index 00000000..bea98f77 --- /dev/null +++ b/_emulator/functions/package.json @@ -0,0 +1,25 @@ +{ + "name": "functions", + "scripts": { + "build": "tsc", + "build:watch": "tsc --watch", + "serve": "npm run build && firebase emulators:start --only functions", + "shell": "npm run build && firebase functions:shell", + "start": "npm run shell", + "deploy": "firebase deploy --only functions", + "logs": "firebase functions:log" + }, + "engines": { + "node": "16" + }, + "main": "lib/index.js", + "dependencies": { + "firebase-admin": "^11.4.1", + "firebase-functions": "^4.1.0", + "@types/express-serve-static-core": "4.17.30" + }, + "devDependencies": { + "typescript": "^4.6.4" + }, + "private": true +} diff --git a/_emulator/functions/src/index.ts b/_emulator/functions/src/index.ts new file mode 100644 index 00000000..65c48daa --- /dev/null +++ b/_emulator/functions/src/index.ts @@ -0,0 +1,8 @@ +import * as functions from "firebase-functions"; + +export const findDocumentReferences = functions.https.onRequest( + (request, response) => { + /** Simply send back the data that we have posted */ + response.send(["searchFunction/testing/functions-testing/example"]); + } +); diff --git a/_emulator/functions/tsconfig.json b/_emulator/functions/tsconfig.json new file mode 100644 index 00000000..a9ed863a --- /dev/null +++ b/_emulator/functions/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "commonjs", + "noImplicitReturns": true, + "noUnusedLocals": true, + "outDir": "lib", + "sourceMap": true, + "strict": true, + "target": "es2017" + }, + "compileOnSave": true, + "include": ["src"] +} diff --git a/firestore-auth-claims/functions/.gitignore b/firestore-auth-claims/functions/.gitignore new file mode 100644 index 00000000..65b4c06e --- /dev/null +++ b/firestore-auth-claims/functions/.gitignore @@ -0,0 +1,9 @@ +# Compiled JavaScript files +lib/**/*.js +lib/**/*.js.map + +# TypeScript v1 declaration files +typings/ + +# Node.js dependency directory +node_modules/ diff --git a/firestore-auth-claims/functions/package.json b/firestore-auth-claims/functions/package.json index c81b8331..783e4ea6 100644 --- a/firestore-auth-claims/functions/package.json +++ b/firestore-auth-claims/functions/package.json @@ -4,21 +4,20 @@ "lint": "eslint \"src/**/*\"", "build": "tsc", "generate-readme": "node ../../generate-experimental-readme.js firestore-auth-claims > ../README.md" - }, "main": "lib/index.js", "dependencies": { - "@types/node": "^12.19.3", - "@typescript-eslint/eslint-plugin": "^4.6.0", - "@typescript-eslint/parser": "^4.6.0", - "firebase-admin": "^8.6.0", - "firebase-functions": "^3.6.1" + "@types/node": "^18.14.6", + "@typescript-eslint/eslint-plugin": "^5.54.1", + "@typescript-eslint/parser": "^5.54.1", + "firebase-admin": "^11.5.0", + "firebase-functions": "^4.2.1" }, "devDependencies": { "eslint": "^7.6.0", "eslint-plugin-import": "^2.22.0", "prettier": "^2.1.2", - "typescript": "^4.1.3" + "typescript": "^4.9.5" }, "private": true } diff --git a/firestore-auth-claims/functions/src/index.ts b/firestore-auth-claims/functions/src/index.ts index ef5e04ed..5ffa85e1 100644 --- a/firestore-auth-claims/functions/src/index.ts +++ b/firestore-auth-claims/functions/src/index.ts @@ -7,76 +7,80 @@ admin.initializeApp(); const auth = admin.auth(); const CLAIMS_FIELD: string | null = process.env.CLAIMS_FIELD || null; +const CLAIMS_COLLECTION = process.env.CLAIMS_COLLECTION || "user_claims"; -exports.sync = functions.handler.firestore.document.onWrite(async (change) => { - const uid = change.after.id; - try { - // make sure the user exists (can be fetched) before trying to set claims - await auth.getUser(uid); - } catch (e) { - functions.logger.error( - `Unable to sync claims for user '${uid}', error:`, - e, - { uid } - ); - } +exports.sync = functions.firestore + .document(CLAIMS_COLLECTION) + .onWrite(async (change) => { + const uid = change.after.id; + try { + // make sure the user exists (can be fetched) before trying to set claims + await auth.getUser(uid); + } catch (e) { + functions.logger.error( + `Unable to sync claims for user '${uid}', error:`, + e, + { uid } + ); + } - if ( - !change.after.exists || - (CLAIMS_FIELD && !change.after.get(CLAIMS_FIELD)) - ) { - functions.logger.info( - `Claims for user '${uid}' were deleted, removing from Auth.`, - { uid } - ); - return auth.setCustomUserClaims(uid, null); - } + if ( + !change.after.exists || + (CLAIMS_FIELD && !change.after.get(CLAIMS_FIELD)) + ) { + functions.logger.info( + `Claims for user '${uid}' were deleted, removing from Auth.`, + { uid } + ); + return auth.setCustomUserClaims(uid, null); + } - const beforeData = - (CLAIMS_FIELD ? change.before.get(CLAIMS_FIELD) : change.before.data()) || - {}; - const data = - (CLAIMS_FIELD ? change.after.get(CLAIMS_FIELD) : change.after.data()) || {}; - // don't write the _synced field to Auth - if (data._synced) { - delete data._synced; - } - if (beforeData._synced) { - delete beforeData._synced; - } + const beforeData = + (CLAIMS_FIELD ? change.before.get(CLAIMS_FIELD) : change.before.data()) || + {}; + const data = + (CLAIMS_FIELD ? change.after.get(CLAIMS_FIELD) : change.after.data()) || + {}; + // don't write the _synced field to Auth + if (data._synced) { + delete data._synced; + } + if (beforeData._synced) { + delete beforeData._synced; + } - if (isDeepStrictEqual(beforeData, data)) { - // don't persist identical claims - return; - } + if (isDeepStrictEqual(beforeData, data)) { + // don't persist identical claims + return; + } - functions.logger.info( - `Updating claims for user '${uid}', setting keys ${Object.keys(data).join( - ", " - )}.`, - { uid } - ); - if (typeof data !== "object") { - functions.logger.error( - `Invalid custom claims for user '${uid}'. Must be object, was ${JSON.stringify( - data - )}`, + functions.logger.info( + `Updating claims for user '${uid}', setting keys ${Object.keys(data).join( + ", " + )}.`, { uid } ); - return; - } - await auth.setCustomUserClaims(uid, data); + if (typeof data !== "object") { + functions.logger.error( + `Invalid custom claims for user '${uid}'. Must be object, was ${JSON.stringify( + data + )}`, + { uid } + ); + return; + } + await auth.setCustomUserClaims(uid, data); - const fpath = ["_synced"]; - if (CLAIMS_FIELD) { - fpath.unshift(CLAIMS_FIELD); - } - functions.logger.info( - `Claims set for user '${uid}', logging sync time to Firestore`, - { uid } - ); - return change.after.ref.update( - new admin.firestore.FieldPath(...fpath), - admin.firestore.FieldValue.serverTimestamp() - ); -}); + const fpath = ["_synced"]; + if (CLAIMS_FIELD) { + fpath.unshift(CLAIMS_FIELD); + } + functions.logger.info( + `Claims set for user '${uid}', logging sync time to Firestore`, + { uid } + ); + return change.after.ref.update( + new admin.firestore.FieldPath(...fpath), + admin.firestore.FieldValue.serverTimestamp() + ); + }); diff --git a/package.json b/package.json index 32f8fdf4..320616a3 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "lint": "prettier --list-different \"**/*.{js,md,yml,ts,json,yaml}\"", "clean": "lerna run --parallel clean && lerna clean", "build": "lerna run --parallel build", + "local:emulator": "cd _emulator && firebase emulators:start -P demo-test", "generate-readmes": "lerna run --parallel generate-readme", "postinstall": "lerna bootstrap --no-ci && lerna run --parallel clean && npm run build", "prepare": "husky install"