yikart/AiToEarn · error · AppException
ChannelAccountAlreadyConnectedToAnotherUser
ChannelAccountAlreadyConnectedToAnotherUser
Error message
ResponseCode.ChannelAccountAlreadyConnectedToAnotherUser
What it means
createOrUpdateAccount throws ChannelAccountAlreadyConnectedToAnotherUser (AppException, ResponseCode.ChannelAccountAlreadyConnectedToAnotherUser) when the platform identity already exists but belongs to a different userId, and the update path was skipped (no allowReassign) so account.userId !== input.userId. This is an ownership conflict guard preventing one user from hijacking a channel account connected by another user. A warning is logged with the input and existing account before throwing.
Source
Thrown at project/aitoearn-backend/apps/aitoearn-server/src/core/channels/auth/auth.service.ts:752
let account = await this.accountRepo.getByIdentity(identity)
let created = false
if (!account) {
account = await this.accountRepo.createByIdentity(identity, accountData)
created = true
}
if (!created && account && (account.userId === input.userId || !account.userId || input.allowReassign)) {
account = await this.accountRepo.updateByIdentity(identity, accountData) ?? account
}
if (!account) {
throw new AppException(ResponseCode.AccountCreateFailed)
}
if (account.userId !== input.userId) {
this.logger.warn(
{ input, existingAccount: account },
'Channel account already connected to another user',
)
throw new AppException(ResponseCode.ChannelAccountAlreadyConnectedToAnotherUser)
}
return account
}
private async saveSelectableCredential(
accountId: string,
platform: AccountType,
credential: PlatformAccountCredentialSnapshot,
): Promise<void> {
await this.credentialService.saveCredential(accountId, platform, {
accessToken: credential.accessToken,
refreshToken: credential.refreshToken,
expiresAt: credential.expiresAt,
scope: credential.scope,
})
}
View on GitHub (pinned to d3aa8bea5b)
Solutions
- If reassignment is intended, set allowReassign=true in the auth input so the existing account is transferred to the new user.
- Have the current owner disconnect the account first, then reconnect under the new user.
- Use a different platform account for the new user, or accept the error and surface 'account already connected to another user' in the UI.
- Verify which userId owns the identity (query by identity) before initiating the OAuth flow to avoid surprise conflicts.
Example fix
// before
await authService.handleAuthCallback({ userId, platform, ... }) // throws if owned by other user
// after
await authService.handleAuthCallback({ userId, platform, allowReassign: true, ... }) Defensive patterns
Strategy: try-catch
Validate before calling
const existing = await accountRepo.getByIdentity({ type: platform, uid: platformUid, account })
if (existing && existing.userId && existing.userId !== userId && !allowReassign) {
throw new AppException(ResponseCode.ChannelAccountAlreadyConnectedToAnotherUser)
} Type guard
function ownedByUser(a: { userId?: string } | null, userId: string): boolean {
return !!a && (!a.userId || a.userId === userId)
} Try / catch
try {
await authService.handleAuthCallback(input)
} catch (e) {
if (e instanceof AppException && e.code === ResponseCode.ChannelAccountAlreadyConnectedToAnotherUser) {
return res.status(409).json({ message: 'This channel account is already connected to another user' })
}
throw e
} Prevention
- Check ownership of the platform identity before starting the OAuth flow.
- Require explicit allowReassign=true (and user confirmation) for account takeover.
- In teams, document which member owns shared channel accounts.
- Return 409-style conflicts so clients can distinguish ownership issues from technical failures.
When it happens
Trigger: Two AiToEarn users authorizing the same platform account (same platform+uid); re-running OAuth for a channel previously connected by a teammate without allowReassign=true; YouTube accounts distinguished by handle where the same channel maps to an existing row owned elsewhere.
Common situations: Agency accounts (company TikTok/YouTube) connected by employee A, then employee B tries to connect the same one; staging data copied from prod retaining another user's ownership; testing with a colleague's platform account.
Related errors
- ResponseCode.ChannelAccountNotAuthorized
- InvalidAiTaskId
- AiLogNotFound
- ResponseCode.ChannelAccountAlreadyConnectedToAnotherUser
- RelayAccountException
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/d82d855448690b96.
Report an issue: GitHub.