UNPKG

@xrengine/server-core

Version:

Shared components for XREngine server

149 lines (136 loc) 5.35 kB
import { NotAuthenticated } from '@feathersjs/errors' import { Paginated } from '@feathersjs/feathers/lib' import crypto from 'crypto' import { iff, isProvider } from 'feathers-hooks-common' import { ServerSettingInterface } from '@xrengine/common/src/dbmodels/ServerSetting' import logger from '@xrengine/common/src/logger' import { Application } from '../../../declarations' import authenticate from '../../hooks/authenticate' import setResponseStatusCode from '../../hooks/set-response-status-code' import { getUserRepos } from '../../projects/project/github-helper' import { UnauthorizedException } from '../../util/exceptions/exception' import { GithubRepoAccess } from './github-repo-access.class' import githubRepoAccessDocs from './github-repo-access.docs' import hooks from './github-repo-access.hooks' import createModel from './github-repo-access.model' declare module '@xrengine/common/declarations' { interface ServiceTypes { 'github-repo-access': any 'github-repo-access-webhook': any 'github-repo-access-refresh': any } } const SIG_HEADER_NAME = 'x-hub-signature-256' const SIG_HASH_ALGORITHM = 'sha256' /** * Initialize our service with any options it requires and docs */ export default (app: Application): void => { const options = { Model: createModel(app), paginate: app.get('paginate'), multi: true } const event = new GithubRepoAccess(options, app) event.docs = githubRepoAccessDocs app.use('github-repo-access', event) const service = app.service('github-repo-access') service.hooks(hooks) app.use('github-repo-access-webhook', { create: async (data, params): Promise<string> => { try { const secret = ((await app.service('server-setting').find()) as Paginated<ServerSettingInterface>).data[0] .githubWebhookSecret const sig = Buffer.from(params.headers[SIG_HEADER_NAME] || '', 'utf8') const hmac = crypto.createHmac(SIG_HASH_ALGORITHM, secret) const digest = Buffer.from(SIG_HASH_ALGORITHM + '=' + hmac.update(JSON.stringify(data)).digest('hex'), 'utf8') if (sig.length !== digest.length || !crypto.timingSafeEqual(digest, sig)) { throw new UnauthorizedException('Invalid secret') } const { blocked_user, member, membership } = data const ghUser = member ? member.login : membership ? membership.user.login : blocked_user ? blocked_user.login : null if (!ghUser) return '' const githubIdentityProvider = await app.service('identity-provider').Model.findOne({ where: { type: 'github', accountIdentifier: ghUser } }) if (!githubIdentityProvider) return '' const user = await app.service('user').get(githubIdentityProvider.userId) // GitHub's API doesn't always reflect changes to user repo permissions right when a webhook is sent. // 10 seconds should be more than enough time for the changes to propagate. setTimeout(() => { app.service('github-repo-access-refresh').find({ user }) }, 10000) return '' } catch (err) { console.error(err) throw err } } }) app.service('github-repo-access-webhook').hooks({ after: { create: [setResponseStatusCode(200)] } }) app.use('github-repo-access-refresh', { find: async (params): Promise<void> => { try { const githubIdentityProvider = await (app.service('identity-provider') as any).Model.findOne({ where: { userId: params.user.id, type: 'github' } }) if (githubIdentityProvider) { const existingGithubRepoAccesses = await app.service('github-repo-access').Model.findAll({ paginate: false, where: { identityProviderId: githubIdentityProvider.id } }) const githubRepos = await getUserRepos(githubIdentityProvider.oauthToken) await Promise.all( githubRepos.map(async (repo) => { const matchingAccess = existingGithubRepoAccesses.find((access) => access.repo === repo.html_url) const hasWriteAccess = repo.permissions.admin || repo.permissions.maintain || repo.permissions.push if (!matchingAccess) await app.service('github-repo-access').create({ repo: repo.html_url, identityProviderId: githubIdentityProvider.id, hasWriteAccess }) else await app.service('github-repo-access').patch(matchingAccess.id, { hasWriteAccess }) }) ) const urlsOnly = githubRepos.map((repo) => repo.html_url) await Promise.all( existingGithubRepoAccesses.map(async (repoAccess) => { if (urlsOnly.indexOf(repoAccess.repo) < 0) await app.service('github-repo-access').remove(repoAccess.id) }) ) } return } catch (err) { logger.error(err) throw err } } }) app.service('github-repo-access-refresh').hooks({ before: { find: [iff(isProvider('external'), authenticate() as any)] } }) }