From 2a9aa204a48f02409f704a57acb7965b40abeff5 Mon Sep 17 00:00:00 2001 From: chime3 Date: Thu, 19 Dec 2024 04:37:22 -0700 Subject: [PATCH] feat: github connector auth --- package.json | 1 + src/providers/github/index.ts | 121 +++++++++++++++++++++++++++++ src/providers/github/interfaces.ts | 11 +++ src/serverconfig.example.json | 8 ++ yarn.lock | 7 ++ 5 files changed, 148 insertions(+) create mode 100644 src/providers/github/index.ts create mode 100644 src/providers/github/interfaces.ts diff --git a/package.json b/package.json index 2bbca8c9..b56e7745 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "officeparser": "^4.1.1", "passport": "^0.5.2", "passport-facebook": "^3.0.0", + "passport-github2": "^0.1.12", "passport-google-oauth20": "^2.0.0", "pdf-parse": "^1.1.1", "sanitize-html": "^2.13.1", diff --git a/src/providers/github/index.ts b/src/providers/github/index.ts new file mode 100644 index 00000000..72d6b4ee --- /dev/null +++ b/src/providers/github/index.ts @@ -0,0 +1,121 @@ +import { Request, Response } from "express"; +import Base from "../BaseProvider"; +import { ConnectionCallbackResponse, PassportProfile } from "../../interfaces"; +import { GithubProviderConfig } from "./interfaces"; + +const passport = require("passport"); +const GitHubStrategy = require("passport-github2"); + +export default class GitHubProvider extends Base { + protected config: GithubProviderConfig; + + public getProviderName() { + return "github"; + } + + public getProviderLabel() { + return "Github"; + } + + public getProviderApplicationUrl() { + return "https://github.com/"; + } + + public syncHandlers(): any[] { + return []; + } + + public getScopes(): string[] { + return ["read:user", "user:email", "repo"]; + } + + public async connect(req: Request, res: Response, next: any): Promise { + this.init(); + + const auth = passport.authenticate("github", { + scope: this.getScopes(), + }); + + return auth(req, res, next); + } + + public async callback(req: Request, res: Response, next: any): Promise { + this.init(); + + return new Promise((resolve, reject) => { + passport.authenticate( + "github", + { + failureRedirect: "/failure/github", + failureMessage: true, + }, + (err: any, user: any) => { + if (err) { + return reject(err); + } + if (!user) { + return reject(new Error("No user data returned from GitHub")); + } + + const profile = this.formatProfile(user.profile); + + resolve({ + id: profile.id, + accessToken: user.accessToken, + refreshToken: user.refreshToken, + profile: { + username: profile.connectionProfile.username, + ...profile, + }, + }); + } + )(req, res, next); + }); + } + + public async getApi(accessToken?: string, refreshToken?: string): Promise { + // Placeholder for GitHub API integration + } + + public init() { + passport.use( + new GitHubStrategy( + { + clientID: this.config.clientId, + clientSecret: this.config.clientSecret, + callbackURL: this.config.callbackUrl, + }, + (accessToken: string, refreshToken: string, profile: any, cb: any) => { + return cb(null, { + accessToken, + refreshToken, + profile, + }); + } + ) + ); + } + + private formatProfile(githubProfile: any): PassportProfile { + const email = githubProfile.emails && githubProfile.emails.length + ? githubProfile.emails[0].value + : null; + + return { + id: githubProfile.id, + provider: this.getProviderName(), + displayName: githubProfile.displayName || email || githubProfile.username, + name: { + familyName: githubProfile.name?.split(" ").slice(-1)[0] || "", + givenName: githubProfile.name?.split(" ").slice(0, -1).join(" ") || "", + }, + photos: githubProfile.photos || [], + connectionProfile: { + username: email ? email.split("@")[0] : githubProfile.username, + readableId: email || githubProfile.username, + email: email, + verified: githubProfile._json?.email_verified || false, + }, + }; + } +} diff --git a/src/providers/github/interfaces.ts b/src/providers/github/interfaces.ts new file mode 100644 index 00000000..94ce2426 --- /dev/null +++ b/src/providers/github/interfaces.ts @@ -0,0 +1,11 @@ +import { BaseHandlerConfig, BaseProviderConfig } from "../../../src/interfaces"; + +export interface GithubProviderConfig extends BaseProviderConfig { + clientId: string; + clientSecret: string; + callbackUrl: string; +} + +export interface GithubHandlerConfig extends BaseHandlerConfig { + batchSize: number +} \ No newline at end of file diff --git a/src/serverconfig.example.json b/src/serverconfig.example.json index a94f74a3..1e553233 100644 --- a/src/serverconfig.example.json +++ b/src/serverconfig.example.json @@ -129,6 +129,14 @@ "messagesPerGroupLimit": 100, "maxGroupSize": 50, "useDbPos": true + }, + "github": { + "status": "active", + "label": "Github", + "clientId": "", + "clientSecret": "", + "batchSize": 50, + "maxSyncLoops": 1 } }, "providerDefaults": { diff --git a/yarn.lock b/yarn.lock index 486e0afb..a5282d7f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6737,6 +6737,13 @@ passport-facebook@^3.0.0: dependencies: passport-oauth2 "1.x.x" +passport-github2@^0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/passport-github2/-/passport-github2-0.1.12.tgz#a72ebff4fa52a35bc2c71122dcf470d1116f772c" + integrity sha512-3nPUCc7ttF/3HSP/k9sAXjz3SkGv5Nki84I05kSQPo01Jqq1NzJACgMblCK0fGcv9pKCG/KXU3AJRDGLqHLoIw== + dependencies: + passport-oauth2 "1.x.x" + passport-google-oauth20@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz#0d241b2d21ebd3dc7f2b60669ec4d587e3a674ef"