From 0cd9d88874e19dd0dbbf3041e37af6287d74c4c5 Mon Sep 17 00:00:00 2001 From: djdefi Date: Tue, 10 Jan 2023 10:13:44 -0800 Subject: [PATCH 1/7] Create codeql.yml --- .github/workflows/codeql.yml | 76 ++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..399315f60 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,76 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '25 1 * * 4' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go', 'java', 'javascript', 'python', 'ruby' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Use only 'java' to analyze code written in Java, Kotlin or both + # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" From e4b9e0b4b73a0d0032dbb2b048b7f4567a05dcc9 Mon Sep 17 00:00:00 2001 From: djdefi Date: Tue, 10 Jan 2023 11:13:44 -0800 Subject: [PATCH 2/7] Update codeql.yml --- .github/workflows/codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 399315f60..3cc97205d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -32,7 +32,7 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'go', 'java', 'javascript', 'python', 'ruby' ] + language: [ 'go', 'javascript', 'python', 'ruby' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Use only 'java' to analyze code written in Java, Kotlin or both # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both From b2cdb1ca3df1a687d2aa41754f5175f22a386748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Sto=CC=88lzle?= Date: Wed, 11 Jan 2023 07:18:34 +0100 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=A9=B9=20Fix=20https=20in=20Gemfiles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes some issues from https://github.com/github/platform-samples/pull/494/checks?check_run_id=10560404570 --- api/ruby/basics-of-authentication/Gemfile | 6 +++--- api/ruby/building-a-ci-server/Gemfile | 6 +++--- api/ruby/building-your-first-github-app/Gemfile | 8 ++++---- api/ruby/delivering-deployments/Gemfile | 6 +++--- api/ruby/discovering-resources-for-a-user/Gemfile | 2 +- api/ruby/rendering-data-as-graphs/Gemfile | 4 ++-- api/ruby/traversing-with-pagination/Gemfile | 2 +- api/ruby/working-with-comments/Gemfile | 2 +- app/ruby/app-issue-creator/Gemfile | 10 +++++----- hooks/ruby/configuring-your-server/Gemfile | 4 ++-- hooks/ruby/delete-repository-event/Gemfile | 2 +- hooks/ruby/dismiss-review-server/Gemfile | 6 +++--- 12 files changed, 29 insertions(+), 29 deletions(-) diff --git a/api/ruby/basics-of-authentication/Gemfile b/api/ruby/basics-of-authentication/Gemfile index 4f35dd6b0..668c484ce 100644 --- a/api/ruby/basics-of-authentication/Gemfile +++ b/api/ruby/basics-of-authentication/Gemfile @@ -1,4 +1,4 @@ -source "http://rubygems.org" +source "https://rubygems.org" -gem 'sinatra', '~> 1.3.5' -gem 'rest-client', '~> 1.8.0' +gem "rest-client", "~> 1.8.0" +gem "sinatra", "~> 1.3.5" diff --git a/api/ruby/building-a-ci-server/Gemfile b/api/ruby/building-a-ci-server/Gemfile index de581d11f..d0dcec578 100644 --- a/api/ruby/building-a-ci-server/Gemfile +++ b/api/ruby/building-a-ci-server/Gemfile @@ -1,6 +1,6 @@ -source "http://rubygems.org" +source "https://rubygems.org" gem "json", "~> 1.8" -gem 'sinatra', '~> 1.3.5' +gem "octokit", "~> 3.0" gem "shotgun" -gem "octokit", '~> 3.0' +gem "sinatra", "~> 1.3.5" diff --git a/api/ruby/building-your-first-github-app/Gemfile b/api/ruby/building-your-first-github-app/Gemfile index 0799a52c5..4dd74a6f6 100644 --- a/api/ruby/building-your-first-github-app/Gemfile +++ b/api/ruby/building-your-first-github-app/Gemfile @@ -1,5 +1,5 @@ -source 'http://rubygems.org' +source "http://rubygems.org" -gem 'sinatra', '~> 2.0' -gem 'jwt', '~> 2.1' -gem 'octokit', '~> 4.0' +gem "jwt", "~> 2.1" +gem "octokit", "~> 4.0" +gem "sinatra", "~> 2.0" diff --git a/api/ruby/delivering-deployments/Gemfile b/api/ruby/delivering-deployments/Gemfile index de581d11f..d0dcec578 100644 --- a/api/ruby/delivering-deployments/Gemfile +++ b/api/ruby/delivering-deployments/Gemfile @@ -1,6 +1,6 @@ -source "http://rubygems.org" +source "https://rubygems.org" gem "json", "~> 1.8" -gem 'sinatra', '~> 1.3.5' +gem "octokit", "~> 3.0" gem "shotgun" -gem "octokit", '~> 3.0' +gem "sinatra", "~> 1.3.5" diff --git a/api/ruby/discovering-resources-for-a-user/Gemfile b/api/ruby/discovering-resources-for-a-user/Gemfile index 051fafde6..aa83b8403 100644 --- a/api/ruby/discovering-resources-for-a-user/Gemfile +++ b/api/ruby/discovering-resources-for-a-user/Gemfile @@ -1,3 +1,3 @@ -source "http://rubygems.org" +source "https://rubygems.org" gem "octokit", "~> 3.0" diff --git a/api/ruby/rendering-data-as-graphs/Gemfile b/api/ruby/rendering-data-as-graphs/Gemfile index 4145eb9fc..7273f1d67 100644 --- a/api/ruby/rendering-data-as-graphs/Gemfile +++ b/api/ruby/rendering-data-as-graphs/Gemfile @@ -1,6 +1,6 @@ -source "http://rubygems.org" +source "https://rubygems.org" gem "json", "~>2.1.0" +gem "octokit", "~>4.7.0" gem "sinatra", "~>1.4.8" gem "sinatra_auth_github", "~>1.2.0" -gem "octokit", "~>4.7.0" diff --git a/api/ruby/traversing-with-pagination/Gemfile b/api/ruby/traversing-with-pagination/Gemfile index ccb6b85b9..9c212a3aa 100644 --- a/api/ruby/traversing-with-pagination/Gemfile +++ b/api/ruby/traversing-with-pagination/Gemfile @@ -1,3 +1,3 @@ -source "http://rubygems.org" +source "https://rubygems.org" gem "octokit", "~> 2.0" diff --git a/api/ruby/working-with-comments/Gemfile b/api/ruby/working-with-comments/Gemfile index ccb6b85b9..9c212a3aa 100644 --- a/api/ruby/working-with-comments/Gemfile +++ b/api/ruby/working-with-comments/Gemfile @@ -1,3 +1,3 @@ -source "http://rubygems.org" +source "https://rubygems.org" gem "octokit", "~> 2.0" diff --git a/app/ruby/app-issue-creator/Gemfile b/app/ruby/app-issue-creator/Gemfile index 466da8fbf..584928125 100644 --- a/app/ruby/app-issue-creator/Gemfile +++ b/app/ruby/app-issue-creator/Gemfile @@ -1,7 +1,7 @@ -source "http://rubygems.org" +source "https://rubygems.org" +gem "activesupport", "~> 5.0" gem "json", "~> 1.8" -gem 'sinatra', '~> 1.3.5' -gem 'octokit' -gem 'jwt' -gem 'activesupport', '~> 5.0' +gem "jwt" +gem "octokit" +gem "sinatra", "~> 1.3.5" diff --git a/hooks/ruby/configuring-your-server/Gemfile b/hooks/ruby/configuring-your-server/Gemfile index eeb447ba1..3d43923a6 100644 --- a/hooks/ruby/configuring-your-server/Gemfile +++ b/hooks/ruby/configuring-your-server/Gemfile @@ -1,4 +1,4 @@ -source "http://rubygems.org" +source "https://rubygems.org" gem "json", "~> 1.8" -gem 'sinatra', '~> 1.3.5' +gem "sinatra", "~> 1.3.5" diff --git a/hooks/ruby/delete-repository-event/Gemfile b/hooks/ruby/delete-repository-event/Gemfile index cd1af99ef..5c45f5da1 100644 --- a/hooks/ruby/delete-repository-event/Gemfile +++ b/hooks/ruby/delete-repository-event/Gemfile @@ -1,4 +1,4 @@ source "https://rubygems.org" -gem "sinatra" gem "octokit" +gem "sinatra" diff --git a/hooks/ruby/dismiss-review-server/Gemfile b/hooks/ruby/dismiss-review-server/Gemfile index ba5d027cf..aebc5ca2d 100644 --- a/hooks/ruby/dismiss-review-server/Gemfile +++ b/hooks/ruby/dismiss-review-server/Gemfile @@ -1,5 +1,5 @@ -source "http://rubygems.org" +source "https://rubygems.org" gem "json", "~> 1.8" -gem 'sinatra', '~> 1.3.5' -gem 'rest-client' +gem "rest-client" +gem "sinatra", "~> 1.3.5" From 7b60fd153c085a7f5b31812973362bcb609d1377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Sto=CC=88lzle?= Date: Wed, 11 Jan 2023 07:23:19 +0100 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=A9=B9=20Disable=20debug=20mode=20for?= =?UTF-8?q?=20flask?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hooks/python/flask-github-webhooks/webhooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/python/flask-github-webhooks/webhooks.py b/hooks/python/flask-github-webhooks/webhooks.py index 52085df3c..b19c56afe 100644 --- a/hooks/python/flask-github-webhooks/webhooks.py +++ b/hooks/python/flask-github-webhooks/webhooks.py @@ -170,4 +170,4 @@ def index(): if __name__ == '__main__': - application.run(debug=True, host='0.0.0.0') + application.run(debug=False, host='0.0.0.0') From f13a09cfc94d6289cfb5bdc70ad32fd2fb5319df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Sto=CC=88lzle?= Date: Wed, 11 Jan 2023 07:23:49 +0100 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=A9=B9=20Fix=20more=20https=20in=20Ge?= =?UTF-8?q?mfiles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/ruby/building-your-first-github-app/Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/ruby/building-your-first-github-app/Gemfile b/api/ruby/building-your-first-github-app/Gemfile index 4dd74a6f6..7861f67d7 100644 --- a/api/ruby/building-your-first-github-app/Gemfile +++ b/api/ruby/building-your-first-github-app/Gemfile @@ -1,4 +1,4 @@ -source "http://rubygems.org" +source "https://rubygems.org" gem "jwt", "~> 2.1" gem "octokit", "~> 4.0" From 84536ab3f87daff633b18c948a7031e41bcb5ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Sto=CC=88lzle?= Date: Wed, 11 Jan 2023 07:34:34 +0100 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=A9=B9=20Attempt=20to=20fix=20JS=20se?= =?UTF-8?q?rver-side=20request=20forgery?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/javascript/search/server.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/api/javascript/search/server.js b/api/javascript/search/server.js index 01cff2f30..84d4d5ac2 100644 --- a/api/javascript/search/server.js +++ b/api/javascript/search/server.js @@ -65,19 +65,23 @@ class Server { } }); + app.set('query parser', 'simple'); + app.get('/search/:query', async (req, res) => { - res.send(await this.searchQuery(req.params.query)); + const query = String(req.params.query) + + res.send(await this.searchQuery(query)) }); - + app.get('/state', async (req, res) => { res.send(await this.getState()); }); - + app.post('/hooks', (req, res) => { res.send(200); }); } - + // We could filter out the properties that we don't want the frontend to have async getState() { await this.refreshState(); From 7f83def6c06206e86de2700e73f34284d3fe1527 Mon Sep 17 00:00:00 2001 From: djdefi Date: Sat, 14 Jan 2023 09:39:16 -0800 Subject: [PATCH 7/7] Delete api/javascript/search directory --- api/javascript/search/.env | 11 - api/javascript/search/README.md | 44 ---- api/javascript/search/package.json | 28 --- api/javascript/search/public/client.js | 109 --------- api/javascript/search/public/index.html | 86 ------- api/javascript/search/server.js | 290 ------------------------ 6 files changed, 568 deletions(-) delete mode 100644 api/javascript/search/.env delete mode 100644 api/javascript/search/README.md delete mode 100644 api/javascript/search/package.json delete mode 100644 api/javascript/search/public/client.js delete mode 100644 api/javascript/search/public/index.html delete mode 100644 api/javascript/search/server.js diff --git a/api/javascript/search/.env b/api/javascript/search/.env deleted file mode 100644 index 6006cce64..000000000 --- a/api/javascript/search/.env +++ /dev/null @@ -1,11 +0,0 @@ -GLITCH_DEBUGGER=true -# Environment Config - -# reference these in your code with process.env.SECRET - -GH_APP_ID= -GH_CLIENT_ID= -GH_CLIENT_SECRET= -INSTALLATION_ID= - -# note: .env is a shell file so there can't be spaces around = diff --git a/api/javascript/search/README.md b/api/javascript/search/README.md deleted file mode 100644 index c38928c3b..000000000 --- a/api/javascript/search/README.md +++ /dev/null @@ -1,44 +0,0 @@ -GitHub Search API demo -================= - -This project employs several authentication strategies to avoid rate limiting while using the GitHub Search API: -1. Using each user's OAuth access token, if available -- this will allow you a maximum of [30 requests per-user / per-minute](https://developer.github.com/v3/search/#rate-limit) -2. Falling back to a server-to-server token, associated with a given installation of your GitHub App -- this will allow you a maximum of [30 requests per-organization / per-minute](https://developer.github.com/v3/search/#rate-limit) -3. Falling back again to simplified functionality, such as validating a given GitHub username, via GET /users/:username -- this will allow you a minimum of [5000 requests per-organization / per-hour](https://developer.github.com/apps/building-github-apps/understanding-rate-limits-for-github-apps/) - -Step 1a: Prereqs via [Glitch](https://glitch.com/~github-search-api) ------------ - -* Remix this app :) - -Step 1b: Prereqs locally ------------ -* Install `node` from [the website](https://nodejs.org/en/) or [Homebrew](https://brew.sh/) -* `git clone` the project -* Navigate to the project directory and install dependencies using `npm i` - -Step 2: App creation and variable-setting ------------ -* Create a new [GitHub App](https://developer.github.com/apps/building-github-apps/creating-a-github-app/). - * Homepage URL = `` - * User authorization callback URL = `/authorized` - * Webhook URL (unused) = `/hooks` - * Download your private key at the bottom of the app settings page. -* Make a new file in Glitch called `.data/pem` and paste the contents of the private key. -* Set the following variables in your Glitch `.env` file: - * `GH_CLIENT_ID` Client ID on app settings page - * `GH_CLIENT_SECRET` Client secret on app settings page - * `GH_APP_ID` App ID on app settings page - * `INSTALLATION_ID` Installation ID, which you can retrieve from [here](https://developer.github.com/v3/apps/installations/#installations) - -Step 3a: Running via Glitch ------------ -* Navigate to your URL for live-reloaded goodness - -Step 3b: Running locally ------------ -* `npm start` - -FYI ------------ -* This app is single-user (for now). It stores the OAuth token in a file found at `.data/oauth`. diff --git a/api/javascript/search/package.json b/api/javascript/search/package.json deleted file mode 100644 index 735120a0f..000000000 --- a/api/javascript/search/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "github-search-api", - "version": "0.0.1", - "description": "Demo of the GitHub Search API, using several authentication strategies to avoid rate limits.", - "main": "server.js", - "scripts": { - "start": "node server.js" - }, - "dependencies": { - "express": "^4.16.4", - "node-fetch": "^2.5.0", - "node-localstorage": "^1.3.1", - "@octokit/app": "^1.1.0", - "@octokit/request": "^2.2.0" - }, - "engines": { - "node": "8.x" - }, - "repository": { - "url": "https://github-search-api.glitch.me/" - }, - "license": "MIT", - "keywords": [ - "node", - "glitch", - "express" - ] -} \ No newline at end of file diff --git a/api/javascript/search/public/client.js b/api/javascript/search/public/client.js deleted file mode 100644 index 02e6e5b02..000000000 --- a/api/javascript/search/public/client.js +++ /dev/null @@ -1,109 +0,0 @@ -const searchInput = document.querySelector('.search-input'); -const searchResults = document.querySelector('.search-results'); -const searchError = document.querySelector('.search-error'); -const searchButton = document.querySelector('.search'); -const login = document.querySelector('.login'); -const loginButton = document.querySelector('.login-button'); -const loginText = document.querySelector('.login-text'); -const authType = document.querySelector('.auth-type'); -const authTarget = document.querySelector('.auth-target'); -const hitsRemaining = document.querySelector('.hits-remaining'); -const hitsTotal = document.querySelector('.hits-total'); -const scheme = document.querySelector('.scheme'); - -let localState = {}; - -// TODO change from javascript handler to
-loginButton && loginButton.addEventListener('click', (evt) => { - - window.location = `https://github.com/login/oauth/authorize?scope=repo&client_id=${localState.clientId}&state=${localState.oAuthState}`; - console.log(localState.clientId); - console.log(localState.oAuthState); -}); - -searchInput && searchInput.addEventListener('input', (evt) => { - const val = evt.target.value; - if (!val) { - searchResults.innerHTML = ''; - searchError.hidden = true; - } -}); - -searchButton && searchButton.addEventListener('click', (user) => { - if (searchInput.value === '') return; - searchResults.innerHTML = ''; - searchError.hidden = true; - search() - .then(data => data.json()) - .then(showResults) - .then(syncState) - .catch(err => { - searchError.innerHTML = 'Error encountered while searching.' - searchError.hidden = false; - }); -}); - -function search() { - return fetch(`/search/${searchInput.value}`, { - headers: { - "Content-Type": "application/json", - } - }); -}; - -function showResults(results) { - // just one result from User API - if (!results.items && !results.items.length) { - if (results.login) { - searchResults.innerHTML = `This user was found on GitHub`; - } - else { - searchResults.innerHTML = 'This user could not be found on GitHub.'; - } - } - // array of results from Search API - else if (results.items.length) { - results.items.forEach(createRow); - } -} - -function createRow(result) { - let node = document.createElement('li'); - let text = document.createTextNode(result.login) - node.appendChild(text); - searchResults.appendChild(node); -} - -function updateUI() { - authType.innerHTML = localState.authType; - authTarget.innerHTML = localState.authTarget; - hitsRemaining.innerHTML = `(${localState.rateLimitRemaining} /`; - hitsTotal.innerHTML = ` ${localState.rateLimitTotal})`; - - if (localState.oAuthToken) { - loginText.innerHTML = 'Logged in.'; - loginButton.disabled = true; - } - - if (localState.rateLimitRemaining) { - scheme.hidden = false; - } -} - -function syncState() { - fetch(`/state`) - .then(data => data.json()) - .then(remoteState => { - localState = remoteState; - updateUI(); - }); -} - -// this executes immediately -(() => { - // await this.getRateLimits(this.getQueryAuthToken()); - scheme.hidden = true; - syncState(); -})(); - - diff --git a/api/javascript/search/public/index.html b/api/javascript/search/public/index.html deleted file mode 100644 index 1da3ad01e..000000000 --- a/api/javascript/search/public/index.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - GitHub Search API - - - - - - - - - - - - -
- -

- Try searching for a GitHub user: -

- -
- - - - -
- -

- -
- - - -
- -
    - -
    - -
    -
    -

    - You are using authentication against the API. -

    -

    - -

    -
    -
    - - - -
    - - - diff --git a/api/javascript/search/server.js b/api/javascript/search/server.js deleted file mode 100644 index 84d4d5ac2..000000000 --- a/api/javascript/search/server.js +++ /dev/null @@ -1,290 +0,0 @@ -const express = require('express'); -const app = express(); -const fetch = require('node-fetch'); -const LocalStorage = require('node-localstorage').LocalStorage; -const localStorage = new LocalStorage('./.data'); -const fs = require('fs'); -const OctokitApp = require('@octokit/app'); -const request = require('@octokit/request'); - -class Server { - - constructor() { - this.basicStr = 'basic'; - this.oAuthStr = 'OAuth'; - this.serverStr = 'Server-to-Server'; - this.searchStr = 'Search'; - this.userStr = 'User'; - this.state = { - authType: '', // || 'OAuth' || 'Server-to-Server' - authTarget: this.searchStr, // || 'User' - clientId: process.env.GH_CLIENT_ID, - oAuthToken: localStorage.getItem('oauth'), - oAuthState: String(Math.random() * 1000000), - rateLimitRemaining: '', - rateLimitTotal: '', - rateResetDate: '', - serverToken: '' - }; - - this.startup(); - this.api(); - } - - startup() { - app.use(express.static('public')); - - // listen for requests :) - const listener = app.listen(process.env.PORT, function() { - console.log('Your app is listening on port ' + listener.address().port); - }); - } - - api() { - app.get('/', async (req, res) => { - res.send(await this.getState()); - }); - - // redirected here via GitHub - app.get('/authorized', async (req, res) => { - // ensure input/output states are equal - if (req.query.state !== this.state.oAuthState) { - res.status(500).send('error'); - } - else { - // OAuth flow Step 2 https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/#2-users-are-redirected-back-to-your-site-by-github - this.getOAuthToken(req) - .then(data => data.json()) - .then(data => { - this.state.oAuthToken = data.access_token; - localStorage.setItem('oauth', data.access_token); - }) - .then(async () => { - res.status(200).redirect('/'); - }); - } - }); - - app.set('query parser', 'simple'); - - app.get('/search/:query', async (req, res) => { - const query = String(req.params.query) - - res.send(await this.searchQuery(query)) - }); - - app.get('/state', async (req, res) => { - res.send(await this.getState()); - }); - - app.post('/hooks', (req, res) => { - res.send(200); - }); - } - - // We could filter out the properties that we don't want the frontend to have - async getState() { - await this.refreshState(); - return this.state; - } - - getOAuthToken(req) { - const body = { - client_id: process.env.GH_CLIENT_ID, - client_secret: process.env.GH_CLIENT_SECRET, - code: req.query.code - }; - return fetch(`https://github.com/login/oauth/access_token`, { - method: 'post', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: JSON.stringify(body) - }); - } - - checkStatus(res) { - if (res.ok) { // res.status >= 200 && res.status < 300 - return res; - } else { - return Promise.reject(res.status); - } - } - - async getServerToken(req) { - const pem = this.getPem(); - const app = new OctokitApp({ id: process.env.GH_APP_ID, privateKey: pem }); - const jwt = app.getSignedJsonWebToken(); - const installationAccessToken = await this.getInstallationAccessToken(jwt, process.env.INSTALLATION_ID); - return installationAccessToken.token; - } - - async getRateLimits(authStr) { - return fetch(`https://api.github.com/rate_limit`, { - headers: { - 'Accept': 'application/json', - 'Authorization': authStr - } - }) - .then(this.checkStatus) - .then(data => data.json()) - .catch((err) => this.errGetRateLimits(err, authStr)); - } - - async getInstallationToken(jwt) { - return fetch(`https://api.github.com/app/installations`, { - method: 'get', - headers: { - 'Accept': 'application/vnd.github.machine-man-preview+json', - 'Authorization': `Bearer ${jwt}` - } - }) - .then(this.checkStatus) - .then(data => data.json()) - .catch(err => { - Promise.reject(err); - }); - } - - async getInstallationAccessToken(jwt, installationToken) { - return fetch(`https://api.github.com/app/installations/${installationToken}/access_tokens`, { - method: 'post', - headers: { - 'Accept': 'application/vnd.github.machine-man-preview+json', - 'Authorization': `Bearer ${jwt}` - } - }) - .then(this.checkStatus) - .then(data => data.json()) - .catch(err => { - Promise.reject(err); - }); - } - - async searchQuery(query) { - await this.refreshState(); - const authStr = this.getQueryAuthToken(this.state.authType); - return this.runQuery(query, authStr); - } - - async runQuery(query, authStr) { - let searchStr = ''; - - if (this.state.authTarget === this.searchStr) { - searchStr = `https://api.github.com/search/users?q=${query}` - } - else if (this.state.authTarget === this.userStr) { - searchStr = `https://api.github.com/users/${query}`; - } - - return fetch(searchStr, { - method: 'get', - headers: { - 'Accept': 'application/json', - 'Authorization': authStr - } - }) - .then(this.checkStatus) - .then(data => { - this.setRateLimits(data) - return data; - }) - .then(data => data.json()); - } - - // Prefer hitting Search API w/ OAuth, then server to server, then basic authentication - async refreshState() { - if (await this.isOAuthAvailable()) { - this.state.authType = this.oAuthStr; - this.state.authTarget = this.searchStr; - } - else if (await this.isServerToServerAvailable()) { - await this.chooseServerToServerAPI(); - } - else { - this.state.authType = this.basicStr; - this.state.authTarget = this.searchStr; - } - } - - async isOAuthAvailable() { - // do we have a token - let haveToken = !!this.state.oAuthToken; - - // what are our current rate limits - const rateLimit = haveToken ? await this.getRateLimits(this.getQueryAuthToken(this.oAuthStr)) : undefined; - - // have we run out of tries - const haveMoreTries = !!rateLimit ? rateLimit.resources.search.remaining > 0 : false; - - return haveToken && haveMoreTries; - } - - async isServerToServerAvailable() { - // get a server token or use the existing one - this.state.serverToken = this.state.serverToken ? this.state.serverToken : await this.getServerToken(); - - return !!this.state.serverToken; - } - - async chooseServerToServerAPI() { - // what are our current rate limits - const rateLimit = await this.getRateLimits(this.getQueryAuthToken(this.serverStr)); - - // have we run out of tries - const haveMoreSearchAPITries = !!rateLimit ? rateLimit.resources.search.remaining > 0 : false; - const haveMoreUserAPITries = !!rateLimit ? rateLimit.resources.core.remaining > 0 : false; - - if (haveMoreSearchAPITries) { - this.state.authType = this.serverStr; - this.state.authTarget = this.searchStr; - } - else if (haveMoreUserAPITries) { - this.state.authType = this.serverStr; - this.state.authTarget = this.userStr; - } - } - - errGetRateLimits(err, authStr) { - console.log(`Encountered ${err} while getting rate limits`); - console.trace(); - if (err === 401) { - if (authStr.indexOf(this.state.oAuthToken) >= 0) { - this.state.oAuthToken = ''; - localStorage.removeItem('oauth'); - } - if (authStr.indexOf(this.state.serverToken) >= 0) { - this.state.serverToken = ''; - } - } - } - - setRateLimits(rateLimits) { - if (rateLimits) { - this.state.rateLimitRemaining = rateLimits.headers.get('x-ratelimit-remaining'); - this.state.rateLimitTotal = rateLimits.headers.get('x-ratelimit-limit'); - this.state.rateResetDate = new Date(+rateLimits.headers.get('x-ratelimit-reset') * 1000); - } - } - - getQueryAuthToken(authType) { - let token = ''; - - // prefer OAuth - if (authType === this.oAuthStr) { - token = `token ${this.state.oAuthToken}`; - } - else if (authType === this.serverStr) { - token = `Bearer ${this.state.serverToken}`; - } - - return token; - } - - getPem() { - return fs.readFileSync('.data/key.pem', 'utf8'); - } -} - -const server = new Server(); \ No newline at end of file