Unable to connect to Room: Invalid Access Token issuer/subject twilio - javascript

I do want to create an access token in the backend and need to pass to the front end to connect to the video chat room.
This is my back-end code
const twilioAccountSid = process.env.twilioAccountSid;
const twilioApiKey = process.env.twilioApiKey;
const twilioApiSecret = process.env.twilioApiSecret;
const room = "cool room";
app.post("/access-token", (req, res) => {
try {
console.log(
"sid",
twilioAccountSid,
"key",
twilioApiKey,
"secret",
twilioApiSecret
);
const identity = "user";
// Create Video Grant
const videoGrant = new VideoGrant({
room,
});
// Create an access token which we will sign and return to the client,
// containing the grant we just created
const token = new AccessToken(
twilioAccountSid,
twilioApiKey,
twilioApiSecret,
{ identity: identity }
);
token.addGrant(videoGrant);
// Serialize the token to a JWT string
console.log(token.toJwt());
res.status(200).json(token.toJwt());
} catch (error) {
console.warn(error);
res.sendStatus(500);
}
});
For the Twilio account SID I used my dashboard's SID which is starting from AC
For the API key I added the friendly name I gave to the API key when I created it.
API secret is that API key's secret id.
A token is crearted succefully and passed to the front-end.
This is my front-end code
const connectRoom = async () => {
try {
const token = await axios.post("http://localhost:5000/access-token");
connect(token.data, { name: roomName, video: { width: 640 } }).then(
(room) => {
console.log(`Successfully joined a Room: ${room}`);
room.on("participantConnected", (participant) => {
console.log(`A remote Participant connected: ${participant}`);
participant.tracks.forEach((publication) => {
console.log("for each");
if (publication.isSubscribed) {
const track = publication.track;
document
.getElementById("remote-media-div")
.appendChild(track.attach());
}
});
participant.on("trackSubscribed", (track) => {
document
.getElementById("remote-media-div")
.appendChild(track.attach());
});
});
},
(error) => {
console.error(`Unable to connect to Room: ${error.message}`);
}
);
} catch (error) {
console.log(error);
}
Then I get this error
Unable to connect to Room: Invalid Access Token issuer/subject
How do I solve this problem?
Any help!
Thanks in advance

You can create an API Key here (or via the Console). Note, the API Key starts with SK....
REST API: API Keys

Related

i try to send notification to specific device using firebase cloud function

i am building chat app with flutter and I try to send notification to specific device using the cloud function so when a user send message to his friend then his friend get notification with the message but I get that error
note : I don not have any knowledge with javascript or node js
Unhandled error Error: Value for argument "documentPath" is not a valid resource path. Path must be a non-empty string.
at Object.validateResourcePath (/workspace/node_modules/#google-cloud/firestore/build/src/path.js:446:15)
at CollectionReference.doc (/workspace/node_modules/#google-cloud/firestore/build/src/reference.js:2061:20)
at /workspace/index.js:14:12
at fixedLen (/workspace/node_modules/firebase-functions/lib/providers/https.js:72:41)
at /workspace/node_modules/firebase-functions/lib/common/providers/https.js:407:32
at processTicksAndRejections (node:internal/process/task_queues:96:5)
first I try to get the device token and save it to firebase
void getToken() async {
await fcm.getToken().then((value) {
tokens = value;
print('my token22 is $tokens');
saveToken(tokens: tokens);
});
}
void saveToken({String? tokens}) async {
FirebaseFirestore.instance.collection('userToken').doc(userphone).set({
'token': tokens,
});
}
then I try to call this token at index.js file at function function
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.addMessage = functions.https.onCall(
(data, context) => {
const friendPhone = data.text;
const userDoc = admin
.firestore()
.collection("userToken")
.doc(friendPhone)
.get();
const deviceTokens = userDoc.data();
console.log(deviceTokens);
const title = data.title;
const body = data.body;
try {
if (deviceTokens) {
exports.myFunction = functions.firestore
.document("chats/{chatId}/messegeId/{messageId}")
.onWrite((snap, context) => {
console.log(snap.data());
admin.messaging().sendToDevice(deviceTokens,
{
notification: {title: title,
body: body,
clickAction: "FLUTTER_NOTIFICATION_CLICK",
},
});
});
}
} catch (error) {
console.log(error);
throw new functions.https.
HttpsError("invalid-argument", "some message");
}
}
);
after that I call the function at sendMessege button so that when the user send messgege it work
Future<void> writeMessage({
String? message,
String? title,
String? friendPhone,
}) async {
HttpsCallable callable =
FirebaseFunctions.instance.httpsCallable("addMessage");
final resp = await callable.call(<String, dynamic>{
"text": friendPhone,
"title": title,
"body": message,
});
print("result: ${resp.data}");
}
oare you sure that the path for the friendPhone is a valid path in firebase?
Is there maybe a spelling mistake in messageId?
document("chats/{chatId}/messegeId/{messageId}")
You wrote messegeId instead of messageId
Hope that helps.

How to mock Twilio using Jest and TypeScript for unit testing

I need your help to mock a twilio service which sends a message, using jest to mock the service
I have the next code:
import { SQSEvent } from "aws-lambda";
import { GetSecretValueResponse } from "aws-sdk/clients/secretsmanager";
export async function sendSms(event: SQSEvent, data: GetSecretValueResponse) {
const secrets = JSON.parse(data.SecretString);
const accountSid = secrets.TWILIO_ACCOUNT_SID;
const authToken = secrets.TWILIO_AUTH_TOKEN;
const twilioNumber = secrets.TWILIO_PHONE_NUMBER;
if (accountSid && authToken && twilioNumber) {
//Create a Twilio Client
const client = new Twilio(accountSid, authToken);
//Loop into al records of the event, every record is every message sent from Sqs
for (const record of event.Records) {
const body = JSON.parse(record.body);
const userNumber = "+" + body.number;
//SendMessage function
try {
const message = client.messages.create({
from: twilioNumber,
to: userNumber,
body: body.message,
});
return message;
} catch (error) {
return `Failed to send sms message. Error Code: ${error.errorCode} / Error Message: ${error.errorMessage}`;
}
}
} else {
return "You are missing one of the variables you need to send a message";
}
}
The I call this function from my index:
import { SQSEvent } from "aws-lambda";
import { sendSms } from "./services/sendSms/sendSms";
import { getSecret } from "./services/obtainSecrets/getSecret";
import { SecretsManager } from "aws-sdk";
export const lambdaHandler = async (event: SQSEvent) => {
try {
const obtainedSecret = await getSecret()
.then((credentials: SecretsManager.GetSecretValueResponse) => {
return credentials;
})
.catch(error => {
return error;
});
const response = sendSms(event, obtainedSecret)
.then(response => {
return response;
})
.catch(error => {
return error;
});
return {
message: "OK " + obtainedSecret + response,
code: 200,
};
} catch (error) {
throw new Error(error);
}
};
I have already make some tests, but them always makes a connection with Twilio api(requiring the real token, sid,etc), and I need to mock the Twilio service, so the function I call in my test.ts doesn't connects to internet.
import { Twilio } from "twilio";
import { MessageInstance } from "twilio/lib/rest/api/v2010/account/message";
import { sendSms } from "../../services/sendSms/sendSms";
//mock Twilio library and sendSms service
jest.mock("twilio");
jest.mock("../../services/sendSms/sendSms");
const smsMessageResultMock: Partial<MessageInstance> = {
status: "sent",
sid: "AC-lorem-ipsum",
errorCode: undefined,
errorMessage: undefined,
};
describe("SMS Service", () => {
describe("Send Message", () => {
it("Should fail", async () => {
// update smsMessageResultMock to simulate a faled response
const smsMessageMock = {
...smsMessageResultMock,
status: "failed",
errorCode: 123,
errorMessage: "lorem-ipsum",
};
// simulated response of secret management
let data = {
ARN: "arn:aws:secretsmanager:us-west-2:123456789012:secret:MyTestDatabaseSecret-a1b2c3",
Name: "MyTestDatabaseSecret",
SecretString:
'{"TWILIO_ACCOUNT_SID": "ACTWILIO_ACCOUNT_SID","TWILIO_AUTH_TOKEN": "TWILIO_AUTH_TOKEN","TWILIO_PHONE_NUMBER": "TWILIO_PHONE_NUMBER"}',
VersionId: "EXAMPLE1-90ab-cdef-fedc-ba987SECRET1",
VersionStages: ["AWSPREVIOUS"],
};
// simulated response of SqsEvent
let event = {
Records: [
{
messageId: "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
receiptHandle: "MessageReceiptHandle",
body: '{"message": "Hello world","number": "(506)88888888"}',
attributes: {
ApproximateReceiveCount: "1",
SentTimestamp: "1523232000000",
SenderId: "123456789012",
ApproximateFirstReceiveTimestamp: "1523232000001",
},
messageAttributes: {},
md5OfBody: "{{{md5_of_body}}}",
eventSource: "aws:sqs",
eventSourceARN: "arn:aws:sqs:us-east-1:123456789012:MyQueue",
awsRegion: "us-east-1",
},
],
};
// simulate tokens for Twilio
const accountSid = "ACfjhdskjfhdsiuy876hfijhfiudsh";
const authToken = "fjfuewfiuewfbodfiudfgifasdsad";
//create client with mocked Twilio
const client = new Twilio(accountSid, authToken);
//call messages.create of Twilio client, and give it the expected result created
client.messages.create = jest
.fn()
.mockResolvedValue({ ...smsMessageMock });
console.log(await sendSms(event, data));
//expectes the function sendSms(event, data) to throw an error
await expect(sendSms(event, data)).rejects.toThrowError(
`Failed to send sms message. Error Code: ${smsMessageMock.errorCode} / Error Message: ${smsMessageMock.errorMessage}`
);
});
});
});
(event and data are simulated responses of SqsEvent and GetSecretValueResponse)
The problem is that when I run the npm test it throws me an error of Twilio's authentication, an it is because I'm passing self created tokens.
Expected substring: "Failed to send sms message. Error Code: 123 / Error Message: lorem-ipsum"
Received message: "Authentication Error - invalid username"
at success (node_modules/twilio/lib/base/Version.js:135:15)
at Promise_then_fulfilled (node_modules/q/q.js:766:44)
at Promise_done_fulfilled (node_modules/q/q.js:835:31)
at Fulfilled_dispatch [as dispatch] (node_modules/q/q.js:1229:9)
at Pending_become_eachMessage_task (node_modules/q/q.js:1369:30)
at RawTask.Object.<anonymous>.RawTask.call (node_modules/asap/asap.js:40:19)
at flush (node_modules/asap/raw.js:50:29)
So what I suppose is that the test is connecting to internet and calling Twilio's api.
I appreciate if you could help me.
I think what you want to do is mock the class returned by the module, using jest.mock('twilio', mockImplementation) and in mockImplementation return a function to act as a constructor that will take your account SID and auth token arguments and then return a mockClient implementation, which in this case needs to return an object which has a messages property, which in turn is an object with a create property that is a mock function.
It's probably easier to just show the code.
const mockClient = {
messages: {
create: jest.fn().mockResolvedValue({ ...smsMessageMock });
}
};
jest.mock("twilio", () => {
return function(accountSid, authToken) {
return mockClient;
}
});

NodeJS: Firebase Admin SDK is not initializing

I am trying to run the below cod which initialises the Firebase Admin SDK, and send a notification message.
const admin = require('firebase-admin/app');
const errorCodes = require('source/error-codes');
const PropertiesReader = require('properties-reader');
const prop = PropertiesReader('properties.properties');
exports.sendSingleNotification = async (event, context) => {
const params = event.queryStringParameters;
var serviceAccount = require("xxx-xxx-firebase-adminsdk-xxx-xxx.json");
try {
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
console.log("INITIALIZED");
// This registration token comes from the client FCM SDKs.
const registrationToken = params.fcmtoken;
console.log()
const message = {
notification: {
title: 'FooCorp up 1.43% on the day',
body: 'FooCorp gained 11.80 points to close at 835.67, up 1.43% on the day.'
},
token: registrationToken
};
// Send a message to the device corresponding to the provided
// registration token.
admin.getMessaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent message:', response);
return {"response":response}
})
.catch((error) => {
console.log('Error sending message:', error);
return {"error 1":error}
});
} catch (error) {
console.log(error);
return {"error 2":error}
}
};
Here the serviceAccount means the path of the Firebase private key file which is in the root of this project.
However when I run this code I always end up with the following error.
START RequestId: e66ffdd9-ab9c-4a68-ade2-7cfa97f42c31 Version: $LATEST
at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)] (/var/task/source/fcm/send-single-notification.js:14:42)rt' of undefined
END RequestId: e66ffdd9-ab9c-4a68-ade2-7cfa97f42c31
Something is undefined and I can't figure out what it is or what the error is.
How can I fix this?

408 timeout from Firebase Cloud Messaging after the message

I'm sending push messages using FCM through Firebase Functions. The messages are being sent properly, but I'm getting the 408 time-out error after the message is sent. I'm suspecting it might have to do with the unregistered tokens not being cleaned up because:
if I were to send another message to the same device, the same timeout occurs and
the only error message I get from the Firebase log is Function execution took 60002 ms, finished with status: 'timeout'.
exports.sendMessage = functions.https.onRequest(async (request, response) => {
const {
sender,
recipient,
content,
docID
} = request.body
functions.logger.log(
"docID:",
docID,
);
// Get the list of device notification tokens.
let deviceTokens; let ref;
try {
ref = admin.firestore().collection("deviceToken").doc(recipient);
const doc = await ref.get();
if (!doc.exists) {
console.log("No such document!");
response.status(500).send(e)
} else {
console.log("doc.data():", doc.data());
deviceTokens = doc.data().token;
}
} catch (e) {
response.status(500).send(e)
}
let senderProfile;
try {
senderProfile = await admin.auth().getUser(sender);
console.log("senderProfile", senderProfile);
} catch (e) {
console.log(e);
response.status(500).send(e)
}
// Notification details.
let payload = {
notification: {
title: senderProfile.displayName,
body: content,
sound: "default",
},
data: {
uid: senderProfile.uid,
displayName: senderProfile.displayName,
docID,
messageType: "status"
}
};
functions.logger.log(
"deviceTokens", deviceTokens,
"payload", payload,
);
// Send notifications to all tokens.
const messageResponse = await admin.messaging().sendToDevice(deviceTokens, payload);
// For each message check if there was an error.
messageResponse.results.forEach((result, index) => {
const error = result.error;
if (error) {
functions.logger.error(
"Failure sending notification to",
deviceTokens[index],
error,
);
// Cleanup the tokens who are not registered anymore.
if (error.code === "messaging/invalid-registration-token" ||
error.code === "messaging/registration-token-not-registered") {
const updatedTokens = deviceTokens.filter((token) => token !== deviceTokens[index]);
console.log("updatedTokens", updatedTokens);
ref.update({
token: updatedTokens,
})
.catch(function(e) {
console.error("Error removing tokens", e);
response.status(500).send(e)
});
}
}
});
response.status(200)
});
I'm unsure why the following isn't cleaning up the unregistered tokens:
const updatedTokens = deviceTokens.filter((token) => token !== deviceTokens[index]);
ref.update({
token: updatedTokens,
})
You always have to end HTTP functions with response.status(200).send() or response.status(200).end(). In the above function, you have response.status(200) so you have to end it either with response.status(200).send() or response.status(200).end(). Please check the documentation if it helps.

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