michangarrito/docs/97-adr/ADR-0010-oauth-social.md
rckrdmrd 2c916e75e5 [SIMCO-V4] feat: Agregar documentación SaaS, ADRs e integraciones
Nuevas Épicas (MCH-029 a MCH-033):
- Infraestructura SaaS multi-tenant
- Auth Social (OAuth2)
- Auditoría Empresarial
- Feature Flags
- Onboarding Wizard

Nuevas Integraciones (INT-010 a INT-014):
- Email Providers (SendGrid, Mailgun, SES)
- Storage Cloud (S3, GCS, Azure)
- OAuth Social
- Redis Cache
- Webhooks Outbound

Nuevos ADRs (0004 a 0011):
- Notifications Realtime
- Feature Flags Strategy
- Storage Abstraction
- Webhook Retry Strategy
- Audit Log Retention
- Rate Limiting
- OAuth Social Implementation
- Email Multi-provider

Actualizados:
- MASTER_INVENTORY.yml
- CONTEXT-MAP.yml
- HERENCIA-SIMCO.md
- Mapas de documentación

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 01:43:15 -06:00

5.6 KiB

id type title status decision_date updated_at simco_version stakeholders tags
ADR-0010 ADR OAuth Social Strategy Accepted 2026-01-10 2026-01-10 4.0.1
Equipo MiChangarrito
oauth
authentication
google
apple
passport

ADR-0010: OAuth Social Strategy

Metadata

Campo Valor
ID ADR-0010
Estado Accepted
Fecha 2026-01-10
Autor Architecture Team
Supersede -

Contexto

MiChangarrito quiere permitir registro e inicio de sesion con cuentas sociales (Google, Apple) para reducir friccion de onboarding. Necesitamos decidir:

  1. Que proveedores soportar
  2. Como integrar con nuestro sistema de auth existente
  3. Como manejar vinculacion de cuentas

Decision

Implementamos OAuth 2.0 con Passport.js para Google y Apple, con tabla separada oauth_accounts vinculada a users.

  • Google: Principal proveedor social
  • Apple: Requerido para iOS App Store
  • Passport.js: Strategies bien mantenidas

Alternativas Consideradas

Opcion 1: Auth0/Firebase Auth

  • Pros:
    • Todo manejado
    • Multiples providers
  • Cons:
    • Costo adicional
    • Dependencia externa
    • Menos control

Opcion 2: Passport.js (Elegida)

  • Pros:
    • Open source
    • Bien documentado
    • Control total
    • Sin costos adicionales
  • Cons:
    • Mas trabajo de implementacion
    • Mantener actualizaciones

Opcion 3: Implementacion manual

  • Pros:
    • Control absoluto
  • Cons:
    • Mucho trabajo
    • Propenso a errores de seguridad
    • Mantener cambios de API

Consecuencias

Positivas

  1. Control: Logica de negocio en nuestro codigo
  2. Flexibilidad: Agregar providers facilmente
  3. Integracion: Se integra con nuestro JWT flow
  4. Costo: Sin gastos adicionales

Negativas

  1. Mantenimiento: Actualizar strategies
  2. Configuracion: Setup en consolas de providers

Implementacion

Modelo de Datos

CREATE TABLE auth.oauth_accounts (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES auth.users(id) NOT NULL,
    provider VARCHAR(20) NOT NULL, -- google, apple
    provider_user_id VARCHAR(255) NOT NULL,
    email VARCHAR(255),
    name VARCHAR(255),
    avatar_url TEXT,
    access_token TEXT,
    refresh_token TEXT,
    expires_at TIMESTAMP WITH TIME ZONE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    UNIQUE(provider, provider_user_id)
);

Flujo de Autenticacion

1. Usuario hace clic en "Continuar con Google"
2. Redirect a Google OAuth
3. Usuario autoriza
4. Callback a nuestro servidor con code
5. Intercambiar code por tokens
6. Obtener perfil de usuario
7. Buscar/crear usuario en nuestra BD
8. Generar JWT nuestro
9. Redirect a frontend con JWT

Casos de Vinculacion

Escenario Accion
Nuevo usuario, nuevo email Crear user + oauth_account
Email existe, mismo provider Error (ya vinculado)
Email existe, otro provider Ofrecer vincular cuentas
Usuario logueado vincula nueva Agregar oauth_account

Codigo de Vinculacion

async findOrCreateFromOAuth(profile: OAuthProfile): Promise<User> {
  // 1. Buscar oauth_account existente
  const existingOAuth = await this.oauthRepo.findOne({
    where: {
      provider: profile.provider,
      providerUserId: profile.id,
    },
  });

  if (existingOAuth) {
    return existingOAuth.user;
  }

  // 2. Buscar user por email
  const existingUser = await this.userRepo.findOne({
    where: { email: profile.email },
  });

  if (existingUser) {
    // Vincular automaticamente si email verificado en provider
    if (profile.emailVerified) {
      await this.createOAuthAccount(existingUser, profile);
      return existingUser;
    }

    // Sino, pedir confirmacion
    throw new EmailExistsError(profile.email, profile.provider);
  }

  // 3. Crear nuevo usuario
  const user = await this.createUser({
    email: profile.email,
    name: profile.name,
    avatarUrl: profile.avatar,
    emailVerified: profile.emailVerified,
  });

  await this.createOAuthAccount(user, profile);

  return user;
}

Providers Soportados

Google

passport.use(new GoogleStrategy({
  clientID: process.env.GOOGLE_CLIENT_ID,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET,
  callbackURL: '/auth/google/callback',
  scope: ['profile', 'email'],
}, verify));

Apple

passport.use(new AppleStrategy({
  clientID: process.env.APPLE_CLIENT_ID,
  teamID: process.env.APPLE_TEAM_ID,
  keyID: process.env.APPLE_KEY_ID,
  privateKeyString: process.env.APPLE_PRIVATE_KEY,
  callbackURL: '/auth/apple/callback',
  scope: ['name', 'email'],
}, verify));

Mobile

Google (Expo)

const [request, response, promptAsync] = Google.useAuthRequest({
  clientId: GOOGLE_CLIENT_ID,
  iosClientId: GOOGLE_IOS_CLIENT_ID,
  androidClientId: GOOGLE_ANDROID_CLIENT_ID,
});

Apple (iOS)

import * as AppleAuthentication from 'expo-apple-authentication';

const credential = await AppleAuthentication.signInAsync({
  requestedScopes: [
    AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
    AppleAuthentication.AppleAuthenticationScope.EMAIL,
  ],
});

Referencias


Fecha decision: 2026-01-10 Autores: Architecture Team