I want to access Google reseller api to get customers and subscriptions using google service account key but not able to do it. Below is my code snippet:
async function runSample() {
const auth = new google.auth.GoogleAuth({
keyFile: "../server/credentials/serviceAccountKey.json",
scopes: ["https://www.googleapis.com/auth/apps.order",
"https://www.googleapis.com/auth/apps.order.readonly"
],
});
// Acquire an auth client, and bind it to all future calls
const authClient = await auth.getClient();
google.options({ auth: authClient });
// Do the magic
const res = await reseller.subscriptions.list();
console.log(res.data);
}
runSample().catch(console.error);
Here I want to get list of the subscription from google reseller console. I referenced above code from google documentation. Here I am getting the error 'Authenticated user is not authorized to perform this action.' and reason given is 'Insufficient permissions'.
errors: [
{
message: 'Authenticated user is not authorized to perform this action.',
domain: 'global',
reason: 'insufficientPermissions'
}
]
If I try to access cloud channel service api I can using the same service account key but it is giving error for reseller api.
I have given service account the owner, cloud workstation admin and service account admin role access.
I have also added scopes in domain wide delegation(dwd).
What else permission do I need?
In order to use a service account it must first be configured though your google workspace account Create a service account
You must also denote in your code the name of the user who your service account has been configured to impersonate.
const auth = new google.auth.GoogleAuth({
keyFile: "../server/credentials/serviceAccountKey.json",
clientOptions: {
subject: 'user#yourdomain.com'
},
scopes: ["https://www.googleapis.com/auth/apps.order"
],
});
I have been spending hours trying to understand why my ATA address owner suddenly changed to the token program instead of my account address. I am using PDA to transfer the token from the PDA ATA account but unable to do so since the PDA is no longer the ATA owner.
I tried testing Anchor to dissect the problem and to find solutions, here are my tests console logs:
Mint test result:
Mint: A2ojTC6aQZYP6bwUq1FmWN9kwaQTB7NKQmMs89j4FUkx
Sender ATA: 2KcR41e2NxnYY5DWDzvgzHiKpSoaZJ55kvBiqU111DaY
Sender ATA owner: 7QzoE1okkpgsn7Rx5pxyGDkXMSc3nsqhWitDHc6c8rKb
program ATA: H9SEYZsU5ao1WoUNoVTQjVMBbJLNjJmKA5N1cGfjxLqE
Supply: 100
PDA: 10
User: 90
✔ Mint token! (7001ms)
Mint test script:
it("Mint token!", async () => {
mintPubkey = await createMint(
program.provider.connection, // conneciton
user, // fee payer
user.publicKey, // mint authority
user.publicKey, // freeze authority (you can use `null` to disable it. when you disable it, you can't turn it on again)
9 // decimals
);
console.log("Mint:", mintPubkey.toBase58())
let tokenAccountPubkeyUser = await getOrCreateAssociatedTokenAccount(program.provider.connection, user, mintPubkey, user.publicKey)
console.log("Sender ATA:", tokenAccountPubkeyUser.address.toBase58())
let tokenAuth = await program.provider.connection.getAccountInfo(tokenAccountPubkeyUser.address);
console.log("Sender ATA owner:", tokenAccountPubkeyUser.owner.toBase58())
let tokenAccountPubkeyPda = await getOrCreateAssociatedTokenAccount(program.provider.connection, user, mintPubkey, program.programId)
console.log("program ATA:", tokenAccountPubkeyPda.address.toBase58())
let txhash = await mintToChecked(
program.provider.connection, // connection
user, // fee payer
mintPubkey, // mint
tokenAccountPubkeyUser.address, // receiver (sholud be a token account)
user, // mint authority
100e9, // amount. if your decimals is 9, you mint 10^9 for 1 token.
9 // decimals
);
let tokenSupply = await program.provider.connection.getTokenSupply(mintPubkey);
console.log("Supply:", tokenSupply.value.uiAmount)
txhash = await transferChecked(
program.provider.connection, // connection
user, // payer
tokenAccountPubkeyUser.address, // from (should be a token account)
mintPubkey, // mint
tokenAccountPubkeyPda.address, // to (should be a token account)
user, // from's owner
10e9, // amount, if your deciamls is 9, send 10^9 for 1 token
9 // decimals
);
let tokenAmount = await program.provider.connection.getTokenAccountBalance(tokenAccountPubkeyPda.address);
console.log("PDA:", tokenAmount.value.uiAmount)
let tokenAmountUser = await program.provider.connection.getTokenAccountBalance(tokenAccountPubkeyUser.address);
console.log("User:", tokenAmountUser.value.uiAmount)
})
Remove vault test result:
Mint: A2ojTC6aQZYP6bwUq1FmWN9kwaQTB7NKQmMs89j4FUkx
userProfilePDA CanbMWdj5UT8KWCAUwsmMyZFeyG8kWQER2tZQdxTohEK
Last vault: 3
vaultAccountPDA: 8tvqq4zWMGZuoe4tsjuC85WRQY8n5qxZeoyY2Ro7UwGi
vaultInfoPDA: A5E257kztkqdwxeqrjgFzjG2uPmECNX7LD96Vp6Tve7z
tokenProgram: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
receiver ATA: 2KcR41e2NxnYY5DWDzvgzHiKpSoaZJ55kvBiqU111DaY
receiver (user) ATA owner: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
sender (VaultInfo) ATA: BQhNK47ygEYARanGqJnSjKBco3Crot9ihDMJzT8u7yLU
VaultInfo ATA supply: 10
VaultATA owner: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
Program owner: BPFLoaderUpgradeab1e11111111111111111111111
vaultInfoPDA owner: GsCu69BThDsobWHorHkNf8h8zobN6VsexiYkwkH2VtfV
Remove vault test script:
it("User Vault removed!", async () => {
// Add your test here.
// const tx = await program.methods.initializeUser().rpc();
console.log("user:", user.publicKey)
console.log("program:", program.programId)
console.log("token program:", TOKEN_PROGRAM_ID)
console.log("Mint:", mintPubkey.toBase58())
const [userProfilePDA] = await anchor.web3.PublicKey.findProgramAddress([
utf8.encode("USER_STATE"),
user.publicKey.toBuffer(),
],
program.programId
);
console.log("userProfilePDA", userProfilePDA.toBase58());
const userProfile = await program.account.userProfile.fetch(userProfilePDA);
// console.log("UserProfile:", userProfile)
console.log("Last vault:", userProfile.lastVault)
const [vaultAccountPDA] = await anchor.web3.PublicKey.findProgramAddress([
utf8.encode("VAULT_STATE"),
user.publicKey.toBuffer(),
new anchor.BN(0).toBuffer()
],
program.programId
);
console.log("vaultAccountPDA:", vaultAccountPDA.toBase58());
const [vaultInfoPDA] = await anchor.web3.PublicKey.findProgramAddress([
utf8.encode("INFO_STATE"),
// user.publicKey.toBuffer(),
],
program.programId
);
console.log("vaultInfoPDA:", vaultInfoPDA.toBase58());
let tokenAccountPubkeyUser = await getOrCreateAssociatedTokenAccount(program.provider.connection, user, mintPubkey, user.publicKey)
console.log("tokenProgram:", TOKEN_PROGRAM_ID.toBase58())
console.log("receiver ATA:", tokenAccountPubkeyUser.address.toBase58())
let tokenAuth = await program.provider.connection.getAccountInfo(tokenAccountPubkeyUser.address);
console.log("receiver (user) ATA owner:", tokenAuth.owner.toBase58())
let tokenAccountPubkeyVault = await getOrCreateAssociatedTokenAccount(program.provider.connection, user, mintPubkey, vaultInfoPDA, true)
console.log("sender (VaultInfo) ATA:", tokenAccountPubkeyVault.address.toBase58())
let txhash = await transferChecked(
program.provider.connection, // connection
user, // payer
tokenAccountPubkeyUser.address, // from (should be a token account)
mintPubkey, // mint
tokenAccountPubkeyVault.address, // to (should be a token account)
user, // from's owner
10e9, // amount, if your deciamls is 9, send 10^9 for 1 token
9 // decimals
);
let tokenAmount = await program.provider.connection.getTokenAccountBalance(tokenAccountPubkeyVault.address);
console.log("VaultInfo ATA supply:", tokenAmount.value.uiAmount)
tokenAuth = await program.provider.connection.getAccountInfo(tokenAccountPubkeyVault.address);
console.log("VaultATA owner:", tokenAuth.owner.toBase58())
tokenAuth = await program.provider.connection.getAccountInfo(program.programId);
console.log("Program owner:", tokenAuth.owner.toBase58())
tokenAuth = await program.provider.connection.getAccountInfo(vaultInfoPDA);
console.log("vaultInfoPDA owner:", tokenAuth.owner.toBase58())
const tx = await program.rpc.removeVault(0, {
accounts: {
authority: user.publicKey,
userProfile: userProfilePDA,
vaultAccount: vaultAccountPDA,
vaultInfo: vaultInfoPDA,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
from: tokenAccountPubkeyVault.address,
to: tokenAccountPubkeyUser.address,
owner: vaultInfoPDA,
// sender: vaultInfoPDA
},
signers: []
})
console.log("Your transaction signature", tx);
});
I am expecting to use the vaultInfoPDA (which should be the owner of the ATA) to send the token out to user removing their vault.
Thanks!
The term owner is overloaded in the context of SPL tokens, which often causes confusion.
When you're logging the owner of the account after calling getAccountInfo, that gives you the program that owns the account, which must be the SPL Token program. The SPL Token program has the right to change the data in the account.
Within that account is also data. In that data, bytes 32-64 define the pubkey that can authorize movements from the account, the SPL token "owner". So there's two owners in one account, one defined by the Solana runtime, and another defined by the SPL Token program.
You can read more about the ownership model at https://docs.solana.com/developing/programming-model/accounts#ownership-and-assignment-to-programs
What I would like to achieve is to make the user use the solana program for "free" and make the company pay for the transaction, what I have in mind is:
Extrapolate the transaction in the frontend
Send it to my backend server through an api
Use the wallet that I have on my BE to sing and set this wallet as payer
Send back the transaction
Sign the transaction with the user that is interacting with the FE
Send the transaction from the FE to the solana program.
Let's consider the hello world example https://github.com/solana-labs/example-helloworld
export async function sayHello(): Promise<void> {
console.log('Saying hello to', greetedPubkey.toBase58());
const instruction = new TransactionInstruction({
keys: [{pubkey: greetedPubkey, isSigner: false, isWritable: true}],
programId,
data: createSetInstruction()
});
console.log(instruction)
await sendAndConfirmTransaction(
connection,
new Transaction().add(instruction),
[payer],
);
}
I guess that in some way I could extrapolate the transaction before the sendAndConfirmTransaction
How can I achieve that and the sign it with my Backend wallet?
Update
In order to manage this problem, I started developing this service: cowsigner.com
You have the entire flow correct, so what you would do is:
const transaction = new Transaction(... your instructions here ...);
const wireTransaction = transaction.serialize();
// send wireTransaction to backend to be signed
// on backend:
const latestBlockhash = await connection.getLatestBlockhash();
transaction.lastValidBlockHeight = latestBlockhash.lastValidBlockHeight;
transaction.recentBlockhash = latestBlockhash.blockhash;
transaction.feePayer = backendKey.publicKey;
transaction.partialSign(backendKey);
const wireTransactionToSendBack = transaction.serialize();
// send back wireTransactionToSendBack to frontend to be signed by user
// back on frontend:
transaction.partialSign(userKey); // or use a wallet adapter, most likely
const finalWireTransaction = transaction.serialize();
const signature = await connection.sendRawTransaction(finalWireTransaction);
I'm trying to see how you can get the "ownerof" token like on Ethereum but on the Solana blockchain instead.
For example, I want a user to access a certain part of the website only if they have x token on their phantom wallet (or other Solana wallet).
If you want to understand if a user owns a specific token, you'll have to check if they current own that specific mint's token account and have a balance > 0.
Code would be as follows checking amount that address GKNcUmNacSJo4S2Kq3DuYRYRGw3sNUfJ4tyqd198t6vQ owns of USDC:
import {clusterApiUrl, Connection, Keypair, LAMPORTS_PER_SOL, ParsedAccountData, PublicKey} from '#solana/web3.js';
import {ASSOCIATED_TOKEN_PROGRAM_ID, Token, TOKEN_PROGRAM_ID} from "#solana/spl-token";
const publicKey = new PublicKey("GKNcUmNacSJo4S2Kq3DuYRYRGw3sNUfJ4tyqd198t6vQ");
const mint = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
const connection = new Connection(clusterApiUrl('mainnet-beta'));
const associatedAddress = await Token.getAssociatedTokenAddress(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
mint,
publicKey
);
console.log(associatedAddress.toBase58());
const tokenAccountInfo = await connection.getParsedAccountInfo(associatedAddress);
console.log((tokenAccountInfo.value?.data as ParsedAccountData).parsed.info.tokenAmount.amount);
I am still struggling with Google's terminology of apis and services but my goal is to have automated functions via aws lambda which act on a G Suite Account (domain?) or more specific on users of this domain.
For now I just want to list all users of that domain. I run this code locally for testing.
What I have done:
I created a service account
I downloaded the json key file which contains the private key, private key id and so on
I enabled G Suite Domain-wide Delegation.
I delegated domain-wide authority to the service account from the GSuite Account
I added the following scopes for the client in the GSuite Admin Console:
https://www.googleapis.com/auth/admin.directory.group
https://www.googleapis.com/auth/admin.directory.user
This is the implementation:
const { google } = require("googleapis");
const auth = new google.auth.GoogleAuth({
keyFile: "credentials.json",
scopes:
"https://www.googleapis.com/auth/drive.readonly,https://www.googleapis.com/admin/directory/v1, https://www.googleapis.com/auth/admin.directory.group, https://www.googleapis.com/auth/admin.directory.user",
});
const service = google.admin({ version: "directory_v1", auth });
service.users.list(
{
domain: "my.domain.com",
maxResults: 10,
orderBy: "email",
},
(err, res) => {
if (err) return console.error("The API returned an error:", err.message);
const users = res.data.users;
if (users.length) {
console.log("Users:");
users.forEach((user) => {
console.log(`${user.primaryEmail} (${user.name.fullName})`);
});
} else {
console.log("No users found.");
}
}
);
I am not sure why I have to add the scopes in the GoogleAuth object but I took this from the google documentation.
When I run this I get the following error:
The API returned an error: invalid_scope: Invalid OAuth scope or ID token audience provided.
The Directory API can only be used by admins
A Service account is not an admin
If the service account shall act on behalf on the admin, you need to
enable G Suite Domain-wide Delegation (as you already did)
impersonate the service account as the admin by setting the user to be impersonated
In general, when you are using a service account you need to build the authentication flow, as explained in the documentation, that is you need to create JSON Web Token (JWT) specifying the user to impersonate.
A sample code snippet for Javascript:
const jwtClient = new google.auth.JWT(
privatekey.client_email,
null,
privatekey.private_key,
scopes,
user // User who will be impersonated (needs to be an admin)
);
await jwtClient.authorize();
return jwtClient;