diff --git a/README.md b/README.md index 518b3a7..6ef56a2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,11 @@ -surreal_access_control_system -=============== +# surreal_access_control_system -This repository explores how to build a access control system on top of SurrealDB using TypeScript. \ No newline at end of file +This repository explores how to build a access control system on top of SurrealDB using TypeScript. +We will build a nodejs backend command line application that uses the SurrealDB Javascript/Typescript SDK to connect to a local SurrealDB instance. +We will authenticate with various users and display a list of 'product' entries from a test database. + +We will use ```DEFINE ACCESS``` statements to define roles based authentication. One of our test users should be able to fully access (create, read, update, delete) +the 'product' entries the other one should not have access to the table and thus should not be able to retrieve a list of products. + +First we add the SDK to our project ```npm install --save surrealdb```. + diff --git a/backend/src/init_db.ts b/backend/src/init_db.ts new file mode 100644 index 0000000..ca88ebc --- /dev/null +++ b/backend/src/init_db.ts @@ -0,0 +1,96 @@ +import Surreal from 'surrealdb'; +import { argv } from 'process'; + +const auth = { + username: 'root', + password: 'root', +} + +const db_url = "http://localhost:8000"; +const db_name = { namespace: "ts_test", database: "access_test" }; + +async function main() { + // Extract username and password from command-line arguments + const db = new Surreal(); + try { + await db.connect(db_url, { auth }); + await db.use(db_name); + } catch (err) { + console.error("Could not connect to SurrealDB server: ", err instanceof Error ? err.message : String(err)); + throw (err); + } + + try { + await db.query(` + define table if not exists user schemafull; + `); + + await db.query(` + define field if not exists username on user type string; + define field if not exists password on user type string; + define field if not exists roles on user type set; + `); + + await db.query(` + define index if not exists idx_username on user fields username unique; + `); + } catch (err) { + console.error("Could not create table user or its fields: ", err instanceof Error ? err.message : String(err)); + throw (err); + } + + try { + await db.query(` + insert into user [ + { + username: "test1", + password: crypto::argon2::generate("test"), + roles: [] + }, + { + username: "test2", + password: crypto::argon2::generate("test"), + roles: ['product_manager'] + } + ]; + `) + } catch (err) { + console.error("Could not create user entries: ", err instanceof Error ? err.message : String(err)); + } + + try { + await db.query(` + define table if not exists product schemafull + permissions for select where $auth.roles contains 'product_manager'; + define field if not exists code on product type string; + define field if not exists available on product type bool; + define index if not exists idx_code on product fields code; + `); + } catch (err) { + console.error("Could not create table product: ", err instanceof Error ? err.message : String(err)); + throw (err); + } + + try { + await db.query(` + insert into product { + code: "test_product1", + available: true + }; + `) + } catch (err) { + console.error("Could not create user entries: ", err instanceof Error ? err.message : String(err)); + } + + try { + await db.query(` + define access overwrite user on database type record + signup (create user set username = $username, password = crypto::argon2::generate($password), roles=[]) + signin (select * from user where username = $username and crypto::argon2::compare(password, $password)); + `) + } catch (err) { + console.error("Could not define access: ", err instanceof Error ? err.message : String(err)); + } +} + +main(); \ No newline at end of file diff --git a/backend/src/main.ts b/backend/src/main.ts new file mode 100644 index 0000000..5fc79d5 --- /dev/null +++ b/backend/src/main.ts @@ -0,0 +1,38 @@ +import Surreal from 'surrealdb'; +import { argv } from 'process'; +import { User } from './user'; + +async function main() { + // Extract username and password from command-line arguments + const username = argv[2]; + const password = argv[3]; + + if (!username || !password) { + console.error('Usage: node main.ts '); + process.exit(1); + } + + const db = new Surreal(); + + // Connect to the local SurrealDB instance + try { + await db.connect("ws://localhost:8000"); + await db.signin({ + username: username, + password: password + }); + await db.use({ namespace: 'ts_test', database: 'ts_test'}); + + const products = await db.select('product'); + + console.log('Products:'); + products.forEach(product => { + console.log(JSON.stringify(product, null, 2)); + }); + + } catch (err) { + console.error("Could not connect to db", err instanceof Error ? err.message : String(err)); + } +} + +main(); \ No newline at end of file diff --git a/backend/src/user.ts b/backend/src/user.ts new file mode 100644 index 0000000..e73c221 --- /dev/null +++ b/backend/src/user.ts @@ -0,0 +1,6 @@ +export type User = { + id: string; + user_name: string; + password: string; + roles: Array; +} \ No newline at end of file