Skip to content

Commit

Permalink
Refactor auth roles (#195)
Browse files Browse the repository at this point in the history
- Deprecate director and team captain roles
- Add faculty advisor approval
- Remove unused currentUserGroup from FE auth context
- Fix emails to handle the case where ticket generated from seed data
has an invalid reporter_id.
  • Loading branch information
ansonjwhe authored Apr 15, 2024
1 parent 2ebcaaf commit 4913fd3
Show file tree
Hide file tree
Showing 24 changed files with 117 additions and 200 deletions.
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,6 @@ REACT_APP_WATO_FINANCE_BACKEND_URL=http://localhost:5000
4. Set Project support email to your own email
5. Select Save and ensure that the Google Provider has the Enabled Status

### Changing authorization for debugging

1. If you would like to change your authorization for debugging, set `REACT_APP_AUTH_OVERRIDE=ADMIN | TEAM_CAPTAIN | DIRECTOR` in the frontend env, and `AUTH_OVERRIDE=ADMIN | TEAM_CAPTAIN | DIRECTOR` in the backend env.

### Starting the app

To start the frontend, in a different terminal run
Expand Down
13 changes: 5 additions & 8 deletions backend/auth/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,12 @@ const authenticateUser = async (req, res, next) => {
next()
return
}
const { isDirector, isAdmin, isTeamCaptain, isReporter } =
await getAuthRoles(
decodedToken.uid,
decodedToken.email,
reporter_id
)
req.user.isDirector = isDirector
const { isAdmin, isReporter } = await getAuthRoles(
decodedToken.uid,
decodedToken.email,
reporter_id
)
req.user.isAdmin = isAdmin
req.user.isTeamCaptain = isTeamCaptain
req.user.isReporter = isReporter
next()
} catch (err) {
Expand Down
12 changes: 6 additions & 6 deletions backend/controller/fundingitems.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ const createFundingItemController = (req, res) => {
}

const updateFundingItemController = (req, res) => {
if (!req.user.isDirector && !req.user.isReporter) {
res.status(403).json('Error: Must be Director+ or reporter to update')
if (!req.user.isAdmin && !req.user.isReporter) {
res.status(403).json('Error: Must be admin or reporter to update')
return
}

Expand All @@ -49,8 +49,8 @@ const updateFundingItemController = (req, res) => {
}

const updateSFLinkFundingItemController = async (req, res) => {
if (!req.user.isDirector && !req.user.isReporter) {
res.status(403).json('Error: Must be Director+ or reporter to update')
if (!req.user.isAdmin && !req.user.isReporter) {
res.status(403).json('Error: Must be admin or reporter to update')
return
}

Expand All @@ -68,8 +68,8 @@ const updateSFLinkFundingItemController = async (req, res) => {
}

const deleteFundingItemController = (req, res) => {
if (!req.user.isDirector && !req.user.isReporter) {
res.status(403).json('Error: Must be Director+ or reporter to delete')
if (!req.user.isAdmin && !req.user.isReporter) {
res.status(403).json('Error: Must be admin or reporter to delete')
return
}

Expand Down
27 changes: 3 additions & 24 deletions backend/controller/getAuthRoles.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
const { getGoogleGroup } = require('../service/googlegroup.service')
const {
ADMIN_IDENTIFIERS,
TEAM_CAPTAIN_TITLES,
DIRECTOR_TITLES,
} = require('../models/constants')
const { AUTH_ROLES } = require('../models/constants')

const getWATIAMFromEmail = async (email) => {
return email.substring(0, email.indexOf('@'))
Expand All @@ -12,28 +8,11 @@ const getWATIAMFromEmail = async (email) => {
const getAuthRoles = async (user_uid, email, reporter_id) => {
const watiam = await getWATIAMFromEmail(email)
const currentGoogleGroup = await getGoogleGroup(watiam)

let isAdmin, isTeamCaptain, isDirector, isReporter

if (process.env?.AUTH_OVERRIDE) {
const authRoles = process.env?.AUTH_OVERRIDE?.split(',')
isAdmin = authRoles.includes('ADMIN')
isTeamCaptain = authRoles.includes('TEAM_CAPTAIN') || isAdmin
isDirector = authRoles.includes('DIRECTOR') || isTeamCaptain
isReporter = authRoles.includes('REPORTER')
} else {
isAdmin = ADMIN_IDENTIFIERS.includes(watiam)
isTeamCaptain =
isAdmin || TEAM_CAPTAIN_TITLES.includes(currentGoogleGroup.title)
isDirector =
isTeamCaptain || DIRECTOR_TITLES.includes(currentGoogleGroup.title)
isReporter = reporter_id === user_uid
}
const isAdmin = currentGoogleGroup.title === AUTH_ROLES.Administrator
const isReporter = reporter_id === user_uid

return {
isAdmin,
isTeamCaptain,
isDirector,
isReporter,
}
}
Expand Down
23 changes: 8 additions & 15 deletions backend/controller/personalpurchases.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ const createPersonalPurchaseController = (req, res) => {
}

const updatePersonalPurchaseController = (req, res) => {
if (!req.user.isDirector && !req.user.isReporter) {
res.status(403).json('Error: Must be Director+ or reporter to update')
if (!req.user.isAdmin && !req.user.isReporter) {
res.status(403).json('Error: Must be admin or reporter to update')
return
}

Expand All @@ -48,8 +48,8 @@ const updatePersonalPurchaseController = (req, res) => {
}

const updateFILinkPersonalPurchaseController = async (req, res) => {
if (!req.user.isDirector && !req.user.isReporter) {
res.status(403).json('Error: Must be Director+ or reporter to update')
if (!req.user.isAdmin && !req.user.isReporter) {
res.status(403).json('Error: Must be admin or reporter to update')
return
}
const { fi_link } = req.params
Expand All @@ -66,16 +66,9 @@ const updateFILinkPersonalPurchaseController = async (req, res) => {
}

const updateApprovalsPersonalPurchaseController = async (req, res) => {
const { new_approval_levels, approval_type } = req.body
const canUpdateApproval =
(approval_type === APPROVAL_LEVELS.admin_approval &&
req.user.isAdmin) ||
(approval_type === APPROVAL_LEVELS.team_captain_approval &&
req.user.isTeamCaptain) ||
(approval_type === APPROVAL_LEVELS.director_approval &&
req.user.isDirector)
const { new_approval_levels } = req.body

if (!canUpdateApproval) {
if (!req.user.isAdmin) {
res.status(403).json('Error: Permission Denied')
return
}
Expand All @@ -88,8 +81,8 @@ const updateApprovalsPersonalPurchaseController = async (req, res) => {
}

const deletePersonalPurchaseController = (req, res) => {
if (!req.user.isDirector && !req.user.isReporter) {
res.status(403).json('Error: Must be Director+ or reporter to delete')
if (!req.user.isAdmin && !req.user.isReporter) {
res.status(403).json('Error: Must be admin or reporter to delete')
return
}
deletePersonalPurchase(req.params.id)
Expand Down
8 changes: 4 additions & 4 deletions backend/controller/sponsorshipfunds.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ const createSponsorshipFundController = (req, res) => {
}

const updateSponsorshipFundController = (req, res) => {
if (!req.user.isDirector && !req.user.isReporter) {
res.status(403).json('Error: Must be Director+ or reporter to update')
if (!req.user.isAdmin && !req.user.isReporter) {
res.status(403).json('Error: Must be admin or reporter to update')
return
}

Expand All @@ -55,8 +55,8 @@ const updateSponsorshipFundController = (req, res) => {
}

const deleteSponsorshipFundController = (req, res) => {
if (!req.user.isDirector && !req.user.isReporter) {
res.status(403).json('Error: Must be Director+ or reporter to delete')
if (!req.user.isAdmin && !req.user.isReporter) {
res.status(403).json('Error: Must be admin or reporter to delete')
return
}

Expand Down
25 changes: 9 additions & 16 deletions backend/controller/uwfinancepurchases.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ const createNewUWFinancePurchaseController = (req, res) => {
}

const updateUWFinancePurchaseController = (req, res) => {
if (!req.user.isDirector && !req.user.isReporter) {
res.status(403).json('Error: Must be Director+ or reporter to update')
if (!req.user.isAdmin && !req.user.isReporter) {
res.status(403).json('Error: Must be admin or reporter to update')
return
}

Expand All @@ -50,8 +50,8 @@ const updateUWFinancePurchaseController = (req, res) => {
}

const updateFILinkUWFinancePurchaseController = async (req, res) => {
if (!req.user.isDirector && !req.user.isReporter) {
res.status(403).json('Error: Must be Director+ or reporter to update')
if (!req.user.isAdmin && !req.user.isReporter) {
res.status(403).json('Error: Must be admin or reporter to update')
return
}

Expand All @@ -69,16 +69,9 @@ const updateFILinkUWFinancePurchaseController = async (req, res) => {
}

const updateApprovalsUWFinancePurchaseController = async (req, res) => {
const { new_approval_levels, approval_type } = req.body
const canUpdateApproval =
(approval_type === APPROVAL_LEVELS.admin_approval &&
req.user.isAdmin) ||
(approval_type === APPROVAL_LEVELS.team_captain_approval &&
req.user.isTeamCaptain) ||
(approval_type === APPROVAL_LEVELS.director_approval &&
req.user.isDirector)

if (!canUpdateApproval) {
const { new_approval_levels } = req.body

if (!req.user.isAdmin) {
res.status(403).json('Error: Permission Denied')
return
}
Expand All @@ -93,8 +86,8 @@ const updateApprovalsUWFinancePurchaseController = async (req, res) => {
}

const deleteUWFinancePurchaseController = (req, res) => {
if (!req.user.isDirector && !req.user.isReporter) {
res.status(403).json('Error: Must be Director+ or reporter to delete')
if (!req.user.isAdmin && !req.user.isReporter) {
res.status(403).json('Error: Must be admin or reporter to delete')
return
}
deleteUWFinancePurchase(req.params.id)
Expand Down
13 changes: 9 additions & 4 deletions backend/emails/emails.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ const getEmailToSection = async (reporter_id, recipients) => {

if (recipients.includes(EMAIL_RECIPIENTS.reporter)) {
const reporter = await getUserByUID(reporter_id)
emailToSet.add(reporter.email)
if (reporter.email) {
emailToSet.add(reporter.email)
}
}

if (recipients.includes(EMAIL_RECIPIENTS.team_captain)) {
Expand All @@ -61,6 +63,7 @@ const getMainMessageHTML = (msg) => `<p>${msg}</p>`

const getPPRTicketInfoHTML = async (ppr) => {
const reporter = await getUserByUID(ppr.reporter_id)
const reporterEmail = reporter.email ? `&lt;${reporter.email}&gt;` : ''
return `
<p>
Ticket Code: ${ppr.code} <br />
Expand All @@ -69,14 +72,15 @@ const getPPRTicketInfoHTML = async (ppr) => {
Purchase URL: ${ppr.purchase_url} <br />
Purchase Justification: ${ppr.purchase_justification} <br />
Status: ${ppr.status} <br />
Reporter: ${reporter.displayName} &lt;${reporter.email}&gt; <br />
Reporter: ${reporter.displayName} ${reporterEmail} <br />
Created: ${new Date(ppr.createdAt).toDateString()}
</p>
`
}

const getUPRTicketInfoHTML = async (upr) => {
const reporter = await getUserByUID(upr.reporter_id)
const reporterEmail = reporter.email ? `&lt;${reporter.email}&gt;` : ''
return `
<p>
Ticket Code: ${upr.code} <br />
Expand All @@ -86,14 +90,15 @@ const getUPRTicketInfoHTML = async (upr) => {
Purchase Instructions: ${upr.purchase_instructions} <br />
Purchase Justification: ${upr.purchase_justification} <br />
Status: ${upr.status} <br />
Reporter: ${reporter.displayName} &lt;${reporter.email}&gt; <br />
Reporter: ${reporter.displayName} ${reporterEmail} <br />
Created: ${new Date(upr.createdAt).toDateString()}
</p>
`
}

const getSFTicketInfoHTML = async (sf) => {
const reporter = await getUserByUID(sf.reporter_id)
const reporterEmail = reporter.email ? `&lt;${reporter.email}&gt;` : ''
return `
<p>
Ticket Code: ${sf.code} <br />
Expand All @@ -111,7 +116,7 @@ const getSFTicketInfoHTML = async (sf) => {
: ``
}
Status: ${sf.status} <br />
Reporter: ${reporter.displayName} &lt;${reporter.email}&gt; <br />
Reporter: ${reporter.displayName} ${reporterEmail} <br />
Created: ${new Date(sf.createdAt).toDateString()} <br />
Claim Deadline: ${new Date(sf.claim_deadline).toDateString()} <br />
</p>
Expand Down
15 changes: 9 additions & 6 deletions backend/models/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,15 @@ const UPR_STATUS = [
const UPR_STATUS_FUNDING_SPENT = ['ORDERED', 'READY_FOR_PICKUP', 'PICKED_UP']

const APPROVAL_LEVELS = Object.freeze({
director_approval: 'director_approval',
team_captain_approval: 'team_captain_approval',
director_approval: 'director_approval', // deprecated
team_captain_approval: 'team_captain_approval', // deprecated
admin_approval: 'admin_approval',
faculty_advisor_approval: 'faculty_advisor_approval',
})

const AUTH_ROLES = Object.freeze({
Member: 'Member',
Administrator: 'Administrator',
})

const EMAIL_RECIPIENTS = Object.freeze({
Expand All @@ -49,8 +55,6 @@ const TICKET_ENDPOINTS = Object.freeze({
})

const ADMIN_IDENTIFIERS = ['drayside', 'v2zheng', 'jw4he', 'william.li']
const TEAM_CAPTAIN_TITLES = ['Team Captain']
const DIRECTOR_TITLES = ['Director']

module.exports = {
ENDOWMENT_FUNDS,
Expand All @@ -61,8 +65,7 @@ module.exports = {
UPR_STATUS_FUNDING_SPENT,
TICKET_ENDPOINTS,
APPROVAL_LEVELS,
AUTH_ROLES,
EMAIL_RECIPIENTS,
ADMIN_IDENTIFIERS,
TEAM_CAPTAIN_TITLES,
DIRECTOR_TITLES,
}
5 changes: 3 additions & 2 deletions backend/models/personalpurchase.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ const PersonalPurchaseSchema = new Schema(
purchase_url: { type: String, required: true },
cost: { type: Number, required: true },
purchase_justification: { type: String, required: true },
director_approval: { type: Boolean, default: false },
team_captain_approval: { type: Boolean, default: false },
director_approval: { type: Boolean, default: false }, // deprecated
team_captain_approval: { type: Boolean, default: false }, // deprecated
admin_approval: { type: Boolean, default: false },
faculty_advisor_approval: { type: Boolean, default: false },
},
{
_id: false,
Expand Down
5 changes: 3 additions & 2 deletions backend/models/uwfinancepurchase.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ const UWFinancePurchaseSchema = new Schema(
cost: { type: Number, required: true },
purchase_justification: { type: String, required: true },
pickup_instruction: { type: String },
director_approval: { type: Boolean, default: false },
team_captain_approval: { type: Boolean, default: false },
director_approval: { type: Boolean, default: false }, // deprecated
team_captain_approval: { type: Boolean, default: false }, // deprecated
admin_approval: { type: Boolean, default: false },
faculty_advisor_approval: { type: Boolean, default: false },
requisition_number: { type: String },
po_number: { type: String },
},
Expand Down
4 changes: 2 additions & 2 deletions backend/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ mongoose.connect(uri, {

const connection = mongoose.connection
connection.once('open', () => {
console.log('MongoDB database connection established successfully')
console.log('🛢️ MongoDB database connection established successfully')
})

const fundingItemsRouter = require('./routes/fundingitems.routes')
Expand All @@ -43,6 +43,6 @@ app.use('/files', filesRouter)
app.use('/comments', commentRouter)

app.listen(port, async () => {
console.log(`Server is running on port: ${port}`)
console.log(`🗄️ Server is running on port: ${port}`)
await updateGroup()
})
4 changes: 2 additions & 2 deletions backend/service/googlegroup.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const deleteUnrecognizedUsers = async (currentEmails, newEmails) => {
await GoogleGroup.deleteMany({
filter: { email: { $in: emailsToDelete } },
})
console.log('User Cleanup Successful')
console.log('🧹 Clean up users completed')
}

const upsertUsers = async (newUsers) => {
Expand All @@ -41,7 +41,7 @@ const upsertUsers = async (newUsers) => {
})
})
await GoogleGroup.bulkWrite(bulkOps)
console.log('Updating users successful')
console.log('🛠️ Update users completed')
}

const updateGoogleGroups = async (body) => {
Expand Down
Loading

0 comments on commit 4913fd3

Please sign in to comment.