D gamer007/add microsoft oauth (#5103)
Need to create a new branch because original branch name is `main` and we cannot push additional commits Linked to https://github.com/twentyhq/twenty/pull/4718   --------- Co-authored-by: DGamer007 <prajapatidhruv266@gmail.com>
This commit is contained in:
@ -21,6 +21,7 @@ import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user
|
||||
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
|
||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
|
||||
import { MicrosoftAuthController } from 'src/engine/core-modules/auth/controllers/microsoft-auth.controller';
|
||||
import { AppTokenService } from 'src/engine/core-modules/app-token/services/app-token.service';
|
||||
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
||||
import { ConnectedAccountObjectMetadata } from 'src/modules/connected-account/standard-objects/connected-account.object-metadata';
|
||||
@ -65,6 +66,7 @@ const jwtModule = JwtModule.registerAsync({
|
||||
],
|
||||
controllers: [
|
||||
GoogleAuthController,
|
||||
MicrosoftAuthController,
|
||||
GoogleAPIsAuthController,
|
||||
VerifyAuthController,
|
||||
],
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common';
|
||||
|
||||
import { Response } from 'express';
|
||||
|
||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
import { MicrosoftOAuthGuard } from 'src/engine/core-modules/auth/guards/microsoft-oauth.guard';
|
||||
import { MicrosoftProviderEnabledGuard } from 'src/engine/core-modules/auth/guards/microsoft-provider-enabled.guard';
|
||||
import { AuthService } from 'src/engine/core-modules/auth/services/auth.service';
|
||||
import { TokenService } from 'src/engine/core-modules/auth/services/token.service';
|
||||
import { MicrosoftRequest } from 'src/engine/core-modules/auth/strategies/microsoft.auth.strategy';
|
||||
|
||||
@Controller('auth/microsoft')
|
||||
export class MicrosoftAuthController {
|
||||
constructor(
|
||||
private readonly tokenService: TokenService,
|
||||
private readonly typeORMService: TypeORMService,
|
||||
private readonly authService: AuthService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(MicrosoftProviderEnabledGuard, MicrosoftOAuthGuard)
|
||||
async microsoftAuth() {
|
||||
// As this method is protected by Microsoft Auth guard, it will trigger Microsoft SSO flow
|
||||
return;
|
||||
}
|
||||
|
||||
@Get('redirect')
|
||||
@UseGuards(MicrosoftProviderEnabledGuard, MicrosoftOAuthGuard)
|
||||
async microsoftAuthRedirect(
|
||||
@Req() req: MicrosoftRequest,
|
||||
@Res() res: Response,
|
||||
) {
|
||||
const { firstName, lastName, email, picture, workspaceInviteHash } =
|
||||
req.user;
|
||||
|
||||
const user = await this.authService.signInUp({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
picture,
|
||||
workspaceInviteHash,
|
||||
fromSSO: true,
|
||||
});
|
||||
|
||||
const loginToken = await this.tokenService.generateLoginToken(user.email);
|
||||
|
||||
return res.redirect(this.tokenService.computeRedirectURI(loginToken.token));
|
||||
}
|
||||
}
|
||||
@ -16,8 +16,7 @@ export class GoogleOauthGuard extends AuthGuard('google') {
|
||||
if (workspaceInviteHash && typeof workspaceInviteHash === 'string') {
|
||||
request.params.workspaceInviteHash = workspaceInviteHash;
|
||||
}
|
||||
const activate = (await super.canActivate(context)) as boolean;
|
||||
|
||||
return activate;
|
||||
return (await super.canActivate(context)) as boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
@Injectable()
|
||||
export class MicrosoftOAuthGuard extends AuthGuard('microsoft') {
|
||||
constructor() {
|
||||
super({
|
||||
prompt: 'select_account',
|
||||
});
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext) {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const workspaceInviteHash = request.query.inviteHash;
|
||||
|
||||
if (workspaceInviteHash && typeof workspaceInviteHash === 'string') {
|
||||
request.params.workspaceInviteHash = workspaceInviteHash;
|
||||
}
|
||||
|
||||
return (await super.canActivate(context)) as boolean;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
import { Injectable, CanActivate, NotFoundException } from '@nestjs/common';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
import { MicrosoftStrategy } from 'src/engine/core-modules/auth/strategies/microsoft.auth.strategy';
|
||||
|
||||
@Injectable()
|
||||
export class MicrosoftProviderEnabledGuard implements CanActivate {
|
||||
constructor(private readonly environmentService: EnvironmentService) {}
|
||||
|
||||
canActivate(): boolean | Promise<boolean> | Observable<boolean> {
|
||||
if (!this.environmentService.get('AUTH_MICROSOFT_ENABLED')) {
|
||||
throw new NotFoundException('Microsoft auth is not enabled');
|
||||
}
|
||||
|
||||
new MicrosoftStrategy(this.environmentService);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
|
||||
import { Request } from 'express';
|
||||
import { VerifyCallback } from 'passport-google-oauth20';
|
||||
import { Strategy } from 'passport-microsoft';
|
||||
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
|
||||
export type MicrosoftRequest = Omit<
|
||||
Request,
|
||||
'user' | 'workspace' | 'cacheVersion'
|
||||
> & {
|
||||
user: {
|
||||
firstName?: string | null;
|
||||
lastName?: string | null;
|
||||
email: string;
|
||||
picture: string | null;
|
||||
workspaceInviteHash?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export class MicrosoftStrategy extends PassportStrategy(Strategy, 'microsoft') {
|
||||
constructor(environmentService: EnvironmentService) {
|
||||
super({
|
||||
clientID: environmentService.get('AUTH_MICROSOFT_CLIENT_ID'),
|
||||
clientSecret: environmentService.get('AUTH_MICROSOFT_CLIENT_SECRET'),
|
||||
callbackURL: environmentService.get('AUTH_MICROSOFT_CALLBACK_URL'),
|
||||
tenant: environmentService.get('AUTH_MICROSOFT_TENANT_ID'),
|
||||
scope: ['user.read'],
|
||||
passReqToCallback: true,
|
||||
});
|
||||
}
|
||||
|
||||
authenticate(req: any, options: any) {
|
||||
options = {
|
||||
...options,
|
||||
state: JSON.stringify({
|
||||
workspaceInviteHash: req.params.workspaceInviteHash,
|
||||
}),
|
||||
};
|
||||
|
||||
return super.authenticate(req, options);
|
||||
}
|
||||
|
||||
async validate(
|
||||
request: MicrosoftRequest,
|
||||
accessToken: string,
|
||||
refreshToken: string,
|
||||
profile: any,
|
||||
done: VerifyCallback,
|
||||
): Promise<void> {
|
||||
const { name, emails, photos } = profile;
|
||||
|
||||
const state =
|
||||
typeof request.query.state === 'string'
|
||||
? JSON.parse(request.query.state)
|
||||
: undefined;
|
||||
|
||||
const email = emails?.[0]?.value ?? null;
|
||||
|
||||
if (!email) {
|
||||
throw new BadRequestException('No email found in your Microsoft profile');
|
||||
}
|
||||
|
||||
const user: MicrosoftRequest['user'] = {
|
||||
email,
|
||||
firstName: name.givenName,
|
||||
lastName: name.familyName,
|
||||
picture: photos?.[0]?.value,
|
||||
workspaceInviteHash: state.workspaceInviteHash,
|
||||
};
|
||||
|
||||
done(null, user);
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,9 @@ class AuthProviders {
|
||||
|
||||
@Field(() => Boolean)
|
||||
password: boolean;
|
||||
|
||||
@Field(() => Boolean)
|
||||
microsoft: boolean;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
|
||||
@ -14,7 +14,8 @@ export class ClientConfigResolver {
|
||||
authProviders: {
|
||||
google: this.environmentService.get('AUTH_GOOGLE_ENABLED'),
|
||||
magicLink: false,
|
||||
password: true,
|
||||
password: this.environmentService.get('AUTH_PASSWORD_ENABLED'),
|
||||
microsoft: this.environmentService.get('AUTH_MICROSOFT_ENABLED'),
|
||||
},
|
||||
telemetry: {
|
||||
enabled: this.environmentService.get('TELEMETRY_ENABLED'),
|
||||
|
||||
Reference in New Issue
Block a user