Send message to microsoft teams channel using nodejs - javascript

I am able to acquire access token but not sure how to send messages because it requires a user and my app is a backend app(nodejs script). On graph explorer, it works.
The code snippet on graph explorer is:
const options = {
authProvider, //I need this value
};
const client = Client.init(options);
const chatMessage = {body: {content: '#.####.#'}};
await client.api('/teams/my-team-id/channels/my-channel-id/messages')
.post(chatMessage);
How do I get authProvider in nodejs?
I tried using MSALAuthenticationProviderOptions but there seems to be an issue (as mentioned in their github repo) by following these steps: https://www.npmjs.com/package/#microsoft/microsoft-graph-client.

You need to run this in the context of an application instead of a user. The Microsoft Graph JavaScript library now supports Azure TokenCredential for acquiring tokens.
const { Client } = require("#microsoft/microsoft-graph-client");
const { TokenCredentialAuthenticationProvider } = require("#microsoft/microsoft-graph-client/authProviders/azureTokenCredentials");
const { ClientSecretCredential } = require("#azure/identity");
const { clientId, clientSecret, scopes, tenantId } = require("./secrets"); // Manage your secret better than this please.
require("isomorphic-fetch");
async function runExample() {
const credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
const authProvider = new TokenCredentialAuthenticationProvider(credential, { scopes: [scopes] });
const client = Client.initWithMiddleware({
debugLogging: true,
authProvider,
});
const chatMessage = {body: {content: '#.####.#'}};
const res = await client.api('/teams/my-team-id/channels/my-channel-id/messages')
.post(chatMessage);
console.log(res);
}
runExample().catch((err) => {
console.log("Encountered an error:\n\n", err);
});
This sample came from:
https://github.com/microsoftgraph/msgraph-sdk-javascript/tree/dev/samples/tokenCredentialSamples/ClientCredentialFlow

Related

How to validate GitHub webhook with Deno?

I'm trying to make a GitHub webhook server with Deno, but I cannot find any possible way to do the validation.
This is my current attempt using webhooks-methods.js:
import { Application } from "https://deno.land/x/oak/mod.ts";
import { verify } from "https://cdn.skypack.dev/#octokit/webhooks-methods?dts";
const app = new Application();
app.use(async (ctx, next) => {
try {
await next();
} catch (_err) {
ctx.response.status = 500;
}
});
const secret = "...";
app.use(async (ctx) => {
const signature = ctx.request.headers.get("X-Hub-Signature-256");
if (signature) {
const payload = await ctx.request.body({ type: "text" }).value;
const result = await verify(secret, payload, signature);
console.log(result);
}
ctx.response.status = 200;
});
The verify function is returning false every time.
Your example is very close. The GitHub webhook documentation details the signature header schema. The value is a digest algorithm prefix followed by the signature, in the format of ${ALGO}=${SIGNATURE}:
X-Hub-Signature-256: sha256=d57c68ca6f92289e6987922ff26938930f6e66a2d161ef06abdf1859230aa23c
So, you need to extract the signature from the value (omitting the prefix):
const signatureHeader = request.headers.get("X-Hub-Signature-256");
const signature = signatureHeader.slice("sha256=".length);
Update: Starting in release version 3.0.1 of octokit/webhooks-methods.js, it is no longer necessary to manually extract the signature from the header — that task is handled by the verify function. The code in the answer has been updated to reflect this change.
Here's a complete, working example that you can simply copy + paste into a project or playground on Deno Deploy:
gh-webhook-logger.ts:
import { assert } from "https://deno.land/std#0.177.0/testing/asserts.ts";
import {
Application,
NativeRequest,
Router,
} from "https://deno.land/x/oak#v11.1.0/mod.ts";
import type { ServerRequest } from "https://deno.land/x/oak#v11.1.0/types.d.ts";
import { verify } from "https://esm.sh/#octokit/webhooks-methods#3.0.2?pin=v106";
// In actual usage, use a private secret:
// const SECRET = Deno.env.get("SIGNING_SECRET");
// But for the purposes of this demo, the exposed secret is:
const SECRET = "Let me know if you found this to be helpful!";
type GitHubWebhookVerificationStatus = {
id: string;
verified: boolean;
};
// Because this uses a native Request,
// it can be used in other contexts besides Oak (e.g. `std/http/serve`):
async function verifyGitHubWebhook(
request: Request,
): Promise<GitHubWebhookVerificationStatus> {
const id = request.headers.get("X-GitHub-Delivery");
// This should be more strict in reality
assert(id, "Not a GH webhhok");
const signatureHeader = request.headers.get("X-Hub-Signature-256");
let verified = false;
if (signatureHeader) {
const payload = await request.clone().text();
verified = await verify(SECRET, payload, signatureHeader);
}
return { id, verified };
}
// Type predicate used to access native Request instance
// Ref: https://github.com/oakserver/oak/issues/501#issuecomment-1084046581
function isNativeRequest(r: ServerRequest): r is NativeRequest {
// deno-lint-ignore no-explicit-any
return (r as any).request instanceof Request;
}
const webhookLogger = new Router().post("/webhook", async (ctx) => {
assert(isNativeRequest(ctx.request.originalRequest));
const status = await verifyGitHubWebhook(ctx.request.originalRequest.request);
console.log(status);
ctx.response.status = 200;
});
const app = new Application()
.use(webhookLogger.routes())
.use(webhookLogger.allowedMethods());
// The port is not important in Deno Deploy
await app.listen({ port: 8080 });

Microsoft Graph API - Problem to access oneDrive in route '/me/drives'

I'm trying to use Microsoft Graph API to acess oneDrive folders and archives in node.js, the authentication goes fine, and returns the calendar as well, but i have used this:
var graph = require('#microsoft/microsoft-graph-client');
require('isomorphic-fetch');
module.exports = {
getUserDetails: async function(accessToken) {
const client = getAuthenticatedClient(accessToken);
const user = await client.api('/me').get();
return user;
},
// GetEventsSnippet
getEvents: async function(accessToken) {
const client = getAuthenticatedClient(accessToken);
const events = await client
.api('/me/events')
.select('subject,organizer,start,end')
.orderby('createdDateTime DESC')
.get();
return events;
},
getDrives: async function(accessToken) {
const client = getAuthenticatedClient(accessToken);
try{
const drive = await client
.api('/me/drive/root/children')
.get();
console.log(drive);
return drive;
} catch (e) {
console.log(e);
}
}
};
function getAuthenticatedClient(accessToken) {
// Initialize Graph client
const client = graph.Client.init({
// Use the provided access token to authenticate
// requests
authProvider: (done) => {
done(null, accessToken);
}
});
console.log(client);
return client;
}
And it don't return anything... But saying that im unauthenticated
I am following this documentation: https://learn.microsoft.com/en-us/onedrive/developer/rest-api/api/drive_list?view=odsp-graph-online
How can i make this work?

Error in Apollo Server deploy with AWS Lambda

People, how are you? I have a query, I just implemented my API made with apollo server in an AWS Lambda. I used the official documentation as a guide, but I'm noticing that the context handling varies a bit. I have a doubt with the latter, since I made certain changes and everything works fine locally using "serverless offline", but once I deploy it doesn't. Apparently the authentication context that I generate does not finish reaching my query. If someone can guide me a bit with this, I will be very grateful.
This is my API index:
const { ApolloServer, gql } = require('apollo-server-lambda');
const typeDefs = require('./db/schema');
const resolvers = require('./db/resolvers');
const db = require('./config/db');
const jwt = require('jsonwebtoken');
require('dotenv').config({ path: 'variables.env' });
db.conectDB();
// The ApolloServer constructor requires two parameters: your schema
// definition and your set of resolvers.
const server = new ApolloServer({
typeDefs,
resolvers,
playground: {
endpoint: "/graphql"
},
context: ({ event, context }) => {
try {
const token = event.headers['authorization'] || '';
if(token){
context.user = jwt.verify(token.replace('Bearer ',''), process.env.KEY_TOKEN);
}
return {
headers: event.headers,
functionName: context.functionName,
event,
context,
}
} catch (error) {
console.error(error);
}
}
});
exports.graphqlHandler = server.createHandler({
cors: {
origin: '*',
credentials: true,
},
});
This is my query:
getUserByToken: async (_, {}, { context }) => {
if(context)
throw new Error((context ? 'context' : '') + ' ' + (context.user ? 'user' : ''));
let user = await db.findOne('users',{ _id: ObjectId(context.user._id) });
if(user.birthdate)
user.birthdate = user.birthdate.toString();
if(user.password)
user.password = true;
else
user.password = false;
return user;
}
My API response:
API response
From what I can see, you're not calling getUserByToken in your context. Is that correct? So, I'm not sure how you're encountering this error.
Can I give you some pointers?
Connecting to your DB is probably (or it should be) asynchronous. For that, I'd run your code like this:
db.connect()
.then(() => {
... handle your request in here
})
.catch(console.error);
I think you meant to call your getUserByToken in this line:
context.user = jwt.verify(token.replace('Bearer ',''), process.env.KEY_TOKEN);

Google login with React and Node.js does not return requested scope

I need a additional scope data from google api in login in my app. I use react-google-login to get token in React app using this scopes:
scope='https://www.googleapis.com/auth/user.birthday.read https://www.googleapis.com/auth/user.addresses.read https://www.googleapis.com/auth/user.organization.read'
When I log in with my credentials and allow app to access requested scopes, I successfully get token.
I send this token to backend (Node.js) where I use google-auth-library to got payload from token:
import { OAuth2Client } from 'google-auth-library'
export const validateGoogleAccessTokenOAuth2 = async (idToken: string): Promise<any> => {
const CLIENT_ID = 'MY_ID'
const client = new OAuth2Client(CLIENT_ID)
const ticket = await client.verifyIdToken({
idToken,
audience: CLIENT_ID
})
const payload = ticket.getPayload()
return payload
}
Here I receive only data from profile and email scope, there is no data from requested scopes. Especially I need birthday, I also check it is allowed in my google profile to be accessed by anyone but didn't help.
Is there something what I do wrong or is there another way how to get requested scope variable from token?
After some research I found that
birthday and gender attributes are not included in payload of public token
client.verifyIdToken function just verify idToken and return its payload
to get additional information about user, in this case you have to use People API
To communicate with People API you can use googleapis npm package, also you have to meet this conditions:
Add People API to your project in Google Developer Console
Add additional scope to your FE call to google (in my case https://www.googleapis.com/auth/user.birthday.read)
you have to send idToken and AccessToken to your backend service
user have to allow share gender with your application
user's gender must be set as public in user's google profile
BE example
import { google } from 'googleapis'
import { OAuth2Client } from 'google-auth-library'
export const validateGoogleAccessToken = async (idToken, accessToken) => {
try {
const CLIENT_ID = 'YOUR_GOOGLE_APP_CLIENT_ID'
const client = new OAuth2Client(CLIENT_ID)
const ticket = await client.verifyIdToken({
idToken,
audience: CLIENT_ID
})
const payload = ticket.getPayload()
const { OAuth2 } = google.auth
const oauth2Client = new OAuth2()
oauth2Client.setCredentials({ access_token: accessToken })
const peopleAPI = google.people({
version: 'v1',
auth: oauth2Client
})
const { data } = await peopleAPI.people.get({
resourceName: 'people/me',
personFields: 'birthdays,genders',
})
const { birthdays, gender } = data
return {
...payload // email, name, tokens
birthdates, // array of birthdays
genders, // array of genders
}
} catch (error) {
throw new Error('Google token validation failed')
}
}

Should I federate cognito user pools along with other social idps, or have social sign in via the userpool itself

I am building a social chat application and initially had a cognito user pool that was federated alongside Google/Facebook. I was storing user data based on the user-sub for cognito users and the identity id for google/facebook. Then in my lambda-gql resolvers, I would authenticate via the AWS-sdk:
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: process.env.IDENTITY_POOL_ID,
Logins: {
[`cognito-idp.us-east-1.amazonaws.com/${
process.env.COGNITO_USERPOOL_ID
}`]: Authorization,
},
});
Because all users are equal and I don't need fine grained controls over access to aws-resources, it seems like it would be preferable to instead have all authentication handled via the userpool and to get rid of the identity pool entirely.
For example, if I wanted to ban a user's account, it seems that I would first have to lookup the provider based on identity-id and then perform a different action based on the provider.
So my questions are:
1. Is this even possible?
- https://github.com/aws-amplify/amplify-js/issues/565
-https://www.reddit.com/r/aws/comments/92ye5s/is_it_possible_to_add_googlefacebook_user_to/
There seems to be a lot of confusion, and the aws docs are less clear than usual (which isn't much imo).
https://docs.aws.amazon.com/cognito/latest/developerguide/authentication.html
It seems that there is clearly a method to do this. I followed the above guide and am getting errors with the hosted UI endpoint, but that's probably on me (https://forums.aws.amazon.com/thread.jspa?threadID=262736). However, I do not want the hosted UI endpoint, I would like cognito users to sign in through my custom form and then social sign in users to click a "continue with fb" button and have that automatically populate my userpool.
Then replace the code above with the following to validate all users:
const validate = token => new Promise(async (resolve) => {
const {
data: { keys },
} = await axios(url);
const { sub, ...res } = decode(token, { complete: true });
const { kid } = decode(token, { header: true });
const jwk = R.find(R.propEq('kid', kid))(keys);
const pem = jwkToPem(jwk);
const response = res && res['cognito:username']
? { sub, user: res['cognito:username'] }
: { sub };
try {
await verify(token, pem);
resolve(response);
} catch (error) {
resolve(false);
}
});
If it is possible, what is the correct mechanism that would replace the following:
Auth.federatedSignIn('facebook', { token: accessToken, expires_at }, user)
.then(credentials => Auth.currentAuthenticatedUser())
.then((user) => {
onStateChange('signedIn', {});
})
.catch((e) => {
console.log(e);
});
From what I have seen, there does not appear to be a method with Amplify to accomplish this. Is there some way to do this with the aws-sdk? What about mapping the callback from the facebook api to create a cognito user client-side? It seems like that could get quite messy.
If there is no mechanism to accomplish the above, should I federate cognito users with social sign ins?
And then what should I use to identify users in my database? Am currently using username and sub for cognito and identity id for federated users. Extracting the sub from the Auth token server-side and then on the client:
Auth.currentSession()
.then((data) => {
const userSub = R.path(['accessToken', 'payload', 'sub'], data);
resolve(userSub);
})
.catch(async () => {
try {
const result = await Auth.currentCredentials();
const credentials = Auth.essentialCredentials(result);
resolve(removeRegionFromId(credentials.identityId));
} catch (error) {
resolve(false);
}
});
If anyone could provide the detailed authoritative answer I have yet to find concerning the use of cognito user pools in place of federating that would be great. Otherwise a general outline of the correct approach to take would be much appreciated.
Here's what I ended up doing for anyone in a similar position, this isn't comprehensive:
Create a userpool, do not specify client secret or any required attributes that could conflict with whats returned from Facebook/Google.
Under domains, in the Cognito sidebar, add what ever you want yours to be.
The add your identity provided from Cognito, for FB you want them to be comma seperated like so: openid, phone, email, profile, aws.cognito.signin.user.admin
Enable FB from app client settings, select implicit grant. I belive, but am not positive, openid is required for generating a access key and signin.user.admin for getting a RS256 token to verify with the public key.
The from FB dev console, https://yourdomain.auth.us-east-1.amazoncognito.com/oauth2/idpresponse, as valid oauth redirects.
Then, still on FB, go to settings (general not app specific), and enter https://yourdomain.auth.us-east-1.amazoncognito.com/oauth2/idpresponse
https://yourdomain.auth.us-east-1.amazoncognito.com/oauth2/idpresponse for your site url.
Then for the login in button you can add the following code,
const authenticate = callbackFn => () => {
const domain = process.env.COGNITO_APP_DOMAIN;
const clientId = process.env.COGNITO_USERPOOL_CLIENT_ID;
const type = 'token';
const scope = 'openid phone email profile aws.cognito.signin.user.admin';
const verification = generateVerification();
const provider = 'Facebook';
const callback = `${window.location.protocol}//${
window.location.host
}/callback`;
const url = `${domain}/authorize?identity_provider=${provider}&response_type=${type}&client_id=${clientId}&redirect_uri=${callback}&state=${verification}&scope=${scope}`;
window.open(url, '_self');
};
Then on your redirect page:
useEffect(() => {
// eslint-disable-next-line no-undef
if (window.location.href.includes('#access_token')) {
const callback = () => history.push('/');
newAuthUser(callback);
}
}, []);
/* eslint-disable no-undef */
import { CognitoAuth } from 'amazon-cognito-auth-js';
import setToast from './setToast';
export default (callback) => {
const AppWebDomain = process.env.COGNITO_APP_DOMAIN;
// https://yourdomainhere.auth.us-east-1.amazoncognito.com'
const TokenScopesArray = [
'phone',
'email',
'profile',
'openid',
'aws.cognito.signin.user.admin',
];
const redirect = 'http://localhost:8080/auth';
const authData = {
ClientId: process.env.COGNITO_USERPOOL_CLIENT_ID,
AppWebDomain,
TokenScopesArray,
RedirectUriSignIn: redirect,
RedirectUriSignOut: redirect,
IdentityProvider: 'Facebook',
UserPoolId: process.env.COGNITO_USERPOOL_ID,
AdvancedSecurityDataCollectionFlag: true,
};
const auth = new CognitoAuth(authData);
auth.userhandler = {
onSuccess() {
setToast('logged-in');
callback();
},
onFailure(error) {
setToast('auth-error', error);
callback();
},
};
const curUrl = window.location.href;
auth.parseCognitoWebResponse(curUrl);
};
You can then use Auth.currentSession() to get user attributes from the client.
Then server-side you can validate all user like so:
const decode = require('jwt-decode');
const jwt = require('jsonwebtoken');
const jwkToPem = require('jwk-to-pem');
const axios = require('axios');
const R = require('ramda');
const logger = require('./logger');
const url = `https://cognito-idp.us-east-1.amazonaws.com/${
process.env.COGNITO_USERPOOL_ID
}/.well-known/jwks.json`;
const verify = (token, n) => new Promise((resolve, reject) => {
jwt.verify(token, n, { algorithms: ['RS256'] }, (err, decoded) => {
if (err) {
reject(new Error('invalid_token', err));
} else {
resolve(decoded);
}
});
});
const validate = token => new Promise(async (resolve) => {
const {
data: { keys },
} = await axios(url);
const { sub, ...res } = decode(token, { complete: true });
const { kid } = decode(token, { header: true });
const jwk = R.find(R.propEq('kid', kid))(keys);
const pem = jwkToPem(jwk);
const response = res && res['cognito:username']
? { sub, user: res['cognito:username'] }
: { sub };
try {
await verify(token, pem);
resolve(response);
} catch (error) {
logger['on-failure']('CHECK_CREDENTIALS', error);
resolve(false);
}
});
const checkCredentialsCognito = Authorization => validate(Authorization);

Categories