FEAT: oauth sign in

This commit is contained in:
Gabriel Rosa
2023-01-24 19:19:00 -03:00
parent 28efdf95d9
commit 2bc060b13d
9 changed files with 281 additions and 11 deletions
+106 -1
View File
@@ -8,6 +8,10 @@ import {
Inject,
UseFilters,
Get,
UseGuards,
Redirect,
Req,
Param,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import {
@@ -35,12 +39,17 @@ import {
AuthSignInRes,
} from './dtos/login';
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
import { AuthGuard } from '@nestjs/passport';
import { Request } from 'express';
import ErrorCodes, { OauthErrors } from 'src/utils/errorCodes';
import jwt from 'jsonwebtoken';
@ApiTags('Auth')
@UseFilters(new GrpcToHttpExceptionFilter())
@Controller('auth')
export class AuthController {
logger: DadosferaLogger;
redirectUrl: string;
constructor(
@Inject(DadosferaLogger)
@@ -48,6 +57,17 @@ export class AuthController {
private authClient: AuthClientService,
) {
this.logger = dadosferaLogger.logger;
switch (process.env.ENV) {
case 'stg':
this.redirectUrl = `https://app.${process.env.ENV}.dadosfera.ai/auth/login`;
break;
case 'prd':
this.redirectUrl = `https://app.dadosfera.ai/auth/login`;
break;
default:
this.redirectUrl = `http://localhost:4200/auth/login`;
}
}
@Post('sign-in')
@@ -204,8 +224,93 @@ export class AuthController {
@Authenticated()
@Get('verify-access-token')
@HttpCode(HttpStatus.OK)
verifyAccessToken() {
return { access_token_status: 'valid' };
}
@Get('session/:session')
getSession(@Param('session') session: string) {
return this.authClient.getSession(session);
}
@Get('oauth/google')
@UseGuards(AuthGuard('google-login'))
googleOauth() {
return true;
}
@Get('oauth/google/callback')
@UseGuards(AuthGuard('google-login'))
@Redirect()
async googleOauthCallback(@Req() req) {
const { url, email, token, language } = await this.callback(req);
if (url.searchParams.get('error')) return { url: url.href };
await this.authClient
.oauthSignIn({
username: email,
token,
})
.then(({ session }) => {
url.searchParams.set('session', session);
})
.catch((err) => {
let error = OauthErrors.INVALID_CREDENTIALS[language].error;
let error_description =
OauthErrors.INVALID_CREDENTIALS[language].error_description;
switch (err.details) {
case ErrorCodes.USER.NOT_FOUND:
error = OauthErrors.USER_NOT_FOUND[language].error;
error_description =
OauthErrors.USER_NOT_FOUND[language].error_description(email);
break;
case ErrorCodes.AUTH.UNAUTHORIZED:
error = OauthErrors.INVALID_SESSION[language].error;
error_description =
OauthErrors.INVALID_SESSION[language].error_description;
break;
}
url.searchParams.set('error', error);
url.searchParams.set('error_description', error_description);
return null;
});
return { url: url.href };
}
async callback(req: Request) {
const { error, state } = req.query;
const { authInfo } = req;
const url = new URL(this.redirectUrl);
let email, token, error_title, error_description;
let language: 'pt-br' | 'en-us' = 'pt-br';
const stateObject = jwt.verify(
state as string,
process.env.JWT_PRIVATE_KEY,
);
if (typeof stateObject != 'string') language = stateObject.language;
if (error || !authInfo) {
this.logger.error(error);
if (!authInfo) this.logger.error('No authInfo', { request: req });
error_title = OauthErrors.INVALID_CREDENTIALS[language].error;
error_description =
OauthErrors.INVALID_CREDENTIALS[language].error_description;
if (error) error_description += ` - [${error}]`;
} else {
const { accessToken } = authInfo as any;
const { _json: userInfo } = req.user as any;
email = userInfo.email;
token = accessToken;
}
if (error_title) {
url.searchParams.set('error', error_title);
url.searchParams.set('error_description', error_description);
}
return { token, email, url, language };
}
}
+8 -1
View File
@@ -6,12 +6,19 @@ import { AuthController } from './auth.controller';
import { AuthClientService } from './auth.service';
import { DucClient } from '../duc/client.config';
import { GoogleLoginStrategy } from './passport-strategies/google-strategy';
import { getOauthSecrets } from 'src/utils/OauthSecrets';
const client = new DucClient();
@Module({
imports: [ClientsModule.register([client.providerOptions])],
controllers: [AuthController],
providers: [AuthClientService, DadosferaLogger],
providers: [
AuthClientService,
DadosferaLogger,
GoogleLoginStrategy,
{ provide: 'OAUTH_SECRETS', useValue: getOauthSecrets() },
],
exports: [AuthClientService],
})
export class AuthModule {}
+9
View File
@@ -157,4 +157,13 @@ export class AuthClientService implements OnModuleInit {
this.authService.AuthVerifyTotpMfa({ accessToken, totp }),
);
}
async getSession(session: string) {
return lastValueFrom(this.authService.AuthGetSession({ session }));
}
async oauthSignIn(data: { username: string; token: string }) {
const { username, token } = data;
return lastValueFrom(this.authService.AuthOauthSignIn({ token, username }));
}
}
@@ -0,0 +1,46 @@
import {
AuthenticateOptionsGoogle,
Profile,
Strategy,
StrategyOptions,
} from 'passport-google-oauth20';
import { PassportStrategy } from '@nestjs/passport';
import { Inject, Injectable } from '@nestjs/common';
import { OauthSecrets } from 'src/utils/OauthSecrets';
import { Request } from 'express';
import jwt from 'jsonwebtoken';
@Injectable()
export class GoogleLoginStrategy extends PassportStrategy(
Strategy,
'google-login',
) {
redirect_uri: string;
constructor(
@Inject('OAUTH_SECRETS')
private readonly oauthSecrets: OauthSecrets,
) {
const options: StrategyOptions = {
clientID: oauthSecrets['google-login'].client_id,
clientSecret: oauthSecrets['google-login'].client_secret,
callbackURL: oauthSecrets['google-login'].client_secret,
scope: ['email', 'profile', 'openid'],
};
const verify = (
accessToken: string,
refreshToken: string,
profile: Profile,
done,
) => {
return done(null, profile, { accessToken, refreshToken });
};
super(options, verify);
}
authenticate(req: Request, options: AuthenticateOptionsGoogle) {
const language = req.headers['dadosfera-lang'] || req.query.language;
options.state = jwt.sign({ language }, process.env.JWT_PRIVATE_KEY);
super.authenticate(req, options);
}
}