Skip to content

Commit

Permalink
feat: implement security via UtahID/Firebase
Browse files Browse the repository at this point in the history
Closes #67
  • Loading branch information
stdavis committed Mar 28, 2023
1 parent 286e248 commit 610a730
Show file tree
Hide file tree
Showing 23 changed files with 11,262 additions and 5,617 deletions.
3 changes: 3 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
QUAD_WORD=
API_KEY=
FIREBASE_CONFIG=
6 changes: 6 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,11 @@ jobs:
service-account-email: ${{ secrets.SERVICE_ACCOUNT_EMAIL }}
project-id: ${{ secrets.PROJECT_ID }}
preview: yes
prebuild-command: |
cd functions
echo "ENVIRONMENT=stage" > .env
echo "AGS_HOST=https://wrimaps.at.utah.gov" >> .env
build-command: npm run build:stage
repo-token: ${{ secrets.GITHUB_TOKEN }}
env:
FIREBASE_CONFIG: ${{ secrets.FIREBASE_CONFIG }}
16 changes: 14 additions & 2 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,15 @@ jobs:
identity-provider: ${{ secrets.IDENTITY_PROVIDER }}
service-account-email: ${{ secrets.SERVICE_ACCOUNT_EMAIL }}
project-id: ${{ secrets.PROJECT_ID }}
prebuild-command: npx grunt bump --setversion=${{ needs.release.outputs.released_version }}
prebuild-command: |
npx grunt bump --setversion=${{ needs.release.outputs.released_version }}
cd functions
echo "ENVIRONMENT=stage" > .env
echo "AGS_HOST=https://wrimaps.at.utah.gov" >> .env
build-command: npm run build:stage
repo-token: ${{ secrets.GITHUB_TOKEN }}
env:
FIREBASE_CONFIG: ${{ secrets.FIREBASE_CONFIG }}

deploy-prod:
name: Deploy to production
Expand All @@ -71,7 +77,11 @@ jobs:
with:
identity-provider: ${{ secrets.IDENTITY_PROVIDER }}
service-account-email: ${{ secrets.SERVICE_ACCOUNT_EMAIL }}
prebuild-command: npx grunt bump --setversion=${{ needs.release.outputs.released_version }}
prebuild-command: |
npx grunt bump --setversion=${{ needs.release.outputs.released_version }}
cd functions
echo "ENVIRONMENT=prod" > .env
echo "AGS_HOST=https://wrimaps.utah.gov" >> .env
project-id: ${{ secrets.PROJECT_ID }}
build-command: npm run build:prod
service-now-instance: ${{ secrets.SN_INSTANCE }}
Expand All @@ -80,3 +90,5 @@ jobs:
service-now-username: ${{ secrets.SN_USERNAME }}
service-now-password: ${{ secrets.SN_PASSWORD }}
repo-token: ${{ secrets.GITHUB_TOKEN }}
env:
FIREBASE_CONFIG: ${{ secrets.FIREBASE_CONFIG }}
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,11 @@ scripts/DATABASE.sde
# pro project
maps/maps.gdb
maps/scratch
maps/Index
maps/Index

.env
functions/.secret.local

# firebase
*-debug.log
emulator_data/
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@
"cSpell.words": [
"ABES",
"autoplay",
"firestore",
"fishsample",
"fullscreen",
"hostingchannels",
"mapservice",
"nonwritable",
"nosniff",
"oidc",
"outfile",
"prebuild",
"SAMEORIGIN",
"setversion",
"UDWR",
"udwrgis",
"utahid",
"WILDADMIN",
"wrimaps"
]
Expand Down
95 changes: 60 additions & 35 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,26 @@ module.exports = function (grunt) {
options: {
sourceMap: true,
presets: ['latest'],
plugins: ['transform-remove-strict-mode']
plugins: [
'transform-remove-strict-mode',
['transform-inline-environment-variables', {
include: [
'API_KEY',
'FIREBASE_CONFIG',
'QUAD_WORD'
]
}]
]
},
src: {
files: [{
expand: true,
cwd: '_src/app/',
src: ['**/*.js'],
dest: 'src/app/'
}]
files: [
{
expand: true,
cwd: '_src/app/',
src: ['**/*.js'],
dest: 'src/app/'
}
]
}
},
bump: {
Expand All @@ -49,12 +60,14 @@ module.exports = function (grunt) {
},
copy: {
dist: {
files: [{
expand: true,
cwd: 'src/',
src: ['*.html'],
dest: 'dist/'
}]
files: [
{
expand: true,
cwd: 'src/',
src: ['*.html'],
dest: 'dist/'
}
]
},
src: {
expand: true,
Expand All @@ -66,12 +79,18 @@ module.exports = function (grunt) {
dojo: {
prod: {
options: {
profiles: ['profiles/prod.build.profile.js', 'profiles/build.profile.js']
profiles: [
'profiles/prod.build.profile.js',
'profiles/build.profile.js'
]
}
},
stage: {
options: {
profiles: ['profiles/stage.build.profile.js', 'profiles/build.profile.js']
profiles: [
'profiles/stage.build.profile.js',
'profiles/build.profile.js'
]
}
},
options: {
Expand All @@ -92,13 +111,15 @@ module.exports = function (grunt) {
options: {
optimizationLevel: 3
},
files: [{
expand: true,
cwd: 'src/',
// exclude tests because some images in dojox throw errors
src: ['**/*.{png,jpg,gif}', '!**/tests/**/*.*'],
dest: 'src/'
}]
files: [
{
expand: true,
cwd: 'src/',
// exclude tests because some images in dojox throw errors
src: ['**/*.{png,jpg,gif}', '!**/tests/**/*.*'],
dest: 'src/'
}
]
}
},
jasmine: {
Expand Down Expand Up @@ -132,13 +153,15 @@ module.exports = function (grunt) {
compress: false,
'resolve url': true
},
files: [{
expand: true,
cwd: '_src/',
src: ['app/resources/App.styl'],
dest: 'src/',
ext: '.css'
}]
files: [
{
expand: true,
cwd: '_src/',
src: ['app/resources/App.styl'],
dest: 'src/',
ext: '.css'
}
]
}
},
uglify: {
Expand All @@ -161,12 +184,14 @@ module.exports = function (grunt) {
dest: 'dist/dojo/dojo.js'
},
prod: {
files: [{
expand: true,
cwd: 'dist',
src: ['**/*.js', '!proj4/**/*.js'],
dest: 'dist'
}]
files: [
{
expand: true,
cwd: 'dist',
src: ['**/*.js', '!proj4/**/*.js'],
dest: 'dist'
}
]
}
},
watch: {
Expand Down
2 changes: 0 additions & 2 deletions _SpecRunner.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@

<script src=".grunt/grunt-contrib-jasmine/json2.js"></script>

<script src=".grunt/grunt-contrib-jasmine/boot.js"></script>

<script src="src/app/tests/jasmineTestBootstrap.js"></script>

<script src="src/dojo/dojo.js"></script>
Expand Down
131 changes: 131 additions & 0 deletions _src/ServiceWorker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/* eslint-disable no-restricted-globals */

// this is copied from https://github.com/agrc/electrofishing

// inspired by: https://firebase.google.com/docs/auth/web/service-worker-sessions
import { initializeApp } from 'firebase/app';
import { getAuth, getIdToken, onAuthStateChanged } from 'firebase/auth';

initializeApp(process.env.FIREBASE_CONFIG);

const auth = getAuth();

const onError = () => {
self.clients.matchAll().then((matchedClients) => {
matchedClients.forEach((client) => {
client.postMessage({ type: 'idTokenError' });
});
});
};

const getIdTokenPromise = () => {
if (!auth.currentUser) {
return new Promise((resolve) => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
unsubscribe();
getIdToken(user).then(
(idToken) => {
resolve(idToken);
},
(error) => {
console.error(
'Error getting ID token after auth state change',
error
);
onError();
resolve(null);
}
);
} else {
console.error('No user is signed in.');
}
});
});
}

return getIdToken(auth.currentUser).catch((error) => {
console.log('error getting initial id token', error);

onError(error);
});
};

self.addEventListener('install', (event) => {
console.log(`service worker install at: ${new Date().toLocaleTimeString()}`);

event.waitUntil(self.skipWaiting()); // don't wait for any previous workers to finish
});

self.addEventListener('activate', (event) => {
console.log(`service worker activate at: ${new Date().toLocaleTimeString()}`);
// eslint-disable-next-line no-undef
event.waitUntil(self.clients.claim()); // immediately begin to catch fetch events without a page reload
});

// Get underlying body if available. Works for text and json bodies.
const getBodyContent = (req) => {
return Promise.resolve()
.then(() => {
if (req.method !== 'GET') {
if (req.headers.get('Content-Type').indexOf('json') !== -1) {
return req.json().then((json) => {
return JSON.stringify(json);
});
}

return req.text();
}
})
.catch(() => {
// Ignore error.
});
};

self.addEventListener('fetch', (event) => {
/** @type {FetchEvent} */
const evt = event;

const requestProcessor = (idToken) => {
let req = evt.request;
let processRequestPromise = Promise.resolve();
// Clone headers as request headers are immutable.
const headers = new Headers();
req.headers.forEach((val, key) => {
headers.append(key, val);
});
// Add ID token to header.
headers.append('Authorization', 'Bearer ' + idToken);
processRequestPromise = getBodyContent(req).then((body) => {
try {
req = new Request(req.url, {
method: req.method,
headers: headers,
cache: req.cache,
redirect: req.redirect,
referrer: req.referrer,
body
});
} catch (e) {
// This will fail for CORS requests. We just continue with the
// fetch caching logic below and do not pass the ID token.
}
});

return processRequestPromise.then(() => {
return fetch(req);
});
};

if (event.request.url.includes('/maps/')) {
return evt.respondWith(
getIdTokenPromise().then(requestProcessor, requestProcessor)
);
}

return event.respondWith(fetch(event.request));
});

console.log(
`service worker initialized at: ${new Date().toLocaleTimeString()}`
);
Loading

0 comments on commit 610a730

Please sign in to comment.