Files
maestro/src/modules/auth/auth.controller.ts
T
2025-07-16 11:46:05 -03:00

422 lines
12 KiB
TypeScript

import {
Body,
Controller,
Headers,
Post,
HttpCode,
HttpStatus,
Inject,
UseFilters,
Get,
UseGuards,
Redirect,
Req,
Param,
} from '@nestjs/common';
import {
ApiHeaders,
ApiOkResponse,
ApiSecurity,
ApiTags,
} from '@nestjs/swagger';
import {
AuthChangePasswordRequest,
AuthResetPasswordRequest,
AuthVerifyResetPasswordCodeRequest,
AuthConfirmResetPasswordRequest,
AuthEnableTotpMfaRequest,
AuthDisableTotpMfaRequest,
AuthVerifyTotpMfaRequest
} from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
import {
Authenticated,
RequireAllPermissions,
} from 'src/decorators/authentication.decorator';
import { AuthClientService } from './auth.service';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { GrpcToHttpExceptionFilter } from '../../error/grpc-to-http-exception.filter';
import { RequestUser, User } from 'src/decorators/user.decorator';
import {
AuthRefreshAccessTokenReq,
AuthRefreshAccessTokenRes,
AuthSignInReq,
AuthSignInRes,
BulkEditRequest,
} 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';
import { LanguageEnum } from 'src/utils/languages.enum';
import { Language } from 'src/decorators/language.decorator';
import { ApiInternalOnlyEndpoint } from 'src/decorators/swagger.decorator';
@ApiTags('Auth')
@ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }])
@UseFilters(new GrpcToHttpExceptionFilter())
@Controller('auth')
export class AuthController {
logger: DadosferaLogger;
redirectUrl: string;
constructor(
@Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger,
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')
@HttpCode(HttpStatus.OK)
async signIn(
@Body() { username, password, totp }: AuthSignInReq,
@Language() language: LanguageEnum,
): Promise<AuthSignInRes> {
this.logger.info('/auth - SignIn');
const metadata = PackTheMetadata({ language });
this.logger.info('metadata: ' + JSON.stringify(metadata.toJSON()));
return this.authClient.signIn({ username, password, totp }, metadata);
}
@Post('refresh-access-token')
@HttpCode(HttpStatus.OK)
@ApiOkResponse({ type: AuthRefreshAccessTokenRes })
async refreshAccessToken(
@Body() body: AuthRefreshAccessTokenReq,
@Language() language: LanguageEnum,
@Headers('origin') origin: string,
) {
this.logger.info('/auth - RefreshAccessToken');
const { refreshToken, userId } = body;
const frontHost = origin.replace(/^https?:\/\//, '');
const metadata = PackTheMetadata({
language,
custom_host: frontHost,
});
return this.authClient.refreshAccessToken({ refreshToken, userId }, metadata);
}
@ApiInternalOnlyEndpoint()
@Post('/sso/snowflake')
@RequireAllPermissions(PERMISSIONS_GROUPS.SNOWFLAKE.permissions.OPEN)
@HttpCode(HttpStatus.OK)
async snowflakeSignIn(
@User() user: RequestUser,
@Body('RelayState') relayState: string,
) {
this.logger.info('/auth - snowflakeSignIn');
return this.authClient.snowflakeSignIn({
userId: user.user_id,
relayState,
});
}
@ApiInternalOnlyEndpoint()
@Post('change-password')
@HttpCode(HttpStatus.OK)
async changePassword(
@Body() body: AuthChangePasswordRequest,
@Headers() headers,
) {
this.logger.info('/auth - change-password');
const { oldPassword, newPassword } = body;
const { authorization: accessToken } = headers;
return this.authClient.changePassword({
accessToken,
oldPassword,
newPassword,
});
}
@ApiInternalOnlyEndpoint()
@Post('reset-password')
@HttpCode(HttpStatus.OK)
async resetPassword(
@Body() body: AuthResetPasswordRequest,
@Language() language: LanguageEnum,
) {
this.logger.info('/auth - reset-password');
const metadata = PackTheMetadata({
language: language,
});
const { username } = body;
return this.authClient.resetPassword({ username }, metadata);
}
@ApiInternalOnlyEndpoint()
@Post('verify-reset-password-code')
@HttpCode(HttpStatus.OK)
async verifyResetPasswordCode(
@Body() body: AuthVerifyResetPasswordCodeRequest,
) {
this.logger.info('/auth - verify-reset-password-code');
const { username, code } = body;
return this.authClient.verifyResetPasswordCode({ username, code });
}
@ApiInternalOnlyEndpoint()
@Post('confirm-reset-password')
@HttpCode(HttpStatus.OK)
async confirmResetPassword(
@Body() body: AuthConfirmResetPasswordRequest,
@Headers('origin') origin: string,
) {
this.logger.info('/auth - confirm-reset-password');
const frontHost = origin.replace(/^https?:\/\//, '');
const metadata = PackTheMetadata({ custom_host: frontHost });
const { username, code, newPassword } = body;
return this.authClient.confirmResetPassword(
{
username,
code,
newPassword,
},
metadata,
);
}
@ApiInternalOnlyEndpoint()
@Post('enable-totp')
@HttpCode(HttpStatus.OK)
async enableTotpMFA(
@Body() body: AuthEnableTotpMfaRequest,
@Headers() headers,
) {
this.logger.info('/auth - enable-totp');
const { password } = body;
const { authorization: accessToken } = headers;
return this.authClient.enableTotpMFA({ accessToken, password });
}
@ApiInternalOnlyEndpoint()
@Post('disable-totp')
@HttpCode(HttpStatus.OK)
async disableTotpMFA(
@Body() body: AuthDisableTotpMfaRequest,
@Headers() headers,
) {
this.logger.info('/auth - disable-totp');
const { password } = body;
const { authorization: accessToken } = headers;
return this.authClient.disableTotpMFA({ accessToken, password });
}
@ApiInternalOnlyEndpoint()
@Post('dismiss-totp')
@HttpCode(HttpStatus.OK)
async dismissTotpMFA(@Headers() headers) {
this.logger.info('/auth - disable-totp');
const { authorization: accessToken } = headers;
return this.authClient.dismissTotpMFA({ accessToken });
}
@ApiInternalOnlyEndpoint()
@Post('verify-totp')
@HttpCode(HttpStatus.OK)
async verifyTotp(@Body() body: AuthVerifyTotpMfaRequest, @Headers() headers) {
this.logger.info('/auth - enable-totp');
const { totp } = body;
const { authorization: accessToken } = headers;
return this.authClient.verifyTotp({ accessToken, totp });
}
@ApiInternalOnlyEndpoint()
@Authenticated()
@ApiSecurity('access-token')
@Get('verify-access-token')
verifyAccessToken() {
return { access_token_status: 'valid' };
}
@ApiInternalOnlyEndpoint()
@Get('session/:session')
getSession(@Param('session') session: string) {
return this.authClient.getSession(session);
}
@ApiInternalOnlyEndpoint()
@Get('oauth/google')
@UseGuards(AuthGuard('google-login'))
googleOauth() {
this.logger.info('/oauth/google');
return true;
}
@ApiInternalOnlyEndpoint()
@Get('oauth/google/callback')
@UseGuards(AuthGuard('google-login'))
@Redirect()
async googleOauthCallback(@Req() req) {
const { url, email, token, language = 'pt-br' } = await this.callback(req);
if (url.searchParams.get('error')) {
this.logger.error('/oauth/google - ERROR');
return { url: url.href };
}
await this.authClient
.oauthSignIn({
username: email,
token,
})
.then(({ session }) => {
this.logger.info('/oauth/google - SUCESS');
url.searchParams.set('session', session);
})
.catch((err) => {
this.logger.error('/oauth/google - ERROR');
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 };
}
@ApiInternalOnlyEndpoint()
@Post('users/block')
@RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN)
@HttpCode(HttpStatus.OK)
async blockUsers(
@Language() language: LanguageEnum,
@User() user: RequestUser,
@Body() body: BulkEditRequest,
) {
this.logger.info('blockUsers - Starting request');
try {
const metadata = PackTheMetadata(user);
this.logger.debug('Calling blockUsers service', {
metadata: {
access_token: metadata.get('access_token'),
language: metadata.get('language'),
},
});
const result = await this.authClient.blockUsers(body.users, metadata);
this.logger.info('blockUsers - Success', { result });
return result;
} catch (error) {
this.logger.error('blockUsers - Error', {
error: error.message,
stack: error.stack,
});
throw error;
}
}
@ApiInternalOnlyEndpoint()
@Post('users/unblock')
@RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN)
@HttpCode(HttpStatus.OK)
async unblockUsers(
@Language() language: LanguageEnum,
@User() user: RequestUser,
@Body() body: BulkEditRequest,
) {
this.logger.info('unblockUsers');
const metadata = PackTheMetadata(user);
return this.authClient.unblockUsers(body.users, metadata);
}
@ApiInternalOnlyEndpoint()
@Post('users/reset')
@RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN)
@HttpCode(HttpStatus.OK)
async resetUsers(
@Language() language: LanguageEnum,
@User() user: RequestUser,
@Body() body: BulkEditRequest,
) {
this.logger.info('resetUsers');
const metadata = PackTheMetadata(user);
return this.authClient.resetUsers(body.users, metadata);
}
}