Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HPC-9280: Support for plan deletion #133

Merged
merged 4 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 53 additions & 7 deletions src/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
* This file is a new (type-safe) interface for common authentication functions
* ahead of the auth refactor outlined in HPC-6999
*/
import Knex = require('knex');
import * as crypto from 'node:crypto';
import { promisify } from 'node:util';
import type v4Models from '../db';
import { type AuthTargetId } from '../db/models/authTarget';
import { type ParticipantId } from '../db/models/participant';
import type { Database } from '../db/type';
import { Op } from '../db/util/conditions';
import { createDeferredFetcher } from '../db/util/deferred';
import { type InstanceOfModel } from '../db/util/types';
import type { InstanceOfModel, UserDataOfModel } from '../db/util/types';
import { type Context } from '../lib/context';
import { type SharedLogContext } from '../lib/logging';
import { organizeObjectsByUniqueProperty } from '../util';
Expand All @@ -31,8 +31,6 @@ import {

const randomBytes = promisify(crypto.randomBytes);

type Models = ReturnType<typeof v4Models>;

type Participant = InstanceOfModel<Database['participant']>;
type AuthGrantee = InstanceOfModel<Database['authGrantee']>;
type AuthTarget = InstanceOfModel<Database['authTarget']>;
Expand Down Expand Up @@ -471,11 +469,11 @@ export const getParticipantFromToken = async (
export const createToken = async ({
participant,
expires,
models,
database,
}: {
participant: ParticipantId;
expires?: Date;
models: Models;
database: Database;
}): Promise<{
instance: AuthToken;
token: string;
Expand All @@ -487,11 +485,59 @@ export const createToken = async ({
.digest()
.toString('hex');
return {
instance: await models.authToken.create({
instance: await database.authToken.create({
tokenHash,
participant,
expires,
}),
token,
};
};

export const deleteAuthTarget = async (
database: Database,
where: UserDataOfModel<Database['authTarget']>,
actor: ParticipantId,
trx: Knex.Transaction<any, any>
): Promise<void> => {
const authTargets = await database.authTarget.find({ where, trx });
const authGrants = await database.authGrant.find({
where: {
target: { [Op.IN]: authTargets.map((at) => at.id) },
},
trx,
});

await Promise.all(
authGrants.map(async (grant) => {
await database.authGrant.update(
{
grantee: grant.grantee,
target: grant.target,
roles: [], // Delete `authGrant` entry
},
actor,
trx
);
})
);

await Promise.all(
authTargets.map(async ({ id }) => {
await database.authGrantLog.destroy({
where: { target: id },
trx,
});

await database.authInvite.destroy({
where: { target: id },
trx,
});

await database.authTarget.destroy({
where: { id },
trx,
});
})
);
};
1 change: 1 addition & 0 deletions src/auth/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export const AUTH_PERMISSIONS = {
*/
CLONE_ANY_PROJECT: 'cloneAnyProject',
DELETE_ANY_PROJECT: 'canDeleteAnyProject',
DELETE_ANY_PLAN: 'canDeleteAnyPlan',
/**
* Can change visibility of any plan in Projects Module
*/
Expand Down
1 change: 1 addition & 0 deletions src/auth/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ export const calculatePermissionsFromRolesGrant = async <
global.add(P.global.EDIT_ANY_PLAN_DATA);
global.add(P.global.EDIT_ANY_MEASUREMENT);
global.add(P.global.CHANGE_ANY_PLAN_VISIBILITY_IN_PROJECTS);
global.add(P.global.DELETE_ANY_PLAN);
} else if (role === 'ftsAdmin') {
// New Permissions
global.add(P.global.VIEW_ANY_FLOW);
Expand Down
21 changes: 15 additions & 6 deletions src/db/models/authGrant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ const authGrantModel = (conn: Knex) => {
const create = (
data: UserData,
actor: ParticipantId,
date = new Date()
date = new Date(),
trx?: Knex.Transaction<any, any>
): Promise<Instance> => {
return conn.transaction(async (trx) => {
const createCallback = async (trx: Knex.Transaction<any, any>) => {
await authGrantLog.create(
{
grantee: data.grantee,
Expand All @@ -58,11 +59,17 @@ const authGrantModel = (conn: Knex) => {
}
);
return await model.create(data, { trx });
});
};

return trx ? createCallback(trx) : conn.transaction(createCallback);
};

const update = (data: UserData, actor: ParticipantId): Promise<void> => {
return conn.transaction(async (trx) => {
const update = (
data: UserData,
actor: ParticipantId,
trx?: Knex.Transaction<any, any>
): Promise<void> => {
const updateCallback = async (trx: Knex.Transaction<any, any>) => {
await authGrantLog.create(
{
grantee: data.grantee,
Expand Down Expand Up @@ -95,7 +102,9 @@ const authGrantModel = (conn: Knex) => {
},
});
}
});
};

return trx ? updateCallback(trx) : conn.transaction(updateCallback);
};

const createOrUpdate = async (
Expand Down
53 changes: 32 additions & 21 deletions src/db/models/authTarget.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as t from 'io-ts';
import { defineIDModel } from '../util/id-model';
import Knex = require('knex');

import { brandedType } from '../../util/io-ts';
import type { Brand } from '../../util/types';
Expand All @@ -21,28 +22,38 @@ const AUTH_TARGET_TYPE = {
governingEntity: null,
};

export default defineIDModel({
tableName: 'authTarget',
fields: {
generated: {
id: {
kind: 'branded-integer',
brand: AUTH_TARGET_ID,
export default (conn: Knex) => {
const model = defineIDModel({
tableName: 'authTarget',
fields: {
generated: {
id: {
kind: 'branded-integer',
brand: AUTH_TARGET_ID,
},
},
},
optional: {
targetId: {
kind: 'checked',
type: t.number,
optional: {
targetId: {
kind: 'checked',
type: t.number,
},
},
},
required: {
type: {
kind: 'enum',
values: AUTH_TARGET_TYPE,
required: {
type: {
kind: 'enum',
values: AUTH_TARGET_TYPE,
},
},
},
},
idField: 'id',
softDeletionEnabled: false,
});
idField: 'id',
softDeletionEnabled: false,
})(conn);

return {
...model,
/**
* @deprecated Use `deleteAuthTarget` utility method instead
*/
destroy: model.destroy,
};
};