surrealdb / surrealdb.js

SurrealDB SDK for JavaScript
https://surrealdb.com
Apache License 2.0
272 stars 45 forks source link

Bug: Scope authentication id variable not working #243

Closed ntorrey closed 2 months ago

ntorrey commented 3 months ago

Describe the bug

I get ResponseError: There was a problem with the database: No record was returned when testing scope authentication with the .connect() method. Root authentication works fine. I have not tried namespace or database auth. I suspect something is happening with the SIGNIN handler and the passing/receiving of the custom variables needed for scope authentication.

Steps to reproduce

My scope in SurrealDB looks something like this when running INFO FOR DB:

"scopes": {
   "user": "DEFINE SCOPE user SESSION 1d SIGNIN (SELECT * FROM $id)" <-- $id here is a custom variable.
}

Here is my my code in javascript:

db = new Surreal()

await this.db.connect('https://[urlHere].com', {
      namespace: 'test',
      database: 'test',
      auth: {
        namespace: 'test',
        database: 'test',
        scope: 'user',
        id: 'users:12345' <-- id here is the custom variable to use.
      }
    }
)

I get ResponseError: There was a problem with the database: No record was returned.

If I hard-code the id, it works:

"scopes": {
   "user": "DEFINE SCOPE user SESSION 1d SIGNIN (SELECT * FROM users:12345)"
}

Expected behaviour

I expect scope authentication to successfully retrieve a record based off of the id I pass to it.

SurrealDB version

1.4.2 for windows on x86_64

SurrealDB.js version

1.0.0-beta.5

Contact Details

via this github issue

Is there an existing issue for this?

Code of Conduct

oskar-gmerek commented 2 months ago
(property) auth?: string | {
    username: string;
    password: string;
    namespace?: undefined;
    database?: undefined;
    scope?: undefined;
} | {
    namespace: string;
    username: string;
    password: string;
    database?: undefined;
    scope?: undefined;
} | {
    ...;
} | objectOutputType<...> | undefined

For scope auth:

Zrzut ekranu 2024-04-24 o 11 49 09

id is not valid property so you are not authenticated because SurrealDB do not get any correct data. If you provide username and password then sign in should work correctly, but of course you need to update schema as well to something like this:

DEFINE TABLE users
   PERMISSIONS
      FOR select FULL;
DEFINE FIELD username ON TABLE users;
DEFINE FIELD password ON TABLE users;
CREATE users:12345 SET username = 'demo_user', password = '123';

DEFINE SCOPE user SESSION 1d
    SIGNIN ( SELECT * FROM users WHERE username = $username AND password = $password )
;

...and then you can:

db = new Surreal()

await db.connect('https://[urlHere].com', {
      namespace: 'test',
      database: 'test',
      auth: {
        scope: 'user',
        username: 'demo_user',
        password: '123'
      }
    }
)

PS. For most use cases I think that way to authenticate scope user is not the best. I think you will want to use db.signin + db.authenticate

PS2. I'm not pretty sure, but I think you can also pass a token to authenticate user like this:

 await db.connect('https://[urlHere].com', {
      namespace: 'test',
      database: 'test',
      auth: <token>
    }
)
tai-kun commented 2 months ago

When I modified SurrealQL as follows, the ResponseError did not occur.

- DEFINE SCOPE user SESSION 1d SIGNIN (SELECT * FROM $id)
+ DEFINE SCOPE user SESSION 1d SIGNIN (SELECT * FROM type::thing($id))

SurrealDB automatically converts strings that meet certain formats into special values. Record ID is one of those special values. For example, quoting from the official documentation, the following query returns Record ID:

-- Interpreted as a record ID, because of the structure with the semicolon:
RETURN "5:20";

reference: Strings | SurrealQL | SurrealDB Docs

I don't know much about SurrealDB, but I think it probably only interprets it as a Record ID when parsing a query. Therefore, after $id in FROM $id is identified as a parameter by the query parser, the actual variable "users:12345" is treated as a string.

I actually ran the following query to check:

const db = new Surreal() // surreal.js v1.0.0-beta.5
// connect ....
await db.query(`
  RETURN $id;
  RETURN "users:12345";
  RETURN type::thing($id);
`, { id: "users:12345" })
// [
//   "users:12345",
//   RecordId { tb: "users", id: 12345 },
//   RecordId { tb: "users", id: 12345 }
// ]

By the way, I looked into it and it seems that the id property as a variable is one of the valid properties.

The variables are validated with zod on the client side. Zod's .catchall(<ZodType>) is a function that validates <ZodType> against all properties except those of object. And <ZodType> is .unknown in this case, which is the same as .any except that it becomes an unknown type in TypeScript (Reference), so the variable containing the id property is sent to server.

On the server side, the variables are probably being validated here. PROTECTED_PARAM_NAMES is defined as [ "auth", "scope", "token", "session" ] in the current version (v1.4.2). id is not in this.

If you actually try to use a variable that contains the auth property, you will get an error:

await db.query("RETURN $auth", { auth: "users:12345" })
// ResponseError: There was a problem with the database: 'auth' is a protected variable and cannot be set

However, I haven't really started looking into SurrealDB for a while, so I don't really know much about it. There may be major errors in what I have said here. I would be happy if I could help you.

ntorrey commented 2 months ago

Thank you @tai-kun ! I made the change that you suggest above and it seems to be working now. Very helpful explanation!