diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..409a14f --- /dev/null +++ b/.env.template @@ -0,0 +1,5 @@ +GITHUB_APP_ID= +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= +GITHUB_OAUTH_CALLBACK_URL= +APP_BASE_URL= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 24a3ebb..705332f 100644 --- a/.gitignore +++ b/.gitignore @@ -81,4 +81,7 @@ typings/ dist qmk_firmware -zmk-config \ No newline at end of file +zmk-config + +private-key.pem +.env \ No newline at end of file diff --git a/api/index.js b/api/index.js index 6077a9e..06749db 100644 --- a/api/index.js +++ b/api/index.js @@ -9,6 +9,7 @@ const app = express() app.use(bodyParser.json()) applicationInit(app) app.use(keyboards) +app.use(require('./routes/github')) module.exports = app diff --git a/api/routes/github.js b/api/routes/github.js new file mode 100644 index 0000000..c668bf2 --- /dev/null +++ b/api/routes/github.js @@ -0,0 +1,83 @@ +const { Router } = require('express') + +const { + getOauthToken, + getOauthUser, + getUserToken, + verifyUserToken, + fetchInstallation, + fetchInstallationRepos, + fetchKeyboardFiles, + createOauthFlowUrl, + createOauthReturnUrl +} = require('../services/github') + +const router = Router() + +const authorize = async (req, res) => { + if (req.query.code) { + const { data: oauth } = await getOauthToken(req.query.code) + const { data: user } = await getOauthUser(oauth.access_token) + const token = getUserToken(oauth, user) + res.redirect(createOauthReturnUrl(token)) + } else { + res.redirect(createOauthFlowUrl()) + } +} + +const authenticate = (req, res, next) => { + const header = req.headers.authorization + const token = (header || '').split(' ')[1] + + if (!token) { + return res.sendStatus(401) + } + + try { + req.user = verifyUserToken(token) + } catch (err) { + console.error('Failed to verify token', err) + return res.sendStatus(401) + } + + next() +} + +const getInstallation = async (req, res) => { + const { user } = req + + try { + const { data: installation } = await fetchInstallation(user.sub) + + if (!installation) { + return res.json({ installation: null }) + } + + const { data: { repositories } } = await fetchInstallationRepos(user.oauth_access_token, installation.id) + + res.json({ installation, repositories }) + } catch (err) { + const message = err.response ? err.response.data : err + console.error(message) + res.status(500).json(message) + } +} + +const getKeyboardFiles = async (req, res) => { + const { installationId, repository } = req.params + + try { + const keyboardFiles = await fetchKeyboardFiles(installationId, repository) + res.json(keyboardFiles) + } catch (err) { + const message = err.response ? err.response.data : err + console.error(message) + res.status(500).json(message) + } +} + +router.get('/github/authorize', authorize) +router.get('/github/installation', authenticate, getInstallation) +router.get('/github/keyboard-files/:installationId/:repository', authenticate, getKeyboardFiles) + +module.exports = router diff --git a/api/services/github/index.js b/api/services/github/index.js new file mode 100644 index 0000000..0c2bf7d --- /dev/null +++ b/api/services/github/index.js @@ -0,0 +1,149 @@ +require('dotenv/config') +const fs = require('fs') +const path = require('path') + +const axios = require('axios') +const jwt = require('jsonwebtoken') + +const pemPath = path.join(__dirname, '..', '..', '..', 'private-key.pem') +const privateKey = fs.readFileSync(pemPath) + +function createAppToken () { + return jwt.sign({ iss: process.env.GITHUB_APP_ID }, privateKey, { + algorithm: 'RS256', + expiresIn: '10m' + }) +} + +function apiRequest (url, token, method='GET') { + const headers = { Accept: 'application/vnd.github.v3+json' } + if (token) { + headers.Authorization = `Bearer ${token}` + } + + return axios({ url, method, headers }) +} + +function createOauthFlowUrl () { + const redirectUrl = new URL('https://github.com/login/oauth/authorize') + + redirectUrl.search = new URLSearchParams({ + client_id: process.env.GITHUB_CLIENT_ID, + redirect_uri: process.env.GITHUB_OAUTH_CALLBACK_URL, + state: 'foo' + }).toString() + + return redirectUrl.toString() +} + +function createOauthReturnUrl (token) { + const url = new URL(process.env.APP_BASE_URL) + url.search = new URLSearchParams({ token }).toString() + return url.toString() +} + +function getUserToken (oauth, user) { + return jwt.sign({ + oauth_access_token: oauth.access_token, + sub: user.login + }, privateKey, { + algorithm: 'RS256' + }) +} + +function verifyUserToken (token) { + return jwt.verify(token, privateKey, { + algorithms: ['RS256'] + }) +} + +function fetchInstallation (user) { + const token = createAppToken() + return axios({ + method: 'GET', + url: `https://api.github.com/users/${user}/installation`, + headers: { + Accept: 'application/vnd.github.v3.raw', + Authorization: `Bearer ${token}` + } + }).catch(err => { + if (err.response && err.response.status === 404) { + return { data: null } + } + + throw err + }) +} + +function fetchInstallationRepos (token, installationId) { + return axios({ + method: 'GET', + url: `https://api.github.com/user/installations/${installationId}/repositories`, + headers: { + Accept: 'application/vnd.github.v3.raw', + Authorization: `Bearer ${token}` + } + }) +} + +function getOauthToken (code) { + return axios({ + method: 'POST', + url: 'https://github.com/login/oauth/access_token', + headers: { + Accept: 'application/json' + }, + data: { + client_id: process.env.GITHUB_CLIENT_ID, + client_secret: process.env.GITHUB_CLIENT_SECRET, + code + } + }) +} + +function getOauthUser (token) { + return axios({ + method: 'GET', + url: 'https://api.github.com/user', + headers: { + Accept: 'application/json', + Authorization: `Bearer ${token}` + } + }) +} + +async function fetchKeyboardFiles (installationId, repository) { + const token = createAppToken() + const accessTokensUrl = `https://api.github.com/app/installations/${installationId}/access_tokens` + const contentsUrl = `https://api.github.com/repos/${repository}/contents` + + const { data: { token: installationToken } } = await apiRequest(accessTokensUrl, token, 'POST') + const { data: info } = await axios({ + url: `${contentsUrl}/config/info.json`, + headers: { + Accept: 'application/vnd.github.v3.raw', + Authorization: `Bearer ${installationToken}` + } + }) + const { data: keymap } = await axios({ + url: `${contentsUrl}/config/keymap.json`, + headers: { + Accept: 'application/vnd.github.v3.raw', + Authorization: `Bearer ${installationToken}` + } + }) + + return { info, keymap } +} + +module.exports = { + createOauthFlowUrl, + createOauthReturnUrl, + getOauthToken, + getOauthUser, + getUserToken, + verifyUserToken, + fetchInstallation, + fetchInstallationRepos, + fetchKeyboardFiles +} diff --git a/application/api.js b/application/api.js index 5774b88..0e88a6b 100644 --- a/application/api.js +++ b/application/api.js @@ -11,17 +11,9 @@ export function loadKeycodes() { export function loadKeymap() { return fetch(`/keymap?firmware=${config.library}`) .then(response => response.json()) - .then(keymap => Object.assign(keymap, { - layer_names: keymap.layer_names || keymap.layers.map((_, i) => `Layer ${i}`) - })) } export function loadLayout() { return fetch(`/layout?firmware=${config.library}`) .then(response => response.json()) - .then(layout => ( - layout.map(key => ( - { ...key, u: key.u || key.w || 1, h: key.h || 1 } - )) - )) } diff --git a/application/main.js b/application/main.js index b94e215..f825da1 100644 --- a/application/main.js +++ b/application/main.js @@ -8,9 +8,70 @@ import App from './components/app.vue' import { loadKeymap } from './keymap.js' import { loadLayout } from './layout.js' +let installation +let repositories + +async function init () { + const token = new URLSearchParams(location.search).get('token') + if (!localStorage.auth_token && token) { + history.replaceState({}, null, '/application') + localStorage.auth_token = token + } + + if (localStorage.auth_token) { + const token = localStorage.auth_token + + const data = await fetch(`http://localhost:8080/github/installation`, { + headers: { + Authorization: `Bearer ${token}` + } + }).then(res => res.json()) + + if (!data.installation) { + console.log('no installation found for authenticated user') + location.href = 'https://github.com/apps/zmk-keymap-editor-dev/installations/new' + } + + installation = data.installation + repositories = data.repositories + + const logout = document.createElement('button') + logout.setAttribute('style', 'display: inline-block; z-index: 100; position: absolute; right: 0px') + logout.textContent = 'Logout' + logout.addEventListener('click', () => { + localStorage.removeItem('auth_token') + location.reload() + }) + + document.body.appendChild(logout) + } else { + const login = document.createElement('button') + login.setAttribute('style', 'display: inline-block; z-index: 100; position: absolute; right: 0px') + login.textContent = 'Login' + login.addEventListener('click', () => { + localStorage.removeItem('auth_token') + location.href = 'http://localhost:8080/github/authorize' + }) + + document.body.appendChild(login) + } +} + async function main() { - const layout = await loadLayout() - const keymap = await loadKeymap() + let layout, keymap + await init() + + if (installation && repositories[0]) { + const data = await fetch( + `http://localhost:8080/github/keyboard-files/${encodeURIComponent(installation.id)}/${encodeURIComponent(repositories[0].full_name)}`, + { headers: { Authorization: `Bearer ${localStorage.auth_token}`} } + ).then(res => res.json()) + layout = data.info.layouts.LAYOUT.layout + keymap = data.keymap + } else { + layout = await loadLayout() + keymap = await loadKeymap() + } const app = Vue.createApp(App) const vm = app.mount('#app') @@ -22,24 +83,16 @@ async function main() { setInterval(() => socket.send('ping'), 10000) - vm.keymap = keymap - vm.layout = layout - vm.layers = keymap.layers - vm.socket = socket - - // document.querySelector('#export').addEventListener('click', () => { - // const keymap = buildKeymap() - // const file = new File([JSON.stringify(keymap, null, 2)], 'default.json', { - // type: 'application/octet-stream' - // }) - - // location.href = URL.createObjectURL(file) - // }) + vm.keymap = Object.assign(keymap, { + layer_names: keymap.layer_names || keymap.layers.map((_, i) => `Layer ${i}`) + }) - // document.querySelector('#compile').addEventListener('click', () => compile()) - // document.querySelector('#flash').addEventListener('click', () => compile({ flash: true })) + vm.layout = layout.map(key => ( + { ...key, u: key.u || key.w || 1, h: key.h || 1 } + )) - // document.querySelector('#toggle').addEventListener('click', () => toggleTerminal()) + vm.layers = keymap.layers + vm.socket = socket } main() diff --git a/package-lock.json b/package-lock.json index af3008a..33ab44f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,12 @@ "hasInstallScript": true, "license": "ISC", "dependencies": { + "axios": "^0.21.4", "body-parser": "^1.19.0", + "dotenv": "^10.0.0", "express": "^4.17.1", - "express-ws": "^4.0.0" + "express-ws": "^4.0.0", + "jsonwebtoken": "^8.5.1" } }, "node_modules/accepts": { @@ -37,6 +40,14 @@ "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" }, + "node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, "node_modules/body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", @@ -57,6 +68,11 @@ "node": ">= 0.8" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "node_modules/bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -118,6 +134,22 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "engines": { + "node": ">=10" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -215,6 +247,25 @@ "node": ">= 0.8" } }, + "node_modules/follow-redirects": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz", + "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -270,6 +321,86 @@ "node": ">= 0.10" } }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -410,6 +541,14 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -538,6 +677,14 @@ "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" }, + "axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "requires": { + "follow-redirects": "^1.14.0" + } + }, "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", @@ -555,6 +702,11 @@ "type-is": "~1.6.17" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -601,6 +753,19 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==" + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -680,6 +845,11 @@ "unpipe": "~1.0.0" } }, + "follow-redirects": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz", + "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==" + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -720,6 +890,84 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -821,6 +1069,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", diff --git a/package.json b/package.json index 8e4850c..fec1393 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,11 @@ "author": "", "license": "ISC", "dependencies": { + "axios": "^0.21.4", "body-parser": "^1.19.0", + "dotenv": "^10.0.0", "express": "^4.17.1", - "express-ws": "^4.0.0" + "express-ws": "^4.0.0", + "jsonwebtoken": "^8.5.1" } }