Skip to content

Commit 7542bd0

Browse files
Jeremias Santosclaude
authored andcommitted
fix(pav-6): fix LinkedIn provider signature and add email validation
- Fix exchangeLinkedinCode to match ExchangeCodeParams interface - Use callbackUrl + expectedState like Google provider - Add email validation in handleCallback — reject profiles without email - Add integration tests for LinkedIn callback and email-absent case Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent faf91e0 commit 7542bd0

3 files changed

Lines changed: 41 additions & 12 deletions

File tree

backend/src/modules/auth/auth.service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ export class AuthService {
3535
callbackUrl,
3636
});
3737

38+
if (!profile.email) {
39+
throw new Error("oauth_email_required");
40+
}
41+
3842
const user = await findOrCreateUser({
3943
provider,
4044
profile,

backend/src/modules/auth/providers/linkedin.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
buildAuthorizationUrl,
44
discovery,
55
} from "openid-client";
6-
import { OAuthProfile } from "../../types/auth.types";
6+
import { ExchangeCodeParams, OAuthProfile } from "../../types/auth.types";
77

88
let _config: Awaited<ReturnType<typeof discovery>> | null = null;
99

@@ -32,20 +32,18 @@ export async function getLinkedinAuthUrl(state: string): Promise<string> {
3232
}
3333

3434
export async function exchangeLinkedinCode({
35-
code,
35+
callbackUrl,
3636
state,
37-
}: {
38-
code: string;
39-
state?: string;
40-
}): Promise<OAuthProfile> {
37+
}: ExchangeCodeParams): Promise<OAuthProfile> {
4138
const config = await getConfig();
4239

43-
const tokens = await authorizationCodeGrant(
44-
config,
45-
new URL(
46-
`${process.env.APP_URL}/auth/linkedin/callback?code=${code}&state=${state}`,
47-
),
48-
);
40+
if (!state) {
41+
throw new Error("State ausente");
42+
}
43+
44+
const tokens = await authorizationCodeGrant(config, new URL(callbackUrl), {
45+
expectedState: state,
46+
});
4947

5048
const claims = tokens.claims();
5149

backend/tests/integration/routes/auth.routes.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,33 @@ describe("Integration - Auth Routes", () => {
282282

283283
expect(res.headers.location).toContain("/login?error=oauth_failed");
284284
});
285+
286+
it("redireciona ao frontend em callback valido para linkedin", async () => {
287+
const res = await request(app)
288+
.get(`${BASE}/linkedin/callback?code=li-code-123&state=valid-state-abc123`)
289+
.expect(302);
290+
291+
expect(res.headers.location).toContain("/auth/callback");
292+
expect(mockAuthService.handleCallback).toHaveBeenCalledWith(
293+
expect.objectContaining({
294+
provider: "linkedin",
295+
code: "li-code-123",
296+
state: "valid-state-abc123",
297+
}),
298+
);
299+
});
300+
301+
it("redireciona com erro quando profile nao tem email", async () => {
302+
mockAuthService.handleCallback.mockRejectedValueOnce(
303+
new Error("oauth_email_required"),
304+
);
305+
306+
const res = await request(app)
307+
.get(`${BASE}/linkedin/callback?code=abc123&state=valid-state-abc123`)
308+
.expect(302);
309+
310+
expect(res.headers.location).toContain("/login?error=oauth_failed");
311+
});
285312
});
286313

287314
// ── POST /register ────────────────────────────────────────────────────────

0 commit comments

Comments
 (0)