|
13 | 13 | // OpenID Connect auth handling using Authorization Code Flow with PKCE. |
14 | 14 | // TODO document _why_ auth-code-flow, and not e.g. implicit flow? |
15 | 15 |
|
| 16 | +const { createHash } = require('node:crypto'); |
| 17 | + |
16 | 18 | const { generators } = require('openid-client'); |
17 | 19 | const config = require('config'); |
18 | 20 | const { parse, render } = require('mustache'); |
@@ -51,38 +53,42 @@ const callbackCookieProps = { |
51 | 53 | path: '/v1/oidc/callback', |
52 | 54 | }; |
53 | 55 |
|
| 56 | +const style = ` |
| 57 | + #container { text-align:center } |
| 58 | +
|
| 59 | + .lds-spinner { display:inline-block; position:relative; width:80px; height:80px } |
| 60 | + .lds-spinner div { transform-origin:40px 40px; animation:lds-spinner 1.2s linear infinite } |
| 61 | + .lds-spinner div:after { |
| 62 | + content:" "; display:block; position:absolute; top:3px; left:37px; |
| 63 | + width:6px; height:18px; border-radius:20%; background:#000; |
| 64 | + } |
| 65 | + .lds-spinner div:nth-child(1) { transform:rotate(0deg); animation-delay:-1.1s } |
| 66 | + .lds-spinner div:nth-child(2) { transform:rotate(30deg); animation-delay:-1s } |
| 67 | + .lds-spinner div:nth-child(3) { transform:rotate(60deg); animation-delay:-0.9s } |
| 68 | + .lds-spinner div:nth-child(4) { transform:rotate(90deg); animation-delay:-0.8s } |
| 69 | + .lds-spinner div:nth-child(5) { transform:rotate(120deg); animation-delay:-0.7s } |
| 70 | + .lds-spinner div:nth-child(6) { transform:rotate(150deg); animation-delay:-0.6s } |
| 71 | + .lds-spinner div:nth-child(7) { transform:rotate(180deg); animation-delay:-0.5s } |
| 72 | + .lds-spinner div:nth-child(8) { transform:rotate(210deg); animation-delay:-0.4s } |
| 73 | + .lds-spinner div:nth-child(9) { transform:rotate(240deg); animation-delay:-0.3s } |
| 74 | + .lds-spinner div:nth-child(10) { transform:rotate(270deg); animation-delay:-0.2s } |
| 75 | + .lds-spinner div:nth-child(11) { transform:rotate(300deg); animation-delay:-0.1s } |
| 76 | + .lds-spinner div:nth-child(12) { transform:rotate(330deg); animation-delay:0s } |
| 77 | + @keyframes lds-spinner { 0% { opacity:1 } 100% { opacity:0 } } |
| 78 | +
|
| 79 | + .continue-message { opacity:0; animation: 1s ease-in 10s 1 normal forwards fade-in; margin-top:1em } |
| 80 | + @keyframes fade-in { from { opacity:0 } to { opacity:1 } } |
| 81 | +`; |
| 82 | +const styleHash = createHash('sha256').update(style).digest('base64'); |
| 83 | + |
54 | 84 | // id=cl only set for playwright. Why can't it locate this anchor in any other way? |
55 | 85 | const loaderTemplate = ` |
56 | 86 | <!doctype html> |
57 | 87 | <html> |
58 | 88 | <head> |
59 | 89 | <title>Loading... | ODK Central</title> |
60 | 90 | <meta http-equiv="refresh" content="0; url={{nextPath}}"> |
61 | | - <style> |
62 | | - #container { text-align:center } |
63 | | -
|
64 | | - .lds-spinner { display:inline-block; position:relative; width:80px; height:80px } |
65 | | - .lds-spinner div { transform-origin:40px 40px; animation:lds-spinner 1.2s linear infinite } |
66 | | - .lds-spinner div:after { |
67 | | - content:" "; display:block; position:absolute; top:3px; left:37px; |
68 | | - width:6px; height:18px; border-radius:20%; background:#000; |
69 | | - } |
70 | | - .lds-spinner div:nth-child(1) { transform:rotate(0deg); animation-delay:-1.1s } |
71 | | - .lds-spinner div:nth-child(2) { transform:rotate(30deg); animation-delay:-1s } |
72 | | - .lds-spinner div:nth-child(3) { transform:rotate(60deg); animation-delay:-0.9s } |
73 | | - .lds-spinner div:nth-child(4) { transform:rotate(90deg); animation-delay:-0.8s } |
74 | | - .lds-spinner div:nth-child(5) { transform:rotate(120deg); animation-delay:-0.7s } |
75 | | - .lds-spinner div:nth-child(6) { transform:rotate(150deg); animation-delay:-0.6s } |
76 | | - .lds-spinner div:nth-child(7) { transform:rotate(180deg); animation-delay:-0.5s } |
77 | | - .lds-spinner div:nth-child(8) { transform:rotate(210deg); animation-delay:-0.4s } |
78 | | - .lds-spinner div:nth-child(9) { transform:rotate(240deg); animation-delay:-0.3s } |
79 | | - .lds-spinner div:nth-child(10) { transform:rotate(270deg); animation-delay:-0.2s } |
80 | | - .lds-spinner div:nth-child(11) { transform:rotate(300deg); animation-delay:-0.1s } |
81 | | - .lds-spinner div:nth-child(12) { transform:rotate(330deg); animation-delay:0s } |
82 | | - @keyframes lds-spinner { 0% { opacity:1 } 100% { opacity:0 } } |
83 | | -
|
84 | | - .continue-message { opacity:0; animation: 1s ease-in 10s 1 normal forwards fade-in; margin-top:1em } |
85 | | - @keyframes fade-in { from { opacity:0 } to { opacity:1 } } |
| 91 | + <style>${style}</style> |
86 | 92 | </style> |
87 | 93 | </head> |
88 | 94 | <body> |
@@ -170,6 +176,7 @@ module.exports = (service, __, anonymousEndpoint) => { |
170 | 176 | // return redirect(303, nextPath); |
171 | 177 | // Instead, we need to render a page and then "browse" from that page to the normal frontend: |
172 | 178 |
|
| 179 | + res.set('Content-Security-Policy', `default-src 'none'; img-src 'self'; style-src-elem 'sha256-${styleHash}'; report-uri /csp-report`); |
173 | 180 | return render(loaderTemplate, { nextPath }); |
174 | 181 | } catch (err) { |
175 | 182 | if (redirect.isRedirect(err)) { |
|
0 commit comments