KeystoneJS: where are callHook hooks defined? - keystonejs

I want to add logic to the pre:signin and post:signin hooks, but can't find where they are defined. In admin/server/api/session/signin.js, I find the callHook()s for both, but can't find the actual hooks. Help greatly appreciated!

KeystoneJS is using grappling-hook . and thats why you can use callHook.
in node_modules/keystone/index.js you can see this code
var grappling = require('grappling-hook');
grappling.mixin(this).allowHooks('pre:static', 'pre:bodyparser', 'pre:session', 'pre:routes', 'pre:render', 'updates', 'signout', 'signin', 'pre:logger');
I am not sure but you are not finding the actual hooks because they really don't exist.
The methods are defined in keystone and grapple simply makes them hoockable. e.g signin is the method defined in keystone instance and grapple made it hoockable.
And here is the way you will just tell what to do after signing is completed.
keystone.callHook(user, 'post:signin', function (err) {
if (err) return res.json({ error: 'post:signin error', detail: err });
res.json({ success: true, user: user });
});
So in layman term you r saying.. Hay keystone, call my function after This user is signed in. and for that there is really no code inside keystone.
MY knowledge is limited here how the user is passed and how it happens that only when this user logged in. I think, we still need some experts light here.

I had a use-case that required a function be called every time a specific model was added to the database. I found out that you can use the Mongoose middleware specifically the post middleware to achieve such results. So in my case I had a Product schema in which I wanted a function to run everytime a new product was added or updated. It was as easy as adding the following:
keystone.lists.Product.schema.post('save',function(){
console.log('called after new item saved');});

Related

Getting RapidAPI to work with GoogleSheets to extract IMDB data

Google Sheets with RapidAPI
First time trying to get APIs to work! I thought a simple project would be to get a Googlesheet to retrieve movie information based on the title.
Googling around I happened upon RapidAPI which has a Googlesheets add-on. Unfortunately I haven't found much useful documentation so have hit a dead end.
What I've learned so far
There only seems to be one example for how to implement it... by using the =GET() command like so (in this case for pulling finance info):
=GET(”https://investors-exchange-iex-trading.p.rapidapi.com/stock/{symbol}/book”,”quote.companyName”,”YOUR_API_KEY_HERE”,”symbol”,”AAPL”)
I couldn't get this example to work, and the IMDB Code Snippet seems a little different, so I'm not sure how that works at all. Not the curly bracers around {symbol}.
var axios = require("axios").default;
var options = {
method: 'GET',
url: 'https://imdb8.p.rapidapi.com/title/find',
params: {q: 'Dredd'},
headers: {
'x-rapidapi-host': 'imdb8.p.rapidapi.com',
'x-rapidapi-key': '5840855726msh193dee7e1600046p145eddjsnc66aff778896'
}
};
axios.request(options).then(function (response) {
console.log(response.data);
}).catch(function (error) {
console.error(error);
});
When I run a typical search on IMDB, I get a URL that looks like this:
https://www.imdb.com/find?q=dredd&ref_=nv_sr_sm
I notice this q parameter there, which seems important...
I'm not sure how I am meant to format this =GET() command for IMDB data. The example suggests one thing, but Googlesheets suggests another: "GET(url, selectPaths, rapidApiKey)"
I'm not sure what the curly bracers are doing in the example URL.
Whatever I try seems to give the same error message:
Error
Request failed for https://imdb8.p.rapidapi.com returned code 400. Truncated server response: 400 - Bad Request (use muteHttpExceptions option to examine full response) (line 98).
Send Help
Does anyone have a better, working tutorial for using this setup? Or could you direct me to some useful reading material that a layperson could understand?
I found a good resource for you. Check out this well-written article on RapidAPI's official blog.
https://rapidapi.com/blog/api-google-sheets/

Closest Facility Arcgis online javascript not returning data

I am trying to use the Closest Facility (CF) function in ArcGIS API for Javascript. I need to be able to pass a shape coming from a feature service as an incident, and use a feature service with multiple points as the facilities.
Currently when I use the Closest Facility task, nothing happens. No calls are made at all if I look at the network activity.
CFTask.solve(CFParams).then(function (solveResult) {
array.forEach(solveResult.routes, function (route, index) {
console.log(route);
});
});
I understand that i may be passing it incorrect data, but would expect an error message, rather than the nothing I get now.
2 questions:
Does the above code snippet actually run the Closest Facility
function?
How do add data from a feature service to a feature set correctly?
First, verify if an error is triggered inside the promise when you run the code snippet by using catch method:
CFTask.solve(CFParams).then(function (solveResult) {
solveResult.routes.forEach(function(route, index) {
console.log(route);
});
}).catch(console.error);
If you see an error message printed in the console, add it to your question.
Also there is a syntax error in your forEach function

aurelia-authentication OAuth2 response state value differs

I'm attempting an implementation of aurelia-authentication with an OIDC provider (IdentityServer4) and seem to be running into an issue with logging a user out.
The short of it is I'm not able to logout users successfully using the authService.logout function mentioned in the OIDC configuration section (https://aurelia-authentication.spoonx.org/oidc.html).
In looking into it a bit further I've tracked it down to a promise rejection in the logout function which provides the message: "OAuth2 response state value differs"
if (logoutResponse.state !== stateValue) {
return Promise.reject('OAuth2 response state value differs');
}
logoutReponse seems to be the culprit as it's coming through as an object with the state property named incorrectly {/login?state: "qAIxYwKqLHYJtyar2PfdvaROWT1O56P7"}.
I can actually change the if statement to:
if (logoutResponse['/login?state'] !== stateValue) {
return Promise.reject('OAuth2 response state value differs');
}
which seems to be working fine, but requires us to modify the aurelia-authentication source directly.
Any thoughts from anyone as to why the "state" property is coming through as a relative path instead of just "state"?
So after spending more time on this I was able to track the issue down and find a solution.
The solution was to change the aurelia-authentication authConfig postLogoutRedirectUri value to just the root page (http://localhost:8080). Additionally, I needed to define that URI under the PostLogoutRedirectUris within my IdentityServer4 Client definition.
logoutResponse was then coming through correctly with a property named state property that holds the correct value and permits the redirect successfully.

setting permissions on redux actions

I am creating a web redux-react app which will have a number of different permission levels. Many users may be interacting with one one piece of data but some may have limitations on what they can do.
To me, the obvious way to set permissions on interactions on the data (held behind the app server) would be to associate certain permissions with different redux actions. Then, when a user saves their state the client side app would bundle up the users action history and send it back to the server. These actions could then be applied to the data in the server and permissions could be checked, action by action, against a user jwt.
This would mean lots our reducer code could be used isomorphically on the server.
I cannot find any resources/disscussions on this. What is the normal way of handling complex permissions in a redux app? Having auth purely at the endpoint seems cumbersome , this would require rewriting a ton of new code that is already written in client side reducers. Is the any reason not to go ahead and create a reducer which checks auth on each action?
Points:
We must assume actions sent to the server are authenticated, but sent by users that do not have permission dispatch these actions
If the permissions have been checked and are inside the actions then the reducer can check permissions and be pure
I think it's not the responsibility of action creators to check the permissions but using a reducer and a selector is definitively the way to go. Here is one possible implementation.
The following component requires some ACL checks:
/**
* Display a user record.
*
* A deletion link is added if the logged user has sufficient permissions to
* delete the record.
*/
function UserRecord({ username, email, avatar, isGranted, deleteUser }) {
return (
<div>
<img src={avatar} />
<b>{username}</b>
{isGranted("DELETE_USER")
? <button onClick={deleteUser}>{"Delete"}</button>
: null
}
</div>
)
}
We need to connect it to our store to properly hydrate all props:
export default connect(
(state) => ({
isGranted: (perm) => state.loggedUser.permissions.has(perm),
}),
{deleteUser},
(stateProps, dispatchProps, ownProps) => ({
...stateProps,
...ownProps,
deleteUser: () => dispatchProps.deleteUser(ownProps.user)
})
)(UserRecord)
The first argument of connect will create isGranted for the logged user. This part could be done using reselect to improve performance.
The second argument will bind the actions. Nothing fancy here.
The third argument will merge all props and will pass them to the wrapped component. deleteUser is bound to the current record.
You can now use UserRecord without dealing with ACL checks since it will auto-update depending on what is stored in loggedUser.
<UserRecord user={someUser} />
In order to get the above example work you need to store the logged user in Redux's store as loggedUser. You don't need check ACL on actions since the UI won't trigger them if current user lacks of permissions. Moreover, ACL have to be checked server-side.
You can set up an helper function that would be built into actions for checking user rights (locally or remotely) where you would also provide with a callback action creator on error. Of course redux-thunk or similar would be needed so you can dispatch actions from other actions.
The key rule you should observe here is:
Reducers are pure functions.
Action creators can be impure. That means reducers always return the same value given the same arguments. Checking for ACL rights in reducer will violate that rule.
Say let's say you need to fetch the list of contacts. Your action is REQUEST_CONTACTS. The action creator would first dispatch something like:
// ACL test function
function canAccessContacts(dispatch) {
if (user !== 'cool') {
dispatch({type: 'ACCESS_DENIED'});
return false;
}
}
// Action creator
function fetchContacts() {
return (dispatch) => {
if (!canAccessContacts(dispatch)) {
return false;
}
// your logic for retrieving contacts goes here
dispatch({
type: 'RECEIVE_CONTACTS',
data: your_contacts_data_here
});
};
}
RECEIVE_CONTACTS will be fired once you have data back. Time between REQUEST_CONTACTS and RECEIVE_CONTACTS (which is likely an async call) is ian opportunity to show your loading indicator.
Of course, this is a very raw example, but it should get you going.

How to tell whether Accounts.addEmail succeeded or failed, and if it failed, the reason why

I have a page where the user can type in a new email address and then this method attempts to add it to their account:
Meteor.methods({
add_new_email: function(address)
{
Accounts.addEmail(Meteor.userId(), address);
}
});
I'm using the accounts-password package in Meteor.
I'd like to give the user meaningful feedback after they try to add the new address, in particular if it failed why did it fail? I have looked at the docs but there doesn't seem to be any method to find out failure reason.
I know that I can count the user's email addresses before and after trying to add the new one, but that doesn't tell me if the address already belongs to another user, or if it's an existing address of the user's, or whatever is the failure reason.
Is there any way to find out the result of an API call like this?
You can read the information about what this method does here:
https://github.com/meteor/meteor/blob/master/packages/accounts-password/password_server.js#L847
As you can see, the method will fail only in one case:
The operation will fail if there is a different user with an email
only differing in case
Therefore if the method fails you can tell to the user that the email is already registered.
After experimenting some more, it seems that all I need to do is add a callback to my client when I call the method, and check there for an error. Any error is automatically returned to the callback.
Server:
Meteor.methods({
add_new_email: function(address)
{
Accounts.addEmail(Meteor.userId(), address);
}
});
Client:
Meteor.call('add_new_email', 'me#example.com', function(error){
if (error) console.log("got an error " + error.reason);
});
I had not realised that the error from the API would be passed up into my method. Meteor - it's always more clever than I expect!
Note also that you can use Meteor.Error in your methods to throw errors which will be passed up to client callbacks in exactly the same way, see the docs:
if (!Meteor.userId()) {
throw new Meteor.Error("not-authorized", "You must be signed in to write a new post");
}
I know I'm a bit late to the party but I ran into this problem today and found your post.
I needed to be able to tell on the server side whether it failed or not so what I did was put it in a try-catch like so:
let addSucceeded = false;
try{
Accounts.addEmail(user._id, newEmailAddress);
addSucceeded = true;
} catch(err) {}
console.log(addSucceeded);
Only if the Accounts.addEmail does not fail will addSucceeded be set to true. To make sure I don't run into the "fail because it replaced the same user's email address in a different case" scenario, I always toLowerCase() the email address when saving.