Newer
Older
surreal_access_control_system / backend / src / init_db.ts
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() {
    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);
    }

    //define user table
    try {
        await db.query(`
            define table if not exists user schemafull;
        `);

        await db.query(`
            define field if not exists email on user type string;
            define field if not exists password on user type string;
        `);

        await db.query(`
            define index if not exists idx_email on user fields email unique;
        `);
    } catch (err) {
        console.error("Could not create table user or its fields: ", 
            err instanceof Error ? err.message : String(err));
        throw (err);
    }

    //insert test user entries
    try {
        await db.query(`
            insert into user [
                {
                    email: "appuser1@example.com",
                    password: crypto::argon2::generate("test")
                },
                {
                    email: "appuser2@example.com",
                    password: crypto::argon2::generate("test")
                }
            ];
        `)
    } catch (err) {
        console.error("Could not create user entries: ",
            err instanceof Error ? err.message : String(err));
    }

    // define role table
    try {
        await db.query(`
            define table role schemafull;            
        `);
        
        await db.query(`
            define field name on role type string;
        `);
    } catch (err) {
        console.error("Could not create table role or its field: ",
            err instanceof Error ? err.message : String(err));
        throw (err);
    }

    // define product_manager role
    try {
        await db.query(`
            create role:product_manager content {
                name: "product_manager"
            }
        `);
    } catch (err) {
        console.error("Could not create role: ", 
            err instanceof Error ? err.message : String(err));
        throw (err);
    }

    // define has_role table
    try {
        await db.query(`
            define table has_role schemafull type relation from user to role enforced;
        `);
    } catch (err) {
        console.error("Could not create has_role: ", 
            err instanceof Error ? err.message : String(err));
        throw (err);
    }

    // define *can_do* tables
    try {
        // additional controlled tables could be added here like:
        // define table can_select schemafull type relation from role to product|person|other_table enforced;
        // or simpler:
        // define table can_select type relation;
        // with no restriction on the types of *in* and *out* tables.
        await db.query(`
            define table can_select schemafull type relation from role to product enforced;
            define table can_create schemafull type relation from role to product enforced;
            define table can_update schemafull type relation from role to product enforced;
            define table can_delete schemafull type relation from role to product enforced;
        `);
    } catch (err) {
        console.error("Could not create relation table: ", 
            err instanceof Error ? err.message : String(err));
        throw (err);
    }

    // create relation entries
    try {
        await db.query(`
            relate user:appuser1->has_role->role:product_manager;
            relate role:product_manager->can_select->(select * from product);
        `);
    } catch (err) {
        console.error("Could not create relation entry: ", 
            err instanceof Error ? err.message : String(err));
        throw (err);
    }

    // define access *account* of type record
    try {
        await db.query(`
            DEFINE ACCESS overwrite account ON DATABASE TYPE RECORD
            SIGNUP ( CREATE user SET email = $email, password = crypto::argon2::generate($password) )
            SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(password, $password) )
            DURATION FOR TOKEN 15m, FOR SESSION 12h
        `);
    } catch (err) {
        console.error("Could not define access method: ", err instanceof Error ? err.message : String(err));
        throw (err);
    }

    // define product table
    // This is where the permissions are ultimately defined.
    // We only want users to be able to select from the product table that are assigned a role that itself 
    // is connected to the product table with the right relationship that is an entry in the *can_select* relation table.
    // Additional permissions for create, update, delete are left out here for the sake of brevity.
    // Additional permissions would make use of the same subquery though with *can_select* replaced by the respective
    // relation table name (*can_create* etc.).
    try {
        await db.query(`
            define table overwrite product schemafull
                permissions for select 
                where $access = "account"
                and (select <-can_select.in<-has_role.in[0]
                    from product)[0]["<-can_select"]["in"]["<-has_role"]["in"] contains $auth.id;
        `);

        await db.query(`
            define field code on product type string;
            define field available on product type bool;
        `);
    } catch (err) {
        console.error("Could not create table product or one of its fields: ", 
            err instanceof Error ? err.message : String(err));
        throw (err);
    }

    try {
        await db.query(`
            create product:testproduct content {
                code: "testproduct",
                available: true
            };
        `)
    } catch (err) {
        console.error("Could not product entry: ", err instanceof Error ? err.message : String(err));
    }
}

main();