Skip to content

Commit

Permalink
Merge pull request passportxyz#753 from gitcoinco/647_enhance_iam_logs
Browse files Browse the repository at this point in the history
fix(passportxyz#647): adding additional logs to be able to debug issues when ver…
  • Loading branch information
nutrina authored Nov 25, 2022
2 parents 7b8346e + a12393b commit 8c18162
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 93 deletions.
11 changes: 9 additions & 2 deletions iam/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,10 @@ const issueCredential = async (
}
} else {
// return error message if an error is present
// limit the error message string to 1000 chars
throw {
error: (verifiedPayload.error && verifiedPayload.error.join(", ")) || "Unable to verify proofs",
error:
(verifiedPayload.error && verifiedPayload.error.join(", ").substring(0, 1000)) || "Unable to verify proofs",
code: 403,
};
}
Expand Down Expand Up @@ -369,7 +371,12 @@ app.post("/api/v0.0.0/challenge", (req: Request, res: Response): void => {
});
} else {
// return error message if an error present
return void errorRes(res, (challenge.error && challenge.error.join(", ")) || "Unable to verify proofs", 403);
// limit the error message string to 1000 chars
return void errorRes(
res,
(challenge.error && challenge.error.join(", ").substring(0, 1000)) || "Unable to verify proofs",
403
);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,10 +268,7 @@ describe("Attempt verification %s", function () {
it("should return invalid payload when a bad status code is returned by github user api", async () => {
(axios.get as jest.Mock).mockImplementation((url) => {
if (url === "https://api.github.com/user")
return Promise.resolve({
...validGithubUserResponse,
status: 500,
});
throw new Error("API EXCEPTION");
else if (url.startsWith("https://gitcoin.co/grants/v1/api/vc/contributor_statistics"))
return Promise.resolve({
status: 200,
Expand Down
51 changes: 41 additions & 10 deletions platforms/src/Gitcoin/Providers/gitcoinGrantsStatistics.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// ----- Types
import type { ProviderContext, RequestPayload, VerifiedPayload } from "@gitcoin/passport-types";
import type { Provider, ProviderOptions } from "../../types";
import { getErrorString, ProviderError } from "../../utils/errors";
import axios from "axios";
import { GithubFindMyUserResponse, verifyGithub } from "../../Github/Providers/github";

const AMI_API_TOKEN = process.env.AMI_API_TOKEN;

export type GitcoinGrantStatistics = {
[k: string]: number;
errors?: string[] | undefined;
record?: { [k: string]: number };
};

export type GitcoinGrantProviderOptions = {
Expand Down Expand Up @@ -46,19 +48,38 @@ export class GitcoinGrantStatisticsProvider implements Provider {
githubUser = await verifyGithub(payload.proofs.code, context);
context["githubUser"] = githubUser;
}
console.log("gitcoin - githubUser", githubUser);

// Only check the contribution condition if a valid github id has been received
valid = !!githubUser.id;
valid = !githubUser.errors && !!githubUser.id;
if (valid) {
const gitcoinGrantsStatistic = await getGitcoinStatistics(this.dataUrl, githubUser.login);
valid = gitcoinGrantsStatistic[this._options.receivingAttribute] >= this._options.threshold;
console.log("gitcoin - getGitcoinStatistics", gitcoinGrantsStatistic);

valid =
!gitcoinGrantsStatistic.errors &&
(gitcoinGrantsStatistic.record
? gitcoinGrantsStatistic.record[this._options.receivingAttribute] >= this._options.threshold
: false);

return {
valid: valid,
error: gitcoinGrantsStatistic.errors,
record: valid
? {
id: githubUser.id,
[this._options.recordAttribute]: `${this._options.threshold}`,
}
: undefined,
};
}
} catch (e) {
return { valid: false };
}

const ret = {
valid: valid,
error: githubUser ? githubUser.errors : undefined,
record: valid
? {
id: `${githubUser.id}`,
Expand All @@ -72,13 +93,23 @@ export class GitcoinGrantStatisticsProvider implements Provider {
}

const getGitcoinStatistics = async (dataUrl: string, handle: string): Promise<GitcoinGrantStatistics> => {
const grantStatisticsRequest = await axios.get(`${dataUrl}?handle=${handle}`, {
headers: { Authorization: `token ${AMI_API_TOKEN}` },
});
try {
const grantStatisticsRequest = await axios.get(`${dataUrl}?handle=${handle}`, {
headers: { Authorization: `token ${AMI_API_TOKEN}` },
});

if (grantStatisticsRequest.status != 200) {
throw `Get user request returned status code ${grantStatisticsRequest.status} instead of the expected 200`;
console.log("gitcoin - API response", handle, dataUrl, grantStatisticsRequest.data);
return { record: grantStatisticsRequest.data } as GitcoinGrantStatistics;
} catch (_error) {
const error = _error as ProviderError;
console.log("gitcoinGrantsStatistics", dataUrl, handle, getErrorString(error));
return {
errors: [
"Error getting user info",
`${error?.message}`,
`Status ${error.response?.status}: ${error.response?.statusText}`,
`Details: ${JSON.stringify(error?.response?.data)}`,
],
};
}

return grantStatisticsRequest.data as GitcoinGrantStatistics;
};
4 changes: 1 addition & 3 deletions platforms/src/Github/Providers/__tests__/github.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,7 @@ describe("Attempt verification", function () {

it("should return invalid payload when a bad status code is returned by github user api", async () => {
mockedAxios.get.mockImplementation(async (url, config) => {
return {
status: 500,
};
throw new Error("Some Error");
});

const github = new GithubProvider();
Expand Down
64 changes: 35 additions & 29 deletions platforms/src/Github/Providers/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
import type { ProviderContext, RequestPayload, VerifiedPayload } from "@gitcoin/passport-types";
import type { Provider, ProviderOptions } from "../../types";
import axios from "axios";
import { getErrorString, ProviderError } from "../../utils/errors";

export type GithubTokenResponse = {
access_token: string;
};

export type GithubFindMyUserResponse = {
errors?: string[] | undefined;
id?: string;
login?: string;
type?: string;
Expand All @@ -28,22 +30,20 @@ export class GithubProvider implements Provider {

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

try {
verifiedPayload = await verifyGithub(payload.proofs.code, context);
} catch (e) {
return { valid: false };
} finally {
valid = verifiedPayload && verifiedPayload.id ? true : false;
}
const verifiedPayload = await verifyGithub(payload.proofs.code, context);

console.log("github - verifiedPayload", verifiedPayload);
const valid = !!(!verifiedPayload.errors && verifiedPayload.id);
console.log("github - valid", valid);

return {
valid: valid,
record: {
id: verifiedPayload.id,
},
error: verifiedPayload.errors,
record: valid
? {
id: verifiedPayload.id,
}
: undefined,
};
}
}
Expand All @@ -66,28 +66,34 @@ export const requestAccessToken = async (code: string, context: ProviderContext)
}
);

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

const tokenResponse = tokenRequest.data as GithubTokenResponse;

context["githubAccessToken"] = tokenResponse.access_token;
return tokenResponse.access_token;
};

export const verifyGithub = async (code: string, context: ProviderContext): Promise<GithubFindMyUserResponse> => {
// retrieve user's auth bearer token to authenticate client
const accessToken = await requestAccessToken(code, context);

// Now that we have an access token fetch the user details
const userRequest = 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`;
try {
// retrieve user's auth bearer token to authenticate client
const accessToken = await requestAccessToken(code, context);

// Now that we have an access token fetch the user details
const userRequest = await axios.get("https://api.github.com/user", {
headers: { Authorization: `token ${accessToken}` },
});
console.log("verifyGithub result:", userRequest.data);

return userRequest.data as GithubFindMyUserResponse;
} catch (_error) {
const error = _error as ProviderError;
console.log("verifyGithub ERROR:", getErrorString(error));
return {
errors: [
"Error getting getting github info",
`${error?.message}`,
`Status ${error.response?.status}: ${error.response?.statusText}`,
`Details: ${JSON.stringify(error?.response?.data)}`,
],
};
}

return userRequest.data as GithubFindMyUserResponse;
};
43 changes: 14 additions & 29 deletions platforms/src/Google/Providers/__tests__/google.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,27 +70,6 @@ describe("Attempt verification", function () {
},
});
});

it("should return invalid payload when verifyGoogle throws exception", async () => {
const googleProvider = new google.GoogleProvider();
const verifyGoogleMock = jest
.spyOn(google, "verifyGoogle")
.mockImplementation((code: string): Promise<google.UserInfo> => {
throw new Error("verifyGoogle error message");
});

const verifiedPayload = await googleProvider.verify({
proofs: {
code: MOCK_TOKEN_ID,
},
} as unknown as RequestPayload);

expect(verifyGoogleMock).toBeCalledWith(MOCK_TOKEN_ID);
expect(verifiedPayload).toEqual({
valid: false,
error: ["verifyGoogle error message"],
});
});
});

describe("verifyGoogle", function () {
Expand Down Expand Up @@ -143,10 +122,16 @@ describe("verifyGoogle", function () {
throw { response: { data: { error: { message: "error message for user data request" } } } };
});

async function verifyGoogle() {
await google.verifyGoogle(MOCK_TOKEN_ID);
}
await expect(verifyGoogle).rejects.toThrow(new Error("Error getting user info: error message for user data request"));
const verifiedGoogleResponse = await google.verifyGoogle(MOCK_TOKEN_ID);

expect(verifiedGoogleResponse).toEqual({
errors: [
"Error getting user info",
"undefined",
"Status undefined: undefined",
"Details: " + JSON.stringify({ error: { message: "error message for user data request" } }),
],
});
expect(requestAccessTokenMock).toBeCalledWith(MOCK_TOKEN_ID);
expect(userInfoMock).toBeCalledTimes(1);
expect(userInfoMock).toBeCalledWith("https://www.googleapis.com/oauth2/v2/userinfo", {
Expand All @@ -159,10 +144,10 @@ describe("verifyGoogle", function () {
throw new Error("ERROR");
});

async function verifyGoogle() {
await google.verifyGoogle(MOCK_TOKEN_ID);
}
const verifiedGoogleResponse = await google.verifyGoogle(MOCK_TOKEN_ID);

await expect(verifyGoogle).rejects.toThrow(new Error("ERROR"));
expect(verifiedGoogleResponse).toEqual({
errors: ["Error getting user info", "ERROR", "Status undefined: undefined", "Details: undefined"],
});
});
});
48 changes: 32 additions & 16 deletions platforms/src/Google/Providers/google.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// ----- Types
import type { RequestPayload, VerifiedPayload } from "@gitcoin/passport-types";
import type { Provider, ProviderOptions } from "../../types";
import { getErrorString, ProviderError } from "../../utils/errors";
import axios from "axios";

// Checking a valid tokenId for a result from Google will result in the following type
Expand Down Expand Up @@ -44,9 +45,8 @@ export class GoogleProvider implements Provider {
// verify that the proof object contains valid === "true"
async verify(payload: RequestPayload): Promise<VerifiedPayload> {
const verifiedPayload = await verifyGoogle(payload.proofs.code);
let valid = !verifiedPayload.errors && verifiedPayload.emailVerified;
console.log("geri verifiedPayload", verifiedPayload);
console.log("geri valid", valid);
const valid = !verifiedPayload.errors && verifiedPayload.emailVerified;
console.log("google - verify - verifiedPayload", verifiedPayload);
return {
valid: valid,
error: verifiedPayload.errors,
Expand All @@ -64,7 +64,6 @@ export const requestAccessToken = async (code: string): Promise<string> => {

try {
const url = `https://oauth2.googleapis.com/token?client_id=${clientId}&client_secret=${clientSecret}&code=${code}&grant_type=authorization_code&redirectUri=${redirectUri}`;
console.log("url", url);

// Exchange the code for an access token
const tokenRequest = await axios.post(
Expand All @@ -76,39 +75,56 @@ export const requestAccessToken = async (code: string): Promise<string> => {
);

const tokenResponse = tokenRequest.data as GoogleTokenResponse;
console.log("tokenResponse", tokenResponse);
console.log(
"google - tokenRequest.statusText, tokenRequest.status, tokenRequest.data",
tokenRequest.statusText,
tokenRequest.status,
tokenRequest.data
);
return tokenResponse.access_token;
} catch (error) {
throw new Error("Error getting authentication token: " + error?.response?.data?.error_description);
} catch (_error) {
const error = _error as ProviderError;
const errorString = getErrorString(error);
console.log(errorString);
throw new Error(errorString);
}
};

// Perform verification on shared google access token
export const verifyGoogle = async (code: string): Promise<UserInfo> => {
// retrieve user's auth bearer token to authenticate client
const accessToken = await requestAccessToken(code);

try {
// retrieve user's auth bearer token to authenticate client
const accessToken = await requestAccessToken(code);

// Now that we have an access token fetch the user details
const userRequest = await axios.get("https://www.googleapis.com/oauth2/v2/userinfo", {
headers: { Authorization: `Bearer ${accessToken}` },
});

console.log("userRequest", userRequest);

console.log(
"google - userRequest.statusText, userRequest.status, userRequest.data",
userRequest.statusText,
userRequest.status,
userRequest.data
);
const userInfo: GoogleUserInfo = userRequest.data as GoogleUserInfo;
console.log("google - userInfo", userInfo);

console.log("userInfo", userInfo);
return {
email: userInfo?.email,
emailVerified: userInfo?.verified_email,
};
} catch (error) {
} catch (_error) {
const error = _error as ProviderError;
const errorString = getErrorString(error);
console.log(errorString);

return {
errors: [
"Error getting user info",
`Status ${error.response.status}: ${error.response.statusText}`,
"Details: " + error?.response?.data?.error?.message,
`${error?.message}`,
`Status ${error.response?.status}: ${error.response?.statusText}`,
`Details: ${JSON.stringify(error?.response?.data)}`,
],
};
}
Expand Down
Loading

0 comments on commit 8c18162

Please sign in to comment.