Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ever teams social signup services (#7878) #7884

Merged
merged 1 commit into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/contracts/src/social-account.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,11 @@ export interface ISocialAccountCreateInput extends ISocialAccount {}

export interface ISocialAccountUpdateInput
extends Partial<Pick<ISocialAccountCreateInput, 'provider' | 'providerAccountId'>> {}

export interface ISocialAccountExistUser extends Partial<ISocialAccountBase> {
isUserExists: boolean;
}

export interface ISocialAccountLogin extends Pick<ISocialAccountBase, 'provider'> {
token: string;
}
35 changes: 33 additions & 2 deletions packages/core/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ import {
import { ApiTags, ApiOperation, ApiResponse, ApiOkResponse, ApiBadRequestResponse } from '@nestjs/swagger';
import { CommandBus } from '@nestjs/cqrs';
import { I18nLang } from 'nestjs-i18n';
import { IAuthResponse, IUserSigninWorkspaceResponse, LanguagesEnum } from '@gauzy/contracts';
import {
IAuthResponse,
ISocialAccount,
ISocialAccountExistUser,
IUserSigninWorkspaceResponse,
LanguagesEnum
} from '@gauzy/contracts';
import { Public } from '@gauzy/common';
import { AuthService } from './auth.service';
import { User as IUser } from '../user/user.entity';
Expand All @@ -37,7 +43,7 @@ import {
WorkspaceSigninEmailVerifyDTO,
WorkspaceSigninDTO
} from './dto';
import { SocialLoginBodyRequestDTO } from './social-account/dto';
import { FindUserBySocialLoginDTO, SocialLoginBodyRequestDTO } from './social-account/dto';

@ApiTags('Auth')
@Controller()
Expand Down Expand Up @@ -160,6 +166,23 @@ export class AuthController {
);
}

/**
* Check if any user with the given provider infos exists

* @param input An object that contains the provider name and the provider Account ID
* @returns A promise that resolves to a boolean specifying if the user exists or not
*/

@HttpCode(HttpStatus.OK)
@Post('/signup.provider.social')
@Public()
@UseValidationPipe()
async socialSignupCheckIfUserExistsBySocial(
@Body() input: FindUserBySocialLoginDTO
): Promise<ISocialAccountExistUser> {
return await this.authService.socialSignupCheckIfUserExistsBySocial(input);
}

/**
* Sign in workspaces by email social media.
*
Expand All @@ -174,6 +197,14 @@ export class AuthController {
return await this.authService.signinWorkspacesByEmailSocial(input, convertNativeParameters(input.includeTeams));
}

@HttpCode(HttpStatus.OK)
@Post('/signup.link.account')
@Public()
@UseValidationPipe()
async linkUserToSocialAccount(@Body() input: SocialLoginBodyRequestDTO): Promise<ISocialAccount> {
return await this.authService.linkUserToSocialAccount(input);
}

/**
* Send a workspace sign-in code by email.
*
Expand Down
54 changes: 51 additions & 3 deletions packages/core/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ import {
IOrganizationTeam,
IWorkspaceResponse,
ITenant,
ProviderEnum
ProviderEnum,
ISocialAccountBase,
ISocialAccountExistUser,
ISocialAccountLogin,
ISocialAccount
} from '@gauzy/contracts';
import { environment } from '@gauzy/config';
import { SocialAuthService } from '@gauzy/auth';
Expand Down Expand Up @@ -226,6 +230,19 @@ export class AuthService extends SocialAuthService {
}
}

/**
* Check if any user with the given provider infos exists
* This function is used to facilitate the GauzyAdapter in Ever Teams try to create new Users or only signin them

* @param input An object that contains the provider name and the provider Account ID
* @returns A promise that resolves to a boolean specifying if the user exists or not
*/
async socialSignupCheckIfUserExistsBySocial(input: ISocialAccountBase): Promise<ISocialAccountExistUser> {
const user = await this.socialAccountService.findUserBySocialId(input);
if (!user) return { isUserExists: false };
return { isUserExists: true };
}

/**
* Authenticate a user by email from social media and return user workspaces.
*
Expand All @@ -235,7 +252,7 @@ export class AuthService extends SocialAuthService {
* @throws UnauthorizedException if authentication fails.
*/
async signinWorkspacesByEmailSocial(
input: { provider: ProviderEnum; token: string },
input: ISocialAccountLogin,
includeTeams: boolean
): Promise<IUserSigninWorkspaceResponse> {
const { provider: inputProvider, token } = input;
Expand Down Expand Up @@ -267,7 +284,7 @@ export class AuthService extends SocialAuthService {
users.map(async (user) => {
return await this.socialAccountService.registerSocialAccount({
provider,
providerAccountId: providerData.id,
providerAccountId,
userId: user.id,
user,
tenantId: user.tenantId,
Expand Down Expand Up @@ -312,6 +329,37 @@ export class AuthService extends SocialAuthService {
}
}

/**
* This method links a user to an oAuth account when signin/singup with a social media provider
*
* @param input The body request that contains the token to be verified and the provider name
* @returns A promise that resolved with an account creation
*/

async linkUserToSocialAccount(input: ISocialAccountLogin): Promise<ISocialAccount> {
try {
const { provider: inputProvider, token } = input;

const providerData = await this.verifyOAuthToken(inputProvider, token);
const { email, id, provider } = providerData;
const user = await this.userService.getUserByEmail(email);

if (!user) {
throw new BadRequestException('User for these credentials could not be found');
}
return await this.socialAccountService.registerSocialAccount({
provider,
providerAccountId: id,
userId: user.id,
user,
tenantId: user.tenantId,
tenant: user.tenant
});
} catch (error) {
throw new BadRequestException('User for these credentials could not be found');
}
}

/**
* Generate a JWT token for the given user.
*
Expand Down
14 changes: 12 additions & 2 deletions packages/core/src/auth/social-account/dto/social-login.dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
import { ApiProperty, PickType } from '@nestjs/swagger';
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
import { ProviderEnum } from '@gauzy/contracts';
import { ISocialAccountBase, ProviderEnum } from '@gauzy/contracts';
import { IncludeTeamsDTO } from '../../../user/dto/include-teams.dto';

/**
Expand All @@ -17,3 +17,13 @@ export class SocialLoginBodyRequestDTO extends IncludeTeamsDTO {
@IsString()
readonly token: string;
}

export class FindUserBySocialLoginDTO
extends PickType(SocialLoginBodyRequestDTO, ['provider'])
implements ISocialAccountBase
{
@ApiProperty({ type: () => String })
@IsNotEmpty()
@IsString()
readonly providerAccountId: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { SocialAccountService } from './social-account.service';
import { RolePermissionModule } from '../../role-permission';
import { SocialAccount } from './social-account.entity';
import { UserModule } from '../../user';

@Module({
imports: [
RouterModule.register([{ path: '/social-account', module: SocialAccountModule }]),
TypeOrmModule.forFeature([SocialAccount]),
MikroOrmModule.forFeature([SocialAccount]),
UserModule,
RolePermissionModule
],
providers: [SocialAccountService],
Expand Down
22 changes: 19 additions & 3 deletions packages/core/src/auth/social-account/social-account.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import { ISocialAccount, ISocialAccountBase } from '@gauzy/contracts';
import { TenantAwareCrudService } from '../../core/crud';
import { SocialAccount } from './social-account.entity';
import { MicroOrmSocialAccountRepository, TypeOrmSocialAccountRepository } from './repository';
import { User } from '../../user';
import { User, UserService } from '../../user';

@Injectable()
export class SocialAccountService extends TenantAwareCrudService<SocialAccount> {
constructor(
@InjectRepository(SocialAccount) readonly typeOrmSocialAccountRepository: TypeOrmSocialAccountRepository,
readonly mikroOrmSocialAccountRepository: MicroOrmSocialAccountRepository
readonly mikroOrmSocialAccountRepository: MicroOrmSocialAccountRepository,
private readonly userService: UserService
) {
super(typeOrmSocialAccountRepository, mikroOrmSocialAccountRepository);
}
Expand All @@ -35,6 +36,21 @@ export class SocialAccountService extends TenantAwareCrudService<SocialAccount>
}

async findUserBySocialId(input: ISocialAccountBase): Promise<User> {
return (await this.findAccountByProvider(input)).user;
try {
const account = await this.findAccountByProvider(input);
const user = account?.user;
if (!user) {
throw new BadRequestException('The user with this accoubt details does not exists');
}
return user;
} catch (error) {
throw new BadRequestException('The user with this accoubt details does not exists');
}
}

async signupFindUserByEmail(email: string): Promise<boolean> {
const user = await this.userService.getUserByEmail(email);
if (!user) return false;
return true;
}
}
Loading