Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(linkedin): wip add linkedin v2 #3001

Merged
merged 9 commits into from
Nov 11, 2024
1 change: 1 addition & 0 deletions app/.env-example.env
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ NEXT_PUBLIC_PASSPORT_TWITTER_CLIENT_ID=ABC123456789
NEXT_PUBLIC_PASSPORT_GITHUB_CLIENT_ID=12345678
NEXT_PUBLIC_PASSPORT_GITHUB_CALLBACK=http://localhost:3000/
NEXT_PUBLIC_PASSPORT_LINKEDIN_CLIENT_ID=12345678
NEXT_PUBLIC_PASSPORT_LINKEDIN_CLIENT_ID_V2=12345678
NEXT_PUBLIC_PASSPORT_LINKEDIN_CALLBACK=http://localhost:3000/
NEXT_PUBLIC_PASSPORT_DISCORD_CLIENT_ID=12345678
NEXT_PUBLIC_PASSPORT_DISCORD_CALLBACK=http://localhost:3000/
Expand Down
2 changes: 2 additions & 0 deletions iam/.env-example.env
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ GRANT_HUB_MACI_GITHUB_CLIENT_ID=MY_GRANT_HUB_GITHUB_CLIENT_ID
GRANT_HUB_MACI_GITHUB_CLIENT_SECRET=MY_GRANT_HUB_GITHUB_CLIENT_SECRET
LINKEDIN_CLIENT_ID=MY_LINKEDIN_CLIENT_ID
LINKEDIN_CLIENT_SECRET=MY_LINKEDIN_CLIENT_SECRET
LINKEDIN_CLIENT_ID_V2=MY_LINKEDIN_CLIENT_ID_V2
LINKEDIN_CLIENT_SECRET_V2=MY_LINKEDIN_CLIENT_SECRET_V2
LINKEDIN_CALLBACK=http://localhost:3000/
DISCORD_CLIENT_ID=MY_APP_CLIENT_ID
DISCORD_CLIENT_SECRET=MY_APP_CLIENT_SECRET
Expand Down
18 changes: 14 additions & 4 deletions platforms/src/Linkedin/App-Bindings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { PlatformOptions } from "../types";
import { Platform } from "../utils/platform";

export class LinkedinPlatform extends Platform {
platformId = "Linkedin";
path = "linkedin";
Expand All @@ -14,13 +15,22 @@ export class LinkedinPlatform extends Platform {
label: "Learn more",
url: "https://support.passport.xyz/passport-knowledge-base/stamps/how-do-i-add-passport-stamps/guide-to-add-a-linkedin-stamp-to-passport",
},
}
};
}

async getOAuthUrl(state: string): Promise<string> {
const linkedinUrl = await Promise.resolve(
`https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${this.clientId}&redirect_uri=${this.redirectUri}&state=${state}&scope=r_emailaddress%20r_liteprofile`
);
const AUTH_URL = "https://www.linkedin.com/oauth/v2/authorization";
const params = new URLSearchParams({
response_type: "code",
client_id: this.clientId,
redirect_uri: this.redirectUri,
scope: "profile email openid",
state: state,
});

const url = `${AUTH_URL}?${params.toString()}`;

const linkedinUrl = await Promise.resolve(url);
return linkedinUrl;
}
}
11 changes: 6 additions & 5 deletions platforms/src/Linkedin/Providers-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import { LinkedinProvider } from "./Providers/linkedin";
export const PlatformDetails: PlatformSpec = {
icon: "./assets/linkedinStampIcon.svg",
platform: "Linkedin",
name: "Linkedin",
description: "Connect your existing Linkedin account to verify.",
connectMessage: "Connect Account",
name: "LinkedIn",
description:
"This stamp confirms that your LinkedIn account is verified and includes a valid, verified email address.",
connectMessage: "Connect Account to LinkedIn",
};

export const ProviderConfig: PlatformGroupSpec[] = [
{
platformGroup: "Account Name",
providers: [{ title: "Encrypted", name: "Linkedin" }],
platformGroup: "Account Verification",
providers: [{ title: "Verified email address", name: "Linkedin" }],
},
];

Expand Down
19 changes: 11 additions & 8 deletions platforms/src/Linkedin/Providers/linkedin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ export type LinkedinTokenResponse = {
};

export type LinkedinFindMyUserResponse = {
id?: string;
firstName?: string;
lastName?: string;
sub?: string;
email_verified?: boolean;
name?: string;
given_name?: string;
family_name?: string;
email?: string;
error?: string;
};

Expand All @@ -42,11 +45,11 @@ export class LinkedinProvider implements Provider {
try {
if (payload.proofs) {
verifiedPayload = await verifyLinkedin(payload.proofs.code);
valid = verifiedPayload && verifiedPayload.id ? true : false;
valid = verifiedPayload && verifiedPayload.sub && verifiedPayload.email_verified ? true : false;

if (valid) {
record = {
id: verifiedPayload.id,
sub: verifiedPayload.sub,
};
} else {
errors.push(`We were unable to verify your LinkedIn account -- LinkedIn Account Valid: ${String(valid)}.`);
Expand All @@ -67,8 +70,8 @@ export class LinkedinProvider implements Provider {

const requestAccessToken = async (code: string): Promise<string> => {
try {
const clientId = process.env.LINKEDIN_CLIENT_ID;
const clientSecret = process.env.LINKEDIN_CLIENT_SECRET;
const clientId = process.env.LINKEDIN_CLIENT_ID_V2;
const clientSecret = process.env.LINKEDIN_CLIENT_SECRET_V2;

const tokenRequest = await axios.post(
`https://www.linkedin.com/oauth/v2/accessToken?grant_type=authorization_code&code=${code}&client_id=${clientId}&client_secret=${clientSecret}&redirect_uri=${process.env.LINKEDIN_CALLBACK}`,
Expand Down Expand Up @@ -96,7 +99,7 @@ const verifyLinkedin = async (code: string): Promise<LinkedinFindMyUserResponse>
// 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://api.linkedin.com/v2/me", {
const userRequest = await axios.get("https://api.linkedin.com/v2/userinfo", {
headers: {
Authorization: `Bearer ${accessToken}`,
"Linkedin-Version": 202305,
Expand Down
28 changes: 17 additions & 11 deletions platforms/src/Linkedin/__tests__/linkedin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ const mockedAxios = axios as jest.Mocked<typeof axios>;

const validLinkedinUserResponse = {
data: {
id: "18723656",
firstName: "First",
lastName: "Last",
sub: "18723656",
email_verified: true,
name: "Foo",
given_name: "Foo",
family_name: "Bar",
email: "mail@mail.com",
},
status: 200,
};
Expand All @@ -41,10 +44,10 @@ beforeEach(() => {
});
});

describe("Attempt verification", function() {
describe("Attempt verification", function () {
it("handles valid verification attempt", async () => {
const clientId = process.env.LINKEDIN_CLIENT_ID;
const clientSecret = process.env.LINKEDIN_CLIENT_SECRET;
const clientId = process.env.LINKEDIN_CLIENT_ID_V2;
const clientSecret = process.env.LINKEDIN_CLIENT_SECRET_V2;
const linkedin = new LinkedinProvider();
const linkedinPayload = await linkedin.verify({
proofs: {
Expand All @@ -62,15 +65,15 @@ describe("Attempt verification", function() {
);

// Check the request to get the user
expect(mockedAxios.get).toHaveBeenCalledWith("https://api.linkedin.com/v2/me", {
expect(mockedAxios.get).toHaveBeenCalledWith("https://api.linkedin.com/v2/userinfo", {
headers: { Authorization: "Bearer 762165719dhiqudgasyuqwt6235", "Linkedin-Version": 202305 },
});

expect(linkedinPayload).toEqual({
valid: true,
errors: [],
record: {
id: validLinkedinUserResponse.data.id,
sub: validLinkedinUserResponse.data.sub,
},
});
});
Expand All @@ -94,9 +97,12 @@ describe("Attempt verification", function() {
mockedAxios.get.mockImplementation(async () => {
return {
data: {
id: undefined,
firstName: "First",
lastName: "Last",
sub: undefined,
email_verified: false,
name: "Foo",
given_name: "Foo",
family_name: "Bar",
email: "mail@mail.com",
},
status: 200,
};
Expand Down
2 changes: 1 addition & 1 deletion platforms/src/platforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const platforms: Record<string, PlatformConfig> = {
Outdid,
AllowList,
Binance,
CustomGithub
CustomGithub,
};

if (process.env.NEXT_PUBLIC_FF_NEW_POAP_STAMPS === "on") {
Expand Down
Loading