feat: add auth proxy controller for login and token exchange

Adds POST /auth/login and POST /auth/tokens endpoints that proxy
GraphQL mutations to the fortytwo-eap-core platform, letting the
frontend use only the sidecar URL for authentication.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-17 09:08:07 +05:30
parent 702afabfa7
commit 3e0d9a4351
3 changed files with 96 additions and 0 deletions

View File

@@ -2,8 +2,10 @@ import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import configuration from './config/configuration';
import { AiModule } from './ai/ai.module';
import { AuthModule } from './auth/auth.module';
import { PlatformModule } from './platform/platform.module';
import { ExotelModule } from './exotel/exotel.module';
import { CallEventsModule } from './call-events/call-events.module';
@Module({
imports: [
@@ -12,8 +14,10 @@ import { ExotelModule } from './exotel/exotel.module';
isGlobal: true,
}),
AiModule,
AuthModule,
PlatformModule,
ExotelModule,
CallEventsModule,
],
})
export class AppModule {}

View File

@@ -0,0 +1,85 @@
import { Controller, Post, Body, Logger, HttpException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import axios from 'axios';
@Controller('auth')
export class AuthController {
private readonly logger = new Logger(AuthController.name);
private readonly graphqlUrl: string;
constructor(private config: ConfigService) {
this.graphqlUrl = config.get<string>('platform.graphqlUrl')!;
}
@Post('login')
async login(@Body() body: { email: string; password: string }) {
this.logger.log(`Login attempt for ${body.email}`);
try {
const response = await axios.post(this.graphqlUrl, {
query: `mutation GetLoginToken($email: String!, $password: String!) {
getLoginTokenFromCredentials(
email: $email
password: $password
origin: "http://localhost:5173"
) {
loginToken { token }
}
}`,
variables: { email: body.email, password: body.password },
}, {
headers: { 'Content-Type': 'application/json' },
});
if (response.data.errors) {
throw new HttpException(
response.data.errors[0]?.message ?? 'Login failed',
401,
);
}
return response.data.data.getLoginTokenFromCredentials;
} catch (error) {
if (error instanceof HttpException) throw error;
this.logger.error(`Login proxy failed: ${error}`);
throw new HttpException('Authentication service unavailable', 503);
}
}
@Post('tokens')
async getTokens(@Body() body: { loginToken: string }) {
this.logger.log('Exchanging login token for access token');
try {
const response = await axios.post(this.graphqlUrl, {
query: `mutation GetAuthTokens($loginToken: String!) {
getAuthTokensFromLoginToken(
loginToken: $loginToken
origin: "http://localhost:5173"
) {
tokens {
accessOrWorkspaceAgnosticToken { token }
refreshToken { token }
}
}
}`,
variables: { loginToken: body.loginToken },
}, {
headers: { 'Content-Type': 'application/json' },
});
if (response.data.errors) {
throw new HttpException(
response.data.errors[0]?.message ?? 'Token exchange failed',
401,
);
}
return response.data.data.getAuthTokensFromLoginToken;
} catch (error) {
if (error instanceof HttpException) throw error;
this.logger.error(`Token exchange proxy failed: ${error}`);
throw new HttpException('Authentication service unavailable', 503);
}
}
}

7
src/auth/auth.module.ts Normal file
View File

@@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
@Module({
controllers: [AuthController],
})
export class AuthModule {}