omnichannel-backend/src/modules/auth/providers/microsoft-oauth.provider.ts

120 lines
3.6 KiB
TypeScript
Raw Normal View History

import {
BadRequestException,
ForbiddenException,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { AuthConfigService } from '../auth.config';
import { AuthTokenService } from '../auth-token.service';
import { AuthResult } from '../auth.types';
import { OAuthStateService } from './oauth-state.service';
const MICROSOFT_SCOPE = 'openid profile email User.Read';
@Injectable()
export class MicrosoftOAuthProvider {
constructor(
private readonly authConfig: AuthConfigService,
private readonly authToken: AuthTokenService,
private readonly oauthState: OAuthStateService,
) {}
getAuthorizeUrl() {
const config = this.authConfig.getConfig();
this.assertMicrosoftConfig();
const params = new URLSearchParams({
client_id: config.microsoft.clientId!,
response_type: 'code',
redirect_uri: config.microsoft.redirectUri!,
response_mode: 'query',
scope: MICROSOFT_SCOPE,
state: this.oauthState.createSignedState(),
});
return `https://login.microsoftonline.com/${config.microsoft.tenantId}/oauth2/v2.0/authorize?${params.toString()}`;
}
async authenticateCallback(query: { code?: string; state?: string }): Promise<AuthResult> {
if (!query.code || !query.state || !this.oauthState.verifySignedState(query.state)) {
throw new BadRequestException('Callback Microsoft invalido');
}
const tokenResponse = await this.exchangeCode(query.code);
const microsoftUser = await this.getMicrosoftUser(tokenResponse.access_token);
const email = microsoftUser.mail || microsoftUser.userPrincipalName;
const user = {
id: microsoftUser.id || email,
name: microsoftUser.displayName || email,
email,
username: microsoftUser.userPrincipalName || email,
provider: 'microsoft' as const,
};
return {
token: this.authToken.issueToken(user),
user,
};
}
private assertMicrosoftConfig() {
const config = this.authConfig.getConfig();
if (!config.microsoft.enabled) {
throw new ForbiddenException('Login Microsoft desabilitado');
}
const missing = [
['MICROSOFT_CLIENT_ID', config.microsoft.clientId],
['MICROSOFT_CLIENT_SECRET', config.microsoft.clientSecret],
['MICROSOFT_REDIRECT_URI', config.microsoft.redirectUri],
].filter(([, value]) => !value);
if (missing.length) {
throw new Error(`${missing.map(([name]) => name).join(', ')} nao configurado`);
}
}
private async exchangeCode(code: string) {
const config = this.authConfig.getConfig();
this.assertMicrosoftConfig();
const tokenUrl = `https://login.microsoftonline.com/${config.microsoft.tenantId}/oauth2/v2.0/token`;
const body = new URLSearchParams({
client_id: config.microsoft.clientId!,
client_secret: config.microsoft.clientSecret!,
code,
grant_type: 'authorization_code',
redirect_uri: config.microsoft.redirectUri!,
scope: MICROSOFT_SCOPE,
});
const response = await fetch(tokenUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body,
});
if (!response.ok) {
throw new UnauthorizedException('Falha ao trocar codigo Microsoft por token');
}
return response.json();
}
private async getMicrosoftUser(accessToken: string) {
const response = await fetch('https://graph.microsoft.com/v1.0/me', {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
if (!response.ok) {
throw new UnauthorizedException('Falha ao consultar usuario Microsoft');
}
return response.json();
}
}