The access and params fields of the Record struct are the only one of the four that requires extra explanation.
The access field comes from the name of the access method used to create a record user. In our case, we will use an access method called 'account'. An access method will generally create a record on signup, and select a record on signin, which is the case with our access method as well.
Since the params of a Record struct used to sign up and sign can be any type that implements SurrealValue, we can put our own struct together and pass them into these methods. On signup, the database will CREATE a user record that holds the name and password, and on signin it will SELECT the user that has a matching name and password.
Whenever a record user is signed into the database, the $auth parameter will be populated with its record ID. The DEFINE TABLE statement then uses this to give the record user permissions to CREATE and SELECT any person record, but to UPDATE and DELETE only when the created_by field matches the ID in $auth. This created_by field is automatically set and is READONLY. It will show up as None when a system user is signed in.
The .signup() and .signin() methods return an AccessToken, which can be printed out if needed using the .into_insecure_token() or .as_insecure_token() methods. As the method names imply, great care should be taken care with them. The AccessToken struct redacts the token so printing out a AccessToken on its own will not display it.
The sample code below uses a crate called faker_rand to generate a random name and password to make it easy to rerun the code and experiment with the behaviour.
asyncfnmake_new_user(db: &Surreal<Client>)->Result<RecordUser,Error> { letname=rand::random::<FirstName>().to_string(); letpass=rand::random::<FirstName>().to_string(); println!("Signing in as user {name} and password {pass}"); letjwt=db .signup(Record{ access: "account".to_string(), namespace: "namespace".to_string(), database: "database".to_string(), params: Params{ name: name, pass: pass, }, }) .await? .into_insecure_token(); println!("New user created!\n\nName: {name}\nPassword: {pass}\nToken: {jwt}\n\nTo log in, use this command:\n\nsurreal sql --namespace namespace --database database --pretty --token \"{jwt}\"\n"); Ok(RecordUser{name,pass}) }
The entire code is as follows. Inside main(), the client first logs in as a root user to define the schema and create a person record. It then signs up as a new record user, and then signs in again to demonstrate that a new token is returned each time this method is called (as long as the name and password match). The record user then creates a person record - which it is permitted to do - and then tries to delete all of the person records in the database. However, the person record created by the root user will remain untouched.
db.query( "DEFINE TABLE person SCHEMALESS PERMISSIONS FOR CREATE, SELECT WHERE $auth, FOR UPDATE, DELETE WHERE created_by = $auth; DEFINE FIELD name ON TABLE person TYPE string; DEFINE FIELD created_by ON TABLE person VALUE $auth READONLY;
DEFINE INDEX unique_name ON TABLE user FIELDS name UNIQUE; DEFINE ACCESS account ON DATABASE TYPE RECORD SIGNUP ( CREATE user SET name = $name, pass = crypto::argon2::generate($pass) ) SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) ) DURATION FOR TOKEN 15m, FOR SESSION 12h ;", ) .await?;
db.query("CREATE person SET name = 'Created by root'") .await?;
letuser=make_new_user(&db).await?;
get_new_token(&db,user).await?;
db.query("CREATE person SET name = 'Created by record user'") .await?;
New token! Sign in with surreal sql --pretty --token "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MzA4NjQ2NDAsIm5iZiI6MTczMDg2NDY0MCwiZXhwIjoxNzMwODY1NTQwLCJpc3MiOiJTdXJyZWFsREIiLCJqdGkiOiIyZjExZTQzZi04ODg1LTRmNzAtOGI2Zi0zNGZmZGZlZWY4MDUiLCJOUyI6Im5hbWVzcGFjZSIsIkRCIjoiZGF0YWJhc2UiLCJBQyI6ImFjY291bnQiLCJJRCI6InVzZXI6YW92bHN0dzBvN2R1Y2J4ZWpqZWsifQ.0GWwMiAjn5kKUoNAw2TxAdVLhWIHeJsVWvlAzw1QZ91qdIhkazygdG5uFl5DHVmmoYC-cLo-ko27jiRCid5xDg"
Two `person` records: [Person { name: "Created by record user", id: RecordId { table: "person", key: String("i2sv6bqk0mso0udgzmdx") }, created_by: Some(RecordId { table: "user", key: String("aovlstw0o7ducbxejjek") }) }, Person { name: "Created by root", id: RecordId { table: "person", key: String("u6mry7agmsmtbwl94yui") }, created_by: None }]
`person` created by root is still there: [Person { name: "Created by root", id: RecordId { table: "person", key: String("u6mry7agmsmtbwl94yui") }, created_by: None }]
See also
Each of the crates featured in the Rust SDK also use the schema above. Three of them (Actix, Axum, Rocket) are web servers, while Egui is a visual UI. See the mini tutorials for each of these crates here: