diff --git a/docs/components-diagram.drawio b/docs/components-diagram.drawio index 0b39563a..f58282b4 100644 --- a/docs/components-diagram.drawio +++ b/docs/components-diagram.drawio @@ -1 +1 @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/docs/components-diagram.svg b/docs/components-diagram.svg index a1a7bbb1..1e996b10 100644 --- a/docs/components-diagram.svg +++ b/docs/components-diagram.svg @@ -1,3 +1,4 @@ + -
Designer
Designer
Client
Client-side application based on React. 
Client-side application ba...
TODOServer
NodeJS server based on HapiJS.
NodeJS server based on Hap...
pluginsDesignerLoginRouterViewComponentsNewConfigVisualisationTODODesigner plugin routesGET   /new: serves the client-side applicationPOST /new: creates (or copies an existing) form configuration and uploads it to S3 and/or publishes to `${publishUrl}/publish`GET   /{id}: serves the client-side applicationGET   /{id}/api/data: proxy request to runners to load a form configuration by ID (`{publishUrl}/published/{id}`)GET   /configurations: loads all available configurations from S3 or preview service (runner running in preview mode)PUT   /{id}/api/data: uploads a form configuration to S3 or preview serviceComponentCreateTODOconditionsTODOServicespersistenceS3persistencePreviewpluginsEngineapplicationStatusblankiecrumberrorPageslocalepulserateLimitroutersessionviewsServer
NodeJS server based on HapiJS.
NodeJS server based on Hap...
Engine
Engine
RoutesGET /: renders default form otherwise Not Found XXGET /published: returns a form configurationGET /{id}: renders form {id}GET /{id}/{path}: renders form {id} in a specific pathPOST /{id}/{path}: handles posted form {id} in a specific path-- Preview Mode routes (when the user sets the runner to Preview Mode)POST /publish: publishes a form JSON payloadGET /published/{id}: servers a form {id} JSON payloadGET /published: servers a list with all published forms' JSON payloadsFormModel
Class responsible for parsing the form configuration JSON and exposing methods the runner uses throughout the engine.

When the user opens the runner with a specific form ID, it instantiates FormModel with that form's JSON, and then passes the instance around to the various routes, page and component controllers.
Class responsible for parsing the form con...
components
Component classes wrap around components' JSON representations. They expose methods the views and page controllers mostly use.

The runner instantiates components' classes inside pageControllers, and these are exposed to views. 
Component classes wrap around components'...
pageControllers
Controller classes for various pages such as form pages, summary etc. 

The runner instantiates these classes with the FormModel. They initialise page specific properties, such as path, title, section, conditions, components etc.

They are mostly used in views.
Controller classes for various pages such...
services
addressService: exposes findByPostcode
addressService: exposes findByPostcode
configurationService: responsible for loading forms configurations from disc
configurationService: responsible for load...
httpService: a wrapper around wreck, interacts with external endpoints such as GovUK Notify
httpService: a wrapper around wreck, inter...
Views
Various HTML views templates to render pages, forms, components, etc. 
Various HTML views templates to render pag...
ServicesemailService
Service based on nodemailer and AWS Simple Email Service.
Service based on nodemailer and AWS Simple...
cacheService
Responsible for caching form submission state, the storage engine can be Redis (if the user provides a host address and configurations), otherwise it defaults to memory. 
Responsible for caching form submission st...
httpService
A wrapper around npm wreck, which interacts with external endpoints such as Webhook Service.
A wrapper around npm wreck, which interact...
notifyService
Service which allows the runner to integrate with GovUK notify.
Service which allows the runner to integra...
payService
Service which allows the runner to integrate with GovUK Pay.
Service which allows the runner to integra...
sheetService
Service which allows the runner to integrate with Google sheets.
Service which allows the runner to integra...
uploadService
Handles the upload of documents during form submissions. The environment variable DOCUMENT_UPLOAD_API_URL specifies the endpoint to post uploaded documents to. 
Handles the upload of documents during for...
webhookService
Sends a POST request with the final form submission's JSON payload to an endpoint the user configures through the designer's output/webhook. If the endpoint responds with a property  `reference`, its value displays to the user on the Confirmation page.  
Sends a POST request with the final form s...
Model
Model
Model
The model package `@xgovformbuilder/model` groups all functionalities related to form data modelling. Exposes classes and helpers functions, which handle representing a form as JSON and data submission, including validation, schemas, etc.

The main components of the package are:
The model package `@xgovformbuilder/model` groups...
form/FormConfiguration
The wrapper class around a form configuration:
The wrapper class around a form configur...
+ Key: string+ DisplayName: string+ Last Modified: string+ feedbackFrom: booleandata-model/Data
Encapsulates the JSON representation of the form. This class is heavily used in many places inside the designer and runner applications. 

It exposes an API to allow operations over the JSON structure, such as `getPages`, `AddPages`, `addCondition`, `updateComponent`, `valuesFor(component)` etc. 


Encapsulates the JSON representation of...
conditions
Classes and methods which handle input conditions, such as "is greater than".
The designer uses these to allow users to configure input specific conditions, and also inside the runner to evaluate those conditions when the user submits a form. 
Classes and methods which handle input c...
components
A basic JSON representation of all components. The designer uses these to create and edit components.

{
    name: "TextField",
    type: "TextField",
    title: "Text field",
    subType: "field",
    hint: "",
    options: {},
    schema: {},
  }


A basic JSON representation of all compo...
values
ListRefValues and StaticValues classes which encapsulate data-model values.  
ListRefValues and StaticValues classes w...
Schema
Joi ObjectSchema, which the JSON payload validation uses.
Joi ObjectSchema, which the JSON payload...
migration/SchemaMigrationService
Responsible for backward compatibility of old JSON representations of forms. 

Whenever the structure of the form JSON changes, the developer must update this class to allow previous versions to process and update.
Responsible for backward compatibility o...
utils
A few utils methods the designer and runner use everywhere, most importantly:
A few utils methods the designer and runn...
+ clone: clone data-model or JSON objects
+ clone: clone data-model or JSON objects
+ filter: filter an object based on a predicate
+ filter: filter an object based on a pre...
+ serialiseAndDeserialise: serialise and deserialise an object 
+ serialiseAndDeserialise: serialise and...
Logger Service
Logger Service
Viewer does not support full SVG 1.1
\ No newline at end of file +
Designer
Designer
Client
Client-side application based on React. 
Client-side application ba...
TODOServer
NodeJS server based on HapiJS.
NodeJS server based on Hap...
pluginsDesignerLoginRouterViewComponentsNewConfigVisualisationTODODesigner plugin routesGET   /new: serves the client-side applicationPOST /new: creates (or copies an existing) form configuration and uploads it to S3 and/or publishes to `${publishUrl}/publish`GET   /{id}: serves the client-side applicationGET   /{id}/api/data: proxy request to runners to load a form configuration by ID (`{publishUrl}/published/{id}`)GET   /configurations: loads all available configurations from S3 or preview service (runner running in preview mode)PUT   /{id}/api/data: uploads a form configuration to S3 or preview serviceComponentCreateTODOconditionsTODOServicespersistenceS3persistencePreviewpluginsEngineapplicationStatusblankiecrumberrorPageslocalepulserateLimitroutersessionauthviewsServer
NodeJS server based on HapiJS.
NodeJS server based on Hap...
Engine
Engine
RoutesGET /: renders default form otherwise Not Found XXGET /published: returns a form configurationGET /{id}: renders form {id}GET /{id}/{path}: renders form {id} in a specific pathPOST /{id}/{path}: handles posted form {id} in a specific path-- Preview Mode routes (when the user sets the runner to Preview Mode)POST /publish: publishes a form JSON payloadGET /published/{id}: servers a form {id} JSON payloadGET /published: servers a list with all published forms' JSON payloads-- If auth is enabled:GET /login: redirects to the oAuth authorise endpoint
POST /login: handles the oAuth login callback, then redirects to the last                       page viewed by the user
POST /login: handles the oAuth login callback, then redirects to...
GET /logout: logs the current user out, then redirects to /FormModel
Class responsible for parsing the form configuration JSON and exposing methods the runner uses throughout the engine.

When the user opens the runner with a specific form ID, it instantiates FormModel with that form's JSON, and then passes the instance around to the various routes, page and component controllers.
Class responsible for parsing the form con...
components
Component classes wrap around components' JSON representations. They expose methods the views and page controllers mostly use.

The runner instantiates components' classes inside pageControllers, and these are exposed to views. 
Component classes wrap around components'...
pageControllers
Controller classes for various pages such as form pages, summary etc. 

The runner instantiates these classes with the FormModel. They initialise page specific properties, such as path, title, section, conditions, components etc.

If auth is enabled, these redirect to login if the user is not signed in.

They are mostly used in views.
Controller classes for various pages such...
services
addressService: exposes findByPostcode
addressService: exposes findByPostcode
configurationService: responsible for loading forms configurations from disc
configurationService: responsible for load...
httpService: a wrapper around wreck, interacts with external endpoints such as GovUK Notify
httpService: a wrapper around wreck, inter...
Views
Various HTML views templates to render pages, forms, components, etc. 
Various HTML views templates to render pag...
ServicesemailService
Service based on nodemailer and AWS Simple Email Service.
Service based on nodemailer and AWS Simple...
cacheService
Responsible for caching form submission state, the storage engine can be Redis (if the user provides a host address and configurations), otherwise it defaults to memory. 
Responsible for caching form submission st...
httpService
A wrapper around npm wreck, which interacts with external endpoints such as Webhook Service.
A wrapper around npm wreck, which interact...
notifyService
Service which allows the runner to integrate with GovUK notify.
Service which allows the runner to integra...
payService
Service which allows the runner to integrate with GovUK Pay.
Service which allows the runner to integra...
sheetService
Service which allows the runner to integrate with Google sheets.
Service which allows the runner to integra...
uploadService
Handles the upload of documents during form submissions. The environment variable DOCUMENT_UPLOAD_API_URL specifies the endpoint to post uploaded documents to. 
Handles the upload of documents during for...
webhookService
Sends a POST request with the final form submission's JSON payload to an endpoint the user configures through the designer's output/webhook. If the endpoint responds with a property  `reference`, its value displays to the user on the Confirmation page.  
Sends a POST request with the final form s...
Model
Model
Model
The model package `@xgovformbuilder/model` groups all functionalities related to form data modelling. Exposes classes and helpers functions, which handle representing a form as JSON and data submission, including validation, schemas, etc.

The main components of the package are:
The model package `@xgovformbuilder/model` groups...
form/FormConfiguration
The wrapper class around a form configuration:
The wrapper class around a form configur...
+ Key: string+ DisplayName: string+ Last Modified: string+ feedbackFrom: booleandata-model/Data
Encapsulates the JSON representation of the form. This class is heavily used in many places inside the designer and runner applications. 

It exposes an API to allow operations over the JSON structure, such as `getPages`, `AddPages`, `addCondition`, `updateComponent`, `valuesFor(component)` etc. 


Encapsulates the JSON representation of...
conditions
Classes and methods which handle input conditions, such as "is greater than".
The designer uses these to allow users to configure input specific conditions, and also inside the runner to evaluate those conditions when the user submits a form. 
Classes and methods which handle input c...
components
A basic JSON representation of all components. The designer uses these to create and edit components.

{
    name: "TextField",
    type: "TextField",
    title: "Text field",
    subType: "field",
    hint: "",
    options: {},
    schema: {},
  }


A basic JSON representation of all compo...
values
ListRefValues and StaticValues classes which encapsulate data-model values.  
ListRefValues and StaticValues classes w...
Schema
Joi ObjectSchema, which the JSON payload validation uses.
Joi ObjectSchema, which the JSON payload...
migration/SchemaMigrationService
Responsible for backward compatibility of old JSON representations of forms. 

Whenever the structure of the form JSON changes, the developer must update this class to allow previous versions to process and update.
Responsible for backward compatibility o...
utils
A few utils methods the designer and runner use everywhere, most importantly:
A few utils methods the designer and runn...
+ clone: clone data-model or JSON objects
+ clone: clone data-model or JSON objects
+ filter: filter an object based on a predicate
+ filter: filter an object based on a pre...
+ serialiseAndDeserialise: serialise and deserialise an object 
+ serialiseAndDeserialise: serialise and...
Logger Service
Logger Service
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/runner/README.md b/runner/README.md index 7cde3079..46ae0126 100644 --- a/runner/README.md +++ b/runner/README.md @@ -56,27 +56,33 @@ To symlink an external .env file, for example inside a [Keybase](https://keybase `symlink-config` accepts two variables, ENV_LOC and LINK_TO. If the file location is not passed in, you will be prompted for a location. LINK_TO is optional, it defaults to `./${PROJECT_DIR}`. -| name | description | required | default | valid | notes | -| ------------------ | ------------------------------------- | :------: | ------- | :-------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | -| NODE_ENV | Node environment | no | | development,test,production | | -| PORT | Port number | no | 3009 | | | -| OS_KEY | Ordnance Survey | no | | | For address lookup by postcode | -| PAY_API_KEY | Pay api key | yes | | | | -| PAY_RETURN_URL | Pay return url | yes | | | For GOV.UK Pay to redirect back to our service | -| PAY_API_URL | Pay api url | yes | | | | -| NOTIFY_TEMPLATE_ID | Notify api key | yes | | | Template ID required to send form payloads via [GOV.UK Notify](https://www.notifications.service.gov.uk) email service. | -| NOTIFY_API_KEY | Notify api key | yes | | | API KEY required to send form payloads via [GOV.UK Notify](https://www.notifications.service.gov.uk) email service. | -| GTM_ID_1 | Google Tag Manager ID 1 | no | | | | -| GTM_ID_2 | Google Tag Manager ID 2 | no | | | | -| MATOMO_URL | URL of Matomo | no | | | | -| MATOMO_ID | ID of Matomo site | no | | | | -| SSL_KEY | SSL Key | no | | | | -| SSL_CERT | SSL Certificate | no | | | | -| PREVIEW_MODE | Preview mode | no | false | | This should only be used in a dev or testing environment. Setting true will allow POST requests from the designer to add or mutate forms. | -| LOG_LEVEL | Log level | no | debug | trace,debug,info,error | | -| PRIVACY_POLICY_URL | The url used in footer's privacy link | no | # | | | -| API_ENV | Switch for API keys | no | | test,production | If the JSON file supplies test and live API keys, this is used to switch between which key which needs to be used | -| PHASE_TAG | Tag to use for phase banner | no | beta | alpha, beta, empty string | | +| name | description | required | default | valid | notes | +| ----------------------- | ------------------------------------- | :---------------------: | ------- | :-------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | +| NODE_ENV | Node environment | no | | development,test,production | | +| PORT | Port number | no | 3009 | | | +| OS_KEY | Ordnance Survey | no | | | For address lookup by postcode | +| PAY_API_KEY | Pay api key | yes | | | | +| PAY_RETURN_URL | Pay return url | yes | | | For GOV.UK Pay to redirect back to our service | +| PAY_API_URL | Pay api url | yes | | | | +| NOTIFY_TEMPLATE_ID | Notify api key | yes | | | Template ID required to send form payloads via [GOV.UK Notify](https://www.notifications.service.gov.uk) email service. | +| NOTIFY_API_KEY | Notify api key | yes | | | API KEY required to send form payloads via [GOV.UK Notify](https://www.notifications.service.gov.uk) email service. | +| GTM_ID_1 | Google Tag Manager ID 1 | no | | | | +| GTM_ID_2 | Google Tag Manager ID 2 | no | | | | +| MATOMO_URL | URL of Matomo | no | | | | +| MATOMO_ID | ID of Matomo site | no | | | | +| SSL_KEY | SSL Key | no | | | | +| SSL_CERT | SSL Certificate | no | | | | +| PREVIEW_MODE | Preview mode | no | false | | This should only be used in a dev or testing environment. Setting true will allow POST requests from the designer to add or mutate forms. | +| LOG_LEVEL | Log level | no | debug | trace,debug,info,error | | +| PRIVACY_POLICY_URL | The url used in footer's privacy link | no | # | | | +| API_ENV | Switch for API keys | no | | test,production | If the JSON file supplies test and live API keys, this is used to switch between which key which needs to be used | +| PHASE_TAG | Tag to use for phase banner | no | beta | alpha, beta, empty string | | +| AUTH_ENABLED | Enable auth for all form pages | no | false | | | +| AUTH_CLIENT_ID | oAuth client ID | If AUTH_ENABLED is true | | | | +| AUTH_CLIENT_SECRET | oAuth client secret | If AUTH_ENABLED is true | | | | +| AUTH_CLIENT_AUTH_URL | oAuth client authorise endpoint | If AUTH_ENABLED is true | | | | +| AUTH_CLIENT_TOKEN_URL | oAuth client token endpoint | If AUTH_ENABLED is true | | | | +| AUTH_CLIENT_PROFILE_URL | oAuth client user profile endpoint | If AUTH_ENABLED is true | | | | # Testing diff --git a/runner/package.json b/runner/package.json index 0c08ade6..0ec9126e 100644 --- a/runner/package.json +++ b/runner/package.json @@ -35,11 +35,13 @@ "license": "SEE LICENSE IN LICENSE", "dependencies": { "@babel/runtime": "7.10.4", + "@hapi/bell": "^12.3.0", "@hapi/boom": "9.1.0", "@hapi/catbox": "11.1.0", "@hapi/catbox-memory": "5.0.0", "@hapi/catbox-redis": "5.0.5", "@hapi/code": "8.0.1", + "@hapi/cookie": "^11.0.2", "@hapi/crumb": "8.0.0", "@hapi/hapi": "^20.0.3", "@hapi/inert": "5.2.2", diff --git a/runner/src/server/config.ts b/runner/src/server/config.ts index a39b6e70..c9ef7e91 100644 --- a/runner/src/server/config.ts +++ b/runner/src/server/config.ts @@ -79,6 +79,27 @@ const schema = Joi.object({ lastCommit: Joi.string().default("undefined"), lastTag: Joi.string().default("undefined"), apiEnv: Joi.string().allow("test", "production", "").optional(), + authEnabled: Joi.boolean().optional(), + authClientId: Joi.string().when("authEnabled", { + then: Joi.required(), + otherwise: Joi.optional(), + }), + authClientSecret: Joi.string().when("authEnabled", { + then: Joi.required(), + otherwise: Joi.optional(), + }), + authClientAuthUrl: Joi.string().when("authEnabled", { + then: Joi.required(), + otherwise: Joi.optional(), + }), + authClientTokenUrl: Joi.string().when("authEnabled", { + then: Joi.required(), + otherwise: Joi.optional(), + }), + authClientProfileUrl: Joi.string().when("authEnabled", { + then: Joi.required(), + otherwise: Joi.optional(), + }), }); export function buildConfig() { @@ -118,6 +139,12 @@ export function buildConfig() { lastCommit: process.env.LAST_COMMIT || process.env.LAST_COMMIT_GH, lastTag: process.env.LAST_TAG || process.env.LAST_TAG_GH, apiEnv: process.env.API_ENV, + authEnabled: process.env.AUTH_ENABLED, + authClientId: process.env.AUTH_CLIENT_ID, + authClientSecret: process.env.AUTH_CLIENT_SECRET, + authClientAuthUrl: process.env.AUTH_CLIENT_AUTH_URL, + authClientTokenUrl: process.env.AUTH_CLIENT_TOKEN_URL, + authClientProfileUrl: process.env.AUTH_CLIENT_PROFILE_URL, }; // Validate config diff --git a/runner/src/server/forms/test.json b/runner/src/server/forms/test.json index 5452ed27..59b6701e 100644 --- a/runner/src/server/forms/test.json +++ b/runner/src/server/forms/test.json @@ -1,6 +1,17 @@ { - "startPage": "/uk-passport", + "startPage": "/start", "pages": [ + { + "title": "Start", + "path": "/start", + "components": [], + "next": [ + { + "path": "/uk-passport" + } + ], + "controller": "./pages/start.js" + }, { "path": "/uk-passport", "components": [ @@ -454,8 +465,7 @@ } ], "phaseBanner": {}, - "fees": [ - ], + "fees": [], "payApiKey": "", "outputs": [ { diff --git a/runner/src/server/index.ts b/runner/src/server/index.ts index c334e191..bfd60e78 100644 --- a/runner/src/server/index.ts +++ b/runner/src/server/index.ts @@ -13,6 +13,7 @@ import { configureBlankiePlugin } from "./plugins/blankie"; import { configureCrumbPlugin } from "./plugins/crumb"; import pluginLocale from "./plugins/locale"; import pluginSession from "./plugins/session"; +import pluginAuth from "./plugins/auth"; import pluginViews from "./plugins/views"; import pluginApplicationStatus from "./plugins/applicationStatus"; import pluginRouter from "./plugins/router"; @@ -20,15 +21,15 @@ import pluginErrorPages from "./plugins/errorPages"; import pluginLogging from "./plugins/logging"; import pluginPulse from "./plugins/pulse"; import { + AddressService, CacheService, catboxProvider, EmailService, NotifyService, PayService, + StatusService, UploadService, WebhookService, - AddressService, - StatusService, } from "./services"; import { HapiRequest, HapiResponseToolkit, RouteConfig } from "./types"; import getRequestInfo from "./utils/getRequestInfo"; @@ -89,6 +90,7 @@ async function createServer(routeConfig: RouteConfig) { await server.register(configureCrumbPlugin(config, routeConfig)); await server.register(pluginLogging); await server.register(Schmervice); + await server.register(pluginAuth); server.registerService([ CacheService, diff --git a/runner/src/server/plugins/auth.ts b/runner/src/server/plugins/auth.ts new file mode 100644 index 00000000..4409455d --- /dev/null +++ b/runner/src/server/plugins/auth.ts @@ -0,0 +1,83 @@ +import AuthCookie from "@hapi/cookie"; +import Bell from "@hapi/bell"; + +import config from "server/config"; +import { HapiRequest, HapiResponseToolkit } from "server/types"; +import { redirectTo } from "server/plugins/engine"; +import generateCookiePassword from "server/utils/generateCookiePassword"; + +export const shouldLogin = (request: HapiRequest) => + config.authEnabled && !request.auth.isAuthenticated; + +export default { + plugin: { + name: "auth", + register: async (server) => { + if (!config.authEnabled) { + return; + } + + await server.register(AuthCookie); + await server.register(Bell); + + server.auth.strategy("session", "cookie", { + cookie: { + name: "auth", + password: config.sessionCookiePassword || generateCookiePassword(), + isSecure: true, + }, + }); + + server.auth.strategy("oauth", "bell", { + provider: { + name: "oauth", + protocol: "oauth2", + auth: config.authClientAuthUrl, + token: config.authClientTokenUrl, + scope: ["read write"], + profile: async (credentials, _params, get) => { + const { email, first_name, last_name, user_id } = await get( + config.authClientProfileUrl + ); + credentials.profile = { email, first_name, last_name, user_id }; + }, + }, + password: config.sessionCookiePassword || generateCookiePassword(), + clientId: config.authClientId, + clientSecret: config.authClientSecret, + forceHttps: config.serviceUrl.startsWith("https"), + }); + + server.auth.default({ strategy: "session", mode: "try" }); + + server.route({ + method: ["GET", "POST"], + path: "/login", + config: { + auth: "oauth", + handler: (request: HapiRequest, h: HapiResponseToolkit) => { + if (request.auth.isAuthenticated) { + request.cookieAuth.set(request.auth.credentials.profile); + const returnUrl = + request.auth.credentials.query?.returnUrl || "/"; + return redirectTo(request, h, returnUrl); + } + + return h.response(JSON.stringify(request)); + }, + }, + }); + + server.route({ + method: "get", + path: "/logout", + handler: async (request: HapiRequest, h: HapiResponseToolkit) => { + request.cookieAuth.clear(); + request.yar.reset(); + + return redirectTo(request, h, "/"); + }, + }); + }, + }, +}; diff --git a/runner/src/server/plugins/engine/plugin.ts b/runner/src/server/plugins/engine/plugin.ts index f86796c1..aa674f30 100644 --- a/runner/src/server/plugins/engine/plugin.ts +++ b/runner/src/server/plugins/engine/plugin.ts @@ -2,12 +2,13 @@ import path from "path"; import { configure } from "nunjucks"; import { redirectTo } from "./helpers"; import { FormConfiguration } from "@xgovformbuilder/model"; -import { HapiServer, HapiRequest, HapiResponseToolkit } from "server/types"; +import { HapiRequest, HapiResponseToolkit, HapiServer } from "server/types"; import { FormModel } from "./models"; import Boom from "boom"; import { PluginSpecificConfiguration } from "@hapi/hapi"; import { FormPayload } from "./types"; +import { shouldLogin } from "server/plugins/auth"; configure([ // Configure Nunjucks to allow rendering of content that is revealed conditionally. @@ -167,6 +168,14 @@ export const plugin = { (page) => normalisePath(page.path) === normalisePath(path) ); if (page) { + // NOTE: Start pages should live on gov.uk, but this allows prototypes to include signposting about having to log in. + if ( + page.pageDef.controller !== "./pages/start.js" && + shouldLogin(request) + ) { + return h.redirect(`/login?returnUrl=${request.path}`); + } + return page.makeGetRouteHandler()(request, h); } if (normalisePath(path) === "") { diff --git a/runner/src/server/plugins/router.ts b/runner/src/server/plugins/router.ts index 05b9d12e..6f70e2c7 100644 --- a/runner/src/server/plugins/router.ts +++ b/runner/src/server/plugins/router.ts @@ -1,7 +1,7 @@ import Joi from "joi"; import Url from "url-parse"; import { redirectTo } from "./engine"; -import { publicRoutes, healthCheckRoute } from "../routes"; +import { healthCheckRoute, publicRoutes } from "../routes"; import { HapiRequest, HapiResponseToolkit } from "../types"; const routes = [...publicRoutes, healthCheckRoute]; diff --git a/runner/src/server/plugins/session.ts b/runner/src/server/plugins/session.ts index 72923365..e7022ed1 100644 --- a/runner/src/server/plugins/session.ts +++ b/runner/src/server/plugins/session.ts @@ -1,4 +1,5 @@ import config from "../config"; +import generateCookiePassword from "server/utils/generateCookiePassword"; export default { plugin: require("@hapi/yar"), @@ -7,12 +8,7 @@ export default { expiresIn: config.sessionTimeout, }, cookieOptions: { - password: - config.sessionCookiePassword || - Array(32) - .fill(0) - .map(() => Math.random().toString(36).charAt(2)) - .join(""), + password: config.sessionCookiePassword || generateCookiePassword(), isSecure: !!config.isDev, isHttpOnly: true, isSameSite: "Lax", diff --git a/runner/src/server/plugins/views.ts b/runner/src/server/plugins/views.ts index 5850cc1b..b885ed0f 100644 --- a/runner/src/server/plugins/views.ts +++ b/runner/src/server/plugins/views.ts @@ -78,6 +78,9 @@ export default { serviceStartPage: config.serviceStartPage || "#", privacyPolicyUrl: config.privacyPolicyUrl || "#", phaseTag: config.phaseTag, + navigation: request?.auth.isAuthenticated + ? [{ text: "Sign out", href: "/logout" }] + : null, }), }, }; diff --git a/runner/src/server/utils/generateCookiePassword.ts b/runner/src/server/utils/generateCookiePassword.ts new file mode 100644 index 00000000..2ac43399 --- /dev/null +++ b/runner/src/server/utils/generateCookiePassword.ts @@ -0,0 +1,7 @@ +const generateCookiePassword = (): String => + Array(32) + .fill(0) + .map(() => Math.random().toString(36).charAt(2)) + .join(""); + +export default generateCookiePassword; diff --git a/runner/src/server/views/layout.html b/runner/src/server/views/layout.html index 5d43ad4e..c195f05b 100644 --- a/runner/src/server/views/layout.html +++ b/runner/src/server/views/layout.html @@ -78,7 +78,7 @@ {{ govukSkipLink({ href: '#main-content', text: 'Skip to main content' - }) }} + }) }} {% endblock %} @@ -87,7 +87,8 @@ homepageUrl: "https://gov.uk", containerClasses: "govuk-width-container", serviceName: name if name else serviceName, - serviceUrl: serviceStartPage + serviceUrl: serviceStartPage, + navigation: navigation }) }} {% endblock %} @@ -102,7 +103,7 @@ {% endif %} - + {% if gtmId2 %}