-
Notifications
You must be signed in to change notification settings - Fork 330
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fetch layout and keymap from a github linked github repository
- Loading branch information
1 parent
25fd989
commit 4a111c2
Showing
9 changed files
with
571 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
GITHUB_APP_ID= | ||
GITHUB_CLIENT_ID= | ||
GITHUB_CLIENT_SECRET= | ||
GITHUB_OAUTH_CALLBACK_URL= | ||
APP_BASE_URL= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -81,4 +81,7 @@ typings/ | |
|
||
dist | ||
qmk_firmware | ||
zmk-config | ||
zmk-config | ||
|
||
private-key.pem | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.