├── .env.example ├── .gitignore ├── vercel.json ├── package.json ├── README.md ├── LICENSE.md └── api └── server.js /.env.example: -------------------------------------------------------------------------------- 1 | CLIENT_ID=replace-me 2 | CLIENT_SECRET=replace-me -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | package-lock.json 4 | 5 | .vercel 6 | .env -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://openapi.vercel.sh/vercel.json", 3 | "rewrites": [{ "source": "/(.*)", "destination": "/api/server" }] 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "release-auth", 4 | "repository": "vercel/release-auth", 5 | "license": "MIT", 6 | "scripts": {}, 7 | "dependencies": { 8 | "node-fetch": "^2.6.7" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # release-auth 2 | 3 | Handles the OAuth authentication with GitHub's webflow for [release](https://github.com/vercel/release). 4 | 5 | ## Contributing 6 | 7 | 1. Create a new OAuth application in [your GitHub account](https://github.com/settings/developers) 8 | 1. Set the "Authorization callback URL" to `https://release-auth.vercel.sh` 9 | 1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device 10 | 1. Install the dependencies: `yarn install` 11 | 1. Download environment variables: `vc env pull` 12 | 1. Start the server: `vercel dev` 13 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Vercel, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /api/server.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch'); 2 | 3 | const client_id = process.env.CLIENT_ID; 4 | const client_secret = process.env.CLIENT_SECRET; 5 | 6 | if (!client_id || !client_secret) { 7 | console.error('Please define the necessary env variables.'); 8 | process.exit(1); 9 | } 10 | 11 | const tokens = new Map(); 12 | 13 | export default async function handler(req, res) { 14 | const keyCount = Object.keys(req.query).length; 15 | if (keyCount < 1 || !req.query.state) { 16 | return res.status(502).json({ 17 | error: 'invalid_request', 18 | }); 19 | } 20 | 21 | if (req.query.code && req.query.state) { 22 | const token = await requestToken(req.query.code, req.query.state); 23 | tokens.set(String(req.query.state), Buffer.from(token)); 24 | 25 | return res.status(200).end('Done. You can close this window now!'); 26 | } 27 | 28 | if (req.query.state) { 29 | const ID = req.query.state; 30 | 31 | if (tokens.has(ID)) { 32 | res.status(200).json({ 33 | token: tokens.get(ID).toString(), 34 | }); 35 | 36 | // Securily wipe token from RAM 37 | tokens.get(ID).fill(0); 38 | tokens.delete(ID); 39 | 40 | return; 41 | } 42 | 43 | return res.status(403).json({ 44 | error: 'state_not_defined', 45 | }); 46 | } 47 | 48 | return res.status(502).json({ 49 | error: 'invalid_request', 50 | }); 51 | } 52 | 53 | const requestToken = async (code, state) => { 54 | if (!code || !state) { 55 | return false; 56 | } 57 | 58 | try { 59 | const response = await fetch( 60 | 'https://github.com/login/oauth/access_token', 61 | { 62 | method: 'POST', 63 | body: JSON.stringify({ 64 | code, 65 | state, 66 | client_id, 67 | client_secret, 68 | }), 69 | headers: { 70 | Accept: 'application/json', 71 | 'Content-Type': 'application/json', 72 | }, 73 | } 74 | ); 75 | 76 | const data = await response.json(); 77 | return data.access_token; 78 | } catch (error) { 79 | console.error(error); 80 | return false; 81 | } 82 | }; 83 | --------------------------------------------------------------------------------