How to add subscriber role and publisher role to deadletter for google cloud pubsub using nodeJS? - node.js

We've been following https://cloud.google.com/pubsub/docs/dead-letter-topics and nodeJS client to create, update our pubsub topics and subscriptions but after following:
async function createSubscriptionWithDeadLetterPolicy() {
// Creates a new subscription
await pubSubClient.topic(topicName).createSubscription(subscriptionName, {
deadLetterPolicy: {
deadLetterTopic: pubSubClient.topic(deadLetterTopicName).name,
maxDeliveryAttempts: 10,
},
});
console.log(
`Created subscription ${subscriptionName} with dead letter topic ${deadLetterTopicName}.`
);
console.log(
'To process dead letter messages, remember to add a subscription to your dead letter topic.'
);
}
We get this in the dead-letter
This Suggests running the command in CLI for each dead-letter but we don't want to do it manually for each subscription is there a way to do this in nodeJS client itself?
Or doing so for all the subscriptions once and for all even for the new subscriptions that will be created in given project later on.

According to this part of the documentation, you need to grant 2 roles to the PubSub service agent service account. And of course, you can do it by API calls. And, it's not so easy!
In fact, it's not difficult, just boring! Why? Because, you can't only "add" a policy, you set the whole policies. To achieve this:
Get all the existing policies
Add your policy in the existing list
Submit the new list of policies.
You need to do this:
Either globally by setting the policies at the project level. Easier but less secure (break the least privilege principle)
Or on each Dead Letter topic and on each subscription with Dead Letter set up.
You have code example in the Client library doc
EDIT1
If you script the grant access mechanism, you don't care to find it or not: it exists, that's all! Maybe you don't view it on the console, but it exists. Only the pattern is important:
service-<project-number>#gcp-sa-pubsub.iam.gserviceaccount.com
If you are looking for it on the console, it's tricky now! You have to go to Access -> IAM. And then click on the check box on the top rigth corner to display the Google technical accounts

In case anyone needs it, here are the functions that I made up from #guillaume blaquiere's answer:
private async bindPolicyToSubscriber(
subscriptionTopicName: string,
subscriptionName: string,
) {
if (process.env.PROJECT_NUMBER) {
try {
const pubSubTopic = this.getClient().topic(subscriptionTopicName);
const myPolicy = {
bindings: [
{
role: 'roles/pubsub.subscriber',
members: [
`serviceAccount:service-${process.env.PROJECT_NUMBER}#gcp-sa-pubsub.iam.gserviceaccount.com`,
],
},
],
};
await pubSubTopic
.subscription(subscriptionName)
.iam.setPolicy(myPolicy);
} catch (e) {
console.error('Error while binding policy.', e);
}
}
}
private async bindPolicyToDeadLetterTopic(deadLetterTopicName: string) {
if (process.env.PROJECT_NUMBER) {
try {
const pubSubTopic = this.getClient().topic(deadLetterTopicName);
const myPolicy = {
bindings: [
{
role: 'roles/pubsub.publisher',
members: [
`serviceAccount:service-${process.env.PROJECT_NUMBER}#gcp-sa-pubsub.iam.gserviceaccount.com`,
],
},
],
};
await pubSubTopic.iam.setPolicy(myPolicy);
} catch (e) {
console.error('Error while binding policy.', e);
}
}
}

Related

How can I edit the backend of firebase cloud function extension?

I am coding a project similar to patreon.com and my goal is to let multiple members create their own subscription plans and sell it.
I came across a firebase extension for stripe payments - https://firebase.google.com/products/extensions/stripe-firestore-stripe-payments
The problem with this extension is that I can only create a 1 premium membership that adds custom claims to the auth object and I can validate it like this:
export default async function isUserPremium(): Promise<boolean> {
await auth.currentUser?.getIdToken(true);
const decodedToken = await auth.currentUser?.getIdTokenResult();
return decodedToken?.claims?.stripeRole ? true : false;
}
That means that even if I have 100 different subscriptions, I can only attach single boolean value, which is useless.
I went to the source code and I found this snippet:
// Update their custom claims
if (role) {
try {
// Get existing claims for the user
const { customClaims } = await admin.auth().getUser(uid);
// Set new role in custom claims as long as the subs status allows
if (['trialing', 'active'].includes(subscription.status)) {
logs.userCustomClaimSet(uid, 'stripeRole', role);
await admin
.auth()
.setCustomUserClaims(uid, { ...customClaims, stripeRole: role });
} else {
logs.userCustomClaimSet(uid, 'stripeRole', 'null');
await admin
.auth()
.setCustomUserClaims(uid, { ...customClaims, stripeRole: null });
}
} catch (error) {
// User has been deleted, simply return.
return;
}
}
I don't fully understand this code, but I think this is where the boolean value is assigned.
Would it be possible to somehow edit this source code, so that instead of boolean value, I could store subscription plan ids in Array , so that in the front end I could validate and allow customer to access users content only if he has active plan in that array
?

Google Analytics RunReport return 7 PERMISSION_DENIED: User does not have sufficient permissions for this property

I want to run a google analytics report using runReport.
I have followed the instructions at https://developers.google.com/analytics/devguides/reporting/data/v1/quickstart-client-libraries and made a copy of the code that uses json-credentials at https://github.com/googleapis/nodejs-analytics-data/blob/main/samples/quickstart_json_credentials.js
I have one Analytics Account with two Properties & Apps - DEV and STAGE.
In each of them I have created a service account with OWNER permissions. After that I created a key and downloaded the generated JSON-file.
I can test and run the API from the "Try this API" link at https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/properties/runReport for both my properties and it works well for both of them. (But then I use OAuth authentication of course).
However when I run the code using the JSON-credentials the DEV always work. The STAGE always fails. I have tried running the code both from my machine as well as from https://shell.cloud.google.com/ with the same result.
The failure message is:
7 PERMISSION_DENIED: User does not have sufficient permissions for this property. To learn more about Property ID, see https://developers.google.com/analytics/devguides/reporting/data/v1/property-id.
I have checked several times that the DEV and STAGE properties are correct and that the I use the JSON-credentials file associated with the correct property.
In STAGE, where calls always fail I have several times created new service accounts (with Basic->Owner credentials), created keys and downloaded JSON-credentials. But all with the same result. STAGE always fails.
I have compared the permissions for the service accounts in DEV and STAGE and they seems to be identical.
I have read and tried tips from other people with "permission denied" issues here at stack overflow but none that solves my issue.
Are there any authorization logs that I read from google console?
I'm kind of stuck now.
The code I run (properties and file name obfuscated):
"use strict";
function main(propertyId = "YOUR-GA4-PROPERTY-ID", credentialsJsonPath = "") {
propertyId = "27..DEV property";
credentialsJsonPath = "./DEVcredentials.json";
// propertyId = "27..STAGE property";
// credentialsJsonPath = "./STAGEcredentials.json";
const { BetaAnalyticsDataClient } = require("#google-analytics/data");
const analyticsDataClient = new BetaAnalyticsDataClient({
keyFilename: credentialsJsonPath,
});
async function runReport() {
const [response] = await analyticsDataClient.runReport({
property: `properties/${propertyId}`,
dateRanges: [
{
startDate: "2020-03-31",
endDate: "today",
},
],
dimensions: [
{
name: "country",
},
],
metrics: [
{
name: "activeUsers",
},
],
});
console.log("Report result:");
response.rows.forEach((row) => {
console.log(row.dimensionValues[0], row.metricValues[0]);
});
}
runReport();
}
process.on("unhandledRejection", (err) => {
console.error(err.message);
process.exitCode = 1;
});
main(...process.argv.slice(2));
User does not have sufficient permissions for this property.
Means that the user who you have authenticated with does not have permissions to access that view. In this case we are talking about properties/${propertyId}
If you are using a service account make sure to grant it permissions at the account level not the view level.
Permissions need to be granted thorough the google analytics website in the admin section

google-cloud/resource' cloud function not listing all projects in response

I am using a cloud function written in node.js to list projects this is the index.js file containing the method, When I trigger this function I am getting only 1 project printed. ProjectA -> the cloud function also resides in ProjectA, I have another ProjectB which is not getting printed which is also in ACTIVE mode. I have owner permission for both the projects.
const {Resource} = require('#google-cloud/resource');
const resource = new Resource();
async function getProjects() {
try {
// Lists all current projects
const [projects] = await resource.getProjects();
console.log(`success in getProjects() call`);
// Set a uniform endTime for all the resulting messages
const endTime = new Date();
const endTimeStr = endTime.toISOString();
// sample 2019-11-12T17:58:26.068483Z
for (var i=0; i<projects.length;i++) {
console.log("Total Projects ",projects.length) //Printing as 1 instead of correct 2
// Only publish messages for active projects
if (projects[i]["metadata"]["lifecycleState"] === config.ACTIVE) {
// Construct a Pub/Sub message
console.log(`About to send Pub/Sub message ${projects[i]}}`);
const pubsubMessage = {
"token": config.METRIC_EXPORT_PUBSUB_VERIFICATION_TOKEN,
"project_id": projects[i]["id"],
"end_time": endTimeStr
}
}
}
} catch(err) {
console.error("Error in getProjects()");
console.error(err);
throw err;
}
}
However if i try the google api link
https://cloud.google.com/resource-manager/reference/rest/v1/projects/list#try-it
I am getting 2 projects as response which i have access to.
When you execute a Cloud Function you choose a service account that will execute it, normally it's the "App Engine default service account (project-id#appspot.gserviceaccount.com), that service account should have the "Project Owner" role.
The API call from the API explorer uses an API key that it's tied to your user account no the service account used to execute the Cloud Functions, that's why it shows you all your projects.
To fix your issue just add the service account, that you're using to execute the Cloud Function, to all your Projects with the Project Owner role, although other roles (like Project Viewer) are enough to list it.

Slack bot fails to send message to restricted general channel via chat.postMessage

This is my first time trying to create a slack bot and I am following this template code to the word, I have not made any changes, and just remixed on glitch, copy-pasted the auth tokens correctly, things worked just fine.
That is until I made the #general channel restricted for Full Member users.
This is the error I see in the logs at glitch.
PostMessage Error: restricted_action
Is there an additional scope that I need to set, other than bot ?
Here is the workspace user permissions, I am the owner for this workspace.
Here is the code:
const postAnnouncementToChannel = (user, announcement) => {
const { title, details, channel } = announcement;
let announcementData = {
token: process.env.SLACK_ACCESS_TOKEN,
channel: channel,
text: `:loudspeaker: Announcement from: <#${user}>`,
attachments: JSON.stringify([
{
title: title,
text: details,
footer: 'DM me to make announcements.'
}
])
};
send(announcementData, user);
}
const send = async(data) => {
data.as_user = true; // send DM as a bot, not Slackbot
const result = await axios.post(`${apiUrl}/chat.postMessage`, qs.stringify(data))
try {
if(result.data.error) console.log(`PostMessage Error: ${result.data.error}`);
} catch(err) {
console.log(err);
}
}
Testing it via
https://api.slack.com/methods/chat.postMessage/test
using bot-token says
{
"ok": false,
"error": "restricted_action"
}
Testing this using xoxp-token gives this:-
{
"ok": false,
"error": "missing_scope",
"needed": "chat:write:user",
"provided": "identify,bot"
}
No. You are not missing any scopes. Its just that the user you used to auth your app can not post into the general channel. Apparently admins have restricted who can post messages in that channel, e.g. to admins only.
Either use a user that has posting rights for that channel to auth your app or switch to a different channel for your testing.
Bots are not full members so I had to use user token
xoxp-token
to post to chat.postmessage, with
as_user:false
and had to add a missing_scope that is
chat:write:user
And then I was able to make this work correctly.
Credit goes to #girliemac for helping out on this one.
https://github.com/slackapi/template-announcement-approvals/issues/6
Thanks

How to delay iron router route lookup

Consider the following scenario:
There's a collection called Resources and a special user can CRUD it on an admin site.
When a regular user accesses the site, the routes are created dynamically in the browser depending on a subscription to Resources.
We would like to pause the router lookup until the subscription's callback function terminates. Otherwise the user is presented a "404" when accessing /<dynamically-created-route> directly.
Is there an easy way to do this?
Something like this? You create a dynamic route based on a resource id, waitOn a subscription with the id value, and then route based on whether or not you can access the resource with the id. This assumes you have the proper publishing setup in your server code also.
Resources = new Mongo.Collection("resources");
Router.route("/resources/:_id", {
name: "resources",
waitOn: function() {
// Waits on the subscription
return Meteor.subscribe("resources", this.params._id);
},
action: function() {
// Should only return the resource you subscribed to unless you
// subscribe to resources in another part of your application
var resource = Resources.findOne({});
if (resource) {
// The resource exists and the user is able to access it.
this.render("resourceTemplate", {
data: {
resource: resource
}
});
}
else {
Router.go("/404");
}
}
});

Resources