separate `canUpdateWorkOrders` permission

pull/11/head
Dan Gowans 2025-04-24 09:36:44 -04:00
parent a167ef4a16
commit 38c1288856
27 changed files with 161 additions and 48 deletions

View File

@ -19,6 +19,7 @@ export declare const configDefaultValues: {
'session.secret': string;
'users.canLogin': string[];
'users.canUpdate': string[];
'users.canUpdateWorkOrders': string[];
'users.isAdmin': string[];
'users.testing': string[];
'aliases.externalReceiptNumber': string;

View File

@ -18,6 +18,7 @@ export const configDefaultValues = {
'session.secret': 'cityssm/sunrise',
'users.canLogin': ['administrator'],
'users.canUpdate': [],
'users.canUpdateWorkOrders': [],
'users.isAdmin': ['administrator'],
'users.testing': [],
'aliases.externalReceiptNumber': 'External Receipt Number',

View File

@ -32,6 +32,7 @@ export const configDefaultValues = {
'users.canLogin': ['administrator'],
'users.canUpdate': [] as string[],
'users.canUpdateWorkOrders': [] as string[],
'users.isAdmin': ['administrator'],
'users.testing': [] as string[],

View File

@ -383,6 +383,7 @@ const initializingUser = {
userProperties: {
apiKey: '',
canUpdate: true,
canUpdateWorkOrders: true,
isAdmin: true
}
};

View File

@ -440,6 +440,7 @@ const initializingUser: User = {
userProperties: {
apiKey: '',
canUpdate: true,
canUpdateWorkOrders: true,
isAdmin: true
}
}

View File

@ -4,3 +4,5 @@ export declare function adminPostHandler(request: Request, response: Response, n
export declare function apiGetHandler(request: Request, response: Response, next: NextFunction): Promise<void>;
export declare function updateGetHandler(request: Request, response: Response, next: NextFunction): void;
export declare function updatePostHandler(request: Request, response: Response, next: NextFunction): void;
export declare function updateWorkOrdersGetHandler(request: Request, response: Response, next: NextFunction): void;
export declare function updateWorkOrdersPostHandler(request: Request, response: Response, next: NextFunction): void;

View File

@ -1,5 +1,5 @@
import { getConfigProperty } from '../helpers/config.helpers.js';
import { apiKeyIsValid, userCanUpdate, userIsAdmin } from '../helpers/functions.user.js';
import { apiKeyIsValid, userCanUpdate, userCanUpdateWorkOrders, userIsAdmin } from '../helpers/functions.user.js';
const urlPrefix = getConfigProperty('reverseProxy.urlPrefix');
const forbiddenStatus = 403;
const forbiddenJSON = {
@ -43,3 +43,17 @@ export function updatePostHandler(request, response, next) {
}
response.status(forbiddenStatus).json(forbiddenJSON);
}
export function updateWorkOrdersGetHandler(request, response, next) {
if (userCanUpdateWorkOrders(request)) {
next();
return;
}
response.redirect(forbiddenRedirectURL);
}
export function updateWorkOrdersPostHandler(request, response, next) {
if (userCanUpdateWorkOrders(request)) {
next();
return;
}
response.status(forbiddenStatus).json(forbiddenJSON);
}

View File

@ -4,6 +4,7 @@ import { getConfigProperty } from '../helpers/config.helpers.js'
import {
apiKeyIsValid,
userCanUpdate,
userCanUpdateWorkOrders,
userIsAdmin
} from '../helpers/functions.user.js'
@ -81,3 +82,29 @@ export function updatePostHandler(
response.status(forbiddenStatus).json(forbiddenJSON)
}
export function updateWorkOrdersGetHandler(
request: Request,
response: Response,
next: NextFunction
): void {
if (userCanUpdateWorkOrders(request)) {
next()
return
}
response.redirect(forbiddenRedirectURL)
}
export function updateWorkOrdersPostHandler(
request: Request,
response: Response,
next: NextFunction
): void {
if (userCanUpdateWorkOrders(request)) {
next()
return
}
response.status(forbiddenStatus).json(forbiddenJSON)
}

View File

@ -10,4 +10,5 @@ export interface UserRequest {
}
export declare function apiKeyIsValid(request: APIRequest): Promise<boolean>;
export declare function userCanUpdate(request: UserRequest): boolean;
export declare function userCanUpdateWorkOrders(request: UserRequest): boolean;
export declare function userIsAdmin(request: UserRequest): boolean;

View File

@ -14,6 +14,9 @@ export async function apiKeyIsValid(request) {
export function userCanUpdate(request) {
return request.session?.user?.userProperties?.canUpdate ?? false;
}
export function userCanUpdateWorkOrders(request) {
return request.session?.user?.userProperties?.canUpdateWorkOrders ?? false;
}
export function userIsAdmin(request) {
return request.session?.user?.userProperties?.isAdmin ?? false;
}

View File

@ -35,6 +35,10 @@ export function userCanUpdate(request: UserRequest): boolean {
return request.session?.user?.userProperties?.canUpdate ?? false
}
export function userCanUpdateWorkOrders(request: UserRequest): boolean {
return request.session?.user?.userProperties?.canUpdateWorkOrders ?? false
}
export function userIsAdmin(request: UserRequest): boolean {
return request.session?.user?.userProperties?.isAdmin ?? false
}

View File

@ -46,12 +46,14 @@ async function postHandler(request, response) {
const canLogin = getConfigProperty('users.canLogin').some((currentUserName) => userNameLowerCase === currentUserName.toLowerCase());
if (canLogin) {
const canUpdate = getConfigProperty('users.canUpdate').some((currentUserName) => userNameLowerCase === currentUserName.toLowerCase());
const canUpdateWorkOrders = getConfigProperty('users.canUpdateWorkOrders').some((currentUserName) => userNameLowerCase === currentUserName.toLowerCase());
const isAdmin = getConfigProperty('users.isAdmin').some((currentUserName) => userNameLowerCase === currentUserName.toLowerCase());
const apiKey = await getApiKey(userNameLowerCase);
userObject = {
userName: userNameLowerCase,
userProperties: {
canUpdate,
canUpdateWorkOrders,
isAdmin,
apiKey
}

View File

@ -86,6 +86,10 @@ async function postHandler(
(currentUserName) => userNameLowerCase === currentUserName.toLowerCase()
)
const canUpdateWorkOrders = getConfigProperty('users.canUpdateWorkOrders').some(
(currentUserName) => userNameLowerCase === currentUserName.toLowerCase()
)
const isAdmin = getConfigProperty('users.isAdmin').some(
(currentUserName) => userNameLowerCase === currentUserName.toLowerCase()
)
@ -96,7 +100,9 @@ async function postHandler(
userName: userNameLowerCase,
userProperties: {
canUpdate,
canUpdateWorkOrders,
isAdmin,
apiKey
}
}

View File

@ -1,5 +1,5 @@
import { Router } from 'express';
import { updateGetHandler, updatePostHandler } from '../handlers/permissions.js';
import { updateWorkOrdersGetHandler, updateWorkOrdersPostHandler } from '../handlers/permissions.js';
import handler_edit from '../handlers/workOrders-get/edit.js';
import handler_milestoneCalendar from '../handlers/workOrders-get/milestoneCalendar.js';
import handler_new from '../handlers/workOrders-get/new.js';
@ -36,30 +36,30 @@ router.post('/doGetWorkOrderMilestones', handler_doGetWorkOrderMilestones);
// Outlook Integration
router.get('/outlook', handler_outlook);
// New
router.get('/new', updateGetHandler, handler_new);
router.post('/doCreateWorkOrder', updatePostHandler, handler_doCreateWorkOrder);
router.get('/new', updateWorkOrdersGetHandler, handler_new);
router.post('/doCreateWorkOrder', updateWorkOrdersPostHandler, handler_doCreateWorkOrder);
// View
router.get('/:workOrderId', handler_view);
router.post('/doReopenWorkOrder', updatePostHandler, handler_doReopenWorkOrder);
router.post('/doReopenWorkOrder', updateWorkOrdersPostHandler, handler_doReopenWorkOrder);
// Edit
router.get('/:workOrderId/edit', updateGetHandler, handler_edit);
router.post('/doUpdateWorkOrder', updatePostHandler, handler_doUpdateWorkOrder);
router.post('/doCloseWorkOrder', updatePostHandler, handler_doCloseWorkOrder);
router.post('/doDeleteWorkOrder', updatePostHandler, handler_doDeleteWorkOrder);
router.get('/:workOrderId/edit', updateWorkOrdersGetHandler, handler_edit);
router.post('/doUpdateWorkOrder', updateWorkOrdersPostHandler, handler_doUpdateWorkOrder);
router.post('/doCloseWorkOrder', updateWorkOrdersPostHandler, handler_doCloseWorkOrder);
router.post('/doDeleteWorkOrder', updateWorkOrdersPostHandler, handler_doDeleteWorkOrder);
// Burial Site Contract
router.post('/doAddWorkOrderContract', updatePostHandler, handler_doAddWorkOrderContract);
router.post('/doDeleteWorkOrderContract', updatePostHandler, handler_doDeleteWorkOrderContract);
router.post('/doAddWorkOrderBurialSite', updatePostHandler, handler_doAddWorkOrderBurialSite);
router.post('/doUpdateBurialSiteStatus', updatePostHandler, handler_doUpdateBurialSiteStatus);
router.post('/doDeleteWorkOrderBurialSite', updatePostHandler, handler_doDeleteWorkOrderBurialSite);
router.post('/doAddWorkOrderContract', updateWorkOrdersPostHandler, handler_doAddWorkOrderContract);
router.post('/doDeleteWorkOrderContract', updateWorkOrdersPostHandler, handler_doDeleteWorkOrderContract);
router.post('/doAddWorkOrderBurialSite', updateWorkOrdersPostHandler, handler_doAddWorkOrderBurialSite);
router.post('/doUpdateBurialSiteStatus', updateWorkOrdersPostHandler, handler_doUpdateBurialSiteStatus);
router.post('/doDeleteWorkOrderBurialSite', updateWorkOrdersPostHandler, handler_doDeleteWorkOrderBurialSite);
// Comments
router.post('/doAddWorkOrderComment', updatePostHandler, handler_doAddWorkOrderComment);
router.post('/doUpdateWorkOrderComment', updatePostHandler, handler_doUpdateWorkOrderComment);
router.post('/doDeleteWorkOrderComment', updatePostHandler, handler_doDeleteWorkOrderComment);
router.post('/doAddWorkOrderComment', updateWorkOrdersPostHandler, handler_doAddWorkOrderComment);
router.post('/doUpdateWorkOrderComment', updateWorkOrdersPostHandler, handler_doUpdateWorkOrderComment);
router.post('/doDeleteWorkOrderComment', updateWorkOrdersPostHandler, handler_doDeleteWorkOrderComment);
// Milestones
router.post('/doAddWorkOrderMilestone', updatePostHandler, handler_doAddWorkOrderMilestone);
router.post('/doUpdateWorkOrderMilestone', updatePostHandler, handler_doUpdateWorkOrderMilestone);
router.post('/doCompleteWorkOrderMilestone', updatePostHandler, handler_doCompleteWorkOrderMilestone);
router.post('/doReopenWorkOrderMilestone', updatePostHandler, handler_doReopenWorkOrderMilestone);
router.post('/doDeleteWorkOrderMilestone', updatePostHandler, handler_doDeleteWorkOrderMilestone);
router.post('/doAddWorkOrderMilestone', updateWorkOrdersPostHandler, handler_doAddWorkOrderMilestone);
router.post('/doUpdateWorkOrderMilestone', updateWorkOrdersPostHandler, handler_doUpdateWorkOrderMilestone);
router.post('/doCompleteWorkOrderMilestone', updateWorkOrdersPostHandler, handler_doCompleteWorkOrderMilestone);
router.post('/doReopenWorkOrderMilestone', updateWorkOrdersPostHandler, handler_doReopenWorkOrderMilestone);
router.post('/doDeleteWorkOrderMilestone', updateWorkOrdersPostHandler, handler_doDeleteWorkOrderMilestone);
export default router;

View File

@ -1,6 +1,9 @@
import { Router } from 'express'
import { updateGetHandler, updatePostHandler } from '../handlers/permissions.js'
import {
updateWorkOrdersGetHandler,
updateWorkOrdersPostHandler
} from '../handlers/permissions.js'
import handler_edit from '../handlers/workOrders-get/edit.js'
import handler_milestoneCalendar from '../handlers/workOrders-get/milestoneCalendar.js'
import handler_new from '../handlers/workOrders-get/new.js'
@ -48,47 +51,75 @@ router.get('/outlook', handler_outlook)
// New
router.get('/new', updateGetHandler, handler_new)
router.get('/new', updateWorkOrdersGetHandler, handler_new)
router.post('/doCreateWorkOrder', updatePostHandler, handler_doCreateWorkOrder)
router.post(
'/doCreateWorkOrder',
updateWorkOrdersPostHandler,
handler_doCreateWorkOrder
)
// View
router.get('/:workOrderId', handler_view)
router.post('/doReopenWorkOrder', updatePostHandler, handler_doReopenWorkOrder)
router.post(
'/doReopenWorkOrder',
updateWorkOrdersPostHandler,
handler_doReopenWorkOrder
)
// Edit
router.get('/:workOrderId/edit', updateGetHandler, handler_edit)
router.get('/:workOrderId/edit', updateWorkOrdersGetHandler, handler_edit)
router.post('/doUpdateWorkOrder', updatePostHandler, handler_doUpdateWorkOrder)
router.post(
'/doUpdateWorkOrder',
updateWorkOrdersPostHandler,
handler_doUpdateWorkOrder
)
router.post('/doCloseWorkOrder', updatePostHandler, handler_doCloseWorkOrder)
router.post(
'/doCloseWorkOrder',
updateWorkOrdersPostHandler,
handler_doCloseWorkOrder
)
router.post('/doDeleteWorkOrder', updatePostHandler, handler_doDeleteWorkOrder)
router.post(
'/doDeleteWorkOrder',
updateWorkOrdersPostHandler,
handler_doDeleteWorkOrder
)
// Burial Site Contract
router.post(
'/doAddWorkOrderContract',
updatePostHandler,
updateWorkOrdersPostHandler,
handler_doAddWorkOrderContract
)
router.post(
'/doDeleteWorkOrderContract',
updatePostHandler,
updateWorkOrdersPostHandler,
handler_doDeleteWorkOrderContract
)
router.post('/doAddWorkOrderBurialSite', updatePostHandler, handler_doAddWorkOrderBurialSite)
router.post(
'/doAddWorkOrderBurialSite',
updateWorkOrdersPostHandler,
handler_doAddWorkOrderBurialSite
)
router.post('/doUpdateBurialSiteStatus', updatePostHandler, handler_doUpdateBurialSiteStatus)
router.post(
'/doUpdateBurialSiteStatus',
updateWorkOrdersPostHandler,
handler_doUpdateBurialSiteStatus
)
router.post(
'/doDeleteWorkOrderBurialSite',
updatePostHandler,
updateWorkOrdersPostHandler,
handler_doDeleteWorkOrderBurialSite
)
@ -96,19 +127,19 @@ router.post(
router.post(
'/doAddWorkOrderComment',
updatePostHandler,
updateWorkOrdersPostHandler,
handler_doAddWorkOrderComment
)
router.post(
'/doUpdateWorkOrderComment',
updatePostHandler,
updateWorkOrdersPostHandler,
handler_doUpdateWorkOrderComment
)
router.post(
'/doDeleteWorkOrderComment',
updatePostHandler,
updateWorkOrdersPostHandler,
handler_doDeleteWorkOrderComment
)
@ -116,31 +147,31 @@ router.post(
router.post(
'/doAddWorkOrderMilestone',
updatePostHandler,
updateWorkOrdersPostHandler,
handler_doAddWorkOrderMilestone
)
router.post(
'/doUpdateWorkOrderMilestone',
updatePostHandler,
updateWorkOrdersPostHandler,
handler_doUpdateWorkOrderMilestone
)
router.post(
'/doCompleteWorkOrderMilestone',
updatePostHandler,
updateWorkOrdersPostHandler,
handler_doCompleteWorkOrderMilestone
)
router.post(
'/doReopenWorkOrderMilestone',
updatePostHandler,
updateWorkOrdersPostHandler,
handler_doReopenWorkOrderMilestone
)
router.post(
'/doDeleteWorkOrderMilestone',
updatePostHandler,
updateWorkOrdersPostHandler,
handler_doDeleteWorkOrderMilestone
)

View File

@ -33,6 +33,7 @@ const user = {
userName: 'import.unix',
userProperties: {
canUpdate: true,
canUpdateWorkOrders: true,
isAdmin: false,
apiKey: ''
}

View File

@ -56,6 +56,7 @@ const user: User = {
userName: 'import.unix',
userProperties: {
canUpdate: true,
canUpdateWorkOrders: true,
isAdmin: false,
apiKey: ''

View File

@ -204,6 +204,7 @@ await describe('functions.user', async () => {
userName: '*test',
userProperties: {
canUpdate: false,
canUpdateWorkOrders: false,
isAdmin: false,
apiKey: ''
}
@ -224,6 +225,7 @@ await describe('functions.user', async () => {
userName: '*test',
userProperties: {
canUpdate: true,
canUpdateWorkOrders: true,
isAdmin: false,
apiKey: ''
}
@ -244,6 +246,7 @@ await describe('functions.user', async () => {
userName: '*test',
userProperties: {
canUpdate: false,
canUpdateWorkOrders: false,
isAdmin: true,
apiKey: ''
}
@ -264,6 +267,7 @@ await describe('functions.user', async () => {
userName: '*test',
userProperties: {
canUpdate: true,
canUpdateWorkOrders: true,
isAdmin: true,
apiKey: ''
}

View File

@ -361,7 +361,9 @@ await describe('functions.user', async () => {
userName: '*test',
userProperties: {
canUpdate: false,
canUpdateWorkOrders: false,
isAdmin: false,
apiKey: ''
}
}
@ -384,7 +386,9 @@ await describe('functions.user', async () => {
userName: '*test',
userProperties: {
canUpdate: true,
canUpdateWorkOrders: true,
isAdmin: false,
apiKey: ''
}
}
@ -407,7 +411,9 @@ await describe('functions.user', async () => {
userName: '*test',
userProperties: {
canUpdate: false,
canUpdateWorkOrders: false,
isAdmin: true,
apiKey: ''
}
}
@ -430,7 +436,9 @@ await describe('functions.user', async () => {
userName: '*test',
userProperties: {
canUpdate: true,
canUpdateWorkOrders: true,
isAdmin: true,
apiKey: ''
}
}

View File

@ -12,6 +12,7 @@ export interface Config {
testing?: Array<`*${string}`>;
canLogin?: string[];
canUpdate?: string[];
canUpdateWorkOrders?: string[];
isAdmin?: string[];
};
aliases: {

View File

@ -18,6 +18,7 @@ export interface Config {
canLogin?: string[]
canUpdate?: string[]
canUpdateWorkOrders?: string[]
isAdmin?: string[]
}

View File

@ -7,6 +7,7 @@ declare global {
export interface UserProperties {
apiKey: string;
canUpdate: boolean;
canUpdateWorkOrders: boolean;
isAdmin: boolean;
}
declare module 'express-session' {

View File

@ -8,6 +8,7 @@ declare global {
export interface UserProperties {
apiKey: string
canUpdate: boolean
canUpdateWorkOrders: boolean
isAdmin: boolean
}

View File

@ -111,7 +111,7 @@
</div>
</a>
<div class="panel-block is-block">
<% if (user.userProperties.canUpdate) { %>
<% if (user.userProperties.canUpdateWorkOrders) { %>
<a class="button is-fullwidth is-success is-light mb-2" href="<%= urlPrefix %>/workOrders/new">
<span class="icon">
<i class="fas fa-plus" aria-hidden="true"></i>

View File

@ -21,7 +21,7 @@
Work Order Search
</h1>
<% if (user.userProperties.canUpdate) { %>
<% if (user.userProperties.canUpdateWorkOrders) { %>
<div class="fixed-container is-fixed-bottom-right mx-4 my-4 has-text-right is-hidden-print">
<a class="button is-circle is-primary has-tooltip-left"
data-tooltip="Create a New Work Order"

View File

@ -80,7 +80,7 @@
</div>
<% } %>
<% } %>
<% if (user.userProperties.canUpdate) { %>
<% if (user.userProperties.canUpdateWorkOrders) { %>
<% if (workOrder.workOrderCloseDate) { %>
<button class="button is-warning" id="button--reopenWorkOrder" data-work-order-id="<%= workOrder.workOrderId %>" type="button">
<span class="icon"><i class="fas fa-undo" aria-hidden="true"></i></span>