Appearance
Authentication - Autenticação OAuth
Fluxo de Login Social
O sistema utiliza autenticação OAuth 2.0 com popup para os provedores: Google, LinkedIn, Microsoft e GitHub.
IMPORTANTE: A URL OAuth é construída diretamente no frontend usando as credenciais de cada provedor. O backend NÃO fornece URLs de OAuth.
LoginPage
↓
Clica no botão social (ex: Google)
↓
useOAuthPopup.openPopup('google')
↓
Frontend constrói URL OAuth com buildOAuthUrl('google')
↓
Popup abre com URL OAuth do provedor
↓
Usuário autoriza no provedor
↓
Provedor redireciona para /auth/callback/:provider?code=...
↓
OAuthCallback.vue (no popup):
- Extrai o código de autorização da URL
- Envia postMessage({ type: 'oauth:success', provider, code })
- Fecha popup
↓
LoginPage recebe mensagem:
- Chama socialAuthService({ provider, code })
↓
Backend troca code por token com provedor e retorna:
{
user, token, companies,
action: 'login' | 'register',
needsProfileCompletion: boolean
}
↓
Se needsProfileCompletion:
→ Redireciona para /complete-profile
Senão:
→ Redireciona para /select-companyConstrução da URL OAuth (Frontend)
A URL OAuth é construída usando constantes definidas em src/domain/auth/constants.ts:
typescript
export const OAUTH_CONFIGS: Record<AuthProvider, OAuthConfig> = {
google: {
authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
clientId: import.meta.env.VITE_GOOGLE_CLIENT_ID,
scope: 'openid email profile',
responseType: 'code'
},
// ... outros provedores
}
export const buildOAuthUrl = (provider: AuthProvider): string => {
const config = OAUTH_CONFIGS[provider]
const params = new URLSearchParams({
client_id: config.clientId,
redirect_uri: `${OAUTH_REDIRECT_URI}/${provider}`,
response_type: config.responseType,
scope: config.scope,
state: crypto.randomUUID()
})
return `${config.authUrl}?${params.toString()}`
}Variáveis de Ambiente para OAuth
env
VITE_GOOGLE_CLIENT_ID="seu-google-client-id"
VITE_LINKEDIN_CLIENT_ID="seu-linkedin-client-id"
VITE_MICROSOFT_CLIENT_ID="seu-microsoft-client-id"
VITE_AUTH_GITHUB_CLIENT_ID="seu-github-client-id"
VITE_MOCK_MODE="false"Endpoints de Autenticação
| Endpoint | Método | Descrição |
|---|---|---|
/auth/social | POST | Autentica com código OAuth e retorna dados do usuário |
/auth/login | POST | Login com email e senha (envia código 2FA) |
/auth/verify-code | POST | Verifica código 2FA e completa login |
/auth/register | POST | Cadastro com email e senha |
/auth/verify-email | POST | Envia código de verificação de email |
/auth/verify-email-code | POST | Verifica código de email |
/auth/forgot-password | POST | Solicita link de recuperação de senha |
/auth/validate-token | GET | Valida token de acesso (query param token) |
/auth/reset-password | POST | Redefine senha com token e faz auto-login |
/auth/complete-profile | POST | Completa dados do perfil do usuário |
/auth/companies | GET | Lista empresas do usuário autenticado |
/auth/create-company | POST | Cadastra nova empresa |
/users/me | GET | Retorna dados do usuário autenticado |
/users/me | PUT | Atualiza perfil do usuário (nome, telefone, CPF) |
/users/me/password | PUT | Altera a senha do usuário |
Headers de Requisição
Todas as requisições autenticadas incluem automaticamente:
| Header | Origem | Descrição |
|---|---|---|
Authorization | localStorage | Token Bearer do usuário |
x-company-id | sessionStorage | ID da empresa selecionada |
O header x-company-id é adicionado automaticamente pelo interceptor do Axios quando há uma empresa selecionada no sessionStorage.
Request do Endpoint /auth/social
typescript
type SocialAuthRequest = {
provider: AuthProvider // 'google' | 'linkedin' | 'microsoft' | 'github'
code: string // Código de autorização retornado pelo provedor
}Response do Endpoint /auth/social
typescript
type SocialAuthResponse = {
user: User
token: string
companies: Company[]
action: 'login' | 'register'
needsProfileCompletion: boolean
}Composable useOAuthPopup
Gerencia o fluxo de popup OAuth:
typescript
const { openPopup, isLoading, error } = useOAuthPopup()
const result = await openPopup('google')
// result: { provider: 'google', code: '...' }Tela de Completar Cadastro
Exibida quando needsProfileCompletion: true na resposta do /auth/social.
IMPORTANTE: Esta é uma página full-screen sem menu lateral (não usa ContainerApp).
Campos obrigatórios:
- Nome Completo
- Telefone
Campos opcionais:
- CPF
Páginas de Autenticação Full-Screen
As seguintes páginas são renderizadas em full-screen, sem o layout ContainerApp:
| Página | Rota | Descrição |
|---|---|---|
| LoginPage | /login | Tela de login social + email |
| RegisterPage | /auth/register | Cadastro com email e senha |
| OAuthCallback | /auth/callback/:provider | Callback do OAuth (popup) |
| CompleteProfilePage | /complete-profile | Completar cadastro |
| SelectCompanyPage | /select-company | Selecionar empresa |
| SetPasswordPage | /auth/recovery?token=... | Recuperação de senha |
| SetPasswordPage | /auth/first-access?token=... | Primeiro acesso (convite) |
| NotFoundPage | /:pathMatch(.*)* | 404 full-screen para usuário logado |
Página 404 (Rota não encontrada)
A rota catch-all (/:pathMatch(.*)*, name not-found) usa meta: { requiresLogin: true, standalone: true }:
- Usuário deslogado: o
authGuard(handleLoginOnlyRoute) redireciona paraloginantes de renderizar — a tela 404 não é exibida. - Usuário logado: a página é renderizada em full-screen (
standalone), semTopHeadernem menu lateral. O botão "Ir para o dashboard" usaresolveAuthLandingRoute()quando não há empresa selecionada ou o usuário é prestador.
Fluxo de Recuperação de Senha
LoginPage → Clica "Esqueceu a senha?"
↓
ForgotPasswordModal (modal com campo de email)
↓
POST /auth/forgot-password { email }
↓
Backend verifica se email existe:
- Se usuário tem AuthProvider.INVITE → gera token tipo INVITE, redireciona para /auth/first-access
- Se usuário normal → gera token tipo PASSWORD_RESET, redireciona para /auth/recovery
↓
Usuário recebe email com link contendo token
↓
Clica no link → SetPasswordPage
↓
GET /auth/validate-token?token=xxx → retorna { name, email, isInvite }
↓
Exibe formulário com nome/email (disabled) + campos de senha
↓
POST /auth/reset-password { token, password, confirmPassword }
↓
Backend: define senha, ativa usuário se INVITE, retorna token de sessão (auto-login)
↓
Frontend: handleLoginResponse → redireciona para complete-profile ou select-companyFluxo de Primeiro Acesso (Convite)
Tomador cadastra prestador → Backend cria usuário com AuthProvider.INVITE
↓
Gera AppLoginToken tipo INVITE (7 dias de validade)
↓
Envia email de convite com link: /auth/first-access?token=xxx
↓
Prestador clica no link → SetPasswordPage
↓
GET /auth/validate-token → { name, email, isInvite: true }
↓
Exibe formulário "Bem-vindo! Crie sua senha" com nome/email disabled
↓
POST /auth/reset-password → define senha, remove INVITE provider, adiciona EMAIL provider, ativa UserCompany
↓
Auto-login → redireciona para complete-profile (se falta phone) ou select-companyToken de Acesso (AppLoginToken)
| Campo | Tipo | Descrição |
|---|---|---|
token | UUID | Token único gerado com crypto.randomUUID() |
type | Enum | INVITE ou PASSWORD_RESET |
userId | UUID | Usuário associado |
companyId | UUID? | Opcional (não usado em password reset) |
expiresAt | DateTime | 24h para reset, 7 dias para invite |
usedAt | DateTime? | Marcado quando o token é consumido |