I am looking for a way to authorize an rpc via NestJS grpc microservices. Basically I want to attach a token in the grpc client, which has to be validated by the grpc server. I found this issue, but I did not get where exactly grpc.Metadata pass as a second argument.
I found a way to solve this. For sure not the most elegant solution, but it works for now. A simplified example:
// gRPC client
#Get(':id')
async call(#Param() params) {
const metadata = new grpc.Metadata();
// add relevant data to the metadata object (e.g from request header)
metadata.add('role', 'admin');
return this.userService.findOne({ id: +params.id}, metadata);
}
// Service
interface UserService {
findOne(data: {id: number}, metadata: grpc.Metadata): Observable<any>;
}
// gRPC server
#GrpcMethod('UserService', 'FindOne')
async findOne(data: UserById, metadata: grpc.Metadata) {
const meta = metadata.getMap();
// do something with the metadata...
console.log(meta.role);
const items: User= [
{ id: 1, name: 'User 1' },
{ id: 2, name: 'User 2' },
{ id: 3, name: 'User 3' },
];
return items.find(({ id }) => id === data.id);
}
Related
How do we pass headers to Apollo server executeOperation in tests?
There is mention about passing a headers object here
I'm trying to pass an auth header with or without a JWT token to test access control.
const result = await server.executeOperation({ query: query, http: { headers: { authorization: "" } } })
// Type '{ authorization: string; }' is not assignable to type 'Headers'.
// Object literal may only specify known properties, and 'authorization' does not exist in type 'Headers'.ts(2322)
This results in a type error. There is a Headers class defined in the Apollo server types in fetch.d.ts but I'm un able to import to instantiate it.
Using "apollo-server": "^2.25.2". Any hints or links to get this going?
Update: as a work around I'm decrypting and decoding the JWT in the server context and passing an authenticated user around in there. Then I'm able to mock the whole context and create a new test server with the mocked context. It'd be nice to be able to user headers for more production like experience but this works for now.
import { mockDeep, mockReset } from 'jest-mock-extended'
interface Context {
prisma: PrismaClient
user: () => User|null
}
export const context = mockDeep<Context>()
export const testServer = new ApolloServer({
typeDefs,
resolvers,
context
});
// ...
context.user.mockReturnValue({
id: 1,
name: "Foo",
slug: "foo",
})
const res = await testServer.executeOperation({ query: query })
Apollo Server now supports a second parameter for executeOperation. It is possible to provide the parameters used in the context function.
const result = await server.executeOperation(
{ query: query },
{ req: { headers: { authorization: '...' } } }
);
Note: With Typescript, it will complain but you can cast it in any and avoid building the whole express.Request type.
Tested with apollo-server-core#3.5.0
The other solution works but I find this one convenient. If you mock the token parsing, you can easily simulate calls from different users.
To solve this, I have created a 'fake' request object with the already decrypted JWT token that I used to initialize my third party auth object (keycloak) and pass to the apollo context. I need to initialize the keycloak object because I have authentication schema directives that require the keycloak object to be initialized.
const req = {
kauth: {
grant: {
access_token: {
isExpired: () => {
return false;
},
token: "abc",
content: {
email: "me#me.com",
resource_access: {
"my-api": {
roles: ["admin"],
},
},
},
},
},
},
};
server = new ApolloServer({
schema,
resolvers,
dataSources: () => ({
users,
}),
context: () => {
return { kauth: new KeycloakContext({ req }) };
},
});
});
Still, I would like a solution that is more native to apollo and not a work around.
Update
I'm able to get my original code, and the suggestions as well working when running it in isolation. However, what I need to do is call it from within a Firebase onRequest or onCall function. When this code gets wrapped by these, the malformed headers and request for authorization are still an issue. We use many other APIs this way so it's puzzling why the Clarifiai API is having these issues. Any suggestions on using it with Firebase?
Original
New to Clarifai and having some authentication issues while attempting to retrieve model outputs from the Food Model.
I've tried two different keys:
API key generated from an app I created in the Portal
API key - the Personal Access Token I generated for myself
In both cases I encounter an Empty or malformed authorization header response.
{
"status":{
"code":11102,
"description":"Invalid request",
"details":"Empty or malformed authorization header. Please provide an API key or session token.",
"req_id":"xyzreasdfasdfasdfasdfasf"
},
"outputs":[
]
}
I've following the following articles to piece together this code. This is running in a Node 10 environment.
Initialization
Food Model
Prediction
const { ClarifaiStub } = require('clarifai-nodejs-grpc');
const grpc = require('#grpc/grpc-js');
const stub = ClarifaiStub.json();
const metadata = new grpc.Metadata();
metadata.set("authorization", "Key xyzKey");
return new Promise((resolve, reject) => {
stub.PostModelOutputs(
{
model_id: 'bd367be194cf45149e75f01d59f77ba7',
inputs: [{ data: { image: { url: 'https://samples.clarifai.com/metro-north.jpg' } } }],
},
metadata,
(err, response) => {
if (err) {
return reject(`ERROR: ${err}`);
}
resolve(JSON.stringify(response));
}
);
});
}
Update: There was an issue in versions prior to 7.0.2 where, if you had another library using #grpc/grpc-js with a different version, the grpc.Metadata object wasn't necessarily constructed from the library version that clarifai-grpc-nodejs was using.
To fix the issue, update the clarifai-grpc-nodejs library, and require the grpc object like this:
const {ClarifaiStub, grpc} = require("clarifai-nodejs-grpc");
Previously, the grpc object was imported directly from #grpc/grpc-js, which was the source of the problem.
There are two ways of authenticating to the Clarifai API:
with an API key, which is application-specific, meaning that an API key is attached to an application and can only do operations inside that application,
with a Personal Access Token (PAT), which is user-specific, which means you can assess / manipulate / do operations on all the applications the user owns / has access to (and also create/update/delete applications themselves).
When using a PAT, you have to specify, in your request data, which application you are targeting. With an API key this is not needed.
I've tested your example (using Node 12, though it should work in 10 as well) with a valid API key and it works fina (after putting it into an async function). Here's a full runnable example (replace YOUR_API_KEY with your valid API key).
function predict() {
const { ClarifaiStub } = require('clarifai-nodejs-grpc');
const grpc = require('#grpc/grpc-js');
const stub = ClarifaiStub.json();
const metadata = new grpc.Metadata();
metadata.set("authorization", "Key YOUR_API_KEY");
return new Promise((resolve, reject) => {
stub.PostModelOutputs(
{
model_id: 'bd367be194cf45149e75f01d59f77ba7',
inputs: [{ data: { image: { url: 'https://samples.clarifai.com/metro-north.jpg' } } }],
},
metadata,
(err, response) => {
if (err) {
return reject(`ERROR: ${err}`);
}
resolve(JSON.stringify(response));
}
);
});
}
async function main() {
const response = await predict();
console.log(response);
}
main();
If you want to use a PAT in the above example, two things must change. Firstly, replace the API key with a PAT:
...
metadata.set("authorization", "Key YOUR_PAT");
...
To the method request object, add the application ID.
...
stub.PostModelOutputs(
{
user_app_id: {
user_id: "me", // The literal "me" resolves to your user ID.
app_id: "YOUR_APPLICATION_ID"
},
model_id: 'bd367be194cf45149e75f01d59f77ba7',
inputs: [{ data: { image: { url: 'https://samples.clarifai.com/metro-north.jpg' } } }],
},
...
Make sure that you have respected the format to pass the key in your code as such:
const metadata = new grpc.Metadata();
metadata.set("authorization", "Key {YOUR_CLARIFAI_API_KEY}");
Make sure that "Key" is present.
Let me know.
EDIT: So looks like Firebase doesn't support custom headers. This is likely impacting the 'Authorization' header. At least this is my best guess. See the comments in the following ticket.
Firebase hosting custom headers not working
The following code works for me:
{
const { ClarifaiStub } = require('clarifai-nodejs-grpc');
const grpc = require('#grpc/grpc-js');
const stub = ClarifaiStub.json();
const metadata = new grpc.Metadata();
metadata.set("authorization", "Key {APP API KEY}");
return new Promise((resolve, reject) => {
stub.PostModelOutputs(
{
model_id: 'bd367be194cf45149e75f01d59f77ba7',
inputs: [{ data: { image: { url: 'https://samples.clarifai.com/metro-north.jpg' } } }],
},
metadata,
(err, response) => {
if (err) {
return reject(`ERROR: ${err}`);
}
console.log(JSON.stringify(response));
resolve(JSON.stringify(response));
}
);
});
}
There was a missing { although I'm not sure if that is what is reflected in the actual code you are running. I'm using in this case an APP API Key (when you create an App, there will be an API Key on the Application Details page.
It sounds like you might be using a Personal Access Token instead, which can be used like this:
{
const { ClarifaiStub } = require('clarifai-nodejs-grpc');
const grpc = require('#grpc/grpc-js');
const stub = ClarifaiStub.json();
const metadata = new grpc.Metadata();
metadata.set("authorization", "Key {Personal Access Token}"); // Sounds like you've made the personal access token correctly - go into settings, then authentication, then create one. Make sure it has proper permissions (I believe all by default).
return new Promise((resolve, reject) => {
stub.PostModelOutputs(
{
user_app_id: {
user_id: "{USER ID}", // I used my actual ID, I did not put 'me'. You can find this under your profile.
app_id: "{APP NAME}" // This is the app ID found in the upper left corner of the app after it is created - not the API Key. This is generally what you named the app when you created it.
},
model_id: 'bd367be194cf45149e75f01d59f77ba7',
inputs: [{ data: { image: { url: 'https://samples.clarifai.com/metro-north.jpg' } } }],
},
metadata,
(err, response) => {
if (err) {
return reject(`ERROR: ${err}`);
}
console.log(JSON.stringify(response));
resolve(JSON.stringify(response));
}
);
});
}
Make sure to fill out the: {Personal Access Token}, {USER ID} and {APP NAME}. I used my actual user id (found in the profile), and the app name is not the API Key for the app, but the name in the upper left corner when you're on the Application details page. This call worked for me.
I am using version 7.2.0 of firebase admin to send fcm push notification, using sendMutlicast method:
async function sendPushRequest({tokens, title, body, customData}) => {
const message = {
notification: {
title,
body,
},
data: customData,
tokens,
}
return firebase.messaging().sendMulticast(message)
}
This is the error I am getting
Error: Exactly one of topic, token or condition is required
at FirebaseMessagingError.Error (native)
at FirebaseMessagingError.FirebaseError [as constructor] (/user_code/node_modules/firebase-admin/lib/utils/error.js:39:28)
...
I tried logging the data and here is the object that sendPushRequest function is called with:
{
tokens: [ null, null, null, 'home-test', null, null ], // this one is a recent sample, I've been getting this error for a while now
title: 'some string',
body: 'some other string',
customData: {
title: 'some string',
body: 'some other string',
bigText: 'again another string',
color: '#9f0e27',
smallIcon: 'notificon',
sound: 'default'
}
}
I'm not sure what is causing the error!
I struggled with this problem too, its quite difficult to configure google admin firebase in nodejs. I find out there is a package that can handle this nicely.
https://www.npmjs.com/package/fcm-notification
but it has some little problem . you can not pass it multiple firebase configuration. here is some example :
const fcm = require('fcm-notification');
const fcm_key = require('../config/customer/fcm.json');
const FcM = new fcm(fcm_key);
module.exports.sendToSingleUser = async (message, token) => {
let message_body = {
notification: {
...message
},
token: token
};
FcM.send(message_body, function (err, response) {
if (err) {
return err
} else {
return response
}
})
}
Facing this error too. Figure out that our tokens array contains null or undefiend value. Resolved by remove that from tokens array and everything works fine.
This might be very simple, but I cannot find any reference regarding this.
I integrated the chat bot in the web app using direct line API; I’m using this API to generate conversation id:
POST: https://directline.botframework.com/v3/directline/conversations
I’m trying to get the generated conversation id from the above API (from web app code) to the chatbot code (NodeJS). Is there a way or any reference to do this?
one way as per this issue comment is to send data to bot before starting the conversation using:
var params = BotChat.queryParams(location.search);
var my_token = params['my_token'];
var botConnection = new BotChat.DirectLine({
secret: 'DIRECTLINE_SECRET'
});
BotChat.App({
botConnection: botConnection
,user: { id: 'USER_ID', name: 'User' } // user.id auto updates after first user message
}, document.getElementById("bot"));
botConnection.connectionStatus$.subscribe(function (status) {
if (status == 2) { // wait for connection is 'OnLine' to send data to bot
var convID = botConnection.conversationId;
botConnection.postActivity({
from: { id: convID } // because first time user ID == conversation ID
,type: 'event'
,name: 'registerUserData' // event name as we need
,value: my_token // data attached to event
}).subscribe(function (activityId) {
// This subscription is a MUST
// If I remove this handler the postActivity not reaches the bot
});
}
});
Here you subscribe to the botConnection.connectionStatus$ and when the status is equal to 2, you get the conversation ID from the botConnection object.
Then, you can add this middleware code in the bot code to get the data:
bot.use({ receive: function(event, next) {
if (!!event && !!event.address && event.name == 'registerUserData') {
var message = new builder.Message().address(event.address).text('my_token:' + event.value);
bot.send(message, function (err) {}); // simulate proactive message to user
}
next();
} });
Hope this helps.
I resolve it using Botframework Web Chat back channel, here's the link for reference:
https://github.com/Microsoft/BotFramework-WebChat
After I generated the conversation id using directline API:
POST: https://directline.botframework.com/v3/directline/conversations
I send data from the web app to the chatbot via backchannel.
<div id="webchat"></div>
<script>
(async function () {
// We are using a customized store to add hooks to connect event
const store = window.WebChat.createStore({}, ({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
// When we receive DIRECT_LINE/CONNECT_FULFILLED action, we will send an event activity using WEB_CHAT/SEND_EVENT
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
value: { conversation_id: conversationID }
}
});
}
return next(action);
});
window.WebChat.renderWebChat({
directLine: window.WebChat.createDirectLine({ token }),
store
}, document.getElementById('webchat'));
document.querySelector('#webchat > *').focus();
})().catch(err => console.error(err));
//Note: conversationID and token is generated in the backend code of the web app.
</script>
How would you make an uptime command with tmi.js, I thought it would be as simple as;
client.on("chat", function(channel, user, message, self, uptime){
client.say("CHANNEL", "Channel has been live for " + uptime
})
How would you go about making this command, the example I gave does not work and I would request you to let me know.
It doesn't look like channel metadata is available over the Twitch WebSocket API. If you want to get that information, you'll need to go through the "New Twitch API" and use the /streams endpoint.
You will also need to have a Client-ID to make requests to this endpoint. You can get one by following the instructions here: Apps & Authentication Guide.
Once you have a Client-ID, you can make a request. I'm using the node-fetch module to make requests easier. This example will get the 2 most active streams. You can adjust the query string parameters to get the appropriate stream.
const querystring = require("querystring"),
fetch = require("node-fetch");
const CLIENT_ID = "YOUR_CLIENT_ID";
const STREAMS_URL = "https://api.twitch.tv/helix/streams";
const qs = querystring.stringify({
first: 2
});
const qUrl = `${STREAMS_URL}?${qs}`;
const fetchArgs = {
headers: {
"Client-ID": CLIENT_ID
}
};
fetch(qUrl, fetchArgs)
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));
This would print out something like the following:
{
data: [{
id: '28378863024',
user_id: '19571641',
game_id: '33214',
community_ids: [],
type: 'live',
title: 'Morning Stream! | #Ninja on Twitter and Insta ;)',
viewer_count: 107350,
started_at: '2018-04-18T14:58:45Z',
language: 'en',
thumbnail_url: 'https://static-cdn.jtvnw.net/previews-ttv/live_user_ninja-{width}x{height}.jpg'
},
{
id: '28379115264',
user_id: '22859264',
game_id: '32399',
community_ids: [Array],
type: 'live',
title: 'LIVE: Astralis vs. Space Soldiers - BO1 - CORSAIR DreamHack Masters Marseille 2018 - Day 1',
viewer_count: 54354,
started_at: '2018-04-18T15:28:44Z',
language: 'nl',
thumbnail_url: 'https://static-cdn.jtvnw.net/previews-ttv/live_user_dreamhackcs-{width}x{height}.jpg'
}]
}
The started_at property is the timestamp of when the stream began. Bear in mind that this API is rate limited so you should probably cache the started_at so you don't immediately run out of requests.