Skip to content

Commit

Permalink
feat: introduce app configs with shared auth clients
Browse files Browse the repository at this point in the history
  • Loading branch information
barinali committed Aug 16, 2023
1 parent 25983e0 commit 9fd1a9e
Show file tree
Hide file tree
Showing 47 changed files with 1,499 additions and 108 deletions.
8 changes: 7 additions & 1 deletion packages/backend/src/apps/gitlab/auth/verify-credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ const verifyCredentials = async ($: IGlobalVariable) => {
$.auth.data.accessToken = data.access_token;

const currentUser = await getCurrentUser($);
const screenName = [
currentUser.username,
$.auth.data.instanceUrl,
]
.filter(Boolean)
.join(' @ ');

await $.auth.set({
clientId: $.auth.data.clientId,
Expand All @@ -34,7 +40,7 @@ const verifyCredentials = async ($: IGlobalVariable) => {
scope: data.scope,
tokenType: data.token_type,
userId: currentUser.id,
screenName: `${currentUser.username} @ ${$.auth.data.instanceUrl}`,
screenName,
});
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Knex } from 'knex';

export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('app_configs', (table) => {
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
table.string('key').unique().notNullable();
table.boolean('allow_custom_connection').notNullable().defaultTo(false);
table.boolean('shared').notNullable().defaultTo(false);
table.boolean('disabled').notNullable().defaultTo(false);

table.timestamps(true, true);
});
}

export async function down(knex: Knex): Promise<void> {
return knex.schema.dropTable('app_configs');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Knex } from 'knex';

export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('app_auth_clients', (table) => {
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
table.string('name').unique().notNullable();
table.uuid('app_config_id').notNullable().references('id').inTable('app_configs');
table.text('auth_defaults').notNullable();
table.boolean('active').notNullable().defaultTo(false);

table.timestamps(true, true);
});
}

export async function down(knex: Knex): Promise<void> {
return knex.schema.dropTable('app_auth_clients');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Knex } from 'knex';

export async function up(knex: Knex): Promise<void> {
await knex.schema.table('connections', async (table) => {
table.uuid('app_auth_client_id').references('id').inTable('app_auth_clients');
});
}

export async function down(knex: Knex): Promise<void> {
return await knex.schema.table('connections', (table) => {
table.dropColumn('app_auth_client_id');
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Knex } from 'knex';

const getPermissionForRole = (
roleId: string,
subject: string,
actions: string[]
) =>
actions.map((action) => ({
role_id: roleId,
subject,
action,
conditions: [],
}));

export async function up(knex: Knex): Promise<void> {
const role = (await knex('roles')
.first(['id', 'key'])
.where({ key: 'admin' })
.limit(1)) as { id: string; key: string };

await knex('permissions').insert(
getPermissionForRole(role.id, 'App', [
'create',
'read',
'delete',
'update',
])
);
}

export async function down(knex: Knex): Promise<void> {
await knex('permissions').where({ subject: 'App' }).delete();
}
8 changes: 8 additions & 0 deletions packages/backend/src/graphql/mutation-resolvers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import createAppAuthClient from './mutations/create-app-auth-client.ee';
import createAppConfig from './mutations/create-app-config.ee';
import createConnection from './mutations/create-connection';
import createFlow from './mutations/create-flow';
import createRole from './mutations/create-role.ee';
Expand All @@ -17,6 +19,8 @@ import login from './mutations/login';
import registerUser from './mutations/register-user.ee';
import resetConnection from './mutations/reset-connection';
import resetPassword from './mutations/reset-password.ee';
import updateAppAuthClient from './mutations/update-app-auth-client.ee';
import updateAppConfig from './mutations/update-app-config.ee';
import updateConfig from './mutations/update-config.ee';
import updateConnection from './mutations/update-connection';
import updateCurrentUser from './mutations/update-current-user';
Expand All @@ -30,6 +34,8 @@ import upsertSamlAuthProvidersRoleMappings from './mutations/upsert-saml-auth-pr
import verifyConnection from './mutations/verify-connection';

const mutationResolvers = {
createAppAuthClient,
createAppConfig,
createConnection,
createFlow,
createRole,
Expand All @@ -49,6 +55,8 @@ const mutationResolvers = {
registerUser,
resetConnection,
resetPassword,
updateAppAuthClient,
updateAppConfig,
updateConfig,
updateConnection,
updateCurrentUser,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { IJSONObject } from '@automatisch/types';
import AppConfig from '../../models/app-config';
import Context from '../../types/express/context';

type Params = {
input: {
appConfigId: string;
name: string;
formattedAuthDefaults?: IJSONObject;
active?: boolean;
};
};

const createAppAuthClient = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('update', 'App');

const appConfig = await AppConfig
.query()
.findById(params.input.appConfigId)
.throwIfNotFound();

const appAuthClient = await appConfig
.$relatedQuery('appAuthClients')
.insert(
params.input
);

return appAuthClient;
};

export default createAppAuthClient;
36 changes: 36 additions & 0 deletions packages/backend/src/graphql/mutations/create-app-config.ee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import App from '../../models/app';
import AppConfig from '../../models/app-config';
import Context from '../../types/express/context';

type Params = {
input: {
key: string;
allowCustomConnection?: boolean;
shared?: boolean;
disabled?: boolean;
};
};

const createAppConfig = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('update', 'App');

const key = params.input.key;

const app = await App.findOneByKey(key);

if (!app) throw new Error('The app cannot be found!');

const appConfig = await AppConfig
.query()
.insert(
params.input
);

return appConfig;
};

export default createAppConfig;
46 changes: 39 additions & 7 deletions packages/backend/src/graphql/mutations/create-connection.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,59 @@
import { IJSONObject } from '@automatisch/types';
import App from '../../models/app';
import AppConfig from '../../models/app-config';
import Context from '../../types/express/context';
import { IJSONObject } from '@automatisch/types';

type Params = {
input: {
key: string;
appAuthClientId: string;
formattedData: IJSONObject;
};
};

const createConnection = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('create', 'Connection');

await App.findOneByKey(params.input.key);
const { key, appAuthClientId } = params.input;

const app = await App.findOneByKey(key);

const appConfig = await AppConfig.query().findOne({ key });

let formattedData = params.input.formattedData;
if (appConfig) {
if (appConfig.disabled) throw new Error('This application has been disabled for new connections!');

if (!appConfig.allowCustomConnection && formattedData) throw new Error(`Custom connections cannot be created for ${app.name}!`);

if (appConfig.shared && !formattedData) {
const authClient = await appConfig
.$relatedQuery('appAuthClients')
.findById(appAuthClientId)
.where({
active: true
})
.throwIfNotFound();

formattedData = authClient.formattedAuthDefaults;
}
}

const createdConnection = await context
.currentUser
.$relatedQuery('connections')
.insert({
key,
appAuthClientId,
formattedData,
verified: false,
});

return await context.currentUser.$relatedQuery('connections').insert({
key: params.input.key,
formattedData: params.input.formattedData,
verified: false,
});
return createdConnection;
};

export default createConnection;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Context from '../../types/express/context';
import AppAuthClient from '../../models/app-auth-client';

type Params = {
input: {
id: string;
};
};

const deleteAppAuthClient = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('delete', 'App');

await AppAuthClient
.query()
.delete()
.findOne({
id: params.input.id,
})
.throwIfNotFound();

return;
};

export default deleteAppAuthClient;
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { IJSONObject } from '@automatisch/types';
import AppAuthClient from '../../models/app-auth-client';
import Context from '../../types/express/context';

type Params = {
input: {
id: string;
name: string;
formattedAuthDefaults?: IJSONObject;
active?: boolean;
};
};

const updateAppAuthClient = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('update', 'App');

const {
id,
...appAuthClientData
} = params.input;

const appAuthClient = await AppAuthClient
.query()
.findById(id)
.throwIfNotFound();

await appAuthClient
.$query()
.patch(appAuthClientData);

return appAuthClient;
};

export default updateAppAuthClient;
39 changes: 39 additions & 0 deletions packages/backend/src/graphql/mutations/update-app-config.ee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import AppConfig from '../../models/app-config';
import Context from '../../types/express/context';

type Params = {
input: {
id: string;
allowCustomConnection?: boolean;
shared?: boolean;
disabled?: boolean;
};
};

const updateAppConfig = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('update', 'App');

const {
id,
...appConfigToUpdate
} = params.input;

const appConfig = await AppConfig
.query()
.findById(id)
.throwIfNotFound();

await appConfig
.$query()
.patch(
appConfigToUpdate
);

return appConfig;
};

export default updateAppConfig;

0 comments on commit 9fd1a9e

Please sign in to comment.