Interact with storefront customer's session in Shopware 6 app - shopware

I need to retrieve a customer connected to the storefront backend side to reward him in different ways.
I created a plugin that extends the plugin.class of the plugins system.
It fetches the customer on the store api using the route store-api/account/customer then it sends to my backend its identifier. I also resolve the shop_url of the admin api with window.location.protocol and window.location.hostname...
This seems to me not secured or accurate (the domain can be different from the sales channel to the admin api) and I would like to know if it would be possible to fetch a secured unique customer's token that would allow me to resolve both the shop_url and the customer's identifier.
I cannot find anything in the documentation that would help me securing that part of my app.
Thanks.
(Edit)
Here is my actual code to fetch the customer inside the plugin:
import Plugin from 'src/plugin-system/plugin.class';
import StoreApiClient from 'src/service/store-api-client.service';
const storeClient = new StoreApiClient();
const handleUser = (data, request) => {
let unsecuredUserId = null;
if (request.status === 200) {
try {
const user = JSON.parse(data);
unsecuredUserId = user.id || null;
} catch (e) {}
}
doSomethingWith(unsecuredUserId);
}
export default class SaylPlugin extends Plugin {
init() {
storeClient.get('store-api/account/customer', handleUser);
}
}

You have access to the customer object in the twig template, given the user is currently logged-in. Using this fact you can pass customer data to your plugin using data attributes. The plugin base offers automatic parsing of options based on the naming convention.
{% set myPluginData = {
customerId: context.customer.id
} %}
<div data-my-custom-plugin="true"
data-my-custom-plugin-options="{{ myPluginData|json_encode }}">
</div>
class MyCustomPlugin extends Plugin {
init() {
if (this.options.customerId) {
// do something when the customer is logged in
}
}
// ...
}
PluginManager.register('MyCustomPlugin', MyCustomPlugin, '[data-my-custom-plugin]');

I finally found a way to get the things more secured.
My new plugin code:
import Plugin from 'src/plugin-system/plugin.class';
import StoreApiClient from 'src/service/store-api-client.service';
const storeClient = new StoreApiClient();
const handleContext = (data, request) => {
if (request.status === 200) {
try {
const context = JSON.parse(data);
if (context instanceof Object) {
resolveCustomerBackendSide(
context.token,
context.salesChannel.id
);
}
} catch (e) {
console.error(e);
}
}
}
export default class SaylPlugin extends Plugin {
init() {
storeClient.get('store-api/context', handleContext);
}
}
With this context I can resolve the admin api credentials backend side using the sales channel identifier that I save during the app registration process (you will have to allow sales_channel read in the app's manifest). Therefore I fetch the sales channel backend side to retrieve the sw-access-key header and I can finally fetch the store-api backend side to retrieve the customer in a secured way (the token that you get after fetching the store-api/context can be used as sw-context-token header.

Related

What is the right way to pass request data to services in nestjs?

I have many services that all need to know the tenant ID from the request (kept in JWT auth token). The request is either GRPC (jwt stored in MetaData) or Graphql (jwt stored in context.headers.authorization).
I would like to be able to force myself not to forget to pass this tenant id when using the services. Ideally I dont want to even have to constantly write the same code to get the info from the request and pass it through. However the only ways I've managed to do it was using:
#Inject(REQUEST) for grpc in the service constructor. This doesn't work for the graphql requests. The only other way I saw was to only return service methods AFTER providing the data, which looks ugly as hell:
class MyService {
private _actions: {
myMethod1() { ... }
}
withTenantDetails(details) {
this._details = details;
return this._actions;
}
}
If I can somehow get the execution context within MyService that would be a good option, and make this easy using:
const getTenantId = (context: ExecutionContext) => {
if (context.getType() === 'rpc') {
logger.debug('received rpc request');
const request = context.switchToRpc().getContext();
const token = request.context.get("x-authorization");
return {
token,
id: parseTokenTenantInfo(token)
};
}
else if (context.getType<GqlContextType>() === 'graphql') {
logger.debug('received graphql request');
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().request;
const token = request.get('Authorization');
return {
token,
id: parseTokenTenantInfo(token)
};
}
else {
throw new Error(`Unknown context type receiving in tenant param decorator`)
}
}
But I can't find any way to get the executioncontext across to the service without also having to remember to pass it every time.
It's possible to inject Request into injectable service.
For that, the Service will be Scope.Request, and no more Singleton, so a new instance will be created for each request. It's an important consideration, to avoid creating too many resources for performance reason.
It's possible to explicit this scope with :
#Injectable({ scope: Scope.REQUEST })
app.service.ts :
#Injectable({ scope: Scope.REQUEST })
export class AppService {
tenantId: string;
constructor(#Inject(REQUEST) private request: Request) {
// because of #Inject(REQUEST),
// this service becomes REQUEST SCOPED
// and no more SINGLETON
// so this will be executed for each request
this.tenantId = getTenantIdFromRequest(this.request);
}
getData(): Data {
// some logic here
return {
tenantId: this.tenantId,
//...
};
}
}
// this is for example...
const getTenantIdFromRequest = (request: Request): string => {
return request?.header('tenant_id');
};
Note that, instead of decode a JWT token in order to retrieve TENANT_ID for each request, and maybe for other service (one per service), an other approach could be to decode JWT one single time, and then add it in Request object.
It could be done with a global Guard, same as authorization guard examples of official docs.
Here just a simple example : (could be merged with a Auth Guard)
#Injectable()
export class TenantIdGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
request['tenantId'] = getTenantIdFromRequest(request);
return true; // or any other validation
}
}
For GraphQL applications, we should inject CONTEXT in place of REQUEST :
constructor(#Inject(CONTEXT) private context) {}
You have to set either request inside context, or directly TENANT_ID inside context in order to retrieve it after inside service.

How can I edit the backend of firebase cloud function extension?

I am coding a project similar to patreon.com and my goal is to let multiple members create their own subscription plans and sell it.
I came across a firebase extension for stripe payments - https://firebase.google.com/products/extensions/stripe-firestore-stripe-payments
The problem with this extension is that I can only create a 1 premium membership that adds custom claims to the auth object and I can validate it like this:
export default async function isUserPremium(): Promise<boolean> {
await auth.currentUser?.getIdToken(true);
const decodedToken = await auth.currentUser?.getIdTokenResult();
return decodedToken?.claims?.stripeRole ? true : false;
}
That means that even if I have 100 different subscriptions, I can only attach single boolean value, which is useless.
I went to the source code and I found this snippet:
// Update their custom claims
if (role) {
try {
// Get existing claims for the user
const { customClaims } = await admin.auth().getUser(uid);
// Set new role in custom claims as long as the subs status allows
if (['trialing', 'active'].includes(subscription.status)) {
logs.userCustomClaimSet(uid, 'stripeRole', role);
await admin
.auth()
.setCustomUserClaims(uid, { ...customClaims, stripeRole: role });
} else {
logs.userCustomClaimSet(uid, 'stripeRole', 'null');
await admin
.auth()
.setCustomUserClaims(uid, { ...customClaims, stripeRole: null });
}
} catch (error) {
// User has been deleted, simply return.
return;
}
}
I don't fully understand this code, but I think this is where the boolean value is assigned.
Would it be possible to somehow edit this source code, so that instead of boolean value, I could store subscription plan ids in Array , so that in the front end I could validate and allow customer to access users content only if he has active plan in that array
?

Detect end of conversation and ask for a feedback in azure Bot

I am creating a chat bot using azure bot framework in Nodejs.
QnA maker to store question answers and one LUIS app.
Now I want to detect end of conversation(either by checking no reply from long time or refreshing a webpage) and add feedback card at the end of conversation.
You can achieve this by use of the onEndDialog method and the use of a separate class to manage the feedback process.
First, I have a component dialog that imports the feedback.js file and calls the associated onTurn() method within onEndDialog.
Next, I create the mainDialog.js file in which MainDialog extends FeedbackDialog. In this way, FeedbackDialog sits "on top" of MainDialog listening for specific user inputs or activities. In this case, it is listening for EndDialog() to be called. You will likely want to add additional validation to be sure it only fires when the EndDialg() you want is called.
Lastly, in the feedback.js file, this is where your feedback code/logic lives. For simplicity, I'm using a community project, botbuilder-feedback, for generating a user feedback interface. The majority of the code is focused on creating and managing the "base" dialog. Additional dialog activity comes from within the botbuilder-feedback package.
For reference, this code is based partly on the 13.core-bot sample found in the Botbuilder-Samples repo.
Hope of help!
feedbackDialog.js:
const { ComponentDialog } = require('botbuilder-dialogs');
const { Feedback } = require('./feedback');
class FeedbackDialog extends ComponentDialog {
constructor() {
super();
this.feedback = new Feedback();
}
async onEndDialog ( innerDc ) {
return await this.feedback.onTurn( innerDc );
}
}
module.exports.FeedbackDialog = FeedbackDialog;
mainDialog.js:
const { FeedbackDialog } = require( './feedbackDialog' );
class MainDialog extends FeedbackDialog {
[...]
}
module.exports.MainDialog = MainDialog;
feedback.js:
const { ActivityTypes } = require('botbuilder');
const { DialogTurnStatus } = require('botbuilder-dialogs');
const Botbuilder_Feedback = require('botbuilder-feedback').Feedback;
class Feedback {
async onTurn(turnContext, next) {
if (turnContext.activity.type === ActivityTypes.Message) {
await Botbuilder_Feedback.sendFeedbackActivity(turnContext, 'Please rate this dialog');
return { 'status': DialogTurnStatus.waiting };
} else {
return { 'status': DialogTurnStatus.cancelled };
}
await next();
};
}
module.exports.Feedback = Feedback;

Access Facebook Messenger User Profile API in DialogFlow

I'm building a cross-platform chatbot in Google's DialogFlow. I'd like to access the Facebook User Profile API to learn the user's first name.
I'm struggling to find advice on how (or if) I can make this happen.
https://developers.facebook.com/docs/messenger-platform/identity/user-profile/
Has anybody here achieved this?
I did that for one of my bots yesterday, you need 2 things, first the Page Token and second is the psid which is Page scope user ID.
On dialogflow, you will receive the request block with psid as sender id. You can find it at:
agent.originalRequest.payload.data.sender.id
This psid needs to be passed to api get request at
/$psid?fields=first_name with your page Token as accessToken to get the first name in response.
You need to make a call to Facebook Graph API in order to get user's profile.
Facebook offers some SDKs for this, but their official JavaScript SDK is more intended to be on a web client, not on a server. They mention some 3rd party Node.js libraries on that link. I'm particularly using fbgraph (at the time of writing, it's the only one that seems to be "kind of" maintained).
So, you need a Page Token to make the calls. While developing, you can get one from here:
https://developers.facebook.com/apps/<your app id>/messenger/settings/
Here's some example code:
const { promisify } = require('util');
let graph = require('fbgraph'); // facebook graph library
const fbGraph = {
get: promisify(graph.get)
}
graph.setAccessToken(FACEBOOK_PAGE_TOKEN); // <--- your facebook page token
graph.setVersion("3.2");
// gets profile from facebook
// user must have initiated contact for sender id to be available
// returns: facebook profile object, if any
async function getFacebookProfile(agent) {
let ctx = agent.context.get('generic');
let fbSenderID = ctx ? ctx.parameters.facebook_sender_id : undefined;
let payload;
console.log('FACEBOOK SENDER ID: ' + fbSenderID);
if ( fbSenderID ) {
try { payload = await fbGraph.get(fbSenderID) }
catch (err) { console.warn( err ) }
}
return payload;
}
Notice you don't always have access to the sender id, and in case you do, you don't always have access to the profile. For some fields like email, you need to request special permissions. Regular fields like name and profile picture are usually available if the user is the one who initiates the conversation. More info here.
Hope it helps.
Edit
Promise instead of async:
function getFacebookProfile(agent) {
return new Promise( (resolve, reject) => {
let ctx = agent.context.get('generic');
let fbSenderID = ctx ? ctx.parameters.facebook_sender_id : undefined;
console.log('FACEBOOK SENDER ID: ' + fbSenderID);
fbGraph.get( fbSenderID )
.then( payload => {
console.log('all fine: ' + payload);
resolve( payload );
})
.catch( err => {
console.warn( err );
reject( err );
});
});
}

Auth0 access control

I am using Auth0 to manage a large set of users across several different applications with some being web based and others desktop and mobile. Under the meta data for each user I have an array of applications each user can access, I wondered how I might check this when authenticating so that access would be refused if not within that list.
I can do this very easily on the applications, however it would be great to do it on Auth0.
Using a Rule defined as follows has provided me with the functionality I was looking for:
function (user, context, callback) {
// ACL object
var acl = {
"someAppName": [ 'user1#mail.com', 'user2#mail.com' ],
"otherApp": ['user2#mail.com']
}
// if App is not in the ACL, skip
if(!acl.hasOwnProperty(context.clientName)){
return callback(null, user, context);
}
// check if user has access to app
var userHasAccess = acl[context.clientName].some(
function (email) {
return email === user.email;
}
);
if (!userHasAccess) {
return callback(new UnauthorizedError('Access denied.'));
}
callback(null, user, context);
}

Resources