Skip to content

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-company

Construçã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

EndpointMétodoDescrição
/auth/socialPOSTAutentica com código OAuth e retorna dados do usuário
/auth/loginPOSTLogin com email e senha (envia código 2FA)
/auth/verify-codePOSTVerifica código 2FA e completa login
/auth/registerPOSTCadastro com email e senha
/auth/verify-emailPOSTEnvia código de verificação de email
/auth/verify-email-codePOSTVerifica código de email
/auth/forgot-passwordPOSTSolicita link de recuperação de senha
/auth/validate-tokenGETValida token de acesso (query param token)
/auth/reset-passwordPOSTRedefine senha com token e faz auto-login
/auth/complete-profilePOSTCompleta dados do perfil do usuário
/auth/companiesGETLista empresas do usuário autenticado
/auth/create-companyPOSTCadastra nova empresa
/users/meGETRetorna dados do usuário autenticado
/users/mePUTAtualiza perfil do usuário (nome, telefone, CPF)
/users/me/passwordPUTAltera a senha do usuário

Headers de Requisição

Todas as requisições autenticadas incluem automaticamente:

HeaderOrigemDescrição
AuthorizationlocalStorageToken Bearer do usuário
x-company-idsessionStorageID 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áginaRotaDescrição
LoginPage/loginTela de login social + email
RegisterPage/auth/registerCadastro com email e senha
OAuthCallback/auth/callback/:providerCallback do OAuth (popup)
CompleteProfilePage/complete-profileCompletar cadastro
SelectCompanyPage/select-companySelecionar 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 para login antes de renderizar — a tela 404 não é exibida.
  • Usuário logado: a página é renderizada em full-screen (standalone), sem TopHeader nem menu lateral. O botão "Ir para o dashboard" usa resolveAuthLandingRoute() 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-company

Fluxo 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-company

Token de Acesso (AppLoginToken)

CampoTipoDescrição
tokenUUIDToken único gerado com crypto.randomUUID()
typeEnumINVITE ou PASSWORD_RESET
userIdUUIDUsuário associado
companyIdUUID?Opcional (não usado em password reset)
expiresAtDateTime24h para reset, 7 dias para invite
usedAtDateTime?Marcado quando o token é consumido