Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.findOne(undefined) returns first item in the database instead of undefined #2500

Closed
ct-reposit opened this issue Jul 11, 2018 · 91 comments
Closed

Comments

@ct-reposit
Copy link

Issue type:

[ ] question
[x] bug report
[ ] feature request
[ ] documentation issue

Database system/driver:

[ ] cordova
[ ] mongodb
[ ] mssql
[ ] mysql / mariadb
[ ] oracle
[x ] postgres
[ ] sqlite
[ ] sqljs
[ ] react-native
[ ] expo

TypeORM version:

[x] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Steps to reproduce or a small repository showing the problem:

user.id = undefined;
User.findOne(user.id); // expect to see undefined / null but returns the first item in the table
@pleerock
Copy link
Member

Looks like an issue. Contributions are welcomed!

@janmisek1
Copy link

janmisek1 commented Aug 1, 2018

Same problem with Mariadb

This:

User.findOne({ where: { id: null } })

works properly return undefined

@x2pdenjo
Copy link

Same here, why there is no fix?

@vlapo
Copy link
Contributor

vlapo commented Dec 7, 2018

Is this real issue? I mean User.findOne(undefined); is same as User.findOne(); but what is correct behaviour of User.findOne();?

From jsdoc:

/**
 * Finds first entity that matches given conditions.
 */

But there are no conditions (undefined) and I think if there is no condition we have to return first entity like from User.findOne({});

Maybe change method signature and mark id or condition as required but it is really big change and it will be necessary sync at least all find* methods behaviour.

@janmisek1
Copy link

But there are no conditions (undefined) and I think if there is no condition we have to return first entity like from User.findOne({});

No, you except same behavior as SQL

SELECT * FROM users WHERE id = null LIMIT 1

return empty row not first entity.

@vlapo
Copy link
Contributor

vlapo commented Dec 7, 2018

But there is function overloading:

findOne(id?: string|number|Date|ObjectID, options?: FindOneOptions<Entity>): Promise<Entity|undefined>;
findOne(options?: FindOneOptions<Entity>): Promise<Entity|undefined>;
findOne(conditions?: FindConditions<Entity>, options?: FindOneOptions<Entity>): Promise<Entity|undefined>;
findOne(optionsOrConditions?: string|number|Date|ObjectID|FindOneOptions<Entity>|FindConditions<Entity>, maybeOptions?: FindOneOptions<Entity>): Promise<Entity|undefined> {
        return this.manager.findOne(this.metadata.target, optionsOrConditions as any, maybeOptions);
}

You say undefined is for first overloaded function and represents id. I say it is possible that undefined is for any other overloaded method.

Your example with WHERE id = null can be right in case method name is findOneById() but I think findOne() is more about LIMIT 1 part.
But maybe I am wrong :-) lets wait to @pleerock or another contributors opinion.

@rustamwin
Copy link
Contributor

rustamwin commented Dec 7, 2018

async findOne<Entity>(entityClass: ObjectType<Entity>|EntitySchema<Entity>|string, idOrOptionsOrConditions?: string|string[]|number|number[]|Date|Date[]|ObjectID|ObjectID[]|FindOneOptions<Entity>|FindConditions<Entity>, maybeOptions?: FindOneOptions<Entity>): Promise<Entity|undefined> {
let findOptions: FindManyOptions<any>|FindOneOptions<any>|undefined = undefined;
if (FindOptionsUtils.isFindOneOptions(idOrOptionsOrConditions)) {
findOptions = idOrOptionsOrConditions;
} else if (maybeOptions && FindOptionsUtils.isFindOneOptions(maybeOptions)) {
findOptions = maybeOptions;
}
let options: ObjectLiteral|undefined = undefined;
if (idOrOptionsOrConditions instanceof Object && !FindOptionsUtils.isFindOneOptions(idOrOptionsOrConditions))
options = idOrOptionsOrConditions as ObjectLiteral;
const metadata = this.connection.getMetadata(entityClass);
let alias: string = metadata.name;
if (findOptions && findOptions.join) {
alias = findOptions.join.alias;
} else if (maybeOptions && FindOptionsUtils.isFindOneOptions(maybeOptions) && maybeOptions.join) {
alias = maybeOptions.join.alias;
}
const qb = this.createQueryBuilder(entityClass, alias);
if (!findOptions || findOptions.loadEagerRelations !== false)
FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias!.metadata);
findOptions = {
...(findOptions || {}),
take: 1,
};
FindOptionsUtils.applyOptionsToQueryBuilder(qb, findOptions);
if (options) {
qb.where(options);
} else if (typeof idOrOptionsOrConditions === "string" || typeof idOrOptionsOrConditions === "number" || (idOrOptionsOrConditions as any) instanceof Date) {
qb.andWhereInIds(metadata.ensureEntityIdMap(idOrOptionsOrConditions));
}
return qb.getOne();
}

Probably need check idOrOptionsOrConditions like this

async findOne<Entity>(entityClass: ObjectType<Entity>|EntitySchema<Entity>|string|undefined, idOrOptionsOrConditions?: string|string[]|number|number[]|Date|Date[]|ObjectID|ObjectID[]|FindOneOptions<Entity>|FindConditions<Entity>, maybeOptions?: FindOneOptions<Entity>): Promise<Entity|undefined> { 
  if (!idOrOptionsOrConditions) {
    return undefined;
  }
....

@vlapo
Copy link
Contributor

vlapo commented Dec 7, 2018

@rustamwin Did you mean idOrOptionsOrConditions instead of entityClass?

async findOne<Entity>(entityClass: ObjectType<Entity>|EntitySchema<Entity>|string|undefined, idOrOptionsOrConditions?: string|string[]|number|number[]|Date|Date[]|ObjectID|ObjectID[]|FindOneOptions<Entity>|FindConditions<Entity>, maybeOptions?: FindOneOptions<Entity>): Promise<Entity|undefined> { 
  if (! idOrOptionsOrConditions) {
    return undefined;
  }
...

This will cause that User.findOne() will also return undefined. I think this is incorrect behaviour of findOne. I would suppose User.findOne() will return one record without any conditions (if exists).

@rustamwin
Copy link
Contributor

rustamwin commented Dec 7, 2018

@vlapo Fixed, thnx 😄

@rustamwin
Copy link
Contributor

Perhaps FindOptionsUtils.isFindOneOptions need to be improved @pleerock @vlapo

@vlapo
Copy link
Contributor

vlapo commented Dec 8, 2018

From my point there is nothing to improve and findOne works as intended.

@pleerock
Copy link
Member

pleerock commented Jan 7, 2019

It looks like we either need to left it as it is, either to enforce findOne to accept some real conditions. What do you think @Kononnable @havenchyk ?

@havenchyk
Copy link
Contributor

@pleerock I personally would add a check if any value was passed or not

@vlapo
Copy link
Contributor

vlapo commented Jan 9, 2019

@havenchyk but current method signatures allow this case User.findOne() and this check will throw error. Of course workaround is User.findOne({}). But in that case we should change method signature and require at least one parameter.

@havenchyk
Copy link
Contributor

@vlapo why do you think it will throw an error?

just naive code:
...args => args.length > 0 ? args[0] === undefined && doSomething() : returnFirstItem()

of course, I see in code base it's much more complicated, but I don't the area for an error. Could you please explain, what I have missed?

@vlapo
Copy link
Contributor

vlapo commented Jan 10, 2019

Sry I see you want to throw error in previous comment. My mistake :-)

So you vote for findOne() (without args) will return undefined. Not first one?

@havenchyk
Copy link
Contributor

Seems I wasn't clear enough. Let me try once again.

If argument is passed and it's value is not undefined, handle this value.
If agrument is passed and it's value is undefined, return undefined/throw an exception
if agrument is not passed, return the first row.

@vlapo
Copy link
Contributor

vlapo commented Jan 10, 2019

Understand now. I think it should work 👍

@pleerock
Copy link
Member

If agrument is passed and it's value is undefined, return undefined/throw an exception

choose one. Return undefined or throw an exception?)

@Kononnable
Copy link
Contributor

Kononnable commented Jan 15, 2019

Sorry for being little late for the party.

No, you except same behavior as SQL
SELECT * FROM users WHERE id = null LIMIT 1

I completely disagree. Passing undefined shouldn't cause passing null to sql query. Null and undefined are completely different primitive values. Undefined should be ignored(optional parameters) or always return no records(without querying).

I think if we want to change current behavior we should throw an exception. If someone is deliberately passing value of undefined we can safely assume that he doesn't really know what he is doing and there is a human error somewhere. Returning undefined will just populate logical error to later called functions(app logic).

We can't search for something if we don't know what are we looking for - it means that there is a logical error while defining search criteria.

@mazyvan
Copy link

mazyvan commented Mar 19, 2019

I'm having the same issue with findOneOrFail IMHO returning the first element when passing undefined as a parameter must not be the desired behavior. It's dangerous and no other ORM that I know does that

@matiasbeltramone
Copy link

The same issue for findOne and findOneOrFail, it's complete dangerous if you passing undefined as a parameter, must not be return the first element. :(

@Blockost
Copy link

Blockost commented Apr 8, 2019

Sorry for being little late for the party.

No, you except same behavior as SQL
SELECT * FROM users WHERE id = null LIMIT 1

I completely disagree. Passing undefined shouldn't cause passing null to sql query. Null and undefined are completely different primitive values. Undefined should be ignored(optional parameters) or always return no records(without querying).

@Kononnable Well, the same behaviour happens when calling findOneOrFail(null): first row is returned.

However, I agree that passing null or undefined does not make sense here and it probably means the code or the payload validation needs to be reviewed.

Having said that, sticking to what the translated SQL query would do is more intuitive for most people.

@mazyvan
Copy link

mazyvan commented Apr 8, 2019

I think this could be fixed just adding a special configuration. So any user could decide by its own if he/she wants the findOne method to return the first item when passing an undefined value or not

@malimccalla
Copy link
Contributor

malimccalla commented Jun 16, 2019

This had me scratching my head for sooo long today! I wrongly assumed that findOne(userId) where userId is undefined would not return me a user. Is this a bug?

edit:
Have just fully read the above discussion and I can see it's not a bug. Will have to make sure to do extra type checks where findOne is used

@uPaymeiFixit
Copy link

This caused several hours of debugging for me today as well. Specifically with the method findOneOrFail (also affected). The name suggests the method will fail if it can't find a matching id in the database, like when the search target is null. This unexpected behavior seems to be causing a headache for many typeorm users.

I cannot find a scenario where a developer would expect to pass in undefined and receive the first row. While it's possible somebody is intentionally using the method this way, I would guess there are far more developers stumped by this.

@malimccalla
Copy link
Contributor

malimccalla commented Jun 20, 2019

If agrument is passed and it's value is undefined, return undefined/throw an exception

IMO this should be the default for findOne having it return undefined and findOneOrFail throw an exception.

edit:

Worth mentioning though that if you wish to avoid this you could just do .findOne({ where: { id } }) in which case undefined would be returned

@LaLaLaOpera
Copy link

Even It should have been our responsibility to check and make sure null or undefined parameter never go though query in a first place, but it is disappointing to see that the ORM still can't handle a simple exception like this.

@Chris4589
Copy link

con Equal(value) pude solucionar el error.

@issar13
Copy link

issar13 commented May 5, 2023

con Equal(value) pude solucionar el error.

doesn't this break other parts of an application?

@Chris4589
Copy link

no

@avilabiel
Copy link

avilabiel commented Jun 8, 2023

TypeORM should be responsible to return the right data whenever there is a nullable or undefined value in findOne() function. This is weird and could be really dangerous depending on your application.

In my case, it returned a random value and we made our data dirty. It took a whole day to fix the messy data caused by this bug.

This is not fixed and should be reopened.

@issar13
Copy link

issar13 commented Jun 8, 2023

jst use the equal option on typeorm, fixes the issue

@avilabiel
Copy link

jst use the equal option on typeorm, fixes the issue

That does not make sense. If I'm finding one by id: null, the logical response would be bringing null, not the 1st resource/row.

@zmts
Copy link

zmts commented Jun 19, 2023

v0.3.12 same issue ((

@TheRaLabs
Copy link

fix this asap

@DaviRJ
Copy link

DaviRJ commented Aug 23, 2023

The current behavior of this functionality is far from being even what appears in the TypeORM documentation. It needs to be fixed asap.

image

@Irungaray
Copy link

Spent a couple hours debugging this. The frontend was not sending the id on the body (as it was meant to), so .findOne was returning the first user, that was a super admin as @AbuOmar said.

Thankfully we caught it on a development enviroment, otherwise it could've been a serious security issue.

@HaiAlison
Copy link

in my case, I will do this:

import { IsNull } from 'typeorm';

const barn = await BarnsModel.findOneBy({
      id: dto.barn_id || IsNull()
    });

@brunomichalski
Copy link

in my case, I will do this:

import { IsNull } from 'typeorm';

const barn = await BarnsModel.findOneBy({
      id: dto.barn_id || IsNull()
    });

This works for me, but I don't think it should work this way

@HaiAlison
Copy link

in my case, I will do this:

import { IsNull } from 'typeorm';

const barn = await BarnsModel.findOneBy({
      id: dto.barn_id || IsNull()
    });

This works for me, but I don't think it should work this way

yes, it works when you ensure that the field does not have any null value

@1mehdifaraji
Copy link

I did solve the issue by using the Equal parameter from typeorm and using it this way

  import { Equal } from "typeorm";

  return await userRepo.findOne({
    where: { phone: phone ? Equal(phone) : Equal(bodyPhone) },
    relations: {
      addresses: true,
      favourites: true,
      cart: true,
    },
  });

@ofuochi
Copy link

ofuochi commented Oct 1, 2023

.findOne({ where: { id } })

This has the same behaviour as returning the first record

@ezescigo
Copy link

ezescigo commented Oct 5, 2023

So what would be the best practice to use findOne? or should we use other method?

@os-moussao
Copy link

So what would be the best practice to use findOne? or should we use other method?

Imo, using .createQueryBuilder() is the most convenient alternative to findOne() so far.

@mariopepe
Copy link

No way this must be a meme

@Alamonall
Copy link

LMAO. When is this will be fixed????

@landersonalmeida
Copy link

LMAO. When is this will be fixed????

Maybe in 2024 🤣

@issar13
Copy link

issar13 commented Jan 12, 2024

LMAO. When is this will be fixed????

Maybe in 2024 🤣

from 2018?.....no hope

@issar13
Copy link

issar13 commented Jan 12, 2024

everyone just use Equal()

@merthanmerter
Copy link

wow. thats a dangerous bug! and yes, it is a bug!

@issar13
Copy link

issar13 commented Jan 18, 2024

wow. thats a dangerous bug! and yes, it is a bug!

but wait there's more...apparently when you query findOne. It makes an sql query twice. turn logging on and see it in realtime.

@magyarb
Copy link

magyarb commented Jan 30, 2024

This is a huge security risk, and not the way it should work. Until PR #9487 is merged, I have created an eslint rule that warns about it.

rules: {
    'no-restricted-syntax': [
      'error',
      {
        selector:
          "ObjectExpression > .properties[key.name='where']:has(.value.properties > .value:not(CallExpression))",
        message:
          'Unsafe where condition, that can leak data. Use Equal() instead.'
      }
    ],
}

@merthanmerter
Copy link

merthanmerter commented Jan 30, 2024

This is a huge security risk, and not the way it should work. Until PR #9487 is merged, I have created an eslint rule that warns about it.

rules: {
    'no-restricted-syntax': [
      'error',
      {
        selector:
          "ObjectExpression > .properties[key.name='where']:has(.value.properties > .value:not(CallExpression))",
        message:
          'Unsafe where condition, that can leak data. Use Equal() instead.'
      }
    ],
}

Thank you a lot. To use it with nested object like the example, I modified as below.

where: { customer: { id: Equal(id) }, status: Equal(Status.ACTIVE) },

'no-restricted-syntax': [
      'error',
      {
        selector:
          "ObjectExpression > .properties[key.name='where'] > .value > .properties:not(:has(CallExpression)), ObjectExpression > .properties[key.name='where'] > .value > .properties > .value > .properties:not(:has(CallExpression))",
        message: 'Unsafe where condition, that can leak data. Use Equal() instead.',
      },
    ],

@YuriiBoikoOpn
Copy link

just leave here
so issue not just with 1 property in findOne
but also with another cases: findOne, findOneBy, findBy when there are many properties
example:
const patientId=120
const id= null

repository.findBy({id, patientId}) - will return not expected all raws where patientId=120 , but will skip id validate at all (like and id = undefined not exist )

more tested examples on local:
Monosnap appointment-availability-milestone-alert test ts — pollin-backend 2023-12-15 21-08-32(1)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests