How to change google profile picture via API? - node.js

I am looking for a way how to change user profile photo programmatically.
Generally, I need to be able to change avatars of domain GSuite users, but if there are API for changing my own avatar is also good.
Had tried already:
https://developers.google.com/admin-sdk/directory/v1/reference/users/photos/update - this API is working but only changes the small icon of a user in admin panel itself. The main profile picture remains the same.
https://developers.google.com/people/api/rest/v1/people/updateContactPhoto - this API throws me an error Resource name "people/10244712xxxxx1564465" is not a valid contact person resource. when I am trying to replace my own profile image. people/me also isn't working.
There are Contacts API, but I am trying to change my own (or impersonated one) profile image, so I believe this API isn't for my case.
If you can point me in some direction to search I'll be very glad. I just do not believe that there is no way to change avatar, it can't be for real.
Update:
Checked Contact API - also doesn't work, looks like it changes an avatar only on me in my contact list, nobody else doesn't see changes, and the main profile picture remains the same.
Code:
// admin directory way
const { google } = require('googleapis');
const SafeBase64 = require('url-safe-base64');
const GOOGLE = require(process.env.GOOGLEAPIS_VAULT);
const encodedPhoto = SafeBase64.encode('R0lGODdhBAAEAIABAAMA/////ywAAAAABAAEAAACBkRiYJgHBQA7');
const JWTClient = new google.auth.JWT(
GOOGLE.client_email,
null,
GOOGLE.private_key,
[
'https://www.googleapis.com/auth/admin.directory.user',
],
'gsync#***.com', // sub
);
const directory = google.admin({ version: 'directory_v1', auth: JWTClient });
directory.users.photos.update({}, ctx.params.email);
const res = await directory.users.photos.update({
userKey: ctx.params.email,
resource: {
photoData: encodedPhoto,
},
});
// Contacts API way
const { google } = require('googleapis');
const SafeBase64 = require('url-safe-base64');
const GOOGLE = require(process.env.GOOGLEAPIS_VAULT);
const JWTClient = new google.auth.JWT(
GOOGLE.client_email,
null,
GOOGLE.private_key,
[
'https://www.google.com/m8/feeds/',
],
ctx.params.email, //sub
);
const res1 = await JWTClient.requestAsync({
headers: {
'GData-Version': 3.0,
},
params: {
alt: 'json',
q: 'arsenyp#***.com',
},
url: `https://www.google.com/m8/feeds/contacts/${ctx.params.email}/full`,
});
const contactIdFull = res1.data.feed.entry.filter((c) => c.gd$email[0].address === ctx.params.email)[0].id.$t;
const [, contactId] = /\/base\/([a-z0-9]+)$/.exec(contactIdFull);
// https://www.google.com/m8/feeds/contacts/{userEmail}/full/{contactId}
const res2 = await JWTClient.requestAsync({
headers: {
'GData-Version': 3.0,
},
params: {
alt: 'json',
},
url: `https://www.google.com/m8/feeds/contacts/${ctx.params.email}/full/${contactId}`,
});
const { href: image, gd$etag: etagJ } = res2.data.entry.link.filter((l) => l.rel === 'http://schemas.google.com/contacts/2008/rel#photo')[0];
const res3 = await axios({
method: 'GET',
url: image,
headers: {
Authorization: `Bearer "${(await JWTClient.getAccessTokenAsync()).token}"`,
},
responseType: 'arraybuffer',
});
const etag = JSON.parse(etagJ);
// PUT /m8/feeds/photos/media/default/contactId
// If-match: Etag
// Content-Type: image/*
const res4 = await axios({
method: 'PUT',
url: `https://www.google.com/m8/feeds/photos/media/default/${contactId}`,
headers: {
Authorization: `Bearer "${(await JWTClient.getAccessTokenAsync()).token}"`,
'Content-Type': 'image/png',
},
// responseType: 'arraybuffer',
data: Buffer.from('R0lGODdhBAAEAIABAAMA/////ywAAAAABAAEAAACBkRiYJgHBQA7', 'base64'),
});
// People API way (won't work, throwing an error)
const userId = '1024471xxxxx251564465';
const JWTClient = new google.auth.JWT(
GOOGLE.client_email,
null,
GOOGLE.private_key,
[
'https://www.googleapis.com/auth/contacts',
'profile',
],
ctx.params.email, // sub
);
const people = google.people({ version: 'v1', auth: JWTClient });
const res = await people.people.updateContactPhoto({
resourceName: `people/${userId}`,
resource: {
photoBytes: SafeBase64.encode('R0lGODdhBAAEAIABAAMA/////ywAAAAABAAEAAACBkRiYJgHBQA7'),
personFields: 'photos',
},
sources: [
'READ_SOURCE_TYPE_PROFILE',
],
});

Ok, so as #Sudhakar mention there is no such API.
But I've found that admin directory API actually changes the profile in most places.
The trick in that the user needs manually once remove the avatar from https://aboutme.google.com/ Profile picture because this photo has top priority against admin directories one.
If the user removes the photo from aboutme.google.com then changes it via admin directory v1 API this photo is seen in the calendar, Gmail, contacts...
So check my first code sample:
// admin directory way
const { google } = require('googleapis');
const SafeBase64 = require('url-safe-base64');
const GOOGLE = require(process.env.GOOGLEAPIS_VAULT);
const encodedPhoto = SafeBase64.encode('R0lGODdhBAAEAIABAAMA/////ywAAAAABAAEAAACBkRiYJgHBQA7');
const targerUser = 'arsenyp#***.com';
const JWTClient = new google.auth.JWT(
GOOGLE.client_email,
null,
GOOGLE.private_key,
[
'https://www.googleapis.com/auth/admin.directory.user',
],
'gsync#***.com', // sub - this is service user with proper permissions, not a target user
);
const directory = google.admin({ version: 'directory_v1', auth: JWTClient });
directory.users.photos.update({}, targerUser);
const res = await directory.users.photos.update({
userKey: targerUser,
resource: {
photoData: encodedPhoto,
},
});

Related

Question about the Fastlane tool regarding authentication in App Store Connect Enterprise account using 2fa

I am trying to rewrite the implementation of the Fastlane authentication in the App Store Connect Enterprise account using 2fa with Node.js . So far, I have managed to get and input a one-time password from the SMS and to get the session out of it. The relevant code is presented below:
Node.js with Typescript rewritten code
let authServiceKey = "";
try {
//sending a get request in order to get the authService (This works as intended)
const authService = await axios.get("https://appstoreconnect.apple.com/olympus/v1/app/config?hostname=itunesconnect.apple.com");
authServiceKey = authService.data\["authServiceKey"\];
//This request tries to sign in the and gets expected 409 error status, with some useful response data
await axios({method: 'post', url: 'https://idmsa.apple.com/appleauth/auth/signin', headers: {
// eslint-disable-next-line #typescript-eslint/naming-convention
'Content-Type': 'application/json',
// eslint-disable-next-line #typescript-eslint/naming-convention
'X-Requested-With':'XMLHttpRequest',
// eslint-disable-next-line #typescript-eslint/naming-convention
'X-Apple-Widget-Key': authServiceKey
},
data: {
accountName: process.env.APP_STORE_CONNECT_ENTERPRISE_ACCOUNT_NAME,
password: process.env.APP_STORE_CONNECT_ENTERPRISE_PASSWORD,
rememberMe: true
}
});
} catch (e:any) {
try{
const response = e["response"];
const headers = response["headers"];
const xAppleIdSessionId:string = headers["x-apple-id-session-id"];
const scnt:string = headers["scnt"];
const authService = await axios.get("https://appstoreconnect.apple.com/olympus/v1/app/config?hostname=itunesconnect.apple.com");
const authKey = authService.data["authServiceKey"];
const authenticationHeaders = {
// eslint-disable-next-line #typescript-eslint/naming-convention
'Content-Type': 'application/json',
// eslint-disable-next-line #typescript-eslint/naming-convention
'X-Apple-Id-Session-Id': xAppleIdSessionId,
// eslint-disable-next-line #typescript-eslint/naming-convention
'X-Apple-Widget-Key': authKey,
// eslint-disable-next-line #typescript-eslint/naming-convention
"Accept": "application/json",
"scnt": scnt
}
const authenticationResult = await axios({method: "get", url: "https://idmsa.apple.com/appleauth/auth", headers:authenticationHeaders });
const phoneId= authenticationResult.data["trustedPhoneNumbers"][0]["id"];
const pushMode = authenticationResult.data["trustedPhoneNumbers"][0]["pushMode"];
const body = {
phoneNumber: {
id: phoneId,
},
mode: pushMode
}
await axios({
method: 'put', url: 'https://idmsa.apple.com/appleauth/auth/verify/phone',
headers: authenticationHeaders,
data: body
});
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question("Input your code, received by the device ",async function (code: string) {
await axios({
method: "post", url:`https://idmsa.apple.com/appleauth/auth/verify/phone/securitycode`,
headers: authenticationHeaders, data: {
securityCode: {
code: code
},
phoneNumber:{
id: phoneId
},
mode: pushMode
}}
);
const finalRes = await axios({
method: "get",
url: "https://idmsa.apple.com/appleauth/auth/2sv/trust",
headers: authenticationHeaders
});
const sessionToCookie = finalRes["headers"]["x-apple-id-session-id"];
rl.close();
});
rl.on("close", function() {
process.exit(0);
});
}
catch (e)
{
console.error(e)
process.exit(1);
}
}
The problem occurs later since I need to use the session to create a cookie, as it is shown in the Fastlane project:
Original Ruby code:
def store_cookie(path: nil)
path ||= persistent_cookie_path
FileUtils.mkdir_p(File.expand_path("..", path))
# really important to specify the session to true
# otherwise myacinfo and more won't be stored
#cookie.save(path, :yaml, session: true)
return File.read(path)
end
def persistent_cookie_path
if ENV\["SPACESHIP_COOKIE_PATH"\]
path = File.expand_path(File.join(ENV\["SPACESHIP_COOKIE_PATH"\], "spaceship", self.user, "cookie"))
else
\[File.join(self.fastlane_user_dir, "spaceship"), "\~/.spaceship", "/var/tmp/spaceship", "#{Dir.tmpdir}/spaceship"\].each do |dir|
dir_parts = File.split(dir)
if directory_accessible?(File.expand_path(dir_parts.first))
path = File.expand_path(File.join(dir, self.user, "cookie"))
break
end
end
end
return path
end
I have no idea what to do with the received session to get the cookie based on the ruby code. Can anybody help me? And then, when I have a session, how do I access the App Store Connect
Enterprise account using cookies and avoiding the 2fa authentication based on the session?
I tried to use the tough-cookie NPM package for Node.js to build the cookie. It was not possible since I did not have options to mention YAML or session parameters.

(NodeJs) Drive revision update

I'm trying to download a specific revision from google drive files and update that revision as the latest. But after doing so I'm getting some unformatted text over the sheets as shown in the picture here. Can someone help with this?
I'm trying to achieve a revision update with a specific revision download as a blob and updating this downloaded version to drive for that file.
const auth = new google.auth.GoogleAuth({
credentials: { ...Credentials },
scopes: [
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive',
],
});
const drive: drive_v2.Drive = google.drive({
version: 'v2',
auth,
});
const driveV3: drive_v3.Drive = google.drive({
version: 'v3',
auth,
});
const list = await driveV3.revisions.list({
fileId: process.env.PRODUCTS_SPREAD_SHEET_ID,
});
console.log(list.data.revisions);
const response = await drive.revisions.get({
fileId: process.env.PRODUCTS_SPREAD_SHEET_ID,
revisionId: '244',
});
const endPoint =
response.data.exportLinks[
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
];
console.log(endPoint);
const apiRes = await makeGetApiCall(
endPoint,
{ Authorization },
'blob'
);
Here I'm updating the file content that I've downloaded above as blob in variable apiRes.
const aa = await driveV3.files.update({
fileId: process.env.PRODUCTS_SPREAD_SHEET_ID,
media: {
body: apiRes,
mimeType: 'application/vnd.google-apps.spreadsheet'
}
});

Update "shared" state of a file/folder with google drive API

I can't resolve how to change "shared" state to false with the google drive API.
Here is what i do:
I fetch all my folders & files witch are public with [this
one][1]
(I use q:"'me' in owners and visibility != 'limited'" filter)
I take file/folder ID and put it inside [this other one][2]
Inside the response object I got this line i want to change : "shared": true
I don't where I can set it to false, is someone getting any idea?
Have a nice day
Edit: I use NodeJs (netlify function), here is my code to get my files & folders :
const { google } = require('googleapis')
const CLIENT_ID = process.env.CLIENT_ID
const CLIENT_SECRET = process.env.CLIENT_SECRET
const REDIRECT_URI = 'https://developers.google.com/oauthplayground'
const REFRESH_TOKEN = process.env.REFRESH_TOKEN
exports.handler = async () => {
const oauth2Client = new google.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI)
oauth2Client.setCredentials({ refresh_token: REFRESH_TOKEN })
const drive = google.drive({
version: 'v3',
auth: oauth2Client,
})
try {
const result = await drive.files.list({
q:"'me' in owners and visibility != 'limited'"
})
return {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({ ...result, Body: result.toString('utf-8') })
}
} catch (e) {
console.log(e.message)
return { statusCode: 500, body: e.message }
}
}
To change visibility ("shared": true -> "shared": false),
I tried #Tanaike answer with :
const fetch = require('node-fetch')
const API_ENDPOINT_A = 'DELETE https://www.googleapis.com/drive/v3/files/'
const API_ENDPOINT_B = '/permissions/anyoneWithLink'
exports.handler = async (event) => {
try {
const itemId = '1-7ESYk_zKJ5Sdfg_z-XiuoXxrKKpHwSa' // event.queryStringParameters.itemId
const response = await fetch(API_ENDPOINT_A + itemId + API_ENDPOINT_B)
const data = await response.json()
return {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({ data })
}
} catch (error) {
console.log(error)
return {
statusCode: 500,
body: JSON.stringify({ error: 'Failed fetching data' })
}
}
}
But i don't know how I can pass my private info (api key...), I used OAuth 2 for fetch, should I use it too for edit visibility ?
[1]: https://developers.google.com/drive/api/v2/reference/files/list
[2]: https://developers.google.com/drive/api/v2/reference/files/patch
If I understand correctly, you are unable to see the place where you can change the file to "shared": true. If so, in the same link you provided from the official documentation you can find it in the "Request body".
From I fetch all my folders & files witch are public with this one (I use q:"'me' in owners and visibility != 'limited'" filter), when you want to change the permission of publicly shared to the restricted, you can achieve this using Drive API as follows.
Request:
Permissions: delete is used.
DELETE https://www.googleapis.com/drive/v3/files/###fileId###/permissions/anyoneWithLink
Sample curl:
curl --request DELETE \
-H 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
'https://www.googleapis.com/drive/v3/files/###fileId###/permissions/anyoneWithLink'
If the file is publicly shared and not shared with other specific users, when this request is run, "shared": true is changed to "shared": false.
Note:
If you want to remove the permission of the specific user, you can achieve this as follows.
Retrieve the permission ID using Permissions: list.
curl \
-H 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
'https://www.googleapis.com/drive/v3/files/###fileId###/permissions'
Using the retrieved permission ID, you can delete the permission as follows.
curl --request DELETE \
-H 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
'https://www.googleapis.com/drive/v3/files/###fileId###/permissions/###permissionId###'
References:
Permissions: delete
Permissions: list
Added:
From your following replying,
I actualy use nodejs for drive API, I tried the http solution, but idk how can i pass my clientId and secretId (error 500), You can find my code on my question, i edited it
When you want to achieve to delete permissions with googleapis for Node.js, you can use the following script. In this case, please include the scope of https://www.googleapis.com/auth/drive.
Sample script:
const fileId = "###"; // Please set your file ID.
const drive = google.drive({ version: "v3", auth }); // Please use your client here.
drive.permissions.delete(
{
fileId: fileId,
permissionId: "anyoneWithLink",
},
(err, res) => {
if (err) {
console.error(err);
return;
}
console.log(res.data); // In this case, no values are returned.
}
);
In this sample script, the permission of the publicly shared file is deleted.
I got it working with the following code :
const result = await drive.permissions.delete({
fileId:"YOUR FILE ID",
permissionId:"anyoneWithLink"
})
Here is the full update code :
const { google } = require('googleapis')
const CLIENT_ID = process.env.CLIENT_ID
const CLIENT_SECRET = process.env.CLIENT_SECRET
const REDIRECT_URI = 'https://developers.google.com/oauthplayground'
const REFRESH_TOKEN = process.env.REFRESH_TOKEN
exports.handler = async () => {
const oauth2Client = new google.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI)
oauth2Client.setCredentials({ refresh_token: REFRESH_TOKEN })
const drive = google.drive({
version: 'v3',
auth: oauth2Client,
})
try {
const result = await drive.permissions.delete({
fileId:"YOUR FILE ID",
permissionId:"anyoneWithLink"
})
return {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({ ...result, Body: result.toString('utf-8') })
}
} catch (e) {
console.log(e.message)
return { statusCode: 500, body: e.message }
}
}

Call cloud function through another in the same project without allUsers permission

I have 2 functions in the same google cloud functions project (myfunction1 and myfunction2.
exports.myfunction1 = async (req, res) => {
await axios({
method: 'post',
url: 'https://SERVER-PROJECT-ID.cloudfunctions.net/myfunction2',
timeout: 15000,
headers: {
'Content-Type': 'application/json',
},
data: myjson
}).then(response => {
console.log(JSON.stringify(response.data));
}).catch(err => {
console.error("catch error");
console.error(err);
})
}
It is works fine, but only if I configure invokers permission for allUsers. If I remove this permission, e receive 403 code error. Not sounds good keep this permisson activate, because the function is exposed. I tried solve with this link and this link, but, no sucess.
Edit1:
const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();
const targetAudience = 'https://SERVER-PROJECT-ID.cloudfunctions.net/myfunction2'
const url = '??????????';
async function request() {
console.info('request ${url} with target audience ${targetAudience}');
const client = await auth.getIdTokenClient(targetAudience);
const res = await client.request({url});
console.info(res.data);
}
I'm trying using this code, but, who is const url?
You must perform service to service authentication. You can find a great tutorial in the Cloud Run page (ok you use Cloud Functions but the underlying infrastructure is the same and the doc is better).
You also have to be aware about the Functions identity and how to change them (or to grant the current service account the correct permission)
let audience = 'https://SERVER-PROJECT-ID.cloudfunctions.net/myfunction2';
let token_request_url = 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=' + audience;
var token_response = await axios.get(token_request_url, { headers: {'Metadata-Flavor': 'Google'} });
let token_auth = token_response.data;
axios({
method: 'post',
url: audience,
timeout: 15000,
headers: {
'Authorization': "Bearer " + token_auth
},
data: myJSON
}).catch(err => {
console.error(err);
});

How to set zapier cli authentication?

I am trying to build an app using zapier cli.
I am having a problem with authorizing the request .
I am using an api key and a client id.
When I try to use the same credentials in the UI
Its working perfect , however in the cli it gives an error code 403.
I have listed the code below . What could be the issue ?
//test
"use strict";
const should = require("should");
const zapier = require("zapier-platform-core");
const App = require("../index");
const appTester = zapier.createAppTester(App);
describe("custom authentication authentication", () => {
// Put your test TEST_USERNAME and TEST_PASSWORD in a .env file.
// The inject method will load them and make them available to use in your
// tests.
zapier.tools.env.inject();
it("should authenticate", (done) => {
const bundle = {
authData: {
api_key: process.env.API_KEY,
client_id: process.env.CLIENT_ID,
},
};
appTester(App.authentication, bundle)
.then((response) => {
should.exist(response);
done();
})
.catch(done);
});
});
//authentication.js
"use strict";
const currentDate = new Date();
const year = currentDate.getFullYear();
const month = currentDate.getMonth() + 1;
const day = currentDate.getDate();
const authentication = (z, bundle) => {
const options = {
url: "url",
method: "GET",
headers: {
ContentType: "application/json",
Accept: "application/json",
"x-api-key": bundle.authData["api_key"],
client_id: bundle.authData["client_id"],
},
params: {
year: year,
month: month,
day: day,
page_size: "1000",
},
};
return z.request(options).then((response) => {
response.throwForStatus();
});
};
module.exports = authentication;
Do you have values in a .env file at the application root? You can also double check that console.log(process.env.API_KEY) prints a value when the test is running.
As an aside, you can use the zapier convert CLI command to copy your integration from the UI to the CLI without having to re-write it. If it works in the UI, it'll work in the CLI too.

Resources