Struggling with apollo-link-ws x-api-key authorization - websocket

I'm using Apollo to connect to an AWS Appsync API which is secured with an API Key. No problems with queries and mutations over http but I'm on unfamiliar territory using websockets for subscriptions.
Using apollo-link-ws I have the following, and I have no idea how to add the API Key. Can any kind soul offer advice?
const ws = new WebSocketLink({
uri: `wss://xxxxx.appsync-api.eu-west-1.amazonaws.com/graphql`,
options: {
reconnect: true
}
});
BTW I'm assuming that the url for wss is the same as for http....

apollo-ws-link does not directly work with AWS AppSync subscriptions. Take a look at the AWS Amplify GraphQL client that handles authorization with AppSync as well as subscriptions. This should get your app working with AppSync in a couple minutes.
If you want to implement your own subscriptions client, the handshake is documented within the Amplify library.

Authorization is usually based on connectionParams, but AWS decided to go different way, and they implemented it some shady way using REST and custom headers.
The problem is Websockets doesn't support custom headers. What makes it difficult to integrate with Apollo.
AppSync has much more flaws than just this one. I wouldn't recommend it for anything more ambitious than a blog or chat anyway. This article explain better some of its drawbacks.

Have you checked out the AppSync guide for "Building a JavaScript Client App": https://docs.aws.amazon.com/appsync/latest/devguide/building-a-client-app-javascript.html
With AWSAppSyncClient setting up subscriptions is effortless. It uses websockets automatically when setting up subscriptions.
I haven't even been aware of apollo-link-ws and such complexities when using AppSync subscriptions with Apollo. I only have experience of AppSync subscriptions with the React client but the plain JavaScript usage seems comparatively simple.
From the example in the guide:
const client = new AWSAppSyncClient({
url: url,
region: region,
auth: {
type: type,
credentials: credentials,
}
});
client.hydrated().then(function (client) {
//Now subscribe to results
const observable = client.subscribe({ query: subquery });
const realtimeResults = function realtimeResults(data) {
console.log('realtime data: ', data);
};
observable.subscribe({
next: realtimeResults,
complete: console.log,
error: console.log,
});
});
// Set up a subscription query
const subquery = gql(`
subscription NewPostSub {
newPost {
__typename
id
title
author
version
}
}`);
For Node.js usage, the guide also shows how to set up global.WebSocket and such dependencies in the example code.

Related

How to log in from a single page react app to Laravel 7.x on another domain?

I have a single page create-react-app running on localhost:3000 and I want to log in to a laravel 7.x instance running on myapp.loc (vhost).
Eventually I would like a single page running on app.mysite.com with laravel running on api.mysite.com.
I'm able to log in to my laravel instance directly from myapp.loc. I've installed Laravel passport and the scaffolding, etc and can create Client IDs and Secrets, but I'm unsure if they are needed and if so, how to use them.
What I am unsure of and cannot find any documentation for, is how to log in to laraval from my SPA (running on localhost:3000). I have set my CORS headers and can connect requests that don't require auth, but I don't know how to log in or structure auth requests once logged in.
I can't find any documentation on axios.post / get requests with a focus on logging in from another domain and maintain user-based access.
Since I don't know enough to ask a concise question, there are three layers that I feel like I should be searching for an answer.
Is it possible for laravel to act as the backend for my single page app from another domain?
If so, are there documented, best practices / well worn paths for accomplishing this?
If so, what would a sample axios login and subsequent session call look like? (e.g. payload and header shape)
Yes you can, I suggest to use https://laravel.com/docs/7.x/sanctum instead of passport because is easier to setup and it was created especially for this scenario.
You need to configure CORS using the official Laravel Package https://github.com/fruitcake/laravel-cors this way you will open Laravel's CORS to be able to reach it from anywhere localhost, or any domain that you can set up into allowed_origins. inside the cors.php config file according to the documentation of the package.
After configuring Sanctum/Passport and ensuring you are generating the required token for your SPA using the createToken method described in Sanctum or Passport docs, you have to save the token to connect to your protected endpoints however they recommend to use cookie SPA based authentication but that's not strictly necessary.
Create an API Service
In this example I will create an API Service to encapsulate API calls
import axios from 'axios';
const URI = 'https://yourlaravel.api/api/';
axios.defaults.headers.common = { Accept: 'application/json', 'Content-Type': 'application/json' };
const ApiInstance = axios.create();
const API = {
login: (user) => {
return ApiInstance.post(`${URI}login`, user);
},
getUser: () => {
return ApiInstance.get(`${URI}user`);
},
setUser: (user) => {
return ApiInstance.post(`${URI}user`, user);
},
};
Send A Login Request to your login endpoint and save the token
import API;
API.login({email:'mail#domain.com',password:'32323'})
.then(response=>{
//save the token
//response.data.accessToken
})
Fetch data from your protected endpoints using the token
//Set the default authorization header when you have an access token
axios.defaults.headers.common = {'Authorization': `Bearer ${token}`}
//Get your data
API.getUser().then(response=>{
//response.data
})
//Post something
API.setUser({user:'Os'}).then(response=>{
//response.data
})
All those things are possible, you just need to set up cors and you are good to go. For auth you can use passport or your own custom app key setup, it all depends on what you are trying to achieve. I suggest reading up about RESTfull apis, that would be a good start.
In order to perform a handshake between FE and BE on FE you would have a login form submission of which will send e request to BE (backend api) and if login is success you send back a key which then FE should store. Any future requests from FE should append that key in the header to gain access to authorised areas.
There should be plenty of information on this subject (RESTfull Api & Token authentication) on google.

Can't get conversationUpdate activity with the Enhanced Direct Line Authentication Features

I'm trying to use the Enhanced Direct Line Authentication Features so I can get rid of the Magic Number.
I just enabled this option and added the trusted origin (https://mychatbot.azurewebsites.net/ <- Not the real one, but is stored on Azure) to the DirectLine.
Then on the code of the website I request the token:
const options = {
method: 'POST',
uri: 'https://directline.botframework.com/v3/directline/tokens/generate',
headers: {
"Authorization": "Bearer MyDirectLineSecret"
},
json: {
User: {
id: "dl_" + uuid.v4(),
name: "UserTest"
},
trustedOrigins: ["https://mychatbot.azurewebsites.net/"]
}
Then I make the request for the token:
const response = await rp(options);
const token = response.token;
Like that I have the token and when I go to my bot website (https://mychatbot.azurewebsites.net/) I don't send the updateActivity request and can't send the user the welcome message.
I don't know if I'm doing something wrong about the DirectLine configuration.
Is there anything I should change? I'm using an app service for the bot framework and inserting directly the webchat uri in the trusted origins. I don't know if I am wrong in the request of the token.
You aren't doing anything wrong. This is a known issue in the DirectLine Connector Service, and the development team is currently working to resolve the issue. Essentially, the second conversation update is not being sent because the user id in the token is causing an error. For more details, checkout this issue on Github. I'll be sure to let you know when it is resolved as well.
In the meantime, I would recommend taking a look at the Web Chat Backchannel Welcome Event sample.

Different graphql endpoint for authenticated requests

How do you provide a different endpoint for authenticated GraphQL queries using Apollo Server? i.e. /graphql for public queries and /auth/graphql for private queries? I can find plenty of examples for doing both over a single endpoint but none for multiple endpoints.
If you are using apollo-server-express, you can create multiple ApolloServer instance and applyMiddleware to express app.
const app = express();
// create server1, server2 with its schema
// then
server1.applyMiddleware({ app, path: '/auth/graphql' });
server2.applyMiddleware({ app, path: '/graphql' });

Google People API gapi.client.people is undefined

After I init the google people api gapi.client like so:
gapi.client.init with object containing the scope, api key, and client id:
init_people_api = async () => {
await gapi.client.init({
apiKey: API_KEY,
clientId: CLIENT_ID,
scope: "https://www.googleapis.com/auth/contacts",
})
await gapi.client.people.people.connections.list({
'resourceName': 'people/me',
'pageSize': 10,
'personFields': 'names,emailAddresses',
}).then(function(response) {
console.log(response)
})
}
When I call gapi.client.people, it is undefined.
gapi.client gives me this object:
Can anyone help me figure out how to debug this? Im doing what the tutorial indicates to do. My user is authenticated, I have waited for the api library to load, waited for gapi.client to initialize, but now when I ask for the gapi.client.people, nothing is there. How can I load this function?
You need to set the discoveryDocs parameter to People API discovery doc ["https://www.googleapis.com/discovery/v1/apis/people/v1/rest"] in order to get the People API loaded in GAPI. Please look at the documentation sample for an example of how to do this.
I seem to get the same problem when my API key is invalid. Make sure that you're creating and using a separate API key and not accidentally using the client secret instead. Also, double check that your API key isn't restricted in any way in the its settings.
If you don't find any problems there, try regenerating your API key or making a new one. Let me know if you're still running into this problem! :)

Discord Bot using AWS Lambda

I am trying to build a discord bot and was wonderig if i can use AWS lambda for it. So far i haven't seen someone did it already so was confuse if its possible or not. Specially when lambda's aren't active all the time.
ancient question, but this is now possible, since discord starting providing outgoing webhooks a few weeks ago
(shameless self plug) i wrote a bit of a guide here
This is now possible but following an integration approach rather than the traditional bot approach. This differs as detailed below.
The slash commands official documentation gives us this info:
Slash Commands and Interactions bring something entirely new to the table: the ability to interact with an application without needing a bot user in the guild.
You are now able to receive interaction events via your preferred URL endpoint such as an AWS Lamba function, Firebase Cloud function, Azure Cloud function, etc.
This official receiving an interaction section tells us:
In your application in the Developer Portal, there is a field on the main page called "Interactions Endpoint URL". If you want to receive Interactions via outgoing webhook, you can set your URL in this field.
It is important to remember, though, that the data sent to the endpoint URL for interactions is not the same as running a bot client, in fact they make comment on that here:
In many cases, you may still need a bot user. If you need to receive gateway events, or need to interact with other parts of our API...
In my opinion, although We can post messages by AWS Lambda using Webhook, But we can not receive and process messages using AWS Lambda.
This is because discord does not provide message posting events.
here's a minimal implementation in nodejs that will give you an acceptable Interactions Endpoint URL:
serverless.yml
service: discord
variablesResolutionMode: 20210326
frameworkVersion: '2'
provider:
name: aws
runtime: nodejs14.x
stage: whatever
region: us-east-1
lambdaHashingVersion: 20201221
iam:
role:
name: discord
# https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-access.html
statements:
-
Effect: Allow
Action:
- 'ssm:DescribeParameters'
Resource: '*'
-
Effect: Allow
Action:
- 'ssm:GetParameter'
Resource:
- 'arn:aws:ssm:us-east-1::parameter/discord_token'
- 'arn:aws:ssm:us-east-1::parameter/discord_application_id'
- 'arn:aws:ssm:us-east-1::parameter/discord_public_key'
functions:
interactions:
handler: handler.interactions
environment:
DISCORD_TOKEN: ${ssm:/discord_token}
DISCORD_APPLICATION_ID: ${ssm:/discord_application_id}
DISCORD_PUBLIC_KEY: ${ssm:/discord_public_key}
events:
-
http:
path: interactions
method: post
cors: true
handler.js
'use strict';
const nacl = require('tweetnacl');
module.exports.interactions = async (event) => {
const verified = nacl.sign.detached.verify(
Buffer.from(event.headers['x-signature-timestamp'] + event.body),
Buffer.from(event.headers['x-signature-ed25519'], 'hex'),
Buffer.from(process.env.DISCORD_PUBLIC_KEY, 'hex')
);
const body = JSON.parse(event.body);
const response = {
body: JSON.stringify(
verified
? { type: body.type }
: { error: 'invalid request signature' },
null,
2
),
statusCode: verified
? 200
: 401
};
console.log(JSON.stringify({ event, response }, null, 2));
return response;
};

Resources