Skip to content

Commit

Permalink
feat: Generate random passwords for user DBs
Browse files Browse the repository at this point in the history
  • Loading branch information
morgsmccauley committed Jul 17, 2023
1 parent 0c2a6e4 commit 0e37c63
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 10 deletions.
17 changes: 15 additions & 2 deletions indexer-js-queue-handler/provisioner.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import VError from "verror";
import pg from "pg";
import cryptoModule from "crypto";

import HasuraClient from "./hasura-client.js";

const DEFAULT_PASSWORD_LENGTH = 16;

const pool = new pg.Pool({
user: process.env.PG_ADMIN_USER,
password: process.env.PG_ADMIN_PASSWORD,
Expand All @@ -16,10 +19,12 @@ const pool = new pg.Pool({
export default class Provisioner {
constructor(
hasuraClient = new HasuraClient(),
pgPool = pool
pgPool = pool,
crypto = cryptoModule
) {
this.hasuraClient = hasuraClient;
this.pgPool = pgPool;
this.crypto = crypto;
}

async query(query, params = []) {
Expand All @@ -39,6 +44,14 @@ export default class Provisioner {
await this.query(`CREATE USER ${name} WITH PASSWORD '${password}';`)
}

generatePassword(length) {
return this.crypto.randomBytes(length)
.toString('base64')
.slice(0,length)
.replace(/\+/g, '0')
.replace(/\//g, '0');
}

async restrictDatabaseToUser(databaseName, userName) {
await this.query(`GRANT ALL PRIVILEGES ON DATABASE ${databaseName} TO ${userName};`);
await this.query(`REVOKE CONNECT ON DATABASE ${databaseName} FROM PUBLIC;`);
Expand Down Expand Up @@ -125,10 +138,10 @@ export default class Provisioner {

async provisionUserApi(userName, databaseSchema) {
const databaseName = userName;
const password = 'password';
const defaultSchema = 'public';

try {
const password = this.generatePassword(DEFAULT_PASSWORD_LENGTH)
await this.createUserDb(userName, password);
await this.addDatasource(userName, password, databaseName);
await this.runMigrations(databaseName, databaseSchema);
Expand Down
26 changes: 18 additions & 8 deletions indexer-js-queue-handler/provisioner.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ describe('Provisioner', () => {
const databaseSchema = 'CREATE TABLE blocks (height numeric)';
const error = new Error('some error');

const password = 'password';
const crypto = {
randomBytes: () => ({
toString: () => ({
slice: () => ({
replace: () => password,
}),
}),
}),
};

beforeEach(() => {
hasuraClient = {
Expand Down Expand Up @@ -46,7 +56,7 @@ describe('Provisioner', () => {
});

it('provisions an API for the user', async () => {
const provisioner = new Provisioner(hasuraClient, pgPool);
const provisioner = new Provisioner(hasuraClient, pgPool, crypto);

await provisioner.provisionUserApi(userName, databaseSchema);

Expand Down Expand Up @@ -77,55 +87,55 @@ describe('Provisioner', () => {
it('throws an error when it fails to create a postgres db', async () => {
pgClient.query = jest.fn().mockRejectedValue(error);

const provisioner = new Provisioner(hasuraClient, pgPool);
const provisioner = new Provisioner(hasuraClient, pgPool, crypto);

await expect(provisioner.provisionUserApi(userName, databaseSchema)).rejects.toThrow('Failed to provision endpoint: Failed to create user db: some error');
});

it('throws an error when it fails to add the db to hasura', async () => {
hasuraClient.addDatasource = jest.fn().mockRejectedValue(error);

const provisioner = new Provisioner(hasuraClient, pgPool);
const provisioner = new Provisioner(hasuraClient, pgPool, crypto);

await expect(provisioner.provisionUserApi(userName, databaseSchema)).rejects.toThrow('Failed to provision endpoint: Failed to add datasource: some error');
});

it('throws an error when it fails to run migrations', async () => {
hasuraClient.runSql = jest.fn().mockRejectedValue(error);

const provisioner = new Provisioner(hasuraClient, pgPool);
const provisioner = new Provisioner(hasuraClient, pgPool, crypto);

await expect(provisioner.provisionUserApi(userName, databaseSchema)).rejects.toThrow('Failed to provision endpoint: Failed to run migrations: some error');
});

it('throws an error when it fails to fetch table names', async () => {
hasuraClient.getTableNames = jest.fn().mockRejectedValue(error);

const provisioner = new Provisioner(hasuraClient, pgPool);
const provisioner = new Provisioner(hasuraClient, pgPool, crypto);

await expect(provisioner.provisionUserApi(userName, databaseSchema)).rejects.toThrow('Failed to provision endpoint: Failed to fetch table names: some error');
});

it('throws an error when it fails to track tables', async () => {
hasuraClient.trackTables = jest.fn().mockRejectedValue(error);

const provisioner = new Provisioner(hasuraClient, pgPool);
const provisioner = new Provisioner(hasuraClient, pgPool, crypto);

await expect(provisioner.provisionUserApi(userName, databaseSchema)).rejects.toThrow('Failed to provision endpoint: Failed to track tables: some error');
});

it('throws an error when it fails to track foreign key relationships', async () => {
hasuraClient.trackForeignKeyRelationships = jest.fn().mockRejectedValue(error);

const provisioner = new Provisioner(hasuraClient, pgPool);
const provisioner = new Provisioner(hasuraClient, pgPool, crypto);

await expect(provisioner.provisionUserApi(userName, databaseSchema)).rejects.toThrow('Failed to provision endpoint: Failed to track foreign key relationships: some error');
})

it('throws an error when it fails to add permissions to tables', async () => {
hasuraClient.addPermissionsToTables = jest.fn().mockRejectedValue(error);

const provisioner = new Provisioner(hasuraClient, pgPool);
const provisioner = new Provisioner(hasuraClient, pgPool, crypto);

await expect(provisioner.provisionUserApi(userName, databaseSchema)).rejects.toThrow('Failed to provision endpoint: Failed to add permissions to tables: some error');
});
Expand Down

0 comments on commit 0e37c63

Please sign in to comment.