Firebase rules test does not pass even when using lockdown - google-cloud-firestore

I'm trying to test my firebase rules, but they seem to not pass even when I use lock down mode. I followed the guide at https://firebase.google.com/docs/firestore/security/test-rules-emulator
const firebase = require('#firebase/rules-unit-testing');
const fs = require('fs');
const projectId = 'test-judge';
function getAuthedFirestore(auth) {
return firebase.initializeAdminApp({
projectId: projectId,
auth: auth
}).firestore();
}
beforeEach(async () => {
await firebase.clearFirestoreData({ projectId });
});
before(async () => {
const rules = fs.readFileSync('firestore.rules', 'utf8');
await firebase.loadFirestoreRules({
projectId: projectId,
rules: rules
});
});
after(async () => {
await Promise.all(firebase.apps().map(app => app.delete()));
});
describe('locked down', () => {
it("require users to log in before creating a profile", async () => {
const db = getAuthedFirestore(null);
const profile = db.collection("users").doc("alice");
await firebase.assertFails(profile.set({ birthday: "January 1" }));
});
});
here is my firebase.json
{
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
}
}
and my package.json
{
"devDependencies": {
"firebase-admin": "^9.11.0",
"#firebase/app": "^0.6.29",
"#firebase/rules-unit-testing": "^1.3.12",
"mocha": "^9.0.3",
"fs-extra": "^10.0.0"
},
"scripts": {
"test": "mocha"
}
}
and here is firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}
It doesn't seem to me like I'm doing anything wrong, but if I run npm test the test fails. I expect it to pass since asserFails is used and in the rules I return false
I should not be able to set the value the test should pass
here is my output
Warning: FIRESTORE_EMULATOR_HOST not set, using default value localhost:8080
locked down
1) require users to log in before creating a profile
0 passing (324ms)
1 failing
1) locked down
require users to log in before creating a profile:
Error: Expected request to fail, but it succeeded.
at C:\Users\Moneer\Desktop\judge_rules\node_modules\#firebase\rules-unit-testing\dist\index.cjs.js:581:31
at async Context.<anonymous> (test\test.js:33:9)
npm ERR! Test failed. See above for more details.

This is expected behavior, Security Rules are what protect your backend services from malicious Client requests. The "AdminApp" is in relation to the Admin-SDK which is a service tool that interacts with the Firebase services directly behind the Security Rules.
For reference, you will notice that the Admin-SDK would normally require Service Account credentials which allows the SDK to authenticate with the GCP IAM services

Just realized I should be using initializeTestApp not initializeAdminApp

Related

Hardhat: how to deploy using a custom signer

Normally, to deploy contracts to a network, we specify the private keys in accounts section of the network config, Like below, and these accounts get used in signing the transactions.
module.exports = {
defaultNetwork: "rinkeby",
networks: {
hardhat: {
},
rinkeby: {
url: "https://eth-rinkeby.alchemyapi.io/v2/1234",
accounts: [privateKey1, privateKey2, ...]
}
},
But we need to use a custom signer that will sign the transactions instead. All transactions that are part of the deployment process needs to be signed via this custom signer.
How do I do this using Hardhat/ethers.js ?
You have to modify the deployment script to attach your custom signer to the contract factory (https://docs.ethers.io/v4/api-contract.html):
async function main() {
const HelloBar = await ethers.getContractFactory("HelloBar");
const signer = createYourCustomSigner();
// attach the signer to the factory
HelloBar = HelloBar.connect(signer);
const hellobar = await HelloBar.deploy();
await hellobar.deployed();
console.log("Address:", hellobar.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Then run the script with:
npx hardhat run --network localhost scripts/deploy.js

POST data to Google Sheet web app from AWS Lambda

CURRENTLY
I have a Google Sheets App Script 'web app'
Script in Goolge Sheets
function doPost(e) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("Sheet1");
sheet.getRange("A1").setValue("Hello!")
return "Success!"
}
Google Apps Script Web App Config:
Execute as: Me // or as User. I've tried both.
Who has access: Anyone within MyOrganisation
I want to make a POST request to the above Web App from AWS Lambda.
AWS Lambda .js:
const { GoogleSpreadsheet } = require("google-spreadsheet");
const doc = new GoogleSpreadsheet(
{spreadsheetId}
);
await doc.useServiceAccountAuth({
client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,
private_key: process.env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, "\n"),
});
let token = doc["jwtClient"]["credentials"]["access_token"];
await new Promise((resolve, reject) => {
const options = {
host: 'script.google.com',
path: "/macros/s/{myscriptid}/exec", //<-- my web app path!
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': "Bearer "+ token
}
};
//create the request object with the callback with the result
const req = HTTPS.request(options, (res) => {
resolve(JSON.stringify(res.statusCode));
});
// handle the possible errors
req.on('error', (e) => {
reject(e.message);
});
//do the request
req.write(JSON.stringify(data));
//finish the request
req.end();
});
console.log("response:"+JSON.stringify(response))
GCP Service Account
I have a GCP Service Account, with permission to Google Sheets API, and otherwise unrestricted access.
This Service account has EDIT access to the Google Sheet with the doPost(e) script.
Token Output:
"jwtClient": {
"_events": {},
"_eventsCount": 0,
"transporter": {},
"credentials": {
"access_token": "somelongvalue...............", //<-- what I use
"token_type": "Bearer",
"expiry_date": 1661662492000,
"refresh_token": "jwt-placeholder"
},
"certificateCache": {},
"certificateExpiry": null,
"certificateCacheFormat": "PEM",
"refreshTokenPromises": {},
"eagerRefreshThresholdMillis": 300000,
"forceRefreshOnFailure": false,
"email": "serviceaccount#appspot.gserviceaccount.com",
"key": "-----BEGIN PRIVATE KEY-----\nsomelongvalue=\n-----END PRIVATE KEY-----\n",
"scopes": [
"https://www.googleapis.com/auth/spreadsheets"
],
"subject": null,
"gtoken": {
"key": "-----BEGIN PRIVATE KEY-----\nsomelongvalue=\n-----END PRIVATE KEY-----\n",
"rawToken": {
"access_token": "somelongvalue...............",
"expires_in": 3599,
"token_type": "Bearer"
},
"iss": "serviceaccount#appspot.gserviceaccount.com",
"sub": null,
"scope": "https://www.googleapis.com/auth/spreadsheets",
"expiresAt": 1661662492000
}
}
ISSUE
Current response:
response:"401"
I cannot find any Google documentation on how to setup the headers to authenticate a request (from my service account) to my organisation restricted web app.
When the Web App is open to "Anyone" then it runs fine, but as soon as I restrict to MyOrganisation, I struggle to find a way to authenticate my POST request.
HELP!
How do I set up a POST request to my Google Sheets web app such that it can be protected by authentication? Right now, I'd be happy to find ANY means to authenticate this request (not necessarily a service account) that doesn't leave it completed open to public.
Should I use this hack?
One idea I had was to put a "secret" into my lambda function, and then make the web app public. The web app would check the secret, if if matched, would execute the function.
Modification points:
In order to access Web Apps using the access token with a script, the scopes of Drive API are required to be included. Those are https://www.googleapis.com/auth/drive.readonly, https://www.googleapis.com/auth/drive, and so on. Ref
When I saw your showing script, it seems that the access token is retrieved using google-spreadsheet. When I saw the script of google-spreadsheet, it seems that this uses only the scope of https://www.googleapis.com/auth/spreadsheets. Ref
From this situation, I thought that the reason for your current issue might be due to this. If my understanding is correct, how about the following modification? In this modification, the access token is retrieved by googleapis for Node.js from the service account. Ref
Modified script:
Google Apps Script side:
function doPost(e) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("Sheet1");
sheet.getRange("A1").setValue("Hello!")
return ContentService.createTextOutput("Success!"); // Modified
}
When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful about this.
You can see the detail of this in the report "Redeploying Web Apps without Changing URL of Web Apps for new IDE".
Node.js side:
const { google } = require("googleapis");
const HTTPS = require("https");
const auth = new google.auth.JWT(
"###", // Please set client_email here.
null,
"###", // Please set private_key here. When you set private_key of service account, please include \n.
["https://www.googleapis.com/auth/drive.readonly"],
null
);
function req(token) {
return new Promise((resolve, reject) => {
const data = { key1: "value1" }; // Please set your value.
const options = {
host: "script.google.com",
path: "/macros/s/{myscriptid}/exec", //<-- my web app path!
method: "POST",
headers: {Authorization: "Bearer " + token},
};
const req = HTTPS.request(options, (res) => {
if (res.statusCode == 302) {
HTTPS.get(res.headers.location, (res) => {
if (res.statusCode == 200) {
res.setEncoding("utf8");
res.on("data", (r) => resolve(r));
}
});
} else {
res.setEncoding("utf8");
res.on("data", (r) => resolve(r));
}
});
req.on("error", (e) => reject(e.message));
req.write(JSON.stringify(data));
req.end();
});
}
auth.getAccessToken().then(({ token }) => {
req(token).then((e) => console.log(e)).catch((e) => console.log(e));
});
When this script is run, when the Web Apps is correctly deployed, the script of Web Apps is run and Success! is returned.
Note:
If this modified script was not useful for your Web Apps setting, please test as follows.
Please confirm whether your service account can access to the Spreadsheet again.
Please share the email address of the service account on the Spreadsheet. From your showing Google Apps Script, I thought that your Google Apps Script is the container-bound script of the Spreadsheet.
Please reflect the latest script to the Web Apps.
When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful about this.
You can see the detail of this in the report "Redeploying Web Apps without Changing URL of Web Apps for new IDE".
When you set private_key of service account, please include \n.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script
Added:
When you will directly put the value to the Spreadsheet using Sheets API with google-spreadsheet module, you can also use the following script.
const { GoogleSpreadsheet } = require("google-spreadsheet");
const sample = async () => {
const doc = new GoogleSpreadsheet("###"); // Please set your Spreadsheet ID.
await doc.useServiceAccountAuth({
client_email: client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,
private_key: process.env.GOOGLE_PRIVATE_KEY,
});
await doc.loadInfo();
const sheet = doc.sheetsByTitle["Sheet1"];
await sheet.loadCells("A1");
sheet.getCell(0, 0).value = "Hello!";
await sheet.saveUpdatedCells();
};
sample();
In this case, your service account is required to be able to access to the Spreadsheet. Please be careful about this.

How to solve Vercel 500 Internal Server Error?

I have created a project that uses MongoDB to store user info and Next-Auth to authenticate users. On local host this is all working seamlessly. Previously I had a couple errors with my next-auth config, but that seems to be working fine now on Vercel live site. Once the user logs in they are redirected to "my-project/suggestions". On this page I am using getServerSideProps to identify if there is a valid session token. If so, data is pulled from a local json file.
On the live site, when the user logs in, the page is redirected to "/suggestions", yet I am receiving an 500 Internal Server Error page. On the function logs I am getting this error message:
[GET] /_next/data/KpsnuV9k44lUAhQ-0rK-B/suggestions.json
10:10:57:12
2022-05-05T14:10:59.270Z 5b7a7375-045f-4518-864b-7968c3c9385f ERROR [Error: ENOENT: no such file or directory, open '/var/task/public/data/data.json'] {
errno: -2,
syscall: 'open',
path: '/var/task/public/data/data.json',
page: '/suggestions'
}
RequestId: 5b7a7375-045f-4518-864b-7968c3c9385f Error: Runtime exited with error: exit status 1
Runtime.ExitError
This is my first project using MongoDB and Next-Auth.. not so sure what the issue is in this case. In my .env.local file I only have these two variables:
NEXTAUTH_SECRET="MUNKNATION"
NEXTAUTH_URL=http://localhost:3000
How I am pulling the data on local host:
export const getServerSideProps = async (context) => {
const session = await getSession({ req: context.req });
if (!session) {
return {
redirect: {
destination: "/",
permanent: false,
},
};
} else {
let filePath = path.join(process.cwd(), "public", "data", "data.json");
let jsonData = await fs.readFile(filePath);
const data = JSON.parse(jsonData);
const inProgressStatusData = data.productRequests.filter(
(item) => item.status == "in-progress"
);
const liveStatusData = data.productRequests.filter(
(item) => item.status == "live"
);
const plannedStatusData = data.productRequests.filter(
(item) => item.status == "planned"
);
let filterData = filteredData(data, "suggestion");
let feedbackData = {
suggestions: filterData,
progress: inProgressStatusData,
planned: plannedStatusData,
live: liveStatusData,
};
return {
props: { session, feedbackData },
};
}
};
Folder structure:
A simple solution to this problem would be to, inside of your getServerSideProps, instead of calling readFile use readFileSync as follows:
export const getServerSideProps = async (context) => {
...
const file = readFileSync(
join(process.cwd(), "public", "data", "data.json"),
"utf8"
);
const data = JSON.parse(fileData);
...
I have tested this solution with Vercel and it works correctly, in development and production mode.

My 'deployed' contract still won't show up on Etherscan. Been about 1.5 hours

need help figuring out why my supposedly 'deployed' contract will still not appear in Etherscan.
Overview:
I used hardhat with following code and got back confirmation:
$ npx hardhat run scripts/deployRoboPunksNFT.js --network rinkeby
RoboPunksNFT deployed to: 0xaBDe0c1A9F7589f21818287287885a2Fef32E3f0
Clearly, it confirms as fully deployed but when I check this contract address at Etherscan (Rinkeby)...nothing: https://rinkeby.etherscan.io/address/0xaBDe0c1A9F7589f21818287287885a2Fef32E3f0
The deployment script used:
const hre = require("hardhat");
async function main() {
const RoboPunksNFT = await hre.ethers.getContractFactory("RoboPunksNFT");
const roboPunksNFT = await RoboPunksNFT.deploy();
await roboPunksNFT.deployed();
console.log("RoboPunksNFT deployed to:", roboPunksNFT.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
My hardhat.config.js
require("#nomiclabs/hardhat-waffle");
const dotenv = require("dotenv");
require("#nomiclabs/hardhat-etherscan");
dotenv.config();
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
const accounts = await hre.ethers.getSigners();
for (const account of accounts) {
console.log(account.address);
}
});
/**
* #type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: "0.8.4",
networks: {
rinkeby: {
url: process.env.REACT_APP_RINKEBY_RPC_URL,
accounts: [process.env.REACT_APP_PRIVATE_KEY]
},
},
etherscan: {
apiKey: process.env.REACT_APP_ETHERSCAN_KEY,
}
};
I got so frustrated that I deployed it again (Code above is 2nd attempt/2nd deployed contract. The first deployed contract address was at 0x9F6040234728493121BCB9A1EaFDBa5E494bB3ed.
Please let me know if anyone sees something that I missed. Hopefully there's enuf info I've submitted here to determine...
Thanks very much!
Problem solved. Rinkeby happened to be down for 6 whole hours. But once it came back up, had 2 freshly deployed contracts on Rinkeby ready to go...

Firestore emulator for testing security rules - running the tests

I have installed the emulator following the instructions at enter link description here and I can start it, so far so good.
After picking some code here and there I have written my first test, here it is:
import * as firebasetesting from '#firebase/testing';
import * as firebase from 'firebase';
import * as fs from 'fs';
const projectId = 'my-firebase-project';
const rules = fs.readFileSync('firestore.rules', 'utf8');
beforeAll(async () => {
// Make your test app load your firestore rules
await firebasetesting.loadFirestoreRules({ projectId, rules });
});
beforeEach(async () => {
// Reset our data from our test database
await firebasetesting.clearFirestoreData({ projectId });
});
after(async () => {
// Shut down all testing Firestore applications after testing is done.
await Promise.all(firebasetesting.apps().map(app => app.delete()));
});
describe("TRACKERS AND ALLIES", () => {
it('TRACKER UP', async () => {
let user = {username: "Bob", uid: 'bobuid'}
let target = { username: "Alice", uid: 'aliceuid'}
const auth = { uid: bob.uid, token: {isadmin: false} };
const app = firebasetesting.initializeTestApp({ projectId, auth }).firestore();
const ref = app.doc('users/'+ user.uid + '/contact/' + target.uid);
await firebasetesting.assertSucceeds(ref.update({ up: true, username: target.uid, timestamp: firebase.firestore.FieldValue.serverTimestamp() }));
});
})
And my question is very simple: how do I run it?
EDIT: I may just add that I am new to Firestore and Javascript in general... The link above simply states
After running a suite of tests, you can access test coverage reports that show how each of your security rules was evaluated.
So I guess it must be simple, but I cannot find the "run" command anywhere...
If you have a nodejs script, run it with node your-script.js. You must have node installed.
If you want to run the script along with the emulator, and shut the emulator down after the script finishes, the page you linked to says:
In many cases you want to start the emulator, run a test suite, and
then shut down the emulator after the tests run. You can do this
easily using the emulators:exec command:
firebase emulators:exec --only firestore "./my-test-script.sh"
If you found the documentation confusing or incomplete, you should use the "send feedback" button at the top right of the page.