Skip to content

Commit

Permalink
Refactor/netlify toml (#357)
Browse files Browse the repository at this point in the history
* Feat: add NetlifyTomlService

* Feat: add netlifyToml router

* Test: add new fixture for netlifyToml

* Test: add tests for netlifyToml service and router

* Fix: remove sitename from netlify-toml endpoint

* Nit: add line space

* Chore: include spec in name of test file and minor comment changes

* Rebase: use auth verify middleware in router

* Nit: add more details to comment

* Fix: move routers to appropriate subrouters and swap to res.locals

* Fix: axios mock in test for netlifytoml service
  • Loading branch information
alexanderleegs authored Apr 6, 2022
1 parent 61b62aa commit fbb38c8
Show file tree
Hide file tree
Showing 6 changed files with 427 additions and 0 deletions.
245 changes: 245 additions & 0 deletions fixtures/netlifyToml.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
const netlifyTomlContent = `
[build.processing]
skip_processing = false
[build.processing.css]
bundle = true
minify = true
[build.processing.js]
bundle = true
minify = true
[build.processing.html]
pretty_urls = true
[build.processing.images]
compress = true
[[headers]]
for = "/*"
[headers.values]
X-XSS-Protection = "1; mode=block"
Referrer-Policy = "no-referrer"
X-Content-Type-Options = "nosniff"
X-Frame-Options = "deny"
Content-Security-Policy = """
default-src
'self'
;
script-src
'self'
blob:
https://assets.dcube.cloud
https://*.wogaa.sg
https://assets.adobedtm.com
https://www.google-analytics.com
https://cdnjs.cloudflare.com
https://va.ecitizen.gov.sg
https://*.cloudfront.net
https://printjs-4de6.kxcdn.com
https://unpkg.com
https://wogadobeanalytics.sc.omtrdc.net
https://connect.facebook.net
https://graph.facebook.com
https://facebook.com
https://www.facebook.com
https://www.googletagmanager.com
https://*.licdn.com
https://webchat.vica.gov.sg
https://vica.gov.sg
https://www.google.com/recaptcha/
https://www.gstatic.com/recaptcha/
https://static.zdassets.com
https://ekr.zdassets.com
https://*.zendesk.com
https://*.zopim.com
wss://*.zendesk.com
wss://*.zopim.com
;
object-src
'self'
;
style-src
'self'
'unsafe-inline'
https://fonts.googleapis.com/
https://*.cloudfront.net
https://va.ecitizen.gov.sg
https://*.wogaa.sg
https://cdnjs.cloudflare.com
https://datagovsg.github.io
https://webchat.vica.gov.sg
https://vica.gov.sg
https://unpkg.com
;
img-src
*
;
media-src
*
;
frame-src
https://form.gov.sg/
https://wogaa.demdex.net/
https://*.youtube.com
https://*.youtube-nocookie.com
https://*.vimeo.com
https://www.google.com
https://checkfirst.gov.sg
https://www.checkfirst.gov.sg
https://docs.google.com
https://nlb.ap.panopto.com
https://www.google.com/recaptcha/
https://www.gstatic.com/recaptcha/
https://data.gov.sg
;
frame-ancestors
'none'
;
font-src
*
data:
;
connect-src
'self'
https://dpm.demdex.net
https://www.google-analytics.com
https://stats.g.doubleclick.net
https://*.wogaa.sg
https://va.ecitizen.gov.sg
https://ifaqs.flexanswer.com
https://*.cloudfront.net
https://fonts.googleapis.com
https://cdnjs.cloudflare.com
https://wogadobeanalytics.sc.omtrdc.net
https://data.gov.sg
https://api.isomer.gov.sg
https://webchat.vica.gov.sg
https://chat.vica.gov.sg
https://vica.gov.sg
https://s3-va-prd-vica.s3-ap-southeast-1.amazonaws.com
wss://chat.vica.gov.sg
https://api-vica-ana.vica.gov.sg/api/v1/response-ratings
https://static.zdassets.com
https://ekr.zdassets.com
https://*.zendesk.com
https://*.zopim.com
wss://*.zendesk.com
wss://*.zopim.com
;
"""
`

const netlifyTomlHeaderValues = {
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "no-referrer",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "deny",
"Content-Security-Policy":
" default-src \n" +
" 'self'\n" +
" ; \n" +
" script-src \n" +
" 'self' \n" +
" blob: \n" +
" https://assets.dcube.cloud \n" +
" https://*.wogaa.sg \n" +
" https://assets.adobedtm.com \n" +
" https://www.google-analytics.com \n" +
" https://cdnjs.cloudflare.com \n" +
" https://va.ecitizen.gov.sg \n" +
" https://*.cloudfront.net \n" +
" https://printjs-4de6.kxcdn.com \n" +
" https://unpkg.com \n" +
" https://wogadobeanalytics.sc.omtrdc.net \n" +
" https://connect.facebook.net \n" +
" https://graph.facebook.com \n" +
" https://facebook.com \n" +
" https://www.facebook.com \n" +
" https://www.googletagmanager.com \n" +
" https://*.licdn.com \n" +
" https://webchat.vica.gov.sg \n" +
" https://vica.gov.sg\n" +
" https://www.google.com/recaptcha/\n" +
" https://www.gstatic.com/recaptcha/\n" +
" https://static.zdassets.com\n" +
" https://ekr.zdassets.com\n" +
" https://*.zendesk.com\n" +
" https://*.zopim.com\n" +
" wss://*.zendesk.com\n" +
" wss://*.zopim.com\n" +
" ; \n" +
" object-src \n" +
" 'self'\n" +
" ; \n" +
" style-src \n" +
" 'self' \n" +
" 'unsafe-inline'\n" +
" https://fonts.googleapis.com/ \n" +
" https://*.cloudfront.net \n" +
" https://va.ecitizen.gov.sg \n" +
" https://*.wogaa.sg \n" +
" https://cdnjs.cloudflare.com \n" +
" https://datagovsg.github.io \n" +
" https://webchat.vica.gov.sg \n" +
" https://vica.gov.sg\n" +
" https://unpkg.com\n" +
" ; \n" +
" img-src \n" +
" *\n" +
" ; \n" +
" media-src \n" +
" *\n" +
" ; \n" +
" frame-src \n" +
" https://form.gov.sg/ \n" +
" https://wogaa.demdex.net/ \n" +
" https://*.youtube.com \n" +
" https://*.youtube-nocookie.com \n" +
" https://*.vimeo.com \n" +
" https://www.google.com \n" +
" https://checkfirst.gov.sg \n" +
" https://www.checkfirst.gov.sg \n" +
" https://docs.google.com \n" +
" https://nlb.ap.panopto.com\n" +
" https://www.google.com/recaptcha/\n" +
" https://www.gstatic.com/recaptcha/\n" +
" https://data.gov.sg\n" +
" ; \n" +
" frame-ancestors \n" +
" 'none'\n" +
" ; \n" +
" font-src \n" +
" * \n" +
" data:\n" +
" ; \n" +
" connect-src \n" +
" 'self' \n" +
" https://dpm.demdex.net \n" +
" https://www.google-analytics.com \n" +
" https://stats.g.doubleclick.net \n" +
" https://*.wogaa.sg \n" +
" https://va.ecitizen.gov.sg \n" +
" https://ifaqs.flexanswer.com \n" +
" https://*.cloudfront.net \n" +
" https://fonts.googleapis.com \n" +
" https://cdnjs.cloudflare.com \n" +
" https://wogadobeanalytics.sc.omtrdc.net \n" +
" https://data.gov.sg \n" +
" https://api.isomer.gov.sg\n" +
" https://webchat.vica.gov.sg\n" +
" https://chat.vica.gov.sg\n" +
" https://vica.gov.sg\n" +
" https://s3-va-prd-vica.s3-ap-southeast-1.amazonaws.com\n" +
" wss://chat.vica.gov.sg\n" +
" https://api-vica-ana.vica.gov.sg/api/v1/response-ratings\n" +
" https://static.zdassets.com\n" +
" https://ekr.zdassets.com\n" +
" https://*.zendesk.com\n" +
" https://*.zopim.com\n" +
" wss://*.zendesk.com\n" +
" wss://*.zopim.com\n" +
" ;\n" +
" ",
}

module.exports = {
netlifyTomlContent,
netlifyTomlHeaderValues,
}
46 changes: 46 additions & 0 deletions newroutes/authenticated/__tests__/NetlifyToml.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const express = require("express")
const request = require("supertest")

const { errorHandler } = require("@middleware/errorHandler")
const { attachReadRouteHandlerWrapper } = require("@middleware/routeHandler")

const { NetlifyTomlRouter } = require("../netlifyToml")

describe("NetlifyToml Router", () => {
const mockNetlifyTomlService = {
read: jest.fn(),
update: jest.fn(),
}

const router = new NetlifyTomlRouter({
netlifyTomlService: mockNetlifyTomlService,
})

const app = express()
app.use(express.json({ limit: "7mb" }))
app.use(express.urlencoded({ extended: false }))

// We can use read route handler here because we don't need to lock the repo
app.get("/netlifyToml", attachReadRouteHandlerWrapper(router.readNetlifyToml))
app.use(errorHandler)

const accessToken = undefined // Can't set request fields - will always be undefined

const reqDetails = { accessToken }

beforeEach(() => {
jest.clearAllMocks()
})

describe("readNetlifyToml", () => {
const netlifyTomlHeaderValues = "netlifyTomlHeaderValues"
mockNetlifyTomlService.read.mockResolvedValue(netlifyTomlHeaderValues)

it("retrieves netlifyToml details", async () => {
const resp = await request(app).get(`/netlifyToml`).expect(200)

expect(resp.body).toStrictEqual({ netlifyTomlHeaderValues })
expect(mockNetlifyTomlService.read).toHaveBeenCalledWith(reqDetails)
})
})
})
7 changes: 7 additions & 0 deletions newroutes/authenticated/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
const express = require("express")

const {
NetlifyTomlService,
} = require("@services/configServices/NetlifyTomlService")
const { SitesService } = require("@services/utilServices/SitesService")

const { NetlifyTomlRouter } = require("./netlifyToml")
const { SitesRouter } = require("./sites")
const { UsersRouter } = require("./users")

Expand All @@ -12,16 +16,19 @@ const getAuthenticatedSubrouter = ({
usersService,
}) => {
const sitesService = new SitesService({ gitHubService, configYmlService })
const netlifyTomlService = new NetlifyTomlService()

const sitesV2Router = new SitesRouter({ sitesService })
const usersRouter = new UsersRouter({ usersService })
const netlifyTomlV2Router = new NetlifyTomlRouter({ netlifyTomlService })

const authenticatedSubrouter = express.Router({ mergeParams: true })

authenticatedSubrouter.use(authMiddleware.verifyJwt)

authenticatedSubrouter.use("/sites", sitesV2Router.getRouter())
authenticatedSubrouter.use("/user", usersRouter.getRouter())
authenticatedSubrouter.use("/netlify-toml", netlifyTomlV2Router.getRouter())

return authenticatedSubrouter
}
Expand Down
33 changes: 33 additions & 0 deletions newroutes/authenticated/netlifyToml.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const autoBind = require("auto-bind")
const express = require("express")

const { attachReadRouteHandlerWrapper } = require("@middleware/routeHandler")

class NetlifyTomlRouter {
constructor({ netlifyTomlService }) {
this.netlifyTomlService = netlifyTomlService
// We need to bind all methods because we don't invoke them from the class directly
autoBind(this)
}

// Read netlify.toml file
async readNetlifyToml(req, res) {
const { accessToken } = res.locals

const netlifyTomlHeaderValues = await this.netlifyTomlService.read({
accessToken,
})

return res.status(200).json({ netlifyTomlHeaderValues })
}

getRouter() {
const router = express.Router({ mergeParams: true })

router.get("/", attachReadRouteHandlerWrapper(this.readNetlifyToml))

return router
}
}

module.exports = { NetlifyTomlRouter }
40 changes: 40 additions & 0 deletions services/configServices/NetlifyTomlService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const axios = require("axios")
const toml = require("toml")

// Import error types
const { NotFoundError } = require("@errors/NotFoundError")

const validateStatus = require("@utils/axios-utils")

const { GITHUB_BUILD_ORG_NAME, GITHUB_BUILD_REPO_NAME } = process.env

class NetlifyTomlService {
async read({ accessToken }) {
const endpoint = `https://api.github.com/repos/${GITHUB_BUILD_ORG_NAME}/${GITHUB_BUILD_REPO_NAME}/contents/overrides/netlify.toml`

const resp = await axios.get(endpoint, {
validateStatus,
headers: {
Authorization: `token ${accessToken}`,
"Content-Type": "application/json",
},
})

if (resp.status === 404)
throw new NotFoundError("netlify.toml file does not exist")

const { content } = resp.data

// Convert to readable form
const netlifyTomlReadableContent = toml.parse(Base64.decode(content))

// Headers is an array of objects, specifying a set of access rules for each specified path
// Under our current assumption, the file only contains a single set of access rules,
// so we apply the first set of access rules to all paths
const netlifyTomlHeaderValues = netlifyTomlReadableContent.headers[0].values

return netlifyTomlHeaderValues
}
}

module.exports = { NetlifyTomlService }
Loading

0 comments on commit fbb38c8

Please sign in to comment.