I'm trying to check if a user is authenticated. I do this by checking some record in asyncStorage, I have the following code
App.js
let AuthService = require('./app/layouts/AuthService/AuthService.js');
export default class App extends React.Component {
componentDidMount() {
AuthService.getAuthInfo((err, authInfo) => {
this.setState({
checkingAuth: false,
isLoggedIn: authInfo != null
})
});
}
}
Auth.js
'use strict';
let AsyncStorage = require('react-native').AsyncStorage;
let _ = require('lodash');
const authKey = 'auth';
const userKey = 'user';
class AuthService {
getAuthInfo(cb){
AsyncStorage.multiGet([authKey, userKey], (err, val)=> {
if(err){
return cb(err);
}
if(!val){
return cb();
}
let zippedObj = _.zipObject(val);
if(!zippedObj[authKey]){
return cb();
}
let authInfo = {
header: {
Authorization: 'Basic ' + zippedObj[authKey]
},
user: JSON.parse(zippedObj[userKey])
}
return cb(null, authInfo);
});
}
}
module.exports = new AuthService();
In app.js, I'm trying to use this function from Auth.js, but I get no response from the fuction, I get console logs from getAuthInfo before I get into the AsyncStorage function. Im pretty new to react-native and ES6, and I think is a promise or async problem but I cant make it work. In app.js im redering a ActivityIndicator so I dont block the UI with checkingAuth and isLoggedIn.
I tried to use some .then in app.js with no results.
First of all, you return your callback function instead of calling it. Try to call it by removing return like this : cb(null, authInfo);.
Related
I'm trying to implement User Authentication in a Remix app and have been going around in circles trying to figure out why destroying the session is returning [object Promise] instead of clearing it.
My auth.server.js file contains functions that manage anything relating to creating, retrieving and destroying the Session Cookie, as well as a function to login.
import { hash, compare } from "bcryptjs";
import { prisma } from "./database.server";
import { createCookieSessionStorage, redirect } from "#remix-run/node";
export const sessionStorage = createCookieSessionStorage({
cookie: {
secure: process.env.NODE_ENV === "production",
path: "/",
secrets: [process.env.SESSION_SECRET],
sameSite: "lax",
maxAge: 30 * 24 * 60 * 60, // 30 days
httpOnly: true
}
});
async function createUserSession(userId, redirectPath) {
const session = await sessionStorage.getSession();
session.set("userId", userId);
return redirect(redirectPath, {
headers: {
"Set-Cookie": await sessionStorage.commitSession(session)
}
});
}
export async function getUserFromSession(request) {
const session = await sessionStorage.getSession(
request.headers.get("Cookie")
);
const userId = session.get("userId");
return userId ? userId : null;
}
export function destroyUserSession(request) {
const session = sessionStorage.getSession(request.headers.get("Cookie"));
return redirect("/", {
headers: {
"Set-Cookie": sessionStorage.destroySession(session)
}
});
}
export async function login({ email, password }) {
const existingUser = await prisma.user.findFirst({ where: { email } });
if (!existingUser) {
return throwError(
"Could not log you in, please check provided email.",
401
);
}
const passwordCorrect = await compare(password, existingUser.password);
if (!passwordCorrect) {
return throwError(
"Could not log you in, please check provided password.",
401
);
}
return createUserSession(existingUser.id, "/");
}
function throwError(text, code) {
const error = new Error(text);
error.status = code; // authentication error code
throw error; // this triggers ErrorBoundary, as it's not an error response
}
My logout function is in a separate .js file located inside the /routes folder.
import { json } from "#remix-run/node";
import { destroyUserSession } from "~/data/auth.server";
export function action({ request }) {
if (request.method !== "POST") {
throw json({ message: "Invalid request method" }, { status: 400 });
}
return destroyUserSession(request);
}
Finally, the Logout button is inside the navigation bar and contained within a <Form> element.
<Form method="post" action="/logout">
<button type="submit">Logout</button>
</Form>
SOLVED
I was somehow able to solve it by moving the logout function into auth.server.js and leaving the following in the logout.js route:
import { redirect } from "#remix-run/node";
import { logout } from "~/data/auth.server";
export const action = ({ request }) => logout(request);
export const loader = () => redirect("/auth");
The session functions are all async. The reason you had [object Promise] was that you were returning the promise directly. You need to use await.
export function destroyUserSession(request) {
const session = sessionStorage.getSession(request.headers.get("Cookie"));
return redirect("/", {
headers: {
// use await on session functions
"Set-Cookie": await sessionStorage.destroySession(session)
}
});
}
P.S. That's one of the benefits of TypeScript. It would have told you that headers required a string value and would not allow the Promise return.
I am using react-query in my TS project:
useOrderItemsForCardsInList.ts:
import { getToken } from '../../tokens/getToken';
import { basePath } from '../../config/basePath';
import { getTokenAuthHeaders } from '../../functions/sharedHeaders';
import { useQuery } from 'react-query';
async function getOrderItemsForCardsInList(listID: string) {
const token = await getToken();
const response = await fetch(`${basePath}/lists/${listID}/order_items/`, {
method: 'GET',
headers: getTokenAuthHeaders(token)
});
return response.json();
}
export default function useOrderItemsForCardsInList(listID: string) {
if (listID != null) {
return useQuery(['list', listID], () => {
return getOrderItemsForCardsInList(listID);
});
}
}
I use my query result over here:
import { useCardsForList } from '../../hooks/Cards/useCardsForList';
import useOrderItemsForCardsInList from '../../hooks/Lists/useOrderItemsForCardsInList';
import usePaginateCardsInList from '../../hooks/Cards/usePaginateCardsInList';
export default function CardsListFetch({ listID }: { listID: string }) {
const { isLoading, isError, error, data } = useCardsForList(listID);
const { orderItems } = useOrderItemsForCardsInList(listID);
const pagesArray = usePaginateCardsInList(orderItems, data);
return (
...
);
}
However, on my const { orderItems } = useOrderItemsForCardsInList(listID); line, I get the following error:
Property 'orderItems' does not exist on type 'UseQueryResult<any, unknown> | undefined'.
How can I resolve this? I don't really know how to consume the result of my query on Typescript, any help is be appreciated
The property on useQuery that you need to consume where you find your data is called data, so it should be:
const { data } = useOrderItemsForCardsInList(listID);
if that data has a property called orderItems, you can access it from there.
However, two things I'm seeing in your code:
a conditional hook call of useQuery (which is forbidden in React)
your queryFn returns any because fetch is untyped, so even though you are using TypeScript, you won't get any typesafety that way.
If you are using React with react-query, I suggest you install this devDependency called: "#types/react-query". When using VS Code or any other smart text editor that will help, because it will help you with type suggestions.
npm i --save-dev #types/react-query
Then go to your code and fix couple things:
remove condition from useOrderItemsForCardsInList(). Don’t call React Hooks inside loops, conditions, or nested functions. See React hooks rules.
import UseCategoryResult and define interface for your return object. You can call it OrderItemsResult or similar. Add type OrderType with the fields of the order object or just use orderItems: any for now.
Add return type UseQueryResult<OrderItemsResult> to useOrderItemsForCardsInList() function.
Fix return value of getOrderItemsForCardsInList(), it should not be response.json() because that would be Promise, not actual data. Instead use await response.json().
So your function useOrderItemsForCardsInList() will return UseQueryResult which has properties like isLoading, error and data. In your second code snipper, you already use data in one place, so instead rename data to orderData and make sure you define default orderItems to empty array to avoid issues: data: orderData = {orderItems: []}
useOrderItemsForCardsInList.ts:
import { getToken } from '../../tokens/getToken';
import { basePath } from '../../config/basePath';
import { getTokenAuthHeaders } from '../../functions/sharedHeaders';
import { useQuery, UseCategoryResult } from 'react-query';
type OrderType = {
id: string;
name: string;
// whatever fields you have.
}
interface OrderItemsResult {
orderItems: OrderType[],
}
async function getOrderItemsForCardsInList(listID: string) {
const token = await getToken();
const response = await fetch(`${basePath}/lists/${listID}/order_items/`, {
method: 'GET',
headers: getTokenAuthHeaders(token)
});
const data = await response.json();
return data;
}
export default function useOrderItemsForCardsInList(listID: string): UseQueryResult<OrderItemsResult> {
return useQuery(['list', listID], () => {
return getOrderItemsForCardsInList(listID);
});
}
Use your query result:
import { useCardsForList } from '../../hooks/Cards/useCardsForList';
import useOrderItemsForCardsInList from '../../hooks/Lists/useOrderItemsForCardsInList';
import usePaginateCardsInList from '../../hooks/Cards/usePaginateCardsInList';
export default function CardsListFetch({ listID }: { listID: string }) {
const { isLoading, isError, error, data } = useCardsForList(listID);
const { data: orderData = { orderItems: []}} = useOrderItemsForCardsInList(listID);
const pagesArray = usePaginateCardsInList(orderItems, data);
return (
...
);
}
I am playing with a client side router code from https://github.com/dcode-youtube/single-page-app-vanilla-js repo. I try to change this code in a way that i can reuse, because in the original code he hardcoded the routes in the router function.
I know(i think i know) the main reason why i get the unexpected reserved word error, i just dont know how to solve it.
Router.js
export default class trdsRouter{
constructor(routes){
this.routes = routes;
window.addEventListener("popstate", this.run);
document.body.addEventListener("click", e => {
if (e.target.matches("[trds-router-link]")) {
e.preventDefault();
this.navigateTo(e.target.href);
}
});
}
run = () => {
const potentialMatches = this.routes.map(route => {
return {
route: route,
result: location.pathname.match(pathToRegex(route.path))
};
});
let match = potentialMatches.find(potentialMatch => potentialMatch.result !== null);
if (!match) {
match = {
route: this.routes[0],
result: [location.pathname]
}
}
// THIS LINE IS THE PROBLEM
const view = new match.route.view(getParams(match));
document.querySelector("#app").innerHTML = await view.getHtml();
}
navigateTo = url => {
history.pushState(null, null, url);
this.run();
}
}
const pathToRegex = path => new RegExp("^" + path.replace(/\//g, "\\/").replace(/:\w+/g, "(.+)") + "$");
const getParams = match => {
const values = match.result.slice(1);
const keys = Array.from(match.route.path.matchAll(/:(\w+)/g)).map(result => result[1]);
return Object.fromEntries(keys.map((key, i) => {
return [key, values[i]];
}));
};
where i construct the router:
import trdsRouter from "./router.js";
import Dashboard from "./views/Dashboard.js";
import Posts from "./views/Posts.js";
import PostView from "./views/PostView.js";
import Settings from "./views/Settings.js";
let router = new trdsRouter([
{ path: "/", view: Dashboard },
{ path: "/posts", view: Posts },
{ path: "/posts/:id", view: PostView },
{ path: "/settings", view: Settings }
]);
router.run();
And then there is the abstract view class(i extend this class for the dashboard, posts, etc.):
export default class {
constructor(params) {
this.params = params;
}
setTitle(title) {
document.title = title;
}
async getHtml() {
return "";
}
}
So i think the problem is that in the trdsRouter class it does not yet know that a routes view property is a class( but i pass a class to it when i construct the router), thats why it throws error. how would i solve this? thank you.
Im sorry guys, it was a terrible mistake. The problem wasnt the new keyword highlighted in the routers code but the next line with the 'await' keyword. The routers run function wasnt declared as async so it threw the error. Changed the run function to async and it works. Thank you for your comments.
I have defined a function service in one of the file
import Category from '../models/Category.js';
export const AllCategories = () => {
console.log('hit');
const cursor = Category.find({});
console.log(cursor);
return cursor
}
export default {AllCategories}
I am importing this in the controller file
import express from 'express';
import categoryService from '../services/categories.js'
const router = express.Router();
export const getCategories = async(req,res) => {
try {
const categoriesInfo = categoryService.AllCategories
res.status(200).json(categoriesInfo)
} catch (error) {
res.status(404).json({ message: error.message });
}
}
export default router;
But the issue is that AllCategories is not getting run, what is wrong here
I also tried adding async/await
import Category from '../models/Category.js';
export const AllCategories = async () => {
try {
console.log("hit");
const cursor = await Category.find({});
console.log(cursor);
return cursor
} catch (error) {
return error
}
}
export default {AllCategories}
But still no luck
You're not calling the function, this saves it in the variable categoriesInfo:
const categoriesInfo = categoryService.AllCategories
To get its return value:
const categoriesInfo = await categoryService.AllCategories();
Note: I think you need to make it async if you're doing a db transaction so keep the second version and test it.
You can't use the ES module or ESM syntax by default in node.js. You either have to use CommonJS syntax or you have to do 1 of the following.
Change the file extension from .js to .mjs
Add to the nearest package.json file a field called type with a value of module
I'm using Nest version ^6.7.2
I'm trying to create a createParamDecorator that gets the req.user value from a request.
Inside the createParamDecorator, the req.user has a value, however when I try to get the value in a controller by using the decorator the value is undefined.
const AuthSession = createParamDecorator((data, req) => {
console.log(req.user); // session data
return req.user;
});
Controller()
export default class AuthController {
#Get("/token/ping")
#UseGuards(AuthGuard("jwt"))
tokenPing(#AuthSession() session: Session) {
console.log(session); // undefined
return session;
}
}
Edit: I just tried updating to nestjs v7 and I'm having the same issue
import { createParamDecorator, ExecutionContext } from "#nestjs/common";
const AuthSession = createParamDecorator((data: any, ctx: ExecutionContext) => {
return { message: "asdf" };
});
export default AuthSession;
#Controller()
export default class AuthController {
#Get("/token/ping")
#UseGuards(AuthGuard("jwt"))
tokenPing(#AuthSession() session: Session) {
console.log(session); // undefined
return session;
}
}
you can get the information firs from ExecutionContext:
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
check the example in the doc : Custom decorator
I figured out what the issue was. I had a custom validation PipeTransform that returned undefined if the ArgumentMetadata.type was neither "body" nor "param". So now I just return the first argument of the validator's transform method (the input) if the ArgumentMetadata.type is neither "body" nor "param" and that fixed the problem.