feat: server

This commit is contained in:
EnixCoda 2020-04-09 15:03:00 +08:00
parent a34489d8c9
commit 3bbac6a6a8
No known key found for this signature in database
GPG key ID: FE06F5DFC1C9B8E4
11 changed files with 281 additions and 2 deletions

1
server/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.now

26
server/api/gitee.ts Normal file
View file

@ -0,0 +1,26 @@
import fetch from 'node-fetch'
import { createCodeHandler } from '.'
const { GITEE_OAUTH_CLIENT_ID, GITEE_OAUTH_CLIENT_SECRET } = process.env
async function oauth(code: string) {
const params = new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_id: GITEE_OAUTH_CLIENT_ID,
client_secret: GITEE_OAUTH_CLIENT_SECRET,
redirect_uri: 'https://gitako.now.sh/redirect/',
})
const res = await fetch('https://gitee.com/oauth/token?' + params.toString(), {
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
redirect: 'follow',
method: 'post',
})
return res.json()
}
export default createCodeHandler(oauth)

31
server/api/github.ts Normal file
View file

@ -0,0 +1,31 @@
import fetch from 'node-fetch'
import { createCodeHandler } from '.'
const { GITHUB_OAUTH_CLIENT_ID, GITHUB_OAUTH_CLIENT_SECRET } = process.env
async function oauth(code: string) {
const res = await fetch('https://github.com/login/oauth/access_token', {
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
redirect: 'follow',
method: 'post',
body: JSON.stringify({
code,
client_id: GITHUB_OAUTH_CLIENT_ID,
client_secret: GITHUB_OAUTH_CLIENT_SECRET,
}),
})
const body = await res.json()
const { access_token: accessToken, scope, error_description: errorDescription } = body
if (errorDescription) {
throw new Error(errorDescription)
} else if (scope !== 'repo' || !accessToken || !(typeof accessToken === 'string')) {
throw new Error(`Cannot resolve response: '${JSON.stringify(res)}'`)
}
return accessToken
}
export default createCodeHandler(oauth)

36
server/api/index.ts Normal file
View file

@ -0,0 +1,36 @@
import { NowRequest, NowResponse } from '@now/node'
export function createCodeHandler(oauthHandler: (code: string) => string | Promise<string>) {
return async function handleCode(request: NowRequest, response: NowResponse) {
const { code } = request.query
try {
setCORSHeaders(response)
if (!request.method || request.method.toLowerCase() !== 'post') {
return sendRejection(response, 405)
}
if (!code || typeof code !== 'string') {
return sendRejection(response, 403)
}
const accessToken = await oauthHandler(code)
writeJSON(response, { accessToken })
response.end()
} catch (err) {
return sendRejection(response, 400, err instanceof Error ? err.message : '')
}
}
}
function setCORSHeaders(response: NowResponse) {
response.setHeader('Access-Control-Allow-Origin', '*')
response.setHeader('Access-Control-Allow-Methods', 'POST')
}
export function sendRejection(response: NowResponse, status = 400, content?: string) {
response.writeHead(status)
response.end(content)
}
function writeJSON(response: NowResponse, data: unknown) {
response.setHeader('Content-Type', 'application/json')
response.write(JSON.stringify(data))
}

21
server/api/redirect.ts Normal file
View file

@ -0,0 +1,21 @@
import { NowRequest, NowResponse } from '@now/node'
import { sendRejection } from './index'
export default async function handleRedirect(request: NowRequest, response: NowResponse) {
const { redirect, ...params } = request.query
if (typeof redirect !== 'string' || !redirect) {
return sendRejection(response)
}
const url = new URL(redirect)
for (const key of Object.keys(params)) {
const value = params[key]
if (Array.isArray(value)) {
for (const v of value) url.searchParams.append(key, v)
} else url.searchParams.append(key, value)
}
response.writeHead(307, { Location: url.href })
response.end()
}

15
server/now.json Normal file
View file

@ -0,0 +1,15 @@
{
"version": 2,
"env": {
"GITHUB_OAUTH_CLIENT_SECRET": "@gitako-github-oauth-client-secret",
"GITHUB_OAUTH_CLIENT_ID": "@gitako-github-oauth-client-id",
"GITEE_OAUTH_CLIENT_SECRET": "@gitako-gitee-oauth-client-secret",
"GITEE_OAUTH_CLIENT_ID": "@gitako-gitee-oauth-client-id"
},
"alias": "gitako.now.sh",
"routes": [
{ "src": "/redirect/?", "dest": "/api/redirect.ts" },
{ "src": "/oauth/github/?", "dest": "/api/github.ts" },
{ "src": "/oauth/gitee/?", "dest": "/api/gitee.ts" }
]
}

13
server/package.json Normal file
View file

@ -0,0 +1,13 @@
{
"name": "gitako-server",
"version": "0.1.0",
"main": "api/index.ts",
"author": "Enix",
"license": "MIT",
"dependencies": {
"@now/node": "^1.5.0",
"@types/node-fetch": "^2.5.5",
"node-fetch": "^2.6.0",
"typescript": "^3.8.3"
}
}

64
server/tsconfig.json Normal file
View file

@ -0,0 +1,64 @@
{
"compilerOptions": {
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "ESNext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
"module": "CommonJS" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
"sourceMap": true /* Generates corresponding '.map' file. */,
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./build" /* Redirect output structure to the directory. */,
"rootDir": "./" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
},
"include": ["src"]
}

71
server/yarn.lock Normal file
View file

@ -0,0 +1,71 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@now/node@^1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@now/node/-/node-1.5.0.tgz#a159772276ae11acf87cf2620a86204abff38f1c"
integrity sha512-0MChNIBHsViTMPDsifTveWmMpUSUbVQYAHg9Qa8p7uF7pkfBAghyCGHSKHJVg3zxx0vVE4z+rYgaRS6xvF8qMA==
dependencies:
"@types/node" "*"
"@types/node-fetch@^2.5.5":
version "2.5.5"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.5.tgz#cd264e20a81f4600a6c52864d38e7fef72485e92"
integrity sha512-IWwjsyYjGw+em3xTvWVQi5MgYKbRs0du57klfTaZkv/B24AEQ/p/IopNeqIYNy3EsfHOpg8ieQSDomPcsYMHpA==
dependencies:
"@types/node" "*"
form-data "^3.0.0"
"@types/node@*":
version "13.11.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.0.tgz#390ea202539c61c8fa6ba4428b57e05bc36dc47b"
integrity sha512-uM4mnmsIIPK/yeO+42F2RQhGUIs39K2RFmugcJANppXe6J1nvH87PvzPZYpza7Xhhs8Yn9yIAVdLZ84z61+0xQ==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
form-data@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682"
integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
mime-db@1.43.0:
version "1.43.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==
mime-types@^2.1.12:
version "2.1.26"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06"
integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==
dependencies:
mime-db "1.43.0"
node-fetch@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
typescript@^3.8.3:
version "3.8.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061"
integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==

View file

@ -7,7 +7,6 @@ export const GITHUB_OAUTH = {
export const GITEE_OAUTH = {
clientId: process.env.GITEE_OAUTH_CLIENT_ID || '',
clientSecret: process.env.GITEE_OAUTH_CLIENT_SECRET || '',
}
export const VERSION = process.env.VERSION

View file

@ -143,7 +143,9 @@ export const Gitee: Platform = {
client_id: GITEE_OAUTH.clientId,
scope: 'projects',
response_type: 'code',
redirect_uri: window.location.href,
redirect_uri:
'https://gitako.now.sh/redirect/?' +
new URLSearchParams({ redirect: window.location.href }).toString(),
})
return `https://gitee.com/oauth/authorize?` + params.toString()
},