<!-- json
{
  "sourceId": "recipe-auth-google-oauth",
  "repositoryId": "recipe-manager",
  "filePath": "modules/api/src/web/auth.config.ts",
  "capabilityTags": ["auth", "oauth", "passport", "google"],
  "operationIds": ["enable-auth-session"],
  "applicability": ["sample-project"],
  "notes": [
    "Reference Google OAuth strategy wiring and principal mapping."
  ]
}
-->

# recipe-auth-google-oauth

- Source: `modules/api/src/web/auth.config.ts`
- Capability tags: auth, oauth, passport, google
- Applicability: sample-project
- Notes:
  - Reference Google OAuth strategy wiring and principal mapping.

## Reference Code

```typescript
import { OAuth2Strategy } from 'passport-google-oauth';

import type { Authenticator, Authorizer } from '@travetto/auth';
import { PassportAuthenticator } from '@travetto/auth-web-passport';
import { Config } from '@travetto/config';
import { InjectableFactory } from '@travetto/di';
import type { PostgresModelService } from '@travetto/model-postgres';
import type { WebConfig } from '@travetto/web';

import { User, userByExternalId } from '../model/user.ts';
import { type FamilyGroupService } from '../services/family-group.ts';

export const GOOGLE = Symbol.for('google');

type GoogleAuthShape = {
  id: string;
  name?: { familyName?: string; givenName?: string };
  email?: string;
  emails?: { value: string; type?: string }[];
};

@Config('google.auth')
class GoogleConfig {
  clientID: string;
  clientSecret: string;
}

class AuthConfig {
  @InjectableFactory()
  static getAuthorizer(modelService: PostgresModelService, familyGroupService: FamilyGroupService): Authorizer {
    return {
      authorize: async ({ details, ...p }) => {
        const existingUser = await modelService.getByIndex(User, userByExternalId, { externalId: p.id }).catch(() => undefined);
        const fullName = [details?.name?.givenName, details?.name?.familyName].filter(Boolean).join(' ');
        const user = await modelService.upsertByIndex(
          User,
          userByExternalId,
          User.from({
            id: existingUser?.id,
            externalId: p.id,
            name: fullName || existingUser?.name || 'Chef',
            email: (details?.email || existingUser?.email)?.toLowerCase()
          })
        );
        user.familyIds = await familyGroupService.getFamilyIdsForUser(user.id);

        return {
          ...p,
          id: user.id,
          details: user
        };
      }
    };
  }

  @InjectableFactory(GOOGLE)
  static getAuthenticator(config: GoogleConfig, web: WebConfig): Authenticator<GoogleAuthShape> {
    return new PassportAuthenticator<GoogleAuthShape>(
      'google',
      new OAuth2Strategy({ ...config, callbackURL: `${web.baseUrl}/auth/login` }, (_token, _refreshToken, profile, done) => {
        done(null, profile);
      }),
      ({ id, name, emails }) => ({
        id,
        details: {
          name,
          email: emails?.[0]?.value
        }
      }),
      {
        scope: ['https://www.googleapis.com/auth/userinfo.profile', 'https://www.googleapis.com/auth/userinfo.email']
      }
    );
  }
}
```
