avirweb-twenty-oauth-mastery
安装 / 下载方式
TotalClaw CLI推荐
totalclaw install clawskills:clawskills~avirweb-twenty-oauth-masterycURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/clawskills%3Aclawskills~avirweb-twenty-oauth-mastery/file -o avirweb-twenty-oauth-mastery.md# Twenty CRM OAuth Mastery Skill
**Author**: Generated from extensive OAuth debugging sessions in OpenCode
**Last Updated**: 2026-02-08
**Version**: 1.0
---
## Skill Metadata
```yaml
name: twenty-oauth-mastery
description: Expert-level OAuth authentication knowledge for Twenty CRM including implementation, troubleshooting, and best practices
expertise_level: Expert/Mastery
category: Authentication
applicable_to:
- Twenty CRM authentication
- Google/Microsoft OAuth
- Token refresh management
- Domain restrictions
- Email/Calendar sync integration
prerequisites:
- Knowledge of TypeScript/JavaScript
- Understanding of OAuth 2.0 protocol
- Familiarity with NestJS framework
keywords:
- oauth
- authentication
- twenty-crm
- google-oauth
- microsoft-oauth
- token-refresh
- sync-integration
- domain-restriction
```
---
## Quick Start
### When to Use This Skill
You should use this skill when working on:
✅ **Implementing** new OAuth providers
✅ **Fixing** OAuth login issues
✅ **Setting up** automatic Gmail/Calendar sync after OAuth
✅ **Debugging** token refresh failures
✅ **Configuring** domain restrictions
✅ **Troubleshooting** redirect loops
### Quick Reference for Common Issues
| Issue | File to Check | Quick Fix |
|-------|---------------|-----------|
| Redirect loop | `auth.service.ts` | Rebuild: `npx nx build twenty-server` |
| .co domain blocked | `google-auth.controller.ts` | Add to allowlist: `['company.com', 'company.co']` |
| Sync not starting | `google.auth.strategy.ts` | Return tokens in validate() |
| Cookie not readable | Controller cookie settings | Set `httpOnly: false` |
| Infinite loop | `SignInUpGlobalScopeFormEffect.tsx` | Track processed token signatures |
---
## Core Knowledge
### 1. Twenty CRM OAuth Architecture
**Key Files**: `twenty/packages/twenty-server/src/engine/core-modules/auth/`
**Structure**:
```
auth/
├── strategies/ # Passport strategies (Google, Microsoft)
├── controllers/ # OAuth endpoints and callbacks
├── services/ # Auth logic, sync setup, token management
├── guards/ # Auth guards and validation
└── utils/ # Scope configuration, utilities
```
---
### 2. Critical Code Patterns
#### Passport Strategy Pattern (MUST FOLLOW)
```typescript
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(twentyConfigService: TwentyConfigService) {
super({
clientID: twentyConfigService.get('AUTH_GOOGLE_CLIENT_ID'),
clientSecret: twentyConfigService.get('AUTH_GOOGLE_CLIENT_SECRET'),
callbackURL: twentyConfigService.get('AUTH_GOOGLE_CALLBACK_URL'),
scope: getGoogleApisOauthScopes(),
passReqToCallback: true, // 🔴 CRITICAL: Required for request state
});
}
async validate(
request: GoogleRequest,
_accessToken: string,
_refreshToken: string,
profile: GoogleProfile,
) {
// 🔴 CRITICAL: Include tokens in return object
// Without this, automatic sync setup fails
return {
...profile,
accessToken: _accessToken,
refreshToken: _refreshToken,
hostedDomain: request.query.hosted_domain || profile.emails?.[0]?.value?.split('@')[1],
};
}
}
```
**Why This Matters**:
- `passReqToCallback: true`: Enables access to request state
- Token preservation: Required for OAuthSyncService to work
---
### 3. Common Issues & Solutions
#### Issue 1: Redirect Loop After OAuth
**Symptoms**: OAuth completes but user stuck on welcome page
**Root Causes**:
1. **Backend not compiled**: Source has fix, container running old JavaScript
**Fix**:
```bash
npx nx build twenty-server
docker restart fratres-twenty
```
2. **Missing isSingleDomainMode**: Redirect logic not in compiled code
**Check**:
```bash
docker exec fratres-twenty cat /app/dist/engine/core-modules/auth/services/auth.service.js | grep isSingleDomainMode
```
3. **Cookie domain mismatch**: Cookie not accessible
**Fix**:
```typescript
// auth.service.ts - Remove explicit domain attribute
res.cookie('tokenPair', JSON.stringify(authTokens), {
path: '/',
secure: true,
sameSite: 'lax',
httpOnly: false, // 🔴 Must be false for JavaScript access
});
```
---
#### Issue 2: Domain Enforcement Blocking .co Users
**Symptoms**: `@company.co` rejected, only `@company.com` allowed
**Three Places to Fix**:
1. **Google Strategy** (`google.auth.strategy.ts`):
```typescript
// ❌ WRONG - Hardcoded
hd: 'company.com'
// ✅ CORRECT - Remove hd parameter
// (no hd parameter)
```
2. **Controller** (`google-auth.controller.ts`):
```typescript
// ❌ WRONG - Hardcoded check
if (hostedDomain !== 'company.com') { throw ... }
// ✅ CORRECT - Allowlist
const allowedOAuthDomains = ['company.com', 'company.co'];
if (!hostedDomain || !allowedOAuthDomains.includes(hostedDomain)) {
throw new UnauthorizedException(
`Only ${allowedOAuthDomains.map(d => `@${d}`).join(', ')} allowed`
);
}
```
3. **Database** (`workspaceMetadata` table):
```sql
INSERT INTO "workspaceMetadata" ("id", "workspaceId", "key", "value", "createdAt", "updatedAt")
VALUES (gen_random_uuid(), 'workspace-id', 'approvedAccessDomains', '["company.com", "company.co"]', NOW(), NOW());
```
---
#### Issue 3: Automatic Sync Not Triggered
**Symptoms**: User logs in but connected account/sync channels not created
**Root Cause**: Tokens lost in validate() method
**Fix**:
```typescript
// google.auth.strategy.ts validate()
async validate(request, accessToken, refreshToken, profile) {
// ❌ WRONG - Tokens lost
return { ...profile };
// ✅ CORRECT - Tokens preserved
return {
...profile,
accessToken,
refreshToken,
};
}
```
**Additional Checks**:
1. Verify `auth.service.ts` calls `oauthSyncService.setupSyncForOAuthUser()` after login
2. Verify tokens are passed to sync service
3. Check Google scopes include `gmail.readonly` and `calendar.events`
4. Verify `CALENDAR_PROVIDER_GOOGLE_ENABLED=true`
---
#### Issue 4: Frontend Token Processing Loop
**Symptoms**: `SignInUpGlobalScopeFormEffect` runs repeatedly, infinite API calls
**Root Cause**: Same token processed multiple times
**Fix**:
```typescript
// SignInUpGlobalScopeFormEffect.tsx
useEffect(() => {
const tokenPairFromUrl = getAuthPairFromUrl();
if (tokenPairFromUrl) {
const tokenSignature = JSON.stringify(tokenPairFromUrl);
// 🔴 CRITICAL: Skip if already processed
if (processedTokenSignatures.current.has(tokenSignature)) {
return;
}
// Track this signature
processedTokenSignatures.current.add(tokenSignature);
// Now process the token
setAuthTokens(tokenPairFromUrl);
}
}, []);
```
---
### 4. OAuth Sync Integration
**When to Use**: Users should have Gmail/Calendar auto-connected after OAuth login
**Implementation**:
1. **Create OAuthSyncService**:
```typescript
async setupSyncForOAuthUser(input: {
workspaceId: string;
userId: string;
workspaceMemberId: string;
email: string;
accessToken: string;
refreshToken: string;
scopes: string[];
}) {
// 1. Create/update connected account with tokens
// 2. Create message channel
// 3. Create calendar channel (if enabled)
// 4. Queue initial sync jobs
}
```
2. **Integrate into AuthService**:
```typescript
// auth.service.ts:signInUpWithSocialSSO()
const { redirectUrl, authTokens } = await this.generateTokens(...);
// 🔴 CRITICAL: Call sync setup BEFORE redirect
if (provider === 'google') {
try {
await this.oauthSyncService.setupSyncForOAuthUser({
workspaceId,
userId,
email: user.email,
accessToken: authTokens.authToken.accessToken,
refreshToken: authTokens.authToken.refreshToken,
scopes: user.scopes || [],
});
} catch (error)