Skip to content

Commit

Permalink
Merge branch 'google_abstraction' of github.com:gitcoinco/passport in…
Browse files Browse the repository at this point in the history
…to google_abstraction
  • Loading branch information
nutrina committed Nov 18, 2022
2 parents ca7ac7f + e3c830c commit d03f7fe
Show file tree
Hide file tree
Showing 10 changed files with 587 additions and 6 deletions.
7 changes: 3 additions & 4 deletions iam/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,12 @@ import {
GtcStaking,
Discord,
Google,
ClearText,
} from "@gitcoin/passport-platforms";

import { BrightIdProvider } from "./providers/brightid";

import { ClearTextSimpleProvider } from "./providers/clearTextSimple";
import { ClearTextTwitterProvider } from "./providers/clearTextTwitter";
import { ClearTextGithubOrgProvider } from "./providers/clearTextGithubOrg";

// get DID from key
const key = process.env.IAM_JWK || DIDKit.generateEd25519Key();
Expand Down Expand Up @@ -101,6 +100,8 @@ export const providers = new Providers([
new Github.FiftyOrMoreGithubFollowers(),
new Github.ForkedGithubRepoProvider(),
new Github.StarredGithubRepoProvider(),
new ClearText.ClearTextGithubOrgProvider(),
new ClearText.ClearTextTwitterProvider(),
new Linkedin.LinkedinProvider(),
new Discord.DiscordProvider(),
new Twitter.TwitterTweetGT10Provider(),
Expand All @@ -115,8 +116,6 @@ export const providers = new Providers([
new GtcStaking.CommunityStakingSilverProvider(),
new GtcStaking.CommunityStakingGoldProvider(),
new ClearTextSimpleProvider(),
new ClearTextTwitterProvider(),
new ClearTextGithubOrgProvider(),
new Snapshot.SnapshotProposalsProvider(),
new Snapshot.SnapshotVotesProvider(),
new ETH.EthGasProvider(),
Expand Down
162 changes: 162 additions & 0 deletions platforms/src/ClearText/Providers/clearTextGithubOrg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// ----- Types
import type { RequestPayload, VerifiedPayload } from "@gitcoin/passport-types";
import type { Provider, ProviderOptions } from "../../types";
import axios from "axios";

export type GithubTokenResponse = {
access_token: string;
};

export type GithubFindMyUserResponse = {
id?: string;
login?: string;
type?: string;
};

export enum ClientType {
GrantHub,
}

export type GHUserRequestPayload = RequestPayload & {
requestedClient: ClientType;
org?: string;
};

type GithubMyOrg = {
providedOrg: string;
matchingOrg: string;
};

type GHVerification = {
validOrg: GithubMyOrg;
id: number;
};

// Export a Github Provider to carry out OAuth and return a record object
export class ClearTextGithubOrgProvider implements Provider {
// Give the provider a type so that we can select it with a payload
type = "ClearTextGithubOrg";

// Options can be set here and/or via the constructor
_options = {};

// construct the provider instance with supplied options
constructor(options: ProviderOptions = {}) {
this._options = { ...this._options, ...options };
}

// verify that the Github user is a memeber of the provided organization
async verify(payload: GHUserRequestPayload): Promise<VerifiedPayload> {
let valid = false,
ghVerification: GHVerification,
pii;
console.log({ payload });
try {
ghVerification = await verifyGithub(payload.proofs.code, payload.org, payload.requestedClient);
} catch (e) {
return { valid: false };
} finally {
const validOrg = ghVerification?.validOrg;
pii = validOrg ? `${validOrg.matchingOrg}#${ghVerification.id}` : "";
valid = validOrg && validOrg.matchingOrg === validOrg.providedOrg;
}

return {
valid: valid,
record: {
pii,
},
};
}
}

type GithubUserResponse = {
status: number;
data: {
login: string;
id: number;
};
};

type Organization = {
login: string;
};

type GithubUserOrgResponse = {
status: number;
data: Organization[];
};

const verifyOrg = (data: Organization[], providedOrg: string): GithubMyOrg => {
const orgs = data;
const matchingOrgs = orgs.filter((org) => org.login === providedOrg);

if (matchingOrgs.length !== 1) {
throw `${providedOrg} not found in user profile`;
}
return {
providedOrg,
matchingOrg: matchingOrgs[0].login,
};
};

const requestAccessToken = async (code: string, requestedClient: ClientType): Promise<string> => {
const clientId =
requestedClient === ClientType.GrantHub ? process.env.GRANT_HUB_GITHUB_CLIENT_ID : process.env.GITHUB_CLIENT_ID;
const clientSecret =
requestedClient === ClientType.GrantHub
? process.env.GRANT_HUB_GITHUB_CLIENT_SECRET
: process.env.GITHUB_CLIENT_SECRET;

// Exchange the code for an access token
const tokenRequest = await axios.post(
`https://github.com/login/oauth/access_token?client_id=${clientId}&client_secret=${clientSecret}&code=${code}`,
{},
{
headers: { Accept: "application/json" },
}
);

if (tokenRequest.status != 200) {
throw `Post for request returned status code ${tokenRequest.status} instead of the expected 200`;
}

const tokenResponse = tokenRequest.data as GithubTokenResponse;

return tokenResponse.access_token;
};

const verifyGithub = async (
code: string,
providedOrg: string,
requestedClient: ClientType
): Promise<GHVerification> => {
// retrieve user's auth bearer token to authenticate client
const accessToken = await requestAccessToken(code, requestedClient);

// Now that we have an access token fetch the user details
const userRequest: GithubUserResponse = await axios.get("https://api.github.com/user", {
headers: { Authorization: `token ${accessToken}` },
});
if (userRequest.status != 200) {
throw `Get user request returned status code ${userRequest.status} instead of the expected 200`;
}

const handle = userRequest.data.login;
const { id } = userRequest.data;

const userOrgRequest: GithubUserOrgResponse = await axios.get(`https://api.github.com/users/${handle}/orgs`, {
headers: { Authorization: `token ${accessToken}` },
});

if (userOrgRequest.status != 200) {
throw `Get user org request returned status code ${userOrgRequest.status} instead of the expected 200`;
}

const validOrg = verifyOrg(userOrgRequest.data, providedOrg);

return {
validOrg,
id,
};
};
58 changes: 58 additions & 0 deletions platforms/src/ClearText/Providers/clearTextTwitter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// ----- Types
import type { RequestPayload, VerifiedPayload } from "@gitcoin/passport-types";

// ----- Twitters OAuth2 library
import {
deleteClient,
getClient,
requestFindMyUser,
TwitterFindMyUserResponse,
} from "../../Twitter/procedures/twitterOauth";
import type { Provider, ProviderOptions } from "../../types";
// import { verifyTwitter } from "../providers/twitter";

// Export a Twitter Provider to carry out OAuth and return a record object
export class ClearTextTwitterProvider implements Provider {
// Give the provider a type so that we can select it with a payload
type = "ClearTextTwitter";
// Options can be set here and/or via the constructor
_options = {};

// construct the provider instance with supplied options
constructor(options: ProviderOptions = {}) {
this._options = { ...this._options, ...options };
}

// verify that the proof object contains valid === "true"
async verify(payload: RequestPayload): Promise<VerifiedPayload> {
let valid = false,
verifiedPayload: TwitterFindMyUserResponse = {},
pii;

try {
verifiedPayload = await verifyUserTwitter(payload.proofs.sessionKey, payload.proofs.code);
} catch (e) {
return { valid: false };
} finally {
valid = verifiedPayload && verifiedPayload.username ? true : false;
pii = verifiedPayload.username;
}

return {
valid,
record: {
pii,
},
};
}
}

async function verifyUserTwitter(sessionKey: string, code: string): Promise<TwitterFindMyUserResponse> {
const client = getClient(sessionKey);

const myUser = await requestFindMyUser(client, code);

deleteClient(sessionKey);

return myUser;
}
Loading

0 comments on commit d03f7fe

Please sign in to comment.