Skip to content
This repository has been archived by the owner on Apr 19, 2023. It is now read-only.

Commit

Permalink
♻️ Move merging accounts to auth.service
Browse files Browse the repository at this point in the history
  • Loading branch information
AnandChowdhary committed Nov 16, 2020
1 parent 9a9cca5 commit 0c2b15b
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 78 deletions.
10 changes: 10 additions & 0 deletions src/modules/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,14 @@ export class AuthController {
): Promise<TokenResponse> {
return this.authService.loginWithEmailToken(ip, userAgent, token);
}

@Post('merge-accounts')
@RateLimit({
points: 10,
duration: 60,
errorMessage: 'Wait for 60 seconds before trying to merge accounts again',
})
async merge(@Body('token') token: string): Promise<void> {
return this.authService.mergeUsers(token);
}
}
8 changes: 4 additions & 4 deletions src/modules/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { PassportModule } from '@nestjs/passport';
import { ApiKeysModule } from '../api-keys/api-keys.module';
import { ApprovedSubnetsModule } from '../approved-subnets/approved-subnets.module';
import { ApprovedSubnetsService } from '../approved-subnets/approved-subnets.service';
import { MailModule } from '../../providers/mail/mail.module';
import { GeolocationModule } from '../../providers/geolocation/geolocation.module';
import { MailModule } from '../../providers/mail/mail.module';
import { PrismaModule } from '../../providers/prisma/prisma.module';
import { PwnedModule } from '../../providers/pwned/pwned.module';
import { TokensModule } from '../../providers/tokens/tokens.module';
import { TwilioModule } from '../../providers/twilio/twilio.module';
import { ApiKeysModule } from '../api-keys/api-keys.module';
import { ApprovedSubnetsModule } from '../approved-subnets/approved-subnets.module';
import { ApprovedSubnetsService } from '../approved-subnets/approved-subnets.service';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { StaartStrategy } from './staart.strategy';
Expand Down
65 changes: 60 additions & 5 deletions src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import {
Injectable,
NotFoundException,
UnauthorizedException,
UnprocessableEntityException,
UnprocessableEntityException
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Authenticator } from '@otplib/core';
import { emails, MfaMethod, users } from '@prisma/client';
import { emails, emailsDelegate, MfaMethod, users } from '@prisma/client';
import { compare, hash } from 'bcrypt';
import { createHash } from 'crypto';
import got from 'got/dist/source';
Expand All @@ -32,7 +32,7 @@ import {
SESSION_NOT_FOUND,
UNVERIFIED_EMAIL,
UNVERIFIED_LOCATION,
USER_NOT_FOUND,
USER_NOT_FOUND
} from '../../errors/errors.constants';
import { safeEmail } from '../../helpers/safe-email';
import { GeolocationService } from '../../providers/geolocation/geolocation.service';
Expand All @@ -45,8 +45,9 @@ import {
EMAIL_MFA_TOKEN,
EMAIL_VERIFY_TOKEN,
LOGIN_ACCESS_TOKEN,
MERGE_ACCOUNTS_TOKEN,
MULTI_FACTOR_TOKEN,
PASSWORD_RESET_TOKEN,
PASSWORD_RESET_TOKEN
} from '../../providers/tokens/tokens.constants';
import { TokensService } from '../../providers/tokens/tokens.service';
import { TwilioService } from '../../providers/twilio/twilio.service';
Expand All @@ -56,7 +57,7 @@ import {
AccessTokenClaims,
MfaTokenPayload,
TokenResponse,
TotpTokenResponse,
TotpTokenResponse
} from './auth.interface';

@Injectable()
Expand Down Expand Up @@ -696,4 +697,58 @@ export class AuthService {
}
return ids;
}

async mergeUsers(token: string): Promise<void> {
let baseUserId: number | undefined = undefined;
let mergeUserId: number | undefined = undefined;
try {
const result = this.tokensService.verify<{
baseUserId: number;
mergeUserId: number;
}>(MERGE_ACCOUNTS_TOKEN, token);
baseUserId = result.baseUserId;
mergeUserId = result.mergeUserId;
} catch (error) {}
if (!baseUserId || !mergeUserId)
throw new BadRequestException(USER_NOT_FOUND);
return this.merge(baseUserId, mergeUserId);
}

private async merge(baseUserId: number, mergeUserId: number): Promise<void> {
const baseUser = await this.prisma.users.findOne({
where: { id: baseUserId },
});
const mergeUser = await this.prisma.users.findOne({
where: { id: mergeUserId },
});
if (!baseUser || !mergeUser) throw new NotFoundException(USER_NOT_FOUND);

const combinedUser = { ...baseUser };
Object.keys(mergeUser).forEach((key) => {
if (mergeUser[key]) combinedUser[key] = mergeUser[key];
});
await this.prisma.users.update({
where: { id: baseUserId },
data: combinedUser,
});

for await (const dataType of [
this.prisma.memberships,
this.prisma.emails,
this.prisma.sessions,
this.prisma.approvedSubnets,
this.prisma.backupCodes,
this.prisma.identities,
this.prisma.auditLogs,
]) {
for await (const item of await (dataType as emailsDelegate).findMany({
where: { user: { id: mergeUserId } },
select: { id: true },
}))
await (dataType as emailsDelegate).update({
where: { id: item.id },
data: { user: { connect: { id: baseUserId } } },
});
}
}
}
2 changes: 1 addition & 1 deletion src/modules/groups/groups.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { AuditLog } from '../audit-logs/audit-log.decorator';
import { Scopes } from '../auth/scope.decorator';
import { ReplaceGroupDto, UpdateGroupDto } from './groups.dto';
import { GroupsService } from './groups.service';
import { SelectIncludePipe } from 'src/pipes/select-include.pipe';
import { SelectIncludePipe } from '../../pipes/select-include.pipe';

@Controller('groups')
export class GroupController {
Expand Down
11 changes: 0 additions & 11 deletions src/modules/users/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,4 @@ export class UserController {
): Promise<void> {
return this.usersService.requestMerge(Number(id), email);
}

@Post('merge')
@Scopes('user-{userId}:merge')
@RateLimit({
points: 10,
duration: 60,
errorMessage: 'Wait for 60 seconds before trying to merge again',
})
async merge(@Body('token') token: string): Promise<void> {
return this.usersService.mergeUsers(token);
}
}
59 changes: 2 additions & 57 deletions src/modules/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import {
emailsDelegate,
users,
usersCreateInput,
usersOrderByInput,
Expand All @@ -20,12 +19,12 @@ import {
USER_NOT_FOUND,
} from '../../errors/errors.constants';
import { safeEmail } from '../../helpers/safe-email';
import { Expose } from '../../providers/prisma/prisma.interface';
import { AuthService } from '../auth/auth.service';
import { MailService } from '../../providers/mail/mail.service';
import { Expose } from '../../providers/prisma/prisma.interface';
import { PrismaService } from '../../providers/prisma/prisma.service';
import { MERGE_ACCOUNTS_TOKEN } from '../../providers/tokens/tokens.constants';
import { TokensService } from '../../providers/tokens/tokens.service';
import { AuthService } from '../auth/auth.service';
import { PasswordUpdateInput } from './users.interface';

@Injectable()
Expand Down Expand Up @@ -139,58 +138,4 @@ export class UsersService {
},
});
}

async mergeUsers(token: string): Promise<void> {
let baseUserId: number | undefined = undefined;
let mergeUserId: number | undefined = undefined;
try {
const result = this.tokensService.verify<{
baseUserId: number;
mergeUserId: number;
}>(MERGE_ACCOUNTS_TOKEN, token);
baseUserId = result.baseUserId;
mergeUserId = result.mergeUserId;
} catch (error) {}
if (!baseUserId || !mergeUserId)
throw new BadRequestException(USER_NOT_FOUND);
return this.merge(baseUserId, mergeUserId);
}

private async merge(baseUserId: number, mergeUserId: number): Promise<void> {
const baseUser = await this.prisma.users.findOne({
where: { id: baseUserId },
});
const mergeUser = await this.prisma.users.findOne({
where: { id: mergeUserId },
});
if (!baseUser || !mergeUser) throw new NotFoundException(USER_NOT_FOUND);

const combinedUser = { ...baseUser };
Object.keys(mergeUser).forEach((key) => {
if (mergeUser[key]) combinedUser[key] = mergeUser[key];
});
await this.prisma.users.update({
where: { id: baseUserId },
data: combinedUser,
});

for await (const dataType of [
this.prisma.memberships,
this.prisma.emails,
this.prisma.sessions,
this.prisma.approvedSubnets,
this.prisma.backupCodes,
this.prisma.identities,
this.prisma.auditLogs,
]) {
for await (const item of await (dataType as emailsDelegate).findMany({
where: { user: { id: mergeUserId } },
select: { id: true },
}))
await (dataType as emailsDelegate).update({
where: { id: item.id },
data: { user: { connect: { id: baseUserId } } },
});
}
}
}

0 comments on commit 0c2b15b

Please sign in to comment.