mirror of
https://github.com/EnixCoda/Gitako.git
synced 2026-03-11 08:54:44 +00:00
feat: server
This commit is contained in:
parent
a34489d8c9
commit
3bbac6a6a8
11 changed files with 281 additions and 2 deletions
1
server/.gitignore
vendored
Normal file
1
server/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.now
|
||||
26
server/api/gitee.ts
Normal file
26
server/api/gitee.ts
Normal 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
31
server/api/github.ts
Normal 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
36
server/api/index.ts
Normal 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
21
server/api/redirect.ts
Normal 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
15
server/now.json
Normal 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
13
server/package.json
Normal 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
64
server/tsconfig.json
Normal 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
71
server/yarn.lock
Normal 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==
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue