Update user in CASL - javascript

I'm using a very basic CASL implementation. Unfortunately, the docs aren't that detailed. I have the following code (basically copy-pasted from the docs).
import { abilitiesPlugin } from '#casl/vue'
import defineAbilitiesFor from './ability'
const ability = defineAbilitiesFor({name: 'guest'})
Vue.use(abilitiesPlugin, ability )
where defineAbilitiesFor is defined as (in ./ability.js)
import { AbilityBuilder } from '#casl/ability'
function defineAbilitiesFor(user) {
return AbilityBuilder.define((can, cannot) => {
can(['read'], 'foo', { username: user.name})
})
}
I know it's possible to update the rules/conditions (i.e. ability.update([])). But how do I update the user's information after initializing CASL? (e.g. after the user has logged in

CASL has nothing to do with user. What eventually it cares is only user's permissions. So, after login you need to update rules, basically use ability.update(myRules)
In your Login component, after login request to API (or after you receive information about currently logged in user), you need to call ability.update(defineRulesFor(user)).
ability can be just an empty Ability instance. For example:
const ability = new Ability([])
function defineRulesFor(user) {
const { can, rules } = AbilityBuilder.extract()
can(['read'], 'foo', { username: user.name })
return rules
}
// Later after login request to API (or after you receive information about currently logged in user)
login() {
return http.post('/login')
.then((response) => {
ability.update(defineRulesFor(response.user))
// after that Ability instance contains rules for returned user
})
}

Related

How to setup like a function should be called only one time even after reload

I'm trying to make a Post request on component Mount. But if user reloads the page or states changes, then the function is called again as I'm useEffect and it sends the request again. But I want any better thing where the Post request should be made once and if even the page refreshes the shouldn't be called again if it has been called.
I'm using the Function base component. and make Post requests using redux.
const Main = () => {
// ....
// Here I'm forcing user to login if there's user is logged in then want to make a silent post request, But it sends request everytime on state change.
useEffect(() => {
getLocalStorage()
if (!userInfo) {
setModalShow(true)
}
if (userInfo) {
dispatch(postRequest())
setModalShow(false)
}
}, [userInfo])
return (
<div>Some JSX </div>
)
}
export default Main
So need your help to fix that issue. Can we use localStorage to store the information either the post request is already have been made or any other better idea?
Best way is to use localstorage, not sure if my placements of setting ang getting value from localstorage are on the right spot.
const Main = () => {
// ....
// Here I'm forcing user to login if there's user is logged in then want to make a silent post request, But it sends request everytime on state change.
useEffect(() => {
getLocalStorage()
// Check if the value of logged is true initiali will be false until the
// first request if made
if (!!localStorage.getItem('logged')) {
setModalShow(true)
}
if (userInfo) {
dispatch(postRequest())
setModalShow(false)
// set the value when the request is finished
localStorage.setItem('logged', true)
}
}, [userInfo])
return (
<div>Some JSX </div>
)
}
export default Main
There is a package named redux-persist that you can save the state, for example in localStorage. You can use this package, and send post request if there is not any data in state.
Using localStorage for that purpose is pretty useful, you can save the information on post request whether it was made or not.
For a basic setup;
this could be like that:
const postRequestStatus = localStorage.getItem('postRequestMade') ? JSON.parse(localStorage.getItem('postRequestMade')) : null
useEffect(() => {
getLocalStorage()
if (!userInfo) {
setModalShow(true)
}
if (userInfo) {
setModalShow(false)
if (!postRequestStatus) {
dispatch(postRequest())
console.log('Post Request Made')
localStorage.setItem('postRequestMade', true)
}
}
}, [userInfo, postRequestStatus])
Here's a catch. As far there is information in localStorage, of postRequestMade true . The request won't be made. So some point on the site you should set any logic to clear it out where it is necessary.
Secondly, What if the request was not successful if there was an error from the server. Then, you should also consider error handling as well. As you mentioned you are using redux and I'm sure there would be Axios as well try the functionality like that:
useEffect(() => {
getLocalStorage()
if (!userInfo) {
setModalShow(true)
}
if (userInfo) {
setModalShow(false)
if (!postRequestStatus) {
dispatch(postRequest())
// That block will take care if request was successful
// After a successful request postRequestMade should be set to true.
if (success) {
console.log('Successful Request')
localStorage.setItem('postRequestMade', true)
}
}
}
}, [userInfo, postRequestStatus, success])

Trigger won't return the proper user object in Realm/MongoDB

With Realm sync of MongoDB, I'm trying to launch a trigger when a realm user is created to insert his newly created ID into my cluster. Here's the javascript function I made that is being called by the trigger :
exports = async function createNewUserDocument({ user }) {
const users = context.services
.get("mongodb-atlas")
.db("BD")
.collection("patients");
const query = { email: context.user.data.email };
const update = {
$set: {
patientId: context.user.id
}
};
// Return the updated document instead of the original document
const options = { returnNewDocument: true };
console.log(context.user.data.email);
return users.findOneAndUpdate(query, update, options)
.then(updatedDocument => {
if(updatedDocument) {
console.log(`Successfully updated document: ${updatedDocument}.`)
} else {
console.log("No document matches the provided query.")
}
return updatedDocument
})
.catch(err => console.error(`Failed to find and update document: ${err}`))
};
When running from the embed editor, while specifying the proper user manually, it's working perfectly. However, when launched by the trigger, it looks like the user is the system user and not the created user, because the error I get in the logs is the same I get when I run from the editor by specifying System user, which is Failed to find and update document: FunctionError: cannot compare to undefined. This makes sense because the System user is not a user per se, so the context.user is undefined.
I find it weird since I specify in the function settings that it should be executed with the permissions of the user calling the function. So my question is, is it possible to access the user.context of a user on his creation, and if so, how would I do it ?

How to hit/consume post and get api in React Native with Ignite Bowser 2 Boilerplate. (Mobx state stree, type script)

I am new to React Native, please provide some Github link or your own code for reference. Consider me as a beginner in RN.
I found very less open support for RN, Mobx State tree, Ignite and all, so not just post and get API reference, if you find anything helpful related to these above-mentioned topics, Feel free to share.
Thanks in advance.
Mobx State Tree, With Ignite Bowler you would have api.ts file where you can specify API calls.
async getUser(userToken: string): Promise<Types.GetUserResult> {
// make the api call
const response: ApiResponse<any> = await this.apisauce.post(`api/v1/sales/login?authCode=${userToken}`)
if (!response.ok) {
const problem = getGeneralApiProblem(response)
if (problem) return problem
}
// transform the data into the format we are expecting
try {
try {
const rawUser = response.data
console.log('rawUser'+ rawUser)
const user: UserSnapshot = convertRawUserToUserStore(rawUser)
return { kind: "ok", user }
console.log({ user })
} catch (e) {
__DEV__ && console.tron.log(e.message)
return { kind: "bad-data" }
}
} catch {
return { kind: "bad-data" }
}
}
Consider, we will be getting user data from this API call,
you can notice that there is UserSnapshot which belongs to User Model, Snapshot will save the data automatically, you don't need Aysnc storage to save or retrieve data.

vuex empty state on logout

Quick story of my problem:
Absolutely no data is stored in my vuex state when the page loads
If the user is logged in(or has info stored in window.localStorage and therefore gets auto logged in) my vuex store retrieves all the info from a socket that requires authentication.
Then the user logs out, But my vuex state save still retains all its data
This would be a security issue as not logged in people(or hackers) on a public pc could view what the state was before the user logged out.
I have seen How to clear state in vuex store?
But I feel that this is a hack and should be avoided.
My current solution is just to refresh the page using location.reload();
Is there a better way to prevent this data leak?
All objects stored in Vue act as an observable. So if the reference of a value is changed/mutated it triggers the actual value to be changed too.
So, In order to reset the state the initial store modules has to be copied as a value.
On logging out of a user, the same value has to be assigned for each module as a copy.
This can be achieved as follows:
// store.js
// Initial store with modules as an object
export const initialStoreModules = {
user,
recruitment,
};
export default new Vuex.Store({
/**
* Assign the modules to the store
* using lodash deepClone to avoid changing the initial store module values
*/
modules: _.cloneDeep(initialStoreModules),
mutations: {
// reset default state modules by looping around the initialStoreModules
resetState(state) {
_.forOwn(initialStoreModules, (value, key) => {
state[key] = _.cloneDeep(value.state);
});
},
}
});
Then call commit("resetState"); when the user logs out.
Normal Approach
If user logs in, then you can add few boolean flags to ensure that user has been loggedin/loggedout.
So initial approach would be -
this.$store.commit('insertToken', {realtoken, isLoggedIn: true})
In vuex than,
insertToken (state, payload) {
state.token = payload.realtoken
state.isLoggedIn = payload.isLoggedIn
localStorage.setItem('token', payload.realtoken)
}
And when user logs out you should set all flags to false,
In component -
logout () {
this.$store.commit('logOut')
this.$router.replace('/login')
}
and in vuex,
logOut (state, payload) {
state.token = null
state.isLoggedIn = false
localStorage.setItem('token', null)
},
So by means of isLoggedIn and token you can tell router where to navigate by using term called Navigation Guards
Example -
const checkToken = () => {
if ((localStorage.getItem('token') == null) ||
(localStorage.getItem('token') == undefined)) {
return false
} else {
return true
}
}
// Navigation guards
if (to.path === '/') {
if (checkToken()) {
next()
} else {
router.push('/login')
}
}
This is the way I use when authentication is done by means of using token as part of interacting with Vuex.
This extension does a nice job
https://www.npmjs.com/package/vuex-extensions
With it installed I can just call reset in the Vuex Logout Action
logout(context) {
// do the logout stuff, such as
context.commit("setUser", {});
// On logout, clear all State, using vuex-extensions
this.reset();
// if using router, change to login page
router.replace("/login");
}
This might be late but I found window.localStorage.removeItem('vuex') useful. Thanks to Thomas von Deyen, https://github.com/championswimmer/vuex-persist/issues/52#issuecomment-413913598

Creating a altjs flux store for fetching data from API

I'm stuck trying to figure out how to write a flux store and action that works in just fetching data from my express API using altjs
import $ from 'jquery';
const utils = {
myProfile: () => {
return $.ajax({
url: '/myProfile',
type: 'GET'
});
}
};
This is how I believe I should write my GET request for just grabbing a user's profile (which should return a json with user info).
then for my store :
import UserActions from 'actions/UserActions';
import alt from 'altInstance';
class UserStore {
constructor() {
this.userProfile = [];
this.on('init', this.bootstrap);
this.on('bootstrap', this.bootstrap);
this.bindListeners({
fetchUserProfile: UserActions.FETCHUSERPROFILE,
});
}
fetchUserProfile(profile) {
this.userProfile = profile;
}
}
export default alt.createStore(UserStore, 'UserStore');
However the action is where i'm the most clueless
import alt from 'altInstance';
import UserWebAPIUtils from 'utils/UserWebAPIUtils';
fetchProfile(){
this.dispatch();
UserWebAPIUtils.getProfile()
//what do we do with it to let our store know we have the data?
});
}
}
}
All im trying to do, is grab data from the server, tell my store we've recieved the data and fill the userprofile array with the data from our api, and the messenger for telling our store is through a dispatcher which belongs to 'actions' correct? I've looked at a lot of tutorials but I still dont feel very confident on how I am thinking about this. What if I wanted to update data through a POST request what would that be like?
Looking through altjs doc it seems like they recommend doing the async operations from actions. I prefer this approach as well because it keeps stores synchronous and easy to understand. Based on their example
LocationAction
LocationsFetcher.fetch()
.then((locations) => {
// we can access other actions within our action through `this.actions`
this.actions.updateLocations(locations);
})
.catch((errorMessage) => {
this.actions.locationsFailed(errorMessage);
});
Basically they are fetching the information and then triggering 2 actions depending on the result of the request which the store is listening on to.
LocationStore
this.bindListeners({
handleUpdateLocations: LocationActions.UPDATE_LOCATIONS,
handleFetchLocations: LocationActions.FETCH_LOCATIONS,
handleLocationsFailed: LocationActions.LOCATIONS_FAILED
});
When the store receives a handleUpdateLocations action which happens when the fetcher returns successfully. The store will update itself with new data and dispatch
handleUpdateLocations(locations) {
this.locations = locations;
this.errorMessage = null;
}
With your code you can do something similar. The fetch user profile will be triggered when data is originally requested. Here I am setting user profile to be [] which is your original init value but you can set it to anything to indicate data is being loaded. I then added 2 more methods, handleFetchUserProfileComplete and handleFetchUserProfileError which get called depending on if your fetch was successful or not. The code below is a rough idea of what you should have.
constructor() {
this.userProfile = [];
this.on('init', this.bootstrap);
this.on('bootstrap', this.bootstrap);
this.bindListeners({
handleFetchUserProfile: UserActions.FETCH_USER_PROFILE,
handleFetchUserProfileComplete: UserActions.FETCH_USER_PROFILE_COMPLETE,
handleFetchUserProfileError: UserActions.FETCH_USER_PROFILE_ERROR,
});
}
fetchUserProfile() {
this.userProfile = [];
}
handleFetchUserProfileComplete(profile) {
this.userProfile = profile;
}
handleFetchUserProfileError(error) {
this.error= error;
}
export default alt.createStore(UserStore, 'UserStore');
The only thing left is to trigger these 2 actions depending on the result of your fetch request in your action code
fetchUserProfile(){
this.dispatch();
UserWebAPIUtils.getProfile().then((data) => {
//what do we do with it to let our store know we have the data?
this.actions.fetchUserProfileComplete(data)
})
.catch((errorMessage) => {
this.actions.locationsFailed(errorMessage);
});
}
fetchUserProfileComplete(profile) {
this.dispatch(profile);
}
fetchUserProfileError(error) {
this.dispatch(error);
}

Categories