Compare commits
	
		
			1 Commits
		
	
	
		
			main
			...
			ng-gitea-o
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c96385f40c | 
| @ -17,6 +17,11 @@ | ||||
|     clientId = "" | ||||
|     clientSecret = "" | ||||
| 
 | ||||
| [gitea] | ||||
|   [gitea.oAuth] | ||||
|     clientId = "" | ||||
|     clientSecret = "" | ||||
| 
 | ||||
| [registryConfig] | ||||
|   fetchDeploymentRecordDelay = 5000 | ||||
|   restEndpoint = "http://localhost:1317" | ||||
|  | ||||
| @ -21,6 +21,7 @@ | ||||
|     "luxon": "^3.4.4", | ||||
|     "nanoid": "3", | ||||
|     "nanoid-dictionary": "^5.0.0-beta.1", | ||||
|     "node-fetch": "2", | ||||
|     "octokit": "^3.1.2", | ||||
|     "reflect-metadata": "^0.2.1", | ||||
|     "semver": "^7.6.0", | ||||
| @ -46,6 +47,7 @@ | ||||
|   "devDependencies": { | ||||
|     "@types/express-session": "^1.17.10", | ||||
|     "@types/fs-extra": "^11.0.4", | ||||
|     "@types/node-fetch": "^2.6.11", | ||||
|     "@typescript-eslint/eslint-plugin": "^6.18.1", | ||||
|     "@typescript-eslint/parser": "^6.18.1", | ||||
|     "better-sqlite3": "^9.2.2", | ||||
|  | ||||
| @ -6,6 +6,7 @@ import { Permission } from './entity/ProjectMember'; | ||||
| import { Domain } from './entity/Domain'; | ||||
| import { Project } from './entity/Project'; | ||||
| import { EnvironmentVariable } from './entity/EnvironmentVariable'; | ||||
| import { GitType } from './types'; | ||||
| 
 | ||||
| const log = debug('snowball:resolver'); | ||||
| 
 | ||||
| @ -206,6 +207,15 @@ export const createResolvers = async (service: Service): Promise<any> => { | ||||
|         } | ||||
|       }, | ||||
| 
 | ||||
|       authenticateGit: async (_: any, { type, code }: { type: GitType, code: string }, context: any) => { | ||||
|         try { | ||||
|           return await service.authenticateGit(type, code, context.user); | ||||
|         } catch (err) { | ||||
|           log(err); | ||||
|           return false; | ||||
|         } | ||||
|       }, | ||||
| 
 | ||||
|       unauthenticateGitHub: async (_: any, __: object, context: any) => { | ||||
|         try { | ||||
|           return service.unauthenticateGitHub(context.user, { gitHubToken: null }); | ||||
|  | ||||
| @ -26,6 +26,11 @@ enum DomainStatus { | ||||
|   Pending | ||||
| } | ||||
| 
 | ||||
| enum GitType { | ||||
|   GitHub | ||||
|   Gitea | ||||
| } | ||||
| 
 | ||||
| type User { | ||||
|   id: String! | ||||
|   name: String | ||||
| @ -203,5 +208,6 @@ type Mutation { | ||||
|   addDomain(projectId: String!, data: AddDomainInput!): Boolean! | ||||
|   updateDomain(domainId: String!, data: UpdateDomainInput!): Boolean! | ||||
|   authenticateGitHub(code: String!): AuthResult! | ||||
|   authenticateGit(type: GitType!, code: String!): AuthResult! | ||||
|   unauthenticateGitHub: Boolean! | ||||
| } | ||||
|  | ||||
| @ -2,6 +2,7 @@ import assert from 'assert'; | ||||
| import debug from 'debug'; | ||||
| import { DeepPartial, FindOptionsWhere } from 'typeorm'; | ||||
| import { Octokit, RequestError } from 'octokit'; | ||||
| import fetch from 'node-fetch'; | ||||
| 
 | ||||
| import { OAuthApp } from '@octokit/oauth-app'; | ||||
| 
 | ||||
| @ -15,12 +16,13 @@ import { Permission, ProjectMember } from './entity/ProjectMember'; | ||||
| import { User } from './entity/User'; | ||||
| import { Registry } from './registry'; | ||||
| import { GitHubConfig, RegistryConfig } from './config'; | ||||
| import { AppDeploymentRecord, GitPushEventPayload, PackageJSON } from './types'; | ||||
| import { AppDeploymentRecord, GitPushEventPayload, GitType, PackageJSON } from './types'; | ||||
| import { Role } from './entity/UserOrganization'; | ||||
| 
 | ||||
| const log = debug('snowball:service'); | ||||
| 
 | ||||
| const GITHUB_UNIQUE_WEBHOOK_ERROR = 'Hook already exists on this repository'; | ||||
| const GITEA_ACCESS_TOKEN_ENDPOINT = 'https://git.vdb.to/login/oauth/access_token'; | ||||
| 
 | ||||
| interface Config { | ||||
|   gitHubConfig: GitHubConfig | ||||
| @ -701,6 +703,48 @@ export class Service { | ||||
|     return { token }; | ||||
|   } | ||||
| 
 | ||||
|   async authenticateGit (type: GitType, code:string, user: User): Promise<{token: string}> { | ||||
|     let token: string; | ||||
| 
 | ||||
|     switch (type) { | ||||
|       case GitType.GitHub: | ||||
|         ({ authentication: { token } } = await this.oauthApp.createToken({ | ||||
|           code | ||||
|         })); | ||||
| 
 | ||||
|         break; | ||||
| 
 | ||||
|       case GitType.Gitea: { | ||||
|         const response = await fetch(GITEA_ACCESS_TOKEN_ENDPOINT, { | ||||
|           method: 'post', | ||||
|           body: JSON.stringify({ | ||||
|             // TODO: Fetch from config
 | ||||
|             client_id: '', | ||||
|             client_secret: '', | ||||
|             code, | ||||
|             grant_type: 'authorization_code', | ||||
|             // TODO: Get frontend app URL from config
 | ||||
|             redirect_uri: 'http://localhost:3000/organization/projects/create' | ||||
|           }), | ||||
|           headers: { 'Content-Type': 'application/json' } | ||||
|         }); | ||||
| 
 | ||||
|         assert(response.ok, `HTTP Error Response: ${response.status} ${response.statusText}`); | ||||
|         const data: any = await response.json(); | ||||
|         ({ access_token: token } = data); | ||||
| 
 | ||||
|         break; | ||||
|       } | ||||
| 
 | ||||
|       default: throw new Error(`Type ${type} not handled for Git authentication`); | ||||
|     } | ||||
| 
 | ||||
|     assert(token, `Access token is not set for type ${type}`); | ||||
|     await this.db.updateUser(user, { gitHubToken: token }); | ||||
| 
 | ||||
|     return { token }; | ||||
|   } | ||||
| 
 | ||||
|   async unauthenticateGitHub (user: User, data: DeepPartial<User>): Promise<boolean> { | ||||
|     return this.db.updateUser(user, data); | ||||
|   } | ||||
|  | ||||
| @ -49,3 +49,8 @@ interface RegistryRecord { | ||||
| export interface AppDeploymentRecord extends RegistryRecord { | ||||
|   attributes: AppDeploymentRecordAttributes | ||||
| } | ||||
| 
 | ||||
| export enum GitType { | ||||
|   GitHub = 'GitHub', | ||||
|   Gitea = 'Gitea', | ||||
| } | ||||
|  | ||||
| @ -3,4 +3,6 @@ REACT_APP_SERVER_URL = 'http://localhost:8000' | ||||
| REACT_APP_GITHUB_CLIENT_ID = | ||||
| REACT_APP_GITHUB_TEMPLATE_REPO = | ||||
| 
 | ||||
| REACT_APP_GITEA_CLIENT_ID = | ||||
| 
 | ||||
| REACT_APP_WALLET_CONNECT_ID = | ||||
|  | ||||
| @ -1,15 +1,21 @@ | ||||
| import { Button } from '@material-tailwind/react'; | ||||
| import React from 'react'; | ||||
| import OauthPopup from 'react-oauth-popup'; | ||||
| import { GitType } from 'gql-client'; | ||||
| 
 | ||||
| import { Button } from '@material-tailwind/react'; | ||||
| 
 | ||||
| import { useGQLClient } from '../../../context/GQLClientContext'; | ||||
| import ConnectAccountTabPanel from './ConnectAccountTabPanel'; | ||||
| 
 | ||||
| const SCOPES = 'repo user'; | ||||
| 
 | ||||
| const GITHUB_OAUTH_URL = `https://github.com/login/oauth/authorize?client_id=${ | ||||
|   process.env.REACT_APP_GITHUB_CLIENT_ID | ||||
| }&scope=${encodeURIComponent(SCOPES)}`;
 | ||||
| 
 | ||||
| const REDIRECT_URI = `${window.location.origin}/organization/projects/create`; | ||||
| const GITEA_OAUTH_URL = `https://git.vdb.to/login/oauth/authorize?client_id=${process.env.REACT_APP_GITEA_CLIENT_ID}&redirect_uri=${REDIRECT_URI}&response_type=code`; | ||||
| 
 | ||||
| interface ConnectAccountInterface { | ||||
|   onAuth: (token: string) => void; | ||||
| } | ||||
| @ -17,11 +23,13 @@ interface ConnectAccountInterface { | ||||
| const ConnectAccount = ({ onAuth: onToken }: ConnectAccountInterface) => { | ||||
|   const client = useGQLClient(); | ||||
| 
 | ||||
|   const handleCode = async (code: string) => { | ||||
|   const handleCode = async (type: GitType, code: string) => { | ||||
|     // Pass code to backend and get access token
 | ||||
|     const { | ||||
|       authenticateGitHub: { token }, | ||||
|     } = await client.authenticateGitHub(code); | ||||
|       authenticateGit: { token }, | ||||
|     } = await client.authenticateGit(type, code); | ||||
| 
 | ||||
|     // TODO: Handle token according to Git type
 | ||||
|     onToken(token); | ||||
|   }; | ||||
| 
 | ||||
| @ -40,7 +48,7 @@ const ConnectAccount = ({ onAuth: onToken }: ConnectAccountInterface) => { | ||||
|       <div className="mt-2 flex"> | ||||
|         <OauthPopup | ||||
|           url={GITHUB_OAUTH_URL} | ||||
|           onCode={handleCode} | ||||
|           onCode={(code) => handleCode(GitType.GitHub, code)} | ||||
|           onClose={() => {}} | ||||
|           title="Snowball" | ||||
|           width={1000} | ||||
| @ -48,7 +56,16 @@ const ConnectAccount = ({ onAuth: onToken }: ConnectAccountInterface) => { | ||||
|         > | ||||
|           <Button className="rounded-full mx-2">Connect to Github</Button> | ||||
|         </OauthPopup> | ||||
|         <Button className="rounded-full mx-2">Connect to Gitea</Button> | ||||
|         <OauthPopup | ||||
|           url={GITEA_OAUTH_URL} | ||||
|           onCode={(code) => handleCode(GitType.Gitea, code)} | ||||
|           onClose={() => {}} | ||||
|           title="Snowball" | ||||
|           width={1000} | ||||
|           height={1000} | ||||
|         > | ||||
|           <Button className="rounded-full mx-2">Connect to Gitea</Button> | ||||
|         </OauthPopup> | ||||
|       </div> | ||||
|       <ConnectAccountTabPanel /> | ||||
|     </div> | ||||
|  | ||||
| @ -311,6 +311,18 @@ export class GQLClient { | ||||
|     return data; | ||||
|   } | ||||
| 
 | ||||
|   async authenticateGit (type: types.GitType, code: string): Promise<types.AuthenticateGitResponse> { | ||||
|     const { data } = await this.client.mutate({ | ||||
|       mutation: mutations.authenticateGit, | ||||
|       variables: { | ||||
|         type, | ||||
|         code | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     return data; | ||||
|   } | ||||
| 
 | ||||
|   async unauthenticateGithub (): Promise<types.UnauthenticateGitHubResponse> { | ||||
|     const { data } = await this.client.mutate({ | ||||
|       mutation: mutations.unauthenticateGitHub | ||||
|  | ||||
| @ -95,6 +95,13 @@ mutation ($code: String!) { | ||||
|   } | ||||
| }`;
 | ||||
| 
 | ||||
| export const authenticateGit = gql` | ||||
| mutation ($type: GitType!, $code: String!) { | ||||
|   authenticateGit(type: $type, code: $code) { | ||||
|     token | ||||
|   } | ||||
| }`;
 | ||||
| 
 | ||||
| export const unauthenticateGitHub = gql` | ||||
| mutation { | ||||
|   unauthenticateGitHub | ||||
|  | ||||
| @ -28,6 +28,11 @@ export enum DomainStatus { | ||||
|   Pending = 'Pending', | ||||
| } | ||||
| 
 | ||||
| export enum GitType { | ||||
|   GitHub = 'GitHub', | ||||
|   Gitea = 'Gitea', | ||||
| } | ||||
| 
 | ||||
| export type EnvironmentVariable = { | ||||
|   id: string | ||||
|   environment: Environment | ||||
| @ -288,6 +293,12 @@ export type AuthenticateGitHubResponse = { | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export type AuthenticateGitResponse = { | ||||
|   authenticateGit: { | ||||
|     token: string | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export type UnauthenticateGitHubResponse = { | ||||
|   unauthenticateGitHub: boolean | ||||
| } | ||||
|  | ||||
							
								
								
									
										22
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								yarn.lock
									
									
									
									
									
								
							| @ -4717,6 +4717,14 @@ | ||||
|   resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433" | ||||
|   integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== | ||||
| 
 | ||||
| "@types/node-fetch@^2.6.11": | ||||
|   version "2.6.11" | ||||
|   resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.11.tgz#9b39b78665dae0e82a08f02f4967d62c66f95d24" | ||||
|   integrity sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g== | ||||
|   dependencies: | ||||
|     "@types/node" "*" | ||||
|     form-data "^4.0.0" | ||||
| 
 | ||||
| "@types/node-forge@^1.3.0": | ||||
|   version "1.3.10" | ||||
|   resolved "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.10.tgz" | ||||
| @ -13550,6 +13558,13 @@ node-fetch-native@^1.4.0, node-fetch-native@^1.4.1, node-fetch-native@^1.6.1: | ||||
|   resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.2.tgz#f439000d972eb0c8a741b65dcda412322955e1c6" | ||||
|   integrity sha512-69mtXOFZ6hSkYiXAVB5SqaRvrbITC/NPyqv7yuu/qw0nmgPyYbIMYYNIDhNtwPrzk0ptrimrLz/hhjvm4w5Z+w== | ||||
| 
 | ||||
| node-fetch@2, node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.6.7: | ||||
|   version "2.7.0" | ||||
|   resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" | ||||
|   integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== | ||||
|   dependencies: | ||||
|     whatwg-url "^5.0.0" | ||||
| 
 | ||||
| node-fetch@2.6.7: | ||||
|   version "2.6.7" | ||||
|   resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" | ||||
| @ -13557,13 +13572,6 @@ node-fetch@2.6.7: | ||||
|   dependencies: | ||||
|     whatwg-url "^5.0.0" | ||||
| 
 | ||||
| node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.6.7: | ||||
|   version "2.7.0" | ||||
|   resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" | ||||
|   integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== | ||||
|   dependencies: | ||||
|     whatwg-url "^5.0.0" | ||||
| 
 | ||||
| node-forge@^1, node-forge@^1.3.1: | ||||
|   version "1.3.1" | ||||
|   resolved "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz" | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user