From f15d37f68d7851a9621f4ed8473a34c2abb15949 Mon Sep 17 00:00:00 2001 From: Pl217 Date: Thu, 12 Aug 2021 19:56:03 +0200 Subject: [PATCH] Define more DB models Define `authToken, `participant`, `participantRole`, `role` and `rolePermittedAction`. Also, completely port src/auth/role.data.ts and define method to extract io-ts types from this array of data. This method needs TypeScript 4.3 to work. --- src/auth/role.data.ts | 178 +++++++++++++++++++++++++++ src/db/index.ts | 12 +- src/db/models/authToken.ts | 18 +++ src/db/models/participant.ts | 19 +++ src/db/models/participantRole.ts | 41 ++++++ src/db/models/permittedAction.ts | 4 + src/db/models/role.ts | 43 +++++++ src/db/models/rolePermittedAction.ts | 37 ++++++ 8 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 src/auth/role.data.ts create mode 100644 src/db/models/authToken.ts create mode 100644 src/db/models/participantRole.ts create mode 100644 src/db/models/permittedAction.ts create mode 100644 src/db/models/role.ts create mode 100644 src/db/models/rolePermittedAction.ts diff --git a/src/auth/role.data.ts b/src/auth/role.data.ts new file mode 100644 index 00000000..d1064e46 --- /dev/null +++ b/src/auth/role.data.ts @@ -0,0 +1,178 @@ +/** + * The raw data from the following tables from prod: + * + * * `role` + * * `rolePermittedAction` + * + * @deprecated + */ +export const ROLES = [ + { + // Usage: 18 + // workflowRole: 382 + name: 'hpcadmin', + description: 'HPC Administrator', + targetTypes: [], + permittedActions: [ + 'accessAllPlans', + 'createDisaggregationModel', + 'createPlan', + 'deletePlan', + 'editParticipants', + 'updatePermittedActions', + 'viewRevisions', + 'createProject', + 'createProcedure', + 'createOperation', + 'moveToAnyStep', + 'updateProject', + 'accessAllFlows', + 'editPlanBlueprint', + 'editOrganizations', + 'manageCategories', + 'editAnyProject', + 'deleteUploadedFile', + 'editPlanRevisionState', + 'editAnyParticipantOrganization', + 'editRoleAuthenticationKeys', + 'editAnyParticipantCountry', + 'editFormAssignmentRawData', + 'editFormAssignmentCleanData', + 'editAssignmentRawData', + ], + }, + { + // Usage: 30 + name: 'rpmadmin', + description: 'RPM Administrator', + targetTypes: [], + permittedActions: [ + 'editPlanBlueprint', + 'deleteUploadedFile', + 'accessAllPlans', + 'viewRevisions', + 'updatePermittedActions', + 'editParticipants', + 'deletePlan', + 'createPlan', + 'editPlanRevisionState', + 'createOperation', + 'createDisaggregationModel', + ], + }, + { + // Usage: 217 + name: 'readonly', + description: 'Read Only', + targetTypes: ['plan'], + permittedActions: [], + }, + { + // Usage: 842 + name: 'planlead', + description: 'Plan Lead', + targetTypes: ['plan'], + permittedActions: ['moveToAnyStep'], + }, + { + // Usage: 1996 + // workflowRole: 910 + name: 'clusterlead', + description: 'Cluster Lead', + targetTypes: ['governingEntity'], + permittedActions: [], + }, + { + // Usage: 18 + name: 'ftsadmin', + description: 'FTS Administrator', + targetTypes: [], + permittedActions: [ + 'editOrganizations', + 'manageCategories', + 'accessAllFlows', + 'deleteUploadedFile', + ], + }, + { + // Usage: 5 + name: 'prismadmin', + description: 'PRISM Administrator', + targetTypes: [], + permittedActions: [ + 'editAnyParticipantCountry', + 'moveToAnyStep', + 'editAnyParticipantOrganization', + 'editAnyProject', + 'deleteUploadedFile', + ], + }, + { + // Usage: 1 (participant ID = 202, no object) + // workflowRole: 384 + name: 'countrylead', + description: 'Country Lead', + targetTypes: ['Country'], + permittedActions: [], + }, + { + // Usage: 1 (participant ID = 7859, no object) + // workflowRole: 384 + name: 'orglead', + description: 'Organization Lead', + targetTypes: ['organization'], + permittedActions: [], + }, + { + // Usage: 0 + name: 'rolegranter', + description: 'role Granter', + targetTypes: ['role'], + permittedActions: [], + }, + { + // Usage: 1 (participant ID = 554, no object) + // workflowRole: 572 + name: 'projectowner', + description: 'Project Owner', + targetTypes: [], + permittedActions: [], + }, + { + // Usage: + // roleAuthenticationKey: 1 + name: 'omniscient', + description: 'Omniscient', + targetTypes: [], + permittedActions: ['accessAllFlows', 'accessAllPlans'], + }, + { + // Usage: 0 + name: 'operationLead', + description: 'Operation Lead', + targetTypes: ['operation'], + permittedActions: [], + }, + { + // Usage: 0 + name: 'opEntityCoordinator', + description: 'Operation entity coordinator', + targetTypes: ['operation', 'opGoverningEntity'], + permittedActions: [], + }, +] as const; + +export type RoleName = typeof ROLES[number]['name']; + +export type PermittedActionIdString = + typeof ROLES[number]['permittedActions'][number]; + +export function extractRoles( + arr: T +): { [K in T[number]['permittedActions'][number]]: null } { + const permittedActions = new Set(...arr.map((a) => a.permittedActions)); + + return Object.fromEntries( + Array.from(permittedActions).map((a) => [a, null]) + ) as { [K in T[number]['permittedActions'][number]]: null }; +} diff --git a/src/db/index.ts b/src/db/index.ts index a75af9a8..facb2277 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -1,17 +1,22 @@ import Knex = require('knex'); -import attachmentVersion from './models/attachmentVersion'; import attachment from './models/attachment'; +import attachmentVersion from './models/attachmentVersion'; import authGrant from './models/authGrant'; import authGrantee from './models/authGrantee'; import authGrantLog from './models/authGrantLog'; import authInvite from './models/authInvite'; import authTarget from './models/authTarget'; +import authToken from './models/authToken'; import form from './models/form'; import governingEntity from './models/governingEntity'; import operation from './models/operation'; import operationCluster from './models/operationCluster'; +import participant from './models/participant'; +import participantRole from './models/participantRole'; import reportingWindow from './models/reportingWindow'; import reportingWindowAssignment from './models/reportingWindowAssignment'; +import role from './models/role'; +import rolePermittedAction from './models/rolePermittedAction'; export default (conn: Knex) => ({ attachment: attachment(conn), @@ -21,10 +26,15 @@ export default (conn: Knex) => ({ authGrantLog: authGrantLog(conn), authInvite: authInvite(conn), authTarget: authTarget(conn), + authToken: authToken(conn), form: form(conn), governingEntity: governingEntity(conn), operation: operation(conn), operationCluster: operationCluster(conn), + participant: participant(conn), + participantRole: participantRole(conn), + role: role(conn), + rolePermittedAction: rolePermittedAction(conn), reportingWindow: reportingWindow(conn), reportingWindowAssignment: reportingWindowAssignment(conn), }); diff --git a/src/db/models/authToken.ts b/src/db/models/authToken.ts new file mode 100644 index 00000000..e32f06c3 --- /dev/null +++ b/src/db/models/authToken.ts @@ -0,0 +1,18 @@ +import * as t from 'io-ts'; +import { DATE } from '../util/datatypes'; +import { defineSequelizeModel } from '../util/sequelize-model'; +import { PARTICIPANT_ID } from './participant'; + +export default defineSequelizeModel({ + tableName: 'authToken', + fields: { + required: { + participant: { kind: 'branded-integer', brand: PARTICIPANT_ID }, + tokenHash: { kind: 'checked', type: t.string }, + }, + optional: { + expires: { kind: 'checked', type: DATE }, + }, + }, + softDeletionEnabled: false, +}); diff --git a/src/db/models/participant.ts b/src/db/models/participant.ts index 38a5414c..92d07192 100644 --- a/src/db/models/participant.ts +++ b/src/db/models/participant.ts @@ -2,6 +2,7 @@ import * as t from 'io-ts'; import { brandedType } from '../../util/io-ts'; import { Brand } from '../../util/types'; +import { defineIDModel } from '../util/id-model'; export type ParticipantId = Brand< number, @@ -10,3 +11,21 @@ export type ParticipantId = Brand< >; export const PARTICIPANT_ID = brandedType(t.number); + +export default defineIDModel({ + tableName: 'participant', + fields: { + generated: { + id: { kind: 'branded-integer', brand: PARTICIPANT_ID }, + }, + optional: { + hidId: { kind: 'checked', type: t.string }, + email: { kind: 'checked', type: t.string }, + name_given: { kind: 'checked', type: t.string }, + name_family: { kind: 'checked', type: t.string }, + }, + required: {}, + }, + idField: 'id', + softDeletionEnabled: false, +}); diff --git a/src/db/models/participantRole.ts b/src/db/models/participantRole.ts new file mode 100644 index 00000000..3c5b2167 --- /dev/null +++ b/src/db/models/participantRole.ts @@ -0,0 +1,41 @@ +import * as t from 'io-ts'; + +import { brandedType } from '../../util/io-ts'; +import { Brand } from '../../util/types'; +import { defineIDModel } from '../util/id-model'; +import { PARTICIPANT_ID } from './participant'; +import { ROLE_ID } from './role'; + +export type ParticipantRoleId = Brand< + number, + { readonly s: unique symbol }, + 'participantRole.id' +>; + +export const PARTICIPANT_ROLE_ID = brandedType( + t.number +); + +const PARTICIPANT_ROLE_OBJECT_TYPE = { + governingEntity: null, + plan: null, +}; + +export default defineIDModel({ + tableName: 'participantRole', + fields: { + generated: { + id: { kind: 'branded-integer', brand: PARTICIPANT_ROLE_ID }, + }, + accidentallyOptional: { + roleId: { kind: 'branded-integer', brand: ROLE_ID }, + participantId: { kind: 'branded-integer', brand: PARTICIPANT_ID }, + }, + optional: { + objectId: { kind: 'checked', type: t.number }, + objectType: { kind: 'enum', values: PARTICIPANT_ROLE_OBJECT_TYPE }, + }, + }, + idField: 'id', + softDeletionEnabled: false, +}); diff --git a/src/db/models/permittedAction.ts b/src/db/models/permittedAction.ts new file mode 100644 index 00000000..1ef8aac5 --- /dev/null +++ b/src/db/models/permittedAction.ts @@ -0,0 +1,4 @@ +import * as t from 'io-ts'; +import { extractRoles, ROLES } from '../../auth/role.data'; + +export const PERMITTED_ACTION_ID = t.keyof(extractRoles(ROLES)); diff --git a/src/db/models/role.ts b/src/db/models/role.ts new file mode 100644 index 00000000..155d8a39 --- /dev/null +++ b/src/db/models/role.ts @@ -0,0 +1,43 @@ +import * as t from 'io-ts'; + +import { brandedType } from '../../util/io-ts'; +import { Brand } from '../../util/types'; +import { defineIDModel } from '../util/id-model'; + +export type RoleId = Brand; + +export const ROLE_ID = brandedType(t.number); + +const ROLE_NAME = { + clusterlead: null, + countrylead: null, + ftsadmin: null, + hpcadmin: null, + omniscient: null, + opEntityCoordinator: null, + operationLead: null, + orglead: null, + planlead: null, + prismadmin: null, + projectowner: null, + readonly: null, + rolegranter: null, + rpmadmin: null, +}; + +export default defineIDModel({ + tableName: 'role', + fields: { + generated: { + id: { kind: 'branded-integer', brand: ROLE_ID }, + }, + accidentallyOptional: { + name: { kind: 'enum', values: ROLE_NAME }, + description: { kind: 'checked', type: t.string }, + targetTypes: { kind: 'checked', type: t.array(t.string) }, + }, + optional: {}, + }, + idField: 'id', + softDeletionEnabled: false, +}); diff --git a/src/db/models/rolePermittedAction.ts b/src/db/models/rolePermittedAction.ts new file mode 100644 index 00000000..43112fd7 --- /dev/null +++ b/src/db/models/rolePermittedAction.ts @@ -0,0 +1,37 @@ +import * as t from 'io-ts'; + +import { brandedType } from '../../util/io-ts'; +import { Brand } from '../../util/types'; +import { defineIDModel } from '../util/id-model'; +import { PERMITTED_ACTION_ID } from './permittedAction'; +import { ROLE_ID } from './role'; + +export type RolePermittedActionId = Brand< + number, + { readonly s: unique symbol }, + 'rolePermittedAction.id' +>; + +export const ROLE_PERMITTED_ACTION_ID = brandedType< + number, + RolePermittedActionId +>(t.number); + +export default defineIDModel({ + tableName: 'rolePermittedAction', + fields: { + generated: { + id: { kind: 'branded-integer', brand: ROLE_PERMITTED_ACTION_ID }, + }, + optional: { + roleId: { kind: 'branded-integer', brand: ROLE_ID }, + permittedActionId: { + kind: 'checked', + type: PERMITTED_ACTION_ID, + }, + }, + required: {}, + }, + idField: 'id', + softDeletionEnabled: false, +});