I have loads of endpoints to protect in my SvelteKit app. I dont' want to add following in all of them:
if (!request.locals.user) {
return { status: 401 };
};
Can I do this from hooks.js or in another easy and safe way?
Currently there is no way to add hook per endpoint in sveltekit and implementing it in the global hooks.js will be difficult because you will have to maintain protected routes path every time you change it.
The only way to do it as you said is to add the auth check in every route which is also will be difficult to maintain. to avoid that we can extract the auth check logic into its own function. the function will accept the handler that holds the route hooks:
// compose one handler function out of number of handlers.
// it will execute handlers in sequence until one returned a value
function withHandlers(...handlers) {
return async (request) => {
for (const handle of handlers) {
const result = await handle(request)
if (result !== undefined) {
return result
}
}
}
}
// implementation of auth check
function authHook(request) {
if (!request.locals.user) {
return {
status: 401,
body: {
message: 'unauthorized'
}
};
}
}
// create a new handler with auth check
function withAuth(handle) {
return withHandlers(authHook, handle);
}
// your final endpoint with authentication check
export const get = withAuth((request) => {
return {
body: `Hello ${request.locals.user}`
};
});
Related
The problem:
Nothing happends when throwing throw redirect(302, '/auth/sign-up-success') in SvelteKit's actions if onSuccess: () => {...} is set in Felte's createForm({...}).
Example:
// +page.server.ts
export const actions: Actions = {
default: async (event) => {
...
throw redirect(302, '/auth/sign-up-success');
}
}
// SignUpForm.svelte
const { form, errors } = createForm({
onSuccess: (response) => {
invalidate('app:auth')
},
...
}
If I would delete the onSuccess part, then redirect would happend.
Question:
Is there a way to reuse that redirect form success response logic from default Felte form config without writing it again myself?
Action responses are JSON objects with a type, you could read the response and redirect on the client:
async onSuccess(response) {
const { type, location } = await response.json();
if (type == 'redirect') {
goto(location); // from '$app/navigation'
return;
}
}
I would not recommend using this library though. It appears to be incompatible with SSR and one of its main actions shares the name of the form data property used by SvelteKit form actions.
Depending on why you are using this, there might be more suitable tools for SvelteKit in particular (if you even need any, SvelteKit does many things out of the box).
So at the moment I am having to put my request / api logic directly into my components because what I need to do a lot of the time is set state based on the response I get from the back end.
Below is a function that I have on my settings page that I use to save the settings to recoil after the user hits save on the form:
const setUserConfig = useSetRecoilState(userAtoms.userConfig);
const submitSettings = async (values: UserConfigInterface) => {
try {
const { data: {data} } = await updateUser(values);
setUserConfig({
...data
});
} catch (error) {
console.log('settings form error: ', error);
}
}
This works perfectly...I just dont want the function in my component as most of my components are getting way bigger than they need to be.
I have tried making a separate file to do this but I can only use the recoil hooks (in this instance useSetRecoilState) inside of components and it just complains when I try and do this outside of a react component.
I have tried implementing this with recoils selector and selectorFamily functions but it gets kind of complicated. Here is how I have tried it inside of a file that has atoms / selectors only:
export const languageProgress = atom<LanguageProgress>({
key: "LanguageProgress",
default: {
level: 1,
xp: 0,
max_xp: 0
}
})
export const languageProgressUpdate = selectorFamily<LanguageProgress>({
key: "LanguageProgress",
get: () => async () => {
try {
const { data: { data } } = await getLanguageProgress();
return data;
} catch (error) {
console.log('get language progress error');
}
},
set: (params:object) => async ({set}) => {
try {
const { data: { data } } = await updateLanguageProgress(params);
set(languageProgress, {
level: data.level,
xp: data.xp,
max_xp: data.max_xp
});
} catch (error) {
console.log('language progress update error: ', error);
}
}
});
What I want to do here is get the values I need from the back end and display it in the front which I can do in the selector function get but now I have 2 points of truth for this...my languageProgress atom will initially be incorrect as its not getting anything from the database so I have to use useGetRevoilValue on the languageProgressUpdate selector I have made but then when I want to update I am updating the atom and not the actual value.
I cannot find a good example anywhere that does what I am trying to here (very suprisingly as I would have thought it is quite a common way to do things...get data from back end and set it in state.) and I can't figure out a way to do it without doing it in the component (as in the first example). Ideally I would like something like the first example but outside of a component because that solution is super simple and works for me.
So I dont know if this is the best answer but it does work and ultimately what I wanted to do was seperate the logic from the screen component.
The answer in my situation is a bit long winded but this is what I used to solve the problem: https://medium.com/geekculture/crud-with-recoiljs-and-remote-api-e36581b77168
Essentially the answer is to put all the logic into a hook and get state from the api and set it there.
get data from back end and set it in state
You may be looking for useRecoilValueLoadable:
"This hook is intended to be used for reading the value of asynchronous selectors. This hook will subscribe the component to the given state."
Here's a quick demonstration of how I've previously used it. To quickly summarise: you pass useRecoilValueLoadable a selector (that you've defined somewhere outside the logic of the component), that selector grabs the data from your API, and that all gets fed back via useRecoilValueLoadable as an array of 1) the current state of the value returned, and 2) the content of that API call.
Note: in this example I'm passing an array of values to the selector each of which makes a separate API call.
App.js
const { state, contents } = useRecoilValueLoadable(myQuery(arr));
if (state.hasValue && contents.length) {
// `map` over the contents
}
selector.js
import { selectorFamily } from 'recoil';
export const myQuery = selectorFamily({
key: 'myQuery',
get: arr => async () => {
const promises = arr.map(async item => {
try {
const response = await fetch(`/endpoint/${item.id}`);
if (response.ok) return response.json();
throw Error('API request not fulfilled');
} catch (err) {
console.log(err);
}
});
const items = await Promise.all(promises);
return items;
}
});
I have a component called PlatformMain that currently depends on a global channel object from Phoenix defined inside of the component's file.
let channel;
let socket = new Socket("...", {params: {token: window.userToken}});
socket.connect();
class PlatformMain extends React.Component {
componentWillMount() {
this.connectUser();
}
connectUser() {
const { user } = this.props;
channel = socket.channel("user_pool:" + user.email, { app: APP });
this.setupChannel();
}
setupChannel() {
channel.join()
.receive("ok", () => { console.log("Successfully joined call channel") })
.receive("error", () => { console.log("Unable to join") })
channel.on("match_found", payload => {
...
});
...
}
If the user presses a button, I'd like that to dispatch an action as well as push a message to the channel.
onPress() {
console.log("APPROVE_MATCH");
const { peer, waitForResponse } = this.props;
approveMatch(peer);
channel.push("approve_match", { // <------ want to put this somewhere else
"matched_client_email": peer.email,
});
}
My question is, if I want to "reduxify" the channel.push call, where should I put it? It feels weird not having the channel.push(...) somewhere else since it's an API call. I was going to put it in a saga using redux-saga like so:
function* approveMatch(action) {
const peer = action.payload.peer;
channel.push("approve_match", { // <------- but how would I get the same channel object?
"matched_client_email": peer.email,
});
}
export default function* watchMatchingStatus() {
yield takeEvery(matchingStatusActions.APPROVE_MATCH, approveMatch);
}
But wouldn't I need to point to the same channel object? How would I do that? If I put the initialization of the channel in its own file and I export and import it in multiple places, wouldn't it execute the file multiple times (and consequently join the channel multiple times)?
You could put the initialization of channel in its own file and safely import multiple times, the evaluation of the module only happens once. You can check the spec for confirmation:
Do nothing if this module has already been evaluated. Otherwise, transitively evaluate all module dependences of this module and then evaluate this module.
I'm building a "secured" application and using redux-saga together with fetchjs for doing the async calls to the backend.
My backend returns a 401 status code when the user is not authorized, i want to catch this "exception" globally and dispatch a action so my react application goes to the login screen.
I found the following solution: https://github.com/redux-saga/redux-saga/issues/110, but in this case the handling for the 401 should be explicit in every saga that we build.
By adding code to every saga it becomes more complex. It also increases the chances a developer forgets to add the code for handling the 401 code.
Is there a nice way to handle this 401 response globally?
I would not use redux-saga since it does not have ability to do what you require.
Instead, when setting up store, API layer and other things configure API layer do invoke handler on every error occurred.
Sample of API layer that reports invokes error handler.
const conf = {
onError: () => {},
}
api.onError = (cb) => {
conf.onError = cb;
}
api.get = (url) => {
fetch(url)
.then(response => {
if (response.ok === false) {
return conf.onError(response);
}
return response;
})
// Network error
.catch(conf.onError)
}
Configure application.
import store from './store';
// Configure your API wrapper and set error callback which will be called on every API error.
api.onError((error) => {
store.dispatch({
type: 'API_ERROR',
payload: error,
});
});
// In you reducers...
isAuthorized(state, action) {
if (action.type === 'API_ERROR' && action.payload.statusCode === 401) {
return false;
}
return state;
}
Then, call API as usually, If error occurs, action is dispatched to store and you may or may not react to this actions.
Generally in Redux (thunk), action creators are static methods. However I have a particular scenario where action creators need to make an async call to different endpoints. One way to do it would be to pass in a flag to determine which endpoint to go to:
function _getEndPoint(isHouse) {
return isHouse ? 'house' : 'flat';
}
export function post(model, isHouse = false) {
return (dispatch, getState) => {
dispatch({
types: [POST, POST_SUCCESS, POST_FAILED],
promise: (client) => client.post(`${_getEndPoint(isHouse)}`, {
data: model,
}),
});
};
}
But is there a way to create a class say Property with post as a public method which serves as action method. So when I instantiate the Property class, I define the type of the property and set the right endpoint on creation?
Coming from a C# background, I was also wondering if I can use something like Generics or inheritance to solve this problem.
Also, is this a good practice to attempt create action methods with classes as I read that Redux is more into functional style of programming.
If you have to make calls to different endpoints (asynchronously or not) – these are different actions. Say you want to handle error. Generic type POST_FAILED will not tell you which resource has failed. What if you want to change state tree based on it? What if different types should be handled in various ways? Functional approach POST_OF_SOMETHING_FAILED suits better here. Small functions do their small things. Otherwise you would have to use complex if block.
Instead of using classes and inheritance you could implement some kind of action generator to get rid of code duplication. For example if you want to create shared CRUD actions, this is how create action might look like:
/actions/generators/create.js
import fetch from 'isomorphic-fetch';
function requestCreateItem(modelName) {
return {
type: `CREATE_${modelName.toUpperCase()}_REQUEST`,
};
}
function receiveCreateItem(modelName, item) {
return {
type: `CREATE_${modelName.toUpperCase()}_SUCCESS`,
item,
receivedAt: Date.now(),
};
}
function catchCreateItemError(modelName, error) {
return {
type: `CREATE_${modelName.toUpperCase()}_ERROR`,
error,
receivedAt: Date.now(),
};
}
function createItem(modelName) {
return (dispatch) => {
dispatch(requestCreateItem(modelName));
return fetch(`/${modelName}s`, {
method: 'POST',
credentials: 'same-origin',
}).then(response => response.json()).then(json => {
if (json.error) {
dispatch(catchCreateItemError(modelName, json.error));
} else {
dispatch(receiveCreateItem(modelName, json));
}
}).catch(error => {
dispatch(catchCreateItemError(modelName, error));
});
};
}
export default function create(modelName) {
return () => createItem(modelName);
}
You can use it inside actions index file as a constructor.
/actions/index.js
import create from './generators/create';
const createBot = create('bot');
export const botActions = { createBot };
This is the main idea.