Skip to content

Commit 9187d1e

Browse files
Jeremias Santosclaude
authored andcommitted
feat(pav-5): integrate Google OAuth login end-to-end
- Backend: fix callback to use req.session (shared with withSession middleware) instead of creating separate getIronSession instance, save userId in session, redirect to frontend instead of returning JSON - Backend: add FRONTEND_URL env var for post-OAuth redirect - Frontend: add getGoogleAuthUrl() to authService - Frontend: wire Google button onClick in login and register pages - Frontend: create AuthCallback page that detects session and navigates to /app - Add AGENTS.md with project rules - Add design spec and implementation plan docs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent dfeb8a4 commit 9187d1e

9 files changed

Lines changed: 405 additions & 55 deletions

File tree

AGENTS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Regras do Projeto
2+
3+
## Git
4+
5+
- NUNCA faça commits automaticamente. Sempre pergunte ao usuario antes de commitar.

backend/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ SESSION_SECRET=
1414
# CORS / frontend access (separar por vírgula)
1515
CORS_ALLOWED_ORIGINS=
1616

17+
# URL do frontend (usada para redirect pos-OAuth)
18+
FRONTEND_URL=http://localhost:5173
19+
1720
# LinkedIn search filters (defaults)
1821
SEARCH_LOCATION=Brasil
1922
SEARCH_GEO_ID=106057199
Lines changed: 15 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { randomBytes } from "crypto";
22
import { Request, Response } from "express";
3-
import { getIronSession } from "iron-session";
43
import { z } from "zod";
54
import {
65
AuthCallbackParamsSchema,
@@ -9,40 +8,17 @@ import {
98

109
import { AuthService } from "./auth.service.js";
1110

12-
interface SessionData {
13-
oauth_state?: string;
14-
userId?: string;
15-
}
16-
17-
const sessionOptions = {
18-
password: process.env.SESSION_SECRET!,
19-
cookieName: "vagas_session",
20-
21-
cookieOptions: {
22-
httpOnly: true,
23-
secure: process.env.NODE_ENV === "production",
24-
maxAge: 60 * 60 * 24 * 7,
25-
},
26-
};
27-
2811
export class AuthController {
2912
constructor(private readonly authService: AuthService) {}
3013

3114
async getUrl(req: Request, res: Response) {
3215
try {
33-
const session = await getIronSession<SessionData>(
34-
req,
35-
res,
36-
sessionOptions,
37-
);
38-
3916
const provider = OAuthProviderSchema.parse(req.params.provider);
4017

4118
const state = randomBytes(16).toString("hex");
4219

43-
session.oauth_state = state;
44-
45-
await session.save();
20+
(req.session as any).oauth_state = state;
21+
await req.session.save();
4622

4723
const url = await this.authService.getAuthUrl(provider, state);
4824

@@ -62,7 +38,7 @@ export class AuthController {
6238
}
6339

6440
async callback(req: Request, res: Response) {
65-
const session = await getIronSession<SessionData>(req, res, sessionOptions);
41+
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:5173";
6642

6743
try {
6844
const callbackUrl = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
@@ -74,42 +50,30 @@ export class AuthController {
7450
callbackUrl,
7551
});
7652

77-
if (!session.oauth_state) {
78-
return res.status(400).json({
79-
error: "OAuth state ausente",
80-
});
53+
const oauthState = (req.session as any).oauth_state;
54+
55+
if (!oauthState) {
56+
return res.redirect(`${frontendUrl}/login?error=oauth_state_missing`);
8157
}
8258

83-
if (session.oauth_state !== params.state) {
84-
return res.status(400).json({
85-
error: "OAuth state inválido",
86-
});
59+
if (oauthState !== params.state) {
60+
return res.redirect(`${frontendUrl}/login?error=oauth_state_invalid`);
8761
}
8862

89-
delete session.oauth_state;
90-
await session.save();
63+
delete (req.session as any).oauth_state;
9164

9265
const result = await this.authService.handleCallback({
9366
...params,
9467
callbackUrl,
9568
});
9669

97-
return res.json(result);
70+
req.session.userId = result.session.userId;
71+
await req.session.save();
72+
73+
return res.redirect(`${frontendUrl}/auth/callback`);
9874
} catch (error) {
9975
console.error("OAuth callback error:", error);
100-
101-
if (error instanceof z.ZodError) {
102-
return res.status(400).json({
103-
error: "Parâmetros de callback inválidos",
104-
details: error.format(),
105-
});
106-
}
107-
108-
const message = error instanceof Error ? error.message : "Erro interno";
109-
110-
return res.status(500).json({
111-
error: message,
112-
});
76+
return res.redirect(`${frontendUrl}/login?error=oauth_failed`);
11377
}
11478
}
11579
}

0 commit comments

Comments
 (0)