Skip to content

Commit

Permalink
fix: allow tenant_id and user_id context to be set
Browse files Browse the repository at this point in the history
  • Loading branch information
jrea committed Oct 19, 2023
1 parent ed44392 commit d2aa140
Show file tree
Hide file tree
Showing 16 changed files with 334 additions and 186 deletions.
1 change: 1 addition & 0 deletions packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
},
"dependencies": {
"@niledatabase/js": "^1.0.0-alpha.195",
"jose": "^4.15.4",
"knex": "^2.4.2",
"pg": "^8.10.0"
}
Expand Down
36 changes: 36 additions & 0 deletions packages/server/src/Server.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Nile from './Server';

describe('server', () => {
it('has reasonable defaults', () => {
const config = {
database: 'database',
workspace: 'workspace',
};
const server = Nile(config);
expect(server.config.db.connection).toEqual({
host: 'db.thenile.dev',
port: 5432,
database: 'database',
});
expect(server.config.api.basePath).toEqual('https://api.thenile.dev');
});
it('sets a tenant id everywhere when set', async () => {
const config = {
database: 'database',
workspace: 'workspace',
};
const nile = Nile(config);
nile.tenantId = 'tenantId';
nile.userId = 'userId';
for (const api in nile.api) {
// @ts-expect-error - checking api
const _api = nile.api[api];
expect(_api.tenantId).toEqual('tenantId');
}

// @ts-expect-error - checking db
expect(nile._db.tenantId).toEqual('tenantId');
// @ts-expect-error - checking db
expect(nile._db.userId).toEqual('userId');
});
});
109 changes: 78 additions & 31 deletions packages/server/src/Server.ts
Original file line number Diff line number Diff line change
@@ -1,84 +1,126 @@
import { Knex } from 'knex';

import { ServerConfig } from './types';
import { Config } from './utils/Config';
import Auth from './auth';
import Users from './users';
import Tenants from './tenants';
import { watchTenantId } from './utils/Event';
import _knex, { extendKnex } from './db';
import { watchTenantId, watchToken, watchUserId } from './utils/Event';
import NileDatabase, { NileDatabaseI } from './db';

type Api = {
auth: Auth;
users: Users;
tenants: Tenants;
};

const init = (config: Config): [Api, Knex] => {
const init = (config: Config): [Api, NileDatabase] => {
const auth = new Auth(config);
const users = new Users(config);
const tenants = new Tenants(config);
const dbConfig = {
...config.db,
client: 'pg',
};
const db = new NileDatabase(config);
return [
{
auth,
users,
tenants,
},
_knex(dbConfig),
db,
];
};

class Server {
config: Config;
api: Api;
db: Knex;
private _db: NileDatabase;

constructor(config?: ServerConfig) {
this.config = new Config(config);
const [api, knex] = init(this.config);
this.api = api;
this.db = knex;
extendKnex();
this._db = knex;

watchTenantId((tenantId) => {
this.tenantId = tenantId;
});
watchUserId((userId) => {
this.userId = userId;
});
watchToken((token) => {
this.token = token;
});
}

setConfig(cfg: Config) {
// if a config has changed, re init everything
this.api = null as unknown as Api;
this.db = null as unknown as Knex;
const [api, knex] = init(cfg);
this.api = api;
this.db = knex;
this.config = cfg;
if (cfg.db.connection) {
this.config.db.connection = cfg.db.connection;
}
if (cfg.database && this.database !== cfg.workspace) {
this.database = cfg.database;
}
if (cfg.workspace && this.workspace !== cfg.workspace) {
this.workspace = cfg.workspace;
}
}

set database(val: string | void) {
if (val) {
this.config.database = val;
this.config.db.connection.database = val;
this.api.auth.database = val;
this.api.users.database = val;
this.api.tenants.database = val;
}
}

set workspace(val: string | void) {
if (val) {
this.config.workspace = val;
this.api.auth.workspace = val;
this.api.users.workspace = val;
this.api.tenants.workspace = val;
}
}

get userId(): string | undefined | null {
return this.config.userId;
}

set userId(userId: string | undefined | null) {
this.database = this.config.database;

this.config.userId = userId;

// update the db with config values
this._db.setConfig(this.config);

if (this.api) {
this.api.auth.userId = this.config.userId;
this.api.users.userId = this.config.userId;
this.api.tenants.userId = this.config.userId;
}
}

get tenantId(): string | undefined | null {
return this.config.tenantId;
}

set tenantId(tenantId: string | undefined | null) {
if (tenantId) {
this.config.tenantId = tenantId;
if (this.api) {
this.api.auth.tenantId = tenantId;
this.api.users.tenantId = tenantId;
this.api.tenants.tenantId = tenantId;
}
this.database = this.config.database;
this.config.tenantId = tenantId;
// update the db with config values
this._db.setConfig(this.config);

if (this.api) {
this.api.auth.tenantId = tenantId;
this.api.users.tenantId = tenantId;
this.api.tenants.tenantId = tenantId;
}
}

get token(): string | undefined {
get token(): string | undefined | null {
return this.config?.api?.token;
}

set token(token: string | undefined) {
set token(token: string | undefined | null) {
if (token) {
this.config.api.token = token;
if (this.api) {
Expand All @@ -88,11 +130,16 @@ class Server {
}
}
}
get db(): NileDatabaseI {
// only need to interact with the knex object
//@ts-expect-error - because that's where it is in the proxy
return this._db.db.db;
}
}

const server = new Server();

// export default Server;
export default function Nile(config: ServerConfig) {
const server = new Server();
server.setConfig(new Config(config as ServerConfig));
return server;
}
1 change: 1 addition & 0 deletions packages/server/src/auth/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Auth from './';

const baseConfig = [
'_tenantId',
'_userId',
'api',
'createProvider',
'database',
Expand Down
33 changes: 31 additions & 2 deletions packages/server/src/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { RestModels } from '@niledatabase/js';
import { Config } from '../utils/Config';
import Requester, { NileRequest, NileResponse } from '../utils/Requester';
import { ResponseError } from '../utils/ResponseError';
import { X_NILE_TENANT, getTenantFromHttp } from '../utils/fetch';
import {
X_NILE_TENANT,
X_NILE_USER_ID,
getTenantFromHttp,
} from '../utils/fetch';
import { updateToken, updateUserId } from '../utils/Event';

export default class Auth extends Config {
constructor(config: Config) {
Expand Down Expand Up @@ -83,6 +88,8 @@ export default class Auth extends Config {
const tenantId = tenant?.next().value;
headers.set(X_NILE_TENANT, tenantId);
headers.append('set-cookie', `tenantId=${tenantId}; path=/; httponly;`);
updateToken(token.token.jwt);

return new Response(JSON.stringify(token), { status: 200, headers });
}
const text = await res.text();
Expand All @@ -98,6 +105,7 @@ export default class Auth extends Config {
const accessToken = (await body.get('access_token')) as string;
const tenantId = (await body.get('tenantId')) as string;
const cookie = `${this.api?.cookieKey}=${accessToken}; path=/; samesite=lax; httponly;`;
updateToken(accessToken);
headers.append('set-cookie', cookie);
headers.set(X_NILE_TENANT, tenantId);
headers.append('set-cookie', `tenantId=${tenantId}; path=/; httponly;`);
Expand Down Expand Up @@ -128,8 +136,29 @@ export default class Auth extends Config {
req: NileRequest<RestModels.CreateBasicUserRequest>,
init?: RequestInit
): NileResponse<RestModels.LoginUserResponse> => {
const headers = new Headers();
const _requester = new Requester(this);
return _requester.post(req, this.signUpUrl, init);
const res = await _requester.post(req, this.signUpUrl, init).catch((e) => {
// eslint-disable-next-line no-console
console.error(e);
return e;
});
if (res instanceof ResponseError) {
return res.response;
}
if (res && res.status >= 200 && res.status < 300) {
const token: RestModels.LoginUserResponse = await res.json();
const cookie = `${this.api?.cookieKey}=${token.token?.jwt}; path=/; samesite=lax; httponly;`;
headers.append('set-cookie', cookie);
const { id } = token;
updateToken(token.token?.jwt);
updateUserId(id);
headers.set(X_NILE_USER_ID, id);
headers.append('set-cookie', `userId=${id}; path=/; httponly;`);
return new Response(JSON.stringify(token), { status: 201, headers });
}
const text = await res.text();
return new Response(text, { status: res.status });
};

updateProviderUrl(providerName: string) {
Expand Down
78 changes: 14 additions & 64 deletions packages/server/src/db/db.test.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,20 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Knex } from 'knex';

import NileDB, { handleWithUser, handleWithTenant } from './index';
import NileDB from './index';

const properties = ['knex', 'tenantId', 'userId', 'db', 'config'];
describe('db', () => {
it('is actually knex', () => {
const db = NileDB({ client: 'pg' });
expect(String(db)).toContain('knex');
});

it('sets and resets tenant', async () => {
const context = {
client: {
raw: jest.fn(),
},
};
await handleWithTenant(
context as unknown as Knex.QueryBuilder<any, any>,
'tenantId'
);
expect(context.client.raw).toBeCalledTimes(2);
expect(context.client.raw).toBeCalledWith('RESET nile.tenant_id');
expect(context.client.raw).toBeCalledWith(
"SET nile.tenant_id = 'tenantId'"
);
});
it('resets tenant with a blank value', async () => {
const context = {
client: {
raw: jest.fn(),
},
};
await handleWithTenant(
context as unknown as Knex.QueryBuilder<any, any>,
null
);
expect(context.client.raw).toBeCalledTimes(1);
expect(context.client.raw).toBeCalledWith('RESET nile.tenant_id');
});
it('sets and resets user', async () => {
const context = {
client: {
raw: jest.fn(),
it('has expected properties', () => {
const db = new NileDB({
workspace: 'workspace',
database: 'database',
db: {
connection: { port: 4433 },
},
};
await handleWithUser(
context as unknown as Knex.QueryBuilder<any, any>,
'tenantId',
'userId'
);
expect(context.client.raw).toBeCalledTimes(4);
expect(context.client.raw).toBeCalledWith('RESET nile.user_id');
expect(context.client.raw).toBeCalledWith("SET nile.user_id = 'userId'");
});
it('resets user with a blank value', async () => {
const context = {
client: {
raw: jest.fn(),
api: {
token: 'blah',
},
};
await handleWithUser(
context as unknown as Knex.QueryBuilder<any, any>,
undefined,
undefined
);
expect(context.client.raw).toBeCalledTimes(2);
expect(context.client.raw).toBeCalledWith('RESET nile.user_id');
tenantId: null,
userId: null,
});
expect(Object.keys(db).sort()).toEqual(properties.sort());
});
});
Loading

0 comments on commit d2aa140

Please sign in to comment.