Authenticate to google cloud as a service account on browser - javascript

I am trying to migrate my google cloud app from Nodejs to native JavaScript, such that it can be run in the browser. However, I can't seem to find any examples of how to authenticate as a service account in the browser. Authenticating in Nodejs looked like this:
const textToSpeech = require('#google-cloud/text-to-speech');
const {Storage} = require('#google-cloud/storage');
const projectId = 'project'
const keyFilename = 'key.json'
const storage = new Storage({projectId, keyFilename});
const client = new textToSpeech.TextToSpeechClient({projectId, keyFilename});
In all of the searching I've done, I've only ever found solutions that use API keys and Client IDs. Additionally, all of these solutions prompt for a user to login, which is not what I want. I'd like to do exactly what the Nodejs code is doing, but in native browser JavaScript.

google-auth-library allows loading creds from hardcoded text:
const {auth} = require('google-auth-library');
const {Storage} = require('#google-cloud/storage');
const authClient = auth.fromJSON({
"type": "service_account",
"project_id": "your-project-id",
"private_key_id": "your-private-key-id",
"private_key": "your-private-key",
"client_email": "your-client-email",
"client_id": "your-client-id",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "your-cert-url"
});
const storage = new Storage({authClient});
However you should be careful. Embedded credentials can be extracted, and then anyone can make any call as this service account. Theoretically you can be safe with carefully restricted access, but this is easy to make a mistake.
For GCS you are likely better off with Signed URLs or some other solution that keeps credentials on the server.

Related

How can I make a script in python to reload an app in qlik cloud

At this moment I'm trying to reload a Qlik app through a python script, but I had a few problems, I'm gonna explain the things I already tested, but if someone knows how can I solve that, I will appreciate it a lot
The first thing I tried, was the "reloads API" from qlik, to do this you have to send a request with the app id, and with the parameter "Partial" true or false, but even more important, you have to send with this request a JSON web token, so I was searching how to obtain the jwt of qlik and I found this page: "https://qlik.dev/tutorials/create-signed-tokens-for-jwt-authorization", I created all as the page said, and finally I make this code in javascript to test, but this doesn't work:
const fs = require('fs');
const uid = require('uid-safe');
const jwt = require('jsonwebtoken');
const https = require('https')
const payload = {
jti: uid.sync(32), // 32 bytes random string
sub: '(id of my user that appears in assignment users)',
subType: 'user',
name: '(Name of my user)',
email: '(email of my user)',
email_verified: true,
};
const privateKey = fs.readFileSync("path/certificate.pem");
// I don't know the meaning of that 'kid and issuer have to match with the IDP config'
// audience has to be qlik.API/jwt-login-session
const signingOptions = {
keyid: I put = 'my-custom-jwt',
algorithm: I put = 'RS256',
issuer: '(hostname)',
audience: I put = 'qlik.api/login/jwt-session',
};
const myToken = jwt.sign(payload, privateKey, signingOptions);
const qlikUrl = "(hostname)"
const data = JSON.stringify({"appId": "(appId)", "partial": true})
const options = {
hostname: qlikUrl,
port: 443,
path: '/api/v1/reloads',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer '+ myToken
}
}
https.request(options)
req.write(data)
The second thing I did was, try to use the "qsAPI" library of python to connect to my qlik and reload an app specifying their id by a method of this library, but I don't understand what I have to put here, and in the documentation, doesn't say nothing about what's the meaning of the parameters, except ('hostname'). The problem is in the method to do the connection because I have to do this:
qrs = qsAPI.QRS(proxy='hostname', user=('yor_domain', 'username', 'password'))
I say that because if I go to the python IDE, this show which parameters I have to put, and these are different, now I have to put this:
qrs = qsAPI.QRS(proxy='hostname', user=('userDirectory', 'userId', 'password'))
I don't know where I can find the user directory because I don't know
what it is.
The user id (I'm guessing it's the user id that appears in "mapping
users").
The "password", is no problem
But still have an opportunity, because I can connect to Qlik through python if I find where I can download the certificate authentication of a user, but I don't know where is it in qlik cloud.
Can someone help me, please
I havent tried with Python (only with JS/TS) but the approach should be the same.
Couple of things:
imo instead of web token you can try with API Key (for a start). Managing API keys -> Generating an API key from the hub.
the second point is that qsAPI seems to be for Qlik Sense Enterprise on Windows and not for SaaS.
the only Python lib dedicated for Qlik SaaS (that im aware of) is qsaas. Havent used it myself ... just found it on GH
and a bit of a warning regarding the partial: true. Please make sure that you really have to use partial reload. Partial reloads have a specific use case and be careful when using them ... just saying :)
I finally have the solution of that as Stefan Stoichev said before publishing this post, the qsApi seems to be a python library for Qlik Sense Enterprise on Windows, and the correct library for the Qlik Cloud is qsaas, but I'm going to explain every step because I don't want that any person of this world suffer this.
First of all, you have to create an API KEY, in Qlik Cloud, IMPORTANT, you have to save the api_key code that appears in a green text box when you created successfully your API KEY, save this as your dear friend because you will need this code in the future
Subsequently, you have to create a new python code as this:
from qsaas.qsaas import Tenant
import JSON
api_key = <API_KEY>
q = Tenant(api_key=api_key, tenant=<hostname>,
tenant_id=<tenant_id>)
q.post('reloads', json.dumps({'appId': 'dbf3e4ce-c6b3-4190-876c-c443a8691fa6'})))
Don't worry my dear friend if you don't know where is it, the 'hostname' and the 'tenant_id' are in qlik cloud, here is a little tutorial for you:
First login to your qlik
Then click on your profile photo
Click in about, and there you have these two data information

Firebase multiple real time databases, custom auth issues

I'm attempting to use multiple real time databases for my single project in Firebase. For my setup, I'm generating tokens and sending to users from a server that is not part of Google's servers.
Everything works so far for the default database, but issues arise when attempting access the second real time database. I have read that it might be because you have to generate a token for each app instance, but to avoid becoming a difficult process, is it possible to generate a single token that can access all databases?
On my backend server we do this to generate a token:
initializeApp({
credential: admin.credential.cert(FirebaseCredentials),
});
usually the next line is a databaseURL. What is passed the service account JSON file.
Is there an easy way to allow a user to use one token to access all databases if we had 10 of them or is the solution to initalizeApp and specify a different database each time with 10 different tokens if the data is spread across 10 different real time databases.
Client side I used the generated token to sign up. The generated token does not specify a databaseURL and appears to use the default database. When I attempt to use the second database, I tried to follow the multi database instructions.
const app2 = initializeApp({ databaseURL: '...'}, 'app2');
const db = getDatabase(app2);
set(ref(db, 'users'), {
user: 'me',
});
When I use that second one, I get hit with permissions denied. The rules are identical between rtdbs. I'm guessing its because my original app config token is for the default database and not for the new one? Is it possible to somehow use a single generated token for all databases?
Firebase Rules for both databases.
{
"rules": {
".read": "auth.token.chatEnabled === true",
".write": "auth.token.chatEnabled === true"
}
}
Here is how I use the token on client side. I simply pass the token back from the server. Client uses the Javascript SDK to initialize app with the firebase config generated from initial startup.
Then I do:
import { initializeApp } from "firebase/app";
import { getDatabase, ref, set } from 'firebase/database';
const firebaseConfig = {
... the config
};
const app = initializeApp(firebaseConfig);
const auth = getAuth();
await signInWithCustomToken(auth, token);
const app2 = initializeApp({
databaseURL: 'secondatabaseurl'
}, 'app2');
const db = getDatabase(app2);
set(ref(db, 'users'), {
user: 'me',
});
I believe my issue arises because when I go to call the second app, the token generated appears to be only usable for the first app or default config.
If all of my databases are on a single project, do I have to create a separate app for each new database? If so, would that mean I need a separate token for each app instance or to sign into each app instance with the same token?
Thank you for your time.
Not the answer, but I can initialize 2 different apps.
This works ->
const secondaryApp = initializeApp({ appconfig2 }, 'secondaryApp');
const auth = getAuth(secondaryApp);
const db = getDatabase(secondaryApp);
await signInWithCustomToken(auth, token);
set(ref(db, 'users'), {
user: 'me',
});
This does not work ->
const secondaryApp = initializeApp({ appconfig2 }, 'secondaryApp');
const auth = getAuth(secondaryApp);
await signInWithCustomToken(auth, token);
const db = getDatabase(secondaryApp);
set(ref(db, 'users'), {
user: 'me',
});
// now try a second database
const db2 = getDatabase();
set(ref(db2, 'users'), {
user: 'me',
});
From my understanding, getDatabase() pulls from the default initalizedApp. It appears that once a user has authenticated using one of the initializedApps (in this case the user initialized with secondaryApp), those authenticated details are not shared with other apps at least I'm not exactly sure how to share them with all apps declared. Even if the app configs have identical api keys, auth domains, etc but differ on the DatabaseURL, they do not somehow just connect.
Ideally once a user has authenticated, it should be that they have access to all databases in the single project/app, but this does not seem to be the case here.
The last response here might be the answer ->
Got permission denied when writing to the second instance of Firebase realtime database
So in order to access another database, a user must authenticate individually?
Okay so that link at the end was the answer, I just didn't expect to have to do this.
If you specify auth for each database, since the databases are declared in different apps, you have to authenticate the user with each one before being able to read/write.
In other words, to make it work I had to signInWithCustomToken per each database instance.
const app1 = initializeApp(config1);
const app2 = initializeApp(config2);
const auth1 = getAuth(app1);
const auth2 = getAuth(app2);
await signInWithCustomToken(auth1, token);
await signInWithCustomToken(auth2, token);
then you can use each app instance to getDatabase(app1/app2).
Maybe the reason for this is that people can use completely different apps. Maybe there is some way in future to make it so that one app with multiple sharded real time databases do not require authentication into each app database. Maybe it would be instead of declaring a new app for each sharded db, it should be some other helper function.

How to impersonate a Google Workspace account through ADC on Google Cloud Functions?

What I'm trying to do is to:
Create a service account
Give that service account Domain Wide Delegations
Use the Application Default Credentials to impersonate a Google Workspace user and access it's email with gmail API
My code works fine in local development, by using the credential key json file generated for the Service Account mentioned in step 1, through Application Credentials Default mechanism (I have set up the environment variable GOOGLE_APPLICATION_CREDENTIALS to the path of the credentials.json)
import {google} from 'googleapis'
const scopes = [
'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/gmail.modify',
];
const auth = new google.auth.GoogleAuth({
clientOptions: {
subject: 'user#email.com',
},
scopes,
});
const gmail = google.gmail({
version: 'v1',
auth,
});
const list = await gmail.users.messages.list({
userId: 'me',
maxResults: 10,
});
The problem is AFAIK, in local environment (aka not GCE) GoogleAuth uses JWT and in GCE it uses Compute Auth method, where a subject can't be configured or if configured is ignored.
So that's why when deploying the Cloud Function it throws an error about Precondition Check Failed and nothing else more specific.
In my limited knowledge and research I think the solution would be to somehow convert the Compute Auth -> JWT with a subject defined.
The current solution I have implemented and works, consists in saving the credentials.json into the Google Secret Manager:
// Acquire credentials from secret manager:
const secret = await getApiKeyFromSecretManager('SECRET_NAME');
const jsonCreds = JSON.parse(Buffer.from(secret).toString());
// Create the JWT
const auth = google.auth.fromJSON(jsonCreds);
auth.subject = 'user#email.com';
auth.scopes = scopes;
But I'm not really comfortable having to access or save the credentials in the Secret Manager, as I think the solution is not as elegant as it could be.

Sending transaction to Smart Contract (myContract.methods.myMethod.send() ) returns error: Unknown Account on mobile Wallets

The Dapp sends Bep20 token from a remote wallet(My wallet) to users after they perform a certain activity such as accepting BNB to be sent to ICO, it works well on a desktop in which metamask is installed. The code that executes this is:
var amountTosend = amount;
var privateKey = 'PRIVATE_KEY';
var accountFrom = web3provider.eth.accounts.privateKeyToAccount(privateKey).address;
var contractCall = contractInstance.methods.transfer(addressTo, "0x"+amountTosend.toString(16));
var icoAccount = web3provider.eth.accounts.privateKeyToAccount(privateKey);
web3provider.eth.accounts.wallet.add(icoAccount);
contractCall.send({ from: accountFrom, gas: 2000000 }).then(function(hashdata) {
console.log(hashdata);
var rawTransaction = {
"from": accountFrom,
"nonce": nonce,
"gasPrice": 20000000000,
"gas": gas,
"to": SmartContractAddress,
"value": "0x" + amountTosend.toString(16),
"data": hashdata.events.Transfer.raw.data,
"chainId": chainid
};
var privKey = new ethereumjs.Buffer.Buffer(privateKey, 'hex');
let tx = new ethereumjs.Tx(rawTransaction);
tx.sign(privKey)
let serializedTx = tx.serialize();
console.log('serializedTx:', serializedTx);
web3provider.eth.sendSignedTransaction('0x' + serializedTx.toString('hex'), function(err, receipt) {
if (!err) {
console.log('success token sent to user: ' + receipt);
alert('You have received your Tokens in your Wallet');
} else {
console.log('error: ' + err);
alert("An error Occured: " + err);
}
});
})
On mobile wallets such as trust and metamask mobile connected with web3modal using wallet connect as provider, I successfully interacted with the wallet, prompt user to send BNB but
contractCall.send({ from: accountFrom, gas: 2000000 })
which is used to change the state of the smart contract to send user Bep20 token fails with Unknown account error after adding remote wallet to local using:
web3provider.eth.accounts.wallet.add(icoAccount);
Note: If I try skipping
contractCall.send({ from: accountFrom, gas: 2000000 })
and use contractCall.encodeABI() for data parameter, it works fine but no token is actually sent to the user and I guess is because the smart contact state needs to be changed/altered with
myContract.methods.myMethod([param1[, param2[, ...]]]).send
Please I need any useful help/tip to make this work on mobile wallet, I have been on this bug for a few days now, thanks
I finally fixed this error, and I will like to answer comprehensively for future visitors. The short answer will come first to implement the solution, and the long comprehensive answer afterward to help you understand better and learn the technicalities of fixing the bug
Note: I am using walletconnect and web3modal under the hood for my DAPP on Mobile browser and Metamask for Desktop and I'm using vanilla javascript for web3 implemenation
Short Answer:
I had to set givenProvider to the currentProvider which was initialized on web3 instance and that was my RPC URL provider, for me my RPC URL provider is: https://getblock.io/ which was passed in walletconnect/web3modal provider options paramater
I'm connecting to BSC so my RPC URL is: https://bsc.getblock.io/mainnet/?api_key=API_KEY
You may use other RPC URL providers of your choice
So the givenProvider is set like this:
web3.givenProvider = web3.currentProvider;
web3.eth.givenProvider = web3.currentProvider;
web3.eth.accounts.givenProvider = web3.currentProvider;
read walletconnect docs on setting provider(s) here: https://docs.walletconnect.com/quick-start/dapps/web3-provider
Setting the web3.givenProvider to web3.currentProvider fixed the issue as web3.givenProvider was null
Long Answer:
You need to understand what is currentProvider and givenProvider are, as the bug resolves around the web3 provider.
In my understanding as of writting this answer givenProvider is the provider injected in an Ethereum enabled browser while currentProvider is the provider that you passed in on the initialization of web3.
The error is: Unknown account
This problem doesn't appear on a desktop browser where metamask is installed because injects the givenProvider, except of course is a remote account that doesn't exist on the provider node
You have to add the account to the provider node using:
var account= web3provider.eth.accounts.privateKeyToAccount(PRIVATE_KEY);
var addAccount = web3provider.eth.accounts.wallet.add(account);
This solves the error on desktop browser without having to configure or set up much and that is because masks makes your browser Ethereum enabled and injects givenProvider
But for mobile browsers, you will face this issue because mobile browsers aren't Ethereum enabled environment. You will have to set givenProvider to a valid provider after initializing web3 through walletconenct.
Note: givenProvider talks to the blockchain through the web and it is null on mobile browsers. Console.log(web3) will show you the values of web3 instance
Putting all together your code should look like this:
import WalletConnectProvider from "#walletconnect/web3-provider";
import Web3 from "web3";
// Create WalletConnect Provider
const provider = new WalletConnectProvider({
rpc: {
1: "https://mainnet.mycustomnode.com",
3: "https://ropsten.mycustomnode.com",
100: "https://dai.poa.network",
// ...
},
});
await provider.enable();
// Create Web3 instance
const web3 = new Web3(provider);
//FIX STARTS HERE
//set givenProvider to currentProvider
web3.givenProvider = web3.currentProvider;
web3.eth.givenProvider = web3.currentProvider;
web3.eth.accounts.givenProvider = web3.currentProvider;
//add wallet to provider node
var account= web3.eth.accounts.privateKeyToAccount(PRIVATE_KEY);
var addAccount = web3.eth.accounts.wallet.add(account);
var accountFrom = account.address;
var gas = contractCall.estimateGas({ from: accountFrom });
gas.then(function(gasTouse) {
contractCall.send({
from: accountFrom, gas: gasTouse }).then(function(hashdata){
console.log(hashdata);
});
});
Note: Be sure you estimate gas and include it, as without it you may encounter some gas-related errors.
Happy coding :)

How do I update a google calendar event using Node js?

I'm following the google calendar documentation to update an event via a service account here. Unfortunately, besides describing the initial setup with node in mind (which still doesn't show the process via a service account) google doesn't seem to include the node flavor in any further documentation.
My authentication is setup below:
//import google library
const {google} = require("googleapis")
//setup scope of auth
const scopes = ['https://www.googleapis.com/auth/calendar.events'];
//load client secrets from a local file
let credentialsRaw = fs.readFileSync(process.env.GOOGLE_CALENDAR_SECRET);
//get credentials as json parsed from raw file contents
let credentials = JSON.parse(credentialsRaw);
//setup auth obj
let auth = new google.auth.JWT(
credentials.client_email, null,
credentials.private_key, scopes
);
Followed by my code that tries to update the event:
//setup calendar object
const calendar = await google.calendar({
version: "v3",
auth
});
//make call to update calendar event
let updateResults = await calendar.events.update({
auth: auth,
calendarId: req.body.calendar_id, //log looks like: i798978asdfjka678sagsdfv3344#group.calendar.google.com
eventId: req.body.event_id, //log looks like: 12432dsfkjhhwqejhkj12
resource: {
"summary": "Test Summary",
"description": "Test Description",
"end": req.body.end_time, //log for end and start look like: { dateTime: '2021-08-09T11:30:00-04:00', timeZone: 'America/New_York' }
"start": req.body.start_time,
}
});
The error I get is "Not found". That's it. I've tried encoding the calendarId with encodeURIComponent() according to this answer but that didn't change anything. The poster of that answer also mentioned later that the encoding fix isn't needed anymore.
Some common issues on the account side I think I've handled correctly:
The project that the service account is in has the calendar api enabled
The service account has domain wide authority
The following should show you how to authorize the service account.
const {google} = require('googleapis');
const auth = new google.auth.GoogleAuth({
keyFile: '/path/to/your-secret-key.json',
scopes: ['https://www.googleapis.com/auth/calendar.events'],
});
const service = google.calendar({
version: 'v3',
auth: auth
});
For more info see service-account-credentials
Not found
Error normally means that you do not have access to the calendar you are trying to access. I would check to be sure the delegation was setup properly to the calendar. Try testing with a calendar.get to be sure you have access.

Categories