this project show how to webhook with Outlook Gdrive and Gmail (will give general understanding also to OneDrive).
-
"dev": "start cmd /k npm run compile && start cmd /k npm start"
"start cmd" //open new command prompt
"/k"//execute command inside that new cmd
-
this is the new script of npm run dev (using npm-run-all library)[run compile and app in the same window]
"dev": "npm-run-all --parallel compile start",
first create microsoft app at the applcation Registeration Portal
get user authorization token using Oauth2.0 process
Improtant!!
- make sure that when you redirect user to the login page the scope parameter - will contain the scope of activities you want to listen to
- this scope should also be enabled in the application registeration portal
after you recevied the access token you can send subscription request in order to recieve notifications about the microsoft user activities , the request will contain NotificationURL that tell microsoft where to send the notifications (user activities info)
//body when wanting to CREATE subscription to user activities
export interface iSubscriptionRequest {
ChangeType: string,// "Created"; // Indicates the type of events that will raise a notification- https://msdn.microsoft.com/en-us/office/office365/api/notify-rest-operations#changetype
ClientState?: string//[Optional] property that will be sent back in each notification event from microsoft graph (in the headers) (can be used to validate the notification legitimacy)
NotificationURL: string; //Specifies where notifications should be sent to (my resource)
"@odata.type": string // "#Microsoft.OutlookServices.PushSubscription";
Resource: string // "https://outlook.office.com/api/v2.0/me/messages";//Specifies the resource to monitor and receive notifications on (outlook/calander/onedrive/ etc..). (for example - https://outlook.office.com/api/v2.0/me/events -this for calander events)
ExpirationDateTime?: number//define when this subscription sould end - if not defiend - will be 1 day for reach sub/7 days for normal
}
NOTES
- use $select if wanting to directly get notification Info (email message info) "You can save a separate GET API call if you use a $select parameter in the subscription request and specify the properties you're interested in. The following is an example that requests a notification to include the subject property when an event has been created:" more info here
- use $filter to get only specific notifications for example - get only notifications about emails with attachments
after that - the subscription will be created and the subscription Response will contain the SubscriptionId - so when notification will get sent to the server the app will know which user that is
/**response when requesting to CREATE no subscription */
export interface iSubscriptionResponse {
id: string, //The unique subscription ID which the client app should save to match with future notifications. // NTM4M0IzOEEtMDEyNi00RTAxLTk2NDEtMzU2NTk4RUM2RTNBX0FFOUFFNDExLTY1MEMtNDEyOC05NkY0LUM4MjI2ODUxNTU4Mw==
ChangeType: string //Apart from the values specified in the request, the response includes the additional notification type, Missed
/**The date and time that the subscription will expire. If the subscription request does not specify an expiration time,
* or if the request specifies an expiration time longer than the maximum allowed, this property will be set to that maximum allowed length from the time the request is sent.
* For a subscription that requests rich notifications of specific properties, the maximum allowed is 1 day. For other subscriptions, the maximum is 7 days. */
SubscriptionExpirationDateTime: string
"@odata.context": string,//'https://outlook.office.com/api/v2.0/$metadata#Me/Subscriptions/$entity'
'@odata.type': string // '#Microsoft.OutlookServices.PushSubscription',
'@odata.id': string //'https://outlook.office.com/api/v2.0/Users(\'6c4d5e1e-c8fd-47ac-9d22-c5e8dc56594e@65a1678f-e343-44a5-8c41-c51024828e11\')/Subscriptions(\'NTM4M0IzOEEtMDEyNi00RTAxLTk2NDEtMzU2NTk4RUM2RTNBX0FFOUFFNDExLTY1MEMtNDEyOC05NkY0LUM4MjI2ODUxNTU4Mw==\')',
Resource: 'https://outlook.office.com/api/v2.0/me/messages',
NotificationURL: string//the endpoint that will recieve the notifications // 'https://shieldox-mail-webhook.azurewebsites.net/outlook/notification',
ClientState?: string//the clientstate that sent in the create subscription request
}
after you receiving the notification you can use the office365 APIs to get more info about it
more details about the process are mentioned here
first create Google app at the Google API Console and Enable the Gmail API
in order to establish Gmail webhook for your app you need to be familiar with the Cloud Pub/Sub API
Domain Verification - in order to use Webhook with Google pub/Sub we should prove ownership of the domain that receive the notifications - follow this steps :
- verify domain from the Search Conosole - you should downalod an html file that you will send back as response for the route '/<html_file_name>'
router.get('/googlebdff09854abfa74b.html', (req, res) => {
res.sendFile(path.join(__dirname, 'googlebdff09854abfa74b.html'));
})
- add that domain to the in the Google API Console
fulfill the Cloud Pub/Sub Prerequisites
get user authorization token using Oauth2.0 process (here is recommended library for nodejs)
Improtant!!
- make sure that in Oauth2.0 flow - when you redirect user to the login page the scope parameter - will contain the scope of activities you want to listen to
after resolving the user access token you can now request to watch user activities (equivalent to Outlook - CreateSubscription request ) along with ['me'] as a value for the [userId] parameter, and the [topicName] youv'e created in the Cloud Pub/Sub Prerequisites
- you didnt enabled the [Google Cloud Pub/Sub API] OR the [Gmail API] in the google console
- you didnt provided the scope premissions when authorazing with Oauth2.0: 'https://www.googleapis.com/auth/pubsub' , .... (look in :https://developers.google.com/gmail/api/v1/reference/users/watch)
- you didnt grant publish priviliges to serviceAccount:[email protected] in the IAM: (https://developers.google.com/gmail/api/guides/push#grant_publish_rights_on_your_topic , https://console.cloud.google.com/iam-admin/iam
- the topicName you are trying to register to is deleted/mismatch
if the watch user activities succeded you will receive the following response body:
{
historyId: 1234567890 //save it in order to pull more details about the notification later on. you will receive notifications that occured from that point in time (historyId =define point in time)
expiration: 1431990098200 // /*When Gmail will stop sending notifications for mailbox updates (epoch millis).Call watch again before this time to renew the watch.*/
}
now notifications will be sent to all the subscribers's urls (for example :https://myserver/gmail/notification) to the topicName specified in the [Watch] request , (the subscription was created in the Cloud Pub/Sub Prerequisites step)
each notification body contain the following:
interface iGmailNotification {
message:
{
// This is the actual notification data, as base64url-encoded JSON.
data: string, //"eyJlbWFpbEFkZHJlc3MiOiAidXNlckBleGFtcGxlLmNvbSIsICJoaXN0b3J5SWQiOiAiMTIzNDU2Nzg5MCJ9",
message_id: string,// This is a Cloud Pub/Sub message id, unrelated to Gmail messages.// "1234567890",
publishTime:string , //"2017-12-03T09:48:51.274Z"
}
//the subscription of the app as mentioned in https://console.cloud.google.com/cloudpubsub/subscriptions
subscription: string //the subscription name //"projects/myproject/subscriptions/mysubscription"
}
//after decrypting the iGmailNotification.message.data from base64:
export interface iGmailNotificationData {
emailAddress: string,
historyId: string
}
now you can use the user access_token (related to that user email) in order to send HistoryList request - that give more details about the user activities occurred.
- GET HistoryList Request that contains:
- [URL PARAM] userId/'me' - the userId that we intersted to get the history about (= we will use the 'me' value)
- [Query-Param] startHistoryId - [REQUIERD] will return history records after the specified startHistoryId
- [Query-Param] pageToken - Page token to retrieve a specific page of results in the list,when we'll recive the HistoryList we'll also get: a.nextPageToken - that indicates that not all activites has been recivied and to get the next chunk of history we should use this 'nextPageToken' and send it as a @param pageToken as a queryparam if nextPageToken doesnt exist it means we got all activities. b.[RELEVANT TO GDRIVE ] *newStartPageToken - indicates the moment (the begin point) from which we want to get the activities next time a push notification happends
*/
furtermore , use the Gmail API to get info about specific activity (notification) - for exmaple get message by id , get message attachment etc..
more details about the process are mentioned [here] (https://developers.google.com/gmail/api/guides/push)
can find full info here.
first of all just to clarify G suite platform gives the ability to perform a Domain-Wide Delegation of Authority which basically means that the admin can let our app access_token to all the g suite users which means we can avoid from requesting each individual user to accept our app in order to do actions on behalf of that user (for example webhook to user Gmail + Gdrive activities.)
in order to to implement Domain-Wide Delegation of Authority do as the following:
- Furnish a new private key
- Enable G Suite Domain-wide Delegation
- save the json file the downloaded (its the only copy of the service account credetials!!)
- administrator of the G Suite account will need to authorize the service account you've created by following this steps
- NOTE - he will need to specify the scopes permissions that he allow for the service account.
for example i used those scopes:
"https://www.googleapis.com/auth/admin.directory.user" - to get all users data in the Gsuite account, "https://www.googleapis.com/auth/admin.directory.domain.readonly" - to read all domains,
"https://www.googleapis.com/auth/gmail.readonly" - to read user emails- if we want to get some administrative operations (for example get all Users data of the G Suite account ) we'll probably need to use the Directory API and we'll need to specify the scopes we need (and also the administrator)
- now we can use the service account but in order to make an authorized API calls the service account will need to receive tokens. we can the manual way (with http calls). OR we can use one of the sdk libraries (for example - this nodejs lib)
- if you need to send api calls on behalf of admin user for example - you will need to impersonate that user by specifing the user email when recieving the tokens :
var google = require('googleapis');
var key = require('/path/to/key.json');
var jwtClient = new google.auth.JWT(
key.client_email, //service account email
null,
key.private_key,//service account private key
["https://www.googleapis.com/auth/admin.directory.user",
"https://www.googleapis.com/auth/admin.directory.domain.readonly",
"https://www.googleapis.com/auth/gmail.readonly"], // an array of auth scopes
someAdminEmail// User to impersonate (leave empty if no impersonation needed)
);
jwtClient.authorize(function (err, tokens) {
if (err) {
console.log(err);
return;
//now you can use this access token to do authorzied api calls
}
});
in my project example i've impersonate a super admin user in order to retrieve all users of the organization, then i've impersonated each user in order to create gmail webhook for each one
gmail webhook
- handle duplicated notifications received from gmail api
β‘βοΈ