Can't insert into table with RLS despite being logged in? - supabase

I want to add a row to a profiles table in the public schema immediately after the user has signed up and their auth user has been created in the database.
The user gets created successfully, and I can see the details of the user in the returned user object from the supabase.auth.signUp function.
However when I try to do a manual insert to the table, I get an error back saying the RLS on the table is being violated. However the user is logged in and the uid is correct, so why won't it let me insert data?
async function handleSubmit(e: any) {
e.preventDefault()
const email = emailRef.current.value;
const password = passwordRef.current.value;
// const navigate = useNavigate();
const {user, error} = await signUp({email, password});
if(error){
alert("Error signing in");
} else {
await addUserDetails({
uuid: user.id,
firstName: firstNameRef.current.value,
lastName: lastNameRef.current.value
});
navigate("/dashboard");
}
}
return //rest of component
}
export async function addUserDetails({
uuid, firstName, lastName
}){
const {data, error } = await supabase.from("profiles").insert([{
id: uuid,
first_name: firstName,
last_name: lastName
}])
}
RLS on table
create policy "Users can insert their own profile."
on profiles for insert
with check ( auth.uid() = id );

Try this:
const {data, error } = await supabase.from("profiles").insert([{
id: uuid,
first_name: firstName,
last_name: lastName
}],{ returning: "minimal" })

I was stuck on this for 2 days. It turns out it's because I was running Supabase in a Node test environment, and Supabase silently fails to setup a session and user id when not in a browser environment like Chromium or jsdom.
You can fix it by using a browser environment like jsdom for your testing environment or just using Playwright.
Rough example:
// #vitest-environment jsdom
test('use jsdom in this test file', () => {
const expectedName = 'Sam';
await supabase.from("profiles").insert([{
id: uuid,
first_name: expectedName
}]);
const { data: profile } = await supabase.from("profiles")
.select()
.eq( 'id', uuid )
expect( profile.first_name ).toEqual( expectedName )
});
In Vitest:
https://vitest.dev/config/#environment
In Jest:
https://jestjs.io/docs/configuration#testenvironment-string

Related

Next.js seems to cache files as in a route of _next/data/[path].json preventing getStaticProps from running in server side render

The issue appears to happen when I post the link on platforms like Discord and Slack, where then to produce a URL preview they send a request to the link. The link which in this case follows this structure (normal format) www.domain.com/ctg/[...ids].
Within [...ids] I either pass one of two ids for the same object, the object has the following structure:
type Catalogue {
id: ID!
edit_id: String!
user_id: String!
title: String
...
}
The first id I could pass into [...ids] would be Catalogue.id
The second id I could pass into [...ids] would be Catalogue.edit_id
Whenever either of those inputs for [...ids] is passed as part of a request the following getStaticProps is ran:
export const getStaticProps: GetStaticProps = async ({ params }) => {
const { ids } = params;
let catalogue: CatalogueType | null = await fetchFullCatalogue(ids[0]);
return {
props: {
catalogue_prop: catalogue,
params,
},
};
};
with fetchFullCatalogue being:
export const fetchFullCatalogue = async (
id: string
): Promise<CatalogueType | null> => {
let catalogue: CatalogueType;
const fetchToUrl =
process.env.NODE_ENV === "development"
? "http://localhost:4000/graphql"
: process.env.BACKEND_URL + "/graphql";
// create a axios fetch request to the http://localhost:4000/graphql
const query = `
<...SOME FRAGMENTS LATER...>
fragment AllCatalogueFields on Catalogue {
id
edit_id
user_id
status
title
description
views
header_image_url
header_color
author
profile_picture_url
event_date
location
created
updated
labels {
...AllLabelFields
}
listings {
...AllListingFields
}
}
query Catalogues($id: ID, $edit_id: String) {
catalogues(id: $id, edit_id: $edit_id) {
...AllCatalogueFields
}
}`;
const config: AxiosRequestConfig = {
method: "post",
url: fetchToUrl,
headers: {
"Content-Type": "application/json",
},
data: JSON.stringify({
query,
variables: { id: id, edit_id: id },
}),
};
let response = await axios(config);
if (response.data.errors) return null;
catalogue = response.data.data.catalogues[0];
console.log("catalogue", catalogue);
return catalogue;
};
The request it is making is to the following API endpoint
Query: {
catalogues: async (
_: null,
args: { id: string; edit_id: string }
): Promise<Catalogue[]> => {
let catalogues: Catalogue[];
// when both id and edit_are passed
if (args.id && args.edit_id) {
catalogues = await getFullCatalogues(args.id, "id", true);
// the following convoluted request is the result of
// me responding to the fact that only the edit_id was working
if (catalogues.length === 0) {
catalogues = await getFullCatalogues(args.edit_id, "edit_id", true);
if (catalogues.length === 0) {
throw new UserInputError("No catalogues found");
}
} else {
catalogues = await getFullCatalogues(
catalogues[0].edit_id,
"edit_id",
true
);
}
console.log("catalogues", catalogues);
} else if (args.id) {
catalogues = await getFullCatalogues(args.id);
} else if (args.edit_id) {
catalogues = await getFullCatalogues(args.edit_id, "edit_id");
} else {
const res = await db.query(fullCatalogueQuery());
catalogues = res.rows;
}
return catalogues;
},
...
},
This results in the following output within the deployed logs:
The logs show the data when the Catalogue is first created which simultaneously navigates me to the URL of "normal format" with Catalogue.id which is interpreted as /_next/data/qOrdpdpcJ0p6rEbV8eEfm/ctg/dab212a0-826f-42fb-ba21-6ebb3c1350de.json. This contains the default data when Catalogue is first generated with Catalogue.title being "Untitled List"
Before sending both requests I changed the Catalogue.title to "asd".
Notice how the request with the Catalogue.edit_id which was sent as the "normal format" was interpreted as /ctg/ee0dc1d7-5458-4232-b208-1cbf529cbf4f?edit=true. This resulted in the correct data being returned with Catalogue.title being "asd".
Yet the following request with the Catalogue.id although being of the same "normal format" never provoked any logs.
(I have tried sending the request without the params ?edit=true and the same happens)
Another important detail is that the (faulty) request with the Catalogue.id produces the (faulty) URL preview much faster than the request with Catalogue.edit_id.
My best theory as to why this is happening is that the data of the URL with Catalogue.id is somehow stored/cached. This would happen as the Catalogue is first created. In turn it would result in the old stored Catalogue.id being returned instead of making the fetch again. Whereas the Catalogue.edit_id makes the fetch again.
Refrences:
Live site: https://www.kuoly.com/
Client: https://github.com/CakeCrusher/kuoly-client
Backend: https://github.com/CakeCrusher/kuoly-backend
Anything helps, I felt like ive tried everything under the sun, thanks in advance!
I learned that For my purposes I had to use getServerSideProps instead of getStaticProps

access req.cookie from supertest

I set a cookie and I usually access it like this when I need to test a "protected" request.
beforeAll(async () => {
await db.connect();
//sign my user and get the token
const response = await request(app).post("/gettoken").send(credentials);
TOKEN = response.headers["set-cookie"].pop().split(";")[0];
})
//test exemple
it("exemple", async () => {
const result = await request(app).post("/path").set(`Cookie`, TOKEN).send(data);
});
So far I had no problem with it but in one of my function that I want to test I have this line:
user = await getUser(req.cookies.token);
the function getUser is pretty simple:
const userToken = jwt.verify(token, process.env.JWTPRIVATEKEY);
user = await User.findOne({ _id: userToken.payload._id });
return user;
Seems like supertest does not work with req.cookies. Is there any way to set "req.cookies.token" during my test ?
I'm going to answer my own question but please, be aware that I have no idea why it works. Actually I was a bit desperate to not find any solution so I did try something without any hope but, surprisely, it worked.
so basically after restarting docker my console.log were all similars (npm run test vs test with postman)
module.exports.getUser = async (token) => {
console.log(token);
console.log(process.env.JWTPRIVATEKEY);
const userToken = jwt.verify(token, process.env.JWTPRIVATEKEY);
console.log(userToken.payload._id);
const user = await User.find({ _id: userToken.payload._id });
return user;
};
it("with success", async () => {
const question = {
title: "title",
content: "content",
};
const result = await request(app).post("/api/forum/question").set("Cookie", TOKEN).send(question);
console.log(result.body);
expect(result.status).toBe(200);
});
And I had this error:
{ error: { message: "Cannot read property '_id' of null" } }
I did change
const user = await User.findOne({ _id: userToken.payload._id });
by
const user = await User.find({ _id: userToken.payload._id });
And now it works as expected.
If someone can explain me why findOne was not working with test (but was ok with postman) I would be happy to understand... because to me, as beginner, it does not make any sense.

GraphQL resolver for certain field not being invoked

I wrote simple GraphQL schemas and resolvers in studying purpose, and I could not get reason why my codes work as expected. I used Prisma for ORM, and express-graphql and graphql-import to compose the API.
type User {
id: ID!
name: String!
}
type Link {
id: ID!
url: String!
user: User!
}
type Query {
links(id: ID!): [Link!]!
}
// resolvers
const links = async (root, args, ctx) => {
const links = await ctx.prisma.links({
where: {
user {
id: args.id
}
}
}
}
// resolver for `Link` type
const user = async (root, args, ctx) => {
const user = await ctx.prisma.link({ id: parent.id }).user()
return user
}
// omitted field resolver for `url` and `id`
module.exports = {
user
}
With these codes, I expected to get id, url, user fields when I query links, but when I send the query, it returns null with user field. Which means, if I check in the server-side, the resolver for user field does not invoked at all. What is wrong with this?

TypeORM - Retrieve data through relation query in resolver

So I'm trying to retrieve elements out of a join relation in an Apollo resolver.
I've got a User table, a Notification table. And a Relation table named:
notification_recipients_user defined by a ManyToMany relation on both entity but hosted on Notification :
//entity/Notification.ts
#ManyToMany(type => User, recipient => recipient.notifications)
#JoinTable()
recipients: User[];
I can create relation without problem through this mutation :
addNotificationForUser: async (_, { id, data }) => {
const user = await User.findOne({ id });
if (user) {
const notification = await Notification.create({
template: data,
recipients: [user]
}).save()
return notification;
} else {
throw new Error("User does not exists!");
}
}
However I'm totally not succeeding in retrieving data for a specific User.
allNotificationsOfUser: (_, { user }, ___) => {
return Notification.find({
where: { userId: user },
relations: ['recipients'],
})
The method find is one of TypeORM native methods.
However I must be doing something wrong because it react as if there wasn't any filter.
Okay so the best way of doing it is by using relation between entity.
So to get Notification you'll go by User.notifications
allUnseenNotificationsOfUser: async (_, { userId }, __) => {
const user = await User.findOne({
relations: ["notifications"],
where: { id: userId }
});
if (user) {
const notifications = user.notifications.filter(notification => !notification.seen)
return notifications;
}
return null;
}
And for the record for anyone stumbeling upon this you can use filter on your result to do a query like resolver.
const notifications = user.notifications.filter(notification => !notification.seen)
It feels tricky but works like a charm.

Return Response from http post in react flux

I'm making a react flux app where the user first create a user and that user will be saved in a database.
so first I create a action named CREATE_USER looks like this
export function createUser(name, username, password) {
dispatcher.dispatch({
type: "CREATE_USER",
name,
username,
password,
});
}
then i register that action in my Store like this
handleActions(action) {
switch(action.type) {
case "CREATE_USER": {
this.createUser(action.name, action.username, action.password);
break;
}
}
}
this will trigger a create user function that will make a http post to the backend and save that user in the database
createUser(name, username, password) {
axios.post('/api/user', {
name: name,
username: username,
password: password
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error.response);
});
}
then in my component i call createUser on handlesubmit function
_handleUserSubmit(event) {
event.preventDefault();
let name = this.name.value;
let username = this.username.value;
let password = this.password.value;
UserActions.createUser(name, username, password);
}
But how do i return the response object or error object from the createuser function to the component?

Resources